Breakpoint shows correct program again.
[dom-editor.git] / Dome / List.py
blob44673c015320e97266acc34c2b23c4aa49cc2bd1
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_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 path = model.get_value(iter, 1)
157 self.chains.switch_to(self.view.name_to_prog(path))
159 sel.connect('changed', change_prog)
161 self.chains = ChainDisplay(view)
162 self.prog_tree_changed()
163 v = g.Viewport()
164 v.add(tree)
165 swin.add(v)
166 v.set_shadow_type(g.SHADOW_NONE)
167 v.show_all()
169 swin = g.ScrolledWindow()
170 swin.set_policy(g.POLICY_AUTOMATIC, g.POLICY_AUTOMATIC)
171 pane.add2(swin)
172 swin.add(self.chains)
173 swin.show_all()
175 pane.set_position(200)
177 sel.set_mode(g.SELECTION_BROWSE)
178 root_iter = self.prog_model.get_iter_first()
179 sel.select_iter(root_iter)
180 tree.expand_row(self.prog_model.get_path(root_iter), FALSE)
181 tree.show()
182 self.view.lists.append(self)
183 self.view.model.root_program.watchers.append(self)
185 def set_innermost_failure(self, op):
186 self.show_prog(op.get_program())
188 def destroy(self):
189 self.view.lists.remove(self)
190 self.view.model.root_program.watchers.remove(self)
192 def update_points(self):
193 self.chains.update_points()
194 for x in self.sub_windows:
195 x.update_points()
197 def program_changed(self, op):
198 pass
200 def prog_tree_changed(self):
201 self.prog_to_path = {}
202 self.prog_model.clear()
203 self.build_tree(self.view.model.root_program)
205 # Check for now deleted programs still being displayed
206 root = self.view.model.root_program
207 if self.chains and self.chains.prog and not self.chains.prog.parent:
208 self.chains.switch_to(None)
209 for x in self.sub_windows:
210 if x.disp.prog is not root and not x.disp.prog.parent:
211 x.destroy()
213 def build_tree(self, prog, iter = None):
214 child_iter = self.prog_model.append(iter)
215 self.prog_model.set(child_iter, 0, prog.name,
216 1, prog.get_path())
218 self.prog_to_path[prog] = self.prog_model.get_path(child_iter)
219 for p in prog.subprograms.values():
220 self.build_tree(p, child_iter)
222 def run_return(self, exit):
223 if exit != 'next':
224 print "run_return: failure!"
225 self.view.jump_to_innermost_failure()
226 def cb(choice, self = self):
227 if choice == 0:
228 self.view.record_at_point()
229 box = MultipleChoice("Program failed - record a failure case?",
230 [('Record', self.view.record_at_point), 'Cancel'])
231 box.set_title('Dome')
232 box.show()
233 print "List: execution done!"
235 def button_press(self, tree, event):
236 if event.button == 2 or event.button == 3:
237 path, col, cx, cy = tree.get_path_at_pos(event.x, event.y)
238 print "Event on", path
239 iter = self.prog_model.get_iter(path)
240 path = self.prog_model.get_value(iter, 1)
241 if event.button == 3:
242 prog = self.view.name_to_prog(path)
243 self.show_menu(event, prog)
244 else:
245 self.view.run_new(self.run_return)
246 if event.state & g.gdk.SHIFT_MASK:
247 self.view.may_record(['map', path])
248 else:
249 self.view.may_record(['play', path])
250 return 0
252 def show_menu(self, event, prog):
253 def del_prog(self = self, prog = prog):
254 parent = prog.parent
255 prog.parent.remove_sub(prog)
256 def rename_prog(prog = prog):
257 def rename(name, prog = prog):
258 prog.rename(name)
259 GetArg('Rename program', rename, ['Program name:'])
260 def new_prog(model = self.view.model, prog = prog):
261 def create(name, model = model, prog = prog):
262 new = Program(name)
263 prog.add_sub(new)
264 GetArg('New program', create, ['Program name:'])
266 view = self.view
267 if prog.parent:
268 dp = del_prog
269 else:
270 dp = None
271 name = prog.get_path()
272 def do(play, self = self, name = name):
273 def ret(play = play, self = self, name = name):
274 self.view.run_new(self.run_return)
275 self.view.may_record([play, name])
276 return ret
278 def new_view(self = self, view = view, prog = prog):
279 cw = ChainWindow(view, prog)
280 cw.show()
281 self.sub_windows.append(cw)
282 def lost_cw(win, self = self, cw = cw):
283 self.sub_windows.remove(cw)
284 print "closed"
285 cw.connect('destroy', lost_cw)
287 prog_menu.popup(self, event)
289 def update_stack(self, op):
290 "The stack has changed - redraw 'op'"
291 if op and op.get_program() == self.chains.prog:
292 self.chains.update_all()
293 l = len(self.view.exec_stack) + len(self.view.foreach_stack)
294 if l == 0:
295 text = 'No stack'
296 elif l == 1:
297 text = '1 frame'
298 else:
299 text = '%d frames' % l
300 if self.view.chroots:
301 text += ' (%d enters)' % len(self.view.chroots)
302 self.stack_frames.set_text(text)
304 def show_prog(self, prog):
305 path = self.prog_to_path[prog]
306 iter = self.prog_model.get_iter(path)
307 self.tree.get_selection().select_iter(iter)
309 class ChainDisplay(canvas.Canvas):
310 "A graphical display of a chain of nodes."
311 def __init__(self, view, prog = None):
312 canvas.Canvas.__init__(self)
313 self.connect('destroy', self.destroyed)
314 self.view = view
315 self.unset_flags(g.CAN_FOCUS)
317 self.drag_last_pos = None
319 self.exec_point = None # CanvasItem, or None
320 self.rec_point = None
322 s = self.get_style().copy()
323 s.bg[g.STATE_NORMAL] = g.gdk.color_parse('light green')
324 self.set_style(s)
326 self.nodes = None
327 self.subs = None
328 self.set_size_request(100, 100)
330 self.prog = None
332 self.view.model.root_program.watchers.append(self)
334 self.switch_to(prog)
336 def update_points(self):
337 self.put_point('rec_point')
338 self.put_point('exec_point')
340 if self.rec_point:
341 self.scroll_to_show(self.rec_point)
343 def scroll_to_show(self, item):
344 (lx, ly, hx, hy) = item.get_bounds()
345 x, y = item.i2w(0, 0)
346 x, y = self.w2c(x, y)
347 lx += x
348 ly += y
349 hx += x
350 hy += y
351 lx -= 16
353 sx, sy = self.get_scroll_offsets()
354 if lx < sx:
355 sx = lx
356 if ly < sy:
357 sy = ly
359 (x, y, w, h) = self.get_allocation()
360 hx -= w
361 hy -= h
363 if hx > sx:
364 sx = hx
365 if hy > sy:
366 sy = hy
368 self.scroll_to(sx, sy)
370 def put_point(self, point):
371 item = getattr(self, point)
372 if item:
373 item.destroy()
374 setattr(self, point, None)
376 if not self.prog:
377 return
379 opexit = getattr(self.view, point)
380 if point == 'exec_point' and self.view.op_in_progress:
381 opexit = (self.view.op_in_progress, None)
382 if opexit:
383 g = None
384 (op, exit) = opexit
385 if op.get_program() != self.prog:
386 return
387 try:
388 g = self.op_to_group[op]
389 except KeyError:
390 pass
391 if point == 'rec_point':
392 c = 'red'
393 s = 6
394 else:
395 c = 'yellow'
396 s = 3
397 item = self.root().add(canvas.CanvasRect,
398 x1 = -s, x2 = s, y1 = -s, y2 = s,
399 fill_color = c,
400 outline_color = 'black', width_pixels = 1)
401 setattr(self, point, item)
402 item.connect('event', self.line_event, op, exit)
404 if g and exit:
405 # TODO: cope with exit == None
406 x1, y1, x2, y2 = self.get_arrow_ends(op, exit)
407 x1, y1 = g.i2w(x1, y1)
408 x2, y2 = g.i2w(x2, y2)
409 item.move((x1 + x2) / 2, (y1 + y2) / 2)
411 def destroyed(self, widget):
412 p = self.prog
413 while p.parent:
414 p = p.parent
415 self.view.model.root_program.watchers.remove(self)
416 print "(ChainDisplay destroyed)"
418 def switch_to(self, prog):
419 if prog is self.prog:
420 return
421 self.prog = prog
422 self.update_all()
424 def prog_tree_changed(self):
425 pass
427 def program_changed(self, op):
428 if (not op) or op.get_program() == self.prog:
429 self.update_all()
431 def update_all(self):
432 if self.nodes:
433 self.nodes.destroy()
435 self.op_to_group = {}
436 self.nodes = self.root().add(canvas.CanvasGroup, x = 0, y = 0)
437 if self.prog:
438 self.create_node(self.prog.code, self.nodes)
439 self.update_links()
440 self.update_points()
442 self.set_bounds()
444 return 1
446 def op_colour(self, op):
447 if op in self.view.exec_stack:
448 return 'cyan'
449 return 'blue'
451 def update_links(self, op = None):
452 """Walk through all nodes in the tree-version of the op graph,
453 making all the links (which already exist as stubs) point to
454 the right place."""
455 if not self.prog:
456 return
457 if not op:
458 op = self.prog.code
459 if op.next:
460 if op.next.prev[0] == op:
461 self.update_links(op.next)
462 else:
463 self.join_nodes(op, 'next')
464 if op.fail:
465 if op.fail.prev[0] == op:
466 self.update_links(op.fail)
467 else:
468 self.join_nodes(op, 'fail')
469 if isinstance(op, Block):
470 self.update_links(op.start)
472 def create_node(self, op, group):
473 self.op_to_group[op] = group
475 if isinstance(op, Block):
476 gr = group.add(canvas.CanvasGroup, x = 0, y = 0)
477 self.create_node(op.start, gr)
478 #self.update_now() # GnomeCanvas bug?
479 (lx, ly, hx, hy) = gr.get_bounds()
480 minx = lx - 4
481 if op.foreach:
482 minx -= 8
483 border = gr.add(canvas.CanvasRect, x1 = minx, x2 = hx + 4, y1 = ly + 4, y2 = hy + 4,
484 outline_color = 'black', width_pixels = 1)
485 border.lower_to_bottom()
486 if op.foreach:
487 gr.add(canvas.CanvasRect, x1 = minx, x2 = minx + 8, y1 = ly + 4, y2 = hy + 4,
488 fill_color = 'blue').lower_to_bottom()
489 if op.enter:
490 colour = 'yellow'
491 gr.add(canvas.CanvasRect, x1 = minx, x2 = hx + 4, y1 = ly + 5, y2 = ly + 13,
492 fill_color = colour).lower_to_bottom()
493 gr.add(canvas.CanvasRect, x1 = minx, x2 = hx + 4, y1 = hy - 3, y2 = hy + 3,
494 fill_color = colour).lower_to_bottom()
495 if op.restore:
496 colour = 'orange'
497 margin = op.enter * 8
498 gr.add(canvas.CanvasRect, x1 = minx, x2 = hx + 4, y1 = ly + 5 + margin, y2 = ly + 13 + margin,
499 fill_color = colour).lower_to_bottom()
500 gr.add(canvas.CanvasRect, x1 = minx, x2 = hx + 4, y1 = hy - 3 - margin, y2 = hy + 3 - margin,
501 fill_color = colour).lower_to_bottom()
502 next_off_y = 0
503 group.width, group.height = hx, hy
504 if op.is_toplevel():
505 return
506 else:
507 if op.action[0] == 'Start':
508 text = str(op.parent.comment.replace('\\n', '\n'))
509 text_y = 0
510 #text_font = '-misc-fixed-bold-r-normal-*-*-120-*-*-c-*-iso8859-1'
511 text_col = 'dark blue'
512 else:
513 text = str(action_to_text(op.action))
514 text_y = -8
515 #text_font = '-misc-fixed-medium-r-normal-*-*-120-*-*-c-*-iso8859-1'
516 text_col = 'black'
518 group.ellipse = group.add(canvas.CanvasEllipse,
519 fill_color = self.op_colour(op),
520 outline_color = 'black',
521 x1 = -4, x2 = 4,
522 y1 = -4, y2 = 4,
523 width_pixels = 1)
524 group.ellipse.connect('event', self.op_event, op)
525 if text:
526 label = group.add(canvas.CanvasText,
527 x = -8,
528 y = text_y,
529 anchor = g.ANCHOR_NE,
530 justification = 'right',
531 fill_color = text_col,
532 text = text)
534 #self.update_now() # GnomeCanvas bug?
535 (lx, ly, hx, hy) = label.get_bounds()
536 next_off_y = hy
537 else:
538 next_off_y = 0
539 group.width, group.height = 0, 0
541 if op.next and op.next.prev[0] == op:
542 sx, sy = self.get_arrow_start(op, 'next')
543 gr = group.add(canvas.CanvasGroup, x = 0, y = 0)
544 self.create_node(op.next, gr)
545 #self.update_now() # GnomeCanvas bug?
546 (lx, ly, hx, hy) = gr.get_bounds()
547 drop = max(20, next_off_y + 10)
548 y = drop - ly
549 next = op.next
550 while isinstance(next, Block):
551 next = next.start
552 x = next.dx
553 y += next.dy
554 gr.move(sx + x, sy + y)
556 group.next_line = group.add(canvas.CanvasLine,
557 fill_color = 'black',
558 points = connect(0, 0, 1, 1),
559 width_pixels = 4,
560 last_arrowhead = 1,
561 arrow_shape_a = 5,
562 arrow_shape_b = 5,
563 arrow_shape_c = 5)
564 group.next_line.connect('event', self.line_event, op, 'next')
566 (x, y) = DEFAULT_FAIL
567 if op.fail and op.fail.prev[0] == op:
568 sx, sy = self.get_arrow_start(op, 'fail')
569 y = 46
570 gr = group.add(canvas.CanvasGroup, x = 0, y = 0)
571 self.create_node(op.fail, gr)
572 #self.update_now() # GnomeCanvas bug?
573 (lx, ly, hx, hy) = gr.get_bounds()
574 x = 20 - lx
575 fail = op.fail
576 while isinstance(fail, Block):
577 fail = fail.start
578 x += fail.dx
579 y += fail.dy
580 gr.move(sx + x, sy + y)
581 group.fail_line = group.add(canvas.CanvasLine,
582 fill_color = '#ff6666',
583 points = connect(0, 0, 1, 1),
584 width_pixels = 4,
585 last_arrowhead = 1,
586 arrow_shape_a = 5,
587 arrow_shape_b = 5,
588 arrow_shape_c = 5)
589 group.fail_line.lower_to_bottom()
590 group.fail_line.connect('event', self.line_event, op, 'fail')
591 if op.action[0] == 'Start':
592 group.fail_line.hide()
594 self.join_nodes(op, 'next')
595 self.join_nodes(op, 'fail')
597 if self.view.breakpoints.has_key((op, 'next')):
598 group.next_line.set(line_style = g.gdk.LINE_ON_OFF_DASH)
599 if self.view.breakpoints.has_key((op, 'fail')):
600 group.fail_line.set(line_style = g.gdk.LINE_ON_OFF_DASH)
602 def edit_op(self, op):
603 def modify():
604 if op.action[0] == 'do_search' or op.action[0] == 'do_global':
605 t = editables[0].get_text()
606 print "Checking", t
607 from Ft.Xml.XPath import XPathParser
608 if t.find('@CURRENT@') == -1:
609 try:
610 XPathParser.new().parse(t)
611 except:
612 alert('Invalid search pattern!')
613 return
614 i = 0
615 for e in editables:
616 i += 1
617 if e:
618 op.action[i] = e.get_text()
619 op.changed()
620 print "Done editing!"
621 win.destroy()
623 win = g.Dialog()
624 win.vbox.pack_start(g.Label(op.action[0]), TRUE, FALSE, 0)
625 editables = [] # [ Entry | None ]
626 focus = None
627 for x in op.action[1:]:
628 entry = g.Entry()
629 entry.set_text(str(x))
630 win.vbox.pack_start(entry, TRUE, FALSE, 0)
631 if type(x) == str or type(x) == unicode:
632 editables.append(entry)
633 entry.connect('activate', lambda e: modify())
634 if not focus:
635 focus = entry
636 entry.grab_focus()
637 else:
638 entry.set_editable(FALSE)
639 editables.append(None)
641 win.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
642 win.add_button(g.STOCK_OK, g.RESPONSE_OK)
644 def response(box, resp):
645 box.destroy()
646 if resp == g.RESPONSE_OK:
647 modify()
648 win.connect('response', response)
650 if not focus:
651 win.set_response_sensitive(g.RESPONSE_OK, FALSE)
653 win.show_all()
655 def join_nodes(self, op, exit):
656 try:
657 x1, y1, x2, y2 = self.get_arrow_ends(op, exit)
659 prev_group = self.op_to_group[op]
660 line = getattr(prev_group, exit + '_line')
661 line.set(points = connect(x1, y1, x2, y2))
662 except Block:
663 print "*** ERROR setting arc from %s:%s" % (op, exit)
665 def op_event(self, item, event, op):
666 if event.type == g.gdk.BUTTON_PRESS:
667 print "Prev %s = %s" % (op, map(str, op.prev))
668 if event.button == 1:
669 if op.parent.start != op or not op.parent.is_toplevel():
670 self.drag_last_pos = (event.x, event.y)
671 else:
672 self.drag_last_pos = None
673 else:
674 self.show_op_menu(event, op)
675 elif event.type == g.gdk.BUTTON_RELEASE:
676 if event.button == 1:
677 self.drag_last_pos = None
678 self.program_changed(None)
679 elif event.type == g.gdk.ENTER_NOTIFY:
680 item.set(fill_color = 'white')
681 elif event.type == g.gdk.LEAVE_NOTIFY:
682 item.set(fill_color = self.op_colour(op))
683 elif event.type == g.gdk.MOTION_NOTIFY and self.drag_last_pos:
684 if not event.state & g.gdk.BUTTON1_MASK:
685 print "(stop drag!)"
686 self.drag_last_pos = None
687 self.program_changed(None)
688 return 1
689 x, y = (event.x, event.y)
690 dx, dy = x - self.drag_last_pos[0], y - self.drag_last_pos[1]
691 if abs(op.dx + dx) < 4:
692 dx = -op.dx
693 x = dx + self.drag_last_pos[0]
694 if abs(op.dy + dy) < 4:
695 dy = -op.dy
696 y = dy + self.drag_last_pos[1]
697 op.dx += dx
698 op.dy += dy
699 self.drag_last_pos = (x, y)
701 self.op_to_group[op].move(dx, dy)
702 for p in op.prev:
703 if p.next == op:
704 self.join_nodes(p, 'next')
705 if p.fail == op:
706 self.join_nodes(p, 'fail')
707 self.update_links()
708 #self.create_node(self.prog.start, self.nodes)
709 self.update_points()
710 elif event.type == g.gdk._2BUTTON_PRESS:
711 if op.action[0] == 'Start':
712 self.edit_comment(op.parent)
713 else:
714 self.edit_op(op)
715 print "(edit; stop drag!)"
716 self.drag_last_pos = None
717 self.program_changed(None)
718 return 1
720 def edit_comment(self, block):
721 assert isinstance(block, Block)
723 def set(comment):
724 block.set_comment(comment)
725 GetArg('Comment', set, ['Comment:'],
726 message = '\\n for a newline', init = [block.comment])
728 def block_toggle_enter(self):
729 self.op_menu_op.toggle_enter()
731 def block_toggle_foreach(self):
732 self.op_menu_op.toggle_foreach()
734 def block_toggle_restore(self):
735 self.op_menu_op.toggle_restore()
737 def block_edit_comment(self):
738 self.edit_comment(self.op_menu_op)
740 def op_edit(self):
741 self.edit_op(self.op_menu_op)
743 def op_swap_nf(self):
744 self.op_menu_op.swap_nf()
746 def op_del_node(self):
747 op = self.op_menu_op
748 if op.next and op.fail:
749 rox.alert("Can't delete a node with both exits in use")
750 return
751 self.clipboard = op.del_node()
753 def show_op_menu(self, event, op):
754 if op.action[0] == 'Start':
755 self.op_menu_op = op.parent
756 block_menu.popup(self, event)
757 else:
758 self.op_menu_op = op
759 op_menu.popup(self, event)
761 def paste_chain(self, op, exit):
762 print "Paste", self.clipboard
763 doc = self.clipboard
764 new = load(doc.documentElement, op.parent)
765 start = new.start.next
766 new.start.unlink('next', may_delete = 0)
767 start.set_parent(None)
768 op.link_to(start, exit)
770 def end_link_drag(self, item, event, src_op, exit):
771 # Scan all the nodes looking for one nearby...
772 x, y = event.x, event.y
774 def closest_node(op):
775 "Return the closest (node, dist) in this chain to (x, y)"
776 nx, ny = self.op_to_group[op].i2w(0, 0)
777 if op is src_op:
778 best = None
779 elif isinstance(op, Block):
780 best = None
781 else:
782 best = (op, math.hypot(nx - x, ny - y))
783 if op.next and op.next.prev[0] == op:
784 next = closest_node(op.next)
785 if next and (best is None or next[1] < best[1]):
786 best = next
787 if op.fail and op.fail.prev[0] == op:
788 fail = closest_node(op.fail)
789 if fail and (best is None or fail[1] < best[1]):
790 best = fail
791 if isinstance(op, Block):
792 sub = closest_node(op.start)
793 if sub and (best is None or sub[1] < best[1]):
794 best = sub
795 return best
797 result = closest_node(self.prog.code)
798 if result:
799 node, dist = result
800 else:
801 dist = 1000
802 if dist > 12:
803 # Too far... put the line back to the disconnected state...
804 self.join_nodes(src_op, exit)
805 return
806 try:
807 while node.action[0] == 'Start':
808 node = node.parent
809 src_op.link_to(node, exit)
810 finally:
811 self.update_all()
813 def line_paste_chain(self):
814 op, exit = self.line_menu_line
815 self.paste_chain(op, exit)
817 def line_add_block(self):
818 op, exit = self.line_menu_line
819 op.link_to(Block(op.parent), exit)
821 def line_toggle_breakpoint(self):
822 op, exit = self.line_menu_line
823 bp = self.view.breakpoints
824 if bp.has_key((op, exit)):
825 del bp[(op, exit)]
826 else:
827 bp[(op, exit)] = 1
828 self.prog.changed()
830 def line_yank_chain(self):
831 op, exit = self.line_menu_line
832 next = getattr(op, exit)
833 if not next:
834 rox.alert('Nothing to yank!')
835 return
836 self.clipboard = op.to_doc()
837 print self.clipboard
839 def line_del_chain(self):
840 op, exit = self.line_menu_line
841 next = getattr(op, exit)
842 if not next:
843 rox.alert('Nothing to delete!')
844 return
845 self.clipboard = next.to_doc()
846 op.unlink(exit)
848 def line_event(self, item, event, op, exit):
849 # Item may be rec_point or exec_point...
850 item = getattr(self.op_to_group[op], exit + '_line')
852 if event.type == g.gdk.BUTTON_PRESS:
853 if event.button == 1:
854 if not getattr(op, exit):
855 self.drag_last_pos = (event.x, event.y)
856 #item.grab(BUTTON_RELEASE | MOTION_NOTIFY, no_cursor, event.time)
857 elif event.button == 2:
858 self.paste_chain(op, exit)
859 elif event.button == 3:
860 self.line_menu_line = (op, exit)
861 line_menu.popup(self, event)
862 elif event.type == g.gdk.BUTTON_RELEASE:
863 if event.button == 1:
864 print "Clicked exit %s of %s" % (exit, op)
865 #item.ungrab(event.time)
866 self.view.set_exec((op, exit))
867 self.drag_last_pos = None
868 if not getattr(op, exit):
869 self.end_link_drag(item, event, op, exit)
870 elif event.type == g.gdk.MOTION_NOTIFY and self.drag_last_pos:
871 if not event.state & g.gdk.BUTTON1_MASK:
872 print "(stop drag!)"
873 self.drag_last_pos = None
874 if not getattr(op, exit):
875 self.end_link_drag(item, event, op, exit)
876 return 1
877 x, y = (event.x, event.y)
878 dx, dy = x - self.drag_last_pos[0], y - self.drag_last_pos[1]
880 if abs(dx) > 4 or abs(dy) > 4:
881 sx, sy = self.get_arrow_start(op, exit)
882 x, y = item.w2i(event.x, event.y)
883 gr = self.op_to_group[op]
884 if exit == 'fail':
885 width = gr.width
886 else:
887 width = 0
888 item.set(points = connect(sx, sy, x, y))
889 elif event.type == g.gdk.ENTER_NOTIFY:
890 item.set(fill_color = 'white')
891 elif event.type == g.gdk.LEAVE_NOTIFY:
892 if exit == 'next':
893 item.set(fill_color = 'black')
894 else:
895 item.set(fill_color = '#ff6666')
896 return 1
898 def get_arrow_start(self, op, exit):
899 gr = self.op_to_group[op]
900 return ((exit == 'fail' and gr.width) or 0, gr.height)
902 def get_arrow_ends(self, op, exit):
903 """Return coords of arrow, relative to op's group."""
904 op2 = getattr(op, exit)
906 prev_group = self.op_to_group[op]
908 x1, y1 = self.get_arrow_start(op, exit)
910 if op2:
911 try:
912 group = self.op_to_group[op2]
913 except:
914 x2 = x1 + 50
915 y2 = y1 + 50
916 else:
917 x2, y2 = group.i2w(0, 0)
918 x2, y2 = prev_group.w2i(x2, y2)
919 elif exit == 'next':
920 x2, y2 = DEFAULT_NEXT
921 x2 += x1
922 y2 += y1
923 else:
924 x2, y2 = DEFAULT_FAIL
925 x2 += x1
926 y2 += y1
927 return (x1, y1, x2, y2)
929 def set_bounds(self):
930 #self.update_now() # GnomeCanvas bug?
931 min_x, min_y, max_x, max_y = self.root().get_bounds()
932 min_x -= 8
933 max_x += 8
934 min_y -= 8
935 max_y += 8
936 self.set_scroll_region(min_x, min_y, max_x, max_y)
937 self.root().move(0, 0) # Magic!
938 #self.set_usize(max_x - min_x, -1)
940 def canvas_to_world(self, (x, y)):
941 "Canvas routine seems to be broken..."
942 mx, my, maxx, maxy = self.get_scroll_region()
943 sx = self.get_hadjustment().value
944 sy = self.get_hadjustment().value
945 return (x + mx + sx , y + my + sy)
947 class ChainWindow(rox.Window):
948 def __init__(self, view, prog):
949 rox.Window.__init__(self)
950 swin = g.ScrolledWindow()
951 self.add(swin)
952 disp = ChainDisplay(view, prog)
953 swin.add(disp)
955 swin.show_all()
956 self.disp = disp
957 self.set_default_size(-1, 200)
958 self.set_title(prog.name)
960 def update_points(self):
961 self.disp.update_points()