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