| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
UndoManager |
|---|
| 4 |
========== |
|---|
| 5 |
|
|---|
| 6 |
Fine grained undo: undo specific properties. |
|---|
| 7 |
|
|---|
| 8 |
Add undo-operation, e.g. Item.request_update() should be executed as |
|---|
| 9 |
part of an undo action. Such actions are normally called after the properties |
|---|
| 10 |
have been set right though. This is not a problem for idle tasks, but for |
|---|
| 11 |
directly executed tasks it is. |
|---|
| 12 |
|
|---|
| 13 |
Should only need to save the originals, values calculated, e.g. during an |
|---|
| 14 |
update shouldn't have to be calculated -> use undo-operations to trigger |
|---|
| 15 |
updates. |
|---|
| 16 |
|
|---|
| 17 |
To update: |
|---|
| 18 |
|
|---|
| 19 |
Handle: |
|---|
| 20 |
x, y (solvable) |
|---|
| 21 |
connectable (attr) |
|---|
| 22 |
visible (attr) |
|---|
| 23 |
movable (attr) |
|---|
| 24 |
connection status (solver?) |
|---|
| 25 |
|
|---|
| 26 |
Item: |
|---|
| 27 |
matrix |
|---|
| 28 |
canvas is managed from Canvas |
|---|
| 29 |
|
|---|
| 30 |
Element: |
|---|
| 31 |
handles |
|---|
| 32 |
width, height |
|---|
| 33 |
min_width, min_height (solvable?) |
|---|
| 34 |
|
|---|
| 35 |
Line: |
|---|
| 36 |
handles |
|---|
| 37 |
line_width |
|---|
| 38 |
fuzzyness (attr) |
|---|
| 39 |
orthogonal (boolean) |
|---|
| 40 |
horizontal (boolean) |
|---|
| 41 |
split_segment() |
|---|
| 42 |
merge_segment() |
|---|
| 43 |
|
|---|
| 44 |
Canvas: |
|---|
| 45 |
tree: |
|---|
| 46 |
add() |
|---|
| 47 |
remove() |
|---|
| 48 |
request_update() (should be performed as part of undo action when called) |
|---|
| 49 |
|
|---|
| 50 |
Solver (?): |
|---|
| 51 |
add_constraint() |
|---|
| 52 |
remove_constraint() |
|---|
| 53 |
Variable state |
|---|
| 54 |
|
|---|
| 55 |
In Gaphor, connecting two diagram items is considered an atomic task, |
|---|
| 56 |
performed by a IConnect adapter. This operation results in a set of primitive |
|---|
| 57 |
tasks (properties set and a constraint created). |
|---|
| 58 |
|
|---|
| 59 |
For methods, it should be possible to create a decorator (@reversible) that |
|---|
| 60 |
schedules a method with the same signature as the calling operation, but with |
|---|
| 61 |
the inverse effect (e.g. the gaphas.tree module): |
|---|
| 62 |
|
|---|
| 63 |
Tree(object): |
|---|
| 64 |
|
|---|
| 65 |
@reversable(lambda s, n, p: s.remove(n)) |
|---|
| 66 |
def add(self, node, parent=None): |
|---|
| 67 |
... add |
|---|
| 68 |
|
|---|
| 69 |
@reversable(add, self='self', node='node', parent='self.get_parent(node)') |
|---|
| 70 |
def remove(self, node): |
|---|
| 71 |
... remove |
|---|
| 72 |
|
|---|
| 73 |
Okay, so the second case is tougher... |
|---|
| 74 |
|
|---|
| 75 |
|
|---|
| 76 |
So what we did: |
|---|
| 77 |
Add a StateManager to gaphas. All changed are send to the statemanager. |
|---|
| 78 |
Gaphor should implement it's own state manager. |
|---|
| 79 |
+ all state changes can easily be recorded |
|---|
| 80 |
+ fix it in one place |
|---|
| 81 |
+ reusable throughout Gaphas and subtypes. |
|---|
| 82 |
|
|---|
| 83 |
|
|---|
| 84 |
Transactions |
|---|
| 85 |
============ |
|---|
| 86 |
|
|---|
| 87 |
Gaphor's Undo manager works transactional. Typically, methods can be |
|---|
| 88 |
decorated with @transactional and undo data is stored in the current |
|---|
| 89 |
transaction. A new tx is created when none exists. |
|---|
| 90 |
|
|---|
| 91 |
Although undo functionality is at the core of Gaphor (diagram items and |
|---|
| 92 |
model elements have been adapted to provide proper undo information), the |
|---|
| 93 |
UndoManager itself is just a service. |
|---|
| 94 |
|
|---|
| 95 |
Transaction support though is a real core functionality. By letting elements |
|---|
| 96 |
and items emit event notifications on important changed other (yet to be |
|---|
| 97 |
defined) services can take benefit of those events. The UML module already |
|---|
| 98 |
works this way. Gaphas (the Gaphor canvas) also emits state changes. |
|---|
| 99 |
|
|---|
| 100 |
When state changes happen in model elements and diagram items an event is |
|---|
| 101 |
emitted. Those events are handled by special handlers that produce |
|---|
| 102 |
"reverse-events". Reverse events are functions that perform exactly the |
|---|
| 103 |
opposite operation. Those functions are stored in a list (which technically is |
|---|
| 104 |
the Transaction). When an undo request is done the list is executed in LIFO |
|---|
| 105 |
order. |
|---|
| 106 |
|
|---|
| 107 |
To start a transaction: |
|---|
| 108 |
|
|---|
| 109 |
1. A Transaction object has been created. |
|---|
| 110 |
2. This results in the emission of a TransactionBegin event. |
|---|
| 111 |
3. The TransactionBegin event is a trigger for the UndoManager to start |
|---|
| 112 |
listening for IUndoEvent actions. |
|---|
| 113 |
|
|---|
| 114 |
Now, that should be done when a model element or diagram item sends a state |
|---|
| 115 |
change: |
|---|
| 116 |
|
|---|
| 117 |
1. The event is handled by the "reverse-handler" |
|---|
| 118 |
2. Reverse handler generates a IUndoEvent signal |
|---|
| 119 |
3. The signal is received and stored as part of the undo-transaction. |
|---|
| 120 |
|
|---|
| 121 |
(Maybe step 2 and 3 can be merged, since only one function is not of any |
|---|
| 122 |
interest to the rest of the application - creates nasty dependencies) |
|---|
| 123 |
|
|---|
| 124 |
If nested transaction are created a simple counter is incremented. |
|---|
| 125 |
|
|---|
| 126 |
When the topmost Transaction is committed: |
|---|
| 127 |
|
|---|
| 128 |
1. A TransactionCommit event is emitted |
|---|
| 129 |
2. This triggers the UndoManager to close and store the transaction. |
|---|
| 130 |
|
|---|
| 131 |
When a transaction is rolled back: |
|---|
| 132 |
|
|---|
| 133 |
1. The main transaction is marked for rollback |
|---|
| 134 |
2. When the toplevel tx is rolled back or commited a |
|---|
| 135 |
TransactionRollback event is emitted |
|---|
| 136 |
2. This triggers the UndoManager to play back all recorded actions and |
|---|
| 137 |
stop listening. |
|---|
| 138 |
|
|---|
| 139 |
|
|---|
| 140 |
References |
|---|
| 141 |
========== |
|---|
| 142 |
|
|---|
| 143 |
A Framework for Undoing Actions in Collaborative Systems |
|---|
| 144 |
http://www.eecs.umich.edu/~aprakash/papers/undo-tochi94.pdf |
|---|
| 145 |
Undoing Actions in Collaborative Work: Framework and Experience |
|---|
| 146 |
https://www.eecs.umich.edu/techreports/cse/94/CSE-TR-196-94.pdf |
|---|
| 147 |
|
|---|
| 148 |
Implementing a Selective Undo Framework in Python |
|---|
| 149 |
http://www.python.org/workshops/1997-10/proceedings/zukowski.html |
|---|
| 150 |
|
|---|
| 151 |
|
|---|