[Dev] Undo and commit()
John Anderson
john at osafoundation.org
Tue Jan 3 14:42:39 PST 2006
Alec Flett wrote:
> John Anderson wrote:
>
>>>
>> 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.
>
>
> John and I brainstormed a bit today in person on this. One of the
> things we talked about was how there are certain view things that you
> actually do want to change, if possible. For instance, if like the
> scenario #2 I described, you delete an event and then switch to
> another collection, you could argue that the undo should also switch
> you back to the previous collection. That way you could actually see
> that the event "came back" rather than just appearing in another
> collection that is out of view.
>
> this is an approach that we talked about:
>
> * An "undoable action" begins say from a key command or a menu.
> Some sort of BeginTransaction() command is called along with a
> user-visible name for the transaction. This BeginTransaction
> starts with a commit() (just to make sure the repository state
> is clean) and then stores some version information
> * since all commands begin with CPIA events of some kind, we could
> actually just build the BeginTransaction() call directly into
> the CPIA event code, along with the user-visible name of the command
> * when the "next" undoable action comes in, then it finishes up
> the previous transaction, allowing it to be undone. The
> advantage of this is that any subsequent actions, such as
> switching views, could be retained, and "undone" automatically.
>
> Now just as John and I came up with a view-specific action that we'd
> like to be able to "undo" (i.e. switching collections) - Grant came up
> with a view-specific action that we'd prefer NOT to "undo" (i.e.
> changing the window size/position)
In practice I don't think this is a big deal -- that fact that window
size don't participate in undo in other applicaitons is simply a side
effect of the mechanism they use to implement undo. If the window size
did participate in undo, I don't think anybody would really mind -- the
situation almost never happens -- and technically you could argue it's
more correct. So my vote is not to waste any code implementing it -- at
least not until it becomes clear that it's a real problem.
>
> Personally, I'm betting that there are more UI things we DON'T want to
> undo than those we DO want to undo.
>
> John and I both agreed that the actual mechanism for "undoing" was
> almost irrelevant, as long as it involves automatic attribute
> re-setting in the repository - i.e. rolling back or re-applying
> changes backwards.. its six of one, half dozen of the other.
>
> So at the moment, I think one decision we need to make is when exactly
> an undoable action "ends" It sounds like there are a few possibilities:
> 1) open-ended: Transactions begin when particular CPIA events fire,
> and stay "open" until the next transaction starts. The advantage here
> is that UI changes get automatically bundled with their previous
> action, allowing the UI to return to exactly the previous state when
> the command ran. This is also the disadvantage, since many UI changes
> shouldn't be undone.
> 2) explicitly closed: transactions end either when someone calls
> EndTransaction(), or automatically with the CPIA events mechanism. The
> advantage is that undoable transactions aren't going to "leak" by
> staying open - i.e. an "open" transaction wouldn't start to accumulate
> irrelevant changes
> 3) Some hybrid, where CPIA events Begin transactions, and certain
> types of CPIA events could also "end" open transactions without
> starting a new one...
>
> Alec
>
>
>>
>>> 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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.osafoundation.org/pipermail/dev/attachments/20060103/150239d7/attachment.htm
More information about the Dev
mailing list