1
2
3
4
5
6
7 """
8 Component plugin classes.
9
10 This module defines base classes and for deriving component plugin
11 classes and contains some global variables used to register components
12 with XRCed.
13
14 Components are objects of Component class or one of the derived
15 classes used to define specialized components (such as sizers). After
16 a component object is constructed and configured it can be registered
17 using the Manager global object.
18
19 """
20
21
22 import os,sys,bisect
23 import wx
24 from sets import Set
25 from globals import *
26 from model import Model
27 from attribute import *
28 import params
29 import view
30 import images
31
32 DEFAULT_POS = (1000,1000)
33
34
35
36
37
38 parentChildGroups = {
39 'root': ['top_level', 'component'],
40 'frame': ['toolbar', 'menubar', 'statusbar'],
41 'wizard': ['wizard_page'],
42 'window': ['control', 'window', 'sizer', 'btnsizer', '!frame'],
43 'sizer': ['control', 'sizer', 'btnsizer', 'spacer'],
44 'book': ['control', 'window', '!sizer', '!btnsizer'],
45 'btnsizer': ['stdbtn'],
46 'menubar': ['menu'],
47 'toolbar': ['tool', 'separator'],
48 'menu': ['menu', 'menu_item', 'separator'],
49 }
50 '''
51 Definition of compatibility of component groups as I{key}:I{group_list} pairs, where
52 I{key} is a parent group name and I{group_list} is a list of children group names or
53 group names prefixed with '!' character to exclude components having corresponding
54 primary group. This dictionary can be modified by component plugins directly
55 (some care must be taken not to redefine existing relations or breake them).
56 '''
57
58
60 '''Base class for component plugins.'''
61
62 windowAttributes = ['fg', 'bg', 'font', 'tooltip', 'help',
63 'enabled', 'focused', 'hidden']
64 '''Default window attributes for window-like components.'''
65 genericStyles = [
66 'wxSIMPLE_BORDER', 'wxSUNKEN_BORDER', 'wxDOUBLE_BORDER',
67 'wxRAISED_BORDER', 'wxSTATIC_BORDER', 'wxNO_BORDER',
68 'wxCLIP_CHILDREN', 'wxTRANSPARENT_WINDOW', 'wxWANTS_CHARS',
69 'wxNO_FULL_REPAINT_ON_RESIZE', 'wxFULL_REPAINT_ON_RESIZE'
70 ]
71 '''Default generic styles.'''
72 genericExStyles = [
73 'wxWS_EX_VALIDATE_RECURSIVELY',
74 'wxWS_EX_BLOCK_EVENTS',
75 'wxWS_EX_TRANSIENT',
76 'wxFRAME_EX_CONTEXTHELP',
77 'wxWS_EX_PROCESS_IDLE',
78 'wxWS_EX_PROCESS_UI_UPDATES'
79 ]
80 '''Default generic extended styles.'''
81 genericEvents = [
82 'EVT_WINDOW_CREATE', 'EVT_WINDOW_DESTROY',
83 'EVT_MOVE', 'EVT_SIZE',
84 'EVT_MOUSE_EVENTS', 'EVT_MOTION',
85 'EVT_LEFT_DOWN', 'EVT_LEFT_DCLICK',
86 'EVT_MIDDLE_DOWN', 'EVT_MIDDLE_DCLICK',
87 'EVT_RIGHT_DOWN', 'EVT_RIGHT_DCLICK', 'EVT_MOUSEWHEEL',
88 'EVT_ENTER_WINDOW', 'EVT_LEAVE_WINDOW',
89 'EVT_KEY_DOWN', 'EVT_KEY_UP', 'EVT_CHAR',
90 'EVT_PAINT', 'EVT_ERASE_BACKGROUND',
91 'EVT_CONTEXT_MENU', 'EVT_HELP',
92 'EVT_SET_FOCUS', 'EVT_KILL_FOCUS', 'EVT_CHILD_FOCUS',
93 'EVT_UPDATE_UI', 'EVT_IDLE',
94 ]
95 '''Default generic events.'''
96 hasName = True
97 '''True if component has an XRC ID attribute.'''
98 isTopLevel = False
99 '''True if component can be a top-level object in XML tree.'''
100 renameDict = {}
101 '''Dictionary of I{old_name}:I{new_name} for renaming some attributes
102 in the Attribute Panel.'''
103
104 - def __init__(self, klass, groups, attributes, **kargs):
105 '''
106 Construct a new Component object.
107
108 @param klass: Interface element class name (e.g. C{'wxButton'}).
109 @param groups: List of group names to which this component belongs.
110 First group is considered to be the I{primary group}.
111 @param attributes: List of XRC attribute names.
112
113 B{Supported keyword parameters:}
114
115 @keyword defaults: Dictionary of default attribute values for creating
116 new items.
117 @keyword specials: Dictionary of I{attribute_name}:I{attribute_class} pairs
118 for specifying special attribute classes for some attributes, instead of
119 using default Attribute class.
120 @keyword params: Dictionary of pairs I{attribute_name}:I{param_class} where
121 I{param_class} is a attribute interface class (one of classes in
122 params.py or a custom class). If a param class is not specified, a default
123 value defined by C{paramDict} dictionary is used.
124 @keyword image,images: C{wx.Image} object or a list of C{wx.Image} objects
125 for tree icons.
126 @keyword events: List of event names for code genration panel.
127 '''
128 self.klass = klass
129 self.groups = groups
130 self.attributes = attributes
131 self.styles = []
132 self.exStyles = []
133 self.defaults = kargs.get('defaults', {})
134
135 self.specials = kargs.get('specials', {})
136
137 self.specials['font'] = FontAttribute
138 self.specials['XRCED'] = CodeAttribute
139
140 self.params = kargs.get('params', {})
141
142 if 'images' in kargs:
143 self.images = kargs['images']
144 elif 'image' in kargs:
145 self.images = [kargs['image']]
146 elif not 'image' in self.__dict__:
147 self.images = []
148
149 self.events = kargs.get('events', [])
150
152 '''Add more styles.'''
153 self.styles.extend(styles)
154
156 '''Add more extra styles.'''
157 self.exStyles.extend(styles)
158
160 '''Set special attribute class for processing XML.
161
162 @param attrName: Attribute name.
163 @param attrClass: Attribute class.
164 '''
165 self.specials[attrName] = attrClass
166
168 '''Set special attribute panel class for editing attribute value.
169
170 @param attrName: Attribute name.
171 @param paramClass: Param class.
172 '''
173 self.params[attrName] = paramClass
174
176 try:
177 return self.images[0].Id
178 except IndexError:
179 return 0
180
181 - def getTreeText(self, node):
182 label = node.getAttribute('subclass')
183 if not label:
184 label = node.getAttribute('class')
185 if self.hasName:
186 name = node.getAttribute('name')
187 if name: label += ' "%s"' % name
188 return label
189
191 '''Add more events.'''
192 self.events.extend(events)
193
194
196 if self.groups < other.groups: return -1
197 elif self.groups == other.groups:
198 if self.klass < other.klass: return -1
199 elif self.klass == other.klass: return 0
200 else: return 1
201 else: return 1
202
204 return "Component('%s', %s)" % (self.klass, self.attributes)
205
207 '''True if the current component can have child of given type.
208
209 This function is redefined by container classes.'''
210 return False
211
213 '''True if the current component can be replaced by component.
214
215 This function can be redefined by derived classes.'''
216 return component.groups == groups
217
219 '''True if component is a container (can have child nodes).'''
220 return isinstance(self, Container)
221
233
238
240 '''Method can be overrided by derived classes to create custom test view.
241
242 @param res: C{wx.xrc.XmlResource} object with current test resource.
243 @param name: XRC ID of tested object.
244 '''
245 if not self.hasName: raise NotImplementedError
246
247 testWin = view.testWin
248 if self.isTopLevel:
249
250 frame = None
251 object = res.LoadObject(view.frame, STD_NAME, self.klass)
252 object.Fit()
253 testWin.size = object.GetSize()
254 else:
255
256 frame = testWin.frame
257 if not frame:
258 frame = wx.MiniFrame(view.frame, -1, '%s: %s' % (self.klass, name), name=STD_NAME,
259 style=wx.CAPTION|wx.CLOSE_BOX|wx.RESIZE_BORDER)
260 frame.panel = wx.Panel(frame)
261 object = res.LoadObject(frame.panel, STD_NAME, self.klass)
262 if not object: raise NotImplementedError
263 object.SetPosition((20,20))
264 object.Fit()
265 if not isinstance(object, wx.Window): raise NotImplementedError
266 if not testWin.frame:
267 frame.SetClientSize(object.GetSize()+(20,20))
268 testWin.size = frame.GetSize()
269 return frame, object
270
272 '''Return bounding box coordinates for C{obj}.'''
273
274 if isinstance(obj, wx.Window):
275 return [obj.GetRect()]
276 elif isinstance(obj, wx.Rect):
277 return [obj]
278 else:
279 return None
280
282 '''Copy relevant attribute nodes from srcNode to dstNode.'''
283 dstComp = Manager.getNodeComp(dstNode)
284 for n in srcNode.childNodes:
285 if n.nodeType == n.ELEMENT_NODE:
286 a = n.tagName
287
288 srcAttrClass = self.specials.get(a, Attribute)
289 dstAttrClass = dstComp.specials.get(a, Attribute)
290 if srcAttrClass is not dstAttrClass: continue
291 srcParamClass = self.params.get(a, params.paramDict.get(a, params.ParamText))
292 dstParamClass = dstComp.params.get(a, params.paramDict.get(a, params.ParamText))
293 if srcParamClass is not dstParamClass: continue
294
295 if a == 'style':
296 styles = self.getAttribute(srcNode, a).split('|')
297 allStyles = dstComp.styles + params.genericStyles
298 dstStyles = [s for s in styles if s.strip() in allStyles]
299 if dstStyles:
300 dstComp.addAttribute(dstNode, a, '|'.join(dstStyles))
301 elif a == 'exstyle':
302 styles = self.getAttribute(srcNode, a).split('|')
303 allStyles = dstComp.exStyles + params.genericExStyles
304 dstStyles = [s for s in styles if s.strip() in allStyles]
305 if dstStyles:
306 dstComp.addAttribute(dstNode, a, '|'.join(dstStyles))
307 elif a in dstComp.attributes:
308 value = self.getAttribute(srcNode, a)
309 dstComp.addAttribute(dstNode, a, value)
310
311
316
317
319 '''Base class for containers.'''
330
332 '''If this container manages children positions and sizes.'''
333 return False
334
336 '''If there are implicit nodes for this particular node.'''
337 return False
338
340 '''Some containers may hide some internal elements.'''
341 return node
342
344 '''Return topmost child (implicit if exists).'''
345 return node
346
348 '''Append child node. Can be overriden to create implicit nodes.'''
349 parentNode.appendChild(node)
350
352 '''Insert node before nextNode. Can be overriden to create implicit nodes.'''
353 parentNode.insertBefore(node, nextNode)
354
356 '''Insert node after prevNode. Can be overriden to create implicit nodes.'''
357 parentNode.insertBefore(node, prevNode.nextSibling)
358
360 '''
361 Remove node and the implicit node (if present). Return
362 top-level removed child.
363 '''
364 return parentNode.removeChild(node)
365
375
384
386 """Get index'th child of a tested interface element."""
387 if isinstance(obj, wx.Window) and obj.GetSizer():
388 return obj.GetSizer()
389 try:
390 return obj.GetChildren()[index]
391 except IndexError:
392 return None
393
394
399
400
406
407
409 '''Base class for containers with implicit nodes.'''
410 implicitRenameDict = {}
411 - def __init__(self, klass, groups, attributes, **kargs):
412 Container.__init__(self, klass, groups, attributes, **kargs)
413 self.implicitKlass = kargs['implicit_klass']
414 self.implicitPageName = kargs['implicit_page']
415 self.implicitAttributes = kargs['implicit_attributes']
416
417 self.implicitParams = kargs.get('implicit_params', {})
418
420 if node.getAttribute('class') == self.implicitKlass:
421 for n in node.childNodes:
422 if is_object(n): return n
423
424 return node
425
427 '''Return topmost child (implicit if exists).'''
428 if node.parentNode.getAttribute('class') == self.implicitKlass:
429 return node.parentNode
430 else:
431 return node
432
440
450
452 if self.requireImplicit(prevNode):
453 nextNode = prevNode.parentNode.nextSibling
454 else:
455 nextNode = prevNode.nextSibling
456 if self.requireImplicit(node):
457 elem = Model.createObjectNode(self.implicitKlass)
458 elem.appendChild(node)
459 parentNode.insertBefore(elem, nextNode)
460 else:
461 parentNode.insertBefore(node, nextNode)
462
470
491
495
497 '''Set special Param class.'''
498 self.implicitParams[attrName] = paramClass
499
500
501 -class Sizer(SmartContainer):
502 '''Sizers are not windows and have common implicit node.'''
503 windowAttributes = []
504 hasName = False
505 genericStyles = []
506 genericExStyles = []
507 renameDict = {'orient':'orientation'}
508 implicitRenameDict = {'option':'proportion'}
509 - def __init__(self, klass, groups, attributes, **kargs):
510 kargs.setdefault('implicit_klass', 'sizeritem')
511 kargs.setdefault('implicit_page', 'SizerItem')
512 kargs.setdefault('implicit_attributes', ['option', 'flag', 'border', 'minsize', 'ratio'])
513 kargs.setdefault('implicit_params', {'option': params.ParamInt,
514 'minsize': params.ParamPosSize,
515 'ratio': params.ParamPosSize})
516 SmartContainer.__init__(self, klass, groups, attributes, **kargs)
517
520
523
525 obj = obj.GetChildren()[index]
526 if obj.IsSizer():
527 return obj.GetSizer()
528 elif obj.IsWindow():
529 return obj.GetWindow()
530 elif obj.IsSpacer():
531 return obj.GetRect()
532 return None
533
535 rects = [wx.RectPS(obj.GetPosition(), obj.GetSize())]
536 for sizerItem in obj.GetChildren():
537 rect = sizerItem.GetRect()
538 rects.append(rect)
539
540 flag = sizerItem.GetFlag()
541 border = sizerItem.GetBorder()
542 if border == 0: continue
543 x = (rect.GetLeft() + rect.GetRight()) / 2
544 if flag & wx.TOP:
545 rects.append(wx.Rect(x, rect.GetTop() - border, 0, border))
546 if flag & wx.BOTTOM:
547 rects.append(wx.Rect(x, rect.GetBottom() + 1, 0, border))
548 y = (rect.GetTop() + rect.GetBottom()) / 2
549 if flag & wx.LEFT:
550 rects.append(wx.Rect(rect.GetLeft() - border, y, border, 0))
551 if flag & wx.RIGHT:
552 rects.append(wx.Rect(rect.GetRight() + 1, y, border, 0))
553 return rects
554
555
557 '''Sizers are not windows and have common implicit node.'''
558
559 - def __init__(self, klass, groups, attributes, **kargs):
562
564 if self.getAttribute(node, 'orient') == 'wxVERTICAL':
565 return self.images[0].Id
566 else:
567 return self.images[1].Id
568
570 rects = Sizer.getRect(self, obj)
571 for sizerItem in obj.GetChildren():
572 rect = sizerItem.GetRect()
573 flag = sizerItem.GetFlag()
574 if flag & wx.EXPAND:
575 if obj.GetOrientation() == wx.VERTICAL:
576 y = (rect.GetTop() + rect.GetBottom()) / 2
577 rects.append(wx.Rect(rect.x, y, rect.width, 0))
578 else:
579 x = (rect.GetLeft() + rect.GetRight()) / 2
580 rects.append(wx.Rect(x, rect.y, 0, rect.height))
581 return rects
582
583
584
586 '''Component manager used to register component plugins.'''
588 self.rootComponent = RootComponent('root', ['root'], ['encoding'],
589 specials={'encoding': EncodingAttribute},
590 params={'encoding': params.ParamEncoding})
591 self.components = {}
592 self.ids = {}
593 self.firstId = self.lastId = -1
594 self.menus = {}
595 self.panels = {}
596 self.menuNames = ['TOP_LEVEL', 'ROOT', 'bar', 'control', 'button', 'box',
597 'container', 'sizer', 'custom']
598 self.panelNames = ['Windows', 'Panels', 'Controls', 'Sizers', 'Menus',
599 'Gizmos', 'Custom']
600 self.panelImages = {}
601 self.handlers = []
602
604 self.firstId = self.lastId = wx.NewId()
605
613
615 '''Remove registered component.'''
616 del self.components[klass]
617 for menu,iclh in self.menus.items():
618 if iclh[1].klass == klass:
619 self.menus[menu].remove(iclh)
620 for panel,icb in self.panels.items():
621 if icb[1].klass == klass:
622 self.panels[panel].remove(icb)
623
626
628 return self.menus.get(menu, None)
629
631 '''Set pulldown menu data.'''
632 if menu not in self.menuNames: self.menuNames.append(menu)
633 if menu not in self.menus: self.menus[menu] = []
634 bisect.insort_left(self.menus[menu], (index, component, label, help))
635
637 return self.panels.get(panel, None)
638
657
659 '''
660 Add an XML resource handler. h must be a class derived from
661 XmlResourceHandler or a function loaded from a dynamic library
662 using ctypes.
663 '''
664 self.handlers.append(h)
665
668
670 '''Register XML handlers before creating a test window.'''
671 for h in self.handlers:
672 TRACE('registering Xml handler %s', h)
673 if g._CFuncPtr and isinstance(h, g._CFuncPtr):
674 try:
675 apply(h, ())
676 except:
677 logger.exception('error calling DL func "%s"', h)
678 wx.LogError('error calling DL func "%s"' % h)
679 else:
680 try:
681 res.AddHandler(apply(h, ()))
682 except:
683 logger.exception('error adding XmlHandler "%s"', h)
684 wx.LogError('error adding XmlHandler "%s"' % h)
685
686
687
688 Manager = _ComponentManager()
689 '''Singleton global object of L{_ComponentManager} class.'''
690