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