Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I'm making an application (wxPython) to process some data from Excel documents. I want the user to be able to undo and redo actions, even gigantic actions like processing the contents of 10 000 cells simultaneously. I Googled the topic, and all the solutions I could find involves a lot of black magic or is overly complicated.

Here is how I imagine my simple undo/redo scheme. I write two classes - one called ActionStack and an abstract one called Action. Every "undoable" operation must be a subclass of Action and define the methods do and undo. The Action subclass is passed the instance of the "document", or data model, and is responsible for committing the operation and remembering how to undo the change.

Now, every document is associated with an instance of the ActionStack. The ActionStack maintains a stack of actions (surprise!). Every time actions are undone and new actions are performed, all undone actions are removed for ever. The ActionStack will also automatically remove the oldest Action when the stack reaches the configurable maximum amount.

I imagine the workflow would produce code looking something like this:

class TableDocument(object):
    def __init__(self, table):
        self.table = table
        self.action_stack = ActionStack(history_limit=50)

    # ...

    def delete_cells(self, cells):
        self.action_stack.push(
            DeleteAction(self, cells)
        )

    def add_column(self, index, name=''):
        self.action_stack.push(
            AddColumnAction(self, index, name)
        )

    # ...

    def undo(self, count=1):
        self.action_stack.undo(count)

    def redo(self, count=1):
        self.action_stack.redo(count)

Given that none of the methods I've found are this simple, I thought I'd get the experts' opinion before I go ahead with this plan. More specifically, what I'm wondering about is - are there any glaring holes in this plan that I'm not seeing?

share|improve this question
    
You looked into the memento pattern? en.wikipedia.org/wiki/Memento_pattern –  KChaloux Nov 27 '12 at 21:45
    
No, you must use zippers! jk. en.wikipedia.org/wiki/Zipper_%28data_structure%29 –  Bradford Nov 27 '12 at 21:51
    
Make sure your actions are undoable. If this sounds too obvious, then make sure no action has irreversible effects. Not every action you can think of has a simple counter-action, you usually need to actually save state to restore things. For instance, how do you undo a delete? You could make it remember the deleted cells/columns/rows so the counter-action is to insert that data. How do you undo a calculation? You could calculate the range of affected cells and remember them for the counter-action, which is to reset those cells. –  acelent Nov 27 '12 at 21:59
add comment

1 Answer

Your approach is just an application of the classical command pattern, not specific for Python, and well known for implementing undo functionality. Google for "command pattern undo" to find examples, even small ones which are not overly complicated.

And yes, if you do it right, you can implement undo functionality that way. If it will be easy or hard to implement, depends much on the kind of Actions you are going to have, and the amount of data to process. In theory, before applying an action, you could save the whole state of your data, and reset to that state whenever you want to "undo". That will make it possible to implement your undo in a simple, generic way, without having the need of individual "undo" operations. However, if you have a huge amount of data to process, and you want a big undo stack, then this approach is often not feasible due to memory constraints. If that is your situation, a specific saving of the modified data and a corresponding "undo" implementation can be much more efficient, but only if you have lots of "small" actions to process which will allow such an optimization.

Further key points:

  • do you expect parallel Actions to be applied to the data, by multiple threads or processes or users? That can make the implementation of "undo" very hard.
  • do you expect Actions where you cannot foresee which part of your data will be changed? In this case, you will need a "save all" approach for that actions, which may or may not be a problem, see above.
share|improve this answer
    
It's a simple single threaded application. I assume that implementing undo can be done differently for each command, as long as the original state of the document is restored. F.inst. changing the contents of a single cell won't be too memory consuming. I suppose if some actions are way too memory consuming I'll just have to warn the user beforehand that the action will not be "undoable", then clear the stack. –  Codemonkey Nov 27 '12 at 22:07
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.