Reid Ellis wrote:
(a) are not tied too tightly to the data
representation (using database rollback as the only method of undo)
I should perhaps offer a little background: The repository is in fact
flexible enough, and tied closely enough to the python language, that
pretty much any action that happens in the application at runtime is
ultimately reflected in the repository as data changing. This means
that many problems that would often be solved at the application level
can be pushed down all the way to the repository.
Now that said, we obviously have to be judicious with this pattern. It
doesn't work everywhere.
However Undo is actually a good example of where we can use the
repository to our advantage: Any "undoable action" ultimately boils
down to a set of attribute changes on objects in the repository. Since
those changes are journaled, it means we have a programmatic, dynamic
way of playing, replaying, or playing backwards, any particular set of
actions... in other words, "undoing" and redoing.
Given this perspective, I think the hard part is really determining
WHICH set of changes are considered the "undoable" action so that we
can undo only exactly what is appropriate.
Alec
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
|