changeset 1478:6fe773e62b2a

Add state to FSM. - Add a state if user left-click on the background of FSM window. - pop a dialog that user can specify name and radius of the new state.
author Thinker K.F. Li <thinker@codemud.net>
date Mon, 25 Apr 2011 17:52:51 +0800
parents e217c7743905
children 92a8497d0361
files pyink/FSM_window.glade pyink/FSM_window.py
diffstat 2 files changed, 370 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/pyink/FSM_window.glade	Sun Apr 24 21:09:37 2011 +0800
+++ b/pyink/FSM_window.glade	Mon Apr 25 17:52:51 2011 +0800
@@ -130,6 +130,7 @@
                     <property name="label" translatable="yes">Add State</property>
                     <property name="use_underline">True</property>
                     <property name="stock_id">gtk-add</property>
+                    <signal name="toggled" handler="on_add_state_toggled"/>
                   </object>
                   <packing>
                     <property name="expand">False</property>
@@ -144,6 +145,7 @@
                     <property name="use_underline">True</property>
                     <property name="icon_name">widget-gtk-alignment</property>
                     <property name="group">add_state</property>
+                    <signal name="toggled" handler="on_move_state_toggled"/>
                   </object>
                   <packing>
                     <property name="expand">False</property>
@@ -289,4 +291,192 @@
       </object>
     </child>
   </object>
+  <object class="GtkDialog" id="state_editor">
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">State Editor</property>
+    <property name="modal">True</property>
+    <property name="window_position">center-on-parent</property>
+    <property name="default_height">200</property>
+    <property name="type_hint">normal</property>
+    <property name="transient_for">FSM_main_win</property>
+    <signal name="delete_event" handler="gtk_widget_hide"/>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkTable" id="table1">
+            <property name="visible">True</property>
+            <property name="n_rows">3</property>
+            <property name="n_columns">2</property>
+            <child>
+              <object class="GtkLabel" id="label2">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Name:</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label3">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Radius:</property>
+              </object>
+              <packing>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="state_name">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="invisible_char">&#x25CF;</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="state_radius">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="invisible_char">&#x25CF;</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+              </packing>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="state_apply">
+                <property name="label">gtk-apply</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+                <signal name="clicked" handler="on_state_apply_clicked"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="state_cancel">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+                <signal name="clicked" handler="on_state_cancel_clicked"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="0">state_apply</action-widget>
+      <action-widget response="0">state_cancel</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkDialog" id="error_dialog">
+    <property name="width_request">200</property>
+    <property name="height_request">200</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Error</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <property name="type_hint">normal</property>
+    <property name="transient_for">state_editor</property>
+    <signal name="delete_event" handler="gtk_widget_hide"/>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox2">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkVBox" id="vbox3">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="error_dialog_label">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Invalid state name.  It is a existing state name or invalid in format!</property>
+                <property name="wrap">True</property>
+              </object>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area2">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <object class="GtkButton" id="error_dialog_ok">
+                <property name="label">gtk-ok</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+                <signal name="clicked" handler="on_error_dialog_ok_clicked"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="0">error_dialog_ok</action-widget>
+    </action-widgets>
+  </object>
 </interface>
--- a/pyink/FSM_window.py	Sun Apr 24 21:09:37 2011 +0800
+++ b/pyink/FSM_window.py	Mon Apr 25 17:52:51 2011 +0800
@@ -3,6 +3,13 @@
 import data_monitor
 
 class FSM_window_base(object):
+    _state_editor = None
+    _state_name = None
+    _state_radius = None
+
+    _error_dialog = None
+    _error_dialog_label = None
+    
     def __init__(self):
         super(FSM_window_base, self).__init__()
         
@@ -14,12 +21,54 @@
 
         main_win = builder.get_object("FSM_main_win")
         view_box = builder.get_object("view_box")
+        
+        state_editor = builder.get_object("state_editor")
+        state_name = builder.get_object('state_name')
+        state_radius = builder.get_object('state_radius')
+
+        error_dialog = builder.get_object('error_dialog')
+        error_dialog_label = builder.get_object('error_dialog_label')
 
         builder.connect_signals(self)
         
         self._builder = builder
         self._main_win = main_win
         self._view_box = view_box
+
+        self._state_editor = state_editor
+        self._state_name = state_name
+        self._state_radius = state_radius
+
+        self._error_dialog = error_dialog
+        self._error_dialog_label = error_dialog_label
+        pass
+
+    def show_error(self, msg):
+        error_dialog = self._error_dialog
+        error_dialog_label = self._error_dialog_label
+        
+        error_dialog_label.set_text(msg)
+        error_dialog.show()
+        pass
+
+    def hide_error(self):
+        error_dialog = self._error_dialog
+        error_dialog.hide()
+        pass
+
+    def show_state_editor(self, state_name=''):
+        state_name_inp = self._state_name
+        state_radius_inp = self._state_radius
+        state_editor = self._state_editor
+        
+        state_name_inp.set_text(state_name)
+        state_radius_inp.set_text('30')
+        state_editor.show()
+        pass
+
+    def hide_state_editor(self):
+        state_editor = self._state_editor
+        state_editor.hide()
         pass
 
     def show(self):
@@ -29,6 +78,10 @@
     def hide(self):
         self._main_win.hide()
         pass
+
+    def gtk_widget_hide(self, widget, event, *data):
+        widget.hide()
+        return True
     
     def on_start_state_activate(self, *args):
         pass
@@ -56,6 +109,19 @@
 
     def on_FSM_main_win_destroy_event(self, *args):
         pass
+
+    def on_state_apply_clicked(self, *args):
+        pass
+    
+    def on_state_cancel_clicked(self, *args):
+        state_editor = self._state_editor
+        state_editor.hide()
+        pass
+
+    def on_error_dialog_ok_clicked(self, *args):
+        error_dialog = self._error_dialog
+        error_dialog.hide()
+        pass
     pass
 
 class FSM_transition(object):
@@ -282,6 +348,12 @@
     _control_layer = None
     width = 1024
     height = 768
+
+    _grab_hdl = None
+    _bg_hdl = None
+
+    _saved_x = 0
+    _saved_y = 0
     
     def __init__(self, domview_ui, close_cb, destroy_cb):
         super(FSM_window, self).__init__()
@@ -319,6 +391,8 @@
         control_layer.setAttribute('inkscape:groupmode', 'layer')
         root.appendChild(control_layer)
         self._control_layer = control_layer
+        
+        self.grab_bg(self.on_add_state_background)
         pass
 
     def _doc(self):
@@ -332,6 +406,9 @@
         root = doc.root()
         return root
 
+    def _translate_xy(self, x, y):
+        return x, y
+
     def _clear_view(self):
         if not self._background:
             self._init_layers()
@@ -347,20 +424,26 @@
         self._states = {}
         pass
 
-    def _update_view(self):
-        self._clear_view()
-        
+    def _draw_state_domview(self, state_name):
         domview = self._domview
         doc = self._doc()
         fsm_layer = self._fsm_layer
         
+        state = FSM_state(state_name)
+        state.init(doc, domview, self._fsm_layer, self._control_layer)
+        self._states[state_name] = state
+        
+        state.draw(fsm_layer)
+        pass
+
+    def _update_view(self):
+        self._clear_view()
+        
+        domview = self._domview
+        
         state_names = domview.all_state_names()
         for state_name in state_names:
-            state = FSM_state(state_name)
-            state.init(doc, domview, self._fsm_layer, self._control_layer)
-            self._states[state_name] = state
-            
-            state.draw(fsm_layer)
+            self._draw_state_domview(state_name)
             pass
         pass
 
@@ -382,6 +465,58 @@
         pass
 
     def on_add_state_toggled(self, *args):
+        self.ungrab_bg()
+        self.grab_bg(self.on_add_state_background)
+        pass
+
+    def on_move_state_toggled(self, *args):
+        self.ungrab_bg()
+        self.grab_bg(self.on_move_state_background)
+        pass
+
+    def on_add_state_background(self, item, evtype, buttons, x, y):
+        import pybInkscape
+        
+        if evtype == pybInkscape.PYSPItem.PYB_EVENT_BUTTON_RELEASE and \
+                buttons == 1:
+            self._saved_x = x
+            self._saved_y = y
+            self.show_state_editor()
+            pass
+        pass
+
+    def on_move_state_background(self, item, evtype, buttons, x, y):
+        pass
+
+    def on_state_apply_clicked(self, *args):
+        import traceback
+        
+        domview = self._domview
+        x, y = self._translate_xy(self._saved_x, self._saved_y)
+
+        state_name = self._state_name.get_text()
+        r_txt = self._state_radius.get_text()
+        try:
+            r = float(r_txt)
+        except ValueError:
+            traceback.print_exc()
+            self.show_error('Invalid value: "%s" is not a valid value '
+                            'for radius.' % (r_txt))
+            return
+        
+        try:
+            domview.add_state(state_name)
+        except:
+            traceback.print_exc()
+            self.show_error('Invalid state name: "%s" is existing' %
+                            (state_name))
+            return
+        domview.set_state_xy(state_name, x, y)
+        domview.set_state_r(state_name, r)
+
+        self._draw_state_domview(state_name)
+
+        self.hide_state_editor()
         pass
 
     def _install_test_data(self):
@@ -432,6 +567,43 @@
         self._update_view()
         super(FSM_window, self).show()
         pass
+
+    def grab_mouse(self, callback):
+        assert self._grab_hdl is None
+        
+        root = self._root()
+        root.setAttribute('inkscape:groupmode', '')
+        self._grab_hdl = root.spitem.connect('mouse-event', callback)
+        pass
+
+    def ungrab_mouse(self):
+        if not self._grab_hdl:
+            return
+        
+        root = self._root()
+        root.spitem.disconnect(self._grab_hdl)
+        self._grab_hdl = None
+        root.setAttribute('inkscape:groupmode', 'layer')
+        pass
+
+    def grab_bg(self, callback):
+        assert self._bg_hdl is None
+        assert self._background
+
+        background = self._background
+        bg_hdl = background.spitem.connect('mouse-event', callback)
+        self._bg_hdl = bg_hdl
+        pass
+
+    def ungrab_bg(self):
+        if not self._bg_hdl:
+            return
+
+        background = self._background
+        bg_hdl = self._bg_hdl
+        background.spitem.disconnect(bg_hdl)
+        self._bg_hdl = None
+        pass
     pass
 
 if __name__ == '__main__':