edf0968ab8ace51886c548f0530683874dbd51d7
[hooke.git] / hooke / ui / gui / panel / propertyeditor-propgrid.py
1 # Copyright\r
2 \r
3 """Property editor panel for Hooke.\r
4 """\r
5 \r
6 import sys\r
7 import os.path\r
8 \r
9 import wx\r
10 import wx.propgrid as wxpg\r
11 \r
12 # There are many comments and code fragments in here from the demo app.\r
13 # They should come in handy to expand the functionality in the future.\r
14 \r
15 class Display (object):\r
16     property_descriptor = []\r
17     def __init__(self):\r
18         pass\r
19 \r
20 class ValueObject (object):\r
21     def __init__(self):\r
22         pass\r
23 \r
24 \r
25 class IntProperty2 (wxpg.PyProperty):\r
26     """This is a simple re-implementation of wxIntProperty.\r
27     """\r
28     def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=0):\r
29         wxpg.PyProperty.__init__(self, label, name)\r
30         self.SetValue(value)\r
31 \r
32     def GetClassName(self):\r
33         return "IntProperty2"\r
34 \r
35     def GetEditor(self):\r
36         return "TextCtrl"\r
37 \r
38     def GetValueAsString(self, flags):\r
39         return str(self.GetValue())\r
40 \r
41     def PyStringToValue(self, s, flags):\r
42         try:\r
43             v = int(s)\r
44             if self.GetValue() != v:\r
45                 return v\r
46         except TypeError:\r
47             if flags & wxpg.PG_REPORT_ERROR:\r
48                 wx.MessageBox("Cannot convert '%s' into a number."%s, "Error")\r
49         return False\r
50 \r
51     def PyIntToValue(self, v, flags):\r
52         if (self.GetValue() != v):\r
53             return v\r
54 \r
55 \r
56 class PyFilesProperty(wxpg.PyArrayStringProperty):\r
57     def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=[]):\r
58         wxpg.PyArrayStringProperty.__init__(self, label, name, value)\r
59         self.SetValue(value)\r
60 \r
61     def OnSetValue(self, v):\r
62         self.value = v\r
63         self.display = ', '.join(self.value)\r
64 \r
65     def GetValueAsString(self, argFlags):\r
66         return self.display\r
67 \r
68     def PyStringToValue(self, s, flags):\r
69         return [a.strip() for a in s.split(',')]\r
70 \r
71     def OnEvent(self, propgrid, ctrl, event):\r
72         if event.GetEventType() == wx.wxEVT_COMMAND_BUTTON_CLICKED:\r
73             # Show dialog to select a string, call DoSetValue and\r
74             # return True, if value changed.\r
75             return True\r
76 \r
77         return False\r
78 \r
79 \r
80 class PyObjectPropertyValue:\r
81     """\\r
82     Value type of our sample PyObjectProperty. We keep a simple dash-delimited\r
83     list of string given as argument to constructor.\r
84     """\r
85     def __init__(self, s=None):\r
86         try:\r
87             self.ls = [a.strip() for a in s.split('-')]\r
88         except:\r
89             self.ls = []\r
90 \r
91     def __repr__(self):\r
92         return ' - '.join(self.ls)\r
93 \r
94 \r
95 class PyObjectProperty(wxpg.PyProperty):\r
96     """\\r
97     Another simple example. This time our value is a PyObject (NOTE: we can't\r
98     return an arbitrary python object in DoGetValue. It cannot be a simple\r
99     type such as int, bool, double, or string, nor an array or wxObject based.\r
100     Dictionary, None, or any user-specified Python object is allowed).\r
101     """\r
102     def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=None):\r
103         wxpg.PyProperty.__init__(self, label, name)\r
104         self.SetValue(value)\r
105 \r
106     def GetClassName(self):\r
107         return self.__class__.__name__\r
108 \r
109     def GetEditor(self):\r
110         return "TextCtrl"\r
111 \r
112     def GetValueAsString(self, flags):\r
113         return repr(self.GetValue())\r
114 \r
115     def PyStringToValue(self, s, flags):\r
116         return PyObjectPropertyValue(s)\r
117 \r
118 \r
119 class ShapeProperty(wxpg.PyEnumProperty):\r
120     """\\r
121     Demonstrates use of OnCustomPaint method.\r
122     """\r
123     def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=-1):\r
124         wxpg.PyEnumProperty.__init__(self, label, name, ['Line','Circle','Rectangle'], [0,1,2], value)\r
125 \r
126     def OnMeasureImage(self, index):\r
127         return wxpg.DEFAULT_IMAGE_SIZE\r
128 \r
129     def OnCustomPaint(self, dc, rect, paint_data):\r
130         """\\r
131         paint_data.m_choiceItem is -1 if we are painting the control,\r
132         in which case we need to get the drawn item using DoGetValue.\r
133         """\r
134         item = paint_data.m_choiceItem\r
135         if item == -1:\r
136             item = self.DoGetValue()\r
137 \r
138         dc.SetPen(wx.Pen(wx.BLACK))\r
139         dc.SetBrush(wx.Brush(wx.BLACK))\r
140 \r
141         if item == 0:\r
142             dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height)\r
143         elif item == 1:\r
144             half_width = rect.width / 2\r
145             dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3)\r
146         elif item == 2:\r
147             dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)\r
148 \r
149 \r
150 class LargeImagePickerCtrl(wx.Window):\r
151     """\\r
152     Control created and used by LargeImageEditor.\r
153     """\r
154     def __init__(self):\r
155         pre = wx.PreWindow()\r
156         self.PostCreate(pre)\r
157 \r
158     def Create(self, parent, id_, pos, size, style = 0):\r
159         wx.Window.Create(self, parent, id_, pos, size, style | wx.BORDER_SIMPLE)\r
160         img_spc = size[1]\r
161         self.tc = wx.TextCtrl(self, -1, "", (img_spc,0), (2048,size[1]), wx.BORDER_NONE)\r
162         self.SetBackgroundColour(wx.WHITE)\r
163         self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)\r
164         self.property = None\r
165         self.bmp = None\r
166         self.Bind(wx.EVT_PAINT, self.OnPaint)\r
167 \r
168     def OnPaint(self, event):\r
169         dc = wx.BufferedPaintDC(self)\r
170 \r
171         whiteBrush = wx.Brush(wx.WHITE)\r
172         dc.SetBackground(whiteBrush)\r
173         dc.Clear()\r
174 \r
175         bmp = self.bmp\r
176         if bmp:\r
177             dc.DrawBitmap(bmp, 2, 2)\r
178         else:\r
179             dc.SetPen(wx.Pen(wx.BLACK))\r
180             dc.SetBrush(whiteBrush)\r
181             dc.DrawRectangle(2, 2, 64, 64)\r
182 \r
183     def RefreshThumbnail(self):\r
184         """\\r
185         We use here very simple image scaling code.\r
186         """\r
187         if not self.property:\r
188             self.bmp = None\r
189             return\r
190 \r
191         path = self.property.DoGetValue()\r
192 \r
193         if not os.path.isfile(path):\r
194             self.bmp = None\r
195             return\r
196 \r
197         image = wx.Image(path)\r
198         image.Rescale(64, 64)\r
199         self.bmp = wx.BitmapFromImage(image)\r
200 \r
201     def SetProperty(self, property):\r
202         self.property = property\r
203         self.tc.SetValue(property.GetDisplayedString())\r
204         self.RefreshThumbnail()\r
205 \r
206     def SetValue(self, s):\r
207         self.RefreshThumbnail()\r
208         self.tc.SetValue(s)\r
209 \r
210     def GetLastPosition(self):\r
211         return self.tc.GetLastPosition()\r
212 \r
213 \r
214 class LargeImageEditor(wxpg.PyEditor):\r
215     """\\r
216     Double-height text-editor with image in front.\r
217     """\r
218     def __init__(self):\r
219         wxpg.PyEditor.__init__(self)\r
220 \r
221     def CreateControls(self, propgrid, property, pos, sz):\r
222         try:\r
223             h = 64 + 6\r
224             x = propgrid.GetSplitterPosition()\r
225             x2 = propgrid.GetClientSize().x\r
226             bw = propgrid.GetRowHeight()\r
227             lipc = LargeImagePickerCtrl()\r
228             if sys.platform == 'win32':\r
229                 lipc.Hide()\r
230             lipc.Create(propgrid, wxpg.PG_SUBID1, (x,pos[1]), (x2-x-bw,h))\r
231             lipc.SetProperty(property)\r
232             # Hmmm.. how to have two-stage creation without subclassing?\r
233             #btn = wx.PreButton()\r
234             #pre = wx.PreWindow()\r
235             #self.PostCreate(pre)\r
236             #if sys.platform == 'win32':\r
237             #    btn.Hide()\r
238             #btn.Create(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
239             btn = wx.Button(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
240             return (lipc, btn)\r
241         except:\r
242             import traceback\r
243             print traceback.print_exc()\r
244 \r
245     def UpdateControl(self, property, ctrl):\r
246         ctrl.SetValue(property.GetDisplayedString())\r
247 \r
248     def DrawValue(self, dc, property, rect):\r
249         if not (property.GetFlags() & wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
250             dc.DrawText( property.GetDisplayedString(), rect.x+5, rect.y );\r
251 \r
252     def OnEvent(self, propgrid, ctrl, event):\r
253         if not ctrl:\r
254             return False\r
255 \r
256         evtType = event.GetEventType()\r
257 \r
258         if evtType == wx.wxEVT_COMMAND_TEXT_ENTER:\r
259             if propgrid.IsEditorsValueModified():\r
260                 return True\r
261 \r
262         elif evtType == wx.wxEVT_COMMAND_TEXT_UPDATED:\r
263             if not property.HasFlag(wxpg.PG_PROP_AUTO_UNSPECIFIED) or not ctrl or \\r
264                ctrl.GetLastPosition() > 0:\r
265 \r
266                 # We must check this since an 'empty' text event\r
267                 # may be triggered when creating the property.\r
268                 PG_FL_IN_SELECT_PROPERTY = 0x00100000\r
269                 if not (propgrid.GetInternalFlags() & PG_FL_IN_SELECT_PROPERTY):\r
270                     event.Skip();\r
271                     event.SetId(propgrid.GetId());\r
272 \r
273                 propgrid.EditorsValueWasModified();\r
274 \r
275         return False\r
276 \r
277 \r
278     def CopyValueFromControl(self, property, ctrl):\r
279         tc = ctrl.tc\r
280         res = property.SetValueFromString(tc.GetValue(),0)\r
281         # Changing unspecified always causes event (returning\r
282         # true here should be enough to trigger it).\r
283         if not res and property.IsFlagSet(wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
284             res = True\r
285 \r
286         return res\r
287 \r
288     def SetValueToUnspecified(self, ctrl):\r
289         ctrl.tc.Remove(0,len(ctrl.tc.GetValue()));\r
290 \r
291     def SetControlStringValue(self, ctrl, txt):\r
292         ctrl.SetValue(txt)\r
293 \r
294     def OnFocus(self, property, ctrl):\r
295         ctrl.tc.SetSelection(-1,-1)\r
296         ctrl.tc.SetFocus()\r
297 \r
298 \r
299 class PropertyEditor(wx.Panel):\r
300 \r
301     def __init__(self, parent):\r
302         # Use the WANTS_CHARS style so the panel doesn't eat the Return key.\r
303         wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS, size=(160, 200))\r
304 \r
305         sizer = wx.BoxSizer(wx.VERTICAL)\r
306 \r
307         self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT)\r
308 \r
309         # Show help as tooltips\r
310         self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)\r
311 \r
312         #pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChange)\r
313         #pg.Bind(wxpg.EVT_PG_SELECTED, self.OnPropGridSelect)\r
314         #self.pg.Bind(wxpg.EVT_PG_RIGHT_CLICK, self.OnPropGridRightClick)\r
315 \r
316         # Needed by custom image editor\r
317         wx.InitAllImageHandlers()\r
318 \r
319         #\r
320         # Let's create a simple custom editor\r
321         #\r
322         # NOTE: Editor must be registered *before* adding a property that uses it.\r
323         self.pg.RegisterEditor(LargeImageEditor)\r
324 \r
325         '''\r
326         #\r
327         # Add properties\r
328         #\r
329 \r
330         pg.Append( wxpg.PropertyCategory("1 - Basic Properties") )\r
331         pg.Append( wxpg.StringProperty("String",value="Some Text") )\r
332         pg.Append( wxpg.IntProperty("Int",value=100) )\r
333         pg.Append( wxpg.FloatProperty("Float",value=100.0) )\r
334         pg.Append( wxpg.BoolProperty("Bool",value=True) )\r
335         pg.Append( wxpg.BoolProperty("Bool_with_Checkbox",value=True) )\r
336         pg.SetPropertyAttribute("Bool_with_Checkbox", "UseCheckbox", True)\r
337 \r
338         pg.Append( wxpg.PropertyCategory("2 - More Properties") )\r
339         pg.Append( wxpg.LongStringProperty("LongString",value="This is a\\nmulti-line string\\nwith\\ttabs\\nmixed\\tin.") )\r
340         pg.Append( wxpg.DirProperty("Dir",value="C:\\Windows") )\r
341         pg.Append( wxpg.FileProperty("File",value="C:\\Windows\\system.ini") )\r
342         pg.Append( wxpg.ArrayStringProperty("ArrayString",value=['A','B','C']) )\r
343 \r
344         pg.Append( wxpg.EnumProperty("Enum","Enum",\r
345                                      ['wxPython Rules','wxPython Rocks','wxPython Is The Best'],\r
346                                      [10,11,12],0) )\r
347         pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") )\r
348 \r
349         pg.Append( wxpg.PropertyCategory("3 - Advanced Properties") )\r
350         pg.Append( wxpg.DateProperty("Date",value=wx.DateTime_Now()) )\r
351         pg.Append( wxpg.FontProperty("Font",value=self.GetFont()) )\r
352         pg.Append( wxpg.ColourProperty("Colour",value=self.GetBackgroundColour()) )\r
353         pg.Append( wxpg.SystemColourProperty("SystemColour") )\r
354         pg.Append( wxpg.ImageFileProperty("ImageFile") )\r
355         pg.Append( wxpg.MultiChoiceProperty("MultiChoice",choices=['wxWidgets','QT','GTK+']) )\r
356 \r
357         pg.Append( wxpg.PropertyCategory("4 - Additional Properties") )\r
358         pg.Append( wxpg.PointProperty("Point",value=self.GetPosition()) )\r
359         pg.Append( wxpg.SizeProperty("Size",value=self.GetSize()) )\r
360         pg.Append( wxpg.FontDataProperty("FontData") )\r
361         pg.Append( wxpg.IntProperty("IntWithSpin",value=256) )\r
362         pg.SetPropertyEditor("IntWithSpin","SpinCtrl")\r
363         pg.Append( wxpg.DirsProperty("Dirs",value=['C:/Lib','C:/Bin']) )\r
364         pg.SetPropertyHelpString( "String", "String Property help string!" )\r
365         pg.SetPropertyHelpString( "Dirs", "Dirs Property help string!" )\r
366 \r
367         pg.SetPropertyAttribute( "File", wxpg.PG_FILE_SHOW_FULL_PATH, 0 )\r
368         pg.SetPropertyAttribute( "File", wxpg.PG_FILE_INITIAL_PATH, "C:\\Program Files\\Internet Explorer" )\r
369         pg.SetPropertyAttribute( "Date", wxpg.PG_DATE_PICKER_STYLE, wx.DP_DROPDOWN|wx.DP_SHOWCENTURY )\r
370 \r
371         pg.Append( wxpg.PropertyCategory("5 - Custom Properties") )\r
372         pg.Append( IntProperty2("IntProperty2", value=1024) )\r
373 \r
374         pg.Append( ShapeProperty("ShapeProperty", value=0) )\r
375         pg.Append( PyObjectProperty("PyObjectProperty") )\r
376 \r
377         pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") )\r
378         pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor")\r
379 \r
380 \r
381         pg.SetPropertyClientData( "Point", 1234 )\r
382         if pg.GetPropertyClientData( "Point" ) != 1234:\r
383             raise ValueError("Set/GetPropertyClientData() failed")\r
384 \r
385         # Test setting unicode string\r
386         pg.GetPropertyByName("String").SetValue(u"Some Unicode Text")\r
387 \r
388         #\r
389         # Test some code that *should* fail (but not crash)\r
390         #try:\r
391             #a_ = pg.GetPropertyValue( "NotARealProperty" )\r
392             #pg.EnableProperty( "NotAtAllRealProperty", False )\r
393             #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" )\r
394         #except:\r
395             #pass\r
396             #raise\r
397 \r
398         '''\r
399         sizer.Add(self.pg, 1, wx.EXPAND)\r
400         self.SetSizer(sizer)\r
401         sizer.SetSizeHints(self)\r
402 \r
403         self.SelectedTreeItem = None\r
404 \r
405     def GetPropertyValues(self):\r
406         return self.pg.GetPropertyValues()\r
407 \r
408     def Initialize(self, properties):\r
409         pg = self.pg\r
410         pg.Clear()\r
411 \r
412         if properties:\r
413             for element in properties:\r
414                 if element[1]['type'] == 'arraystring':\r
415                     elements = element[1]['elements']\r
416                     if 'value' in element[1]:\r
417                         property_value = element[1]['value']\r
418                     else:\r
419                         property_value = element[1]['default']\r
420                     #retrieve individual strings\r
421                     property_value = split(property_value, ' ')\r
422                     #remove " delimiters\r
423                     values = [value.strip('"') for value in property_value]\r
424                     pg.Append(wxpg.ArrayStringProperty(element[0], value=values))\r
425 \r
426                 if element[1]['type'] == 'boolean':\r
427                     if 'value' in element[1]:\r
428                         property_value = element[1].as_bool('value')\r
429                     else:\r
430                         property_value = element[1].as_bool('default')\r
431                     property_control = wxpg.BoolProperty(element[0], value=property_value)\r
432                     pg.Append(property_control)\r
433                     pg.SetPropertyAttribute(element[0], 'UseCheckbox', True)\r
434 \r
435                 #if element[0] == 'category':\r
436                     #pg.Append(wxpg.PropertyCategory(element[1]))\r
437 \r
438                 if element[1]['type'] == 'color':\r
439                     if 'value' in element[1]:\r
440                         property_value = element[1]['value']\r
441                     else:\r
442                         property_value = element[1]['default']\r
443                     property_value = eval(property_value)\r
444                     pg.Append(wxpg.ColourProperty(element[0], value=property_value))\r
445 \r
446                 if element[1]['type'] == 'enum':\r
447                     elements = element[1]['elements']\r
448                     if 'value' in element[1]:\r
449                         property_value = element[1]['value']\r
450                     else:\r
451                         property_value = element[1]['default']\r
452                     pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value)))\r
453 \r
454                 if element[1]['type'] == 'filename':\r
455                     if 'value' in element[1]:\r
456                         property_value = element[1]['value']\r
457                     else:\r
458                         property_value = element[1]['default']\r
459                     pg.Append(wxpg.FileProperty(element[0], value=property_value))\r
460 \r
461                 if element[1]['type'] == 'float':\r
462                     if 'value' in element[1]:\r
463                         property_value = element[1].as_float('value')\r
464                     else:\r
465                         property_value = element[1].as_float('default')\r
466                     property_control = wxpg.FloatProperty(element[0], value=property_value)\r
467                     pg.Append(property_control)\r
468 \r
469                 if element[1]['type'] == 'folder':\r
470                     if 'value' in element[1]:\r
471                         property_value = element[1]['value']\r
472                     else:\r
473                         property_value = element[1]['default']\r
474                     pg.Append(wxpg.DirProperty(element[0], value=property_value))\r
475 \r
476                 if element[1]['type'] == 'integer':\r
477                     if 'value' in element[1]:\r
478                         property_value = element[1].as_int('value')\r
479                     else:\r
480                         property_value = element[1].as_int('default')\r
481                     property_control = wxpg.IntProperty(element[0], value=property_value)\r
482                     if 'maximum' in element[1]:\r
483                         property_control.SetAttribute('Max', element[1].as_int('maximum'))\r
484                     if 'minimum' in element[1]:\r
485                         property_control.SetAttribute('Min', element[1].as_int('minimum'))\r
486                     property_control.SetAttribute('Wrap', True)\r
487                     pg.Append(property_control)\r
488                     pg.SetPropertyEditor(element[0], 'SpinCtrl')\r
489 \r
490                 if element[1]['type'] == 'string':\r
491                     if 'value' in element[1]:\r
492                         property_value = element[1]['value']\r
493                     else:\r
494                         property_value = element[1]['default']\r
495                     pg.Append(wxpg.StringProperty(element[0], value=property_value))\r
496 \r
497         pg.Refresh()\r
498 \r
499     def OnReserved(self, event):\r
500         pass\r