root/trunk/ide/ClassDesignerControlMixin.py

Revision 4668, 44.9 kB (checked in by ed, 3 weeks ago)

Added the 'AppendOnEnter?' property to combo boxes in the Class Designer.

  • Property svn:eol-style set to native
Line 
1 # -*- coding: utf-8 -*-
2 import os
3 import dabo
4 from dabo.dLocalize import _
5 import dabo.dEvents as dEvents
6 import dabo.dColors as dColors
7 import dabo.ui.dialogs as dlgs
8 from ClassDesignerComponents import LayoutPanel
9 from ClassDesignerComponents import LayoutSizer
10 from ClassDesignerComponents import LayoutBorderSizer
11 from ClassDesignerComponents import LayoutGridSizer
12 from ClassDesignerComponents import LayoutSaverMixin
13 from ClassDesignerComponents import classFlagProp
14 from dabo.ui.uiwx.dPageFrameMixin import dPageFrameMixin
15 from ClassDesignerExceptions import PropertyUpdateException
16 from dabo.ui import dKeys
17
18
19 class ClassDesignerControlMixin(LayoutSaverMixin):
20     """ The purpose of this mixin class is to add the features to the native
21     controls so that they will work in the dabo form design surface.
22     """
23     def __init__(self, parent, *args, **kwargs):
24         # Smallest dimension a control can be sized
25         self.minDimension = 10
26         # When creating the control without sizers, this is the default size
27         self.defaultWd = self.defaultHt = 10
28         # Holds the ID that identifies the control type in the designer
29         self.typeID = -1
30         # Are we the main control in the designer, or contained within the main?
31         self._isMain = False
32         # Need to hide the actual RegID property and its setter
33         self._tmpRegID = None
34         # Let the rest of the framework know that this is ClassDesigner object
35         self.isDesignerControl = True
36         # Is this control being edited?
37         self._selected = False
38         # Controls the hilite when the control is selected
39         self._hiliteBorderColor = "gold"
40         self._hiliteBorderLineStyle = "dot"
41         self._hiliteBorderWidth = 0
42         # Create the actual hilite border object
43         self._hiliteBorder = None
44         # Caches the form's setting for 'useSizers'
45         self._usingSizers = None
46         # References for resizing interactively
47         self._startX = self._startY = self._startWd = self._startHt = 0
48         # Reference for dragging interactively
49         self._startDragPos = (0,0)
50        
51         # Turned this off in Win; it was making controls disappear
52         # on that platform only.
53         ### NOTE: seems to not flicker as much with this commented out (at least on Mac).
54         #self.autoClearDrawings = (self.Application.Platform != "Win")
55        
56         # Store the defaults for the various props
57         self._propDefaults = {}
58         for prop in self.DesignerProps.keys():
59             self._propDefaults[prop] = eval("self.%s" % prop)
60         # Update bindings; do control-specific things.
61         if isinstance(self, dabo.ui.dGrid):
62             coolEvents = (dEvents.GridHeaderPaint,
63                     dEvents.GridRowSize,
64                     dEvents.GridColSize,
65                     dEvents.GridHeaderMouseLeftDown,
66                     dEvents.GridHeaderMouseMove,
67                     dEvents.GridHeaderMouseLeftUp)
68             badEvents = []
69             for bnd in self._eventBindings:
70                 if bnd[0] not in coolEvents:
71                     badEvents.append(bnd)
72             for bad in badEvents:
73                 self._eventBindings.remove(bad)
74             # Need to kill the sorting behavior
75             def _killProcessSort(col): pass
76             self.processSort = _killProcessSort
77             # Kill cell editing
78             self._vetoAllEditing = True
79             self.bindEvent(dEvents.GridCellSelected,
80                     self.Controller.onGridCellSelected)
81             self.bindEvent(dEvents.GridHeaderMouseLeftUp,
82                     self.Controller.onGridHeaderSelected)
83         elif isinstance(self, dabo.ui.dSplitter):
84             pass
85         elif isinstance(self, dabo.ui.dImage):
86             self.bindEvent(dEvents.Resize, self._onResize)
87         elif isinstance(self, (dabo.ui.dSlidePanelControl, dabo.ui.dSlidePanel)):
88             pass
89         else:
90             # This removes all previously-defined bindings
91             self.unbindEvent(None)
92         self.noUpdateForm = False
93        
94         # Set up some defaults
95         if isinstance(self, dabo.ui.dButton):
96             self.defaultWd = 100
97             self.defaultHt = 24
98             if not self.Caption:
99                 self.Caption = "Button"
100         elif isinstance(self, (dabo.ui.dLabel, dabo.ui.dTextBox)):
101             self.defaultWd = 100
102             self.defaultHt = 24
103             if isinstance(self, dabo.ui.dLabel) and not self.Caption:
104                 self.Caption = "Label"
105         elif isinstance(self, dabo.ui.dTreeView):
106             self.defaultWd = 200
107             self.defaultHt = 360
108             self.setRootNode("Tree")
109             # Bind the selected node to the current selection
110             self.bindEvent(dEvents.TreeSelection, self.desSelectNode)
111         elif isinstance(self, (dabo.ui.dPageFrame, dabo.ui.dPageList,
112                 dabo.ui.dPageSelect, dabo.ui.dPageFrameNoTabs)):
113             self.defaultWd = 400
114             self.defaultHt = 300
115             # Bind the active page to the current selection
116             self.bindEvent(dEvents.PageChanged, self.desSelectPage)
117         elif isinstance(self, dabo.ui.dSlidePanel):
118             self.bindEvent(dEvents.SlidePanelChange, self.desSlidePanelChg)
119         elif isinstance(self, (dabo.ui.dPanel, dabo.ui.dImage, dabo.ui.dBitmap,
120                 dabo.ui.dBitmapButton, dabo.ui.dToggleButton)):
121             self.defaultWd = 60
122             self.defaultHt = 60
123         else:
124             self.defaultWd = self.defaultHt = 100
125
126         # This seems to happen after the main autobinding, so
127         # it is necessary to call this manually.
128 #       self.autoBindEvents()
129         # Need to set the properties here to get the drawing updated.
130         self.HiliteBorderColor = "gold"
131         self.HiliteBorderLineStyle = "dot"
132         self.HiliteBorderWidth = 0
133        
134         # If we are on a sizer-less design, create the handles for the
135         # control ahead of time.
136         if not self.UsingSizers:
137             self.Form.createControlHandles(self)
138 #       self.bindKey("left", self.Form.keyMoveLeft)
139
140
141     def _insertPageOverride(self, pos, pgCls=None, caption="", imgKey=None,
142             makeActive=False, ignoreOverride=False):
143         if not isinstance(self, self.Controller.pagedControls):
144             return
145
146         cnt = self.Controller
147         if cnt.openingClassXML or not isinstance(pgCls, basestring):
148             tmpPgCls = self.Controller.getControlClass(dabo.ui.dPage)
149             pg = self.insertPage(pos, tmpPgCls, ignoreOverride=True)
150             pg.Sizer = LayoutSizer("v")
151             LayoutPanel(pg)
152         else:
153             dct = cnt._importClassXML(pgCls)
154             atts = dct["attributes"]
155             nm = self._extractKey(atts, "Name")
156             atts["NameBase"] = nm
157             tmpPgCls = cnt.getControlClass(dabo.ui.dPage)
158             pg = self.insertPage(pos, tmpPgCls, ignoreOverride=True)
159             classID = self._extractKey(atts, "classID", "")
160             pg.setPropertiesFromAtts(atts)
161             pg.classID = classID
162             prop = classFlagProp
163             pg.__setattr__(prop, pgCls)
164             pth = dabo.lib.utils.relativePath(pgCls)
165             pth = os.path.abspath(os.path.split(pth)[0])
166             cnt._basePath = pth
167             # For some reason, setting DefaultBorder causes a segfault!
168             # This hack turns it off.
169             propBorder = self.Controller._propagateDefaultBorder
170             cnt._propagateDefaultBorder = False
171             # We need to set _srcObj and create a dummy layout panel
172             # so that the recreateChildren() code can work properly.
173             cnt._srcObj = pg
174             pg.Sizer = LayoutSizer("v")
175             LayoutPanel(pg)
176             # OK, we can create the children of the page now.
177             cnt.recreateChildren(pg, dct["children"], None, False)
178             cnt._propagateDefaultBorder = propBorder
179         if makeActive:
180             self.SelectedPage = pg
181         return pg
182        
183    
184     def makeSizer(self):
185         if isinstance(self, dlgs.WizardPage):
186             self.Sizer = LayoutSizer("v", DefaultSpacing=5, DefaultBorder=12,
187                     DefaultBorderLeft=True, DefaultBorderRight=True)
188         else:
189             return super(ClassDesignerControlMixin, self).makeSizer()
190            
191
192     def bringToFront(self):
193         super(ClassDesignerControlMixin, self).bringToFront()
194         prn = self.Parent
195         if prn is self.Form.mainPanel:
196             prn = self.Form
197         kids = prn.Children[:]
198         kids.remove(self)
199         kids.append(self)
200         prn.zChildren = kids
201        
202        
203     def sendToBack(self):
204         super(ClassDesignerControlMixin, self).sendToBack()
205         prn = self.Parent
206         if prn is self.Form.mainPanel:
207             prn = self.Form
208         kids = prn.Children[:]
209         kids.remove(self)
210         kids.insert(0, self)
211         prn.zChildren = kids
212        
213        
214     def onKeyChar(self, evt):
215         if isinstance(self, (dabo.ui.dPage, dabo.ui.dColumn)):
216             # The key will get processed by the container
217             return
218         self.Form.onKeyChar(evt)
219
220        
221     def _onResizeHiliteBorder(self, evt):
222         """Called when the control is resized."""
223         brd = self._hiliteBorder
224         brd.Width, brd.Height = self.Width, self.Height
225
226        
227     def setMouseHandling(self, turnOn):
228         """When turnOn is True, sets all the mouse event bindings. When
229         it is False, removes the bindings.
230         """
231         if turnOn:
232             self.bindEvent(dEvents.MouseMove, self.handleMouseMove)
233         else:
234             self.unbindEvent(dEvents.MouseMove)
235
236
237     def handleMouseMove(self, evt):
238         self.Form.onMouseDrag(evt)
239        
240    
241     def onMouseLeftDown(self, evt):
242         if isinstance(self, (dPageFrameMixin, dabo.ui.dSplitter)):
243             pass
244         else:
245             if not isinstance(self, dabo.ui.dTreeView):
246                 evt.stop()
247             if not self.UsingSizers:
248                 self.Form.onControlLeftDown(evt)
249
250
251     def onMouseLeftUp(self, evt):
252         if isinstance(self, (dabo.ui.dSplitter, )):
253             pass
254         else:
255             if not isinstance(self, dabo.ui.dTreeView):
256                 evt.stop()
257             else:
258                 nd = self.getNodeUnderMouse(includeSpace=True, includeButton=False)
259                 if nd is not None:
260                     return
261             self.Form.processLeftUp(self, evt)
262
263
264     def onMouseLeftDoubleClick(self, evt):
265         self.Form.processLeftDoubleClick(evt)
266    
267    
268     def onEditContainer(self, evt):
269         self.Form.ActiveContainer = self
270        
271
272     def onMouseRightClick(self, evt):
273         if isinstance(self, dabo.ui.dTreeView):
274             evt.stop()
275             self.onContextMenu(evt)
276            
277            
278     def onContextMenu(self, evt):
279         # If it is a LayoutPanel or page, return - the event
280         # is handled elsewhere
281         evt.stop()
282         if self.UsingSizers and isinstance(self, (dabo.ui.dPage, LayoutPanel)):
283             return
284         if isinstance(self.Parent, dabo.ui.dRadioList):
285             self.Parent.onContextMenu(evt)
286             return
287         pop = self.createContextMenu(evt)
288         self.showContextMenu(pop)
289
290
291     def createContextMenu(self, evt=None):
292         pop = None
293         if self.UsingSizers:
294             if isinstance(self, (dabo.ui.dPanel, dabo.ui.dScrollPanel, dabo.ui.dPage)):
295                 pop = self.Controller.getControlMenu(self, True)
296         else:
297             if self is self.Form.ActiveContainer:
298                 # If the control can contain child objects, get that menu.
299                 pop = self.Controller.getControlMenu(self, False)
300         if pop is None:
301             pop = dabo.ui.dMenu()
302         if len(pop.Children):
303             pop.prependSeparator()
304         if not self.UsingSizers and self.IsContainer \
305                 and not self is self.Form.ActiveContainer:
306             pop.prepend(_("Edit Contents"), OnHit=self.onEditContainer)         
307         if len(pop.Children):
308             pop.prependSeparator()
309         pop.prepend(_("Edit Code"), OnHit=self.onEditCode)
310         pop.prependSeparator()
311         if not self.UsingSizers and self is self.Form.ActiveContainer:
312             if self.Controller.Clipboard:
313                 pop.prepend(_("Paste"), OnHit=self.onPaste)
314         else:
315             pop.prepend(_("Delete"), OnHit=self.onDelete)
316             pop.prepend(_("Copy"), OnHit=self.onCopy)
317             pop.prepend(_("Cut"), OnHit=self.onCut)
318         if isinstance(self, dabo.ui.dPage):
319             # Add option to delete the page or the entire pageframe
320             pop.prependSeparator()
321             sepAdded =True
322             pop.prepend(_("Delete the entire Paged Control"), self.Parent.onDelete)
323             pop.prepend(_("Delete this Page"), OnHit=self.onDelete)
324            
325         if isinstance(self, dabo.ui.dTreeView):
326             self.activeNode = self.Selection
327             if isinstance(self.activeNode, (list, tuple)):
328                 self.activeNode = self.activeNode[0]
329             pop.append(_("Add Child Node"), OnHit=self.onAddChild)
330             if not self.activeNode.IsRootNode:
331                 pop.append(_("Add Sibling Node"), OnHit=self.onAddSibling)
332             if not self.Editable:
333                 pop.append(_("Change Node Caption"),
334                         OnHit=self.onChangeCaption)
335             if not self.activeNode.IsRootNode:
336                 pop.append(_("Delete this node"), OnHit=self.onDelNode)
337         elif isinstance(self, (dabo.ui.dLabel, dabo.ui.dButton, dabo.ui.dCheckBox,
338                 dabo.ui.dBitmapButton, dabo.ui.dToggleButton, dabo.ui.dPage,
339                 dabo.ui.dColumn, dlgs.WizardPage)):
340             pop.append(_("Change Caption"),
341                     OnHit=self.onChangeCaption)
342         if self.UsingSizers:
343             self.Controller.addSlotOptions(self, pop, sepBefore=True)
344             # Add the Sizer editing option
345             pop.appendSeparator()
346             pop.append(_("Edit Sizer Settings"), OnHit=self.onEditSizer)
347         return pop
348        
349
350     def getClass(self):
351         """Returns a string representing the class's name. Default behavior
352         is to return the BaseClass, but this allows for specific subclasses
353         to override that behavior.
354         """
355         if isinstance(self, dlgs.WizardPage):
356             ret = "dabo.ui.dialogs.WizardPage"
357         else:
358             ret = super(ClassDesignerControlMixin, self).getClass()
359         return ret
360        
361        
362     def onAddChild(self, evt):
363         nd = self.activeNode
364         self.activeNode = None
365         txt = dabo.ui.getString(_("New Node Caption?"), _("Adding Child Node"))
366         if txt is not None:
367             nd.appendChild(txt)
368         self.Controller.updateLayout()
369  
370    
371     def onAddSibling(self, evt):
372         nd = self.activeNode
373         self.activeNode = None
374         txt = dabo.ui.getString(_("New Node Caption?"), _("Adding Sibling Node"))
375         if txt is not None:
376             nd.parent.appendChild(txt)
377         self.Controller.updateLayout()
378  
379  
380     def onDelNode(self, evt):
381         nd = self.activeNode
382         self.activeNode = None
383         self.removeNode(nd)
384         self.Controller.updateLayout()
385  
386  
387     def onChangeCaption(self, evt):
388         if isinstance(self, dabo.ui.dTreeView):
389             nd = self.activeNode
390             self.activeNode = None
391             target = nd
392             title = _("Changing Node")
393             defVal = nd.Caption
394         else:
395             target = self
396             title = _("Changing Caption")
397             defVal = self.Caption
398         txt = dabo.ui.getString(_("New Caption"), caption=title,
399                 defaultValue=defVal, Width=500, SelectOnEntry=True)
400         if txt is not None:
401             target.Caption = txt
402         self.Controller.updateLayout()
403
404
405     def onPaste(self, evt):
406         self.Controller.pasteObject(self)
407        
408
409     def onEditSizer(self, evt):
410         """Called when the user selects the context menu option
411         to edit this control's sizer information.
412         """
413         self.Controller.editSizerSettings(self)
414        
415        
416     def onCut(self, evt):
417         """Place a copy of this control on the Controller clipboard,
418         and then delete the control
419         """
420         self.Controller.copyObject(self)
421         self.onDelete(evt)
422        
423
424     def onCopy(self, evt):
425         """Place a copy of this control on the Controller clipboard"""
426         self.Controller.copyObject(self)
427        
428
429     def onEditCode(self, evt):
430         """Open the editor"""
431         self.Form.editCode(self)
432    
433    
434     def onDelete(self, evt):
435         # When a page in a pageframe gets this event, pass it up
436         # to its parent.
437         if isinstance(self, dabo.ui.dPage):
438             dabo.ui.callAfter(self.Parent.removePage, self)
439             dabo.ui.callAfter(self.Controller.updateLayout)
440             return
441         if self.UsingSizers:
442             self.ControllingSizer.delete(self)
443         else:
444             self.Form.select(self.Parent)
445             dabo.ui.callAfter(self.release)
446             dabo.ui.callAfter(self.Controller.updateLayout)
447        
448
449     def isSelected(self):
450         return self.Parent.isSelected(self)
451        
452    
453     def desSelectPage(self, evt):
454         """Called when a page is selected"""
455         if not self.UsingSizers: return
456         try:
457             obj = self.Controller.Selection[0]
458             if obj.isContainedBy(self.SelectedPage):
459                 # No need to do anything
460                 return
461         except: pass
462         self.Form.selectControl(self.SelectedPage, False)
463    
464    
465     def desSelectNode(self, evt):
466         """Called when a node in a tree is selected"""
467         self.Form.selectControl(self.Selection, False)
468
469
470     def desSlidePanelChg(self, evt):
471         dabo.ui.callAfterInterval(100, self.Form.refresh)
472
473
474     def moveControl(self, pos, shft=False):
475         """ Wraps the Move command with the necessary
476         screen updating stuff.
477         """
478         self.Position = pos
479     ######     
480         if not self.noUpdateForm:
481             self.Form.redrawHandles(self)
482    
483    
484     def resizeControl(self, sz):
485         """ Wraps the SetSize command with the necessary
486         screen updating stuff.
487         """
488         self.Size = sz
489         self.Form.redrawHandles(self)
490    
491    
492     def nudgeControl(self, horiz, vert):
493         """ Used to move the control relative to its current position.
494         Each direction is the number of pixels to move in that direction,
495         with negative moving left/up.
496         """
497         lf, top = self.Position
498         lfNew = lf + horiz
499         topNew = top + vert
500         self.moveControl( (lfNew, topNew) )
501        
502        
503     def growControl(self, horiz, vert):
504         """ Used to resize the control relative to its current size.
505         Each direction is the number of pixels to change the
506         size in that direction
507         """
508         wd, ht = self.Size
509         wdNew = max(wd + horiz, self.minDimension)
510         htNew = max(ht + vert, self.minDimension)
511         self.resizeControl( (wdNew, htNew) )
512        
513        
514     def startResize(self, evt, up, right, down, left):
515         """ Determine the offset of the mouse, dependi