Added gui.panel.propertyeditor2 to work around *nix+wxPropertyGrid issues.
authorW. Trevor King <wking@drexel.edu>
Thu, 29 Jul 2010 15:22:09 +0000 (11:22 -0400)
committerW. Trevor King <wking@drexel.edu>
Thu, 29 Jul 2010 15:22:09 +0000 (11:22 -0400)
The basic framework works, but only IntProperty is currently implemented.

hooke/ui/gui/__init__.py
hooke/ui/gui/panel/__init__.py
hooke/ui/gui/panel/propertyeditor2.py [new file with mode: 0644]

index d9706f2a1a3d3391daec8ee7d312f18e111638c5..592e55e498e7232e179d0b51109fde99fd3c0135 100644 (file)
@@ -20,7 +20,7 @@ import wx.aui as aui
 import wx.lib.evtmgr as evtmgr\r
 \r
 \r
-# wxPropertyGrid included in wxPython >= 2.9.1, until then, see\r
+# wxPropertyGrid is included in wxPython >= 2.9.1, see\r
 #   http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download\r
 # until then, we'll avoid it because of the *nix build problems.\r
 #import wx.propgrid as wxpg\r
@@ -148,7 +148,12 @@ class HookeFrame (wx.Frame):
                     # WANTS_CHARS so the panel doesn't eat the Return key.\r
 #                    size=(160, 200)\r
                     ), 'center'),\r
-            #('properties', panel.propertyeditor.PropertyEditor(self),'right'),\r
+            ('property', panel.PANELS['propertyeditor2'](\r
+                    callbacks={},\r
+                    parent=self,\r
+                    style=wx.WANTS_CHARS,\r
+                    # WANTS_CHARS so the panel doesn't eat the Return key.\r
+                    ), 'center'),\r
 #            ('assistant', wx.TextCtrl(\r
 #                    parent=self,\r
 #                    pos=wx.Point(0, 0),\r
index 50aa1dc28a899bbc8e5dcd2ffa876c39208ca8e5..2457f5c60e162615f792a200b3826dd5669d47c5 100644 (file)
@@ -11,6 +11,7 @@ PANEL_MODULES = [
 #    'playlist',\r
 #    'plot',\r
 #    'propertyeditor',\r
+    'propertyeditor2',\r
 #    'results',\r
 #    'selection',\r
 #    'welcome',\r
diff --git a/hooke/ui/gui/panel/propertyeditor2.py b/hooke/ui/gui/panel/propertyeditor2.py
new file mode 100644 (file)
index 0000000..f361a37
--- /dev/null
@@ -0,0 +1,474 @@
+# Copyright\r
+\r
+"""Property editor panel for Hooke.\r
+\r
+wxPropertyGrid is `included in wxPython >= 2.9.1 <included>`_.  Until\r
+then, we'll avoid it because of the *nix build problems.\r
+\r
+This module hacks together a workaround to be used until 2.9.1 is\r
+widely installed (or at least released ;).\r
+\r
+.. _included:\r
+  http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download\r
+"""\r
+\r
+import wx.grid\r
+\r
+from . import Panel\r
+\r
+\r
+def prop_from_setting(setting):\r
+    """Convert a :class:`~hooke.config.Setting` to a :class:`Property`.\r
+    """\r
+    raise NotImplementedError()\r
+\r
+def prop_from_argument(argument):\r
+    """Convert a :class:`~hooke.command.Argument` to a :class:`Property`.\r
+    """\r
+    raise NotImplementedError()\r
+\r
+\r
+class Property (object):\r
+    def __init__(self, type, label, default, help=None):\r
+        self.type = type\r
+        self.label = label\r
+        self.default = default\r
+        self.help = help\r
+\r
+    def get_editor(self):\r
+        """Return a wx.Window instance containing a suitable editor.\r
+\r
+        Retains a reference to the returned editory as `._editor`.\r
+        """\r
+        raise NotImplementedError()\r
+\r
+    def value_string(self, value):\r
+        """Return a string representation of `value` for loading the table.\r
+        """\r
+        return str(value)\r
+\r
+    def get_value(self):\r
+        """Return the value of `._editor`.\r
+        """\r
+        raise NotImplementedError()\r
+\r
+\r
+class IntProperty (Property):\r
+    def __init__(self, **kwargs):\r
+        assert 'type' not in kwargs, kwargs\r
+        if 'default' not in kwargs:\r
+            kwargs['default'] = 0\r
+        super(IntProperty, self).__init__(type='int', **kwargs)\r
+\r
+    def get_editor(self):\r
+        self._editor = wx.grid.GridCellNumberEditor()\r
+        return self._editor\r
+\r
+    def get_value(self):\r
+        raise int(self._editor.GetValue())\r
+\r
+\r
+#class PyFilesProperty(wxpg.PyArrayStringProperty):\r
+#    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=[]):\r
+#        wxpg.PyArrayStringProperty.__init__(self, label, name, value)\r
+#        self.SetValue(value)\r
+#\r
+#    def OnSetValue(self, v):\r
+#        self.value = v\r
+#        self.display = ', '.join(self.value)\r
+#\r
+#    def GetValueAsString(self, argFlags):\r
+#        return self.display\r
+#\r
+#    def PyStringToValue(self, s, flags):\r
+#        return [a.strip() for a in s.split(',')]\r
+#\r
+#    def OnEvent(self, propgrid, ctrl, event):\r
+#        if event.GetEventType() == wx.wxEVT_COMMAND_BUTTON_CLICKED:\r
+#            # Show dialog to select a string, call DoSetValue and\r
+#            # return True, if value changed.\r
+#            return True\r
+#\r
+#        return False\r
+#\r
+#\r
+#class PyObjectPropertyValue:\r
+#    """\\r
+#    Value type of our sample PyObjectProperty. We keep a simple dash-delimited\r
+#    list of string given as argument to constructor.\r
+#    """\r
+#    def __init__(self, s=None):\r
+#        try:\r
+#            self.ls = [a.strip() for a in s.split('-')]\r
+#        except:\r
+#            self.ls = []\r
+#\r
+#    def __repr__(self):\r
+#        return ' - '.join(self.ls)\r
+#\r
+#\r
+#class PyObjectProperty(wxpg.PyProperty):\r
+#    """\\r
+#    Another simple example. This time our value is a PyObject (NOTE: we can't\r
+#    return an arbitrary python object in DoGetValue. It cannot be a simple\r
+#    type such as int, bool, double, or string, nor an array or wxObject based.\r
+#    Dictionary, None, or any user-specified Python object is allowed).\r
+#    """\r
+#    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=None):\r
+#        wxpg.PyProperty.__init__(self, label, name)\r
+#        self.SetValue(value)\r
+#\r
+#    def GetClassName(self):\r
+#        return self.__class__.__name__\r
+#\r
+#    def GetEditor(self):\r
+#        return "TextCtrl"\r
+#\r
+#    def GetValueAsString(self, flags):\r
+#        return repr(self.GetValue())\r
+#\r
+#    def PyStringToValue(self, s, flags):\r
+#        return PyObjectPropertyValue(s)\r
+#\r
+#\r
+#class ShapeProperty(wxpg.PyEnumProperty):\r
+#    """\\r
+#    Demonstrates use of OnCustomPaint method.\r
+#    """\r
+#    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=-1):\r
+#        wxpg.PyEnumProperty.__init__(self, label, name, ['Line','Circle','Rectangle'], [0,1,2], value)\r
+#\r
+#    def OnMeasureImage(self, index):\r
+#        return wxpg.DEFAULT_IMAGE_SIZE\r
+#\r
+#    def OnCustomPaint(self, dc, rect, paint_data):\r
+#        """\\r
+#        paint_data.m_choiceItem is -1 if we are painting the control,\r
+#        in which case we need to get the drawn item using DoGetValue.\r
+#        """\r
+#        item = paint_data.m_choiceItem\r
+#        if item == -1:\r
+#            item = self.DoGetValue()\r
+#\r
+#        dc.SetPen(wx.Pen(wx.BLACK))\r
+#        dc.SetBrush(wx.Brush(wx.BLACK))\r
+#\r
+#        if item == 0:\r
+#            dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height)\r
+#        elif item == 1:\r
+#            half_width = rect.width / 2\r
+#            dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3)\r
+#        elif item == 2:\r
+#            dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)\r
+#\r
+#\r
+#class LargeImagePickerCtrl(wx.Window):\r
+#    """\\r
+#    Control created and used by LargeImageEditor.\r
+#    """\r
+#    def __init__(self):\r
+#        pre = wx.PreWindow()\r
+#        self.PostCreate(pre)\r
+#\r
+#    def Create(self, parent, id_, pos, size, style = 0):\r
+#        wx.Window.Create(self, parent, id_, pos, size, style | wx.BORDER_SIMPLE)\r
+#        img_spc = size[1]\r
+#        self.tc = wx.TextCtrl(self, -1, "", (img_spc,0), (2048,size[1]), wx.BORDER_NONE)\r
+#        self.SetBackgroundColour(wx.WHITE)\r
+#        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)\r
+#        self.property = None\r
+#        self.bmp = None\r
+#        self.Bind(wx.EVT_PAINT, self.OnPaint)\r
+#\r
+#    def OnPaint(self, event):\r
+#        dc = wx.BufferedPaintDC(self)\r
+#\r
+#        whiteBrush = wx.Brush(wx.WHITE)\r
+#        dc.SetBackground(whiteBrush)\r
+#        dc.Clear()\r
+#\r
+#        bmp = self.bmp\r
+#        if bmp:\r
+#            dc.DrawBitmap(bmp, 2, 2)\r
+#        else:\r
+#            dc.SetPen(wx.Pen(wx.BLACK))\r
+#            dc.SetBrush(whiteBrush)\r
+#            dc.DrawRectangle(2, 2, 64, 64)\r
+#\r
+#    def RefreshThumbnail(self):\r
+#        """\\r
+#        We use here very simple image scaling code.\r
+#        """\r
+#        if not self.property:\r
+#            self.bmp = None\r
+#            return\r
+#\r
+#        path = self.property.DoGetValue()\r
+#\r
+#        if not os.path.isfile(path):\r
+#            self.bmp = None\r
+#            return\r
+#\r
+#        image = wx.Image(path)\r
+#        image.Rescale(64, 64)\r
+#        self.bmp = wx.BitmapFromImage(image)\r
+#\r
+#    def SetProperty(self, property):\r
+#        self.property = property\r
+#        self.tc.SetValue(property.GetDisplayedString())\r
+#        self.RefreshThumbnail()\r
+#\r
+#    def SetValue(self, s):\r
+#        self.RefreshThumbnail()\r
+#        self.tc.SetValue(s)\r
+#\r
+#    def GetLastPosition(self):\r
+#        return self.tc.GetLastPosition()\r
+#\r
+#\r
+#class LargeImageEditor(wxpg.PyEditor):\r
+#    """\\r
+#    Double-height text-editor with image in front.\r
+#    """\r
+#    def __init__(self):\r
+#        wxpg.PyEditor.__init__(self)\r
+#\r
+#    def CreateControls(self, propgrid, property, pos, sz):\r
+#        try:\r
+#            h = 64 + 6\r
+#            x = propgrid.GetSplitterPosition()\r
+#            x2 = propgrid.GetClientSize().x\r
+#            bw = propgrid.GetRowHeight()\r
+#            lipc = LargeImagePickerCtrl()\r
+#            if sys.platform == 'win32':\r
+#                lipc.Hide()\r
+#            lipc.Create(propgrid, wxpg.PG_SUBID1, (x,pos[1]), (x2-x-bw,h))\r
+#            lipc.SetProperty(property)\r
+#            # Hmmm.. how to have two-stage creation without subclassing?\r
+#            #btn = wx.PreButton()\r
+#            #pre = wx.PreWindow()\r
+#            #self.PostCreate(pre)\r
+#            #if sys.platform == 'win32':\r
+#            #    btn.Hide()\r
+#            #btn.Create(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
+#            btn = wx.Button(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
+#            return (lipc, btn)\r
+#        except:\r
+#            import traceback\r
+#            print traceback.print_exc()\r
+#\r
+#    def UpdateControl(self, property, ctrl):\r
+#        ctrl.SetValue(property.GetDisplayedString())\r
+#\r
+#    def DrawValue(self, dc, property, rect):\r
+#        if not (property.GetFlags() & wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
+#            dc.DrawText( property.GetDisplayedString(), rect.x+5, rect.y );\r
+#\r
+#    def OnEvent(self, propgrid, ctrl, event):\r
+#        if not ctrl:\r
+#            return False\r
+#\r
+#        evtType = event.GetEventType()\r
+#\r
+#        if evtType == wx.wxEVT_COMMAND_TEXT_ENTER:\r
+#            if propgrid.IsEditorsValueModified():\r
+#                return True\r
+#\r
+#        elif evtType == wx.wxEVT_COMMAND_TEXT_UPDATED:\r
+#            if not property.HasFlag(wxpg.PG_PROP_AUTO_UNSPECIFIED) or not ctrl or \\r
+#               ctrl.GetLastPosition() > 0:\r
+#\r
+#                # We must check this since an 'empty' text event\r
+#                # may be triggered when creating the property.\r
+#                PG_FL_IN_SELECT_PROPERTY = 0x00100000\r
+#                if not (propgrid.GetInternalFlags() & PG_FL_IN_SELECT_PROPERTY):\r
+#                    event.Skip();\r
+#                    event.SetId(propgrid.GetId());\r
+#\r
+#                propgrid.EditorsValueWasModified();\r
+#\r
+#        return False\r
+#\r
+#\r
+#    def CopyValueFromControl(self, property, ctrl):\r
+#        tc = ctrl.tc\r
+#        res = property.SetValueFromString(tc.GetValue(),0)\r
+#        # Changing unspecified always causes event (returning\r
+#        # true here should be enough to trigger it).\r
+#        if not res and property.IsFlagSet(wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
+#            res = True\r
+#\r
+#        return res\r
+#\r
+#    def SetValueToUnspecified(self, ctrl):\r
+#        ctrl.tc.Remove(0,len(ctrl.tc.GetValue()));\r
+#\r
+#    def SetControlStringValue(self, ctrl, txt):\r
+#        ctrl.SetValue(txt)\r
+#\r
+#    def OnFocus(self, property, ctrl):\r
+#        ctrl.tc.SetSelection(-1,-1)\r
+#        ctrl.tc.SetFocus()\r
+\r
+\r
+class PropertyPanel(Panel, wx.grid.Grid):\r
+    def __init__(self, callbacks=None, **kwargs):\r
+        super(PropertyPanel, self).__init__(\r
+            name='propertyeditor', callbacks=callbacks, **kwargs)\r
+        self._properties = []\r
+\r
+        self.CreateGrid(numRows=0, numCols=1)\r
+        self.SetColLabelValue(0, 'value')\r
+\r
+        self._last_tooltip = None\r
+        self.GetGridWindow().Bind(wx.EVT_MOTION, self._on_mouse_over)\r
+\r
+        self.append_property(IntProperty(\r
+                label='my int',\r
+                default=5,\r
+                help='help for my int',\r
+                ))\r
+\r
+    def GetRowLabelValue(self, col=0):\r
+        """Retrieve the label for a particular column.\r
+\r
+        Overrides the default labels, since 'SetRowLabelValue' seems\r
+        to be `ignored`_.\r
+\r
+        .. _ignored:\r
+          http://wiki.wxpython.org/wxPyGridTableBase#Column.2BAC8-Row_Labels\r
+        """\r
+        return self._column_labels[col]\r
+\r
+    def _on_mouse_over(self, event):\r
+        """Enable tooltips.\r
+        """\r
+        x,y = self.CalcUnscrolledPosition(event.GetPosition())\r
+        col,row = self.XYToCell(x, y)\r
+        if col == -1 or row == -1:\r
+            msg = ''\r
+        else:\r
+            msg = self._properties[row].help\r
+        if msg != self._last_tooltip:\r
+            self._last_tooltip = msg\r
+            event.GetEventObject().SetToolTipString(msg)\r
+\r
+    def append_property(self, property):\r
+        if len([p for p in self._properties if p.label == label]) > 0:\r
+            raise ValueError(property)  # property.label collision\r
+        self._properties.append(property)\r
+        row = len(self._properties) - 1\r
+        self.AppendRows(numRows=1)\r
+        self.SetRowLabelValue(row, property.label)\r
+        self.SetCellEditor(row=row, col=0, editor=property.get_editor())\r
+        self.set_property(property.label, property.default)\r
+\r
+    def set_property(self, label, value):\r
+        props = [(i,p) for i,p in enumerate(self._properties)\r
+                 if p.label == label]\r
+        assert len(props) == 1, props\r
+        row,property = props[0]\r
+        self.SetCellValue(row=row, col=0, s=property.value_string(value))\r
+\r
+    def remove_property(self, property):\r
+        pass\r
+\r
+    def GetPropertyValues(self):\r
+        raise NotImplementedError()\r
+        return self.pg.GetPropertyValues()\r
+\r
+    def Initialize(self, properties):\r
+        raise NotImplementedError()\r
+        pg = self.pg\r
+        pg.Clear()\r
+\r
+        if properties:\r
+            for element in properties:\r
+                if element[1]['type'] == 'arraystring':\r
+                    elements = element[1]['elements']\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1]['value']\r
+                    else: \r
+                       property_value = element[1]['default']\r
+                    #retrieve individual strings\r
+                    property_value = split(property_value, ' ')\r
+                    #remove " delimiters\r
+                    values = [value.strip('"') for value in property_value]\r
+                    pg.Append(wxpg.ArrayStringProperty(element[0], value=values))\r
+\r
+                if element[1]['type'] == 'boolean':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1].as_bool('value')\r
+                    else:\r
+                        property_value = element[1].as_bool('default')\r
+                    property_control = wxpg.BoolProperty(element[0], value=property_value)\r
+                    pg.Append(property_control)\r
+                    pg.SetPropertyAttribute(element[0], 'UseCheckbox', True)\r
+\r
+                #if element[0] == 'category':\r
+                    #pg.Append(wxpg.PropertyCategory(element[1]))\r
+\r
+                if element[1]['type'] == 'color':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1]['value']\r
+                    else:\r
+                        property_value = element[1]['default']\r
+                    property_value = eval(property_value)\r
+                    pg.Append(wxpg.ColourProperty(element[0], value=property_value))\r
+\r
+                if element[1]['type'] == 'enum':\r
+                    elements = element[1]['elements']\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1]['value']\r
+                    else:\r
+                        property_value = element[1]['default']\r
+                    pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value)))\r
+\r
+                if element[1]['type'] == 'filename':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1]['value']\r
+                    else:\r
+                        property_value = element[1]['default']\r
+                    pg.Append(wxpg.FileProperty(element[0], value=property_value))\r
+\r
+                if element[1]['type'] == 'float':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1].as_float('value')\r
+                    else:\r
+                        property_value = element[1].as_float('default')\r
+                    property_control = wxpg.FloatProperty(element[0], value=property_value)\r
+                    pg.Append(property_control)\r
+\r
+                if element[1]['type'] == 'folder':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1]['value']\r
+                    else:\r
+                        property_value = element[1]['default']\r
+                    pg.Append(wxpg.DirProperty(element[0], value=property_value))\r
+\r
+                if element[1]['type'] == 'integer':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1].as_int('value')\r
+                    else:\r
+                        property_value = element[1].as_int('default')\r
+                    property_control = wxpg.IntProperty(element[0], value=property_value)\r
+                    if 'maximum' in element[1]:\r
+                        property_control.SetAttribute('Max', element[1].as_int('maximum'))\r
+                    if 'minimum' in element[1]:\r
+                        property_control.SetAttribute('Min', element[1].as_int('minimum'))\r
+                    property_control.SetAttribute('Wrap', True)\r
+                    pg.Append(property_control)\r
+                    pg.SetPropertyEditor(element[0], 'SpinCtrl')\r
+\r
+                if element[1]['type'] == 'string':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1]['value']\r
+                    else:\r
+                        property_value = element[1]['default']\r
+                    pg.Append(wxpg.StringProperty(element[0], value=property_value))\r
+\r
+        pg.Refresh()\r
+\r
+    def OnReserved(self, event):\r
+        raise NotImplementedError()\r
+        pass\r