1 # Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
2 # W. Trevor King <wking@drexel.edu>
4 # This file is part of Hooke.
6 # Hooke is free software: you can redistribute it and/or modify it
7 # under the terms of the GNU Lesser General Public License as
8 # published by the Free Software Foundation, either version 3 of the
9 # License, or (at your option) any later version.
11 # Hooke is distributed in the hope that it will be useful, but WITHOUT
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14 # Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with Hooke. If not, see
18 # <http://www.gnu.org/licenses/>.
20 """Property editor panel for Hooke.
22 wxPropertyGrid is `included in wxPython >= 2.9.1 <included>`_. Until
23 then, we'll avoid it because of the *nix build problems.
25 This module hacks together a workaround to be used until 2.9.1 is
26 widely installed (or at least released ;).
29 http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download
35 from ....plugin import argument_to_setting
36 from ....util.convert import ANALOGS
39 def props_from_argument(argument, curves=None, playlists=None):
40 """Convert a :class:`~hooke.command.Argument` to a list of
44 if type in ['driver']: # intentionally not handled (yet)
46 if argument.count == -1:
47 raise NotImplementedError(argument) # TODO: maybe just set count to 1?
49 #'label':argument.name,
50 'default':argument.default,
51 'help':argument.help(),
53 type = ANALOGS.get(type, type) # type consolidation
55 if type in ['string', 'bool', 'int', 'float', 'path']:
56 _class = globals()['%sProperty' % type.capitalize()]
57 elif type in ['curve', 'playlist']:
59 choices = curves # extracted from the current playlist
63 _class = ChoiceProperty
64 kwargs['choices'] = choices
66 raise NotImplementedError(argument.type)
67 labels = ['%s %d' % (argument.name, i) for i in range(argument.count)]
68 return [(label, _class(label=label, **kwargs)) for label in labels]
71 def props_from_setting(setting):
72 """Convert a :class:`~hooke.config.Setting` to a list of
75 # TODO: move props_from_argument code here and use
76 # argument_to_setting there.
77 raise NotImplementedError()
80 class Property (object):
81 def __init__(self, type, label, default, help=None):
84 self.default = default
88 """Return a suitable grid editor.
90 raise NotImplementedError()
92 def get_renderer(self):
93 """Return a suitable grid renderer.
95 Returns `None` if no special renderer is required.
99 def string_for_value(self, value):
100 """Return a string representation of `value` for loading the table.
104 def value_for_string(self, string):
105 """Return the value represented by `string`.
110 class StringProperty (Property):
111 def __init__(self, **kwargs):
112 assert 'type' not in kwargs, kwargs
113 if 'default' not in kwargs:
114 kwargs['default'] = 0
115 super(StringProperty, self).__init__(type='string', **kwargs)
117 def get_editor(self):
118 return wx.grid.GridCellTextEditor()
120 def get_renderer(self):
121 return wx.grid.GridCellStringRenderer()
124 class BoolProperty (Property):
125 """A boolean property.
129 Unfortunately, changing a boolean property takes two clicks:
134 There are `ways around this`_, but it's not pretty.
136 .. _ways around this:
137 http://wiki.wxpython.org/Change%20wxGrid%20CheckBox%20with%20one%20click
139 def __init__(self, **kwargs):
140 assert 'type' not in kwargs, kwargs
141 if 'default' not in kwargs:
142 kwargs['default'] = True
143 super(BoolProperty, self).__init__(type='bool', **kwargs)
145 def get_editor(self):
146 return wx.grid.GridCellBoolEditor()
148 def get_renderer(self):
149 return wx.grid.GridCellBoolRenderer()
151 def string_for_value(self, value):
156 def value_for_string(self, string):
160 class IntProperty (Property):
161 def __init__(self, **kwargs):
162 assert 'type' not in kwargs, kwargs
163 if 'default' not in kwargs:
164 kwargs['default'] = 0
165 super(IntProperty, self).__init__(type='int', **kwargs)
167 def get_editor(self):
168 return wx.grid.GridCellNumberEditor()
170 def get_renderer(self):
171 return wx.grid.GridCellNumberRenderer()
173 def value_for_string(self, string):
177 class FloatProperty (Property):
178 def __init__(self, **kwargs):
179 assert 'type' not in kwargs, kwargs
180 if 'default' not in kwargs:
181 kwargs['default'] = 0.0
182 super(FloatProperty, self).__init__(type='float', **kwargs)
184 def get_editor(self):
185 return wx.grid.GridCellFloatEditor()
187 def get_renderer(self):
188 return wx.grid.GridCellFloatRenderer()
190 def value_for_string(self, string):
194 class ChoiceProperty (Property):
195 def __init__(self, choices, **kwargs):
196 assert 'type' not in kwargs, kwargs
197 if 'default' in kwargs:
198 if kwargs['default'] not in choices:
199 choices.insert(0, kwargs['default'])
201 kwargs['default'] = choices[0]
202 super(ChoiceProperty, self).__init__(type='choice', **kwargs)
203 self._choices = choices
205 def get_editor(self):
206 choices = [self.string_for_value(c) for c in self._choices]
207 return wx.grid.GridCellChoiceEditor(choices=choices)
209 def get_renderer(self):
211 #return wx.grid.GridCellChoiceRenderer()
213 def string_for_value(self, value):
214 if hasattr(value, 'name'):
218 def value_for_string(self, string):
219 for choice in self._choices:
220 if self.string_for_value(choice) == string:
222 raise ValueError(string)
225 class PathProperty (StringProperty):
226 """Simple file or path property.
228 Currently there isn't a fancy file-picker popup. Perhaps in the
231 def __init__(self, **kwargs):
232 super(PathProperty, self).__init__(**kwargs)
236 class PropertyPanel(Panel, wx.grid.Grid):
237 """UI to view/set config values and command argsuments.
239 def __init__(self, callbacks=None, **kwargs):
240 super(PropertyPanel, self).__init__(
241 name='property editor', callbacks=callbacks, **kwargs)
242 self._properties = []
244 self.CreateGrid(numRows=0, numCols=1)
245 self.SetColLabelValue(0, 'value')
247 self._last_tooltip = None
248 self.GetGridWindow().Bind(wx.EVT_MOTION, self._on_motion)
250 def _on_motion(self, event):
253 x,y = self.CalcUnscrolledPosition(event.GetPosition())
254 row,col = self.XYToCell(x, y)
255 if col == -1 or row == -1:
258 msg = self._properties[row].help or ''
259 if msg != self._last_tooltip:
260 self._last_tooltip = msg
261 event.GetEventObject().SetToolTipString(msg)
263 def append_property(self, property):
264 if len([p for p in self._properties if p.label == property.label]) > 0:
265 raise ValueError(property) # property.label collision
266 self._properties.append(property)
267 row = len(self._properties) - 1
268 self.AppendRows(numRows=1)
269 self.SetRowLabelValue(row, property.label)
270 self.SetCellEditor(row=row, col=0, editor=property.get_editor())
271 r = property.get_renderer()
273 self.SetCellRenderer(row=row, col=0, renderer=r)
274 self.set_property(property.label, property.default)
276 def remove_property(self, label):
277 row,property = self._property_by_label(label)
278 self._properties.pop(row)
279 self.DeleteRows(pos=row)
282 while(len(self._properties) > 0):
283 self.remove_property(self._properties[-1].label)
285 def set_property(self, label, value):
286 row,property = self._property_by_label(label)
287 self.SetCellValue(row=row, col=0, s=property.string_for_value(value))
289 def get_property(self, label):
290 row,property = self._property_by_label(label)
291 string = self.GetCellValue(row=row, col=0)
292 return property.value_for_string(string)
294 def get_values(self):
295 return dict([(p.label, self.get_property(p.label))
296 for p in self._properties])
298 def _property_by_label(self, label):
299 props = [(i,p) for i,p in enumerate(self._properties)
301 assert len(props) == 1, props
302 row,property = props[0]
303 return (row, property)