1 #from __future__ import nested_scopes
4 from rox
import g
, TRUE
, FALSE
, alert
5 from gnome2
import canvas
9 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
53 no_cursor
= g
.gdk
.Cursor(g
.gdk
.TCROSS
)
58 return text
[:16] + '...'
60 def connect(x1
, y1
, x2
, y2
):
61 """Chop 5 pixels off both ends of this line"""
65 l
= math
.hypot(dx
, dy
)
69 return (x1
+ dx
, y1
+ dy
, x2
- dx
, y2
- dy
)
71 DEFAULT_NEXT
= (0, 25)
72 DEFAULT_FAIL
= (20, 20)
74 expand_history
= {} # Prog name -> expanded flag
76 def action_to_text(action
):
80 text
= string
.capitalize(string
.replace(text
, '_', ' '))
83 if action
[0] == 'do_search':
85 pat
= string
.replace(pat
, 'following-sibling::', '>>')
86 pat
= string
.replace(pat
, 'preceding-sibling::', '<<')
87 pat
= string
.replace(pat
, 'child::', '')
88 pat
= string
.replace(pat
, '[1]', '')
89 pat
= string
.replace(pat
, 'text()[ext:match', '[')
92 i
= string
.rfind(pat
[:20], '/')
95 details
= details
+ pat
[:i
+ 1] + '\n'
97 details
= details
+ pat
98 elif action
[0] == 'attribute':
99 details
= trunc(str(action
[2]))
100 elif action
[0] == 'set_attrib':
101 details
= trunc(str(action
[1]))
102 elif action
[0] == 'add_attrib':
103 details
= trunc(str(action
[2]))
104 elif action
[0] == 'add_node':
105 details
= trunc(action
[2])
106 elif action
[0] == 'subst':
107 details
= action
[1] + ' -> ' + action
[2]
108 elif action
[0] == 'play' or action
[0] == 'map':
109 if len(action
[1]) > 20:
110 details
= '...' + str(action
[1][-19:])
112 details
= str(action
[1])
115 details
= `action
[1:]`
117 details
= str(action
[1])
118 if len(details
) > 20:
119 details
= `details`
[:19] + '...'
120 text
= text
+ '\n' + details
124 def __init__(self
, view
):
125 g
.VBox
.__init
__(self
)
128 self
.sub_windows
= []
130 self
.stack_frames
= g
.Label('')
131 self
.pack_start(self
.stack_frames
, FALSE
, TRUE
, 0)
132 self
.stack_frames
.show()
133 self
.update_stack(None)
136 self
.pack_start(pane
, expand
= 1, fill
= 1)
138 swin
= g
.ScrolledWindow()
139 swin
.set_policy(g
.POLICY_NEVER
, g
.POLICY_AUTOMATIC
)
142 self
.prog_model
= g
.TreeStore(str, str)
143 tree
= g
.TreeView(self
.prog_model
)
144 tree
.connect('button-press-event', self
.button_press
)
145 tree
.unset_flags(g
.CAN_FOCUS
)
146 tree
.set_headers_visible(FALSE
)
149 cell
= g
.CellRendererText()
150 column
= g
.TreeViewColumn('Program', cell
, text
= 0)
151 tree
.append_column(column
)
153 sel
= tree
.get_selection()
154 def change_prog(tree
, sel
= sel
, self
= self
): # Huh?
155 selected
= sel
.get_selected()
158 model
, iter = selected
160 path
= model
.get_value(iter, 1)
161 self
.chains
.switch_to(self
.view
.name_to_prog(path
))
163 self
.chains
.switch_to(None)
165 sel
.connect('changed', change_prog
)
167 self
.chains
= ChainDisplay(view
)
168 self
.prog_tree_changed()
172 v
.set_shadow_type(g
.SHADOW_NONE
)
175 swin
= g
.ScrolledWindow()
176 swin
.set_policy(g
.POLICY_AUTOMATIC
, g
.POLICY_AUTOMATIC
)
178 swin
.add(self
.chains
)
181 pane
.set_position(200)
183 sel
.set_mode(g
.SELECTION_BROWSE
)
184 root_iter
= self
.prog_model
.get_iter_first()
185 sel
.select_iter(root_iter
)
186 tree
.expand_row(self
.prog_model
.get_path(root_iter
), FALSE
)
188 self
.view
.lists
.append(self
)
189 self
.view
.model
.root_program
.watchers
.append(self
)
191 def set_innermost_failure(self
, op
):
192 self
.show_prog(op
.get_program())
195 self
.view
.lists
.remove(self
)
196 self
.view
.model
.root_program
.watchers
.remove(self
)
198 def update_points(self
):
199 self
.chains
.update_points()
200 for x
in self
.sub_windows
:
203 def program_changed(self
, op
):
206 def prog_tree_changed(self
):
207 self
.prog_to_path
= {}
208 self
.prog_model
.clear()
209 self
.build_tree(self
.view
.model
.root_program
)
211 # Check for now deleted programs still being displayed
212 root
= self
.view
.model
.root_program
213 if self
.chains
and self
.chains
.prog
and not self
.chains
.prog
.parent
:
214 self
.chains
.switch_to(None)
215 for x
in self
.sub_windows
:
216 if x
.disp
.prog
is not root
and not x
.disp
.prog
.parent
:
219 def build_tree(self
, prog
, iter = None):
220 child_iter
= self
.prog_model
.append(iter)
221 self
.prog_model
.set(child_iter
, 0, prog
.name
,
224 self
.prog_to_path
[prog
] = self
.prog_model
.get_path(child_iter
)
225 for p
in prog
.subprograms
.values():
226 self
.build_tree(p
, child_iter
)
228 def run_return(self
, exit
):
230 print "run_return: failure!"
231 self
.view
.jump_to_innermost_failure()
232 def cb(choice
, self
= self
):
234 self
.view
.record_at_point()
235 box
= MultipleChoice("Program failed - record a failure case?",
236 [('Record', self
.view
.record_at_point
), 'Cancel'])
237 box
.set_title('Dome')
239 print "List: execution done!"
241 def button_press(self
, tree
, event
):
242 if event
.button
== 2 or event
.button
== 3:
243 ret
= tree
.get_path_at_pos(event
.x
, event
.y
)
245 return 1 # Click on blank area
246 path
, col
, cx
, cy
= ret
247 print "Event on", path
248 iter = self
.prog_model
.get_iter(path
)
249 path
= self
.prog_model
.get_value(iter, 1)
250 if event
.button
== 3:
251 prog
= self
.view
.name_to_prog(path
)
252 self
.show_menu(event
, prog
)
254 self
.view
.run_new(self
.run_return
)
255 if event
.state
& g
.gdk
.SHIFT_MASK
:
256 self
.view
.may_record(['map', path
])
258 self
.view
.may_record(['play', path
])
261 def menu_delete(self
):
262 prog
= self
.prog_menu_prog
264 rox
.alert("Can't delete the root program!")
266 prog
.parent
.remove_sub(prog
)
268 def menu_rename(self
):
269 prog
= self
.prog_menu_prog
270 def rename(name
, prog
= prog
):
272 GetArg('Rename program', rename
, ['Program name:'])
274 def menu_new_prog(self
):
275 prog
= self
.prog_menu_prog
279 GetArg('New program', create
, ['Program name:'])
281 def menu_new_view(self
):
282 prog
= self
.prog_menu_prog
283 cw
= ChainWindow(self
.view
, prog
)
285 self
.sub_windows
.append(cw
)
287 self
.sub_windows
.remove(cw
)
289 cw
.connect('destroy', lost_cw
)
292 prog
= self
.prog_menu_prog
293 self
.view
.run_new(self
.run_return
)
294 self
.view
.may_record(['map', prog
.get_path()])
297 prog
= self
.prog_menu_prog
298 self
.view
.run_new(self
.run_return
)
299 self
.view
.may_record(['play', prog
.get_path()])
301 def show_menu(self
, event
, prog
):
302 self
.prog_menu_prog
= prog
303 prog_menu
.popup(self
, event
)
305 def update_stack(self
, op
):
306 "The stack has changed - redraw 'op'"
307 if op
and op
.get_program() == self
.chains
.prog
:
308 self
.chains
.update_all()
309 l
= len(self
.view
.exec_stack
) + len(self
.view
.foreach_stack
)
315 text
= '%d frames' % l
316 if self
.view
.chroots
:
317 text
+= ' (%d enters)' % len(self
.view
.chroots
)
318 self
.stack_frames
.set_text(text
)
320 def show_prog(self
, prog
):
321 path
= self
.prog_to_path
[prog
]
322 iter = self
.prog_model
.get_iter(path
)
323 self
.tree
.get_selection().select_iter(iter)
325 class ChainDisplay(canvas
.Canvas
):
326 "A graphical display of a chain of nodes."
327 def __init__(self
, view
, prog
= None):
328 canvas
.Canvas
.__init
__(self
)
329 self
.connect('destroy', self
.destroyed
)
331 self
.unset_flags(g
.CAN_FOCUS
)
333 self
.drag_last_pos
= None
335 self
.exec_point
= None # CanvasItem, or None
336 self
.rec_point
= None
338 s
= self
.get_style().copy()
340 s
.bg
[g
.STATE_NORMAL
] = g
.gdk
.color_parse('white')
342 s
.bg
[g
.STATE_NORMAL
] = g
.gdk
.color_parse('light green')
347 self
.set_size_request(100, 100)
351 self
.view
.model
.root_program
.watchers
.append(self
)
355 def update_points(self
):
356 self
.put_point('rec_point')
357 self
.put_point('exec_point')
360 self
.scroll_to_show(self
.rec_point
)
362 def scroll_to_show(self
, item
):
363 self
.update_now() # Canvas bug
365 (lx
, ly
, hx
, hy
) = item
.get_bounds()
366 x
, y
= item
.i2w(0, 0)
367 x
, y
= self
.w2c(x
, y
)
374 sx
, sy
= self
.get_scroll_offsets()
380 (x
, y
, w
, h
) = self
.get_allocation()
389 self
.scroll_to(sx
, sy
)
391 def put_point(self
, point
):
392 item
= getattr(self
, point
)
395 setattr(self
, point
, None)
400 opexit
= getattr(self
.view
, point
)
401 if point
== 'exec_point' and self
.view
.op_in_progress
:
402 opexit
= (self
.view
.op_in_progress
, None)
406 if op
.get_program() != self
.prog
:
409 g
= self
.op_to_group
[op
]
412 if point
== 'rec_point':
418 item
= self
.root().add(canvas
.CanvasRect
,
419 x1
= -s
, x2
= s
, y1
= -s
, y2
= s
,
421 outline_color
= 'black', width_pixels
= 1)
422 setattr(self
, point
, item
)
423 item
.connect('event', self
.line_event
, op
, exit
)
426 # TODO: cope with exit == None
427 x1
, y1
, x2
, y2
= self
.get_arrow_ends(op
, exit
)
428 x1
, y1
= g
.i2w(x1
, y1
)
429 x2
, y2
= g
.i2w(x2
, y2
)
430 item
.move((x1
+ x2
) / 2, (y1
+ y2
) / 2)
432 def destroyed(self
, widget
):
436 self
.view
.model
.root_program
.watchers
.remove(self
)
437 print "(ChainDisplay destroyed)"
439 def switch_to(self
, prog
):
440 if prog
is self
.prog
:
445 def prog_tree_changed(self
):
448 def program_changed(self
, op
):
449 if (not op
) or op
.get_program() == self
.prog
:
452 def update_all(self
):
456 self
.op_to_group
= {}
457 self
.nodes
= self
.root().add(canvas
.CanvasGroup
, x
= 0, y
= 0)
459 self
.create_node(self
.prog
.code
, self
.nodes
)
467 def op_colour(self
, op
):
468 if op
in self
.view
.exec_stack
:
472 def update_links(self
, op
= None):
473 """Walk through all nodes in the tree-version of the op graph,
474 making all the links (which already exist as stubs) point to
481 if op
.next
.prev
[0] == op
:
482 self
.update_links(op
.next
)
484 self
.join_nodes(op
, 'next')
486 if op
.fail
.prev
[0] == op
:
487 self
.update_links(op
.fail
)
489 self
.join_nodes(op
, 'fail')
490 if isinstance(op
, Block
):
491 self
.update_links(op
.start
)
493 def create_node(self
, op
, group
):
494 self
.op_to_group
[op
] = group
496 if isinstance(op
, Block
):
497 gr
= group
.add(canvas
.CanvasGroup
, x
= 0, y
= 0)
498 self
.create_node(op
.start
, gr
)
499 #self.update_now() # GnomeCanvas bug?
500 (lx
, ly
, hx
, hy
) = gr
.get_bounds()
504 border
= gr
.add(canvas
.CanvasRect
, x1
= minx
, x2
= hx
+ 4, y1
= ly
+ 4, y2
= hy
+ 4,
505 outline_color
= 'black', width_pixels
= 1)
506 border
.lower_to_bottom()
508 gr
.add(canvas
.CanvasRect
, x1
= minx
, x2
= minx
+ 8, y1
= ly
+ 4, y2
= hy
+ 4,
509 fill_color
= 'blue').lower_to_bottom()
512 gr
.add(canvas
.CanvasRect
, x1
= minx
, x2
= hx
+ 4, y1
= ly
+ 5, y2
= ly
+ 13,
513 fill_color
= colour
).lower_to_bottom()
514 gr
.add(canvas
.CanvasRect
, x1
= minx
, x2
= hx
+ 4, y1
= hy
- 3, y2
= hy
+ 3,
515 fill_color
= colour
).lower_to_bottom()
518 margin
= op
.enter
* 8
519 gr
.add(canvas
.CanvasRect
, x1
= minx
, x2
= hx
+ 4, y1
= ly
+ 5 + margin
, y2
= ly
+ 13 + margin
,
520 fill_color
= colour
).lower_to_bottom()
521 gr
.add(canvas
.CanvasRect
, x1
= minx
, x2
= hx
+ 4, y1
= hy
- 3 - margin
, y2
= hy
+ 3 - margin
,
522 fill_color
= colour
).lower_to_bottom()
524 group
.width
, group
.height
= hx
, hy
528 if op
.action
[0] == 'Start':
529 text
= str(op
.parent
.comment
.replace('\\n', '\n'))
531 #text_font = '-misc-fixed-bold-r-normal-*-*-120-*-*-c-*-iso8859-1'
532 text_col
= 'dark blue'
534 text
= str(action_to_text(op
.action
))
536 #text_font = '-misc-fixed-medium-r-normal-*-*-120-*-*-c-*-iso8859-1'
539 group
.ellipse
= group
.add(canvas
.CanvasEllipse
,
540 fill_color
= self
.op_colour(op
),
541 outline_color
= 'black',
545 group
.ellipse
.connect('event', self
.op_event
, op
)
547 label
= group
.add(canvas
.CanvasText
,
550 anchor
= g
.ANCHOR_NE
,
551 justification
= 'right',
552 fill_color
= text_col
,
555 #self.update_now() # GnomeCanvas bug?
556 (lx
, ly
, hx
, hy
) = label
.get_bounds()
560 group
.width
, group
.height
= 0, 0
562 if op
.next
and op
.next
.prev
[0] == op
:
563 sx
, sy
= self
.get_arrow_start(op
, 'next')
564 gr
= group
.add(canvas
.CanvasGroup
, x
= 0, y
= 0)
565 self
.create_node(op
.next
, gr
)
566 #self.update_now() # GnomeCanvas bug?
567 (lx
, ly
, hx
, hy
) = gr
.get_bounds()
568 drop
= max(20, next_off_y
+ 10)
571 while isinstance(next
, Block
):
575 gr
.move(sx
+ x
, sy
+ y
)
577 group
.next_line
= group
.add(canvas
.CanvasLine
,
578 fill_color
= 'black',
579 points
= connect(0, 0, 1, 1),
585 group
.next_line
.connect('event', self
.line_event
, op
, 'next')
587 (x
, y
) = DEFAULT_FAIL
588 if op
.fail
and op
.fail
.prev
[0] == op
:
589 sx
, sy
= self
.get_arrow_start(op
, 'fail')
591 gr
= group
.add(canvas
.CanvasGroup
, x
= 0, y
= 0)
592 self
.create_node(op
.fail
, gr
)
593 #self.update_now() # GnomeCanvas bug?
594 (lx
, ly
, hx
, hy
) = gr
.get_bounds()
597 while isinstance(fail
, Block
):
601 gr
.move(sx
+ x
, sy
+ y
)
602 group
.fail_line
= group
.add(canvas
.CanvasLine
,
603 fill_color
= '#ff6666',
604 points
= connect(0, 0, 1, 1),
610 group
.fail_line
.lower_to_bottom()
611 group
.fail_line
.connect('event', self
.line_event
, op
, 'fail')
612 if op
.action
[0] == 'Start':
613 group
.fail_line
.hide()
615 self
.join_nodes(op
, 'next')
616 self
.join_nodes(op
, 'fail')
618 if self
.view
.breakpoints
.has_key((op
, 'next')):
619 group
.next_line
.set(line_style
= g
.gdk
.LINE_ON_OFF_DASH
)
620 if self
.view
.breakpoints
.has_key((op
, 'fail')):
621 group
.fail_line
.set(line_style
= g
.gdk
.LINE_ON_OFF_DASH
)
623 def edit_op(self
, op
):
625 if op
.action
[0] == 'do_search' or op
.action
[0] == 'do_global':
626 t
= editables
[0].get_text()
628 from Ft
.Xml
.XPath
import XPathParser
629 if t
.find('@CURRENT@') == -1:
631 XPathParser
.new().parse(t
)
633 alert('Invalid search pattern!')
639 op
.action
[i
] = e
.get_text()
641 print "Done editing!"
645 win
.vbox
.pack_start(g
.Label(op
.action
[0]), TRUE
, FALSE
, 0)
646 editables
= [] # [ Entry | None ]
648 for x
in op
.action
[1:]:
650 entry
.set_text(str(x
))
651 win
.vbox
.pack_start(entry
, TRUE
, FALSE
, 0)
652 if type(x
) == str or type(x
) == unicode:
653 editables
.append(entry
)
654 entry
.connect('activate', lambda e
: modify())
659 entry
.set_editable(FALSE
)
660 editables
.append(None)
662 win
.add_button(g
.STOCK_CANCEL
, g
.RESPONSE_CANCEL
)
663 win
.add_button(g
.STOCK_OK
, g
.RESPONSE_OK
)
665 def response(box
, resp
):
667 if resp
== g
.RESPONSE_OK
:
669 win
.connect('response', response
)
672 win
.set_response_sensitive(g
.RESPONSE_OK
, FALSE
)
676 def join_nodes(self
, op
, exit
):
678 x1
, y1
, x2
, y2
= self
.get_arrow_ends(op
, exit
)
680 prev_group
= self
.op_to_group
[op
]
681 line
= getattr(prev_group
, exit
+ '_line')
682 line
.set(points
= connect(x1
, y1
, x2
, y2
))
684 print "*** ERROR setting arc from %s:%s" % (op
, exit
)
686 def op_event(self
, item
, event
, op
):
687 if event
.type == g
.gdk
.BUTTON_PRESS
:
688 print "Prev %s = %s" % (op
, map(str, op
.prev
))
689 if event
.button
== 1:
690 if op
.parent
.start
!= op
or not op
.parent
.is_toplevel():
691 self
.drag_last_pos
= (event
.x
, event
.y
)
693 self
.drag_last_pos
= None
695 self
.show_op_menu(event
, op
)
696 elif event
.type == g
.gdk
.BUTTON_RELEASE
:
697 if event
.button
== 1:
698 self
.drag_last_pos
= None
699 self
.program_changed(None)
700 elif event
.type == g
.gdk
.ENTER_NOTIFY
:
701 item
.set(fill_color
= 'white')
702 elif event
.type == g
.gdk
.LEAVE_NOTIFY
:
703 item
.set(fill_color
= self
.op_colour(op
))
704 elif event
.type == g
.gdk
.MOTION_NOTIFY
and self
.drag_last_pos
:
705 if not event
.state
& g
.gdk
.BUTTON1_MASK
:
707 self
.drag_last_pos
= None
708 self
.program_changed(None)
710 x
, y
= (event
.x
, event
.y
)
711 dx
, dy
= x
- self
.drag_last_pos
[0], y
- self
.drag_last_pos
[1]
712 if abs(op
.dx
+ dx
) < 4:
714 x
= dx
+ self
.drag_last_pos
[0]
715 if abs(op
.dy
+ dy
) < 4:
717 y
= dy
+ self
.drag_last_pos
[1]
720 self
.drag_last_pos
= (x
, y
)
722 self
.op_to_group
[op
].move(dx
, dy
)
725 self
.join_nodes(p
, 'next')
727 self
.join_nodes(p
, 'fail')
729 #self.create_node(self.prog.start, self.nodes)
731 elif event
.type == g
.gdk
._2BUTTON
_PRESS
:
732 if op
.action
[0] == 'Start':
733 self
.edit_comment(op
.parent
)
736 print "(edit; stop drag!)"
737 self
.drag_last_pos
= None
738 self
.program_changed(None)
741 def edit_comment(self
, block
):
742 assert isinstance(block
, Block
)
745 block
.set_comment(comment
)
746 GetArg('Comment', set, ['Comment:'],
747 message
= '\\n for a newline', init
= [block
.comment
])
749 def block_toggle_enter(self
):
750 self
.op_menu_op
.toggle_enter()
752 def block_toggle_foreach(self
):
753 self
.op_menu_op
.toggle_foreach()
755 def block_toggle_restore(self
):
756 self
.op_menu_op
.toggle_restore()
758 def block_edit_comment(self
):
759 self
.edit_comment(self
.op_menu_op
)
762 self
.edit_op(self
.op_menu_op
)
764 def op_swap_nf(self
):
765 self
.op_menu_op
.swap_nf()
767 def op_del_node(self
):
769 if op
.next
and op
.fail
:
770 rox
.alert("Can't delete a node with both exits in use")
772 self
.clipboard
= op
.del_node()
774 def show_op_menu(self
, event
, op
):
775 if op
.action
[0] == 'Start':
776 self
.op_menu_op
= op
.parent
777 block_menu
.popup(self
, event
)
780 op_menu
.popup(self
, event
)
782 def paste_chain(self
, op
, exit
):
783 print "Paste", self
.clipboard
785 new
= load(doc
.documentElement
, op
.parent
)
786 start
= new
.start
.next
787 new
.start
.unlink('next', may_delete
= 0)
788 start
.set_parent(None)
789 op
.link_to(start
, exit
)
791 def end_link_drag(self
, item
, event
, src_op
, exit
):
792 # Scan all the nodes looking for one nearby...
793 x
, y
= event
.x
, event
.y
795 def closest_node(op
):
796 "Return the closest (node, dist) in this chain to (x, y)"
797 nx
, ny
= self
.op_to_group
[op
].i2w(0, 0)
800 elif isinstance(op
, Block
):
803 best
= (op
, math
.hypot(nx
- x
, ny
- y
))
804 if op
.next
and op
.next
.prev
[0] == op
:
805 next
= closest_node(op
.next
)
806 if next
and (best
is None or next
[1] < best
[1]):
808 if op
.fail
and op
.fail
.prev
[0] == op
:
809 fail
= closest_node(op
.fail
)
810 if fail
and (best
is None or fail
[1] < best
[1]):
812 if isinstance(op
, Block
):
813 sub
= closest_node(op
.start
)
814 if sub
and (best
is None or sub
[1] < best
[1]):
818 result
= closest_node(self
.prog
.code
)
824 # Too far... put the line back to the disconnected state...
825 self
.join_nodes(src_op
, exit
)
828 while node
.action
[0] == 'Start':
830 src_op
.link_to(node
, exit
)
834 def line_paste_chain(self
):
835 op
, exit
= self
.line_menu_line
836 self
.paste_chain(op
, exit
)
838 def line_add_block(self
):
839 op
, exit
= self
.line_menu_line
840 op
.link_to(Block(op
.parent
), exit
)
842 def line_toggle_breakpoint(self
):
843 op
, exit
= self
.line_menu_line
844 bp
= self
.view
.breakpoints
845 if bp
.has_key((op
, exit
)):
851 def line_yank_chain(self
):
852 op
, exit
= self
.line_menu_line
853 next
= getattr(op
, exit
)
855 rox
.alert('Nothing to yank!')
857 self
.clipboard
= op
.to_doc()
860 def line_del_chain(self
):
861 op
, exit
= self
.line_menu_line
862 next
= getattr(op
, exit
)
864 rox
.alert('Nothing to delete!')
866 self
.clipboard
= next
.to_doc()
869 def line_event(self
, item
, event
, op
, exit
):
870 # Item may be rec_point or exec_point...
871 item
= getattr(self
.op_to_group
[op
], exit
+ '_line')
873 if event
.type == g
.gdk
.BUTTON_PRESS
:
874 if event
.button
== 1:
875 if not getattr(op
, exit
):
876 self
.drag_last_pos
= (event
.x
, event
.y
)
877 #item.grab(BUTTON_RELEASE | MOTION_NOTIFY, no_cursor, event.time)
878 elif event
.button
== 2:
879 self
.paste_chain(op
, exit
)
880 elif event
.button
== 3:
881 self
.line_menu_line
= (op
, exit
)
882 line_menu
.popup(self
, event
)
883 elif event
.type == g
.gdk
.BUTTON_RELEASE
:
884 if event
.button
== 1:
885 print "Clicked exit %s of %s" % (exit
, op
)
886 #item.ungrab(event.time)
887 self
.view
.set_exec((op
, exit
))
888 self
.drag_last_pos
= None
889 if not getattr(op
, exit
):
890 self
.end_link_drag(item
, event
, op
, exit
)
891 elif event
.type == g
.gdk
.MOTION_NOTIFY
and self
.drag_last_pos
:
892 if not event
.state
& g
.gdk
.BUTTON1_MASK
:
894 self
.drag_last_pos
= None
895 if not getattr(op
, exit
):
896 self
.end_link_drag(item
, event
, op
, exit
)
898 x
, y
= (event
.x
, event
.y
)
899 dx
, dy
= x
- self
.drag_last_pos
[0], y
- self
.drag_last_pos
[1]
901 if abs(dx
) > 4 or abs(dy
) > 4:
902 sx
, sy
= self
.get_arrow_start(op
, exit
)
903 x
, y
= item
.w2i(event
.x
, event
.y
)
904 gr
= self
.op_to_group
[op
]
909 item
.set(points
= connect(sx
, sy
, x
, y
))
910 elif event
.type == g
.gdk
.ENTER_NOTIFY
:
911 item
.set(fill_color
= 'white')
912 elif event
.type == g
.gdk
.LEAVE_NOTIFY
:
914 item
.set(fill_color
= 'black')
916 item
.set(fill_color
= '#ff6666')
919 def get_arrow_start(self
, op
, exit
):
920 gr
= self
.op_to_group
[op
]
921 return ((exit
== 'fail' and gr
.width
) or 0, gr
.height
)
923 def get_arrow_ends(self
, op
, exit
):
924 """Return coords of arrow, relative to op's group."""
925 op2
= getattr(op
, exit
)
927 prev_group
= self
.op_to_group
[op
]
929 x1
, y1
= self
.get_arrow_start(op
, exit
)
933 group
= self
.op_to_group
[op2
]
938 x2
, y2
= group
.i2w(0, 0)
939 x2
, y2
= prev_group
.w2i(x2
, y2
)
941 x2
, y2
= DEFAULT_NEXT
945 x2
, y2
= DEFAULT_FAIL
948 return (x1
, y1
, x2
, y2
)
950 def set_bounds(self
):
951 #self.update_now() # GnomeCanvas bug?
952 min_x
, min_y
, max_x
, max_y
= self
.root().get_bounds()
957 self
.set_scroll_region(min_x
, min_y
, max_x
, max_y
)
958 self
.root().move(0, 0) # Magic!
959 #self.set_usize(max_x - min_x, -1)
961 def canvas_to_world(self
, (x
, y
)):
962 "Canvas routine seems to be broken..."
963 mx
, my
, maxx
, maxy
= self
.get_scroll_region()
964 sx
= self
.get_hadjustment().value
965 sy
= self
.get_hadjustment().value
966 return (x
+ mx
+ sx
, y
+ my
+ sy
)
968 class ChainWindow(rox
.Window
):
969 def __init__(self
, view
, prog
):
970 rox
.Window
.__init
__(self
)
971 swin
= g
.ScrolledWindow()
973 disp
= ChainDisplay(view
, prog
)
978 self
.set_default_size(-1, 200)
979 self
.set_title(prog
.name
)
981 def update_points(self
):
982 self
.disp
.update_points()