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