Forward-reverse Framework¶
The forward-reverse framework is the simplest way to implement undo.
The “forward” function is the “do” operation. Any function can be considered as a forward function. The “reverse” function is a conjugative function to the forward one and carries out the opposite operation.
When a UndoManager undoes a command, it passes all the arguments of forward
call to the reverse function. For instance, if you called f(10) and the
reverse function for f is g, then the undo operation will be g(10).
Thus, you have to make sure that calling reverse function after the forward
function will restore the original state.
Here’s a simple example. Defining a forward-reverse set is very similar to defining
getter and setter of a Python property.
from collections_undo import UndoManager
mgr = UndoManager() # prepare undo manager
@mgr.undoable # decorate any functions you want
def f(a):
print("do", a)
@f.undo_def # define a reverse function
def f(a):
print("undo", a)
Function f can be used as usual. To undo or redo the action, call undo()
and redo() from the UndoManager instance.
>>> f(10)
do 10
>>> mgr.undo()
undo 10
>>> mgr.undo() # nothing happens
>>> mgr.redo()
do 10
>>> mgr.redo() # nothing happens
Undo implementation for a custom class¶
UndoManager can be used as a field-like object of a class. This is the best way
to define object-specific undo managers.
Following example shows how to make the attribute x undoable.
from collections_undo import UndoManager
class A:
mgr = UndoManager()
def __init__(self):
self.x = 0
def set_value(self, x):
return self._set_value(x, self.x)
@mgr.undoable
def _set_value(self, x, x_old):
self.x = x
@_set_value.undo_def
def _set_value(self, x, x_old):
self.x = x_old
Note that to set an attribute in an undoable way, you have to pass the old value to the forward function because it is needed for the reverse function.
Note
If you feel this is too complicated, it’s totally OK. That’s why collections-undo
has other frameworks. See Server-receiver Framework and Property-like Framework for the better way to
do this.
Class A works like this.
>>> a = A()
>>> a.set_value(10)
>>> a.x
10
>>> a.mgr.undo()
>>> a.x
0
>>> a.mgr.redo()
>>> a.x
10