root/gaphas/tags/gaphas-0.3.2/undo.txt

Revision 1882, 10.5 kB (checked in by arj..@yirdis.nl, 1 year ago)

Line.split_segment and merge_segment now return a list of added/removed handles. also fixed some bugs in merge_segment.

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 .. contents::
13
14 For this to work, some boilerplate has to be configured:
15
16     >>> from gaphas import state
17     >>> state.observers.clear()
18     >>> state.subscribers.clear()
19
20     >>> undo_list = []
21     >>> redo_list = []
22     >>> def undo_handler(event):
23     ...     undo_list.append(event)
24     >>> state.observers.add(state.revert_handler)
25     >>> state.subscribers.add(undo_handler)
26
27 This simple undo function will revert all states collected in the undo_list:
28
29     >>> def undo():
30     ...     apply_me = list(undo_list)
31     ...     del undo_list[:]
32     ...     apply_me.reverse()
33     ...     for e in apply_me:
34     ...         state.saveapply(*e)
35     ...     redo_list[:] = undo_list[:]
36     ...     del undo_list[:]
37
38 tree.py: Tree
39 -------------
40 Tree's ``add()`` and ``remove()`` methods are disabled by default.
41
42     >>> from gaphas.tree import Tree
43     >>> state.enable_dispatching(Tree.add)
44     >>> state.enable_dispatching(Tree._remove)
45
46 One can create a nice tree:
47
48     >>> tree = Tree()
49     >>> tree.add(1)
50     >>> tree.nodes
51     [1]
52     >>> undo()
53     >>> tree.nodes
54     []
55     >>> undo_list[:] = redo_list[:]
56     >>> undo()
57     >>> tree.nodes
58     [1]
59    
60 Adding and removing complete sub-trees also works:
61
62     >>> tree.add(2, parent=1)
63     >>> tree.add(3, parent=2)
64     >>> tree.add(4, parent=2)
65     >>> tree.add(5, parent=4)
66     >>> tree.nodes
67     [1, 2, 3, 4, 5]
68     >>> del undo_list[:]
69     >>> tree.remove(1)
70     >>> tree.nodes
71     []
72     >>> undo()
73     >>> tree.nodes
74     [1, 2, 3, 4, 5]
75     >>> tree.get_children(2)
76     [3, 4]
77     >>> tree.get_children(4)
78     [5]
79
80 Undo the last undo action:
81
82     >>> undo_list[:] = redo_list[:]
83     >>> undo()
84     >>> tree.nodes
85     []
86     >>> undo_list[:] = redo_list[:]
87     >>> undo()
88     >>> tree.nodes
89     [1, 2, 3, 4, 5]
90     >>> tree.get_children(2)
91     [3, 4]
92     >>> tree.get_children(4)
93     [5]
94
95 Moving a node from one node to another is easy:
96
97     >>> tree.reparent(3, parent=5)
98     >>> tree.get_children(2)
99     [4]
100     >>> tree.get_children(5)
101     [3]
102     >>> tree.nodes
103     [1, 2, 4, 5, 3]
104
105 This change can be undone:
106
107     >>> undo()
108     >>> tree.nodes
109     [1, 2, 4, 5, 3]
110     >>> tree.get_children(2)
111     [4, 3]
112     >>> tree.get_children(5)
113     []
114
115 As you can see, there's currently one flaw: the item is added at the end of the list.
116
117 (disable dispatching again, not frustrating other tests)
118
119     >>> state.disable_dispatching(Tree.add)
120     >>> state.disable_dispatching(Tree._remove)
121
122 matrix.py: Matrix
123 -----------------
124 Matrix is used by Item classes.
125
126     >>> from gaphas.matrix import Matrix
127     >>> m = Matrix()
128     >>> m
129     Matrix(1, 0, 0, 1, 0, 0)
130
131 translate(tx, ty):
132
133     >>> m.translate(12, 16)
134     >>> m
135     Matrix(1, 0, 0, 1, 12, 16)
136     >>> undo()
137     >>> m
138     Matrix(1, 0, 0, 1, 0, 0)
139
140 scale(sx, sy):
141
142     >>> m.scale(1.5, 1.5)
143     >>> m
144     Matrix(1.5, 0, 0, 1.5, 0, 0)
145     >>> undo()
146     >>> m
147     Matrix(1, 0, 0, 1, 0, 0)
148
149 rotate(radians):
150
151     >>> def matrix_approx(m):
152     ...     a = []
153     ...     for i in tuple(m):
154     ...         if -1e-10 < i < 1e-10: i=0
155     ...         a.append(i)
156     ...     return tuple(a)
157
158     >>> m.rotate(0.5)
159     >>> m
160     Matrix(0.877583, 0.479426, -0.479426, 0.877583, 0, 0)
161     >>> undo()
162     >>> matrix_approx(m)
163     (1.0, 0, 0, 1.0, 0, 0)
164
165 Okay, nearly, close enough IMHO...
166
167     >>> m = Matrix()
168     >>> m.translate(12, 10)
169     >>> m.scale(1.5, 1.5)
170     >>> m.rotate(0.5)
171     >>> m
172     Matrix(1.31637, 0.719138, -0.719138, 1.31637, 12, 10)
173     >>> m.invert()
174     >>> m
175     Matrix(0.585055, -0.319617, 0.319617, 0.585055, -10.2168, -2.01515)
176     >>> undo()
177     >>> matrix_approx(m)
178     (1.0, 0, 0, 1.0, 0, 0)
179
180 Again, rotate does not result in an exact match, but it's close enough.
181
182     >>> undo_list
183     []
184
185 canvas.py: Canvas
186 -----------------
187
188     >>> from gaphas import Canvas, Item
189     >>> canvas = Canvas()
190     >>> canvas.get_all_items()
191     []
192     >>> item = Item()
193     >>> canvas.add(item)
194
195 The ``request_update()`` method is observed:
196
197     >>> len(undo_list)
198     3
199     >>> canvas.request_update(item)
200     >>> len(undo_list)
201     4
202
203 On the canvas only ``add()`` and ``remove()`` are monitored:
204
205     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
206     [<gaphas.item.Item object at 0x...>]
207     >>> item.canvas is canvas
208     True
209     >>> undo()
210     >>> canvas.get_all_items()
211     []
212     >>> item.canvas is None
213     True
214     >>> canvas.add(item)
215     >>> del undo_list[:]
216     >>> canvas.remove(item)
217     >>> canvas.get_all_items()
218     []
219     >>> undo()
220     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
221     [<gaphas.item.Item object at 0x...>]
222     >>> undo_list
223     []
224
225 Parent-child relationships are restored as well:
226
227     TODO!
228     >>> child = Item()
229     >>> canvas.add(child, parent=item)
230     >>> child.canvas is canvas
231     True
232     >>> canvas.get_parent(child) is item
233     True
234     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
235     [<gaphas.item.Item object at 0x...>, <gaphas.item.Item object at 0x...>]
236     >>> undo()
237     >>> child.canvas is None
238     True
239     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
240     [<gaphas.item.Item object at 0x...>]
241     >>> child in canvas.get_all_items()
242     False
243
244 Now redo the previous undo action:
245
246     >>> undo_list[:] = redo_list[:]
247     >>> undo()
248     >>> child.canvas is canvas
249     True
250     >>> canvas.get_parent(child) is item
251     True
252     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
253     [<gaphas.item.Item object at 0x...>, <gaphas.item.Item object at 0x...>]
254
255 Remove also works when items are removed recursively (an item and it's
256 children):
257
258     >>> child = Item()
259     >>> canvas.add(child, parent=item)
260     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
261     [<gaphas.item.Item object at 0x...>, <gaphas.item.Item object at 0x...>]
262     >>> del undo_list[:]
263     >>> canvas.remove(item)
264     >>> canvas.get_all_items()
265     []
266     >>> undo()
267     >>> canvas.get_all_items()                          # doctest: +ELLIPSIS
268     [<gaphas.item.Item object at 0x...>, <gaphas.item.Item object at 0x...>]
269     >>> canvas.get_children(item)                       # doctest: +ELLIPSIS
270     [<gaphas.item.Item object at 0x...>]
271
272 item.py: Handle
273 ---------------
274 Changing the Handle's position is reversible:
275
276     >>> from gaphas import Handle
277     >>> handle = Handle()
278     >>> handle.x, handle.y = 10, 12
279     >>> handle.pos
280     (Variable(10, 20), Variable(12, 20))
281     >>> undo()
282     >>> handle.pos
283     (Variable(0, 20), Variable(0, 20))
284
285 As are all other properties:
286
287     >>> handle.connectable, handle.movable, handle.visible, handle.connected_to
288     (False, True, True, None)
289     >>> handle.disconnect                               # doctest: +ELLIPSIS
290     <function <lambda> at 0x...>
291     >>> handle.connectable = True
292     >>> handle.movable = False
293     >>> handle.visible = False
294     >>> handle.connected_to = item
295     >>> def my_fancy_disconnect(): pass
296     >>> handle.disconnect = my_fancy_disconnect
297     >>> handle.connectable, handle.movable, handle.visible
298     (True, False, False)
299     >>> handle.connected_to                             # doctest: +ELLIPSIS
300     <gaphas.item.Item object at 0x...>
301     >>> handle.disconnect                               # doctest: +ELLIPSIS
302     <function my_fancy_disconnect at 0x...>
303
304 And now undo the whole lot at once:
305
306     >>> undo()
307     >>> handle.connectable, handle.movable, handle.visible, handle.connected_to
308     (False, True, True, None)
309     >>> handle.disconnect                               # doctest: +ELLIPSIS
310     <function <lambda> at 0x...>
311
312 item.py: Item
313 -------------
314
315 The basic Item properties are canvas and matrix. Canvas has been tested before,
316 while testing the Canvas class.
317
318 The Matrix has been tested in section matrix.py: Matrix.
319
320 item.py: Element
321 ----------------
322
323 An element has ``min_height`` and ``min_width`` properties.
324
325     >>> from gaphas import Element
326     >>> e = Element()
327     >>> e.min_height, e.min_width
328     (10, 10)
329     >>> e.min_height, e.min_width = 30, 40
330     >>> e.min_height, e.min_width
331     (30, 40)
332
333     >>> undo()
334     >>> e.min_height, e.min_width
335     (10, 10)
336    
337     >>> canvas = Canvas()
338     >>> e.canvas = canvas
339     >>> undo()
340     >>> e.canvas
341
342 item.py: Line
343 -------------
344
345 A line has the following properties: ``line_width``, ``fuzziness``,
346 ``orthogonal`` and ``horizontal``. Each one of then is observed for changes:
347
348     >>> from gaphas import Line
349     >>> l = Line()
350     >>> l.line_width, l.fuzziness, l.orthogonal, l.horizontal
351     (2, 0, False, False)
352
353 Now change the properties:
354
355     >>> l.line_width = 4
356     >>> l.fuzziness = 2
357     >>> l.orthogonal = True
358     >>> l.horizontal = True
359     >>> l.line_width, l.fuzziness, l.orthogonal, l.horizontal
360     (4, 2, True, True)
361
362 And undo the changes:
363
364     >>> undo()
365     >>> l.line_width, l.fuzziness, l.orthogonal, l.horizontal
366     (2, 0, False, False)
367
368 In addition to those properties, line segments can be split and merged.
369
370     >>> l.handles()[1].pos = 10, 10
371     >>> l.handles()
372     [<Handle object on (0, 0)>, <Handle object on (10, 10)>]
373
374 This is our basis for further testing.
375
376     >>> del undo_list[:]
377
378     >>> l.split_segment(0)
379     [<Handle object on (5, 5)>]
380     >>> l.handles()
381     [<Handle object on (0, 0)>, <Handle object on (5, 5)>, <Handle object on (10, 10)>]
382
383 The opposite operation is performed with the merge_segment() method:
384
385     >>> undo()
386     >>> l.handles()
387     [<Handle object on (0, 0)>, <Handle object on (10, 10)>]
388
389 solver.py: Variable
390 -------------------
391
392 Variable's strength and value properties are observed:
393
394     >>> from gaphas.solver import Variable
395     >>> v = Variable()
396     >>> v.value = 10
397     >>> v.strength = 100
398     >>> v
399     Variable(10, 100)
400     >>> undo()
401     >>> v
402     Variable(0, 20)
403
404 solver.py: Solver
405 -----------------
406
407 Solvers ``add_constraint()`` and ``remove_constraint()`` are observed.
408
409     >>> from gaphas.solver import Solver
410     >>> from gaphas.constraint import EquationConstraint
411     >>> s = Solver()
412     >>> a, b = Variable(1.0), Variable(2.0)
413     >>> s.add_constraint(EquationConstraint(lambda a,b: a+b, a=a, b=b))
414     EquationConstraint(<lambda>, a=Variable(1, 20), b=Variable(2, 20))
415     >>> list(s.constraints_with_variable(a))
416     [EquationConstraint(<lambda>, a=Variable(1, 20), b=Variable(2, 20))]
417    
418     >>> undo()
419     >>> list(s.constraints_with_variable(a))
420     []
421
Note: See TracBrowser for help on using the browser.