Fill out prop_from_arguments so PropertyPanel displays command args.
[hooke.git] / hooke / ui / gui / panel / propertyeditor2.py
1 # Copyright\r
2 \r
3 """Property editor panel for Hooke.\r
4 \r
5 wxPropertyGrid is `included in wxPython >= 2.9.1 <included>`_.  Until\r
6 then, we'll avoid it because of the *nix build problems.\r
7 \r
8 This module hacks together a workaround to be used until 2.9.1 is\r
9 widely installed (or at least released ;).\r
10 \r
11 .. _included:\r
12   http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download\r
13 """\r
14 \r
15 import wx.grid\r
16 \r
17 from . import Panel\r
18 \r
19 \r
20 def prop_from_argument(argument, curves=None, playlists=None):\r
21     """Convert a :class:`~hooke.command.Argument` to a :class:`Property`.\r
22     """\r
23     if argument.count != 1:\r
24         raise NotImplementedError(argument)\r
25     kwargs = {\r
26         'label':argument.name,\r
27         'default':argument.default,\r
28         'help':argument.help,\r
29         }\r
30     type = argument.type\r
31     if type == 'file':\r
32         type = 'path'\r
33     if argument.type in ['string', 'bool', 'int', 'float', 'path']:\r
34         _class = globals()['%sProperty' % type.capitalize()]\r
35         return _class(**kwargs)\r
36     elif argument.type in ['curve', 'playlist']:\r
37         if argument.type == 'curve':\r
38             choices = curves  # extract from a particular playlist?\r
39         else:\r
40             choices = playlists\r
41         return ChoiceProperty(choices=[c.name for c in choices], **kwargs)\r
42     raise NotImplementedError(argument.type)\r
43 \r
44 def prop_from_setting(setting):\r
45     """Convert a :class:`~hooke.config.Setting` to a :class:`Property`.\r
46     """    \r
47     raise NotImplementedError()\r
48 \r
49 \r
50 class Property (object):\r
51     def __init__(self, type, label, default, help=None):\r
52         self.type = type\r
53         self.label = label\r
54         self.default = default\r
55         self.help = help\r
56 \r
57     def get_editor(self):\r
58         """Return a suitable grid editor.\r
59         """\r
60         raise NotImplementedError()\r
61 \r
62     def get_renderer(self):\r
63         """Return a suitable grid renderer.\r
64 \r
65         Returns `None` if no special renderer is required.\r
66         """\r
67         return None\r
68 \r
69     def string_for_value(self, value):\r
70         """Return a string representation of `value` for loading the table.\r
71         """\r
72         return str(value)\r
73 \r
74     def value_for_string(self, string):\r
75         """Return the value represented by `string`.\r
76         """\r
77         return string\r
78 \r
79 \r
80 class StringProperty (Property):\r
81     def __init__(self, **kwargs):\r
82         assert 'type' not in kwargs, kwargs\r
83         if 'default' not in kwargs:\r
84             kwargs['default'] = 0\r
85         super(StringProperty, self).__init__(type='string', **kwargs)\r
86 \r
87     def get_editor(self):\r
88         return wx.grid.GridCellTextEditor()\r
89 \r
90     def get_renderer(self):\r
91         return wx.grid.GridCellStringRenderer()\r
92 \r
93 \r
94 class BoolProperty (Property):\r
95     """A boolean property.\r
96 \r
97     Notes\r
98     -----\r
99     Unfortunately, changing a boolean property takes two clicks:\r
100 \r
101     1) create the editor\r
102     2) change the value\r
103 \r
104     There are `ways around this`_, but it's not pretty.\r
105 \r
106     .. _ways around this:\r
107       http://wiki.wxpython.org/Change%20wxGrid%20CheckBox%20with%20one%20click\r
108     """\r
109     def __init__(self, **kwargs):\r
110         assert 'type' not in kwargs, kwargs\r
111         if 'default' not in kwargs:\r
112             kwargs['default'] = True\r
113         super(BoolProperty, self).__init__(type='bool', **kwargs)\r
114 \r
115     def get_editor(self):\r
116         return wx.grid.GridCellBoolEditor()\r
117 \r
118     def get_renderer(self):\r
119         return wx.grid.GridCellBoolRenderer()\r
120 \r
121     def string_for_value(self, value):\r
122         if value == True:\r
123             return '1'\r
124         return ''\r
125 \r
126     def value_for_string(self, string):\r
127         return string == '1'\r
128 \r
129 \r
130 class IntProperty (Property):\r
131     def __init__(self, **kwargs):\r
132         assert 'type' not in kwargs, kwargs\r
133         if 'default' not in kwargs:\r
134             kwargs['default'] = 0\r
135         super(IntProperty, self).__init__(type='int', **kwargs)\r
136 \r
137     def get_editor(self):\r
138         return wx.grid.GridCellNumberEditor()\r
139 \r
140     def get_renderer(self):\r
141         return wx.grid.GridCellNumberRenderer()\r
142 \r
143     def value_for_string(self, string):\r
144         return int(string)\r
145 \r
146 \r
147 class FloatProperty (Property):\r
148     def __init__(self, **kwargs):\r
149         assert 'type' not in kwargs, kwargs\r
150         if 'default' not in kwargs:\r
151             kwargs['default'] = 0.0\r
152         super(FloatProperty, self).__init__(type='float', **kwargs)\r
153 \r
154     def get_editor(self):\r
155         return wx.grid.GridCellFloatEditor()\r
156 \r
157     def get_renderer(self):\r
158         return wx.grid.GridCellFloatRenderer()\r
159 \r
160     def value_for_string(self, string):\r
161         return float(string)\r
162 \r
163 \r
164 class ChoiceProperty (Property):\r
165     def __init__(self, choices, **kwargs):\r
166         assert 'type' not in kwargs, kwargs\r
167         if 'default' not in kwargs:\r
168             kwargs['default'] = choices[0]\r
169         super(ChoiceProperty, self).__init__(type='choice', **kwargs)\r
170         self._choices = choices\r
171 \r
172     def get_editor(self):\r
173         return wx.grid.GridCellChoiceEditor(choices=self._choices)\r
174 \r
175     def get_renderer(self):\r
176         return None\r
177         #return wx.grid.GridCellChoiceRenderer()\r
178 \r
179 class PathProperty (StringProperty):\r
180     """Simple file or path property.\r
181 \r
182     Currently there isn't a fancy file-picker popup.  Perhaps in the\r
183     future.\r
184     """\r
185     def __init__(self, **kwargs):\r
186         super(PathProperty, self).__init__(**kwargs)\r
187         self.type = 'path'\r
188 \r
189 \r
190 class PropertyPanel(Panel, wx.grid.Grid):\r
191     """UI to view/set config values and command argsuments.\r
192     """\r
193     def __init__(self, callbacks=None, **kwargs):\r
194         super(PropertyPanel, self).__init__(\r
195             name='propertyeditor', callbacks=callbacks, **kwargs)\r
196         self._properties = []\r
197 \r
198         self.CreateGrid(numRows=0, numCols=1)\r
199         self.SetColLabelValue(0, 'value')\r
200 \r
201         self._last_tooltip = None\r
202         self.GetGridWindow().Bind(wx.EVT_MOTION, self._on_mouse_over)\r
203 \r
204     def _on_mouse_over(self, event):\r
205         """Enable tooltips.\r
206         """\r
207         x,y = self.CalcUnscrolledPosition(event.GetPosition())\r
208         col,row = self.XYToCell(x, y)\r
209         if col == -1 or row == -1:\r
210             msg = ''\r
211         else:\r
212             msg = self._properties[row].help or ''\r
213         if msg != self._last_tooltip:\r
214             self._last_tooltip = msg\r
215             event.GetEventObject().SetToolTipString(msg)\r
216 \r
217     def append_property(self, property):\r
218         if len([p for p in self._properties if p.label == property.label]) > 0:\r
219             raise ValueError(property)  # property.label collision\r
220         self._properties.append(property)\r
221         row = len(self._properties) - 1\r
222         self.AppendRows(numRows=1)\r
223         self.SetRowLabelValue(row, property.label)\r
224         self.SetCellEditor(row=row, col=0, editor=property.get_editor())\r
225         r = property.get_renderer()\r
226         if r != None:\r
227             self.SetCellRenderer(row=row, col=0, renderer=r)\r
228         self.set_property(property.label, property.default)\r
229 \r
230     def remove_property(self, label):\r
231         row,property = self._property_by_label(label)\r
232         self._properties.pop(row)\r
233         self.DeleteRows(pos=row)\r
234 \r
235     def clear(self):\r
236         while(len(self._properties) > 0):\r
237             self.remove_property(self._properties[-1].label)\r
238 \r
239     def set_property(self, label, value):\r
240         row,property = self._property_by_label(label)\r
241         self.SetCellValue(row=row, col=0, s=property.string_for_value(value))\r
242 \r
243     def get_property(self, label):\r
244         row,property = self._property_by_label(label)\r
245         string = self.GetCellValue(row=row, col=0)\r
246         return property.value_for_string(string)\r
247 \r
248     def _property_by_label(self, label):\r
249         props = [(i,p) for i,p in enumerate(self._properties)\r
250                  if p.label == label]\r
251         assert len(props) == 1, props\r
252         row,property = props[0]\r
253         return (row, property)\r