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