root/trunk/dabo/dObject.py

Revision 4242, 13.7 kB (checked in by ed, 5 months ago)

Fixed a problem reported by Uwe Grauer with values for int-type properties being stored in a float format.

  • Property svn:eol-style set to native
Line 
1 # -*- coding: utf-8 -*-
2 import string
3 import types
4 import new
5 import dabo
6 from dabo.lib.propertyHelperMixin import PropertyHelperMixin
7 from dabo.lib.doDefaultMixin import DoDefaultMixin
8 from dabo.lib.eventMixin import EventMixin
9 from dabo.lib.autosuper import autosuper
10 from dabo.dPref import dPref
11 from dabo.dLocalize import _
12
13 class Dummy(object):
14     # Much thanks to Robin Dunn for a workaround to a nasty problem that reared
15     # its head starting with wxPython 2.7, when we had to switch around the order
16     # of base classes so that wxPython's properties didn't trump Dabo's. Neither
17     # of us really understand what is happening, but it is at the Python level,
18     # and suffice it to say making dObject first inherit from this Dummy class
19     # fixes the issue, which was ultimately caused by autosuper's __slots__.
20     pass
21
22 class dObject(Dummy, autosuper, DoDefaultMixin, PropertyHelperMixin,
23         EventMixin):
24     """ The basic ancestor of all dabo objects."""
25     # Subclasses can set these to False, in which case they are responsible
26     # for maintaining the following call order:
27     #   self._beforeInit()
28     #   # optional stuff
29     #   super().__init__()
30     #   # optional stuff
31     #   self._afterInit()
32     # or, not calling super() at all, but remember to call _initProperties() and
33     # the call to setProperties() at the end!
34     _call_beforeInit, _call_afterInit, _call_initProperties = True, True, True
35
36     def __init__(self, properties=None, attProperties=None, *args, **kwargs):
37         if not hasattr(self, "_properties"):
38             self._properties = {}
39         if self._call_beforeInit:
40             self._beforeInit()
41         if self._call_initProperties:
42             self._initProperties()
43
44         # Now that user code has had an opportunity to set the properties, we can
45         # see if there are properties sent to the constructor which will augment
46         # or override the properties set in beforeInit().
47        
48         # Some classes that are not inherited from the ui-layer PEM mixin classes
49         # can have attProperties passed. Since these are all passed as strings, we
50         # need to convert them to their proper type and add them to the properties
51         # dict.
52         if properties is None:
53             properties = {}
54         if attProperties:
55             for prop, val in attProperties.items():
56                 if prop in ("designerClass", ):
57                     continue
58                 if prop in properties:
59                     # The properties value has precedence, so ignore.
60                     continue
61                 typ = type(getattr(self, prop))
62                 if not issubclass(typ, basestring):
63                     try:
64                         val = typ(val)
65                     except ValueError, e:
66                         # Sometimes int values can be stored as floats
67                         if typ in (int, long):
68                             val = float(val)
69                         else:
70                             raise e
71                 properties[prop] = val
72
73         # The keyword properties can come from either, both, or none of:
74         #    + the properties dict
75         #    + the kwargs dict
76         # Get them sanitized into one dict:
77         if properties is not None:
78             # Override the class values
79             for k,v in properties.items():
80                 self._properties[k] = v
81         properties = self._extractKeywordProperties(kwargs, self._properties)
82         if kwargs:
83             # Some kwargs haven't been handled.
84             bad = ", ".join(["'%s'" % kk for kk in kwargs.keys()])
85             raise TypeError, ("Invalid keyword arguments passed to %s: %s") % (self.__repr__(), bad)
86
87         if self._call_afterInit:
88             self._afterInit()
89         self.setProperties(properties)
90        
91         DoDefaultMixin.__init__(self)       
92         PropertyHelperMixin.__init__(self)     
93         EventMixin.__init__(self)       
94
95
96     def __repr__(self):
97         bc = self.BaseClass
98         if bc is None:
99             bc = self.__class__
100         strval = "%s" % bc
101         classname = strval.split("'")[1]
102         classparts = classname.split(".")
103         if ".ui.ui" in classname:
104             # Simplify the different UI toolkits
105             pos = classparts.index("ui")
106             classparts.pop(pos+1)
107         # Remove the duplicate class name that happens
108         # when the class name is the same as the file.
109         while (len(classparts) > 1) and (classparts[-1] == classparts[-2]):
110             classparts.pop()
111         classname = ".".join(classparts)
112
113         try:
114             nm = self.Name
115         except AttributeError:
116             nm = ""
117
118         regid = getattr(self, "RegID", "")
119         if regid:
120             nm = regid
121
122         if (not nm) or (nm == "?"):
123             # No name; use module.classname
124             nm = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
125         _id = self._getID()
126         return "<%(nm)s (baseclass %(classname)s, id:%(_id)s)>" % locals()
127
128
129     def _getID(self):
130         """Defaults to the Python id() function. Objects in sub-modules, such as the various
131         UI toolkits, can override to substitute something more relevant to them.
132         """
133         return id(self)
134
135
136     def beforeInit(self, *args, **kwargs):
137         """ Subclass hook. Called before the object is fully instantiated.
138         Usually, user code should override afterInit() instead, but there may be
139         cases where you need to set an attribute before the init stage is fully
140         underway.
141         """
142         pass
143        
144
145     def afterInit(self):
146         """ Subclass hook. Called after the object's __init__ has run fully.
147         Subclasses should place their __init__ code here in this hook, instead of
148         overriding __init__ directly, to avoid conflicting with base Dabo behavior.
149         """
150         pass
151        
152
153     def initProperties(self):
154         """ Hook for subclasses. User subclasses should set properties
155         here, such as:
156             self.Name = "MyTextBox"
157             self.BackColor = (192,192,192)
158         """
159         pass
160
161        
162     def initEvents(self):
163         """ Hook for subclasses. User code should do custom event binding
164         here, such as:
165             self.bindEvent(dEvents.GotFocus, self.customGotFocusHandler)
166         """
167         pass
168        
169            
170     def _beforeInit(self):
171         """Framework subclass hook."""
172         self.beforeInit()
173
174
175     def _initProperties(self):
176         """Framework subclass hook."""
177         self.initProperties()
178
179
180     def _afterInit(self):
181         """Framework subclass hook."""
182         self.afterInit()
183
184
185     def getAbsoluteName(self):
186         """Return the fully qualified name of the object."""
187         names = [self.Name, ]
188         object = self
189         while True:
190             try:
191                 parent = object.Parent
192             except AttributeError:
193                 # Parent not necessarily defined
194                 parent = None
195             if parent:
196                 try:
197                     name = parent.Name
198                 except AttributeError:
199                     name = "?"
200                 names.append(name)
201                 object = parent
202             else:
203                 break
204         names.reverse()
205         return ".".join(names)
206
207        
208     def getMethodList(cls, refresh=False):
209         """Return the list of (Dabo) methods for this class or instance."""
210         try:
211             methodList = cls.__methodList
212         except AttributeError:
213             methodList = None
214
215         if refresh:
216             methodList = None
217
218         if isinstance(methodList, list):
219             ## A prior call has already generated the methodList
220             return methodList
221
222         methodList = []
223         for c in cls.__mro__:
224             for item in dir(c):
225                 if item[0] in string.lowercase:
226                     if c.__dict__.has_key(item):
227                         if type(c.__dict__[item]) in (types.MethodType, types.FunctionType):
228                             if methodList.count(item) == 0:
229                                 methodList.append(item)
230         methodList.sort()
231         cls.__methodList = methodList
232         return methodList
233     getMethodList = classmethod(getMethodList)
234    
235    
236     def _addCodeAsMethod(self, cd):
237         """This method takes a dictionary containing method names as
238         keys, and the method code as the corresponding values, compiles
239         it, and adds the methods to this object. If the method name begins
240         with 'on', and dabo.autoBindEvents is True, an event binding will be
241         made just as with normal auto-binding. If the code cannot be
242         compiled successfully, an error message will be added
243         to the Dabo ErrorLog, and the method will not be added.
244         """
245         cls = self.__class__
246         for nm, code in cd.items():
247             try:
248                 code = code.replace("\n]", "]")
249                 compCode = compile(code, "", "exec")
250             except SyntaxError, e:
251                 dabo.errorLog.write(_("Method '%s' of object '%s' has the following error: %s")
252                         % (nm, self.Name, e))
253                 continue
254             # OK, we have the compiled code. Add it to the class definition.
255             # NOTE: if the method name and the name in the 'def' statement
256             # are not the same, the results are undefined, and will probably crash.
257             nmSpace = {}
258             exec compCode in nmSpace
259             mthd = nmSpace[nm]
260             exec "self.%s = %s.__get__(self)" % (nm, nm)
261             newMethod = new.instancemethod(mthd, self)
262             setattr(self, nm, newMethod)
263            
264
265     # Property definitions begin here
266     def _getApplication(self):
267         # dApp saves a ref to itself inside the dabo module object.
268         return dabo.dAppRef
269    
270    
271     def _getBaseClass(self):
272         # Every Dabo baseclass must set self._baseClass explicitly, to itself. For instance:
273         #   class dBackend(object)
274         #       def __init__(self):
275         #           self._baseClass = dBackend
276         # IOW, BaseClass isn't the actual Python base class of the object, but the Dabo-
277         # relative base class.
278         try:
279             return self._baseClass
280         except AttributeError:
281             return None
282
283        
284     def _getBasePrefKey(self):
285         try:
286             ret = self._basePrefKey
287         except AttributeError:
288             ret = self._basePrefKey = ""
289         return ret
290
291     def _setBasePrefKey(self, val):
292         if not isinstance(val, types.StringTypes):
293             raise TypeError, 'BasePrefKey must be a string.'
294         self._basePrefKey = val
295         pm = self.PreferenceManager
296         if pm is not None:
297             if not pm._key:
298                 pm._key = val
299
300
301     def _getClass(self):
302         return self.__class__
303
304
305     def _getLogEvents(self):
306         # This is expensive, as it is semi-recursive upwards in the containership
307         # until some parent object finally reports a LogEvents property. In normal
308         # use, this will be the Application object.
309         try:
310             le = self._logEvents
311         except AttributeError:
312             # Try to get the value from the parent object, or the Application if
313             # no Parent.
314             if self.Parent is not None:
315                 parent = self.Parent
316             else:
317                 if self == self.Application:
318                     parent = None
319                 else:
320                     parent = self.Application
321             try:
322                 le = parent.LogEvents
323             except AttributeError:
324                 le = []
325         return le
326            
327     def _setLogEvents(self, val):
328         self._logEvents = list(val)
329
330        
331     def _getName(self):
332         try:
333             return self._name
334         except AttributeError:
335             return "?"
336    
337     def _setName(self, val):
338         if not isinstance(val, types.StringTypes):
339             raise TypeError, 'Name must be a string.'
340         if not len(val.split()) == 1:
341             raise KeyError, 'Name must not contain any spaces'
342         self._name = val
343        
344        
345     def _getParent(self):
346         # Subclasses must override as necessary. Parent/child relationships
347         # don't exist for all nonvisual objects, and implementation of parent/child
348         # relationships will vary. This implementation is the simplest.
349         try:
350             return self._parent
351         except AttributeError:
352             return None
353        
354     def _setParent(self, obj):
355         # Subclasses must override as necessary.
356         self._parent = obj
357
358
359     def _getPreferenceManager(self):
360         try:
361             ret = self._preferenceManager
362         except AttributeError:
363             ret = None
364             if self.Application is not self:
365                 try:
366                     ret = self._preferenceManager = self.Application.PreferenceManager
367                 except AttributeError: pass
368             if ret is None:
369                 ret = self._preferenceManager = dPref(key=self.BasePrefKey)
370         return ret
371
372     def _setPreferenceManager(self, val):
373         if not isinstance(val, dPref):
374             raise TypeError, 'PreferenceManager must be a dPref object'
375         self._preferenceManager = val
376    
377
378     Application = property(_getApplication, None, None,
379             _("Read-only object reference to the Dabo Application object.  (dApp)."))
380    
381     BaseClass = property(_getBaseClass, None, None,
382             _("The base Dabo class of the object. Read-only.  (class)"))
383    
384     BasePrefKey = property(_getBasePrefKey, _setBasePrefKey, None,
385             _("Base key used when saving/restoring preferences  (str)"))
386    
387     Class = property(_getClass, None, None,
388             _("The class the object is based on. Read-only.  (class)"))
389    
390     LogEvents = property(_getLogEvents, _setLogEvents, None,
391             _("""Specifies which events to log.  (list of strings)
392             
393             If the first element is 'All', all events except the following listed events
394             will be logged.
395             Event logging is resource-intensive, so in addition to setting this LogEvents
396             property, you also need to make the following call:
397             >>> dabo.eventLogging = True
398             """))
399                    
400     Name = property(_getName, _setName, None,
401             _("The name of the object.  (str)"))
402    
403     Parent = property(_getParent, _setParent, None,
404             _("The containing object.  (obj)"))
405    
406     PreferenceManager = property(_getPreferenceManager, _setPreferenceManager, None,
407             _("Reference to the Preference Management object  (dPref)"))
408    
409
410 if __name__ == "__main__":
411     d = dObject()
412     print d.Application
413     app = dabo.dApp()
414     print d.Application
415
416     print _("Testing doDefault():")
417     class TestBase(list, dObject):
418         # No myMethod here
419         pass
420
421     class MyTest1(TestBase):
422         def myMethod(self):
423             print _("MyTest1.myMethod called.")
424             MyTest1.doDefault()
425        
426     class MyTest2(MyTest1): pass
427
428     class MyTest(MyTest2):
429         def myMethod(self):
430             print _("MyTest.myMethod called.")
431             MyTest.doDefault()
432
433     print _("Test 1: simple test:")         
434     t = MyTest()
435     t.myMethod()
436
437     print _("\nTest 2: diamond inheritence test:")
438
439     class A(dObject):
440         def meth(self, arg):
441             print self.__class__
442             arg.append("A")
443
444     class B(A):
445         def meth(self, arg):
446             print self.__class__
447             arg.append("B")
448             B.doDefault(arg)
449
450     class C(A):
451         def meth(self, arg):
452             print self.__class__
453             arg.append("C")
454             C.doDefault(arg)
455
456     class D(B,C):
457         def meth(self, arg):
458             print self.__class__
459             arg.append("D")
460             D.doDefault(arg)
461
462     t = D()
463     testList = []
464     t.meth(testList)
465     print testList
466
467     print _("\n\nTesting super():")
468     class TestBase(list, dObject):
469         # No myMethod here
470         pass
471
472     class MyTest1(TestBase):
473         def myMethod(self):
474             print _("MyTest1.myMethod called.")
475             self.super()
476        
477     class MyTest2(MyTest1): pass
478
479     class MyTest(MyTest2):
480         def myMethod(self):
481             print _("MyTest.myMethod called.")
482             self.super()
483
484     print _("Test 1: simple test:")         
485     t = MyTest()
486     t.myMethod()
487
488     print _("\nTest 2: diamond inheritence test:")
489
490     class A(dObject):
491         def meth(self, arg):
492             print self.__class__
493             arg.append("A")
494
495     class B(A):
496         def meth(self, arg):
497             print self.__class__
498             arg.append("B")
499             self.super(arg)
500
501     class C(A):
502         def meth(self, arg):
503             print self.__class__
504             arg.append("C")
505             self.super(arg)
506
507     class D(B,C):
508         def meth(self, arg):
509             print self.__class__
510             arg.append("D")
511             self.super(arg)
512
513     t = D()
514     testList = []
515     t.meth(testList)
516     print testList
Note: See TracBrowser for help on using the browser.