1 from __future__
import generators
4 from rox
import g
, TRUE
, FALSE
, alert
8 from StringIO
import StringIO
12 from rox
.Menu
import Menu
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
60 return text
[:26] + '...'
62 def connect(x1
, y1
, x2
, y2
):
63 """Chop 5 pixels off both ends of this line"""
67 l
= math
.hypot(dx
, dy
)
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
):
80 if text
== 'Start': return ''
83 text
= string
.capitalize(string
.replace(text
, '_', ' '))
88 if action
[0] in ('do_search', 'xpath', 'move_selection'):
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', '[')
97 i
= string
.rfind(pat
[:20], '/')
99 i
= string
.rfind(pat
[:20], ':')
102 details
= details
+ pat
[:i
+ 1] + '\n'
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:])
119 details
= str(action
[1])
122 details
= `action
[1:]`
124 details
= str(action
[1])
125 if len(details
) > 20:
126 details
= trunc(`details`
)
127 text
= text
+ '\n' + details
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
)
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)
150 self
.pack_start(pane
, expand
= 1, fill
= 1)
152 swin
= g
.ScrolledWindow()
153 swin
.set_policy(g
.POLICY_NEVER
, g
.POLICY_AUTOMATIC
)
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
)
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()
175 v
.set_shadow_type(g
.SHADOW_NONE
)
178 swin
= g
.ScrolledWindow()
179 swin
.set_policy(g
.POLICY_AUTOMATIC
, g
.POLICY_AUTOMATIC
)
181 swin
.add_with_viewport(self
.chains
)
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
)
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()
198 model
, iter = selected
200 path
= model
.get_value(iter, 1)
201 self
.chains
.switch_to(self
.view
.name_to_prog(path
))
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
210 def update_points(self
):
211 self
.chains
.update_points()
212 for x
in self
.sub_windows
:
215 def program_changed(self
, op
):
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
:
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
,
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
243 #self.view.jump_to_innermost_failure()
245 if rox
.confirm("Program failed - record a failure case?",
246 g
.STOCK_NO
, 'Record'):
247 self
.view
.record_at_point()
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
))
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
)
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
])
269 self
.view
.may_record(['play', path
])
272 def menu_delete(self
):
273 prog
= self
.prog_menu_prog
275 rox
.alert("Can't delete the root program!")
277 prog
.parent
.remove_sub(prog
)
279 def menu_rename(self
):
280 prog
= self
.prog_menu_prog
281 def rename(name
, prog
= prog
):
283 GetArg('Rename program', rename
, ['Program name:'])
285 def menu_new_prog(self
):
286 prog
= self
.prog_menu_prog
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
)
296 self
.sub_windows
.append(cw
)
298 self
.sub_windows
.remove(cw
)
299 cw
.connect('destroy', lost_cw
)
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()])
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
)
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
]
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
)
348 def switch_to(self
, prog
):
350 def update_points(self
):
354 "A visual object in the display."
355 def __init__(self
, da
, x
, y
):
363 w
.draw_rectangle(da
.style
.black_gc
, True, self
.x
, self
.y
, 10, 10)
365 def maybe_clicked(self
, event
):
368 class ChainOp(ChainNode
):
369 def __init__(self
, da
, op
, x
, y
):
371 ChainNode
.__init
__(self
, da
, x
, y
)
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
)
382 self
.total_width
= self
.width
384 if op
.fail
and op
.fail
.prev
[0] == op
:
386 indent
= self
.total_width
+ 20
389 self
.fail
= da
.create_op(op
.fail
, x
+ indent
, y
+ self
.height
+ 4)
390 self
.total_width
= indent
+ self
.fail
.total_width
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()
400 self
.height
= max(self
.height
, 20)
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
):
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'"
435 if x
< 0: return False
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
:
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
:
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
)
452 if event
.button
== 1:
453 if pos
in ('next', 'fail'):
454 self
.da
.view
.set_exec((self
.op
, pos
))
457 self
.da
.show_menu(event
, self
.op
, pos
)
459 self
.da
.show_menu(event
, self
.op
)
465 for n
in self
.next
.all_nodes(): yield n
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
)
476 while p
and not isinstance(p
, Program
):
480 def build_leaf(self
):
485 self
.layout
= self
.da
.create_pango_layout(self
.op
.comment
.replace('\\n', '\n'))
486 self
.width
, height
= self
.layout
.get_pixel_size()
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])
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
)
504 self
.height
+= 4 + (self
.op
.enter
+ self
.op
.restore
) * 6
509 w
.draw_rectangle(da
.style
.black_gc
, False, self
.x
, self
.y
, self
.width
, self
.height
)
510 pen
= da
.style
.white_gc
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)
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)
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)
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)
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)
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
):
553 def where(self
, x
, y
):
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
))
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
577 self
.set_size_request(100, 100)
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
)
589 self
.connect('motion-notify-event', self
.motion
)
593 def set_active(self
, active
):
595 self
.modify_bg(g
.STATE_NORMAL
, g
.gdk
.color_parse('white'))
597 self
.modify_bg(g
.STATE_NORMAL
, g
.gdk
.color_parse('#B3AA73'))
599 self
.modify_bg(g
.STATE_NORMAL
, g
.gdk
.color_parse('#FFC0C0'))
601 def update_points(self
):
605 self
.scroll_to_show(self
.rec_point
)
607 def scroll_to_show(self
, item
):
611 (lx
, ly
, hx
, hy
) = item
.get_bounds()
612 x
, y
= item
.i2w(0, 0)
613 x
, y
= self
.w2c(x
, y
)
620 sx
, sy
= self
.get_scroll_offsets()
626 (x
, y
, w
, h
) = self
.get_allocation()
635 self
.scroll_to(sx
, sy
)
637 def put_point(self
, point
):
641 if op
.get_program() != self
.prog
: return
643 obj
= self
.op_to_object
[op
]
645 print "Can't find %s!\n" % op
650 if point
is self
.view
.rec_point
:
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
:
678 def prog_tree_changed(self
):
681 def program_changed(self
, op
):
682 if (not op
) or op
.get_program() == self
.prog
:
685 def create_op(self
, op
, x
, y
):
686 if isinstance(op
, Block
):
687 return ChainBlock(self
, op
, x
, y
)
689 return ChainOp(self
, op
, x
, y
)
691 def update_all(self
):
692 self
.op_to_object
= {}
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)
697 self
.root_object
= None
698 self
.set_size_request(-1, -1)
703 def size_allocate(self
, alloc
):
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
)
713 self
.root_object
.expose()
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)
723 self
.put_point(self
.view
.rec_point
)
724 self
.put_point(self
.view
.exec_point
)
729 op
, exit
= self
.hover
730 pen
= self
.style
.black_gc
733 w
.draw_rectangle(pen
, False, op
.x
+ fail_box
[0], op
.y
+ fail_box
[1],
736 w
.draw_rectangle(pen
, False, op
.x
+ next_box
[0], op
.y
+ next_box
[1],
739 def motion(self
, box
, event
):
741 for op
in self
.op_to_object
.itervalues():
742 pos
= op
.where(event
.x
, event
.y
)
743 if pos
in ('next', 'fail'):
745 if hover
== self
.hover
:
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
:
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
768 if op
.next
.prev
[0] == op
:
769 self
.update_links(op
.next
)
771 self
.join_nodes(op
, 'next')
773 if op
.fail
.prev
[0] == op
:
774 self
.update_links(op
.fail
)
776 self
.join_nodes(op
, 'fail')
777 if isinstance(op
, Block
):
778 self
.update_links(op
.start
)
780 def create_node(self
, op
, parent
):
784 def edit_op(self
, op
):
786 if op
.action
[0] == 'do_search' or op
.action
[0] == 'do_global':
787 t
= editables
[0].get_text()
789 from Ft
.Xml
.XPath
import XPathParser
790 if t
.find('@CURRENT@') == -1:
792 XPathParser
.new().parse(t
)
794 alert('Invalid search pattern!')
800 op
.action
[i
] = e
.get_text()
802 print "Done editing!"
806 win
.vbox
.pack_start(g
.Label(op
.action
[0]), TRUE
, FALSE
, 0)
807 editables
= [] # [ Entry | None ]
809 for x
in op
.action
[1:]:
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())
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
):
828 if resp
== g
.RESPONSE_OK
:
830 win
.connect('response', response
)
833 win
.set_response_sensitive(g
.RESPONSE_OK
, FALSE
)
837 def join_nodes(self
, op
, exit
):
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
))
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
)
854 self
.drag_last_pos
= None
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
:
868 self
.drag_last_pos
= None
869 self
.program_changed(None)
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:
875 x
= dx
+ self
.drag_last_pos
[0]
876 if abs(op
.dy
+ dy
) < 4:
878 y
= dy
+ self
.drag_last_pos
[1]
881 self
.drag_last_pos
= (x
, y
)
883 self
.op_to_group
[op
].move(dx
, dy
)
886 self
.join_nodes(p
, 'next')
888 self
.join_nodes(p
, 'fail')
890 #self.create_node(self.prog.start, self.nodes)
892 elif event
.type == g
.gdk
._2BUTTON
_PRESS
:
893 if op
.action
[0] == 'Start':
894 self
.edit_comment(op
.parent
)
897 print "(edit; stop drag!)"
898 self
.drag_last_pos
= None
899 self
.program_changed(None)
902 def edit_comment(self
, block
):
903 assert isinstance(block
, Block
)
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
)
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
):
930 if op
.next
and op
.fail
:
931 rox
.alert("Can't delete a node with both exits in use")
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
)
941 op_menu
.popup(self
, event
)
943 def paste_chain(self
, op
, exit
):
944 print "Paste", 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)
962 elif isinstance(op
, Block
):
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]):
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]):
974 if isinstance(op
, Block
):
975 sub
= closest_node(op
.start
)
976 if sub
and (best
is None or sub
[1] < best
[1]):
980 result
= closest_node(self
.prog
.code
)
986 # Too far... put the line back to the disconnected state...
987 self
.join_nodes(src_op
, exit
)
990 while node
.action
[0] == 'Start':
992 src_op
.link_to(node
, exit
)
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
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
)
1016 if resp
!= g
.RESPONSE_OK
:
1019 b
= Block(op
.parent
)
1020 if foreach
.get_active():
1022 if enter
.get_active():
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
))
1031 self
.view
.do_one_step()
1033 except View
.InProgress
:
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
)):
1047 def line_yank_chain(self
):
1048 op
, exit
= self
.line_menu_line
1049 next
= getattr(op
, exit
)
1051 rox
.alert('Nothing to yank!')
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
)
1060 rox
.alert('Nothing to delete!')
1062 self
.clipboard
= next
.to_doc()
1065 def show_menu(self
, event
, op
, exit
= None):
1067 self
.line_menu_line
= (op
, exit
)
1068 line_menu
.popup(self
, event
)
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
)
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
]
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
:
1115 item
.set(fill_color
= 'black')
1117 item
.set(fill_color
= '#ff6666')
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
)
1134 group
= self
.op_to_group
[op2
]
1139 x2
, y2
= group
.i2w(0, 0)
1140 x2
, y2
= prev_group
.w2i(x2
, y2
)
1141 elif exit
== 'next':
1142 x2
, y2
= DEFAULT_NEXT
1146 x2
, y2
= DEFAULT_FAIL
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()
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()
1167 disp
= ChainDisplay(view
, prog
)
1172 self
.set_default_size(-1, 200)
1173 self
.set_title(prog
.name
)
1175 def update_points(self
):
1176 self
.disp
.update_points()