Reid Ellis wrote:
On Tue Jan 3 2006, at 14:22, John Anderson wrote:
Alec Flett wrote:
John Anderson wrote:
I was hoping, someday, to have the commit point be the unit of
undo, e.g. if you did a delete, you'd commit after the operation.
Of course this requires the ability to roll back changes to
previous commit points, which doesn't yet exist.
Personally, I think that trying to tie Undo transactions to
repository transactions is going to land us in a heap of trouble.
I'm all for thinking of Undo-able actions as a series of repository
changes, but there is a disconnect between what the user probably
considers "undoable" events and what the repository considers
uncommittable.
Most undo/redo mechanisms I've dealt with:
(a) are not tied too tightly to the data representation (using
database rollback as the only method of undo)
(b) do not act on UI changes (e.g. changing from week to day view)
Most applications I've worked on implemented undo this way. However, one
applicaiton that I worked on was tied to the repository. It was by far
the nicest because it didn't require and any extra work by the
programmer implementing a command, wasn't buggy and worked consistently
everywhere.
I would argue that undo/redo should be achieved by parcelling up the
code into "actions", similar to what Alec described, that know how to
change the state of the model in both directions, thus being undoable
and redoable. Based on what I've seen about how the repository is
tied to both UI actions and external actions (like new mail), I do
not think it would be a good idea to tie the undo architecture to the
repository. Rather, they would make additional repository changes in
order to achieve their required state change.
I don't really care about whether it's implemented as roll back or
playing some repository change log forward, all I care about is that we
automate the work necessary to keep track of changes so that I don't
need to write any special code when I implement a command -- except
perhaps for saying where the undo points are and what the name of the
command is.
Perhaps as an optimization, they could check for external changes,
and if there are none, then use the repository's rollback mechanism,
but even that seems difficult, based on what Andi said about not
being able to discard changes (or is that something that is coming
soon?). Regardless, it goes out the window if something else happens
like getting mail in the middle of doing undo/redo.
As I mentioned earlier, when undoing we do need to merge in changes to
the repository from other threads, so, for example newly arrived mail
isn't lost when you undo.
Is the "rerender" mechanism actually just marking items as "dirty",
or is it forcing them to draw?
Rendering creates widgets and synchronizes them to the state of the
blocks (blocks persist and widgets don't).
Reid
Here are a few scenarios that I think would cause us trouble if we
were to tie undo points directly to calls to commit()/uncommit().
I'm using the term "undoable change" to be a user-comprehensible
change, such as deleting an event or changing data in the detail
view - one that the user would consider "undoable" by selecting
Edit->Undo. I'm using the term "uncommit" to refer to playing back
a series of changes in reverse order - this may or may not mean
simply rolling back, as you'll see below.
Scenario 1: an undoable change may actually span multiple commits
A user makes an undoable change that causes commit() to be called
three times just because of the codepath it follows happens to have
an extra commit() or two.. (maybe it goes through sharing and that
causes some repository view manipulation) Now any sort of "Undo"
command would have to be run three times to uncommit that single
user action
In past systems that used a mechanism like I'm proposing, you can
avoid this problem quite easily. Organize your code into operations
that do work, but don't commit. A command calls one or more routines
that the work then the command does a commit at the end (Actually,
to be more precise, each time a command was begun it commited the
last commands changes)
Scenario 2: A user manipulates the UI after making an undoable change
A user makes an undoable change, and then clicks on another
collection. Thanks to CPIA, changes in the UI are also changes in
the repository. This means that "Undo" operation might actually
just cause the user to switch back to their preview collection. It
would take a 2nd Undo operation to actually "undo" the original
change.
I'm not sure I understand this, but if you mean that undo moves the
user interface back to exactly what it was when you did the last
commit, yes that's the way it works and this is what you expect --
undo should get you back to exactly where you were, view included.
Scenario 3: Some changes might occur in different repository views
A user makes an undoable change, and that change may cause changes
in multiple views - i.e. sharing. What exactly do we "undo" when we
uncommit?
If I understand this, another variation of it is the following:
Suppose while you're doing some operation, new mail arrives, when
you undo you don't want to undo the getting of the new mail. This is
a real problem. One possible solution is to save information about
changes to the repository from other threads and when you undo (roll
back) replay (merge) those changes into the repository.
Personally, I think we need to be very explicit about undoable
changes. I think we need to bracket such changes with some sort of
begin/end undoable transaction mechanism. We can still exploit all
the benefits of the repository though - for instance:
Currently, there is no obvious rule about when to do a commit, so it
ends up being a little arbitrary. This would eliminate the any
confusion about when to do commit.
1) an undoable transaction might just be a pair of repository
version numbers - and we just play back all the changes backwards
from the newest version to the oldest. (i..e even if we're at
version 103, the previous undoable change might be from revision
100 to 101, so we'd just play the changes backwards between 101 and
100)
Yes, I agree something like this makes sense
2) notifications would fire when we play back the changes, so the
UI would (hopefully) stay up to date
To keep the UI up to date, all we need to do is unrender the UI,
roll back the repository, then rerender it (which also calls
synchronizeWiget)
3) perhaps we could even look ahead at the changes to be played
back, to see if an undo operation is even valid. (i.e. if it might
cause conflicts or something)
4) Redo support would be easy - even multiple redo support like in
Word/etc.
An explicit Undo mechanism gives us another advantage: we could
assign user-visible names to the undo actions.. and then we could
display that user-visible name in the edit menu, so it might say
"Undo Cut" instead of just "Undo"
In another system I used based on a begin/end mechanism like commit,
commit took an argument which was the name of the command, e.g.
"Cut", which was used to keep the menu up to date.
Here's an example of this system in action:
UndoManager.BeginTransaction(_(u"Cut"))
self.CutSomething()
UndoManger.EndTransaction()
If your BeginTransaction happened to call commit, I think we could
eliminate explicit commits and our proposals would be identical.
Maybe there's even a more pythonic way to handle this:
UndoManager.DoTransaction(_(u"Cut"), self.CutSomething)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Open Source Applications Foundation "Dev" mailing list
http://lists.osafoundation.org/mailman/listinfo/dev
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Open Source Applications Foundation "Dev" mailing list
http://lists.osafoundation.org/mailman/listinfo/dev