1 # Copyright (C) 2010-2012 W. Trevor King <wking@drexel.edu>
3 # This file is part of Hooke.
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
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
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/>.
18 """Property editor panel for Hooke.
25 import wx.propgrid as wxpg
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.
30 class Display (object):
31 property_descriptor = []
35 class ValueObject (object):
40 class IntProperty2 (wxpg.PyProperty):
41 """This is a simple re-implementation of wxIntProperty.
43 def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=0):
44 wxpg.PyProperty.__init__(self, label, name)
47 def GetClassName(self):
53 def GetValueAsString(self, flags):
54 return str(self.GetValue())
56 def PyStringToValue(self, s, flags):
59 if self.GetValue() != v:
62 if flags & wxpg.PG_REPORT_ERROR:
63 wx.MessageBox("Cannot convert '%s' into a number."%s, "Error")
66 def PyIntToValue(self, v, flags):
67 if (self.GetValue() != v):
71 class PyFilesProperty(wxpg.PyArrayStringProperty):
72 def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=[]):
73 wxpg.PyArrayStringProperty.__init__(self, label, name, value)
76 def OnSetValue(self, v):
78 self.display = ', '.join(self.value)
80 def GetValueAsString(self, argFlags):
83 def PyStringToValue(self, s, flags):
84 return [a.strip() for a in s.split(',')]
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.
95 class PyObjectPropertyValue:
97 Value type of our sample PyObjectProperty. We keep a simple dash-delimited
98 list of string given as argument to constructor.
100 def __init__(self, s=None):
102 self.ls = [a.strip() for a in s.split('-')]
107 return ' - '.join(self.ls)
110 class PyObjectProperty(wxpg.PyProperty):
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).
117 def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=None):
118 wxpg.PyProperty.__init__(self, label, name)
121 def GetClassName(self):
122 return self.__class__.__name__
127 def GetValueAsString(self, flags):
128 return repr(self.GetValue())
130 def PyStringToValue(self, s, flags):
131 return PyObjectPropertyValue(s)
134 class ShapeProperty(wxpg.PyEnumProperty):
136 Demonstrates use of OnCustomPaint method.
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)
141 def OnMeasureImage(self, index):
142 return wxpg.DEFAULT_IMAGE_SIZE
144 def OnCustomPaint(self, dc, rect, paint_data):
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.
149 item = paint_data.m_choiceItem
151 item = self.DoGetValue()
153 dc.SetPen(wx.Pen(wx.BLACK))
154 dc.SetBrush(wx.Brush(wx.BLACK))
157 dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height)
159 half_width = rect.width / 2
160 dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3)
162 dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)
165 class LargeImagePickerCtrl(wx.Window):
167 Control created and used by LargeImageEditor.
173 def Create(self, parent, id_, pos, size, style = 0):
174 wx.Window.Create(self, parent, id_, pos, size, style | wx.BORDER_SIMPLE)
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)
181 self.Bind(wx.EVT_PAINT, self.OnPaint)
183 def OnPaint(self, event):
184 dc = wx.BufferedPaintDC(self)
186 whiteBrush = wx.Brush(wx.WHITE)
187 dc.SetBackground(whiteBrush)
192 dc.DrawBitmap(bmp, 2, 2)
194 dc.SetPen(wx.Pen(wx.BLACK))
195 dc.SetBrush(whiteBrush)
196 dc.DrawRectangle(2, 2, 64, 64)
198 def RefreshThumbnail(self):
200 We use here very simple image scaling code.
202 if not self.property:
206 path = self.property.DoGetValue()
208 if not os.path.isfile(path):
212 image = wx.Image(path)
213 image.Rescale(64, 64)
214 self.bmp = wx.BitmapFromImage(image)
216 def SetProperty(self, property):
217 self.property = property
218 self.tc.SetValue(property.GetDisplayedString())
219 self.RefreshThumbnail()
221 def SetValue(self, s):
222 self.RefreshThumbnail()
225 def GetLastPosition(self):
226 return self.tc.GetLastPosition()
229 class LargeImageEditor(wxpg.PyEditor):
231 Double-height text-editor with image in front.
234 wxpg.PyEditor.__init__(self)
236 def CreateControls(self, propgrid, property, pos, sz):
239 x = propgrid.GetSplitterPosition()
240 x2 = propgrid.GetClientSize().x
241 bw = propgrid.GetRowHeight()
242 lipc = LargeImagePickerCtrl()
243 if sys.platform == 'win32':
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':
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)
258 print traceback.print_exc()
260 def UpdateControl(self, property, ctrl):
261 ctrl.SetValue(property.GetDisplayedString())
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 );
267 def OnEvent(self, propgrid, ctrl, event):
271 evtType = event.GetEventType()
273 if evtType == wx.wxEVT_COMMAND_TEXT_ENTER:
274 if propgrid.IsEditorsValueModified():
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:
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):
286 event.SetId(propgrid.GetId());
288 propgrid.EditorsValueWasModified();
293 def CopyValueFromControl(self, property, ctrl):
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):
303 def SetValueToUnspecified(self, ctrl):
304 ctrl.tc.Remove(0,len(ctrl.tc.GetValue()));
306 def SetControlStringValue(self, ctrl, txt):
309 def OnFocus(self, property, ctrl):
310 ctrl.tc.SetSelection(-1,-1)
314 class PropertyEditor(wx.Panel):
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))
320 sizer = wx.BoxSizer(wx.VERTICAL)
322 self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT)
324 # Show help as tooltips
325 self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)
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)
331 # Needed by custom image editor
332 wx.InitAllImageHandlers()
335 # Let's create a simple custom editor
337 # NOTE: Editor must be registered *before* adding a property that uses it.
338 self.pg.RegisterEditor(LargeImageEditor)
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)
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']) )
359 pg.Append( wxpg.EnumProperty("Enum","Enum",
360 ['wxPython Rules','wxPython Rocks','wxPython Is The Best'],
362 pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") )
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+']) )
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!" )
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 )
386 pg.Append( wxpg.PropertyCategory("5 - Custom Properties") )
387 pg.Append( IntProperty2("IntProperty2", value=1024) )
389 pg.Append( ShapeProperty("ShapeProperty", value=0) )
390 pg.Append( PyObjectProperty("PyObjectProperty") )
392 pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") )
393 pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor")
396 pg.SetPropertyClientData( "Point", 1234 )
397 if pg.GetPropertyClientData( "Point" ) != 1234:
398 raise ValueError("Set/GetPropertyClientData() failed")
400 # Test setting unicode string
401 pg.GetPropertyByName("String").SetValue(u"Some Unicode Text")
404 # Test some code that *should* fail (but not crash)
406 #a_ = pg.GetPropertyValue( "NotARealProperty" )
407 #pg.EnableProperty( "NotAtAllRealProperty", False )
408 #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" )
414 sizer.Add(self.pg, 1, wx.EXPAND)
416 sizer.SetSizeHints(self)
418 self.SelectedTreeItem = None
420 def GetPropertyValues(self):
421 return self.pg.GetPropertyValues()
423 def Initialize(self, 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']
434 property_value = element[1]['default']
435 #retrieve individual strings
436 property_value = split(property_value, ' ')
438 values = [value.strip('"') for value in property_value]
439 pg.Append(wxpg.ArrayStringProperty(element[0], value=values))
441 if element[1]['type'] == 'boolean':
442 if 'value' in element[1]:
443 property_value = element[1].as_bool('value')
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)
450 #if element[0] == 'category':
451 #pg.Append(wxpg.PropertyCategory(element[1]))
453 if element[1]['type'] == 'color':
454 if 'value' in element[1]:
455 property_value = element[1]['value']
457 property_value = element[1]['default']
458 property_value = eval(property_value)
459 pg.Append(wxpg.ColourProperty(element[0], value=property_value))
461 if element[1]['type'] == 'enum':
462 elements = element[1]['elements']
463 if 'value' in element[1]:
464 property_value = element[1]['value']
466 property_value = element[1]['default']
467 pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value)))
469 if element[1]['type'] == 'filename':
470 if 'value' in element[1]:
471 property_value = element[1]['value']
473 property_value = element[1]['default']
474 pg.Append(wxpg.FileProperty(element[0], value=property_value))
476 if element[1]['type'] == 'float':
477 if 'value' in element[1]:
478 property_value = element[1].as_float('value')
480 property_value = element[1].as_float('default')
481 property_control = wxpg.FloatProperty(element[0], value=property_value)
482 pg.Append(property_control)
484 if element[1]['type'] == 'folder':
485 if 'value' in element[1]:
486 property_value = element[1]['value']
488 property_value = element[1]['default']
489 pg.Append(wxpg.DirProperty(element[0], value=property_value))
491 if element[1]['type'] == 'integer':
492 if 'value' in element[1]:
493 property_value = element[1].as_int('value')
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')
505 if element[1]['type'] == 'string':
506 if 'value' in element[1]:
507 property_value = element[1]['value']
509 property_value = element[1]['default']
510 pg.Append(wxpg.StringProperty(element[0], value=property_value))
514 def OnReserved(self, event):
520 # self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)
521 # def OnPropGridChanged (self, event):
522 # prop = event.GetProperty()
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()
532 # config[property_section][property_key]['value'] = property_value