1 #!/usr/bin/env python
\r
6 Property editor panel for Hooke.
\r
8 Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada)
\r
10 This program is released under the GNU General Public License version 2.
\r
17 import wx.propgrid as wxpg
\r
19 from string import split
\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
25 property_descriptor = []
\r
35 class IntProperty2(wxpg.PyProperty):
\r
37 This is a simple re-implementation of wxIntProperty.
\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
43 def GetClassName(self):
\r
45 This is not 100% necessary and in future is probably going to be
\r
46 automated to return class name.
\r
48 return "IntProperty2"
\r
50 def GetEditor(self):
\r
53 def GetValueAsString(self, flags):
\r
54 return str(self.GetValue())
\r
56 def PyStringToValue(self, s, flags):
\r
59 if self.GetValue() != v:
\r
62 if flags & wxpg.PG_REPORT_ERROR:
\r
63 wx.MessageBox("Cannot convert '%s' into a number."%s, "Error")
\r
66 def PyIntToValue(self, v, flags):
\r
67 if (self.GetValue() != v):
\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
76 def OnSetValue(self, v):
\r
78 self.display = ', '.join(self.value)
\r
80 def GetValueAsString(self, argFlags):
\r
83 def PyStringToValue(self, s, flags):
\r
84 return [a.strip() for a in s.split(',')]
\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
95 class PyObjectPropertyValue:
\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
100 def __init__(self, s=None):
\r
102 self.ls = [a.strip() for a in s.split('-')]
\r
106 def __repr__(self):
\r
107 return ' - '.join(self.ls)
\r
110 class PyObjectProperty(wxpg.PyProperty):
\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
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
121 def GetClassName(self):
\r
122 return self.__class__.__name__
\r
124 def GetEditor(self):
\r
127 def GetValueAsString(self, flags):
\r
128 return repr(self.GetValue())
\r
130 def PyStringToValue(self, s, flags):
\r
131 return PyObjectPropertyValue(s)
\r
134 class ShapeProperty(wxpg.PyEnumProperty):
\r
136 Demonstrates use of OnCustomPaint method.
\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
141 def OnMeasureImage(self, index):
\r
142 return wxpg.DEFAULT_IMAGE_SIZE
\r
144 def OnCustomPaint(self, dc, rect, paint_data):
\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
149 item = paint_data.m_choiceItem
\r
151 item = self.DoGetValue()
\r
153 dc.SetPen(wx.Pen(wx.BLACK))
\r
154 dc.SetBrush(wx.Brush(wx.BLACK))
\r
157 dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height)
\r
159 half_width = rect.width / 2
\r
160 dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3)
\r
162 dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)
\r
165 class LargeImagePickerCtrl(wx.Window):
\r
167 Control created and used by LargeImageEditor.
\r
169 def __init__(self):
\r
170 pre = wx.PreWindow()
\r
171 self.PostCreate(pre)
\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
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
181 self.Bind(wx.EVT_PAINT, self.OnPaint)
\r
183 def OnPaint(self, event):
\r
184 dc = wx.BufferedPaintDC(self)
\r
186 whiteBrush = wx.Brush(wx.WHITE)
\r
187 dc.SetBackground(whiteBrush)
\r
192 dc.DrawBitmap(bmp, 2, 2)
\r
194 dc.SetPen(wx.Pen(wx.BLACK))
\r
195 dc.SetBrush(whiteBrush)
\r
196 dc.DrawRectangle(2, 2, 64, 64)
\r
198 def RefreshThumbnail(self):
\r
200 We use here very simple image scaling code.
\r
202 if not self.property:
\r
206 path = self.property.DoGetValue()
\r
208 if not os.path.isfile(path):
\r
212 image = wx.Image(path)
\r
213 image.Rescale(64, 64)
\r
214 self.bmp = wx.BitmapFromImage(image)
\r
216 def SetProperty(self, property):
\r
217 self.property = property
\r
218 self.tc.SetValue(property.GetDisplayedString())
\r
219 self.RefreshThumbnail()
\r
221 def SetValue(self, s):
\r
222 self.RefreshThumbnail()
\r
223 self.tc.SetValue(s)
\r
225 def GetLastPosition(self):
\r
226 return self.tc.GetLastPosition()
\r
229 class LargeImageEditor(wxpg.PyEditor):
\r
231 Double-height text-editor with image in front.
\r
233 def __init__(self):
\r
234 wxpg.PyEditor.__init__(self)
\r
236 def CreateControls(self, propgrid, property, pos, sz):
\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
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
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
258 print traceback.print_exc()
\r
260 def UpdateControl(self, property, ctrl):
\r
261 ctrl.SetValue(property.GetDisplayedString())
\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
267 def OnEvent(self, propgrid, ctrl, event):
\r
271 evtType = event.GetEventType()
\r
273 if evtType == wx.wxEVT_COMMAND_TEXT_ENTER:
\r
274 if propgrid.IsEditorsValueModified():
\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
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
286 event.SetId(propgrid.GetId());
\r
288 propgrid.EditorsValueWasModified();
\r
293 def CopyValueFromControl(self, property, ctrl):
\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
303 def SetValueToUnspecified(self, ctrl):
\r
304 ctrl.tc.Remove(0,len(ctrl.tc.GetValue()));
\r
306 def SetControlStringValue(self, ctrl, txt):
\r
309 def OnFocus(self, property, ctrl):
\r
310 ctrl.tc.SetSelection(-1,-1)
\r
314 class PropertyEditor(wx.Panel):
\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
320 sizer = wx.BoxSizer(wx.VERTICAL)
\r
322 self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT)
\r
324 # Show help as tooltips
\r
325 self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)
\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
331 # Needed by custom image editor
\r
332 wx.InitAllImageHandlers()
\r
335 # Let's create a simple custom editor
\r
337 # NOTE: Editor must be registered *before* adding a property that uses it.
\r
338 self.pg.RegisterEditor(LargeImageEditor)
\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
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
359 pg.Append( wxpg.EnumProperty("Enum","Enum",
\r
360 ['wxPython Rules','wxPython Rocks','wxPython Is The Best'],
\r
362 pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") )
\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
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
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
386 pg.Append( wxpg.PropertyCategory("5 - Custom Properties") )
\r
387 pg.Append( IntProperty2("IntProperty2", value=1024) )
\r
389 pg.Append( ShapeProperty("ShapeProperty", value=0) )
\r
390 pg.Append( PyObjectProperty("PyObjectProperty") )
\r
392 pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") )
\r
393 pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor")
\r
396 pg.SetPropertyClientData( "Point", 1234 )
\r
397 if pg.GetPropertyClientData( "Point" ) != 1234:
\r
398 raise ValueError("Set/GetPropertyClientData() failed")
\r
400 # Test setting unicode string
\r
401 pg.GetPropertyByName("String").SetValue(u"Some Unicode Text")
\r
404 # Test some code that *should* fail (but not crash)
\r
406 #a_ = pg.GetPropertyValue( "NotARealProperty" )
\r
407 #pg.EnableProperty( "NotAtAllRealProperty", False )
\r
408 #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" )
\r
414 sizer.Add(self.pg, 1, wx.EXPAND)
\r
415 self.SetSizer(sizer)
\r
416 sizer.SetSizeHints(self)
\r
418 self.SelectedTreeItem = None
\r
420 def GetPropertyValues(self):
\r
421 return self.pg.GetPropertyValues()
\r
423 def Initialize(self, 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
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
441 if element[1]['type'] == 'boolean':
\r
442 if 'value' in element[1]:
\r
443 property_value = element[1].as_bool('value')
\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
450 #if element[0] == 'category':
\r
451 #pg.Append(wxpg.PropertyCategory(element[1]))
\r
453 if element[1]['type'] == 'color':
\r
454 if 'value' in element[1]:
\r
455 property_value = element[1]['value']
\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
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
466 property_value = element[1]['default']
\r
467 pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value)))
\r
469 if element[1]['type'] == 'filename':
\r
470 if 'value' in element[1]:
\r
471 property_value = element[1]['value']
\r
473 property_value = element[1]['default']
\r
474 pg.Append(wxpg.FileProperty(element[0], value=property_value))
\r
476 if element[1]['type'] == 'float':
\r
477 if 'value' in element[1]:
\r
478 property_value = element[1].as_float('value')
\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
484 if element[1]['type'] == 'folder':
\r
485 if 'value' in element[1]:
\r
486 property_value = element[1]['value']
\r
488 property_value = element[1]['default']
\r
489 pg.Append(wxpg.DirProperty(element[0], value=property_value))
\r
491 if element[1]['type'] == 'integer':
\r
492 if 'value' in element[1]:
\r
493 property_value = element[1].as_int('value')
\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
505 if element[1]['type'] == 'string':
\r
506 if 'value' in element[1]:
\r
507 property_value = element[1]['value']
\r
509 property_value = element[1]['default']
\r
510 pg.Append(wxpg.StringProperty(element[0], value=property_value))
\r
514 def OnReserved(self, event):
\r