2 Creates functions at import time that are mixed into the
3 Accessibility.Accessible base class to make it more Pythonic.
5 Based on public domain code originally posted at
6 U{http://wwwx.cs.unc.edu/~parente/cgi-bin/RuntimeClassMixins}.
8 @var _ACCESSIBLE_CACHE: Pairs hash values for accessible objects to
9 L{_PropertyCache} bags. We do not store actual accessibles in the dictionary
10 because that would +1 their ref counts and cause __del__ to never be called
11 which is the method we rely on to properly invalidate cache entries.
12 @type _ACCESSIBLE_CACHE: dictionary
13 @var _CACHE_LEVEL: Current level of caching enabled. Checked dynamically by
15 @type _CACHE_LEVEL: integer
17 @author: Peter Parente
18 @organization: IBM Corporation
19 @copyright: Copyright (c) 2005, 2007 IBM Corporation
22 This library is free software; you can redistribute it and/or
23 modify it under the terms of the GNU Library General Public
24 License as published by the Free Software Foundation; either
25 version 2 of the License, or (at your option) any later version.
27 This library is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
30 Library General Public License for more details.
32 You should have received a copy of the GNU Library General Public
33 License along with this library; if not, write to the
34 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
35 Boston, MA 02111-1307, USA.
37 Portions of this code originally licensed and copyright (c) 2005, 2007
38 IBM Corporation under the BSD license, available at
39 U{http://www.opensource.org/licenses/bsd-license.php}
50 _ACCESSIBLE_CACHE = weakref.WeakValueDictionary()
51 _ACCESSIBLE_USER_DATA = weakref.WeakValueDictionary()
62 Gets the current level of caching.
64 @return: None indicating no caching is in effect.
65 L{constants.CACHE_INTERFACES} indicating all interface query results are
66 cached. L{constants.CACHE_PROPERTIES} indicating all basic accessible
67 properties are cached.
72 def setCacheLevel(val):
74 Sets the desired level of caching for all accessible objects created after
75 this function is invoked. Immediately clears the current accessible cache.
77 @param val: None indicating no caching is in effect.
78 L{constants.CACHE_INTERFACES} indicating all interface query results are
79 cached. L{constants.CACHE_PROPERTIES} indicating all basic accessible
80 properties are cached plus all interfaces.
84 if _CACHE_LEVEL != val:
85 # empty our accessible cache
86 _ACCESSIBLE_CACHE.clear()
87 # need to register/unregister for listeners depending on caching level
88 if val == constants.CACHE_PROPERTIES:
89 r = registry.Registry()
90 r.registerEventListener(_updateCache, *constants.CACHE_EVENTS)
92 r = registry.Registry()
93 r.deregisterEventListener(_updateCache, *constants.CACHE_EVENTS)
97 '''Forces a clear of the entire cache.'''
98 _ACCESSIBLE_CACHE.clear()
100 def printCache(template='%s'):
102 Prints the contents of the cache.
104 @param template: Format string to use when printing
105 @type template: string
107 print template % _ACCESSIBLE_CACHE
109 def _updateCache(event):
111 Invalidates an entry in the cache when the hash value of a source of an event
112 matches an entry in the cache.
114 @param event: One of the L{constants.CACHE_EVENTS} event types
115 @type event: L{event.Event}
118 del _ACCESSIBLE_CACHE[hash(event.source)]
122 def _getAndCache(acc, value_name, get_method):
124 If property caching is enabled, use the cached proprty, or get the
125 property and cache it. If property caching is disabled, simply get the
128 @param value_name: The name of the value, like 'role' or 'description'.
129 @type value_name: string
130 @param get_method: Method used to get the property, should not have any
132 @type get_method: callable
133 @return: Value of property we are retrieving.
136 if _CACHE_LEVEL != constants.CACHE_PROPERTIES:
139 cache = _ACCESSIBLE_CACHE
143 pc = acc._property_cache
144 except AttributeError:
148 # no cached info for this accessible yet
149 pc = _PropertyCache()
151 acc._property_cache = pc
154 value = getattr(pc, value_name)
155 except AttributeError:
156 # no cached property of this type
158 setattr(pc, value_name, value)
163 def _makeQuery(interface):
165 Builds a function querying to a specific interface and returns it.
167 @param interface: Class representing an AT-SPI interface
168 @type interface: class
169 @return: Function querying to the given interface
174 Queries an object for another interface.
176 @return: An object with the desired interface
178 @raise NotImplementedError: When the desired interface is not supported
180 iid = utils.getInterfaceIID(interface)
182 i = self._icache[iid]
184 # interface not cached
186 except AttributeError:
187 # determine if we're caching
188 caching = _CACHE_LEVEL is not None
190 # initialize the cache
193 # check if our cached result was an interface, or an indicator that the
194 # interface is not supported
196 raise NotImplementedError
201 # do the query remotely
202 i = self.queryInterface(iid)
204 i = i._narrow(interface)
208 # cache that the interface is not supported
210 self._icache[iid] = None
211 raise NotImplementedError
214 # cache the narrow'ed result, but only if we're caching for this object
215 self._icache[iid] = i
220 def _makeExceptionHandler(func):
222 Builds a function calling the one it wraps in try/except statements catching
225 @return: Function calling the method being wrapped
228 def _inner(self, *args, **kwargs):
230 # try calling the original func
231 return func(self, *args, **kwargs)
232 except ORBit.CORBA.NO_IMPLEMENT, e:
233 # raise Python exception
234 raise NotImplementedError(e)
235 except ORBit.CORBA.Exception, e:
236 # raise Python exception
240 def _mixInterfaces(cls, interfaces):
242 Add methods for querying to interfaces other than the base accessible to
245 @param cls: Class to mix interface methods into
247 @param interfaces: Classes representing AT-SPI interfaces
248 @type interfaces: list of class
250 # create functions in this module for all interfaces listed in constants
251 for interface in interfaces:
252 # build name of converter from the name of the interface
253 name = 'query%s' % utils.getInterfaceName(interface)
254 # build a function that queries to the given interface
255 func = _makeQuery(interface)
256 # build a new method that is a clone of the original function
257 method = new.function(func.func_code, func.func_globals, name,
258 func.func_defaults, func.func_closure)
259 # add the method to the given class
260 setattr(cls, name, method)
262 def _mixExceptions(cls):
264 Wraps all methods and properties in a class with handlers for CORBA
267 @param cls: Class to mix interface methods into
270 # get a method type as a reference from a known method
271 method_type = Accessibility.Accessible.getRole.__class__
272 # loop over all names in the new class
273 for name in cls.__dict__.keys():
274 obj = cls.__dict__[name]
275 # check if we're on a protected or private method
276 if name.startswith('_'):
278 # check if we're on a method
279 elif isinstance(obj, method_type):
280 # wrap the function in an exception handler
281 method = _makeExceptionHandler(obj)
282 # add the wrapped function to the class
283 setattr(cls, name, method)
284 # check if we're on a property
285 elif isinstance(obj, property):
286 # wrap the getters and setters
288 func = getattr(cls, obj.fget.__name__)
289 getter = _makeExceptionHandler(func)
293 func = getattr(cls, obj.fset.__name__)
294 setter = _makeExceptionHandler(func)
297 setattr(cls, name, property(getter, setter))
299 def _mixClass(cls, new_cls, ignore=[]):
301 Adds the methods in new_cls to cls. After mixing, all instances of cls will
302 have the new methods. If there is a method name clash, the method already in
303 cls will be prefixed with '_mix_' before the new method of the same name is
306 @note: _ is not the prefix because if you wind up with __ in front of a
307 variable, it becomes private and mangled when an instance is created.
308 Difficult to invoke from the mixin class.
310 @param cls: Existing class to mix features into
312 @param new_cls: Class containing features to add
314 @param ignore: Ignore these methods from the mixin
315 @type ignore: iterable
317 # loop over all names in the new class
318 for name, func in new_cls.__dict__.items():
321 if isinstance(func, types.FunctionType):
322 # build a new function that is a clone of the one from new_cls
323 method = new.function(func.func_code, func.func_globals, name,
324 func.func_defaults, func.func_closure)
326 # check if a method of the same name already exists in the target
327 old_method = getattr(cls, name)
328 except AttributeError:
331 # rename the old method so we can still call it if need be
332 setattr(cls, '_mix_'+name, old_method)
333 # add the clone to cls
334 setattr(cls, name, method)
335 elif isinstance(func, staticmethod):
337 # check if a method of the same name already exists in the target
338 old_method = getattr(cls, name)
339 except AttributeError:
342 # rename the old method so we can still call it if need be
343 setattr(cls, '_mix_'+name, old_method)
344 setattr(cls, name, func)
345 elif isinstance(func, property):
347 # check if a method of the same name already exists in the target
348 old_prop = getattr(cls, name)
349 except AttributeError:
352 # IMPORTANT: We save the old property before overwriting it, even
353 # though we never end up calling the old prop from our mixin class.
354 # If we don't save the old one, we seem to introduce a Python ref count
355 # problem where the property get/set methods disappear before we can
356 # use them at a later time. This is a minor waste of memory because
357 # a property is a class object and we only overwrite a few of them.
358 setattr(cls, '_mix_'+name, old_prop)
359 setattr(cls, name, func)
361 class _AccessibleMixin(object):
363 Defines methods to be added to the Accessibility.Accessible class. The
364 features defined here will be added to the Accessible class at run time so
365 that all instances of Accessible have them (i.e. there is no need to
366 explicitly wrap an Accessible in this class or derive a new class from it.)
368 @cvar SLOTTED_CLASSES: Mapping from raw Accessibility class to a new class
369 having the slots defined by L{SLOTS}
370 @type SLOTTED_CLASSES: dictionary
371 @cvar SLOTS: All slots to create
375 SLOTS = ('_icache', '_property_cache', '_user_data')
379 Creates a new class mimicking the one requested, but with extra named
380 defined in __slots__. The _cache attribute is used internally for interface
381 caching. The user_data field may be populated with whatever data structure
382 a client wishes to use. Neither is set to a default value by default.
384 Note that we can't simply mix __slots__ into this class because __slots__
385 has an effect only at class creation time.
387 We also do not completely obliterate __slots__ to allow __dict__ to be
388 instantiated as normal as reducing the initialization and memory overhead
389 of the millions of accessible objects that are created is a good thing for
392 @param cls: Accessibility object class
394 @return: Instance of the new class
398 # check if we've already created a new version of the class
399 new_cls = _AccessibleMixin.SLOTTED_CLASSES[cls]
401 # create the new class if not
402 new_cls = type(cls.__name__, (cls,),
403 {'__module__' : cls.__module__,
404 '__slots__' : _AccessibleMixin.SLOTS})
405 _AccessibleMixin.SLOTTED_CLASSES[cls] = new_cls
406 obj = cls._mix___new__(new_cls)
411 Decrements the reference count on the accessible object when there are no
412 Python references to this object. This provides automatic reference
413 counting for AT-SPI objects. Also removes this object from the cache if
414 we're caching properties.
423 Iterator that yields one accessible child per iteration. If an exception is
424 encountered, None is yielded instead.
426 @return: A child accessible
427 @rtype: Accessibility.Accessible
429 for i in xrange(self.childCount):
431 yield self.getChildAtIndex(i)
437 Gets a human readable representation of the accessible.
439 @return: Role and name information for the accessible
443 return '[%s | %s]' % (self.getRoleName(), self.name)
447 def __nonzero__(self):
449 @return: True, always
454 def __getitem__(self, index):
456 Thin wrapper around getChildAtIndex.
458 @param index: Index of desired child
460 @return: Accessible child
461 @rtype: Accessibility.Accessible
470 return self.getChildAtIndex(index)
474 Thin wrapper around childCount.
476 @return: Number of child accessibles
479 return self.childCount
481 def _get_user_data(self):
483 Get user_data from global dictionay fo this accessible.
485 @return: Any data the user assigned, or None.
488 global _ACCESSIBLE_USER_DATA
493 except AttributeError:
495 ud = _ACCESSIBLE_USER_DATA[h]
497 # no cached info for this object yet
499 _ACCESSIBLE_USER_DATA[h] = ud
504 def _set_user_data(self, value):
506 Set arbitrary data to user_data.
508 @param value: Value to set in user_data
511 global _ACCESSIBLE_USER_DATA
516 except AttributeError:
518 ud = _ACCESSIBLE_USER_DATA[h]
520 # no cached info for this object yet
522 _ACCESSIBLE_USER_DATA[h] = ud
527 user_data = property(_get_user_data, _set_user_data)
531 Gets the name of the accessible from the cache if it is available,
532 otherwise, fetches it remotely.
534 @return: Name of the accessible
537 return _getAndCache(self, 'name', self._get_name)
539 name = property(_get_name, Accessibility.Accessible._set_name)
541 def _get_parent(self):
543 Gets the parent of the accessible from the cache if it is available,
544 otherwise, fetches it remotely.
546 @return: Parent of the accessible
547 @rtype: Accessibility.Accessible
549 return _getAndCache(self, 'parent', self._get_parent)
551 parent = property(_get_parent)
553 def getRoleName(self):
555 Gets the unlocalized role name of the accessible from the cache if it is
556 available, otherwise, fetches it remotely.
558 @return: Role name of the accessible
561 return _getAndCache(self, 'rolename', self._mix_getRoleName)
565 Gets the role of the accessible from the cache if it is
566 available, otherwise, fetches it remotely.
568 @return: Role of the accessible
569 @rtype: Accessibility.Role
571 return _getAndCache(self, 'role', self._mix_getRole)
573 def _get_description(self):
575 Gets the description of the accessible from the cache if it is available,
576 otherwise, fetches it remotely.
578 @return: Description of the accessible
581 return _getAndCache(self, 'description', self._get_description)
583 description = property(_get_description,
584 Accessibility.Accessible._set_description)
586 def getIndexInParent(self):
588 Gets the index of this accessible in its parent. Uses the implementation of
589 this method provided by the Accessibility.Accessible object, but checks the
590 bound of the value to ensure it is not outside the range of childCount
591 reported by this accessible's parent.
593 @return: Index of this accessible in its parent
596 i = self._mix_getIndexInParent()
598 # correct for out-of-bounds index reporting
599 return min(self.parent.childCount-1, i)
600 except AttributeError:
601 # return sentinel if there is no parent
604 def getApplication(self):
606 Gets the most-parent accessible (the application) of this
607 accessible. Tries using the getApplication method introduced in
608 AT-SPI 1.7.0 first before resorting to traversing parent links.
610 @warning: Cycles involving more than the previously traversed accessible
611 are not detected by this code.
612 @return: Application object
613 @rtype: Accessibility.Application
616 app = self._mix_getApplication()
617 except AttributeError:
620 # Some toolkits (e.g., Java) do not support getApplication yet and
621 # will return None as a result.
626 # If we didn't find anything, traverse up the tree, making sure to
627 # attempt to turn the thing we found into an Application object.
631 while curr.parent is not None and (not curr.parent == curr):
634 return curr._narrow(Accessibility.Application)
638 class _RelationMixin(object):
640 Defines methods to be added to the Relation class. At this time it only
641 overrides L{_RelationMixin.getTarget} which by the IDL's standard is
642 supposed to return CORBA.Objects but we expect LAccessibility.Accessible
643 objects (see http://bugzilla.gnome.org/show_bug.cgi?id=435833).
644 This seems to be a problem especially with the Java implementation of CORBA.
646 def getTarget(self, index):
648 Overrides the regular getTarget to return Accessibility.Accessible
651 @return: The 'nth' target of this Relation.
652 @rtype: Accessibility.Accessible
654 target = self._mix_getTarget(index)
656 return target._narrow(Accessibility.Accessible)
658 class _UnrefMixin(object):
660 This mixin addresses the issue we have with unreferencing non-primitives.
664 Unrefence the instance when Python GCs it. Why do we need this twice?
671 # 1. mix the exception handlers into all queryable interfaces
672 map(_mixExceptions, constants.ALL_INTERFACES)
673 # 2. mix the exception handlers into other Accessibility objects
674 map(_mixExceptions, [Accessibility.StateSet])
675 # 3. mix the new functions
676 _mixClass(Accessibility.Accessible, _AccessibleMixin,
677 ['_get_name', '_get_description', '_get_parent'])
678 # 4. mix queryInterface convenience methods
679 _mixInterfaces(Accessibility.Accessible, constants.ALL_INTERFACES)
680 # 5. mix Relation class
681 _mixClass(Accessibility.Relation, _RelationMixin)
682 # 6. mix in neccessary unrefs
683 map(lambda cls: _mixClass(cls, _UnrefMixin),
684 (Accessibility.StateSet,Accessibility.Relation))