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