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