comparison pyink/FSM_window.py @ 1490:3f107ceee9c1

User can add transitions for states by popup menu
author Thinker K.F. Li <thinker@codemud.net>
date Sat, 30 Apr 2011 21:42:28 +0800
parents 1e607ce4bf7d
children 06c101bba830
comparison
equal deleted inserted replaced
1489:1e607ce4bf7d 1490:3f107ceee9c1
1 import gtk 1 import gtk
2 import os 2 import os
3 import math
3 import data_monitor 4 import data_monitor
5 import pybInkscape
4 6
5 class FSM_window_base(object): 7 class FSM_window_base(object):
6 _add_state_button = None 8 _add_state_button = None
7 _move_state_button = None 9 _move_state_button = None
8 10
156 _doc = None 158 _doc = None
157 _domview = None 159 _domview = None
158 _fsm_layer = None 160 _fsm_layer = None
159 _control_layer = None 161 _control_layer = None
160 _state = None 162 _state = None
163 _states = None
161 trn_cond = None 164 trn_cond = None
162 trn_g = None 165 trn_g = None
163 _arrow_node = None 166 _arrow_node = None
164 _path_node = None 167 _path_node = None
165 168
166 def __init__(self, trn_cond): 169 def __init__(self, trn_cond):
167 self.trn_cond = trn_cond 170 self.trn_cond = trn_cond
168 pass 171 pass
169 172
170 def init(self, doc, domview, state, fsm_layer, control_layer): 173 def init(self, doc, domview, state, states, fsm_layer, control_layer):
171 self._doc = doc 174 self._doc = doc
172 self._domview = domview 175 self._domview = domview
173 self._state = state 176 self._state = state
177 self._states = states
174 self._fsm_layer = fsm_layer 178 self._fsm_layer = fsm_layer
175 self._control_layer = control_layer 179 self._control_layer = control_layer
176 pass 180 pass
177 181
178 @staticmethod 182 @staticmethod
179 def _update_graph(path, arrow_node, path_node): 183 def _update_graph(path, arrow_node, path_node):
180 import math
181
182 path_txt = 'M %f,%f C %f,%f %f,%f %f,%f' % tuple(path) 184 path_txt = 'M %f,%f C %f,%f %f,%f %f,%f' % tuple(path)
183 path_node.setAttribute('d', path_txt) 185 path_node.setAttribute('d', path_txt)
184 path_node.setAttribute('style', 'stroke: #000000; stroke-width: 1; ' 186 path_node.setAttribute('style', 'stroke: #000000; stroke-width: 1; '
185 'fill: none') 187 'fill: none')
186 188
214 216
215 parent.appendChild(trn_g) 217 parent.appendChild(trn_g)
216 218
217 return trn_g, path_node, arrow_node 219 return trn_g, path_node, arrow_node
218 220
221 def _gen_path(self):
222 states = self._states
223 target_name = self.target
224 target_state = states[target_name]
225 src_state = self._state
226
227 src_x, src_y = src_state.xy
228 src_r = src_state.r
229 target_x, target_y = target_state.xy
230 target_r = target_state.r
231
232 src_target_v = (target_x - src_x, target_y - src_y)
233 src_target_len = \
234 math.sqrt(src_target_v[0] ** 2 + src_target_v[1] ** 2)
235 distance = src_target_len - src_r - target_r
236 distance3 = distance / 3
237 src_target_uv = (src_target_v[0] / src_target_len,
238 src_target_v[1] / src_target_len)
239
240 c0x = src_x + src_target_uv[0] * src_r
241 c0y = src_y + src_target_uv[1] * src_r
242 c1x = c0x + src_target_uv[0] * distance3
243 c1y = c0y + src_target_uv[1] * distance3
244 c3x = target_x - src_target_uv[0] * target_r
245 c3y = target_y - src_target_uv[1] * target_r
246 c2x = c3x - src_target_uv[0] * distance3
247 c2y = c3y - src_target_uv[1] * distance3
248
249 path = [c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y]
250 return path
251
219 @property 252 @property
220 def path(self): 253 def path(self):
221 domview = self._domview 254 domview = self._domview
222 state_name = self._state.state_name 255 state_name = self._state.state_name
223 trn_cond = self.trn_cond 256 trn_cond = self.trn_cond
224 trn = domview.get_transition(state_name, trn_cond) 257 trn = domview.get_transition(state_name, trn_cond)
225 return trn[3] 258 path = trn[3]
259
260 if not path:
261 path = self._gen_path()
262 pass
263
264 return path
226 265
227 @property 266 @property
228 def target(self): 267 def target(self):
229 domview = self._domview 268 domview = self._domview
230 state_name = self._state.state_name 269 state_name = self._state.state_name
251 arrow_node = self._arrow_node 290 arrow_node = self._arrow_node
252 path_node = self._path_node 291 path_node = self._path_node
253 self._update_graph(path, arrow_node, path_node) 292 self._update_graph(path, arrow_node, path_node)
254 pass 293 pass
255 294
256 def adjust_by_ends(self, states): 295 def adjust_by_ends(self):
257 import math 296 states = self._states
258 297
259 state = self._state 298 state = self._state
260 state_name = state.state_name 299 state_name = state.state_name
261 trn_cond = self.trn_cond 300 trn_cond = self.trn_cond
262 301
263 path = self.path 302 path = self.path
297 pass 336 pass
298 337
299 class FSM_state(object): 338 class FSM_state(object):
300 _doc = None 339 _doc = None
301 _domview = None 340 _domview = None
341 _states = None
302 _fsm_layer = None 342 _fsm_layer = None
303 _control_layer = None 343 _control_layer = None
304 state_name = None 344 state_name = None
305 state_g = None 345 state_g = None
306 _text_node = None 346 _text_node = None
307 _circle_node = None 347 _circle_node = None
308 transitions = None 348 transitions = None
309 from_states = None 349 from_states = None # There is one or more transitions
350 # from these states (name).
310 351
311 _state_g_hdl_id = None 352 _state_g_hdl_id = None
312 _selected_rect = None 353 _selected_rect = None
313 354
314 def __init__(self, state_name): 355 def __init__(self, state_name):
315 self.state_name = state_name 356 self.state_name = state_name
316 self.transitions = {} 357 self.transitions = {}
317 self.from_states = set() 358 self.from_states = set()
318 pass 359 pass
319 360
320 def init(self, doc, domview, fsm_layer, control_layer): 361 def init(self, doc, domview, states, fsm_layer, control_layer):
321 self._doc = doc 362 self._doc = doc
322 self._domview = domview 363 self._domview = domview
364 self._states = states
323 self._fsm_layer = fsm_layer 365 self._fsm_layer = fsm_layer
324 self._control_layer = control_layer 366 self._control_layer = control_layer
325 pass 367 pass
326 368
327 def _update_graph(self, text_node, text_content, circle_node, 369 def _update_graph(self, text_node, text_content, circle_node,
442 domview = self._domview 484 domview = self._domview
443 state_name = self.state_name 485 state_name = self.state_name
444 conds = domview.all_transitions(state_name) 486 conds = domview.all_transitions(state_name)
445 return conds 487 return conds
446 488
489 def _load_transition_domview(self, parent, condition):
490 domview = self._domview
491 states = self._states
492
493 trn = FSM_transition(condition)
494 trn.init(self._doc, domview, self, states,
495 self._fsm_layer, self._control_layer)
496 trn.draw(parent)
497 self.transitions[condition] = trn
498 pass
499
447 def draw(self, parent): 500 def draw(self, parent):
448 domview = self._domview
449 state_name = self.state_name 501 state_name = self.state_name
450 502
451 r = self.r 503 r = self.r
452 x, y = self.xy 504 x, y = self.xy
453 state_g, text_node, text_content, circle_node = \ 505 state_g, text_node, text_content, circle_node = \
456 self._text_node = text_node 508 self._text_node = text_node
457 self._text_content = text_content 509 self._text_content = text_content
458 self._circle_node = circle_node 510 self._circle_node = circle_node
459 511
460 for trn_cond in self.all_transitions: 512 for trn_cond in self.all_transitions:
461 trn = FSM_transition(trn_cond) 513 self._load_transition_domview(parent, trn_cond)
462 trn.init(self._doc, domview, self,
463 self._fsm_layer, self._control_layer)
464 trn.draw(parent)
465 self.transitions[trn_cond] = trn
466 pass 514 pass
467 pass 515 pass
468 516
469 def clear(self): 517 def clear(self):
470 state_g = self.state_g 518 state_g = self.state_g
481 x, y = self.xy 529 x, y = self.xy
482 self._update_graph(text_node, text_content, circle_node, state_name, 530 self._update_graph(text_node, text_content, circle_node, state_name,
483 r, x, y) 531 r, x, y)
484 pass 532 pass
485 533
486 def tell_target_states(self, states): 534 ## \brief Tell states there are transitions to them.
535 #
536 # This function is only called when loading states of a FSM from
537 # domview. When loading, not all states was loaded that target
538 # state may not in the memory. So, we call this function after
539 # all states being loaded. Transitions added later does need to
540 # call this function to notify end state.
541 #
542 def tell_target_states(self):
543 states = self._states
487 transitions = self.transitions 544 transitions = self.transitions
488 target_state_names = [trn.target for trn in transitions.values()] 545 target_state_names = [trn.target for trn in transitions.values()]
489 target_states = [states[target_name] 546 target_states = [states[target_name]
490 for target_name in target_state_names] 547 for target_name in target_state_names]
491 state_name = self.state_name 548 state_name = self.state_name
492 for target_state in target_states: 549 for target_state in target_states:
493 target_state.from_states.add(state_name) 550 target_state.from_states.add(state_name)
494 pass 551 pass
495 pass 552 pass
496 553
497 def adjust_transitions(self, states): 554 def adjust_transitions(self):
498 import itertools 555 import itertools
556
557 states = self._states
499 558
500 for trn in self.transitions.values(): 559 for trn in self.transitions.values():
501 trn.adjust_by_ends(states) 560 trn.adjust_by_ends()
502 trn.update() 561 trn.update()
503 pass 562 pass
504 563
505 state_name = self.state_name 564 state_name = self.state_name
506 from_states = [states[from_state_name] 565 from_states = [states[from_state_name]
510 in_state_transitions = [[trn for trn in state_transitions 569 in_state_transitions = [[trn for trn in state_transitions
511 if trn.target == state_name] 570 if trn.target == state_name]
512 for state_transitions in states_transitions] 571 for state_transitions in states_transitions]
513 in_transitions = itertools.chain(*in_state_transitions) 572 in_transitions = itertools.chain(*in_state_transitions)
514 for trn in in_transitions: 573 for trn in in_transitions:
515 trn.adjust_by_ends(states) 574 trn.adjust_by_ends()
516 trn.update() 575 trn.update()
517 pass 576 pass
577 pass
578
579 def add_transition(self, parent, condition, target_state):
580 domview = self._domview
581
582 state_name = self.state_name
583 target_name = target_state.state_name
584 domview.add_transition(state_name, condition, target_name)
585
586 self._load_transition_domview(parent, condition)
587
588 states = self._states
589 target_state = states[target_name]
590 target_state.from_states.add(state_name)
518 pass 591 pass
519 pass 592 pass
520 593
521 594
522 class _FSM_move_state_mode(object): 595 class _FSM_move_state_mode(object):
551 pass 624 pass
552 self._selected_state = None 625 self._selected_state = None
553 pass 626 pass
554 627
555 def handle_move_state_state(self, state, evtype, button, x, y): 628 def handle_move_state_state(self, state, evtype, button, x, y):
556 import pybInkscape
557
558 window = self._window 629 window = self._window
559 states = window._states
560 630
561 def moving_state(item, evtype, button, x, y): 631 def moving_state(item, evtype, button, x, y):
562 if evtype == pybInkscape.PYSPItem.PYB_EVENT_BUTTON_RELEASE: 632 if evtype == pybInkscape.PYSPItem.PYB_EVENT_BUTTON_RELEASE:
563 window.ungrab_mouse() 633 window.ungrab_mouse()
564 pass 634 pass
566 new_state_y = orign_state_y + start_y - y 636 new_state_y = orign_state_y + start_y - y
567 637
568 domview = self._domview 638 domview = self._domview
569 domview.set_state_xy(state.state_name, x, y) 639 domview.set_state_xy(state.state_name, x, y)
570 state.update() 640 state.update()
571 state.adjust_transitions(states) 641 state.adjust_transitions()
572 state.show_selected() 642 state.show_selected()
573 pass 643 pass
574 644
575 window = self._window 645 window = self._window
576 646
610 680
611 class _FSM_add_state_mode(object): 681 class _FSM_add_state_mode(object):
612 __metaclass__ = data_monitor.data_monitor 682 __metaclass__ = data_monitor.data_monitor
613 __data_monitor_prefix__ = 'on_' 683 __data_monitor_prefix__ = 'on_'
614 684
685 _window = None
686 _domview = None
687
615 _saved_x = 0 688 _saved_x = 0
616 _saved_y = 0 689 _saved_y = 0
690
691 _select_state = None
617 692
618 def __init__(self, window, domview_ui): 693 def __init__(self, window, domview_ui):
619 super(_FSM_add_state_mode, self).__init__() 694 super(_FSM_add_state_mode, self).__init__()
620 695
621 self._window = window 696 self._window = window
654 729
655 window.hide_state_editor() 730 window.hide_state_editor()
656 pass 731 pass
657 732
658 def on_add_state_background(self, item, evtype, button, x, y): 733 def on_add_state_background(self, item, evtype, button, x, y):
659 import pybInkscape
660
661 window = self._window 734 window = self._window
662 735
663 if evtype == pybInkscape.PYSPItem.PYB_EVENT_BUTTON_RELEASE and \ 736 if evtype == pybInkscape.PYSPItem.PYB_EVENT_BUTTON_RELEASE and \
664 button == 1: 737 button == 1:
665 self._saved_x = x 738 self._saved_x = x
666 self._saved_y = y 739 self._saved_y = y
667 window.show_state_editor() 740 window.show_state_editor()
668 pass 741 pass
669 pass 742 pass
670 743
744 def _handle_select_transition_target(self, state, evtype, button, x, y):
745 if evtype != pybInkscape.PYSPItem.PYB_EVENT_BUTTON_RELEASE:
746 return
747 if button != 1:
748 return
749
750 if state == self._select_state:
751 self.deactivate()
752 self.activate()
753 return
754
755 window = self._window
756 fsm_layer = window._fsm_layer
757
758 target_state = state
759 src_state = self._select_state
760 cond = ''
761 src_state.add_transition(fsm_layer, cond, target_state)
762 pass
763
764 def _handle_add_transition(self, *args):
765 def restore_bg(item, evtype, *args):
766 if evtype != pybInkscape.PYSPItem.PYB_EVENT_BUTTON_PRESS:
767 return
768 self.deactivate()
769 self.activate()
770 pass
771
772 window = self._window
773 window.ungrab_bg()
774 window.grab_bg(restore_bg)
775
776 window.ungrab_state()
777 window.grab_state(self._handle_select_transition_target)
778 pass
779
671 def _handle_state_mouse_events(self, state, evtype, button, x, y): 780 def _handle_state_mouse_events(self, state, evtype, button, x, y):
672 import pybInkscape
673
674 if evtype == pybInkscape.PYSPItem.PYB_EVENT_BUTTON_RELEASE and \ 781 if evtype == pybInkscape.PYSPItem.PYB_EVENT_BUTTON_RELEASE and \
675 button == 3: 782 button == 3:
783 self._select_state = state
784
676 window = self._window 785 window = self._window
677 window.popup_state_menu() 786 window.popup_state_menu()
678 pass 787 pass
679 pass 788 pass
680 789
684 window._emit_leave_mode() 793 window._emit_leave_mode()
685 window.ungrab_all() 794 window.ungrab_all()
686 795
687 window.grab_bg(self.on_add_state_background) 796 window.grab_bg(self.on_add_state_background)
688 window.grab_state(self._handle_state_mouse_events) 797 window.grab_state(self._handle_state_mouse_events)
798 window.grab_add_transition(self._handle_add_transition)
689 pass 799 pass
690 800
691 def deactivate(self): 801 def deactivate(self):
692 pass 802 pass
693 pass 803 pass
707 817
708 _leave_mode_cb = None 818 _leave_mode_cb = None
709 _move_state_mode = None 819 _move_state_mode = None
710 _add_state_mode = None 820 _add_state_mode = None
711 _state_mouse_event_handler = None 821 _state_mouse_event_handler = None
822 _add_transition_cb = None
712 823
713 def __init__(self, domview_ui, close_cb, destroy_cb): 824 def __init__(self, domview_ui, close_cb, destroy_cb):
714 super(FSM_window, self).__init__() 825 super(FSM_window, self).__init__()
715 826
716 self._locker = domview_ui 827 self._locker = domview_ui
782 893
783 def _draw_state_domview(self, state_name): 894 def _draw_state_domview(self, state_name):
784 domview = self._domview 895 domview = self._domview
785 doc = self._doc() 896 doc = self._doc()
786 fsm_layer = self._fsm_layer 897 fsm_layer = self._fsm_layer
898 states = self._states
787 899
788 state = FSM_state(state_name) 900 state = FSM_state(state_name)
789 state.init(doc, domview, self._fsm_layer, self._control_layer) 901 state.init(doc, domview, states, self._fsm_layer, self._control_layer)
790 self._states[state_name] = state 902 self._states[state_name] = state
791 903
792 state.draw(fsm_layer) 904 state.draw(fsm_layer)
793 pass 905 pass
794 906
809 921
810 def ungrab_all(self): 922 def ungrab_all(self):
811 self.ungrab_bg() 923 self.ungrab_bg()
812 self.ungrab_mouse() 924 self.ungrab_mouse()
813 self.ungrab_state() 925 self.ungrab_state()
926 self.ungrab_add_transition()
814 pass 927 pass
815 928
816 def on_state_mouse_event(self, state, evtype, button, x, y): 929 def on_state_mouse_event(self, state, evtype, button, x, y):
817 if self._state_mouse_event_handler: 930 if self._state_mouse_event_handler:
818 self._state_mouse_event_handler(state, evtype, button, x, y) 931 self._state_mouse_event_handler(state, evtype, button, x, y)
831 self._state_mouse_event_handler = callback 944 self._state_mouse_event_handler = callback
832 pass 945 pass
833 946
834 def ungrab_state(self): 947 def ungrab_state(self):
835 self._state_mouse_event_handler = None 948 self._state_mouse_event_handler = None
949 pass
950
951 def grab_add_transition(self, callback):
952 assert self._add_transition_cb is None
953 self._add_transition_cb = callback
954 pass
955
956 def ungrab_add_transition(self):
957 self._add_transition_cb = None
836 pass 958 pass
837 959
838 def _load_new_state(self, state_name): 960 def _load_new_state(self, state_name):
839 states = self._states 961 states = self._states
840 962
847 # 969 #
848 def _load_new_state_incr(self, state_name): 970 def _load_new_state_incr(self, state_name):
849 self._load_new_state(state_name) 971 self._load_new_state(state_name)
850 states = self._states 972 states = self._states
851 state = states[state_name] 973 state = states[state_name]
852 state.tell_target_states(states) 974 state.tell_target_states()
853 pass 975 pass
854 976
855 def _rebuild_from_states(self): 977 def _rebuild_from_states(self):
856 states = self._states 978 states = self._states
857 domview = self._domview 979 domview = self._domview
858 state_names = domview.all_state_names() 980 state_names = domview.all_state_names()
859 for state_name in state_names: 981 for state_name in state_names:
860 state = states[state_name] 982 state = states[state_name]
861 state.tell_target_states(states) 983 state.tell_target_states()
862 pass 984 pass
863 pass 985 pass
864 986
865 def _update_view(self): 987 def _update_view(self):
866 self._clear_view() 988 self._clear_view()
912 self._set_leave_mode_cb(lambda: mode.deactivate()) 1034 self._set_leave_mode_cb(lambda: mode.deactivate())
913 pass 1035 pass
914 1036
915 def on_state_apply_clicked(self, *args): 1037 def on_state_apply_clicked(self, *args):
916 self._add_state_mode.handle_new_state() 1038 self._add_state_mode.handle_new_state()
1039 pass
1040
1041 def on_add_transition_activate(self, *args):
1042 if self._add_transition_cb:
1043 self._add_transition_cb(*args)
1044 pass
917 pass 1045 pass
918 1046
919 def _install_test_data(self): 1047 def _install_test_data(self):
920 self._init_layers() 1048 self._init_layers()
921 1049