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):
123 if _CACHE_LEVEL != constants.CACHE_PROPERTIES:
126 cache = _ACCESSIBLE_CACHE
130 pc = acc._property_cache
131 except AttributeError:
135 # no cached info for this accessible yet
136 pc = _PropertyCache()
138 acc._property_cache = pc
141 value = getattr(pc, value_name)
142 except AttributeError:
143 # no cached property of this type
145 setattr(pc, value_name, value)
150 def _makeQuery(interface):
152 Builds a function querying to a specific interface and returns it.
154 @param interface: Class representing an AT-SPI interface
155 @type interface: class
156 @return: Function querying to the given interface
161 Queries an object for another interface.
163 @return: An object with the desired interface
165 @raise NotImplementedError: When the desired interface is not supported
167 iid = utils.getInterfaceIID(interface)
169 i = self._icache[iid]
171 # interface not cached
173 except AttributeError:
174 # determine if we're caching
175 caching = _CACHE_LEVEL is not None
177 # initialize the cache
180 # check if our cached result was an interface, or an indicator that the
181 # interface is not supported
183 raise NotImplementedError
188 # do the query remotely
189 i = self.queryInterface(iid)
191 i = i._narrow(interface)
195 # cache that the interface is not supported
197 self._icache[iid] = None
198 raise NotImplementedError
201 # cache the narrow'ed result, but only if we're caching for this object
202 self._icache[iid] = i
207 def _makeExceptionHandler(func):
209 Builds a function calling the one it wraps in try/except statements catching
212 @return: Function calling the method being wrapped
215 def _inner(self, *args, **kwargs):
217 # try calling the original func
218 return func(self, *args, **kwargs)
219 except ORBit.CORBA.NO_IMPLEMENT, e:
220 # raise Python exception
221 raise NotImplementedError(e)
222 except ORBit.CORBA.Exception, e:
223 # raise Python exception
227 def _mixInterfaces(cls, interfaces):
229 Add methods for querying to interfaces other than the base accessible to
232 @param cls: Class to mix interface methods into
234 @param interfaces: Classes representing AT-SPI interfaces
235 @type interfaces: list of class
237 # create functions in this module for all interfaces listed in constants
238 for interface in interfaces:
239 # build name of converter from the name of the interface
240 name = 'query%s' % utils.getInterfaceName(interface)
241 # build a function that queries to the given interface
242 func = _makeQuery(interface)
243 # build a new method that is a clone of the original function
244 method = new.function(func.func_code, func.func_globals, name,
245 func.func_defaults, func.func_closure)
246 # add the method to the given class
247 setattr(cls, name, method)
249 def _mixExceptions(cls):
251 Wraps all methods and properties in a class with handlers for CORBA
254 @param cls: Class to mix interface methods into
257 # get a method type as a reference from a known method
258 method_type = Accessibility.Accessible.getRole.__class__
259 # loop over all names in the new class
260 for name in cls.__dict__.keys():
261 obj = cls.__dict__[name]
262 # check if we're on a protected or private method
263 if name.startswith('_'):
265 # check if we're on a method
266 elif isinstance(obj, method_type):
267 # wrap the function in an exception handler
268 method = _makeExceptionHandler(obj)
269 # add the wrapped function to the class
270 setattr(cls, name, method)
271 # check if we're on a property
272 elif isinstance(obj, property):
273 # wrap the getters and setters
275 func = getattr(cls, obj.fget.__name__)
276 getter = _makeExceptionHandler(func)
280 func = getattr(cls, obj.fset.__name__)
281 setter = _makeExceptionHandler(func)
284 setattr(cls, name, property(getter, setter))
286 def _mixClass(cls, new_cls, ignore=[]):
288 Adds the methods in new_cls to cls. After mixing, all instances of cls will
289 have the new methods. If there is a method name clash, the method already in
290 cls will be prefixed with '_mix_' before the new method of the same name is
293 @note: _ is not the prefix because if you wind up with __ in front of a
294 variable, it becomes private and mangled when an instance is created.
295 Difficult to invoke from the mixin class.
297 @param cls: Existing class to mix features into
299 @param new_cls: Class containing features to add
301 @param ignore: Ignore these methods from the mixin
302 @type ignore: iterable
304 # loop over all names in the new class
305 for name, func in new_cls.__dict__.items():
308 if isinstance(func, types.FunctionType):
309 # build a new function that is a clone of the one from new_cls
310 method = new.function(func.func_code, func.func_globals, name,
311 func.func_defaults, func.func_closure)
313 # check if a method of the same name already exists in the target
314 old_method = getattr(cls, name)
315 except AttributeError:
318 # rename the old method so we can still call it if need be
319 setattr(cls, '_mix_'+name, old_method)
320 # add the clone to cls
321 setattr(cls, name, method)
322 elif isinstance(func, staticmethod):
324 # check if a method of the same name already exists in the target
325 old_method = getattr(cls, name)
326 except AttributeError:
329 # rename the old method so we can still call it if need be
330 setattr(cls, '_mix_'+name, old_method)
331 setattr(cls, name, func)
332 elif isinstance(func, property):
334 # check if a method of the same name already exists in the target
335 old_prop = getattr(cls, name)
336 except AttributeError:
339 # IMPORTANT: We save the old property before overwriting it, even
340 # though we never end up calling the old prop from our mixin class.
341 # If we don't save the old one, we seem to introduce a Python ref count
342 # problem where the property get/set methods disappear before we can
343 # use them at a later time. This is a minor waste of memory because
344 # a property is a class object and we only overwrite a few of them.
345 setattr(cls, '_mix_'+name, old_prop)
346 setattr(cls, name, func)
348 class _AccessibleMixin(object):
350 Defines methods to be added to the Accessibility.Accessible class. The
351 features defined here will be added to the Accessible class at run time so
352 that all instances of Accessible have them (i.e. there is no need to
353 explicitly wrap an Accessible in this class or derive a new class from it.)
355 @cvar SLOTTED_CLASSES: Mapping from raw Accessibility class to a new class
356 having the slots defined by L{SLOTS}
357 @type SLOTTED_CLASSES: dictionary
358 @cvar SLOTS: All slots to create
362 SLOTS = ('_icache', '_property_cache', '_user_data')
366 Creates a new class mimicking the one requested, but with extra named
367 defined in __slots__. The _cache attribute is used internally for interface
368 caching. The user_data field may be populated with whatever data structure
369 a client wishes to use. Neither is set to a default value by default.
371 Note that we can't simply mix __slots__ into this class because __slots__
372 has an effect only at class creation time.
374 We also do not completely obliterate __slots__ to allow __dict__ to be
375 instantiated as normal as reducing the initialization and memory overhead
376 of the millions of accessible objects that are created is a good thing for
379 @param cls: Accessibility object class
381 @return: Instance of the new class
385 # check if we've already created a new version of the class
386 new_cls = _AccessibleMixin.SLOTTED_CLASSES[cls]
388 # create the new class if not
389 new_cls = type(cls.__name__, (cls,),
390 {'__module__' : cls.__module__,
391 '__slots__' : _AccessibleMixin.SLOTS})
392 _AccessibleMixin.SLOTTED_CLASSES[cls] = new_cls
393 obj = cls._mix___new__(new_cls)
398 Decrements the reference count on the accessible object when there are no
399 Python references to this object. This provides automatic reference
400 counting for AT-SPI objects. Also removes this object from the cache if
401 we're caching properties.
410 Iterator that yields one accessible child per iteration. If an exception is
411 encountered, None is yielded instead.
413 @return: A child accessible
414 @rtype: Accessibility.Accessible
416 for i in xrange(self.childCount):
418 yield self.getChildAtIndex(i)
424 Gets a human readable representation of the accessible.
426 @return: Role and name information for the accessible
430 return '[%s | %s]' % (self.getRoleName(), self.name)
434 def __nonzero__(self):
436 @return: True, always
441 def __getitem__(self, index):
443 Thin wrapper around getChildAtIndex.
445 @param index: Index of desired child
447 @return: Accessible child
448 @rtype: Accessibility.Accessible
457 return self.getChildAtIndex(index)
461 Thin wrapper around childCount.
463 @return: Number of child accessibles
466 return self.childCount
468 def _get_user_data(self):
470 Get user_data from global dictionay fo this accessible.
472 @return: Any data the user assigned, or None.
475 global _ACCESSIBLE_USER_DATA
480 except AttributeError:
482 ud = _ACCESSIBLE_USER_DATA[h]
484 # no cached info for this object yet
486 _ACCESSIBLE_USER_DATA[h] = ud
491 def _set_user_data(self, value):
493 Set arbitrary data to user_data.
495 @param value: Value to set in user_data
498 global _ACCESSIBLE_USER_DATA
503 except AttributeError:
505 ud = _ACCESSIBLE_USER_DATA[h]
507 # no cached info for this object yet
509 _ACCESSIBLE_USER_DATA[h] = ud
514 user_data = property(_get_user_data, _set_user_data)
518 Gets the name of the accessible from the cache if it is available,
519 otherwise, fetches it remotely.
521 @return: Name of the accessible
524 return _getAndCache(self, 'name', self._get_name)
526 name = property(_get_name, Accessibility.Accessible._set_name)
528 def _get_parent(self):
530 Gets the parent of the accessible from the cache if it is available,
531 otherwise, fetches it remotely.
533 @return: Parent of the accessible
534 @rtype: Accessibility.Accessible
536 return _getAndCache(self, 'parent', self._get_parent)
538 parent = property(_get_parent)
540 def getRoleName(self):
542 Gets the unlocalized role name of the accessible from the cache if it is
543 available, otherwise, fetches it remotely.
545 @return: Role name of the accessible
548 return _getAndCache(self, 'rolename', self._mix_getRoleName)
552 Gets the role of the accessible from the cache if it is
553 available, otherwise, fetches it remotely.
555 @return: Role of the accessible
556 @rtype: Accessibility.Role
558 return _getAndCache(self, 'role', self._mix_getRole)
560 def _get_description(self):
562 Gets the description of the accessible from the cache if it is available,
563 otherwise, fetches it remotely.
565 @return: Description of the accessible
568 return _getAndCache(self, 'description', self._get_description)
570 description = property(_get_description,
571 Accessibility.Accessible._set_description)
573 def getIndexInParent(self):
575 Gets the index of this accessible in its parent. Uses the implementation of
576 this method provided by the Accessibility.Accessible object, but checks the
577 bound of the value to ensure it is not outside the range of childCount
578 reported by this accessible's parent.
580 @return: Index of this accessible in its parent
583 i = self._mix_getIndexInParent()
585 # correct for out-of-bounds index reporting
586 return min(self.parent.childCount-1, i)
587 except AttributeError:
588 # return sentinel if there is no parent
591 def getApplication(self):
593 Gets the most-parent accessible (the application) of this accessible. Tries
594 using the getApplication method introduced in AT-SPI 1.7.0 first before
595 resorting to traversing parent links.
597 @warning: Cycles involving more than the previously traversed accessible
598 are not detected by this code.
599 @return: Application object
600 @rtype: Accessibility.Application
603 return self._mix_getApplication()
604 except AttributeError:
608 while curr.parent is not None and (not curr.parent == curr):
613 # return None if the application isn't reachable for any reason
616 class _RelationMixin(object):
618 Defines methods to be added to the Relation class. At this time it only
619 overrides L{_RelationMixin.getTarget} which by the IDL's standard is
620 supposed to return CORBA.Objects but we expect LAccessibility.Accessible
621 objects (see http://bugzilla.gnome.org/show_bug.cgi?id=435833).
622 This seems to be a problem especially with the Java implementation of CORBA.
624 def getTarget(self, index):
626 Overrides the regular getTarget to return Accessibility.Accessible
629 @return: The 'nth' target of this Relation.
630 @rtype: Accessibility.Accessible
632 target = self._mix_getTarget(index)
634 return target._narrow(Accessibility.Accessible)
636 class _UnrefMixin(object):
638 This mixin addresses the issue we have with unreferencing non-primitives.
642 Unrefence the instance when Python GCs it. Why do we need this twice?
649 # 1. mix the exception handlers into all queryable interfaces
650 map(_mixExceptions, constants.ALL_INTERFACES)
651 # 2. mix the exception handlers into other Accessibility objects
652 map(_mixExceptions, [Accessibility.StateSet])
653 # 3. mix the new functions
654 _mixClass(Accessibility.Accessible, _AccessibleMixin,
655 ['_get_name', '_get_description', '_get_parent'])
656 # 4. mix queryInterface convenience methods
657 _mixInterfaces(Accessibility.Accessible, constants.ALL_INTERFACES)
658 # 5. mix Relation class
659 _mixClass(Accessibility.Relation, _RelationMixin)
660 # 6. mix in neccessary unrefs
661 map(lambda cls: _mixClass(cls, _UnrefMixin),
662 (Accessibility.StateSet,Accessibility.Relation))