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
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.
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.
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/>.
19 """Property editor panel for Hooke.
26 import wx.propgrid as wxpg
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.
31 class Display (object):
32 property_descriptor = []
36 class ValueObject (object):
41 class IntProperty2 (wxpg.PyProperty):
42 """This is a simple re-implementation of wxIntProperty.
44 def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=0):
45 wxpg.PyProperty.__init__(self, label, name)
48 def GetClassName(self):
54 def GetValueAsString(self, flags):
55 return str(self.GetValue())
57 def PyStringToValue(self, s, flags):
60 if self.GetValue() != v:
63 if flags & wxpg.PG_REPORT_ERROR:
64 wx.MessageBox("Cannot convert '%s' into a number."%s, "Error")
67 def PyIntToValue(self, v, flags):
68 if (self.GetValue() != v):
72 class PyFilesProperty(wxpg.PyArrayStringProperty):
73 def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=[]):
74 wxpg.PyArrayStringProperty.__init__(self, label, name, value)
77 def OnSetValue(self, v):
79 self.display = ', '.join(self.value)
81 def GetValueAsString(self, argFlags):
84 def PyStringToValue(self, s, flags):
85 return [a.strip() for a in s.split(',')]
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.
96 class PyObjectPropertyValue:
98 Value type of our sample PyObjectProperty. We keep a simple dash-delimited
99 list of string given as argument to constructor.
101 def __init__(self, s=None):
103 self.ls = [a.strip() for a in s.split('-')]
108 return ' - '.join(self.ls)
111 class PyObjectProperty(wxpg.PyProperty):
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).
118 def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=None):
119 wxpg.PyProperty.__init__(self, label, name)
122 def GetClassName(self):
123 return self.__class__.__name__
128 def GetValueAsString(self, flags):
129 return repr(self.GetValue())
131 def PyStringToValue(self, s, flags):
132 return PyObjectPropertyValue(s)
135 class ShapeProperty(wxpg.PyEnumProperty):
137 Demonstrates use of OnCustomPaint method.
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)
142 def OnMeasureImage(self, index):
143 return wxpg.DEFAULT_IMAGE_SIZE
145 def OnCustomPaint(self, dc, rect, paint_data):
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.
150 item = paint_data.m_choiceItem
152 item = self.DoGetValue()
154 dc.SetPen(wx.Pen(wx.BLACK))
155 dc.SetBrush(wx.Brush(wx.BLACK))
158 dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height)
160 half_width = rect.width / 2
161 dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3)
163 dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)
166 class LargeImagePickerCtrl(wx.Window):
168 Control created and used by LargeImageEditor.
174 def Create(self, parent, id_, pos, size, style = 0):
175 wx.Window.Create(self, parent, id_, pos, size, style | wx.BORDER_SIMPLE)
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)
182 self.Bind(wx.EVT_PAINT, self.OnPaint)
184 def OnPaint(self, event):
185 dc = wx.BufferedPaintDC(self)
187 whiteBrush = wx.Brush(wx.WHITE)
188 dc.SetBackground(whiteBrush)
193 dc.DrawBitmap(bmp, 2, 2)
195 dc.SetPen(wx.Pen(wx.BLACK))
196 dc.SetBrush(whiteBrush)
197 dc.DrawRectangle(2, 2, 64, 64)
199 def RefreshThumbnail(self):
201 We use here very simple image scaling code.
203 if not self.property:
207 path = self.property.DoGetValue()
209 if not os.path.isfile(path):
213 image = wx.Image(path)
214 image.Rescale(64, 64)
215 self.bmp = wx.BitmapFromImage(image)
217 def SetProperty(self, property):
218 self.property = property
219 self.tc.SetValue(property.GetDisplayedString())
220 self.RefreshThumbnail()
222 def SetValue(self, s):
223 self.RefreshThumbnail()
226 def GetLastPosition(self):
227 return self.tc.GetLastPosition()
230 class LargeImageEditor(wxpg.PyEditor):
232 Double-height text-editor with image in front.
235 wxpg.PyEditor.__init__(self)
237 def CreateControls(self, propgrid, property, pos, sz):
240 x = propgrid.GetSplitterPosition()
241 x2 = propgrid.GetClientSize().x
242 bw = propgrid.GetRowHeight()
243 lipc = LargeImagePickerCtrl()
244 if sys.platform == 'win32':
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':
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)
259 print traceback.print_exc()
261 def UpdateControl(self, property, ctrl):
262 ctrl.SetValue(property.GetDisplayedString())
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 );
268 def OnEvent(self, propgrid, ctrl, event):
272 evtType = event.GetEventType()
274 if evtType == wx.wxEVT_COMMAND_TEXT_ENTER:
275 if propgrid.IsEditorsValueModified():
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:
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):
287 event.SetId(propgrid.GetId());
289 propgrid.EditorsValueWasModified();
294 def CopyValueFromControl(self, property, ctrl):
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):
304 def SetValueToUnspecified(self, ctrl):
305 ctrl.tc.Remove(0,len(ctrl.tc.GetValue()));
307 def SetControlStringValue(self, ctrl, txt):
310 def OnFocus(self, property, ctrl):
311 ctrl.tc.SetSelection(-1,-1)
315 class PropertyEditor(wx.Panel):
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))
321 sizer = wx.BoxSizer(wx.VERTICAL)
323 self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT)
325 # Show help as tooltips
326 self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)
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)
332 # Needed by custom image editor
333 wx.InitAllImageHandlers()
336 # Let's create a simple custom editor
338 # NOTE: Editor must be registered *before* adding a property that uses it.
339 self.pg.RegisterEditor(LargeImageEditor)
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)
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']) )
360 pg.Append( wxpg.EnumProperty("Enum","Enum",
361 ['wxPython Rules','wxPython Rocks','wxPython Is The Best'],
363 pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") )
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+']) )
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!" )
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 )
387 pg.Append( wxpg.PropertyCategory("5 - Custom Properties") )
388 pg.Append( IntProperty2("IntProperty2", value=1024) )
390 pg.Append( ShapeProperty("ShapeProperty", value=0) )
391 pg.Append( PyObjectProperty("PyObjectProperty") )
393 pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") )
394 pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor")
397 pg.SetPropertyClientData( "Point", 1234 )
398 if pg.GetPropertyClientData( "Point" ) != 1234:
399 raise ValueError("Set/GetPropertyClientData() failed")
401 # Test setting unicode string
402 pg.GetPropertyByName("String").SetValue(u"Some Unicode Text")
405 # Test some code that *should* fail (but not crash)
407 #a_ = pg.GetPropertyValue( "NotARealProperty" )
408 #pg.EnableProperty( "NotAtAllRealProperty", False )
409 #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" )
415 sizer.Add(self.pg, 1, wx.EXPAND)
417 sizer.SetSizeHints(self)
419 self.SelectedTreeItem = None
421 def GetPropertyValues(self):
422 return self.pg.GetPropertyValues()
424 def Initialize(self, 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']
435 property_value = element[1]['default']
436 #retrieve individual strings
437 property_value = split(property_value, ' ')
439 values = [value.strip('"') for value in property_value]
440 pg.Append(wxpg.ArrayStringProperty(element[0], value=values))
442 if element[1]['type'] == 'boolean':
443 if 'value' in element[1]:
444 property_value = element[1].as_bool('value')
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)
451 #if element[0] == 'category':
452 #pg.Append(wxpg.PropertyCategory(element[1]))
454 if element[1]['type'] == 'color':
455 if 'value' in element[1]:
456 property_value = element[1]['value']
458 property_value = element[1]['default']
459 property_value = eval(property_value)
460 pg.Append(wxpg.ColourProperty(element[0], value=property_value))
462 if element[1]['type'] == 'enum':
463 elements = element[1]['elements']
464 if 'value' in element[1]:
465 property_value = element[1]['value']
467 property_value = element[1]['default']
468 pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value)))
470 if element[1]['type'] == 'filename':
471 if 'value' in element[1]:
472 property_value = element[1]['value']
474 property_value = element[1]['default']
475 pg.Append(wxpg.FileProperty(element[0], value=property_value))
477 if element[1]['type'] == 'float':
478 if 'value' in element[1]:
479 property_value = element[1].as_float('value')
481 property_value = element[1].as_float('default')
482 property_control = wxpg.FloatProperty(element[0], value=property_value)
483 pg.Append(property_control)
485 if element[1]['type'] == 'folder':
486 if 'value' in element[1]:
487 property_value = element[1]['value']
489 property_value = element[1]['default']
490 pg.Append(wxpg.DirProperty(element[0], value=property_value))
492 if element[1]['type'] == 'integer':
493 if 'value' in element[1]:
494 property_value = element[1].as_int('value')
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')
506 if element[1]['type'] == 'string':
507 if 'value' in element[1]:
508 property_value = element[1]['value']
510 property_value = element[1]['default']
511 pg.Append(wxpg.StringProperty(element[0], value=property_value))
515 def OnReserved(self, event):
521 # self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)
522 # def OnPropGridChanged (self, event):
523 # prop = event.GetProperty()
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()
533 # config[property_section][property_key]['value'] = property_value