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