[Dev] Undo and commit()

John Anderson john at osafoundation.org
Tue Jan 3 13:31:58 PST 2006



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



More information about the Dev mailing list