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