File: undo.txt

package info (click to toggle)
gaphor 0.17.2-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 4,552 kB
  • ctags: 3,629
  • sloc: python: 23,713; xml: 222; makefile: 112; sh: 1
file content (148 lines) | stat: -rw-r--r-- 4,633 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
UndoManager
===========

Fine grained undo: undo specific properties.

Add undo-operation, e.g. Item.request_update() should be executed as
part of an undo action. Such actions are normally called after the properties
have been set right though. This is not a problem for idle tasks, but for
directly executed tasks it is.

Should only need to save the originals, values calculated, e.g. during an
update shouldn't have to be calculated -> use undo-operations to trigger
updates.

To update:

  Handle:
    x, y (solvable)
    connectable (attr)
    visible (attr)
    movable (attr)
    connection status (solver?)

  Item:
    matrix
    canvas is managed from Canvas

    Element:
      handles
      width, height
      min_width, min_height (solvable?)

    Line:
      handles
      line_width
      fuzzyness (attr)
      orthogonal (boolean)
      horizontal (boolean)

  Canvas:
    tree:
      add()
      remove()

    request_update() (should be performed as part of undo action when called)

  Solver (?):
    add_constraint()
    remove_constraint()
    Variable state

In Gaphor, connecting two diagram items is considered an atomic task,
performed by a IConnect adapter. This operation results in a set of primitive
tasks (properties set and a constraint created).

For methods, it should be possible to create a decorator (@reversible) that
schedules a method with the same signature as the calling operation, but with
the inverse effect (e.g. the gaphas.tree module)::

  class Tree(object):

    @reversable(lambda s, n, p: s.remove(n))
    def add(self, node, parent=None):
        ... add 

    @reversable(add, self='self', node='node', parent='self.get_parent(node)')
    def remove(self, node):
        ... remove

Okay, so the second case is tougher...
 
 
So what we did:
Add a StateManager to gaphas. All changes are sent to the statemanager.
Gaphor should implement its own state manager.

  * all state changes can easily be recorded
  * fix it in one place
  * reusable throughout Gaphas and subtypes.


Transactions
------------

Gaphor's Undo manager works transactionally. Typically, methods can be 
decorated with @transactional and undo data is stored in the current
transaction. A new tx is created when none exists.

Although undo functionality is at the core of Gaphor (diagram items and
model elements have been adapted to provide proper undo information), the 
UndoManager itself is just a service.

Transaction support though is a real core functionality. By letting elements
and items emit event notifications on important changed other (yet to be
defined) services can take benefit of those events. The UML module already
works this way. Gaphas (the Gaphor canvas) also emits state changes. 

When state changes happen in model elements and diagram items an event is
emitted. Those events are handled by special handlers that produce
"reverse-events". Reverse events are functions that perform exactly the
opposite operation. Those functions are stored in a list (which technically is
the Transaction). When an undo request is done the list is executed in LIFO
order.

To start a transaction:

1. A Transaction object has been created.
2. This results in the emission of a TransactionBegin event.
3. The TransactionBegin event is a trigger for the UndoManager to start
   listening for IUndoEvent actions.

Now, that should be done when a model element or diagram item sends a state
change:

1. The event is handled by the "reverse-handler"
2. Reverse handler generates a IUndoEvent signal 
3. The signal is received and stored as part of the undo-transaction.

(Maybe step 2 and 3 can be merged, since only one function is not of any
interest to the rest of the application - creates nasty dependencies)

If nested transaction are created a simple counter is incremented.

When the topmost Transaction is committed:

1. A TransactionCommit event is emitted
2. This triggers the UndoManager to close and store the transaction.

When a transaction is rolled back:
 
1. The main transaction is marked for rollback
2. When the toplevel tx is rolled back or committed a
   TransactionRollback event is emitted
3. This triggers the UndoManager to play back all recorded actions and
   stop listening.


References
----------

A Framework for Undoing Actions in Collaborative Systems
  http://www.eecs.umich.edu/~aprakash/papers/undo-tochi94.pdf
Undoing Actions in Collaborative Work: Framework and Experience
  https://www.eecs.umich.edu/techreports/cse/94/CSE-TR-196-94.pdf
Implementing a Selective Undo Framework in Python
  http://www.python.org/workshops/1997-10/proceedings/zukowski.html