apply-templates uses match attribute.
[dom-editor.git] / Dome / List.py
bloba1bd501a87a508118104d89fdee4819a7d168987
1 #from __future__ import nested_scopes
3 import rox
4 from rox import g, TRUE, FALSE, alert
5 from gnome2 import canvas
7 from support import *
8 import string
9 from StringIO import StringIO
10 import math
12 from rox.Menu import Menu
14 prog_menu = Menu('programs', [
15 ('/Play', 'menu_play', '', ''),
16 ('/Map', 'menu_map', '', ''),
17 ('/View', 'menu_new_view', '', ''),
18 ('/', '', '', '<separator>'),
19 ('/New program', 'menu_new_prog', '', ''),
20 ('/Rename', 'menu_rename', '', ''),
21 ('/Delete', 'menu_delete', '', ''),
24 line_menu = Menu('line', [
25 ('/Set\/clear breakpoint', 'line_toggle_breakpoint', '', ''),
26 ('/Yank chain', 'line_yank_chain', '', ''),
27 ('/Remove link', 'line_del_chain', '', ''),
28 ('/Paste chain', 'line_paste_chain', '', ''),
29 ('/Add block', 'line_add_block', '', '')
32 block_menu = Menu('op', [
33 ('/Toggle Enter\/Leave', 'block_toggle_enter', '', ''),
34 ('/Toggle Foreach','block_toggle_foreach', '', ''),
35 ('/Toggle Restore Mark','block_toggle_restore', '', ''),
36 ('/Edit comment', 'block_edit_comment', '', ''),
37 ('/Swap next\/fail', 'op_swap_nf', '', ''),
38 ('/Remove node', 'op_del_node', '', '')
41 op_menu = Menu('op', [
42 ('/Edit node', 'op_edit', '', ''),
43 ('/Swap next\/fail', 'op_swap_nf', '', ''),
44 ('/Remove node', 'op_del_node', '', '')
47 from GetArg import GetArg
48 from Program import Program, load, Block
50 no_cursor = g.gdk.Cursor(g.gdk.TCROSS)
52 def trunc(text):
53 if len(text) < 18:
54 return text
55 return text[:16] + '...'
57 def connect(x1, y1, x2, y2):
58 """Chop 5 pixels off both ends of this line"""
59 gap = 5.0
60 dx = x2 - x1
61 dy = y2 - y1
62 l = math.hypot(dx, dy)
63 if l:
64 dx *= gap / l
65 dy *= gap / l
66 return (x1 + dx, y1 + dy, x2 - dx, y2 - dy)
68 DEFAULT_NEXT = (0, 25)
69 DEFAULT_FAIL = (20, 20)
71 expand_history = {} # Prog name -> expanded flag
73 def action_to_text(action):
74 text = action[0]
75 if text[:3] == 'do_':
76 text = text[3:]
77 text = string.capitalize(string.replace(text, '_', ' '))
79 if len(action) > 1:
80 if action[0] == 'do_search':
81 pat = str(action[1])
82 pat = string.replace(pat, 'following-sibling::', '>>')
83 pat = string.replace(pat, 'preceding-sibling::', '<<')
84 pat = string.replace(pat, 'child::', '')
85 pat = string.replace(pat, '[1]', '')
86 pat = string.replace(pat, 'text()[ext:match', '[')
87 details = ''
88 while len(pat) > 20:
89 i = string.rfind(pat[:20], '/')
90 if i == -1:
91 i = 20
92 details = details + pat[:i + 1] + '\n'
93 pat = pat[i + 1:]
94 details = details + pat
95 elif action[0] == 'attribute':
96 details = trunc(str(action[2]))
97 elif action[0] == 'set_attrib':
98 details = trunc(str(action[1]))
99 elif action[0] == 'add_attrib':
100 details = trunc(str(action[2]))
101 elif action[0] == 'add_node':
102 details = trunc(action[2])
103 elif action[0] == 'subst':
104 details = action[1] + ' -> ' + action[2]
105 elif action[0] == 'play' or action[0] == 'map':
106 if len(action[1]) > 20:
107 details = '...' + str(action[1][-19:])
108 else:
109 details = str(action[1])
110 else:
111 if len(action) > 2:
112 details = `action[1:]`
113 else:
114 details = str(action[1])
115 if len(details) > 20:
116 details = `details`[:19] + '...'
117 text = text + '\n' + details
118 return text
120 class List(g.VBox):
121 def __init__(self, view):
122 g.VBox.__init__(self)
124 self.view = view
125 self.sub_windows = []
127 self.stack_frames = g.Label('')
128 self.pack_start(self.stack_frames, FALSE, TRUE, 0)
129 self.stack_frames.show()
130 self.update_stack(None)
132 pane = g.VPaned()
133 self.pack_start(pane, expand = 1, fill = 1)
135 swin = g.ScrolledWindow()
136 swin.set_policy(g.POLICY_NEVER, g.POLICY_AUTOMATIC)
137 pane.add1(swin)
139 self.prog_model = g.TreeStore(str, str)
140 tree = g.TreeView(self.prog_model)
141 tree.connect('button-press-event', self.button_press)
142 tree.unset_flags(g.CAN_FOCUS)
143 tree.set_headers_visible(FALSE)
144 self.tree = tree
146 cell = g.CellRendererText()
147 column = g.TreeViewColumn('Program', cell, text = 0)
148 tree.append_column(column)
150 sel = tree.get_selection()
151 def change_prog(tree, sel = sel, self = self): # Huh?
152 selected = sel.get_selected()
153 if not selected:
154 return
155 model, iter = selected
156 if iter:
157 path = model.get_value(iter, 1)
158 self.chains.switch_to(self.view.name_to_prog(path))
159 else:
160 self.chains.switch_to(None)
162 sel.connect('changed', change_prog)
164 self.chains = ChainDisplay(view)
165 self.prog_tree_changed()
166 v = g.Viewport()
167 v.add(tree)
168 swin.add(v)
169 v.set_shadow_type(g.SHADOW_NONE)
170 v.show_all()
172 swin = g.ScrolledWindow()
173 swin.set_policy(g.POLICY_AUTOMATIC, g.POLICY_AUTOMATIC)
174 pane.add2(swin)
175 swin.add(self.chains)
176 swin.show_all()
178 pane.set_position(200)
180 sel.set_mode(g.SELECTION_BROWSE)
181 root_iter = self.prog_model.get_iter_first()
182 sel.select_iter(root_iter)
183 tree.expand_row(self.prog_model.get_path(root_iter), FALSE)
184 tree.show()
185 self.view.lists.append(self)
186 self.view.model.root_program.watchers.append(self)
188 def set_innermost_failure(self, op):
189 self.show_prog(op.get_program())
191 def destroy(self):
192 self.view.lists.remove(self)
193 self.view.model.root_program.watchers.remove(self)
195 def update_points(self):
196 self.chains.update_points()
197 for x in self.sub_windows:
198 x.update_points()
200 def program_changed(self, op):
201 pass
203 def prog_tree_changed(self):
204 self.prog_to_path = {}
205 self.prog_model.clear()
206 self.build_tree(self.view.model.root_program)
208 # Check for now deleted programs still being displayed
209 root = self.view.model.root_program
210 if self.chains and self.chains.prog and not self.chains.prog.parent:
211 self.chains.switch_to(None)
212 for x in self.sub_windows:
213 if x.disp.prog is not root and not x.disp.prog.parent:
214 x.destroy()
216 def build_tree(self, prog, iter = None):
217 child_iter = self.prog_model.append(iter)
218 self.prog_model.set(child_iter, 0, prog.name,
219 1, prog.get_path())
221 self.prog_to_path[prog] = self.prog_model.get_path(child_iter)
222 for p in prog.subprograms.values():
223 self.build_tree(p, child_iter)
225 def run_return(self, exit):
226 if exit != 'next':
227 print "run_return: failure!"
228 self.view.jump_to_innermost_failure()
229 def cb(choice, self = self):
230 if choice == 0:
231 self.view.record_at_point()
232 box = MultipleChoice("Program failed - record a failure case?",
233 [('Record', self.view.record_at_point), 'Cancel'])
234 box.set_title('Dome')
235 box.show()
236 print "List: execution done!"
238 def button_press(self, tree, event):
239 if event.button == 2 or event.button == 3:
240 path, col, cx, cy = tree.get_path_at_pos(event.x, event.y)
241 print "Event on", path
242 iter = self.prog_model.get_iter(path)
243 path = self.prog_model.get_value(iter, 1)
244 if event.button == 3:
245 prog = self.view.name_to_prog(path)
246 self.show_menu(event, prog)
247 else:
248 self.view.run_new(self.run_return)
249 if event.state & g.gdk.SHIFT_MASK:
250 self.view.may_record(['map', path])
251 else:
252 self.view.may_record(['play', path])
253 return 0
255 def menu_delete(self):
256 prog = self.prog_menu_prog
257 if not prog.parent:
258 rox.alert("Can't delete the root program!")
259 return
260 prog.parent.remove_sub(prog)
262 def menu_rename(self):
263 prog = self.prog_menu_prog
264 def rename(name, prog = prog):
265 prog.rename(name)
266 GetArg('Rename program', rename, ['Program name:'])
268 def menu_new_prog(self):
269 prog = self.prog_menu_prog
270 def create(name):
271 new = Program(name)
272 prog.add_sub(new)
273 GetArg('New program', create, ['Program name:'])
275 def menu_new_view(self):
276 prog = self.prog_menu_prog
277 cw = ChainWindow(self.view, prog)
278 cw.show()
279 self.sub_windows.append(cw)
280 def lost_cw(win):
281 self.sub_windows.remove(cw)
282 print "closed"
283 cw.connect('destroy', lost_cw)
285 def menu_map(self):
286 prog = self.prog_menu_prog
287 self.view.run_new(self.run_return)
288 self.view.may_record(['map', prog.get_path()])
290 def menu_play(self):
291 prog = self.prog_menu_prog
292 self.view.run_new(self.run_return)
293 self.view.may_record(['play', prog.get_path()])
295 def show_menu(self, event, prog):
296 self.prog_menu_prog = prog
297 prog_menu.popup(self, event)
299 def update_stack(self, op):
300 "The stack has changed - redraw 'op'"
301 if op and op.get_program() == self.chains.prog:
302 self.chains.update_all()
303 l = len(self.view.exec_stack) + len(self.view.foreach_stack)
304 if l == 0:
305 text = 'No stack'
306 elif l == 1:
307 text = '1 frame'
308 else:
309 text = '%d frames' % l
310 if self.view.chroots:
311 text += ' (%d enters)' % len(self.view.chroots)
312 self.stack_frames.set_text(text)
314 def show_prog(self, prog):
315 path = self.prog_to_path[prog]
316 iter = self.prog_model.get_iter(path)
317 self.tree.get_selection().select_iter(iter)
319 class ChainDisplay(canvas.Canvas):
320 "A graphical display of a chain of nodes."
321 def __init__(self, view, prog = None):
322 canvas.Canvas.__init__(self)
323 self.connect('destroy', self.destroyed)
324 self.view = view
325 self.unset_flags(g.CAN_FOCUS)
327 self.drag_last_pos = None
329 self.exec_point = None # CanvasItem, or None
330 self.rec_point = None
332 s = self.get_style().copy()
333 s.bg[g.STATE_NORMAL] = g.gdk.color_parse('light green')
334 self.set_style(s)
336 self.nodes = None
337 self.subs = None
338 self.set_size_request(100, 100)
340 self.prog = None
342 self.view.model.root_program.watchers.append(self)
344 self.switch_to(prog)
346 def update_points(self):
347 self.put_point('rec_point')
348 self.put_point('exec_point')
350 if self.rec_point:
351 self.scroll_to_show(self.rec_point)
353 def scroll_to_show(self, item):
354 (lx, ly, hx, hy) = item.get_bounds()
355 x, y = item.i2w(0, 0)
356 x, y = self.w2c(x, y)
357 lx += x
358 ly += y
359 hx += x
360 hy += y
361 lx -= 16
363 sx, sy = self.get_scroll_offsets()
364 if lx < sx:
365 sx = lx
366 if ly < sy:
367 sy = ly
369 (x, y, w, h) = self.get_allocation()
370 hx -= w
371 hy -= h
373 if hx > sx:
374 sx = hx
375 if hy > sy:
376 sy = hy
378 self.scroll_to(sx, sy)
380 def put_point(self, point):
381 item = getattr(self, point)
382 if item:
383 item.destroy()
384 setattr(self, point, None)
386 if not self.prog:
387 return
389 opexit = getattr(self.view, point)
390 if point == 'exec_point' and self.view.op_in_progress:
391 opexit = (self.view.op_in_progress, None)
392 if opexit:
393 g = None
394 (op, exit) = opexit
395 if op.get_program() != self.prog:
396 return
397 try:
398 g = self.op_to_group[op]
399 except KeyError:
400 pass
401 if point == 'rec_point':
402 c = 'red'
403 s = 6
404 else:
405 c = 'yellow'
406 s = 3
407 item = self.root().add(canvas.CanvasRect,
408 x1 = -s, x2 = s, y1 = -s, y2 = s,
409 fill_color = c,
410 outline_color = 'black', width_pixels = 1)
411 setattr(self, point, item)
412 item.connect('event', self.line_event, op, exit)
414 if g and exit:
415 # TODO: cope with exit == None
416 x1, y1, x2, y2 = self.get_arrow_ends(op, exit)
417 x1, y1 = g.i2w(x1, y1)
418 x2, y2 = g.i2w(x2, y2)
419 item.move((x1 + x2) / 2, (y1 + y2) / 2)
421 def destroyed(self, widget):
422 p = self.prog
423 while p.parent:
424 p = p.parent
425 self.view.model.root_program.watchers.remove(self)
426 print "(ChainDisplay destroyed)"
428 def switch_to(self, prog):
429 if prog is self.prog:
430 return
431 self.prog = prog
432 self.update_all()
434 def prog_tree_changed(self):
435 pass
437 def program_changed(self, op):
438 if (not op) or op.get_program() == self.prog:
439 self.update_all()
441 def update_all(self):
442 if self.nodes:
443 self.nodes.destroy()
445 self.op_to_group = {}
446 self.nodes = self.root().add(canvas.CanvasGroup, x = 0, y = 0)
447 if self.prog:
448 self.create_node(self.prog.code, self.nodes)
449 self.update_links()
450 self.update_points()
452 self.set_bounds()
454 return 1
456 def op_colour(self, op):
457 if op in self.view.exec_stack:
458 return 'cyan'
459 return 'blue'
461 def update_links(self, op = None):
462 """Walk through all nodes in the tree-version of the op graph,
463 making all the links (which already exist as stubs) point to
464 the right place."""
465 if not self.prog:
466 return
467 if not op:
468 op = self.prog.code
469 if op.next:
470 if op.next.prev[0] == op:
471 self.update_links(op.next)
472 else:
473 self.join_nodes(op, 'next')
474 if op.fail:
475 if op.fail.prev[0] == op:
476 self.update_links(op.fail)
477 else:
478 self.join_nodes(op, 'fail')
479 if isinstance(op, Block):
480 self.update_links(op.start)
482 def create_node(self, op, group):
483 self.op_to_group[op] = group
485 if isinstance(op, Block):
486 gr = group.add(canvas.CanvasGroup, x = 0, y = 0)
487 self.create_node(op.start, gr)
488 #self.update_now() # GnomeCanvas bug?
489 (lx, ly, hx, hy) = gr.get_bounds()
490 minx = lx - 4
491 if op.foreach:
492 minx -= 8
493 border = gr.add(canvas.CanvasRect, x1 = minx, x2 = hx + 4, y1 = ly + 4, y2 = hy + 4,
494 outline_color = 'black', width_pixels = 1)
495 border.lower_to_bottom()
496 if op.foreach:
497 gr.add(canvas.CanvasRect, x1 = minx, x2 = minx + 8, y1 = ly + 4, y2 = hy + 4,
498 fill_color = 'blue').lower_to_bottom()
499 if op.enter:
500 colour = 'yellow'
501 gr.add(canvas.CanvasRect, x1 = minx, x2 = hx + 4, y1 = ly + 5, y2 = ly + 13,
502 fill_color = colour).lower_to_bottom()
503 gr.add(canvas.CanvasRect, x1 = minx, x2 = hx + 4, y1 = hy - 3, y2 = hy + 3,
504 fill_color = colour).lower_to_bottom()
505 if op.restore:
506 colour = 'orange'
507 margin = op.enter * 8
508 gr.add(canvas.CanvasRect, x1 = minx, x2 = hx + 4, y1 = ly + 5 + margin, y2 = ly + 13 + margin,
509 fill_color = colour).lower_to_bottom()
510 gr.add(canvas.CanvasRect, x1 = minx, x2 = hx + 4, y1 = hy - 3 - margin, y2 = hy + 3 - margin,
511 fill_color = colour).lower_to_bottom()
512 next_off_y = 0
513 group.width, group.height = hx, hy
514 if op.is_toplevel():
515 return
516 else:
517 if op.action[0] == 'Start':
518 text = str(op.parent.comment.replace('\\n', '\n'))
519 text_y = 0
520 #text_font = '-misc-fixed-bold-r-normal-*-*-120-*-*-c-*-iso8859-1'
521 text_col = 'dark blue'
522 else:
523 text = str(action_to_text(op.action))
524 text_y = -8
525 #text_font = '-misc-fixed-medium-r-normal-*-*-120-*-*-c-*-iso8859-1'
526 text_col = 'black'
528 group.ellipse = group.add(canvas.CanvasEllipse,
529 fill_color = self.op_colour(op),
530 outline_color = 'black',
531 x1 = -4, x2 = 4,
532 y1 = -4, y2 = 4,
533 width_pixels = 1)
534 group.ellipse.connect('event', self.op_event, op)
535 if text:
536 label = group.add(canvas.CanvasText,
537 x = -8,
538 y = text_y,
539 anchor = g.ANCHOR_NE,
540 justification = 'right',
541 fill_color = text_col,
542 text = text)
544 #self.update_now() # GnomeCanvas bug?
545 (lx, ly, hx, hy) = label.get_bounds()
546 next_off_y = hy
547 else:
548 next_off_y = 0
549 group.width, group.height = 0, 0
551 if op.next and op.next.prev[0] == op:
552 sx, sy = self.get_arrow_start(op, 'next')
553 gr = group.add(canvas.CanvasGroup, x = 0, y = 0)
554 self.create_node(op.next, gr)
555 #self.update_now() # GnomeCanvas bug?
556 (lx, ly, hx, hy) = gr.get_bounds()
557 drop = max(20, next_off_y + 10)
558 y = drop - ly
559 next = op.next
560 while isinstance(next, Block):
561 next = next.start
562 x = next.dx
563 y += next.dy
564 gr.move(sx + x, sy + y)
566 group.next_line = group.add(canvas.CanvasLine,
567 fill_color = 'black',
568 points = connect(0, 0, 1, 1),
569 width_pixels = 4,
570 last_arrowhead = 1,
571 arrow_shape_a = 5,
572 arrow_shape_b = 5,
573 arrow_shape_c = 5)
574 group.next_line.connect('event', self.line_event, op, 'next')
576 (x, y) = DEFAULT_FAIL
577 if op.fail and op.fail.prev[0] == op:
578 sx, sy = self.get_arrow_start(op, 'fail')
579 y = 46
580 gr = group.add(canvas.CanvasGroup, x = 0, y = 0)
581 self.create_node(op.fail, gr)
582 #self.update_now() # GnomeCanvas bug?
583 (lx, ly, hx, hy) = gr.get_bounds()
584 x = 20 - lx
585 fail = op.fail
586 while isinstance(fail, Block):
587 fail = fail.start
588 x += fail.dx
589 y += fail.dy
590 gr.move(sx + x, sy + y)
591 group.fail_line = group.add(canvas.CanvasLine,
592 fill_color = '#ff6666',
593 points = connect(0, 0, 1, 1),
594 width_pixels = 4,
595 last_arrowhead = 1,
596 arrow_shape_a = 5,
597 arrow_shape_b = 5,
598 arrow_shape_c = 5)
599 group.fail_line.lower_to_bottom()
600 group.fail_line.connect('event', self.line_event, op, 'fail')
601 if op.action[0] == 'Start':
602 group.fail_line.hide()
604 self.join_nodes(op, 'next')
605 self.join_nodes(op, 'fail')
607 if self.view.breakpoints.has_key((op, 'next')):
608 group.next_line.set(line_style = g.gdk.LINE_ON_OFF_DASH)
609 if self.view.breakpoints.has_key((op, 'fail')):
610 group.fail_line.set(line_style = g.gdk.LINE_ON_OFF_DASH)
612 def edit_op(self, op):
613 def modify():
614 if op.action[0] == 'do_search' or op.action[0] == 'do_global':
615 t = editables[0].get_text()
616 print "Checking", t
617 from Ft.Xml.XPath import XPathParser
618 if t.find('@CURRENT@') == -1:
619 try:
620 XPathParser.new().parse(t)
621 except:
622 alert('Invalid search pattern!')
623 return
624 i = 0
625 for e in editables:
626 i += 1
627 if e:
628 op.action[i] = e.get_text()
629 op.changed()
630 print "Done editing!"
631 win.destroy()
633 win = g.Dialog()
634 win.vbox.pack_start(g.Label(op.action[0]), TRUE, FALSE, 0)
635 editables = [] # [ Entry | None ]
636 focus = None
637 for x in op.action[1:]:
638 entry = g.Entry()
639 entry.set_text(str(x))
640 win.vbox.pack_start(entry, TRUE, FALSE, 0)
641 if type(x) == str or type(x) == unicode:
642 editables.append(entry)
643 entry.connect('activate', lambda e: modify())
644 if not focus:
645 focus = entry
646 entry.grab_focus()
647 else:
648 entry.set_editable(FALSE)
649 editables.append(None)
651 win.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
652 win.add_button(g.STOCK_OK, g.RESPONSE_OK)
654 def response(box, resp):
655 box.destroy()
656 if resp == g.RESPONSE_OK:
657 modify()
658 win.connect('response', response)
660 if not focus:
661 win.set_response_sensitive(g.RESPONSE_OK, FALSE)
663 win.show_all()
665 def join_nodes(self, op, exit):
666 try:
667 x1, y1, x2, y2 = self.get_arrow_ends(op, exit)
669 prev_group = self.op_to_group[op]
670 line = getattr(prev_group, exit + '_line')
671 line.set(points = connect(x1, y1, x2, y2))
672 except Block:
673 print "*** ERROR setting arc from %s:%s" % (op, exit)
675 def op_event(self, item, event, op):
676 if event.type == g.gdk.BUTTON_PRESS:
677 print "Prev %s = %s" % (op, map(str, op.prev))
678 if event.button == 1:
679 if op.parent.start != op or not op.parent.is_toplevel():
680 self.drag_last_pos = (event.x, event.y)
681 else:
682 self.drag_last_pos = None
683 else:
684 self.show_op_menu(event, op)
685 elif event.type == g.gdk.BUTTON_RELEASE:
686 if event.button == 1:
687 self.drag_last_pos = None
688 self.program_changed(None)
689 elif event.type == g.gdk.ENTER_NOTIFY:
690 item.set(fill_color = 'white')
691 elif event.type == g.gdk.LEAVE_NOTIFY:
692 item.set(fill_color = self.op_colour(op))
693 elif event.type == g.gdk.MOTION_NOTIFY and self.drag_last_pos:
694 if not event.state & g.gdk.BUTTON1_MASK:
695 print "(stop drag!)"
696 self.drag_last_pos = None
697 self.program_changed(None)
698 return 1
699 x, y = (event.x, event.y)
700 dx, dy = x - self.drag_last_pos[0], y - self.drag_last_pos[1]
701 if abs(op.dx + dx) < 4:
702 dx = -op.dx
703 x = dx + self.drag_last_pos[0]
704 if abs(op.dy + dy) < 4:
705 dy = -op.dy
706 y = dy + self.drag_last_pos[1]
707 op.dx += dx
708 op.dy += dy
709 self.drag_last_pos = (x, y)
711 self.op_to_group[op].move(dx, dy)
712 for p in op.prev:
713 if p.next == op:
714 self.join_nodes(p, 'next')
715 if p.fail == op:
716 self.join_nodes(p, 'fail')
717 self.update_links()
718 #self.create_node(self.prog.start, self.nodes)
719 self.update_points()
720 elif event.type == g.gdk._2BUTTON_PRESS:
721 if op.action[0] == 'Start':
722 self.edit_comment(op.parent)
723 else:
724 self.edit_op(op)
725 print "(edit; stop drag!)"
726 self.drag_last_pos = None
727 self.program_changed(None)
728 return 1
730 def edit_comment(self, block):
731 assert isinstance(block, Block)
733 def set(comment):
734 block.set_comment(comment)
735 GetArg('Comment', set, ['Comment:'],
736 message = '\\n for a newline', init = [block.comment])
738 def block_toggle_enter(self):
739 self.op_menu_op.toggle_enter()
741 def block_toggle_foreach(self):
742 self.op_menu_op.toggle_foreach()
744 def block_toggle_restore(self):
745 self.op_menu_op.toggle_restore()
747 def block_edit_comment(self):
748 self.edit_comment(self.op_menu_op)
750 def op_edit(self):
751 self.edit_op(self.op_menu_op)
753 def op_swap_nf(self):
754 self.op_menu_op.swap_nf()
756 def op_del_node(self):
757 op = self.op_menu_op
758 if op.next and op.fail:
759 rox.alert("Can't delete a node with both exits in use")
760 return
761 self.clipboard = op.del_node()
763 def show_op_menu(self, event, op):
764 if op.action[0] == 'Start':
765 self.op_menu_op = op.parent
766 block_menu.popup(self, event)
767 else:
768 self.op_menu_op = op
769 op_menu.popup(self, event)
771 def paste_chain(self, op, exit):
772 print "Paste", self.clipboard
773 doc = self.clipboard
774 new = load(doc.documentElement, op.parent)
775 start = new.start.next
776 new.start.unlink('next', may_delete = 0)
777 start.set_parent(None)
778 op.link_to(start, exit)
780 def end_link_drag(self, item, event, src_op, exit):
781 # Scan all the nodes looking for one nearby...
782 x, y = event.x, event.y
784 def closest_node(op):
785 "Return the closest (node, dist) in this chain to (x, y)"
786 nx, ny = self.op_to_group[op].i2w(0, 0)
787 if op is src_op:
788 best = None
789 elif isinstance(op, Block):
790 best = None
791 else:
792 best = (op, math.hypot(nx - x, ny - y))
793 if op.next and op.next.prev[0] == op:
794 next = closest_node(op.next)
795 if next and (best is None or next[1] < best[1]):
796 best = next
797 if op.fail and op.fail.prev[0] == op:
798 fail = closest_node(op.fail)
799 if fail and (best is None or fail[1] < best[1]):
800 best = fail
801 if isinstance(op, Block):
802 sub = closest_node(op.start)
803 if sub and (best is None or sub[1] < best[1]):
804 best = sub
805 return best
807 result = closest_node(self.prog.code)
808 if result:
809 node, dist = result
810 else:
811 dist = 1000
812 if dist > 12:
813 # Too far... put the line back to the disconnected state...
814 self.join_nodes(src_op, exit)
815 return
816 try:
817 while node.action[0] == 'Start':
818 node = node.parent
819 src_op.link_to(node, exit)
820 finally:
821 self.update_all()
823 def line_paste_chain(self):
824 op, exit = self.line_menu_line
825 self.paste_chain(op, exit)
827 def line_add_block(self):
828 op, exit = self.line_menu_line
829 op.link_to(Block(op.parent), exit)
831 def line_toggle_breakpoint(self):
832 op, exit = self.line_menu_line
833 bp = self.view.breakpoints
834 if bp.has_key((op, exit)):
835 del bp[(op, exit)]
836 else:
837 bp[(op, exit)] = 1
838 self.prog.changed()
840 def line_yank_chain(self):
841 op, exit = self.line_menu_line
842 next = getattr(op, exit)
843 if not next:
844 rox.alert('Nothing to yank!')
845 return
846 self.clipboard = op.to_doc()
847 print self.clipboard
849 def line_del_chain(self):
850 op, exit = self.line_menu_line
851 next = getattr(op, exit)
852 if not next:
853 rox.alert('Nothing to delete!')
854 return
855 self.clipboard = next.to_doc()
856 op.unlink(exit)
858 def line_event(self, item, event, op, exit):
859 # Item may be rec_point or exec_point...
860 item = getattr(self.op_to_group[op], exit + '_line')
862 if event.type == g.gdk.BUTTON_PRESS:
863 if event.button == 1:
864 if not getattr(op, exit):
865 self.drag_last_pos = (event.x, event.y)
866 #item.grab(BUTTON_RELEASE | MOTION_NOTIFY, no_cursor, event.time)
867 elif event.button == 2:
868 self.paste_chain(op, exit)
869 elif event.button == 3:
870 self.line_menu_line = (op, exit)
871 line_menu.popup(self, event)
872 elif event.type == g.gdk.BUTTON_RELEASE:
873 if event.button == 1:
874 print "Clicked exit %s of %s" % (exit, op)
875 #item.ungrab(event.time)
876 self.view.set_exec((op, exit))
877 self.drag_last_pos = None
878 if not getattr(op, exit):
879 self.end_link_drag(item, event, op, exit)
880 elif event.type == g.gdk.MOTION_NOTIFY and self.drag_last_pos:
881 if not event.state & g.gdk.BUTTON1_MASK:
882 print "(stop drag!)"
883 self.drag_last_pos = None
884 if not getattr(op, exit):
885 self.end_link_drag(item, event, op, exit)
886 return 1
887 x, y = (event.x, event.y)
888 dx, dy = x - self.drag_last_pos[0], y - self.drag_last_pos[1]
890 if abs(dx) > 4 or abs(dy) > 4:
891 sx, sy = self.get_arrow_start(op, exit)
892 x, y = item.w2i(event.x, event.y)
893 gr = self.op_to_group[op]
894 if exit == 'fail':
895 width = gr.width
896 else:
897 width = 0
898 item.set(points = connect(sx, sy, x, y))
899 elif event.type == g.gdk.ENTER_NOTIFY:
900 item.set(fill_color = 'white')
901 elif event.type == g.gdk.LEAVE_NOTIFY:
902 if exit == 'next':
903 item.set(fill_color = 'black')
904 else:
905 item.set(fill_color = '#ff6666')
906 return 1
908 def get_arrow_start(self, op, exit):
909 gr = self.op_to_group[op]
910 return ((exit == 'fail' and gr.width) or 0, gr.height)
912 def get_arrow_ends(self, op, exit):
913 """Return coords of arrow, relative to op's group."""
914 op2 = getattr(op, exit)
916 prev_group = self.op_to_group[op]
918 x1, y1 = self.get_arrow_start(op, exit)
920 if op2:
921 try:
922 group = self.op_to_group[op2]
923 except:
924 x2 = x1 + 50
925 y2 = y1 + 50
926 else:
927 x2, y2 = group.i2w(0, 0)
928 x2, y2 = prev_group.w2i(x2, y2)
929 elif exit == 'next':
930 x2, y2 = DEFAULT_NEXT
931 x2 += x1
932 y2 += y1
933 else:
934 x2, y2 = DEFAULT_FAIL
935 x2 += x1
936 y2 += y1
937 return (x1, y1, x2, y2)
939 def set_bounds(self):
940 #self.update_now() # GnomeCanvas bug?
941 min_x, min_y, max_x, max_y = self.root().get_bounds()
942 min_x -= 8
943 max_x += 8
944 min_y -= 8
945 max_y += 8
946 self.set_scroll_region(min_x, min_y, max_x, max_y)
947 self.root().move(0, 0) # Magic!
948 #self.set_usize(max_x - min_x, -1)
950 def canvas_to_world(self, (x, y)):
951 "Canvas routine seems to be broken..."
952 mx, my, maxx, maxy = self.get_scroll_region()
953 sx = self.get_hadjustment().value
954 sy = self.get_hadjustment().value
955 return (x + mx + sx , y + my + sy)
957 class ChainWindow(rox.Window):
958 def __init__(self, view, prog):
959 rox.Window.__init__(self)
960 swin = g.ScrolledWindow()
961 self.add(swin)
962 disp = ChainDisplay(view, prog)
963 swin.add(disp)
965 swin.show_all()
966 self.disp = disp
967 self.set_default_size(-1, 200)
968 self.set_title(prog.name)
970 def update_points(self):
971 self.disp.update_points()