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