root/gaphas/trunk/demo.py

Revision 2398, 10.2 kB (checked in by gaph..@gmail.com, 3 weeks ago)

Import gaphas.picklers in demo app. Fixed #130

  • Property svn:executable set to *
  • Property svn:keywords set to Revision HeadURL
Line 
1 #!/usr/bin/env python
2 """
3 A simple demo app.
4
5 It sports a small canvas and some trivial operations:
6
7  - Add a line/box
8  - Zoom in/out
9  - Split a line segment
10  - Delete focused item
11  - Record state changes
12  - Play back state changes (= undo !) With visual updates
13  - Exports to SVG and PNG
14
15 """
16
17 __version__ = "$Revision$"
18 # $HeadURL$
19
20 import pygtk
21 pygtk.require('2.0') 
22
23 import math
24 import gtk
25 import cairo
26 from gaphas import Canvas, GtkView, View
27 from gaphas.examples import Box, Text, FatLine, Circle, DefaultExampleTool
28 from gaphas.item import Line, NW, SE
29 from gaphas.tool import PlacementTool, HandleTool
30 from gaphas.painter import ItemPainter
31 from gaphas import state
32 from gaphas.util import text_extents
33
34 from gaphas import painter
35
36 # Ensure data gets picked well:
37 import gaphas.picklers
38
39
40 #painter.DEBUG_DRAW_BOUNDING_BOX = True
41
42 # Global undo list
43 undo_list = []
44
45 def undo_handler(event):
46     global undo_list
47     undo_list.append(event)
48
49
50 def factory(view, cls):
51     """
52     Simple canvas item factory.
53     """
54     def wrapper():
55         item = cls()
56         view.canvas.add(item)
57         return item
58     return wrapper
59
60
61 class MyBox(Box):
62     """Box with an example connection protocol.
63     """
64
65 class MyLine(Line):
66     """Line with experimental connection protocol.
67     """
68     def __init__(self):
69         super(MyLine, self).__init__()
70         self.fuzziness = 2
71
72     def draw_head(self, context):
73         cr = context.cairo
74         cr.move_to(0, 0)
75         cr.line_to(10, 10)
76         cr.stroke()
77         # Start point for the line to the next handle
78         cr.move_to(0, 0)
79
80     def draw_tail(self, context):
81         cr = context.cairo
82         cr.line_to(0, 0)
83         cr.line_to(10, 10)
84         cr.stroke()
85
86
87
88
89 class MyText(Text):
90     """
91     Text with experimental connection protocol.
92     """
93    
94     def draw(self, context):
95         Text.draw(self, context)
96         cr = context.cairo
97         w, h = text_extents(cr, self.text, multiline=self.multiline)
98         cr.rectangle(0, 0, w, h)
99         cr.set_source_rgba(.3, .3, 1., .6)
100         cr.stroke()
101
102
103 def create_window(canvas, title, zoom=1.0):
104     view = GtkView()
105     view.tool = DefaultExampleTool()
106
107     w = gtk.Window()
108     w.set_title(title)
109     h = gtk.HBox()
110     w.add(h)
111
112     # VBox contains buttons that can be used to manipulate the canvas:
113     v = gtk.VBox()
114     v.set_property('border-width', 3)
115     v.set_property('spacing', 2)
116     f = gtk.Frame()
117     f.set_property('border-width', 1)
118     f.add(v)
119     h.pack_start(f, expand=False)
120
121     v.add(gtk.Label('Item placement:'))
122    
123     b = gtk.Button('Add box')
124
125     def on_clicked(button, view):
126         #view.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.CROSSHAIR))
127         view.tool.grab(PlacementTool(factory(view, MyBox), HandleTool(), 2))
128
129     b.connect('clicked', on_clicked, view)
130     v.add(b)
131
132     b = gtk.Button('Add line')
133
134     def on_clicked(button):
135         view.tool.grab(PlacementTool(factory(view, MyLine), HandleTool(), 1))
136
137     b.connect('clicked', on_clicked)
138     v.add(b)
139
140     v.add(gtk.Label('Zooming:'))
141    
142     b = gtk.Button('Zoom in')
143
144     def on_clicked(button):
145         view.zoom(1.2)
146
147     b.connect('clicked', on_clicked)
148     v.add(b)
149
150     b = gtk.Button('Zoom out')
151
152     def on_clicked(button):
153         view.zoom(1/1.2)
154
155     b.connect('clicked', on_clicked)
156     v.add(b)
157
158     v.add(gtk.Label('Misc:'))
159
160     b = gtk.Button('Split line')
161
162     def on_clicked(button):
163         if isinstance(view.focused_item, Line):
164             view.focused_item.split_segment(0)
165             view.queue_draw_item(view.focused_item, handles=True)
166
167     b.connect('clicked', on_clicked)
168     v.add(b)
169
170     b = gtk.Button('Delete focused')
171
172     def on_clicked(button):
173         if view.focused_item:
174             canvas.remove(view.focused_item)
175             print 'items:', canvas.get_all_items()
176
177     b.connect('clicked', on_clicked)
178     v.add(b)
179
180     v.add(gtk.Label('State:'))
181     b = gtk.ToggleButton('Record')
182
183     def on_toggled(button):
184         global undo_list
185         if button.get_active():
186             print 'start recording'
187             del undo_list[:]
188             state.subscribers.add(undo_handler)
189         else:
190             print 'stop recording'
191             state.subscribers.remove(undo_handler)
192
193     b.connect('toggled', on_toggled)
194     v.add(b)
195
196     b = gtk.Button('Play back')
197    
198     def on_clicked(self):
199         global undo_list
200         apply_me = list(undo_list)
201         del undo_list[:]
202         apply_me.reverse()
203         saveapply = state.saveapply
204         for event in apply_me:
205             print 'Undo: invoking', event
206             saveapply(*event)
207             # Visualize each event:
208             while gtk.events_pending():
209                 gtk.main_iteration()
210
211     b.connect('clicked', on_clicked)
212     v.add(b)
213
214     v.add(gtk.Label('Export:'))
215
216     b = gtk.Button('Write demo.png')
217
218     def on_clicked(button):
219         svgview = View(view.canvas)
220         svgview.painter = ItemPainter()
221
222         # Update bounding boxes with a temporaly CairoContext
223         # (used for stuff like calculating font metrics)
224         tmpsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0)
225         tmpcr = cairo.Context(tmpsurface)
226         svgview.update_bounding_box(tmpcr)
227         tmpcr.show_page()
228         tmpsurface.flush()
229        
230         w, h = svgview.bounding_box.width, svgview.bounding_box.height
231         surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(w), int(h))
232         cr = cairo.Context(surface)
233         svgview.matrix.translate(-svgview.bounding_box.x, -svgview.bounding_box.y)
234         cr.save()
235         svgview.paint(cr)
236
237         cr.restore()
238         cr.show_page()
239         surface.write_to_png('demo.png')
240
241     b.connect('clicked', on_clicked)
242     v.add(b)
243
244     b = gtk.Button('Write demo.svg')
245
246     def on_clicked(button):
247         svgview = View(view.canvas)
248         svgview.painter = ItemPainter()
249
250         # Update bounding boxes with a temporaly CairoContext
251         # (used for stuff like calculating font metrics)
252         tmpsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0)
253         tmpcr = cairo.Context(tmpsurface)
254         svgview.update_bounding_box(tmpcr)
255         tmpcr.show_page()
256         tmpsurface.flush()
257        
258         w, h = svgview.bounding_box.width, svgview.bounding_box.height
259         surface = cairo.SVGSurface('demo.svg', w, h)
260         cr = cairo.Context(surface)
261         svgview.matrix.translate(-svgview.bounding_box.x, -svgview.bounding_box.y)
262         svgview.paint(cr)
263         cr.show_page()
264         surface.flush()
265         surface.finish()
266
267     b.connect('clicked', on_clicked)
268     v.add(b)
269
270    
271     b = gtk.Button('Dump QTree')
272
273     def on_clicked(button, li):
274         view._qtree.dump()
275
276     b.connect('clicked', on_clicked, [0])
277     v.add(b)
278
279
280     b = gtk.Button('Pickle (save)')
281
282     def on_clicked(button, li):
283         f = open('demo.pickled', 'w')
284         try:
285             import pickle
286             pickle.dump(view.canvas, f)
287         finally:
288             f.close()
289
290     b.connect('clicked', on_clicked, [0])
291     v.add(b)
292
293
294     b = gtk.Button('Unpickle (load)')
295
296     def on_clicked(button, li):
297         f = open('demo.pickled', 'r')
298         try:
299             import pickle
300             canvas = pickle.load(f)
301             canvas.update_now()
302         finally:
303             f.close()
304         create_window(canvas, 'Unpickled diagram')
305
306     b.connect('clicked', on_clicked, [0])
307     v.add(b)
308
309
310     # Add the actual View:
311
312     t = gtk.Table(2,2)
313     h.add(t)
314
315     w.connect('destroy', gtk.main_quit)
316
317     view.canvas = canvas
318     view.zoom(zoom)
319     view.set_size_request(150, 120)
320     hs = gtk.HScrollbar(view.hadjustment)
321     vs = gtk.VScrollbar(view.vadjustment)
322     t.attach(view, 0, 1, 0, 1)
323     t.attach(hs, 0, 1, 1, 2, xoptions=gtk.FILL, yoptions=gtk.FILL)
324     t.attach(vs, 1, 2, 0, 1, xoptions=gtk.FILL, yoptions=gtk.FILL)
325
326     w.show_all()
327    
328     def handle_changed(view, item, what):
329         print what, 'changed: ', item
330
331     view.connect('focus-changed', handle_changed, 'focus')
332     view.connect('hover-changed', handle_changed, 'hover')
333     view.connect('selection-changed', handle_changed, 'selection')
334
335    
336 def create_canvas(c=None):
337     if not c:
338         c = Canvas()
339     b=MyBox()
340     b.min_width = 20
341     b.min_height = 30
342     print 'box', b
343     b.matrix=(1.0, 0.0, 0.0, 1, 20,20)
344     b.width = b.height = 40
345     c.add(b)
346
347     bb=Box()
348     print 'box', bb
349     bb.matrix=(1.0, 0.0, 0.0, 1, 10,10)
350     c.add(bb, parent=b)
351
352     fl = FatLine()
353     fl.height = 50
354     fl.matrix.translate(100, 100)
355     c.add(fl)
356
357
358     circle = Circle()
359     h1, h2 = circle.handles()
360     circle.radius = 20
361     circle.matrix.translate(50, 100)
362     c.add(circle)
363
364     # AJM: extra boxes:
365     bb=Box()
366     print 'box', bb
367     bb.matrix.rotate(math.pi/4.)
368     c.add(bb, parent=b)
369 #    for i in xrange(10):
370 #        bb=Box()
371 #        print 'box', bb
372 #        bb.matrix.rotate(math.pi/4.0 * i / 10.0)
373 #        c.add(bb, parent=b)
374
375     t=MyText('Single line')
376     t.matrix.translate(70,70)
377     c.add(t)
378
379     l=MyLine()
380     l.handles()[1].pos = (30, 30)
381     l.split_segment(0, 3)
382     l.matrix.translate(30, 60)
383     c.add(l)
384     l.orthogonal = True
385
386     off_y = 0
387     for align_x in (-1, 0, 1):
388         for align_y in (-1, 0, 1):
389             t=MyText('Aligned text %d/%d' % (align_x, align_y),
390                      align_x=align_x, align_y=align_y)
391             t.matrix.translate(120, 200 + off_y)
392             off_y += 30
393             c.add(t)
394
395     t=MyText('Multiple\nlines', multiline = True)
396     t.matrix.translate(70,100)
397     c.add(t)
398
399     return c
400
401
402 def main():
403     ##
404     ## State handling (a.k.a. undo handlers)
405     ##
406
407     # First, activate the revert handler:
408     state.observers.add(state.revert_handler)
409
410     def print_handler(event):
411         print 'event:', event
412
413     c=Canvas()
414
415     create_window(c, 'View created before')
416
417     create_canvas(c)
418
419     #state.subscribers.add(print_handler)
420
421     ##
422     ## Start the main application
423     ##
424
425     create_window(c, 'View created after')
426
427     gtk.main()
428
429
430 if __name__ == '__main__':
431     import sys
432     if '-p' in sys.argv:
433         print 'Profiling...'
434         import hotshot, hotshot.stats
435         prof = hotshot.Profile('demo-gaphas.prof')
436         prof.runcall(main)
437         prof.close()
438         stats = hotshot.stats.load('demo-gaphas.prof')
439         stats.strip_dirs()
440         stats.sort_stats('time', 'calls')
441         stats.print_stats(20)
442     else:
443         main()
444
445 # vim: sw=4:et:
Note: See TracBrowser for help on using the browser.