3 """Property editor panel for Hooke.
\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
8 This module hacks together a workaround to be used until 2.9.1 is
\r
9 widely installed (or at least released ;).
\r
12 http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download
\r
20 def prop_from_argument(argument, curves=None, playlists=None):
\r
21 """Convert a :class:`~hooke.command.Argument` to a :class:`Property`.
\r
23 if argument.count != 1:
\r
24 raise NotImplementedError(argument)
\r
26 'label':argument.name,
\r
27 'default':argument.default,
\r
28 'help':argument.help,
\r
30 type = argument.type
\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
41 return ChoiceProperty(choices=[c.name for c in choices], **kwargs)
\r
42 raise NotImplementedError(argument.type)
\r
44 def prop_from_setting(setting):
\r
45 """Convert a :class:`~hooke.config.Setting` to a :class:`Property`.
\r
47 raise NotImplementedError()
\r
50 class Property (object):
\r
51 def __init__(self, type, label, default, help=None):
\r
54 self.default = default
\r
57 def get_editor(self):
\r
58 """Return a suitable grid editor.
\r
60 raise NotImplementedError()
\r
62 def get_renderer(self):
\r
63 """Return a suitable grid renderer.
\r
65 Returns `None` if no special renderer is required.
\r
69 def string_for_value(self, value):
\r
70 """Return a string representation of `value` for loading the table.
\r
74 def value_for_string(self, string):
\r
75 """Return the value represented by `string`.
\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
87 def get_editor(self):
\r
88 return wx.grid.GridCellTextEditor()
\r
90 def get_renderer(self):
\r
91 return wx.grid.GridCellStringRenderer()
\r
94 class BoolProperty (Property):
\r
95 """A boolean property.
\r
99 Unfortunately, changing a boolean property takes two clicks:
\r
101 1) create the editor
\r
102 2) change the value
\r
104 There are `ways around this`_, but it's not pretty.
\r
106 .. _ways around this:
\r
107 http://wiki.wxpython.org/Change%20wxGrid%20CheckBox%20with%20one%20click
\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
115 def get_editor(self):
\r
116 return wx.grid.GridCellBoolEditor()
\r
118 def get_renderer(self):
\r
119 return wx.grid.GridCellBoolRenderer()
\r
121 def string_for_value(self, value):
\r
126 def value_for_string(self, string):
\r
127 return string == '1'
\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
137 def get_editor(self):
\r
138 return wx.grid.GridCellNumberEditor()
\r
140 def get_renderer(self):
\r
141 return wx.grid.GridCellNumberRenderer()
\r
143 def value_for_string(self, string):
\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
154 def get_editor(self):
\r
155 return wx.grid.GridCellFloatEditor()
\r
157 def get_renderer(self):
\r
158 return wx.grid.GridCellFloatRenderer()
\r
160 def value_for_string(self, string):
\r
161 return float(string)
\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
172 def get_editor(self):
\r
173 return wx.grid.GridCellChoiceEditor(choices=self._choices)
\r
175 def get_renderer(self):
\r
177 #return wx.grid.GridCellChoiceRenderer()
\r
179 class PathProperty (StringProperty):
\r
180 """Simple file or path property.
\r
182 Currently there isn't a fancy file-picker popup. Perhaps in the
\r
185 def __init__(self, **kwargs):
\r
186 super(PathProperty, self).__init__(**kwargs)
\r
190 class PropertyPanel(Panel, wx.grid.Grid):
\r
191 """UI to view/set config values and command argsuments.
\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
198 self.CreateGrid(numRows=0, numCols=1)
\r
199 self.SetColLabelValue(0, 'value')
\r
201 self._last_tooltip = None
\r
202 self.GetGridWindow().Bind(wx.EVT_MOTION, self._on_mouse_over)
\r
204 def _on_mouse_over(self, event):
\r
205 """Enable tooltips.
\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
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
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
227 self.SetCellRenderer(row=row, col=0, renderer=r)
\r
228 self.set_property(property.label, property.default)
\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
236 while(len(self._properties) > 0):
\r
237 self.remove_property(self._properties[-1].label)
\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
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
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