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