2 from rox
import g
, TRUE
, FALSE
, alert
3 from gnome
import canvas
7 from StringIO
import StringIO
11 from rox
.Menu
import Menu
16 prog_menu
= Menu('programs', [
17 ('/Play', 'menu_play', '', ''),
18 ('/Map', 'menu_map', '', ''),
19 ('/View', 'menu_new_view', '', ''),
20 ('/', '', '', '<separator>'),
21 ('/New program', 'menu_new_prog', '', ''),
22 ('/Rename', 'menu_rename', '', ''),
23 ('/Delete', 'menu_delete', '', ''),
26 line_menu
= Menu('line', [
27 ('/Set\/clear breakpoint', 'line_toggle_breakpoint', '', ''),
28 ('/Yank chain', 'line_yank_chain', '', ''),
29 ('/Remove link', 'line_del_chain', '', ''),
30 ('/Paste chain', 'line_paste_chain', '', ''),
31 ('/Add block', 'line_add_block', '', '')
34 block_menu
= Menu('op', [
35 ('/Toggle Enter\/Leave', 'block_toggle_enter', '', ''),
36 ('/Toggle Foreach','block_toggle_foreach', '', ''),
37 ('/Toggle Restore Mark','block_toggle_restore', '', ''),
38 ('/Edit comment', 'block_edit_comment', '', ''),
39 ('/Swap next\/fail', 'op_swap_nf', '', ''),
40 ('/Remove node', 'op_del_node', '', '')
43 op_menu
= Menu('op', [
44 ('/Edit node', 'op_edit', '', ''),
45 ('/Swap next\/fail', 'op_swap_nf', '', ''),
46 ('/Remove node', 'op_del_node', '', '')
49 from GetArg
import GetArg
50 from Program
import Program
, load
, Block
52 no_cursor
= g
.gdk
.Cursor(g
.gdk
.TCROSS
)
57 return text
[:26] + '...'
59 def connect(x1
, y1
, x2
, y2
):
60 """Chop 5 pixels off both ends of this line"""
64 l
= math
.hypot(dx
, dy
)
68 return (x1
+ dx
, y1
+ dy
, x2
- dx
, y2
- dy
)
70 DEFAULT_NEXT
= (0, 25)
71 DEFAULT_FAIL
= (20, 20)
73 expand_history
= {} # Prog name -> expanded flag
75 def action_to_text(action
):
79 text
= string
.capitalize(string
.replace(text
, '_', ' '))
84 if action
[0] == 'do_search' or action
[0] == 'xpath':
86 pat
= string
.replace(pat
, 'following-sibling::', '>>')
87 pat
= string
.replace(pat
, 'preceding-sibling::', '<<')
88 pat
= string
.replace(pat
, 'child::', '')
89 pat
= string
.replace(pat
, '[1]', '')
90 pat
= string
.replace(pat
, 'text()[ext:match', '[')
93 i
= string
.rfind(pat
[:20], '/')
95 i
= string
.rfind(pat
[:20], ':')
98 details
= details
+ pat
[:i
+ 1] + '\n'
100 details
= details
+ pat
101 elif action
[0] == 'attribute':
102 details
= trunc(str(action
[2]))
103 elif action
[0] == 'set_attrib':
104 details
= trunc(str(action
[1]))
105 elif action
[0] == 'add_attrib':
106 details
= trunc(str(action
[2]))
107 elif action
[0] == 'add_node':
108 details
= trunc(action
[2])
109 elif action
[0] == 'subst':
110 details
= action
[1] + ' -> ' + action
[2]
111 elif action
[0] == 'play' or action
[0] == 'map':
112 if len(action
[1]) > 20:
113 details
= '...' + str(action
[1][-19:])
115 details
= str(action
[1])
118 details
= `action
[1:]`
120 details
= str(action
[1])
121 if len(details
) > 20:
122 details
= trunc(`details`
)
123 text
= text
+ '\n' + details
127 def __init__(self
, view
):
128 g
.VBox
.__init
__(self
)
130 def destroyed(widget
):
131 print "List destroy!!"
132 sel
.disconnect(self
.sel_changed_signal
)
133 self
.view
.lists
.remove(self
)
134 self
.view
.model
.root_program
.watchers
.remove(self
)
135 self
.connect('destroy', destroyed
)
138 self
.sub_windows
= []
140 self
.stack_frames
= g
.Label('')
141 self
.pack_start(self
.stack_frames
, FALSE
, TRUE
, 0)
142 self
.stack_frames
.show()
143 self
.update_stack(None)
146 self
.pack_start(pane
, expand
= 1, fill
= 1)
148 swin
= g
.ScrolledWindow()
149 swin
.set_policy(g
.POLICY_NEVER
, g
.POLICY_AUTOMATIC
)
151 self
.prog_model
= g
.TreeStore(str, str)
152 tree
= g
.TreeView(self
.prog_model
)
153 tree
.connect('button-press-event', self
.button_press
)
154 tree
.unset_flags(g
.CAN_FOCUS
)
155 tree
.set_headers_visible(FALSE
)
158 cell
= g
.CellRendererText()
159 column
= g
.TreeViewColumn('Program', cell
, text
= 0)
160 tree
.append_column(column
)
162 sel
= tree
.get_selection()
163 # Doesn't get destroyed, so record signal number
164 self
.sel_changed_signal
= sel
.connect('changed', self
.change_prog
)
166 self
.chains
= ChainDisplay(view
)
167 self
.prog_tree_changed()
171 v
.set_shadow_type(g
.SHADOW_NONE
)
174 swin
= g
.ScrolledWindow()
175 swin
.set_policy(g
.POLICY_AUTOMATIC
, g
.POLICY_AUTOMATIC
)
177 swin
.add_with_viewport(self
.chains
)
180 pane
.set_position(200)
182 sel
.set_mode(g
.SELECTION_BROWSE
)
183 root_iter
= self
.prog_model
.get_iter_first()
184 sel
.select_iter(root_iter
)
185 tree
.expand_row(self
.prog_model
.get_path(root_iter
), FALSE
)
187 self
.view
.lists
.append(self
)
188 self
.view
.model
.root_program
.watchers
.append(self
)
190 def change_prog(self
, sel
):
191 selected
= sel
.get_selected()
194 model
, iter = selected
196 path
= model
.get_value(iter, 1)
197 self
.chains
.switch_to(self
.view
.name_to_prog(path
))
199 self
.chains
.switch_to(None)
201 def set_innermost_failure(self
, op
):
202 prog
= op
.get_program()
203 print "list: set_innermost_failure:", prog
206 def update_points(self
):
207 self
.chains
.update_points()
208 for x
in self
.sub_windows
:
211 def program_changed(self
, op
):
214 def prog_tree_changed(self
):
215 self
.prog_to_path
= {}
216 self
.prog_model
.clear()
217 self
.build_tree(self
.view
.model
.root_program
)
219 # Check for now deleted programs still being displayed
220 root
= self
.view
.model
.root_program
221 if self
.chains
and self
.chains
.prog
and not self
.chains
.prog
.parent
:
222 self
.chains
.switch_to(None)
223 for x
in self
.sub_windows
:
224 if x
.disp
.prog
is not root
and not x
.disp
.prog
.parent
:
227 def build_tree(self
, prog
, iter = None):
228 child_iter
= self
.prog_model
.append(iter)
229 self
.prog_model
.set(child_iter
, 0, prog
.name
,
232 self
.prog_to_path
[prog
] = self
.prog_model
.get_path(child_iter
)
233 for p
in prog
.subprograms
.values():
234 self
.build_tree(p
, child_iter
)
236 def run_return(self
, exit
):
238 print "run_return: failure!"
239 self
.view
.jump_to_innermost_failure()
240 def cb(choice
, self
= self
):
242 self
.view
.record_at_point()
243 if rox
.confirm("Program failed - record a failure case?", g
.STOCK_NO
, 'Record'):
244 self
.view
.record_at_point()
245 print "List: execution done!"
247 def button_press(self
, tree
, event
):
248 if event
.button
== 2 or event
.button
== 3:
249 ret
= tree
.get_path_at_pos(event
.x
, event
.y
)
251 return 1 # Click on blank area
252 path
, col
, cx
, cy
= ret
253 print "Event on", path
254 iter = self
.prog_model
.get_iter(path
)
255 path
= self
.prog_model
.get_value(iter, 1)
256 if event
.button
== 3:
257 prog
= self
.view
.name_to_prog(path
)
258 self
.show_menu(event
, prog
)
260 self
.view
.run_new(self
.run_return
)
261 if event
.state
& g
.gdk
.SHIFT_MASK
:
262 self
.view
.may_record(['map', path
])
264 self
.view
.may_record(['play', path
])
267 def menu_delete(self
):
268 prog
= self
.prog_menu_prog
270 rox
.alert("Can't delete the root program!")
272 prog
.parent
.remove_sub(prog
)
274 def menu_rename(self
):
275 prog
= self
.prog_menu_prog
276 def rename(name
, prog
= prog
):
278 GetArg('Rename program', rename
, ['Program name:'])
280 def menu_new_prog(self
):
281 prog
= self
.prog_menu_prog
285 GetArg('New program', create
, ['Program name:'])
287 def menu_new_view(self
):
288 prog
= self
.prog_menu_prog
289 cw
= ChainWindow(self
.view
, prog
)
291 self
.sub_windows
.append(cw
)
293 self
.sub_windows
.remove(cw
)
295 cw
.connect('destroy', lost_cw
)
298 prog
= self
.prog_menu_prog
299 self
.view
.run_new(self
.run_return
)
300 self
.view
.may_record(['map', prog
.get_path()])
303 prog
= self
.prog_menu_prog
304 self
.view
.run_new(self
.run_return
)
305 self
.view
.may_record(['play', prog
.get_path()])
307 def show_menu(self
, event
, prog
):
308 self
.prog_menu_prog
= prog
309 prog_menu
.popup(self
, event
)
311 def update_stack(self
, op
):
312 "The stack has changed - redraw 'op'"
313 if op
and op
.get_program() == self
.chains
.prog
:
314 self
.chains
.update_all()
315 l
= len(self
.view
.exec_stack
) + len(self
.view
.foreach_stack
)
321 text
= '%d frames' % l
322 if self
.view
.chroots
:
323 text
+= ' (%d enters)' % len(self
.view
.chroots
)
324 self
.stack_frames
.set_text(text
)
326 def show_prog(self
, prog
):
327 path
= self
.prog_to_path
[prog
]
331 self
.tree
.expand_row(tuple(partial
), FALSE
)
332 iter = self
.prog_model
.get_iter(path
)
333 self
.tree
.get_selection().select_iter(iter)
335 class ChainDummy(g
.TreeView
):
336 def __init__(self
, view
, prog
= None):
337 g
.TreeView
.__init
__(self
)
339 def switch_to(self
, prog
):
341 def update_points(self
):
344 def create_op(op
, x
, y
):
345 if isinstance(op
, Block
):
346 return ChainBlock(op
, x
, y
)
348 return ChainOp(op
, x
, y
)
351 "A visual object in the display."
352 def __init__(self
, x
, y
):
356 def expose(self
, da
):
359 w
.draw_rectangle(da
.style
.black_gc
, True, self
.x
, self
.y
, 10, 10)
361 class ChainOp(ChainNode
):
362 def __init__(self
, op
, x
, y
):
364 ChainNode
.__init
__(self
, x
, y
)
367 if op
.next
: self
.next
= create_op(op
.next
, x
, y
+ 20)
368 else: self
.next
= None
370 if op
.fail
: self
.fail
= create_op(op
.fail
, x
+ 100, y
+ 20)
371 else: self
.fail
= None
373 def expose(self
, da
):
377 text
= str(action_to_text(op
.action
))
378 self
.layout
= da
.create_pango_layout(text
)
380 w
.draw_arc(da
.style
.black_gc
, False, self
.x
, self
.y
, 10, 10, 0, 360 * 60)
381 w
.draw_layout(da
.style
.black_gc
, self
.x
+ 12, self
.y
, self
.layout
)
383 if self
.next
: self
.next
.expose(da
)
384 if self
.fail
: self
.fail
.expose(da
)
387 text_font
= 'verdana 10'
389 if op
.action
[0] == 'Start':
390 text
= str(op
.parent
.comment
.replace('\\n', '\n'))
393 text_font
= 'verdana bold 10'
395 text_col
= 'dark blue'
397 text
= str(action_to_text(op
.action
))
400 group
.ellipse
= group
.add(canvas
.CanvasEllipse
,
401 fill_color
= self
.op_colour(op
),
402 outline_color
= 'black',
406 group
.ellipse
.connect('event', self
.op_event
, op
)
408 label
= group
.add(canvas
.CanvasText
,
411 anchor
= g
.ANCHOR_NE
,
412 justification
= 'right',
413 fill_color
= text_col
,
417 #self.update_now() # GnomeCanvas bug?
418 (lx
, ly
, hx
, hy
) = label
.get_bounds()
422 group
.width
, group
.height
= 0, 0
424 class ChainBlock(ChainOp
):
425 def __init__(self
, block
, x
, y
):
426 assert isinstance(block
, Block
)
427 ChainOp
.__init
__(self
, block
, x
, y
)
432 self
.start
= create_op(block
.start
, x
+ 4, y
+ 4)
434 def expose(self
, da
):
436 w
.draw_rectangle(da
.style
.black_gc
, False, self
.x
, self
.y
, self
.width
, self
.height
)
440 w
.draw_rectangle(da
.style
.white_gc
, True, self
.x
+ 1, self
.y
+ 1, 6, self
.height
- 2)
442 w
.draw_rectangle(da
.style
.white_gc
, True, self
.x
+ 1, self
.y
+ 1, self
.width
, 6)
443 w
.draw_rectangle(da
.style
.white_gc
, True, self
.x
+ 1, self
.y
+ self
.height
- 5, 6)
446 margin
= op
.enter
* 8
447 w
.draw_rectangle(da
.style
.white_gc
, True, self
.x
+ 1, self
.y
+ 1 + margin
, self
.width
, 6)
448 w
.draw_rectangle(da
.style
.white_gc
, True, self
.x
+ 1, self
.y
+ self
.height
- 5 - margin
, 6)
450 self
.start
.expose(da
)
452 class ChainDisplay(g
.DrawingArea
):
453 "A graphical display of a chain of nodes."
454 def __init__(self
, view
, prog
= None):
455 g
.DrawingArea
.__init
__(self
)
456 self
.connect('destroy', self
.destroyed
)
459 self
.unset_flags(g
.CAN_FOCUS
)
461 self
.drag_last_pos
= None
463 self
.exec_point
= None # CanvasItem, or None
464 self
.rec_point
= None
470 self
.set_size_request(100, 100)
474 self
.view
.model
.root_program
.watchers
.append(self
)
476 self
.connect('expose-event', self
.expose
)
477 self
.add_events(g
.gdk
.BUTTON_PRESS_MASK
)
478 self
.connect('button-press-event', self
.button_press
)
482 def set_active(self
, active
):
484 self
.modify_bg(g
.STATE_NORMAL
, g
.gdk
.color_parse('white'))
486 self
.modify_bg(g
.STATE_NORMAL
, g
.gdk
.color_parse('#F7F7F7'))
488 self
.modify_bg(g
.STATE_NORMAL
, g
.gdk
.color_parse('#FFC0C0'))
490 def update_points(self
):
491 self
.put_point('rec_point')
492 self
.put_point('exec_point')
495 self
.scroll_to_show(self
.rec_point
)
497 def scroll_to_show(self
, item
):
501 (lx
, ly
, hx
, hy
) = item
.get_bounds()
502 x
, y
= item
.i2w(0, 0)
503 x
, y
= self
.w2c(x
, y
)
510 sx
, sy
= self
.get_scroll_offsets()
516 (x
, y
, w
, h
) = self
.get_allocation()
525 self
.scroll_to(sx
, sy
)
527 def put_point(self
, point
):
530 item
= getattr(self
, point
)
533 setattr(self
, point
, None)
538 opexit
= getattr(self
.view
, point
)
539 if point
== 'exec_point' and self
.view
.op_in_progress
:
540 opexit
= (self
.view
.op_in_progress
, None)
544 if op
.get_program() != self
.prog
:
547 g
= self
.op_to_group
[op
]
550 if point
== 'rec_point':
556 item
= self
.root().add(canvas
.CanvasRect
,
557 x1
= -s
, x2
= s
, y1
= -s
, y2
= s
,
559 outline_color
= 'black', width_pixels
= 1)
560 setattr(self
, point
, item
)
561 item
.connect('event', self
.line_event
, op
, exit
)
564 # TODO: cope with exit == None
565 x1
, y1
, x2
, y2
= self
.get_arrow_ends(op
, exit
)
566 x1
, y1
= g
.i2w(x1
, y1
)
567 x2
, y2
= g
.i2w(x2
, y2
)
568 item
.move((x1
+ x2
) / 2, (y1
+ y2
) / 2)
570 def destroyed(self
, widget
):
571 self
.view
.model
.root_program
.watchers
.remove(self
)
572 print "(ChainDisplay destroyed)"
574 def switch_to(self
, prog
):
575 if prog
is self
.prog
:
580 def prog_tree_changed(self
):
583 def program_changed(self
, op
):
584 if (not op
) or op
.get_program() == self
.prog
:
587 def update_all(self
):
590 self
.root_object
= create_op(self
.prog
.code
, 4, 4)
592 self
.root_object
= None
596 def expose(self
, da
, event
):
599 self
.root_object
.expose(da
)
601 #self.update_points()
605 def button_press(self
, da
, event
):
607 self
.view
.set_exec((self
.root_object
.op
.start
, 'next'))
609 def op_colour(self
, op
):
610 if op
in self
.view
.exec_stack
:
614 def update_links(self
, op
= None):
615 """Walk through all nodes in the tree-version of the op graph,
616 making all the links (which already exist as stubs) point to
623 if op
.next
.prev
[0] == op
:
624 self
.update_links(op
.next
)
626 self
.join_nodes(op
, 'next')
628 if op
.fail
.prev
[0] == op
:
629 self
.update_links(op
.fail
)
631 self
.join_nodes(op
, 'fail')
632 if isinstance(op
, Block
):
633 self
.update_links(op
.start
)
635 def create_node(self
, op
, parent
):
640 if op
.next
and op
.next
.prev
[0] == op
:
641 sx
, sy
= self
.get_arrow_start(op
, 'next')
642 gr
= group
.add(canvas
.CanvasGroup
, x
= 0, y
= 0)
643 self
.create_node(op
.next
, gr
)
644 #self.update_now() # GnomeCanvas bug?
645 (lx
, ly
, hx
, hy
) = gr
.get_bounds()
646 drop
= max(20, next_off_y
+ 10)
649 while isinstance(next
, Block
):
653 gr
.move(sx
+ x
, sy
+ y
)
655 group
.next_line
= group
.add(canvas
.CanvasLine
,
656 fill_color
= 'black',
657 points
= connect(0, 0, 1, 1),
663 group
.next_line
.connect('event', self
.line_event
, op
, 'next')
665 (x
, y
) = DEFAULT_FAIL
666 if op
.fail
and op
.fail
.prev
[0] == op
:
667 sx
, sy
= self
.get_arrow_start(op
, 'fail')
669 gr
= group
.add(canvas
.CanvasGroup
, x
= 0, y
= 0)
670 self
.create_node(op
.fail
, gr
)
671 #self.update_now() # GnomeCanvas bug?
672 (lx
, ly
, hx
, hy
) = gr
.get_bounds()
675 while isinstance(fail
, Block
):
679 gr
.move(sx
+ x
, sy
+ y
)
680 group
.fail_line
= group
.add(canvas
.CanvasLine
,
681 fill_color
= '#ff6666',
682 points
= connect(0, 0, 1, 1),
688 group
.fail_line
.lower_to_bottom()
689 group
.fail_line
.connect('event', self
.line_event
, op
, 'fail')
690 if op
.action
[0] == 'Start':
691 group
.fail_line
.hide()
693 self
.join_nodes(op
, 'next')
694 self
.join_nodes(op
, 'fail')
696 if self
.view
.breakpoints
.has_key((op
, 'next')):
697 group
.next_line
.set(line_style
= g
.gdk
.LINE_ON_OFF_DASH
)
698 if self
.view
.breakpoints
.has_key((op
, 'fail')):
699 group
.fail_line
.set(line_style
= g
.gdk
.LINE_ON_OFF_DASH
)
701 def edit_op(self
, op
):
703 if op
.action
[0] == 'do_search' or op
.action
[0] == 'do_global':
704 t
= editables
[0].get_text()
706 from Ft
.Xml
.XPath
import XPathParser
707 if t
.find('@CURRENT@') == -1:
709 XPathParser
.new().parse(t
)
711 alert('Invalid search pattern!')
717 op
.action
[i
] = e
.get_text()
719 print "Done editing!"
723 win
.vbox
.pack_start(g
.Label(op
.action
[0]), TRUE
, FALSE
, 0)
724 editables
= [] # [ Entry | None ]
726 for x
in op
.action
[1:]:
728 entry
.set_text(str(x
))
729 win
.vbox
.pack_start(entry
, TRUE
, FALSE
, 0)
730 if type(x
) == str or type(x
) == unicode:
731 editables
.append(entry
)
732 entry
.connect('activate', lambda e
: modify())
737 entry
.set_editable(FALSE
)
738 editables
.append(None)
740 win
.add_button(g
.STOCK_CANCEL
, g
.RESPONSE_CANCEL
)
741 win
.add_button(g
.STOCK_OK
, g
.RESPONSE_OK
)
743 def response(box
, resp
):
745 if resp
== g
.RESPONSE_OK
:
747 win
.connect('response', response
)
750 win
.set_response_sensitive(g
.RESPONSE_OK
, FALSE
)
754 def join_nodes(self
, op
, exit
):
756 x1
, y1
, x2
, y2
= self
.get_arrow_ends(op
, exit
)
758 prev_group
= self
.op_to_group
[op
]
759 line
= getattr(prev_group
, exit
+ '_line')
760 line
.set(points
= connect(x1
, y1
, x2
, y2
))
762 print "*** ERROR setting arc from %s:%s" % (op
, exit
)
764 def op_event(self
, item
, event
, op
):
765 if event
.type == g
.gdk
.BUTTON_PRESS
:
766 print "Prev %s = %s" % (op
, map(str, op
.prev
))
767 if event
.button
== 1:
768 if op
.parent
.start
!= op
or not op
.parent
.is_toplevel():
769 self
.drag_last_pos
= (event
.x
, event
.y
)
771 self
.drag_last_pos
= None
773 self
.show_op_menu(event
, op
)
774 elif event
.type == g
.gdk
.BUTTON_RELEASE
:
775 if event
.button
== 1:
776 self
.drag_last_pos
= None
777 self
.program_changed(None)
778 elif event
.type == g
.gdk
.ENTER_NOTIFY
:
779 item
.set(fill_color
= '#339900')
780 elif event
.type == g
.gdk
.LEAVE_NOTIFY
:
781 item
.set(fill_color
= self
.op_colour(op
))
782 elif event
.type == g
.gdk
.MOTION_NOTIFY
and self
.drag_last_pos
:
783 if not event
.state
& g
.gdk
.BUTTON1_MASK
:
785 self
.drag_last_pos
= None
786 self
.program_changed(None)
788 x
, y
= (event
.x
, event
.y
)
789 dx
, dy
= x
- self
.drag_last_pos
[0], y
- self
.drag_last_pos
[1]
790 if abs(op
.dx
+ dx
) < 4:
792 x
= dx
+ self
.drag_last_pos
[0]
793 if abs(op
.dy
+ dy
) < 4:
795 y
= dy
+ self
.drag_last_pos
[1]
798 self
.drag_last_pos
= (x
, y
)
800 self
.op_to_group
[op
].move(dx
, dy
)
803 self
.join_nodes(p
, 'next')
805 self
.join_nodes(p
, 'fail')
807 #self.create_node(self.prog.start, self.nodes)
809 elif event
.type == g
.gdk
._2BUTTON
_PRESS
:
810 if op
.action
[0] == 'Start':
811 self
.edit_comment(op
.parent
)
814 print "(edit; stop drag!)"
815 self
.drag_last_pos
= None
816 self
.program_changed(None)
819 def edit_comment(self
, block
):
820 assert isinstance(block
, Block
)
823 block
.set_comment(comment
)
824 GetArg('Comment', set, ['Comment:'],
825 message
= '\\n for a newline', init
= [block
.comment
])
827 def block_toggle_enter(self
):
828 self
.op_menu_op
.toggle_enter()
830 def block_toggle_foreach(self
):
831 self
.op_menu_op
.toggle_foreach()
833 def block_toggle_restore(self
):
834 self
.op_menu_op
.toggle_restore()
836 def block_edit_comment(self
):
837 self
.edit_comment(self
.op_menu_op
)
840 self
.edit_op(self
.op_menu_op
)
842 def op_swap_nf(self
):
843 self
.op_menu_op
.swap_nf()
845 def op_del_node(self
):
847 if op
.next
and op
.fail
:
848 rox
.alert("Can't delete a node with both exits in use")
850 self
.clipboard
= op
.del_node()
852 def show_op_menu(self
, event
, op
):
853 if op
.action
[0] == 'Start':
854 self
.op_menu_op
= op
.parent
855 block_menu
.popup(self
, event
)
858 op_menu
.popup(self
, event
)
860 def paste_chain(self
, op
, exit
):
861 print "Paste", self
.clipboard
863 new
= load(doc
.documentElement
, op
.parent
)
864 start
= new
.start
.next
865 new
.start
.unlink('next', may_delete
= 0)
866 start
.set_parent(None)
867 op
.link_to(start
, exit
)
869 def end_link_drag(self
, item
, event
, src_op
, exit
):
870 # Scan all the nodes looking for one nearby...
871 x
, y
= event
.x
, event
.y
873 def closest_node(op
):
874 "Return the closest (node, dist) in this chain to (x, y)"
875 nx
, ny
= self
.op_to_group
[op
].i2w(0, 0)
878 elif isinstance(op
, Block
):
881 best
= (op
, math
.hypot(nx
- x
, ny
- y
))
882 if op
.next
and op
.next
.prev
[0] == op
:
883 next
= closest_node(op
.next
)
884 if next
and (best
is None or next
[1] < best
[1]):
886 if op
.fail
and op
.fail
.prev
[0] == op
:
887 fail
= closest_node(op
.fail
)
888 if fail
and (best
is None or fail
[1] < best
[1]):
890 if isinstance(op
, Block
):
891 sub
= closest_node(op
.start
)
892 if sub
and (best
is None or sub
[1] < best
[1]):
896 result
= closest_node(self
.prog
.code
)
902 # Too far... put the line back to the disconnected state...
903 self
.join_nodes(src_op
, exit
)
906 while node
.action
[0] == 'Start':
908 src_op
.link_to(node
, exit
)
912 def line_paste_chain(self
):
913 op
, exit
= self
.line_menu_line
914 self
.paste_chain(op
, exit
)
916 def line_add_block(self
):
917 op
, exit
= self
.line_menu_line
919 box
.add_button(g
.STOCK_CANCEL
, g
.RESPONSE_CANCEL
)
920 box
.add_button(g
.STOCK_ADD
, g
.RESPONSE_OK
)
922 foreach
= g
.CheckButton('Foreach block')
923 box
.vbox
.pack_start(foreach
)
924 enter
= g
.CheckButton('Enter-leave block')
925 box
.vbox
.pack_start(enter
)
930 if resp
== g
.RESPONSE_OK
:
932 if foreach
.get_active():
934 if enter
.get_active():
937 if self
.view
.rec_point
== (op
, exit
):
938 self
.view
.single_step
= 1
939 self
.view
.stop_recording()
941 self
.view
.do_one_step()
942 except View
.InProgress
:
945 def line_toggle_breakpoint(self
):
946 op
, exit
= self
.line_menu_line
947 bp
= self
.view
.breakpoints
948 if bp
.has_key((op
, exit
)):
954 def line_yank_chain(self
):
955 op
, exit
= self
.line_menu_line
956 next
= getattr(op
, exit
)
958 rox
.alert('Nothing to yank!')
960 self
.clipboard
= next
.to_doc()
963 def line_del_chain(self
):
964 op
, exit
= self
.line_menu_line
965 next
= getattr(op
, exit
)
967 rox
.alert('Nothing to delete!')
969 self
.clipboard
= next
.to_doc()
972 def line_event(self
, item
, event
, op
, exit
):
973 # Item may be rec_point or exec_point...
974 item
= getattr(self
.op_to_group
[op
], exit
+ '_line')
976 if event
.type == g
.gdk
.BUTTON_PRESS
:
977 if event
.button
== 1:
978 if not getattr(op
, exit
):
979 self
.drag_last_pos
= (event
.x
, event
.y
)
980 #item.grab(BUTTON_RELEASE | MOTION_NOTIFY, no_cursor, event.time)
981 elif event
.button
== 2:
982 self
.paste_chain(op
, exit
)
983 elif event
.button
== 3:
984 self
.line_menu_line
= (op
, exit
)
985 line_menu
.popup(self
, event
)
986 elif event
.type == g
.gdk
.BUTTON_RELEASE
:
987 if event
.button
== 1:
988 print "Clicked exit %s of %s" % (exit
, op
)
989 #item.ungrab(event.time)
990 self
.view
.set_exec((op
, exit
))
991 self
.drag_last_pos
= None
992 if not getattr(op
, exit
):
993 self
.end_link_drag(item
, event
, op
, exit
)
994 elif event
.type == g
.gdk
.MOTION_NOTIFY
and self
.drag_last_pos
:
995 if not event
.state
& g
.gdk
.BUTTON1_MASK
:
997 self
.drag_last_pos
= None
998 if not getattr(op
, exit
):
999 self
.end_link_drag(item
, event
, op
, exit
)
1001 x
, y
= (event
.x
, event
.y
)
1002 dx
, dy
= x
- self
.drag_last_pos
[0], y
- self
.drag_last_pos
[1]
1004 if abs(dx
) > 4 or abs(dy
) > 4:
1005 sx
, sy
= self
.get_arrow_start(op
, exit
)
1006 x
, y
= item
.w2i(event
.x
, event
.y
)
1007 gr
= self
.op_to_group
[op
]
1012 item
.set(points
= connect(sx
, sy
, x
, y
))
1013 elif event
.type == g
.gdk
.ENTER_NOTIFY
:
1014 item
.set(fill_color
= '#339900')
1015 elif event
.type == g
.gdk
.LEAVE_NOTIFY
:
1017 item
.set(fill_color
= 'black')
1019 item
.set(fill_color
= '#ff6666')
1022 def get_arrow_start(self
, op
, exit
):
1023 gr
= self
.op_to_group
[op
]
1024 return ((exit
== 'fail' and gr
.width
) or 0, gr
.height
)
1026 def get_arrow_ends(self
, op
, exit
):
1027 """Return coords of arrow, relative to op's group."""
1028 op2
= getattr(op
, exit
)
1030 prev_group
= self
.op_to_group
[op
]
1032 x1
, y1
= self
.get_arrow_start(op
, exit
)
1036 group
= self
.op_to_group
[op2
]
1041 x2
, y2
= group
.i2w(0, 0)
1042 x2
, y2
= prev_group
.w2i(x2
, y2
)
1043 elif exit
== 'next':
1044 x2
, y2
= DEFAULT_NEXT
1048 x2
, y2
= DEFAULT_FAIL
1051 return (x1
, y1
, x2
, y2
)
1053 def set_bounds(self
):
1054 #self.update_now() # GnomeCanvas bug?
1055 min_x
, min_y
, max_x
, max_y
= self
.root().get_bounds()
1060 self
.set_scroll_region(min_x
, min_y
, max_x
, max_y
)
1061 self
.root().move(0, 0) # Magic!
1062 #self.set_usize(max_x - min_x, -1)
1064 def canvas_to_world(self
, (x
, y
)):
1065 "Canvas routine seems to be broken..."
1066 mx
, my
, maxx
, maxy
= self
.get_scroll_region()
1067 sx
= self
.get_hadjustment().value
1068 sy
= self
.get_hadjustment().value
1069 return (x
+ mx
+ sx
, y
+ my
+ sy
)
1071 class ChainWindow(rox
.Window
):
1072 def __init__(self
, view
, prog
):
1073 rox
.Window
.__init
__(self
)
1074 swin
= g
.ScrolledWindow()
1076 disp
= ChainDisplay(view
, prog
)
1081 self
.set_default_size(-1, 200)
1082 self
.set_title(prog
.name
)
1084 def update_points(self
):
1085 self
.disp
.update_points()