Cope with loading data for XSLT.
[dom-editor.git] / Dome / List.py
blobd6106a147f93aa8d587c5e5bff4241b2f6df8361
1 from __future__ import generators
3 import rox
4 from rox import g, TRUE, FALSE, alert
6 from support import *
7 import string
8 from StringIO import StringIO
9 import math
10 import View
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 box_size = 9
54 next_box = (0, 12)
55 fail_box = (12, 8)
57 def trunc(text):
58 if len(text) < 28:
59 return text
60 return text[:26] + '...'
62 def connect(x1, y1, x2, y2):
63 """Chop 5 pixels off both ends of this line"""
64 gap = 5.0
65 dx = x2 - x1
66 dy = y2 - y1
67 l = math.hypot(dx, dy)
68 if l:
69 dx *= gap / l
70 dy *= gap / l
71 return (x1 + dx, y1 + dy, x2 - dx, y2 - dy)
73 DEFAULT_NEXT = (0, 25)
74 DEFAULT_FAIL = (20, 20)
76 expand_history = {} # Prog name -> expanded flag
78 def action_to_text(action):
79 text = action[0]
80 if text == 'Start': return ''
81 if text[:3] == 'do_':
82 text = text[3:]
83 text = string.capitalize(string.replace(text, '_', ' '))
84 if text == 'Global':
85 text = 'Select nodes'
87 if len(action) > 1:
88 if action[0] in ('do_search', 'xpath', 'move_selection'):
89 pat = str(action[1])
90 pat = string.replace(pat, 'following-sibling::', '>>')
91 pat = string.replace(pat, 'preceding-sibling::', '<<')
92 pat = string.replace(pat, 'child::', '')
93 pat = string.replace(pat, '[1]', '')
94 pat = string.replace(pat, 'text()[ext:match', '[')
95 details = ''
96 while len(pat) > 20:
97 i = string.rfind(pat[:20], '/')
98 if i == -1:
99 i = string.rfind(pat[:20], ':')
100 if i == -1:
101 i = 20
102 details = details + pat[:i + 1] + '\n'
103 pat = pat[i + 1:]
104 details = details + pat
105 elif action[0] == 'attribute':
106 details = trunc(str(action[2]))
107 elif action[0] == 'set_attrib':
108 details = trunc(str(action[1]))
109 elif action[0] == 'add_attrib':
110 details = trunc(str(action[2]))
111 elif action[0] == 'add_node':
112 details = trunc(action[2])
113 elif action[0] == 'subst':
114 details = action[1] + ' -> ' + action[2]
115 elif action[0] == 'play' or action[0] == 'map':
116 if len(action[1]) > 20:
117 details = '...' + str(action[1][-19:])
118 else:
119 details = str(action[1])
120 else:
121 if len(action) > 2:
122 details = `action[1:]`
123 else:
124 details = str(action[1])
125 if len(details) > 20:
126 details = trunc(`details`)
127 text = text + '\n' + details
128 return text
130 class List(g.VBox):
131 def __init__(self, view):
132 g.VBox.__init__(self)
134 def destroyed(widget):
135 #print "List destroy!!"
136 sel.disconnect(self.sel_changed_signal)
137 self.view.lists.remove(self)
138 self.view.model.root_program.watchers.remove(self)
139 self.connect('destroy', destroyed)
141 self.view = view
142 self.sub_windows = []
144 self.stack_frames = g.Label('')
145 self.pack_start(self.stack_frames, FALSE, TRUE, 0)
146 self.stack_frames.show()
147 self.update_stack(None)
149 pane = g.VPaned()
150 self.pack_start(pane, expand = 1, fill = 1)
152 swin = g.ScrolledWindow()
153 swin.set_policy(g.POLICY_NEVER, g.POLICY_AUTOMATIC)
154 pane.add1(swin)
155 self.prog_model = g.TreeStore(str, str)
156 tree = g.TreeView(self.prog_model)
157 tree.connect('button-press-event', self.button_press)
158 tree.unset_flags(g.CAN_FOCUS)
159 tree.set_headers_visible(FALSE)
160 self.tree = tree
162 cell = g.CellRendererText()
163 column = g.TreeViewColumn('Program', cell, text = 0)
164 tree.append_column(column)
166 sel = tree.get_selection()
167 # Doesn't get destroyed, so record signal number
168 self.sel_changed_signal = sel.connect('changed', self.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_with_viewport(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)
194 def change_prog(self, sel):
195 selected = sel.get_selected()
196 if not selected:
197 return
198 model, iter = selected
199 if iter:
200 path = model.get_value(iter, 1)
201 self.chains.switch_to(self.view.name_to_prog(path))
202 else:
203 self.chains.switch_to(None)
205 def set_innermost_failure(self, op):
206 prog = op.get_program()
207 #print "list: set_innermost_failure:", prog
208 self.show_prog(prog)
210 def update_points(self):
211 self.chains.update_points()
212 for x in self.sub_windows:
213 x.update_points()
215 def program_changed(self, op):
216 pass
218 def prog_tree_changed(self):
219 self.prog_to_path = {}
220 self.prog_model.clear()
221 self.build_tree(self.view.model.root_program)
223 # Check for now deleted programs still being displayed
224 root = self.view.model.root_program
225 if self.chains and self.chains.prog and not self.chains.prog.parent:
226 self.chains.switch_to(None)
227 for x in self.sub_windows:
228 if x.disp.prog is not root and not x.disp.prog.parent:
229 x.destroy()
231 def build_tree(self, prog, iter = None):
232 child_iter = self.prog_model.append(iter)
233 self.prog_model.set(child_iter, 0, prog.name,
234 1, prog.get_path())
236 self.prog_to_path[prog] = self.prog_model.get_path(child_iter)
237 for p in prog.subprograms.values():
238 self.build_tree(p, child_iter)
240 def run_return(self, exit):
241 print "List execution finished:", exit
242 if exit != 'next':
243 #self.view.jump_to_innermost_failure()
244 def record():
245 if rox.confirm("Program failed - record a failure case?",
246 g.STOCK_NO, 'Record'):
247 self.view.record_at_point()
248 return False
249 g.idle_add(record)
251 def button_press(self, tree, event):
252 if event.button == 2 or event.button == 3:
253 ret = tree.get_path_at_pos(int(event.x), int(event.y))
254 if not ret:
255 return 1 # Click on blank area
256 path, col, cx, cy = ret
257 #print "Event on", path
258 iter = self.prog_model.get_iter(path)
259 path = self.prog_model.get_value(iter, 1)
260 if event.button == 3:
261 prog = self.view.name_to_prog(path)
262 self.show_menu(event, prog)
263 else:
264 self.view.run_new(self.run_return)
265 self.view.set_status("Running '%s'" % path)
266 if event.state & g.gdk.SHIFT_MASK:
267 self.view.may_record(['map', path])
268 else:
269 self.view.may_record(['play', path])
270 return 0
272 def menu_delete(self):
273 prog = self.prog_menu_prog
274 if not prog.parent:
275 rox.alert("Can't delete the root program!")
276 return
277 prog.parent.remove_sub(prog)
279 def menu_rename(self):
280 prog = self.prog_menu_prog
281 def rename(name, prog = prog):
282 prog.rename(name)
283 GetArg('Rename program', rename, ['Program name:'])
285 def menu_new_prog(self):
286 prog = self.prog_menu_prog
287 def create(name):
288 new = Program(name)
289 prog.add_sub(new)
290 GetArg('New program', create, ['Program name:'])
292 def menu_new_view(self):
293 prog = self.prog_menu_prog
294 cw = ChainWindow(self.view, prog)
295 cw.show()
296 self.sub_windows.append(cw)
297 def lost_cw(win):
298 self.sub_windows.remove(cw)
299 cw.connect('destroy', lost_cw)
301 def menu_map(self):
302 prog = self.prog_menu_prog
303 self.view.run_new(self.run_return)
304 self.view.set_status("Running '%s'" % prog.get_path())
305 self.view.may_record(['map', prog.get_path()])
307 def menu_play(self):
308 prog = self.prog_menu_prog
309 self.view.run_new(self.run_return)
310 self.view.set_status("Running '%s'" % prog.get_path())
311 self.view.may_record(['play', prog.get_path()])
313 def show_menu(self, event, prog):
314 self.prog_menu_prog = prog
315 prog_menu.popup(self, event)
317 def update_stack(self, op):
318 "The stack has changed - redraw 'op'"
319 if op and op.get_program() == self.chains.prog:
320 self.chains.update_all()
321 l = len(self.view.exec_stack) + len(self.view.foreach_stack)
322 if l == 0:
323 text = 'No stack'
324 elif l == 1:
325 text = '1 frame'
326 else:
327 text = '%d frames' % l
328 if self.view.chroots:
329 text += ' (%d enters)' % len(self.view.chroots)
330 self.stack_frames.set_text(text)
332 def show_prog(self, prog):
333 path = self.prog_to_path[prog]
334 partial = []
335 for p in path[:-1]:
336 partial.append(p)
337 self.tree.expand_row(tuple(partial), FALSE)
338 iter = self.prog_model.get_iter(path)
339 self.tree.get_selection().select_iter(iter)
341 def selected_program(self):
342 return self.chains.prog
344 class ChainDummy(g.TreeView):
345 def __init__(self, view, prog = None):
346 g.TreeView.__init__(self)
347 self.prog = prog
348 def switch_to(self, prog):
349 self.prog = prog
350 def update_points(self):
351 pass
353 class ChainNode:
354 "A visual object in the display."
355 def __init__(self, da, x, y):
356 self.x = x
357 self.y = y
358 self.da = da
360 def expose(self):
361 da = self.da
362 w = da.backing
363 w.draw_rectangle(da.style.black_gc, True, self.x, self.y, 10, 10)
365 def maybe_clicked(self, event):
366 return False
368 class ChainOp(ChainNode):
369 def __init__(self, da, op, x, y):
370 self.op = op
371 ChainNode.__init__(self, da, x, y)
373 self.build_leaf()
375 da.op_to_object[op] = self
377 if op.next and op.next.prev[0] == op:
378 self.next = da.create_op(op.next, x, y + self.height + 4)
379 self.total_width = max(self.width, self.next.total_width)
380 else:
381 self.next = None
382 self.total_width = self.width
384 if op.fail and op.fail.prev[0] == op:
385 if op.next:
386 indent = self.total_width + 20
387 else:
388 indent = 100
389 self.fail = da.create_op(op.fail, x + indent, y + self.height + 4)
390 self.total_width = indent + self.fail.total_width
391 else:
392 self.fail = None
394 def build_leaf(self):
395 text = str(action_to_text(self.op.action))
396 self.layout = self.da.create_pango_layout(text)
398 self.width, self.height = self.layout.get_pixel_size()
399 self.width += 12
400 self.height = max(self.height, 20)
402 def expose(self):
403 da = self.da
404 w = da.backing
405 op = self.op
407 w.draw_arc(da.style.white_gc, True, self.x, self.y, 10, 10, 0, 400 * 60)
408 w.draw_arc(da.style.black_gc, False, self.x, self.y, 10, 10, 0, 400 * 60)
409 w.draw_layout(da.style.black_gc, self.x + 12, self.y - 2, self.layout)
411 self.draw_link(self.next, 5, 10, 'black')
412 self.draw_link(self.fail, 5, 12, 'red')
414 if (op, 'next') in self.da.view.breakpoints:
415 w.draw_arc(da.style.black_gc, True,
416 self.x + 2, self.y + 12, 6, 6, 0, 400 * 60)
417 if (op, 'fail') in self.da.view.breakpoints:
418 w.draw_arc(da.style.black_gc, True,
419 self.x + 14, self.y + 10, 6, 6, 0, 400 * 60)
421 def draw_link(self, dest, dx, dy, colour):
422 if not dest: return
424 dest.expose()
425 da = self.da
426 pen = da.style.white_gc
427 pen.set_rgb_fg_color(g.gdk.color_parse(colour))
428 da.backing.draw_line(pen, self.x + dx, self.y + dy, dest.x + 5, self.y + dy)
429 da.backing.draw_line(pen, dest.x + 5, self.y + dy, dest.x + 5, dest.y)
430 pen.set_rgb_fg_color(g.gdk.color_parse('white'))
432 def where(self, x, y):
433 "Identify where (x,y) falls on us -> None, 'op', 'next', 'fail'"
434 x -= self.x
435 if x < 0: return False
436 y -= self.y
437 if y < 0: return False
439 if x >= next_box[0] and y >= next_box[1] and \
440 x <= next_box[0] + box_size and y <= next_box[1] + box_size:
441 return 'next'
443 if x >= fail_box[0] and y >= fail_box[1] and \
444 x <= fail_box[0] + box_size and y <= fail_box[1] + box_size:
445 return 'fail'
447 if x < self.width and y < self.height: return 'op'
449 def maybe_clicked(self, event):
450 pos = self.where(event.x, event.y)
451 if not pos: return
452 if event.button == 1:
453 if pos in ('next', 'fail'):
454 self.da.view.set_exec((self.op, pos))
455 else:
456 if pos != 'op':
457 self.da.show_menu(event, self.op, pos)
458 else:
459 self.da.show_menu(event, self.op)
460 return True
462 def all_nodes(self):
463 yield self
464 if self.next:
465 for n in self.next.all_nodes(): yield n
466 if self.fail:
467 for n in self.fail.all_nodes(): yield n
470 class ChainBlock(ChainOp):
471 def __init__(self, da, block, x, y):
472 assert isinstance(block, Block)
473 ChainOp.__init__(self, da, block, x, y)
474 self.depth = 1
475 p = block.parent
476 while p and not isinstance(p, Program):
477 p = p.parent
478 self.depth += 1
480 def build_leaf(self):
481 x = self.x
482 y = self.y
484 if self.op.comment:
485 self.layout = self.da.create_pango_layout(self.op.comment.replace('\\n', '\n'))
486 self.width, height = self.layout.get_pixel_size()
487 y += height + 4
488 else:
489 self.layout = None
490 self.width = 40
492 self.margin = (4 + self.op.foreach * 6, 4 + (self.op.enter + self.op.restore) * 6)
493 self.width += self.margin[0]
495 self.start = self.da.create_op(self.op.start, x + self.margin[0], y + self.margin[1])
497 self.height = 20
499 for node in self.start.all_nodes():
500 self.width = max(self.width, node.x + node.width - self.x)
501 self.height = max(self.height, node.y + node.height - self.y)
503 self.width += 4
504 self.height += 4 + (self.op.enter + self.op.restore) * 6
506 def expose(self):
507 da = self.da
508 w = da.backing
509 w.draw_rectangle(da.style.black_gc, False, self.x, self.y, self.width, self.height)
510 pen = da.style.white_gc
511 width = self.width
512 x = self.x
513 y = self.y
515 d = 15 - min(self.depth, 7)
516 pen.set_rgb_fg_color(g.gdk.color_parse('#%x%x%x' % (d, d, d)))
517 w.draw_rectangle(pen, True, self.x + 1, self.y + 1, self.width - 1, self.height - 1)
519 op = self.op
520 if op.foreach:
521 pen.set_rgb_fg_color(g.gdk.color_parse('blue'))
522 w.draw_rectangle(pen, True, x + 1, y + 1, 6, self.height - 1)
523 x += 6
524 width -= 6
526 if op.enter:
527 pen.set_rgb_fg_color(g.gdk.color_parse('yellow'))
528 w.draw_rectangle(pen, True, x + 1, y + 1, width - 1, 6)
529 w.draw_rectangle(pen, True, x + 1, y + self.height - 6, width - 1, 6)
530 if op.restore:
531 pen.set_rgb_fg_color(g.gdk.color_parse('orange'))
532 margin = op.enter * 6
533 w.draw_rectangle(pen, True, x + 1, y + 1 + margin, width - 1, 6)
534 w.draw_rectangle(pen, True, x + 1, y + self.height - 6 - margin, width - 1, 6)
536 if self.layout:
537 pen.set_rgb_fg_color(g.gdk.color_parse('blue'))
538 w.draw_layout(pen, self.x + self.margin[0], self.y + self.margin[1], self.layout)
540 pen.set_rgb_fg_color(g.gdk.color_parse('white'))
542 w.draw_line(pen, self.x + 1, self.y + 1, self.x + self.width - 2, self.y + 1)
543 w.draw_line(pen, self.x + 1, self.y + 1, self.x + 1, self.y + self.height - 2)
545 self.start.expose()
547 self.draw_link(self.next, 5, self.height, 'black')
548 self.draw_link(self.fail, self.width, self.height, 'red')
550 def maybe_clicked(self, event):
551 return False
553 def where(self, x, y):
554 return None
556 class ChainDisplay(g.EventBox):
557 "A graphical display of a chain of nodes."
558 def __init__(self, view, prog = None):
559 g.EventBox.__init__(self)
560 self.connect('destroy', self.destroyed)
561 self.set_app_paintable(True)
562 self.set_double_buffered(False)
563 self.connect('size-allocate', lambda w, a: self.size_allocate(a))
565 self.view = view
566 self.unset_flags(g.CAN_FOCUS)
568 self.drag_last_pos = None
570 self.exec_point = None # CanvasItem, or None
571 self.rec_point = None
573 self.set_active(1)
575 self.nodes = None
576 self.subs = None
577 self.set_size_request(100, 100)
579 self.prog = None
581 self.view.model.root_program.watchers.append(self)
583 self.connect('expose-event', self.expose)
585 self.add_events(g.gdk.BUTTON_PRESS_MASK |
586 g.gdk.POINTER_MOTION_MASK)
587 self.connect('button-press-event', self.button_press)
588 self.hover = None
589 self.connect('motion-notify-event', self.motion)
591 self.switch_to(prog)
593 def set_active(self, active):
594 if mono:
595 self.modify_bg(g.STATE_NORMAL, g.gdk.color_parse('white'))
596 elif active:
597 self.modify_bg(g.STATE_NORMAL, g.gdk.color_parse('#B3AA73'))
598 else:
599 self.modify_bg(g.STATE_NORMAL, g.gdk.color_parse('#FFC0C0'))
601 def update_points(self):
602 self.queue_draw()
604 if self.rec_point:
605 self.scroll_to_show(self.rec_point)
607 def scroll_to_show(self, item):
608 print "XXX"
609 return
611 (lx, ly, hx, hy) = item.get_bounds()
612 x, y = item.i2w(0, 0)
613 x, y = self.w2c(x, y)
614 lx += x
615 ly += y
616 hx += x
617 hy += y
618 lx -= 16
620 sx, sy = self.get_scroll_offsets()
621 if lx < sx:
622 sx = lx
623 if ly < sy:
624 sy = ly
626 (x, y, w, h) = self.get_allocation()
627 hx -= w
628 hy -= h
630 if hx > sx:
631 sx = hx
632 if hy > sy:
633 sy = hy
635 self.scroll_to(sx, sy)
637 def put_point(self, point):
638 if not point: return
639 w = self.window
640 op, exit = point
641 if op.get_program() != self.prog: return
642 try:
643 obj = self.op_to_object[op]
644 except:
645 print "Can't find %s!\n" % op
646 return
647 x = obj.x
648 y = obj.y
649 size = box_size
650 if point is self.view.rec_point:
651 colour = 'red'
652 else:
653 size -= 4
654 x += 2
655 y += 2
656 colour = 'yellow'
657 if exit == 'fail':
658 x += fail_box[0]
659 y += fail_box[1]
660 else:
661 x += next_box[0]
662 y += next_box[1]
663 pen = self.style.white_gc
664 pen.set_rgb_fg_color(g.gdk.color_parse(colour))
665 w.draw_rectangle(self.style.black_gc, False, x, y, size, size)
666 w.draw_rectangle(pen, True, x + 1, y + 1, size - 1, size - 1)
667 pen.set_rgb_fg_color(g.gdk.color_parse('white'))
669 def destroyed(self, widget):
670 self.view.model.root_program.watchers.remove(self)
672 def switch_to(self, prog):
673 if prog is self.prog:
674 return
675 self.prog = prog
676 self.update_all()
678 def prog_tree_changed(self):
679 pass
681 def program_changed(self, op):
682 if (not op) or op.get_program() == self.prog:
683 self.update_all()
685 def create_op(self, op, x, y):
686 if isinstance(op, Block):
687 return ChainBlock(self, op, x, y)
688 else:
689 return ChainOp(self, op, x, y)
691 def update_all(self):
692 self.op_to_object = {}
693 if self.prog:
694 self.root_object = self.create_op(self.prog.code, 4, 4)
695 self.set_size_request(self.root_object.width + 8, self.root_object.height + 8)
696 else:
697 self.root_object = None
698 self.set_size_request(-1, -1)
699 self.backing = None
700 self.queue_draw()
701 return 1
703 def size_allocate(self, alloc):
704 self.backing = None
705 self.window.clear()
707 def create_backing(self):
708 self.backing = g.gdk.Pixmap(self.window, self.allocation.width, self.allocation.height, -1)
709 self.window.set_back_pixmap(self.backing, False)
710 self.backing.draw_rectangle(self.style.bg_gc[g.STATE_NORMAL], True,
711 0, 0, self.allocation.width, self.allocation.height)
712 if self.root_object:
713 self.root_object.expose()
714 self.window.clear()
715 return
717 def expose(self, da, event):
718 if not self.backing: self.create_backing()
720 self.window.draw_drawable(self.style.white_gc, self.backing, 0, 0, 0, 0, -1, -1)
722 #self.update_links()
723 self.put_point(self.view.rec_point)
724 self.put_point(self.view.exec_point)
726 #self.set_bounds()
728 if self.hover:
729 op, exit = self.hover
730 pen = self.style.black_gc
731 w = self.window
732 if exit == 'fail':
733 w.draw_rectangle(pen, False, op.x + fail_box[0], op.y + fail_box[1],
734 box_size, box_size)
735 else:
736 w.draw_rectangle(pen, False, op.x + next_box[0], op.y + next_box[1],
737 box_size, box_size)
739 def motion(self, box, event):
740 hover = None
741 for op in self.op_to_object.itervalues():
742 pos = op.where(event.x, event.y)
743 if pos in ('next', 'fail'):
744 hover = (op, pos)
745 if hover == self.hover:
746 return
747 self.hover = hover
748 self.queue_draw()
750 def button_press(self, da, event):
751 for op in self.op_to_object.itervalues():
752 if op.maybe_clicked(event): break
754 def op_colour(self, op):
755 if op in self.view.exec_stack:
756 return 'cyan'
757 return 'blue'
759 def update_links(self, op = None):
760 """Walk through all nodes in the tree-version of the op graph,
761 making all the links (which already exist as stubs) point to
762 the right place."""
763 if not self.prog:
764 return
765 if not op:
766 op = self.prog.code
767 if op.next:
768 if op.next.prev[0] == op:
769 self.update_links(op.next)
770 else:
771 self.join_nodes(op, 'next')
772 if op.fail:
773 if op.fail.prev[0] == op:
774 self.update_links(op.fail)
775 else:
776 self.join_nodes(op, 'fail')
777 if isinstance(op, Block):
778 self.update_links(op.start)
780 def create_node(self, op, parent):
781 if op.is_toplevel():
782 return obj
784 def edit_op(self, op):
785 def modify():
786 if op.action[0] == 'do_search' or op.action[0] == 'do_global':
787 t = editables[0].get_text()
788 print "Checking", t
789 from Ft.Xml.XPath import XPathParser
790 if t.find('@CURRENT@') == -1:
791 try:
792 XPathParser.new().parse(t)
793 except:
794 alert('Invalid search pattern!')
795 return
796 i = 0
797 for e in editables:
798 i += 1
799 if e:
800 op.action[i] = e.get_text()
801 op.changed()
802 print "Done editing!"
803 win.destroy()
805 win = g.Dialog()
806 win.vbox.pack_start(g.Label(op.action[0]), TRUE, FALSE, 0)
807 editables = [] # [ Entry | None ]
808 focus = None
809 for x in op.action[1:]:
810 entry = g.Entry()
811 entry.set_text(str(x))
812 win.vbox.pack_start(entry, TRUE, FALSE, 0)
813 if type(x) == str or type(x) == unicode:
814 editables.append(entry)
815 entry.connect('activate', lambda e: modify())
816 if not focus:
817 focus = entry
818 entry.grab_focus()
819 else:
820 entry.set_editable(FALSE)
821 editables.append(None)
823 win.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
824 win.add_button(g.STOCK_OK, g.RESPONSE_OK)
826 def response(box, resp):
827 box.destroy()
828 if resp == g.RESPONSE_OK:
829 modify()
830 win.connect('response', response)
832 if not focus:
833 win.set_response_sensitive(g.RESPONSE_OK, FALSE)
835 win.show_all()
837 def join_nodes(self, op, exit):
838 try:
839 x1, y1, x2, y2 = self.get_arrow_ends(op, exit)
841 prev_group = self.op_to_group[op]
842 line = getattr(prev_group, exit + '_line')
843 line.set(points = connect(x1, y1, x2, y2))
844 except Block:
845 print "*** ERROR setting arc from %s:%s" % (op, exit)
847 def op_event(self, item, event, op):
848 if event.type == g.gdk.BUTTON_PRESS:
849 print "Prev %s = %s" % (op, map(str, op.prev))
850 if event.button == 1:
851 if op.parent.start != op or not op.parent.is_toplevel():
852 self.drag_last_pos = (event.x, event.y)
853 else:
854 self.drag_last_pos = None
855 else:
856 self.show_op_menu(event, op)
857 elif event.type == g.gdk.BUTTON_RELEASE:
858 if event.button == 1:
859 self.drag_last_pos = None
860 self.program_changed(None)
861 elif event.type == g.gdk.ENTER_NOTIFY:
862 item.set(fill_color = '#339900')
863 elif event.type == g.gdk.LEAVE_NOTIFY:
864 item.set(fill_color = self.op_colour(op))
865 elif event.type == g.gdk.MOTION_NOTIFY and self.drag_last_pos:
866 if not event.state & g.gdk.BUTTON1_MASK:
867 print "(stop drag!)"
868 self.drag_last_pos = None
869 self.program_changed(None)
870 return 1
871 x, y = (event.x, event.y)
872 dx, dy = x - self.drag_last_pos[0], y - self.drag_last_pos[1]
873 if abs(op.dx + dx) < 4:
874 dx = -op.dx
875 x = dx + self.drag_last_pos[0]
876 if abs(op.dy + dy) < 4:
877 dy = -op.dy
878 y = dy + self.drag_last_pos[1]
879 op.dx += dx
880 op.dy += dy
881 self.drag_last_pos = (x, y)
883 self.op_to_group[op].move(dx, dy)
884 for p in op.prev:
885 if p.next == op:
886 self.join_nodes(p, 'next')
887 if p.fail == op:
888 self.join_nodes(p, 'fail')
889 self.update_links()
890 #self.create_node(self.prog.start, self.nodes)
891 self.update_points()
892 elif event.type == g.gdk._2BUTTON_PRESS:
893 if op.action[0] == 'Start':
894 self.edit_comment(op.parent)
895 else:
896 self.edit_op(op)
897 print "(edit; stop drag!)"
898 self.drag_last_pos = None
899 self.program_changed(None)
900 return 1
902 def edit_comment(self, block):
903 assert isinstance(block, Block)
905 def set(comment):
906 block.set_comment(comment)
907 GetArg('Comment', set, ['Comment:'],
908 message = '\\n for a newline', init = [block.comment])
910 def block_toggle_enter(self):
911 self.op_menu_op.toggle_enter()
913 def block_toggle_foreach(self):
914 self.op_menu_op.toggle_foreach()
916 def block_toggle_restore(self):
917 self.op_menu_op.toggle_restore()
919 def block_edit_comment(self):
920 self.edit_comment(self.op_menu_op)
922 def op_edit(self):
923 self.edit_op(self.op_menu_op)
925 def op_swap_nf(self):
926 self.op_menu_op.swap_nf()
928 def op_del_node(self):
929 op = self.op_menu_op
930 if op.next and op.fail:
931 rox.alert("Can't delete a node with both exits in use")
932 return
933 self.clipboard = op.del_node()
935 def show_op_menu(self, event, op):
936 if op.action[0] == 'Start':
937 self.op_menu_op = op.parent
938 block_menu.popup(self, event)
939 else:
940 self.op_menu_op = op
941 op_menu.popup(self, event)
943 def paste_chain(self, op, exit):
944 print "Paste", self.clipboard
945 doc = self.clipboard
946 new = load(doc.documentElement, op.parent,
947 self.view.model.namespaces)
948 start = new.start.next
949 new.start.unlink('next', may_delete = 0)
950 start.set_parent(None)
951 op.link_to(start, exit)
953 def end_link_drag(self, item, event, src_op, exit):
954 # Scan all the nodes looking for one nearby...
955 x, y = event.x, event.y
957 def closest_node(op):
958 "Return the closest (node, dist) in this chain to (x, y)"
959 nx, ny = self.op_to_group[op].i2w(0, 0)
960 if op is src_op:
961 best = None
962 elif isinstance(op, Block):
963 best = None
964 else:
965 best = (op, math.hypot(nx - x, ny - y))
966 if op.next and op.next.prev[0] == op:
967 next = closest_node(op.next)
968 if next and (best is None or next[1] < best[1]):
969 best = next
970 if op.fail and op.fail.prev[0] == op:
971 fail = closest_node(op.fail)
972 if fail and (best is None or fail[1] < best[1]):
973 best = fail
974 if isinstance(op, Block):
975 sub = closest_node(op.start)
976 if sub and (best is None or sub[1] < best[1]):
977 best = sub
978 return best
980 result = closest_node(self.prog.code)
981 if result:
982 node, dist = result
983 else:
984 dist = 1000
985 if dist > 12:
986 # Too far... put the line back to the disconnected state...
987 self.join_nodes(src_op, exit)
988 return
989 try:
990 while node.action[0] == 'Start':
991 node = node.parent
992 src_op.link_to(node, exit)
993 finally:
994 self.update_all()
996 def line_paste_chain(self):
997 op, exit = self.line_menu_line
998 self.paste_chain(op, exit)
1000 def line_add_block(self):
1001 op, exit = self.line_menu_line
1002 box = rox.Dialog()
1003 box.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
1004 box.add_button(g.STOCK_ADD, g.RESPONSE_OK)
1005 box.set_position(g.WIN_POS_MOUSE)
1006 box.set_has_separator(False)
1008 foreach = g.CheckButton('Foreach block')
1009 box.vbox.pack_start(foreach)
1010 enter = g.CheckButton('Enter-leave block')
1011 box.vbox.pack_start(enter)
1012 box.vbox.show_all()
1014 resp = box.run()
1015 box.destroy()
1016 if resp != g.RESPONSE_OK:
1017 return
1019 b = Block(op.parent)
1020 if foreach.get_active():
1021 b.toggle_foreach()
1022 if enter.get_active():
1023 b.toggle_enter()
1024 op.link_to(b, exit)
1025 #if self.view.rec_point == (op, exit):
1026 self.view.single_step = 1
1027 if self.view.rec_point:
1028 self.view.stop_recording()
1029 self.view.set_exec((op, exit))
1030 try:
1031 self.view.do_one_step()
1032 assert 0
1033 except View.InProgress:
1034 pass
1035 print self.exec_point
1036 self.view.record_at_point()
1038 def line_toggle_breakpoint(self):
1039 op, exit = self.line_menu_line
1040 bp = self.view.breakpoints
1041 if bp.has_key((op, exit)):
1042 del bp[(op, exit)]
1043 else:
1044 bp[(op, exit)] = 1
1045 self.prog.changed()
1047 def line_yank_chain(self):
1048 op, exit = self.line_menu_line
1049 next = getattr(op, exit)
1050 if not next:
1051 rox.alert('Nothing to yank!')
1052 return
1053 self.clipboard = next.to_doc()
1054 print self.clipboard
1056 def line_del_chain(self):
1057 op, exit = self.line_menu_line
1058 next = getattr(op, exit)
1059 if not next:
1060 rox.alert('Nothing to delete!')
1061 return
1062 self.clipboard = next.to_doc()
1063 op.unlink(exit)
1065 def show_menu(self, event, op, exit = None):
1066 if exit:
1067 self.line_menu_line = (op, exit)
1068 line_menu.popup(self, event)
1069 else:
1070 self.show_op_menu(event, op)
1072 def line_event(self, item, event, op, exit):
1073 # Item may be rec_point or exec_point...
1074 item = getattr(self.op_to_group[op], exit + '_line')
1076 if event.type == g.gdk.BUTTON_PRESS:
1077 if event.button == 1:
1078 if not getattr(op, exit):
1079 self.drag_last_pos = (event.x, event.y)
1080 elif event.button == 2:
1081 self.paste_chain(op, exit)
1082 elif event.button == 3:
1083 self.line_menu_line = (op, exit)
1084 line_menu.popup(self, event)
1085 elif event.type == g.gdk.BUTTON_RELEASE:
1086 if event.button == 1:
1087 print "Clicked exit %s of %s" % (exit, op)
1088 self.view.set_exec((op, exit))
1089 self.drag_last_pos = None
1090 if not getattr(op, exit):
1091 self.end_link_drag(item, event, op, exit)
1092 elif event.type == g.gdk.MOTION_NOTIFY and self.drag_last_pos:
1093 if not event.state & g.gdk.BUTTON1_MASK:
1094 print "(stop drag!)"
1095 self.drag_last_pos = None
1096 if not getattr(op, exit):
1097 self.end_link_drag(item, event, op, exit)
1098 return 1
1099 x, y = (event.x, event.y)
1100 dx, dy = x - self.drag_last_pos[0], y - self.drag_last_pos[1]
1102 if abs(dx) > 4 or abs(dy) > 4:
1103 sx, sy = self.get_arrow_start(op, exit)
1104 x, y = item.w2i(event.x, event.y)
1105 gr = self.op_to_group[op]
1106 if exit == 'fail':
1107 width = gr.width
1108 else:
1109 width = 0
1110 item.set(points = connect(sx, sy, x, y))
1111 elif event.type == g.gdk.ENTER_NOTIFY:
1112 item.set(fill_color = '#339900')
1113 elif event.type == g.gdk.LEAVE_NOTIFY:
1114 if exit == 'next':
1115 item.set(fill_color = 'black')
1116 else:
1117 item.set(fill_color = '#ff6666')
1118 return 1
1120 def get_arrow_start(self, op, exit):
1121 gr = self.op_to_group[op]
1122 return ((exit == 'fail' and gr.width) or 0, gr.height)
1124 def get_arrow_ends(self, op, exit):
1125 """Return coords of arrow, relative to op's group."""
1126 op2 = getattr(op, exit)
1128 prev_group = self.op_to_group[op]
1130 x1, y1 = self.get_arrow_start(op, exit)
1132 if op2:
1133 try:
1134 group = self.op_to_group[op2]
1135 except:
1136 x2 = x1 + 50
1137 y2 = y1 + 50
1138 else:
1139 x2, y2 = group.i2w(0, 0)
1140 x2, y2 = prev_group.w2i(x2, y2)
1141 elif exit == 'next':
1142 x2, y2 = DEFAULT_NEXT
1143 x2 += x1
1144 y2 += y1
1145 else:
1146 x2, y2 = DEFAULT_FAIL
1147 x2 += x1
1148 y2 += y1
1149 return (x1, y1, x2, y2)
1151 def set_bounds(self):
1152 #self.update_now() # GnomeCanvas bug?
1153 min_x, min_y, max_x, max_y = self.root().get_bounds()
1154 min_x -= 8
1155 max_x += 8
1156 min_y -= 8
1157 max_y += 8
1158 self.set_scroll_region(min_x, min_y, max_x, max_y)
1159 self.root().move(0, 0) # Magic!
1160 #self.set_usize(max_x - min_x, -1)
1162 class ChainWindow(rox.Window):
1163 def __init__(self, view, prog):
1164 rox.Window.__init__(self)
1165 swin = g.ScrolledWindow()
1166 self.add(swin)
1167 disp = ChainDisplay(view, prog)
1168 swin.add(disp)
1170 swin.show_all()
1171 self.disp = disp
1172 self.set_default_size(-1, 200)
1173 self.set_title(prog.name)
1175 def update_points(self):
1176 self.disp.update_points()