root/gaphas/tags/release-0.1.4/undo.txt

Revision 1174, 8.8 kB (checked in by arj..@yirdis.nl, 2 years ago)

do not raise exception when constraints are removed from solver more than once

Line 
1 Undo - implementing basic undo behavior with Gaphas
2 ===================================================
3
4 This document describes a basic undo system and tests Gaphas' classes with this
5 system.
6
7 This document contains a set of test cases that is used to prove that it really
8 works.
9
10 See state.txt about how state is recorded.
11
12 For this to work, some boilerplate has to be configured:
13
14     >>> from gaphas import state
15     >>> state.observers.clear()
16     >>> state.subscribers.clear()
17
18     >>> undo_list = []
19     >>> redo_list = []
20     >>> def undo_handler(event):
21     ...     undo_list.append(event)
22     >>> state.observers.add(state.revert_handler)
23     >>> state.subscribers.add(undo_handler)
24
25 This simple undo function will revert all states collected in the undo_list:
26
27     >>> def undo():
28     ...     apply_me = list(undo_list)
29     ...     del undo_list[:]
30     ...     apply_me.reverse()
31     ...     for e in apply_me:
32     ...         state.saveapply(*e)
33     ...     redo_list[:] = undo_list[:]
34     ...     del undo_list[:]
35
36 tree.py: Tree
37 -------------
38 Tree's add() and remove() methods are use as demonstration in state.txt.
39
40 Those methods are disabled by default and will not be tested here.
41
42 matrix.py: Matrix
43 -----------------
44 Matrix is used by Item classes.
45     invert, translate, rotate and scale
46
47     >>> from gaphas.matrix import Matrix
48     >>> m = Matrix()
49     >>> m
50     Matrix(1, 0, 0, 1, 0, 0)
51
52 translate(tx, ty):
53
54     >>> m.translate(12, 16)
55     >>> m
56     Matrix(1, 0, 0, 1, 12, 16)
57     >>> undo()
58     >>> m
59     Matrix(1, 0, 0, 1, 0, 0)
60
61 scale(sx, sy):
62
63     >>> m.scale(1.5, 1.5)
64     >>> m
65     Matrix(1.5, 0, 0, 1.5, 0, 0)
66     >>> undo()
67     >>> m
68     Matrix(1, 0, 0, 1, 0, 0)
69
70 rotate(radians):
71
72     >>> def matrix_approx(m):
73     ...     a = []
74     ...     for i in tuple(m):
75     ...         if -1e-10 < i < 1e-10: i=0
76     ...         a.append(i)
77     ...     return tuple(a)
78
79     >>> m.rotate(0.5)
80     >>> m
81     Matrix(0.877583, 0.479426, -0.479426, 0.877583, 0, 0)
82     >>> undo()
83     >>> matrix_approx(m)
84     (1.0, 0, 0, 1.0, 0, 0)
85
86 Okay, nearly, close enough IMHO...
87
88     >>> m = Matrix()
89     >>> m.translate(12, 10)
90     >>> m.scale(1.5, 1.5)
91     >>> m.rotate(0.5)
92     >>> m
93     Matrix(1.31637, 0.719138, -0.719138, 1.31637, 12, 10)
94     >>> m.invert()
95     >>> m
96     Matrix(0.585055, -0.319617, 0.319617, 0.585055, -10.2168, -2.01515)
97     >>> undo()
98     >>> matrix_approx(m)
99     (1.0, 0, 0, 1.0, 0, 0)
100
101 Again, rotate does not result in an exact match, but it's close enough.
102
103     >>> undo_list
104     []
105
106 canvas.py: Canvas
107 -----------------
108
109     >>> from gaphas import Canvas, Item
110     >>> canvas = Canvas()
111     >>> canvas.get_all_items()
112     []
113     >>> item = Item()
114     >>> canvas.add(item)
115
116 The request_update() method is observed:
117
118     >>> len(undo_list)
119     3
120     >>> canvas.request_update(item)
121     >>> len(undo_list)
122     4
123
124 On the canvas only add() and remove() are monitored:
125
126     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
127     [<gaphas.item.Item object at 0x...>]
128     >>> item.canvas is canvas
129     True
130     >>> undo()
131     >>> canvas.get_all_items()
132     []
133     >>> item.canvas is None
134     True
135     >>> canvas.add(item)
136     >>> del undo_list[:]
137     >>> canvas.remove(item)
138     >>> canvas.get_all_items()
139     []
140     >>> undo()
141     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
142     [<gaphas.item.Item object at 0x...>]
143     >>> undo_list
144     []
145
146 Parent-child relationships are restored as well:
147
148     TODO!
149     >>> child = Item()
150     >>> canvas.add(child, parent=item)
151     >>> child.canvas is canvas
152     True
153     >>> canvas.get_parent(child) is item
154     True
155     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
156     [<gaphas.item.Item object at 0x...>, <gaphas.item.Item object at 0x...>]
157     >>> undo()
158     >>> child.canvas is None
159     True
160     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
161     [<gaphas.item.Item object at 0x...>]
162     >>> child in canvas.get_all_items()
163     False
164
165 Now redo the previous undo action:
166
167     >>> undo_list[:] = redo_list[:]
168     >>> undo()
169     >>> child.canvas is canvas
170     True
171     >>> canvas.get_parent(child) is item
172     True
173     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
174     [<gaphas.item.Item object at 0x...>, <gaphas.item.Item object at 0x...>]
175
176
177 NOTE: If remove() is invoked recursively (e.i. when the to-be removed item has
178       children), the remove items can not simply be applied in reverse order.
179
180     >>> child = Item()
181     >>> canvas.add(child, parent=item)
182     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
183     [<gaphas.item.Item object at 0x...>, <gaphas.item.Item object at 0x...>]
184     >>> del undo_list[:]
185     >>> canvas.remove(item)
186     >>> canvas.get_all_items()
187     []
188
189
190 item.py: Handle
191 ---------------
192      x, y, connectable, movable, visible, connected_to and disconnect properties
193 Changing the Handle's position is reversible:
194
195     >>> from gaphas import Handle
196     >>> handle = Handle()
197     >>> handle.x, handle.y = 10, 12
198     >>> handle.pos
199     (Variable(10, 20), Variable(12, 20))
200     >>> undo()
201     >>> handle.pos
202     (Variable(0, 20), Variable(0, 20))
203
204 As are all other properties:
205
206     >>> handle.connectable, handle.movable, handle.visible, handle.connected_to
207     (False, True, True, None)
208     >>> handle.disconnect                               # doctest: +ELLIPSIS
209     <function <lambda> at 0x...>
210     >>> handle.connectable = True
211     >>> handle.movable = False
212     >>> handle.visible = False
213     >>> handle.connected_to = item
214     >>> def my_fancy_disconnect(): pass
215     >>> handle.disconnect = my_fancy_disconnect
216     >>> handle.connectable, handle.movable, handle.visible
217     (True, False, False)
218     >>> handle.connected_to                             # doctest: +ELLIPSIS
219     <gaphas.item.Item object at 0x...>
220     >>> handle.disconnect                               # doctest: +ELLIPSIS
221     <function my_fancy_disconnect at 0x...>
222
223 And now undo the whole lot at once:
224
225     >>> undo()
226     >>> handle.connectable, handle.movable, handle.visible, handle.connected_to
227     (False, True, True, None)
228     >>> handle.disconnect                               # doctest: +ELLIPSIS
229     <function <lambda> at 0x...>
230
231 item.py: Item
232 -------------
233
234 The basic Item properties are canvas and matrix. Canvas has been tested before,
235 while testing the Canvas class.
236
237 The Matrix has been tested in section matrix.y: Matrix.
238
239 item.py: Element
240 ----------------
241
242 An element has min_height and min_width properties.
243
244     >>> from gaphas import Element
245     >>> e = Element()
246     >>> e.min_height, e.min_width
247     (10, 10)
248     >>> e.min_height, e.min_width = 30, 40
249     >>> e.min_height, e.min_width
250     (30, 40)
251
252     >>> undo()
253     >>> e.min_height, e.min_width
254     (10, 10)
255    
256     >>> canvas = Canvas()
257     >>> e.canvas = canvas
258     >>> undo()
259     >>> e.canvas
260
261 item.py: Line
262 -------------
263
264 A line has the following properties: line_width, fuzziness, orthogonal and
265 horizontal. Each one of then is observed for changes:
266
267     >>> from gaphas import Line
268     >>> l = Line()
269     >>> l.line_width, l.fuzziness, l.orthogonal, l.horizontal
270     (2, 0, False, False)
271
272 Now change the properties:
273
274     >>> l.line_width = 4
275     >>> l.fuzziness = 2
276     >>> l.orthogonal = True
277     >>> l.horizontal = True
278     >>> l.line_width, l.fuzziness, l.orthogonal, l.horizontal
279     (4, 2, True, True)
280
281 And undo the changes:
282
283     >>> undo()
284     >>> l.line_width, l.fuzziness, l.orthogonal, l.horizontal
285     (2, 0, False, False)
286
287 In addition to those properties, line segments can be split and merged.
288
289     >>> l.handles()[1].pos = 10, 10
290     >>> l.handles()
291     [<Handle object on (0, 0)>, <Handle object on (10, 10)>]
292
293 This is our basis for further testing.
294
295     >>> del undo_list[:]
296
297     >>> l.split_segment(0)
298     >>> l.handles()
299     [<Handle object on (0, 0)>, <Handle object on (5, 5)>, <Handle object on (10, 10)>]
300
301 The opposite operation is performed with the merge_segment() method:
302
303     >>> undo()
304     >>> l.handles()
305     [<Handle object on (0, 0)>, <Handle object on (10, 10)>]
306
307 solver.py: Variable
308 -------------------
309
310 Variable's strength and value properties are observed:
311
312     >>> from gaphas.solver import Variable
313     >>> v = Variable()
314     >>> v.value = 10
315     >>> v.strength = 100
316     >>> v
317     Variable(10, 100)
318     >>> undo()
319     >>> v
320     Variable(0, 20)
321
322 solver.py: Solver
323 -----------------
324
325 Solvers add_constraint() and remove_constraint() are observed.
326
327     >>> from gaphas.solver import Solver
328     >>> from gaphas.constraint import EquationConstraint
329     >>> s = Solver()
330     >>> a, b = Variable(1.0), Variable(2.0)
331     >>> s.add_constraint(EquationConstraint(lambda a,b: a+b, a=a, b=b))
332     EquationConstraint(<lambda>, a=Variable(1, 20), b=Variable(2, 20))
333     >>> list(s.constraints_with_variable(a))
334     [EquationConstraint(<lambda>, a=Variable(1, 20), b=Variable(2, 20))]
335    
336     >>> undo()
337     >>> list(s.constraints_with_variable(a))
338     []
339
Note: See TracBrowser for help on using the browser.