1 from __future__
import generators
4 from rox
import g
, TRUE
, FALSE
, alert
5 from gnome
import canvas
9 from StringIO
import StringIO
13 from rox
.Menu
import Menu
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
)
59 return text
[:26] + '...'
61 def connect(x1
, y1
, x2
, y2
):
62 """Chop 5 pixels off both ends of this line"""
66 l
= math
.hypot(dx
, dy
)
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
):
79 if text
== 'Start': return ''
82 text
= string
.capitalize(string
.replace(text
, '_', ' '))
87 if action
[0] == 'do_search' or action
[0] == 'xpath':
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', '[')
96 i
= string
.rfind(pat
[:20], '/')
98 i
= string
.rfind(pat
[:20], ':')
101 details
= details
+ pat
[:i
+ 1] + '\n'
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:])
118 details
= str(action
[1])
121 details
= `action
[1:]`
123 details
= str(action
[1])
124 if len(details
) > 20:
125 details
= trunc(`details`
)
126 text
= text
+ '\n' + details
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
)
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)
149 self
.pack_start(pane
, expand
= 1, fill
= 1)
151 swin
= g
.ScrolledWindow()
152 swin
.set_policy(g
.POLICY_NEVER
, g
.POLICY_AUTOMATIC
)
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
)
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()
174 v
.set_shadow_type(g
.SHADOW_NONE
)
177 swin
= g
.ScrolledWindow()
178 swin
.set_policy(g
.POLICY_AUTOMATIC
, g
.POLICY_AUTOMATIC
)
180 swin
.add_with_viewport(self
.chains
)
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
)
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()
197 model
, iter = selected
199 path
= model
.get_value(iter, 1)
200 self
.chains
.switch_to(self
.view
.name_to_prog(path
))
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
209 def update_points(self
):
210 self
.chains
.update_points()
211 for x
in self
.sub_windows
:
214 def program_changed(self
, op
):
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
:
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
,
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
):
241 print "run_return: failure!"
242 self
.view
.jump_to_innermost_failure()
243 def cb(choice
, self
= self
):
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
)
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
)
263 self
.view
.run_new(self
.run_return
)
264 if event
.state
& g
.gdk
.SHIFT_MASK
:
265 self
.view
.may_record(['map', path
])
267 self
.view
.may_record(['play', path
])
270 def menu_delete(self
):
271 prog
= self
.prog_menu_prog
273 rox
.alert("Can't delete the root program!")
275 prog
.parent
.remove_sub(prog
)
277 def menu_rename(self
):
278 prog
= self
.prog_menu_prog
279 def rename(name
, prog
= prog
):
281 GetArg('Rename program', rename
, ['Program name:'])
283 def menu_new_prog(self
):
284 prog
= self
.prog_menu_prog
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
)
294 self
.sub_windows
.append(cw
)
296 self
.sub_windows
.remove(cw
)
298 cw
.connect('destroy', lost_cw
)
301 prog
= self
.prog_menu_prog
302 self
.view
.run_new(self
.run_return
)
303 self
.view
.may_record(['map', prog
.get_path()])
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
)
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
]
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
)
342 def switch_to(self
, prog
):
344 def update_points(self
):
348 "A visual object in the display."
349 def __init__(self
, da
, x
, y
):
358 w
.draw_rectangle(da
.style
.black_gc
, True, self
.x
, self
.y
, 10, 10)
360 def maybe_clicked(self
, event
):
363 class ChainOp(ChainNode
):
364 def __init__(self
, da
, op
, x
, y
):
366 ChainNode
.__init
__(self
, da
, x
, y
)
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)
377 if op
.fail
and op
.fail
.prev
[0] == op
:
378 self
.fail
= da
.create_op(op
.fail
, x
+ 100, y
+ self
.height
+ 4)
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()
388 self
.height
= max(self
.height
, 20)
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
):
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
):
414 if x
< 0: return False
416 if y
< 0: return False
418 if x
> self
.width
or y
> self
.height
:
421 if event
.button
== 1:
422 self
.da
.view
.set_exec((self
.op
, 'next'))
424 if x
< 10 and y
> 10:
425 self
.da
.show_menu(event
, self
.op
, 'next')
427 self
.da
.show_menu(event
, self
.op
)
433 for n
in self
.next
.all_nodes(): yield n
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
)
444 while p
and not isinstance(p
, Program
):
448 def build_leaf(self
):
453 self
.layout
= self
.da
.create_pango_layout(self
.op
.comment
.replace('\\n', '\n'))
454 self
.width
, height
= self
.layout
.get_pixel_size()
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])
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
)
472 self
.height
+= 4 + (self
.op
.enter
+ self
.op
.restore
) * 6
477 w
.draw_rectangle(da
.style
.black_gc
, False, self
.x
, self
.y
, self
.width
, self
.height
)
478 pen
= da
.style
.white_gc
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)
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)
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)
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)
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)
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
):
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
)
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
539 self
.set_size_request(100, 100)
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
)
551 def set_active(self
, active
):
553 self
.modify_bg(g
.STATE_NORMAL
, g
.gdk
.color_parse('white'))
555 self
.modify_bg(g
.STATE_NORMAL
, g
.gdk
.color_parse('#B3AA73'))
557 self
.modify_bg(g
.STATE_NORMAL
, g
.gdk
.color_parse('#FFC0C0'))
559 def update_points(self
):
563 self
.scroll_to_show(self
.rec_point
)
565 def scroll_to_show(self
, item
):
569 (lx
, ly
, hx
, hy
) = item
.get_bounds()
570 x
, y
= item
.i2w(0, 0)
571 x
, y
= self
.w2c(x
, y
)
578 sx
, sy
= self
.get_scroll_offsets()
584 (x
, y
, w
, h
) = self
.get_allocation()
593 self
.scroll_to(sx
, sy
)
595 def put_point(self
, point
):
599 if op
.get_program() != self
.prog
: return
601 obj
= self
.op_to_object
[op
]
603 print "Can't find %s!\n" % op
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
:
618 def prog_tree_changed(self
):
621 def program_changed(self
, op
):
622 if (not op
) or op
.get_program() == self
.prog
:
625 def create_op(self
, op
, x
, y
):
626 if isinstance(op
, Block
):
627 return ChainBlock(self
, op
, x
, y
)
629 return ChainOp(self
, op
, x
, y
)
631 def update_all(self
):
632 self
.op_to_object
= {}
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)
637 self
.root_object
= None
638 self
.set_size_request(-1, -1)
642 def expose(self
, da
, event
):
644 self
.root_object
.expose()
646 self
.put_point(self
.view
.rec_point
)
647 self
.put_point(self
.view
.exec_point
)
651 def button_press(self
, da
, event
):
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
:
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
670 if op
.next
.prev
[0] == op
:
671 self
.update_links(op
.next
)
673 self
.join_nodes(op
, 'next')
675 if op
.fail
.prev
[0] == op
:
676 self
.update_links(op
.fail
)
678 self
.join_nodes(op
, 'fail')
679 if isinstance(op
, Block
):
680 self
.update_links(op
.start
)
682 def create_node(self
, op
, parent
):
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)
696 while isinstance(next
, Block
):
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),
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')
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()
722 while isinstance(fail
, Block
):
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),
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
):
750 if op
.action
[0] == 'do_search' or op
.action
[0] == 'do_global':
751 t
= editables
[0].get_text()
753 from Ft
.Xml
.XPath
import XPathParser
754 if t
.find('@CURRENT@') == -1:
756 XPathParser
.new().parse(t
)
758 alert('Invalid search pattern!')
764 op
.action
[i
] = e
.get_text()
766 print "Done editing!"
770 win
.vbox
.pack_start(g
.Label(op
.action
[0]), TRUE
, FALSE
, 0)
771 editables
= [] # [ Entry | None ]
773 for x
in op
.action
[1:]:
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())
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
):
792 if resp
== g
.RESPONSE_OK
:
794 win
.connect('response', response
)
797 win
.set_response_sensitive(g
.RESPONSE_OK
, FALSE
)
801 def join_nodes(self
, op
, exit
):
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
))
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
)
818 self
.drag_last_pos
= None
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
:
832 self
.drag_last_pos
= None
833 self
.program_changed(None)
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:
839 x
= dx
+ self
.drag_last_pos
[0]
840 if abs(op
.dy
+ dy
) < 4:
842 y
= dy
+ self
.drag_last_pos
[1]
845 self
.drag_last_pos
= (x
, y
)
847 self
.op_to_group
[op
].move(dx
, dy
)
850 self
.join_nodes(p
, 'next')
852 self
.join_nodes(p
, 'fail')
854 #self.create_node(self.prog.start, self.nodes)
856 elif event
.type == g
.gdk
._2BUTTON
_PRESS
:
857 if op
.action
[0] == 'Start':
858 self
.edit_comment(op
.parent
)
861 print "(edit; stop drag!)"
862 self
.drag_last_pos
= None
863 self
.program_changed(None)
866 def edit_comment(self
, block
):
867 assert isinstance(block
, Block
)
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
)
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
):
894 if op
.next
and op
.fail
:
895 rox
.alert("Can't delete a node with both exits in use")
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
)
905 op_menu
.popup(self
, event
)
907 def paste_chain(self
, op
, exit
):
908 print "Paste", 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)
925 elif isinstance(op
, Block
):
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]):
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]):
937 if isinstance(op
, Block
):
938 sub
= closest_node(op
.start
)
939 if sub
and (best
is None or sub
[1] < best
[1]):
943 result
= closest_node(self
.prog
.code
)
949 # Too far... put the line back to the disconnected state...
950 self
.join_nodes(src_op
, exit
)
953 while node
.action
[0] == 'Start':
955 src_op
.link_to(node
, exit
)
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
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
)
977 if resp
== g
.RESPONSE_OK
:
979 if foreach
.get_active():
981 if enter
.get_active():
984 if self
.view
.rec_point
== (op
, exit
):
985 self
.view
.single_step
= 1
986 self
.view
.stop_recording()
988 self
.view
.do_one_step()
989 except View
.InProgress
:
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
)):
1001 def line_yank_chain(self
):
1002 op
, exit
= self
.line_menu_line
1003 next
= getattr(op
, exit
)
1005 rox
.alert('Nothing to yank!')
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
)
1014 rox
.alert('Nothing to delete!')
1016 self
.clipboard
= next
.to_doc()
1019 def show_menu(self
, event
, op
, exit
= None):
1021 self
.line_menu_line
= (op
, exit
)
1022 line_menu
.popup(self
, event
)
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
)
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
]
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
:
1071 item
.set(fill_color
= 'black')
1073 item
.set(fill_color
= '#ff6666')
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
)
1090 group
= self
.op_to_group
[op2
]
1095 x2
, y2
= group
.i2w(0, 0)
1096 x2
, y2
= prev_group
.w2i(x2
, y2
)
1097 elif exit
== 'next':
1098 x2
, y2
= DEFAULT_NEXT
1102 x2
, y2
= DEFAULT_FAIL
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()
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()
1130 disp
= ChainDisplay(view
, prog
)
1135 self
.set_default_size(-1, 200)
1136 self
.set_title(prog
.name
)
1138 def update_points(self
):
1139 self
.disp
.update_points()