Mercurial > MadButterfly
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 |