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