Send rox-changed instead of row-deleted/row-inserted.
[dom-editor.git] / Dome / List.py
blobeb1d6e14bf071a6fc7e3342db2b595733cf8a355
1 from __future__ import generators
3 import rox
4 from rox import g, TRUE, FALSE, alert
5 from gnome import canvas
7 from support import *
8 import string
9 from StringIO import StringIO
10 import math
11 import View
13 from rox.Menu import Menu
15 import __main__
16 mono = __main__.mono
18 prog_menu = Menu('programs', [
19 ('/Play', 'menu_play', '', ''),
20 ('/Map', 'menu_map', '', ''),
21 ('/View', 'menu_new_view', '', ''),
22 ('/', '', '', '<separator>'),
23 ('/New program', 'menu_new_prog', '', ''),
24 ('/Rename', 'menu_rename', '', ''),
25 ('/Delete', 'menu_delete', '', ''),
28 line_menu = Menu('line', [
29 ('/Set\/clear breakpoint', 'line_toggle_breakpoint', '', ''),
30 ('/Yank chain', 'line_yank_chain', '', ''),
31 ('/Remove link', 'line_del_chain', '', ''),
32 ('/Paste chain', 'line_paste_chain', '', ''),
33 ('/Add block', 'line_add_block', '', '')
36 block_menu = Menu('op', [
37 ('/Toggle Enter\/Leave', 'block_toggle_enter', '', ''),
38 ('/Toggle Foreach','block_toggle_foreach', '', ''),
39 ('/Toggle Restore Mark','block_toggle_restore', '', ''),
40 ('/Edit comment', 'block_edit_comment', '', ''),
41 ('/Swap next\/fail', 'op_swap_nf', '', ''),
42 ('/Remove node', 'op_del_node', '', '')
45 op_menu = Menu('op', [
46 ('/Edit node', 'op_edit', '', ''),
47 ('/Swap next\/fail', 'op_swap_nf', '', ''),
48 ('/Remove node', 'op_del_node', '', '')
51 from GetArg import GetArg
52 from Program import Program, load, Block
54 no_cursor = g.gdk.Cursor(g.gdk.TCROSS)
56 def trunc(text):
57 if len(text) < 28:
58 return text
59 return text[:26] + '...'
61 def connect(x1, y1, x2, y2):
62 """Chop 5 pixels off both ends of this line"""
63 gap = 5.0
64 dx = x2 - x1
65 dy = y2 - y1
66 l = math.hypot(dx, dy)
67 if l:
68 dx *= gap / l
69 dy *= gap / l
70 return (x1 + dx, y1 + dy, x2 - dx, y2 - dy)
72 DEFAULT_NEXT = (0, 25)
73 DEFAULT_FAIL = (20, 20)
75 expand_history = {} # Prog name -> expanded flag
77 def action_to_text(action):
78 text = action[0]
79 if text == 'Start': return ''
80 if text[:3] == 'do_':
81 text = text[3:]
82 text = string.capitalize(string.replace(text, '_', ' '))
83 if text == 'Global':
84 text = 'Select nodes'
86 if len(action) > 1:
87 if action[0] == 'do_search' or action[0] == 'xpath':
88 pat = str(action[1])
89 pat = string.replace(pat, 'following-sibling::', '>>')
90 pat = string.replace(pat, 'preceding-sibling::', '<<')
91 pat = string.replace(pat, 'child::', '')
92 pat = string.replace(pat, '[1]', '')
93 pat = string.replace(pat, 'text()[ext:match', '[')
94 details = ''
95 while len(pat) > 20:
96 i = string.rfind(pat[:20], '/')
97 if i == -1:
98 i = string.rfind(pat[:20], ':')
99 if i == -1:
100 i = 20
101 details = details + pat[:i + 1] + '\n'
102 pat = pat[i + 1:]
103 details = details + pat
104 elif action[0] == 'attribute':
105 details = trunc(str(action[2]))
106 elif action[0] == 'set_attrib':
107 details = trunc(str(action[1]))
108 elif action[0] == 'add_attrib':
109 details = trunc(str(action[2]))
110 elif action[0] == 'add_node':
111 details = trunc(action[2])
112 elif action[0] == 'subst':
113 details = action[1] + ' -> ' + action[2]
114 elif action[0] == 'play' or action[0] == 'map':
115 if len(action[1]) > 20:
116 details = '...' + str(action[1][-19:])
117 else:
118 details = str(action[1])
119 else:
120 if len(action) > 2:
121 details = `action[1:]`
122 else:
123 details = str(action[1])
124 if len(details) > 20:
125 details = trunc(`details`)
126 text = text + '\n' + details
127 return text
129 class List(g.VBox):
130 def __init__(self, view):
131 g.VBox.__init__(self)
133 def destroyed(widget):
134 print "List destroy!!"
135 sel.disconnect(self.sel_changed_signal)
136 self.view.lists.remove(self)
137 self.view.model.root_program.watchers.remove(self)
138 self.connect('destroy', destroyed)
140 self.view = view
141 self.sub_windows = []
143 self.stack_frames = g.Label('')
144 self.pack_start(self.stack_frames, FALSE, TRUE, 0)
145 self.stack_frames.show()
146 self.update_stack(None)
148 pane = g.VPaned()
149 self.pack_start(pane, expand = 1, fill = 1)
151 swin = g.ScrolledWindow()
152 swin.set_policy(g.POLICY_NEVER, g.POLICY_AUTOMATIC)
153 pane.add1(swin)
154 self.prog_model = g.TreeStore(str, str)
155 tree = g.TreeView(self.prog_model)
156 tree.connect('button-press-event', self.button_press)
157 tree.unset_flags(g.CAN_FOCUS)
158 tree.set_headers_visible(FALSE)
159 self.tree = tree
161 cell = g.CellRendererText()
162 column = g.TreeViewColumn('Program', cell, text = 0)
163 tree.append_column(column)
165 sel = tree.get_selection()
166 # Doesn't get destroyed, so record signal number
167 self.sel_changed_signal = sel.connect('changed', self.change_prog)
169 self.chains = ChainDisplay(view)
170 self.prog_tree_changed()
171 v = g.Viewport()
172 v.add(tree)
173 swin.add(v)
174 v.set_shadow_type(g.SHADOW_NONE)
175 v.show_all()
177 swin = g.ScrolledWindow()
178 swin.set_policy(g.POLICY_AUTOMATIC, g.POLICY_AUTOMATIC)
179 pane.add2(swin)
180 swin.add_with_viewport(self.chains)
181 swin.show_all()
183 pane.set_position(200)
185 sel.set_mode(g.SELECTION_BROWSE)
186 root_iter = self.prog_model.get_iter_first()
187 sel.select_iter(root_iter)
188 tree.expand_row(self.prog_model.get_path(root_iter), FALSE)
189 tree.show()
190 self.view.lists.append(self)
191 self.view.model.root_program.watchers.append(self)
193 def change_prog(self, sel):
194 selected = sel.get_selected()
195 if not selected:
196 return
197 model, iter = selected
198 if iter:
199 path = model.get_value(iter, 1)
200 self.chains.switch_to(self.view.name_to_prog(path))
201 else:
202 self.chains.switch_to(None)
204 def set_innermost_failure(self, op):
205 prog = op.get_program()
206 print "list: set_innermost_failure:", prog
207 self.show_prog(prog)
209 def update_points(self):
210 self.chains.update_points()
211 for x in self.sub_windows:
212 x.update_points()
214 def program_changed(self, op):
215 pass
217 def prog_tree_changed(self):
218 self.prog_to_path = {}
219 self.prog_model.clear()
220 self.build_tree(self.view.model.root_program)
222 # Check for now deleted programs still being displayed
223 root = self.view.model.root_program
224 if self.chains and self.chains.prog and not self.chains.prog.parent:
225 self.chains.switch_to(None)
226 for x in self.sub_windows:
227 if x.disp.prog is not root and not x.disp.prog.parent:
228 x.destroy()
230 def build_tree(self, prog, iter = None):
231 child_iter = self.prog_model.append(iter)
232 self.prog_model.set(child_iter, 0, prog.name,
233 1, prog.get_path())
235 self.prog_to_path[prog] = self.prog_model.get_path(child_iter)
236 for p in prog.subprograms.values():
237 self.build_tree(p, child_iter)
239 def run_return(self, exit):
240 if exit != 'next':
241 print "run_return: failure!"
242 self.view.jump_to_innermost_failure()
243 def cb(choice, self = self):
244 if choice == 0:
245 self.view.record_at_point()
246 if rox.confirm("Program failed - record a failure case?", g.STOCK_NO, 'Record'):
247 self.view.record_at_point()
248 print "List: execution done!"
250 def button_press(self, tree, event):
251 if event.button == 2 or event.button == 3:
252 ret = tree.get_path_at_pos(event.x, event.y)
253 if not ret:
254 return 1 # Click on blank area
255 path, col, cx, cy = ret
256 print "Event on", path
257 iter = self.prog_model.get_iter(path)
258 path = self.prog_model.get_value(iter, 1)
259 if event.button == 3:
260 prog = self.view.name_to_prog(path)
261 self.show_menu(event, prog)
262 else:
263 self.view.run_new(self.run_return)
264 if event.state & g.gdk.SHIFT_MASK:
265 self.view.may_record(['map', path])
266 else:
267 self.view.may_record(['play', path])
268 return 0
270 def menu_delete(self):
271 prog = self.prog_menu_prog
272 if not prog.parent:
273 rox.alert("Can't delete the root program!")
274 return
275 prog.parent.remove_sub(prog)
277 def menu_rename(self):
278 prog = self.prog_menu_prog
279 def rename(name, prog = prog):
280 prog.rename(name)
281 GetArg('Rename program', rename, ['Program name:'])
283 def menu_new_prog(self):
284 prog = self.prog_menu_prog
285 def create(name):
286 new = Program(name)
287 prog.add_sub(new)
288 GetArg('New program', create, ['Program name:'])
290 def menu_new_view(self):
291 prog = self.prog_menu_prog
292 cw = ChainWindow(self.view, prog)
293 cw.show()
294 self.sub_windows.append(cw)
295 def lost_cw(win):
296 self.sub_windows.remove(cw)
297 print "closed"
298 cw.connect('destroy', lost_cw)
300 def menu_map(self):
301 prog = self.prog_menu_prog
302 self.view.run_new(self.run_return)
303 self.view.may_record(['map', prog.get_path()])
305 def menu_play(self):
306 prog = self.prog_menu_prog
307 self.view.run_new(self.run_return)
308 self.view.may_record(['play', prog.get_path()])
310 def show_menu(self, event, prog):
311 self.prog_menu_prog = prog
312 prog_menu.popup(self, event)
314 def update_stack(self, op):
315 "The stack has changed - redraw 'op'"
316 if op and op.get_program() == self.chains.prog:
317 self.chains.update_all()
318 l = len(self.view.exec_stack) + len(self.view.foreach_stack)
319 if l == 0:
320 text = 'No stack'
321 elif l == 1:
322 text = '1 frame'
323 else:
324 text = '%d frames' % l
325 if self.view.chroots:
326 text += ' (%d enters)' % len(self.view.chroots)
327 self.stack_frames.set_text(text)
329 def show_prog(self, prog):
330 path = self.prog_to_path[prog]
331 partial = []
332 for p in path[:-1]:
333 partial.append(p)
334 self.tree.expand_row(tuple(partial), FALSE)
335 iter = self.prog_model.get_iter(path)
336 self.tree.get_selection().select_iter(iter)
338 class ChainDummy(g.TreeView):
339 def __init__(self, view, prog = None):
340 g.TreeView.__init__(self)
341 self.prog = prog
342 def switch_to(self, prog):
343 self.prog = prog
344 def update_points(self):
345 pass
347 class ChainNode:
348 "A visual object in the display."
349 def __init__(self, da, x, y):
350 self.x = x
351 self.y = y
352 self.da = da
354 def expose(self):
355 da = self.da
356 w = da.window
357 print "draw"
358 w.draw_rectangle(da.style.black_gc, True, self.x, self.y, 10, 10)
360 def maybe_clicked(self, event):
361 return False
363 class ChainOp(ChainNode):
364 def __init__(self, da, op, x, y):
365 self.op = op
366 ChainNode.__init__(self, da, x, y)
368 self.build_leaf()
370 da.op_to_object[op] = self
372 if op.next and op.next.prev[0] == op:
373 self.next = da.create_op(op.next, x, y + self.height + 4)
374 else:
375 self.next = None
377 if op.fail and op.fail.prev[0] == op:
378 self.fail = da.create_op(op.fail, x + 100, y + self.height + 4)
379 else:
380 self.fail = None
382 def build_leaf(self):
383 text = str(action_to_text(self.op.action))
384 self.layout = self.da.create_pango_layout(text)
386 self.width, self.height = self.layout.get_pixel_size()
387 self.width += 12
388 self.height = max(self.height, 20)
390 def expose(self):
391 da = self.da
392 w = da.window
393 op = self.op
395 w.draw_arc(da.style.white_gc, True, self.x, self.y, 10, 10, 0, 400 * 60)
396 w.draw_arc(da.style.black_gc, False, self.x, self.y, 10, 10, 0, 400 * 60)
397 w.draw_layout(da.style.black_gc, self.x + 12, self.y, self.layout)
399 self.draw_link(self.next, 5, 10, 'black')
400 self.draw_link(self.fail, 10, 10, 'red')
402 def draw_link(self, dest, dx, dy, colour):
403 if not dest: return
405 dest.expose()
406 da = self.da
407 pen = da.style.white_gc
408 pen.set_rgb_fg_color(g.gdk.color_parse(colour))
409 da.window.draw_line(pen, self.x + dx, self.y + dy, dest.x + 5, dest.y)
410 pen.set_rgb_fg_color(g.gdk.color_parse('white'))
412 def maybe_clicked(self, event):
413 x = event.x - self.x
414 if x < 0: return False
415 y = event.y - self.y
416 if y < 0: return False
418 if x > self.width or y > self.height:
419 return False
421 if event.button == 1:
422 self.da.view.set_exec((self.op, 'next'))
423 else:
424 if x < 10 and y > 10:
425 self.da.show_menu(event, self.op, 'next')
426 else:
427 self.da.show_menu(event, self.op)
428 return True
430 def all_nodes(self):
431 yield self
432 if self.next:
433 for n in self.next.all_nodes(): yield n
434 if self.fail:
435 for n in self.fail.all_nodes(): yield n
438 class ChainBlock(ChainOp):
439 def __init__(self, da, block, x, y):
440 assert isinstance(block, Block)
441 ChainOp.__init__(self, da, block, x, y)
442 self.depth = 1
443 p = block.parent
444 while p and not isinstance(p, Program):
445 p = p.parent
446 self.depth += 1
448 def build_leaf(self):
449 x = self.x
450 y = self.y
452 if self.op.comment:
453 self.layout = self.da.create_pango_layout(self.op.comment.replace('\\n', '\n'))
454 self.width, height = self.layout.get_pixel_size()
455 y += height + 4
456 else:
457 self.layout = None
458 self.width = 40
460 self.margin = (4 + self.op.foreach * 6, 4 + (self.op.enter + self.op.restore) * 6)
461 self.width += self.margin[0]
463 self.start = self.da.create_op(self.op.start, x + self.margin[0], y + self.margin[1])
465 self.height = 20
467 for node in self.start.all_nodes():
468 self.width = max(self.width, node.x + node.width - self.x)
469 self.height = max(self.height, node.y + node.height - self.y)
471 self.width += 4
472 self.height += 4 + (self.op.enter + self.op.restore) * 6
474 def expose(self):
475 da = self.da
476 w = da.window
477 w.draw_rectangle(da.style.black_gc, False, self.x, self.y, self.width, self.height)
478 pen = da.style.white_gc
479 width = self.width
480 x = self.x
481 y = self.y
483 d = 15 - min(self.depth, 7)
484 pen.set_rgb_fg_color(g.gdk.color_parse('#%x%x%x' % (d, d, d)))
485 w.draw_rectangle(pen, True, self.x + 1, self.y + 1, self.width - 1, self.height - 1)
487 op = self.op
488 if op.foreach:
489 pen.set_rgb_fg_color(g.gdk.color_parse('blue'))
490 w.draw_rectangle(pen, True, x + 1, y + 1, 6, self.height - 1)
491 x += 6
492 width -= 6
494 if op.enter:
495 pen.set_rgb_fg_color(g.gdk.color_parse('yellow'))
496 w.draw_rectangle(pen, True, x + 1, y + 1, width - 1, 6)
497 w.draw_rectangle(pen, True, x + 1, y + self.height - 6, width - 1, 6)
498 if op.restore:
499 pen.set_rgb_fg_color(g.gdk.color_parse('orange'))
500 margin = op.enter * 6
501 w.draw_rectangle(pen, True, x + 1, y + 1 + margin, width - 1, 6)
502 w.draw_rectangle(pen, True, x + 1, y + self.height - 6 - margin, width - 1, 6)
504 if self.layout:
505 pen.set_rgb_fg_color(g.gdk.color_parse('blue'))
506 w.draw_layout(pen, self.x + self.margin[0], self.y + self.margin[1], self.layout)
508 pen.set_rgb_fg_color(g.gdk.color_parse('white'))
510 w.draw_line(pen, self.x + 1, self.y + 1, self.x + self.width - 2, self.y + 1)
511 w.draw_line(pen, self.x + 1, self.y + 1, self.x + 1, self.y + self.height - 2)
513 self.start.expose()
515 self.draw_link(self.next, 5, self.height, 'black')
516 self.draw_link(self.fail, self.width, self.height, 'red')
518 def maybe_clicked(self, event):
519 return False
521 class ChainDisplay(g.DrawingArea):
522 "A graphical display of a chain of nodes."
523 def __init__(self, view, prog = None):
524 g.DrawingArea.__init__(self)
525 self.connect('destroy', self.destroyed)
527 self.view = view
528 self.unset_flags(g.CAN_FOCUS)
530 self.drag_last_pos = None
532 self.exec_point = None # CanvasItem, or None
533 self.rec_point = None
535 self.set_active(1)
537 self.nodes = None
538 self.subs = None
539 self.set_size_request(100, 100)
541 self.prog = None
543 self.view.model.root_program.watchers.append(self)
545 self.connect('expose-event', self.expose)
546 self.add_events(g.gdk.BUTTON_PRESS_MASK)
547 self.connect('button-press-event', self.button_press)
549 self.switch_to(prog)
551 def set_active(self, active):
552 if mono:
553 self.modify_bg(g.STATE_NORMAL, g.gdk.color_parse('white'))
554 elif active:
555 self.modify_bg(g.STATE_NORMAL, g.gdk.color_parse('#B3AA73'))
556 else:
557 self.modify_bg(g.STATE_NORMAL, g.gdk.color_parse('#FFC0C0'))
559 def update_points(self):
560 self.queue_draw()
562 if self.rec_point:
563 self.scroll_to_show(self.rec_point)
565 def scroll_to_show(self, item):
566 print "XXX"
567 return
569 (lx, ly, hx, hy) = item.get_bounds()
570 x, y = item.i2w(0, 0)
571 x, y = self.w2c(x, y)
572 lx += x
573 ly += y
574 hx += x
575 hy += y
576 lx -= 16
578 sx, sy = self.get_scroll_offsets()
579 if lx < sx:
580 sx = lx
581 if ly < sy:
582 sy = ly
584 (x, y, w, h) = self.get_allocation()
585 hx -= w
586 hy -= h
588 if hx > sx:
589 sx = hx
590 if hy > sy:
591 sy = hy
593 self.scroll_to(sx, sy)
595 def put_point(self, point):
596 if not point: return
597 w = self.window
598 op, exit = point
599 if op.get_program() != self.prog: return
600 try:
601 obj = self.op_to_object[op]
602 except:
603 print "Can't find %s!\n" % op
604 return
605 w.draw_rectangle(self.style.black_gc, False, obj.x, obj.y + 5, 11, 11)
606 w.draw_rectangle(self.style.bg_gc[g.STATE_SELECTED], True, obj.x + 1, obj.y + 5 + 1, 10, 10)
608 def destroyed(self, widget):
609 self.view.model.root_program.watchers.remove(self)
610 print "(ChainDisplay destroyed)"
612 def switch_to(self, prog):
613 if prog is self.prog:
614 return
615 self.prog = prog
616 self.update_all()
618 def prog_tree_changed(self):
619 pass
621 def program_changed(self, op):
622 if (not op) or op.get_program() == self.prog:
623 self.update_all()
625 def create_op(self, op, x, y):
626 if isinstance(op, Block):
627 return ChainBlock(self, op, x, y)
628 else:
629 return ChainOp(self, op, x, y)
631 def update_all(self):
632 self.op_to_object = {}
633 if self.prog:
634 self.root_object = self.create_op(self.prog.code, 4, 4)
635 self.set_size_request(self.root_object.width + 8, self.root_object.height + 8)
636 else:
637 self.root_object = None
638 self.set_size_request(-1, -1)
639 self.queue_draw()
640 return 1
642 def expose(self, da, event):
643 if self.root_object:
644 self.root_object.expose()
645 #self.update_links()
646 self.put_point(self.view.rec_point)
647 self.put_point(self.view.exec_point)
649 #self.set_bounds()
651 def button_press(self, da, event):
652 print "click"
653 for op in self.op_to_object.itervalues():
654 if op.maybe_clicked(event): break
656 def op_colour(self, op):
657 if op in self.view.exec_stack:
658 return 'cyan'
659 return 'blue'
661 def update_links(self, op = None):
662 """Walk through all nodes in the tree-version of the op graph,
663 making all the links (which already exist as stubs) point to
664 the right place."""
665 if not self.prog:
666 return
667 if not op:
668 op = self.prog.code
669 if op.next:
670 if op.next.prev[0] == op:
671 self.update_links(op.next)
672 else:
673 self.join_nodes(op, 'next')
674 if op.fail:
675 if op.fail.prev[0] == op:
676 self.update_links(op.fail)
677 else:
678 self.join_nodes(op, 'fail')
679 if isinstance(op, Block):
680 self.update_links(op.start)
682 def create_node(self, op, parent):
683 if op.is_toplevel():
684 return obj
685 return
687 if op.next and op.next.prev[0] == op:
688 sx, sy = self.get_arrow_start(op, 'next')
689 gr = group.add(canvas.CanvasGroup, x = 0, y = 0)
690 self.create_node(op.next, gr)
691 #self.update_now() # GnomeCanvas bug?
692 (lx, ly, hx, hy) = gr.get_bounds()
693 drop = max(20, next_off_y + 10)
694 y = drop - ly
695 next = op.next
696 while isinstance(next, Block):
697 next = next.start
698 x = next.dx
699 y += next.dy
700 gr.move(sx + x, sy + y)
702 group.next_line = group.add(canvas.CanvasLine,
703 fill_color = 'black',
704 points = connect(0, 0, 1, 1),
705 width_pixels = 4,
706 last_arrowhead = 1,
707 arrow_shape_a = 5,
708 arrow_shape_b = 5,
709 arrow_shape_c = 5)
710 group.next_line.connect('event', self.line_event, op, 'next')
712 (x, y) = DEFAULT_FAIL
713 if op.fail and op.fail.prev[0] == op:
714 sx, sy = self.get_arrow_start(op, 'fail')
715 y = 46
716 gr = group.add(canvas.CanvasGroup, x = 0, y = 0)
717 self.create_node(op.fail, gr)
718 #self.update_now() # GnomeCanvas bug?
719 (lx, ly, hx, hy) = gr.get_bounds()
720 x = 20 - lx
721 fail = op.fail
722 while isinstance(fail, Block):
723 fail = fail.start
724 x += fail.dx
725 y += fail.dy
726 gr.move(sx + x, sy + y)
727 group.fail_line = group.add(canvas.CanvasLine,
728 fill_color = '#ff6666',
729 points = connect(0, 0, 1, 1),
730 width_pixels = 4,
731 last_arrowhead = 1,
732 arrow_shape_a = 5,
733 arrow_shape_b = 5,
734 arrow_shape_c = 5)
735 group.fail_line.lower_to_bottom()
736 group.fail_line.connect('event', self.line_event, op, 'fail')
737 if op.action[0] == 'Start':
738 group.fail_line.hide()
740 self.join_nodes(op, 'next')
741 self.join_nodes(op, 'fail')
743 if self.view.breakpoints.has_key((op, 'next')):
744 group.next_line.set(line_style = g.gdk.LINE_ON_OFF_DASH)
745 if self.view.breakpoints.has_key((op, 'fail')):
746 group.fail_line.set(line_style = g.gdk.LINE_ON_OFF_DASH)
748 def edit_op(self, op):
749 def modify():
750 if op.action[0] == 'do_search' or op.action[0] == 'do_global':
751 t = editables[0].get_text()
752 print "Checking", t
753 from Ft.Xml.XPath import XPathParser
754 if t.find('@CURRENT@') == -1:
755 try:
756 XPathParser.new().parse(t)
757 except:
758 alert('Invalid search pattern!')
759 return
760 i = 0
761 for e in editables:
762 i += 1
763 if e:
764 op.action[i] = e.get_text()
765 op.changed()
766 print "Done editing!"
767 win.destroy()
769 win = g.Dialog()
770 win.vbox.pack_start(g.Label(op.action[0]), TRUE, FALSE, 0)
771 editables = [] # [ Entry | None ]
772 focus = None
773 for x in op.action[1:]:
774 entry = g.Entry()
775 entry.set_text(str(x))
776 win.vbox.pack_start(entry, TRUE, FALSE, 0)
777 if type(x) == str or type(x) == unicode:
778 editables.append(entry)
779 entry.connect('activate', lambda e: modify())
780 if not focus:
781 focus = entry
782 entry.grab_focus()
783 else:
784 entry.set_editable(FALSE)
785 editables.append(None)
787 win.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
788 win.add_button(g.STOCK_OK, g.RESPONSE_OK)
790 def response(box, resp):
791 box.destroy()
792 if resp == g.RESPONSE_OK:
793 modify()
794 win.connect('response', response)
796 if not focus:
797 win.set_response_sensitive(g.RESPONSE_OK, FALSE)
799 win.show_all()
801 def join_nodes(self, op, exit):
802 try:
803 x1, y1, x2, y2 = self.get_arrow_ends(op, exit)
805 prev_group = self.op_to_group[op]
806 line = getattr(prev_group, exit + '_line')
807 line.set(points = connect(x1, y1, x2, y2))
808 except Block:
809 print "*** ERROR setting arc from %s:%s" % (op, exit)
811 def op_event(self, item, event, op):
812 if event.type == g.gdk.BUTTON_PRESS:
813 print "Prev %s = %s" % (op, map(str, op.prev))
814 if event.button == 1:
815 if op.parent.start != op or not op.parent.is_toplevel():
816 self.drag_last_pos = (event.x, event.y)
817 else:
818 self.drag_last_pos = None
819 else:
820 self.show_op_menu(event, op)
821 elif event.type == g.gdk.BUTTON_RELEASE:
822 if event.button == 1:
823 self.drag_last_pos = None
824 self.program_changed(None)
825 elif event.type == g.gdk.ENTER_NOTIFY:
826 item.set(fill_color = '#339900')
827 elif event.type == g.gdk.LEAVE_NOTIFY:
828 item.set(fill_color = self.op_colour(op))
829 elif event.type == g.gdk.MOTION_NOTIFY and self.drag_last_pos:
830 if not event.state & g.gdk.BUTTON1_MASK:
831 print "(stop drag!)"
832 self.drag_last_pos = None
833 self.program_changed(None)
834 return 1
835 x, y = (event.x, event.y)
836 dx, dy = x - self.drag_last_pos[0], y - self.drag_last_pos[1]
837 if abs(op.dx + dx) < 4:
838 dx = -op.dx
839 x = dx + self.drag_last_pos[0]
840 if abs(op.dy + dy) < 4:
841 dy = -op.dy
842 y = dy + self.drag_last_pos[1]
843 op.dx += dx
844 op.dy += dy
845 self.drag_last_pos = (x, y)
847 self.op_to_group[op].move(dx, dy)
848 for p in op.prev:
849 if p.next == op:
850 self.join_nodes(p, 'next')
851 if p.fail == op:
852 self.join_nodes(p, 'fail')
853 self.update_links()
854 #self.create_node(self.prog.start, self.nodes)
855 self.update_points()
856 elif event.type == g.gdk._2BUTTON_PRESS:
857 if op.action[0] == 'Start':
858 self.edit_comment(op.parent)
859 else:
860 self.edit_op(op)
861 print "(edit; stop drag!)"
862 self.drag_last_pos = None
863 self.program_changed(None)
864 return 1
866 def edit_comment(self, block):
867 assert isinstance(block, Block)
869 def set(comment):
870 block.set_comment(comment)
871 GetArg('Comment', set, ['Comment:'],
872 message = '\\n for a newline', init = [block.comment])
874 def block_toggle_enter(self):
875 self.op_menu_op.toggle_enter()
877 def block_toggle_foreach(self):
878 self.op_menu_op.toggle_foreach()
880 def block_toggle_restore(self):
881 self.op_menu_op.toggle_restore()
883 def block_edit_comment(self):
884 self.edit_comment(self.op_menu_op)
886 def op_edit(self):
887 self.edit_op(self.op_menu_op)
889 def op_swap_nf(self):
890 self.op_menu_op.swap_nf()
892 def op_del_node(self):
893 op = self.op_menu_op
894 if op.next and op.fail:
895 rox.alert("Can't delete a node with both exits in use")
896 return
897 self.clipboard = op.del_node()
899 def show_op_menu(self, event, op):
900 if op.action[0] == 'Start':
901 self.op_menu_op = op.parent
902 block_menu.popup(self, event)
903 else:
904 self.op_menu_op = op
905 op_menu.popup(self, event)
907 def paste_chain(self, op, exit):
908 print "Paste", self.clipboard
909 doc = self.clipboard
910 new = load(doc.documentElement, op.parent)
911 start = new.start.next
912 new.start.unlink('next', may_delete = 0)
913 start.set_parent(None)
914 op.link_to(start, exit)
916 def end_link_drag(self, item, event, src_op, exit):
917 # Scan all the nodes looking for one nearby...
918 x, y = event.x, event.y
920 def closest_node(op):
921 "Return the closest (node, dist) in this chain to (x, y)"
922 nx, ny = self.op_to_group[op].i2w(0, 0)
923 if op is src_op:
924 best = None
925 elif isinstance(op, Block):
926 best = None
927 else:
928 best = (op, math.hypot(nx - x, ny - y))
929 if op.next and op.next.prev[0] == op:
930 next = closest_node(op.next)
931 if next and (best is None or next[1] < best[1]):
932 best = next
933 if op.fail and op.fail.prev[0] == op:
934 fail = closest_node(op.fail)
935 if fail and (best is None or fail[1] < best[1]):
936 best = fail
937 if isinstance(op, Block):
938 sub = closest_node(op.start)
939 if sub and (best is None or sub[1] < best[1]):
940 best = sub
941 return best
943 result = closest_node(self.prog.code)
944 if result:
945 node, dist = result
946 else:
947 dist = 1000
948 if dist > 12:
949 # Too far... put the line back to the disconnected state...
950 self.join_nodes(src_op, exit)
951 return
952 try:
953 while node.action[0] == 'Start':
954 node = node.parent
955 src_op.link_to(node, exit)
956 finally:
957 self.update_all()
959 def line_paste_chain(self):
960 op, exit = self.line_menu_line
961 self.paste_chain(op, exit)
963 def line_add_block(self):
964 op, exit = self.line_menu_line
965 box = rox.Dialog()
966 box.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
967 box.add_button(g.STOCK_ADD, g.RESPONSE_OK)
969 foreach = g.CheckButton('Foreach block')
970 box.vbox.pack_start(foreach)
971 enter = g.CheckButton('Enter-leave block')
972 box.vbox.pack_start(enter)
973 box.vbox.show_all()
975 resp = box.run()
976 box.destroy()
977 if resp == g.RESPONSE_OK:
978 b = Block(op.parent)
979 if foreach.get_active():
980 b.toggle_foreach()
981 if enter.get_active():
982 b.toggle_enter()
983 op.link_to(b, exit)
984 if self.view.rec_point == (op, exit):
985 self.view.single_step = 1
986 self.view.stop_recording()
987 try:
988 self.view.do_one_step()
989 except View.InProgress:
990 pass
992 def line_toggle_breakpoint(self):
993 op, exit = self.line_menu_line
994 bp = self.view.breakpoints
995 if bp.has_key((op, exit)):
996 del bp[(op, exit)]
997 else:
998 bp[(op, exit)] = 1
999 self.prog.changed()
1001 def line_yank_chain(self):
1002 op, exit = self.line_menu_line
1003 next = getattr(op, exit)
1004 if not next:
1005 rox.alert('Nothing to yank!')
1006 return
1007 self.clipboard = next.to_doc()
1008 print self.clipboard
1010 def line_del_chain(self):
1011 op, exit = self.line_menu_line
1012 next = getattr(op, exit)
1013 if not next:
1014 rox.alert('Nothing to delete!')
1015 return
1016 self.clipboard = next.to_doc()
1017 op.unlink(exit)
1019 def show_menu(self, event, op, exit = None):
1020 if exit:
1021 self.line_menu_line = (op, exit)
1022 line_menu.popup(self, event)
1023 else:
1024 self.show_op_menu(event, op)
1026 def line_event(self, item, event, op, exit):
1027 # Item may be rec_point or exec_point...
1028 item = getattr(self.op_to_group[op], exit + '_line')
1030 if event.type == g.gdk.BUTTON_PRESS:
1031 if event.button == 1:
1032 if not getattr(op, exit):
1033 self.drag_last_pos = (event.x, event.y)
1034 #item.grab(BUTTON_RELEASE | MOTION_NOTIFY, no_cursor, event.time)
1035 elif event.button == 2:
1036 self.paste_chain(op, exit)
1037 elif event.button == 3:
1038 self.line_menu_line = (op, exit)
1039 line_menu.popup(self, event)
1040 elif event.type == g.gdk.BUTTON_RELEASE:
1041 if event.button == 1:
1042 print "Clicked exit %s of %s" % (exit, op)
1043 #item.ungrab(event.time)
1044 self.view.set_exec((op, exit))
1045 self.drag_last_pos = None
1046 if not getattr(op, exit):
1047 self.end_link_drag(item, event, op, exit)
1048 elif event.type == g.gdk.MOTION_NOTIFY and self.drag_last_pos:
1049 if not event.state & g.gdk.BUTTON1_MASK:
1050 print "(stop drag!)"
1051 self.drag_last_pos = None
1052 if not getattr(op, exit):
1053 self.end_link_drag(item, event, op, exit)
1054 return 1
1055 x, y = (event.x, event.y)
1056 dx, dy = x - self.drag_last_pos[0], y - self.drag_last_pos[1]
1058 if abs(dx) > 4 or abs(dy) > 4:
1059 sx, sy = self.get_arrow_start(op, exit)
1060 x, y = item.w2i(event.x, event.y)
1061 gr = self.op_to_group[op]
1062 if exit == 'fail':
1063 width = gr.width
1064 else:
1065 width = 0
1066 item.set(points = connect(sx, sy, x, y))
1067 elif event.type == g.gdk.ENTER_NOTIFY:
1068 item.set(fill_color = '#339900')
1069 elif event.type == g.gdk.LEAVE_NOTIFY:
1070 if exit == 'next':
1071 item.set(fill_color = 'black')
1072 else:
1073 item.set(fill_color = '#ff6666')
1074 return 1
1076 def get_arrow_start(self, op, exit):
1077 gr = self.op_to_group[op]
1078 return ((exit == 'fail' and gr.width) or 0, gr.height)
1080 def get_arrow_ends(self, op, exit):
1081 """Return coords of arrow, relative to op's group."""
1082 op2 = getattr(op, exit)
1084 prev_group = self.op_to_group[op]
1086 x1, y1 = self.get_arrow_start(op, exit)
1088 if op2:
1089 try:
1090 group = self.op_to_group[op2]
1091 except:
1092 x2 = x1 + 50
1093 y2 = y1 + 50
1094 else:
1095 x2, y2 = group.i2w(0, 0)
1096 x2, y2 = prev_group.w2i(x2, y2)
1097 elif exit == 'next':
1098 x2, y2 = DEFAULT_NEXT
1099 x2 += x1
1100 y2 += y1
1101 else:
1102 x2, y2 = DEFAULT_FAIL
1103 x2 += x1
1104 y2 += y1
1105 return (x1, y1, x2, y2)
1107 def set_bounds(self):
1108 #self.update_now() # GnomeCanvas bug?
1109 min_x, min_y, max_x, max_y = self.root().get_bounds()
1110 min_x -= 8
1111 max_x += 8
1112 min_y -= 8
1113 max_y += 8
1114 self.set_scroll_region(min_x, min_y, max_x, max_y)
1115 self.root().move(0, 0) # Magic!
1116 #self.set_usize(max_x - min_x, -1)
1118 def canvas_to_world(self, (x, y)):
1119 "Canvas routine seems to be broken..."
1120 mx, my, maxx, maxy = self.get_scroll_region()
1121 sx = self.get_hadjustment().value
1122 sy = self.get_hadjustment().value
1123 return (x + mx + sx , y + my + sy)
1125 class ChainWindow(rox.Window):
1126 def __init__(self, view, prog):
1127 rox.Window.__init__(self)
1128 swin = g.ScrolledWindow()
1129 self.add(swin)
1130 disp = ChainDisplay(view, prog)
1131 swin.add(disp)
1133 swin.show_all()
1134 self.disp = disp
1135 self.set_default_size(-1, 200)
1136 self.set_title(prog.name)
1138 def update_points(self):
1139 self.disp.update_points()