Remove cspi_bus from cspi-lowlevel.h--it is defined in spi-private.h
[platform/core/uifw/at-spi2-atk.git] / python / pyatspi / accessible.py
1 '''
2 Creates functions at import time that are mixed into the 
3 Accessibility.Accessible base class to make it more Pythonic.
4
5 Based on public domain code originally posted at 
6 U{http://wwwx.cs.unc.edu/~parente/cgi-bin/RuntimeClassMixins}.
7
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
14   L{_AccessibleMixin}
15 @type _CACHE_LEVEL: integer
16
17 @author: Peter Parente
18 @organization: IBM Corporation
19 @copyright: Copyright (c) 2005, 2007 IBM Corporation
20 @license: LGPL
21
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.
26
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.
31
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.
36
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}
40 '''
41 import new
42 import types
43 import ORBit
44 import Accessibility
45 import constants
46 import utils
47 import registry
48 import weakref
49
50 _ACCESSIBLE_CACHE = weakref.WeakValueDictionary()
51 _ACCESSIBLE_USER_DATA = weakref.WeakValueDictionary()
52 _CACHE_LEVEL = None
53
54 class _PropertyCache:
55   pass
56
57 class _UserData:
58   value = None
59
60 def getCacheLevel():
61   '''
62   Gets the current level of caching.
63   
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.
68   @rtype: integer
69   '''
70   return _CACHE_LEVEL
71
72 def setCacheLevel(val):
73   '''
74   Sets the desired level of caching for all accessible objects created after
75   this function is invoked. Immediately clears the current accessible cache.
76   
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.
81   @type val: integer
82   '''
83   global _CACHE_LEVEL
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)
91     else:
92       r = registry.Registry()
93       r.deregisterEventListener(_updateCache, *constants.CACHE_EVENTS)
94   _CACHE_LEVEL = val
95   
96 def clearCache():
97   '''Forces a clear of the entire cache.'''
98   _ACCESSIBLE_CACHE.clear()
99   
100 def printCache(template='%s'):
101   '''
102   Prints the contents of the cache.
103   
104   @param template: Format string to use when printing
105   @type template: string
106   '''
107   print template % _ACCESSIBLE_CACHE
108
109 def _updateCache(event):
110   '''
111   Invalidates an entry in the cache when the hash value of a source of an event
112   matches an entry in the cache.
113   
114   @param event: One of the L{constants.CACHE_EVENTS} event types
115   @type event: L{event.Event}
116   '''
117   try:
118     del _ACCESSIBLE_CACHE[hash(event.source)]
119   except KeyError:
120     return
121
122 def _getAndCache(acc, value_name, get_method):
123   '''
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 
126   property.
127
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 
131   arguments.
132   @type get_method: callable
133   @return: Value of property we are retrieving.
134   @rtype: object
135   '''
136   if _CACHE_LEVEL != constants.CACHE_PROPERTIES:
137     return get_method()
138     
139   cache = _ACCESSIBLE_CACHE
140   h = hash(acc)
141
142   try:
143     pc = acc._property_cache
144   except AttributeError:
145     try:
146       pc = cache[h]
147     except KeyError:
148       # no cached info for this accessible yet
149       pc = _PropertyCache()
150       cache[h] = pc
151     acc._property_cache = pc  
152     
153   try:
154     value = getattr(pc, value_name)
155   except AttributeError:
156     # no cached property of this type
157     value = get_method()
158     setattr(pc, value_name, value)
159     
160   return value
161   
162
163 def _makeQuery(interface):
164   '''
165   Builds a function querying to a specific interface and returns it.
166   
167   @param interface: Class representing an AT-SPI interface
168   @type interface: class
169   @return: Function querying to the given interface
170   @rtype: function
171   '''
172   def _inner(self):
173     '''
174     Queries an object for another interface.
175   
176     @return: An object with the desired interface
177     @rtype: object
178     @raise NotImplementedError: When the desired interface is not supported    
179     '''
180     iid = utils.getInterfaceIID(interface)
181     try:
182       i = self._icache[iid]
183     except KeyError:
184       # interface not cached
185       caching = True
186     except AttributeError:
187       # determine if we're caching
188       caching = _CACHE_LEVEL is not None
189       if caching:
190         # initialize the cache
191         self._icache = {}
192     else:
193       # check if our cached result was an interface, or an indicator that the
194       # interface is not supported
195       if i is None:
196         raise NotImplementedError
197       else:
198         return i
199
200     try:
201       # do the query remotely
202       i = self.queryInterface(iid)
203       if i is not None:
204         i = i._narrow(interface)
205     except Exception, e:
206       raise LookupError(e)      
207     if i is None:
208       # cache that the interface is not supported
209       if caching:
210         self._icache[iid] = None
211       raise NotImplementedError
212     
213     if caching:
214       # cache the narrow'ed result, but only if we're caching for this object
215       self._icache[iid] = i
216     return i
217   
218   return _inner
219
220 def _makeExceptionHandler(func):
221   '''
222   Builds a function calling the one it wraps in try/except statements catching
223   CORBA exceptions.
224   
225   @return: Function calling the method being wrapped
226   @rtype: function
227   '''
228   def _inner(self, *args, **kwargs):
229     try:
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
237       raise LookupError(e)
238   return _inner
239
240 def _mixInterfaces(cls, interfaces):
241   '''
242   Add methods for querying to interfaces other than the base accessible to
243   the given class.
244   
245   @param cls: Class to mix interface methods into
246   @type cls: class
247   @param interfaces: Classes representing AT-SPI interfaces
248   @type interfaces: list of class
249   '''
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)
261
262 def _mixExceptions(cls):
263   '''
264   Wraps all methods and properties in a class with handlers for CORBA 
265   exceptions.
266   
267   @param cls: Class to mix interface methods into
268   @type cls: class
269   '''
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('_'):
277       continue
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
287       if obj.fget:
288         func = getattr(cls, obj.fget.__name__)
289         getter = _makeExceptionHandler(func)
290       else:
291         getter = None
292       if obj.fset:
293         func = getattr(cls, obj.fset.__name__)
294         setter = _makeExceptionHandler(func)
295       else:
296         setter = None
297       setattr(cls, name, property(getter, setter))
298
299 def _mixClass(cls, new_cls, ignore=[]):
300   '''
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 
304   mixed in.
305   
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.
309
310   @param cls: Existing class to mix features into
311   @type cls: class
312   @param new_cls: Class containing features to add
313   @type new_cls: class
314   @param ignore: Ignore these methods from the mixin
315   @type ignore: iterable
316   '''
317   # loop over all names in the new class
318   for name, func in new_cls.__dict__.items():
319     if name in ignore:
320       continue
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)
325       try:
326         # check if a method of the same name already exists in the target
327         old_method = getattr(cls, name)
328       except AttributeError:
329         pass
330       else:
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):
336       try:
337         # check if a method of the same name already exists in the target
338         old_method = getattr(cls, name)
339       except AttributeError:
340         pass
341       else:
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):
346       try:
347         # check if a method of the same name already exists in the target
348         old_prop = getattr(cls, name)
349       except AttributeError:
350         pass
351       else:
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)
360
361 class _AccessibleMixin(object):
362   '''
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.)
367   
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
372   @type SLOTS: tuple
373   '''
374   SLOTTED_CLASSES = {}
375   SLOTS = ('_icache', '_property_cache', '_user_data')
376   
377   def __new__(cls):
378     '''
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.
383     
384     Note that we can't simply mix __slots__ into this class because __slots__
385     has an effect only at class creation time. 
386     
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
390     many clients.
391     
392     @param cls: Accessibility object class
393     @type cls: class
394     @return: Instance of the new class
395     @rtype: object
396     '''
397     try:
398       # check if we've already created a new version of the class
399       new_cls = _AccessibleMixin.SLOTTED_CLASSES[cls]
400     except KeyError:
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)
407     return obj
408   
409   def __del__(self):
410     '''    
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. 
415     '''
416     try:
417       self.unref()
418     except Exception:
419       pass
420     
421   def __iter__(self):
422     '''
423     Iterator that yields one accessible child per iteration. If an exception is
424     encountered, None is yielded instead.
425     
426     @return: A child accessible
427     @rtype: Accessibility.Accessible
428     '''
429     for i in xrange(self.childCount):
430       try:
431         yield self.getChildAtIndex(i)
432       except LookupError:
433         yield None
434     
435   def __str__(self):
436     '''
437     Gets a human readable representation of the accessible.
438     
439     @return: Role and name information for the accessible
440     @rtype: string
441     '''
442     try:
443       return '[%s | %s]' % (self.getRoleName(), self.name)
444     except Exception:
445       return '[DEAD]'
446     
447   def __nonzero__(self):
448     '''
449     @return: True, always
450     @rtype: boolean
451     '''
452     return True
453     
454   def __getitem__(self, index):
455     '''
456     Thin wrapper around getChildAtIndex.
457     
458     @param index: Index of desired child
459     @type index: integer
460     @return: Accessible child
461     @rtype: Accessibility.Accessible
462     '''
463     n = self.childCount
464     if index >= n:
465       raise IndexError
466     elif index < -n:
467       raise IndexError
468     elif index < 0:
469       index += n
470     return self.getChildAtIndex(index)
471   
472   def __len__(self):
473     '''
474     Thin wrapper around childCount.
475     
476     @return: Number of child accessibles
477     @rtype: integer
478     '''
479     return self.childCount
480   
481   def _get_user_data(self):
482     '''
483     Get user_data from global dictionay fo this accessible.
484
485     @return: Any data the user assigned, or None.
486     @rtype: object
487     '''
488     global _ACCESSIBLE_USER_DATA
489     h = hash(self)
490     
491     try:
492       ud = self._user_data
493     except AttributeError:
494       try:
495         ud = _ACCESSIBLE_USER_DATA[h]
496       except KeyError:
497         # no cached info for this object yet
498         ud = _UserData()
499         _ACCESSIBLE_USER_DATA[h] = ud
500
501     self._user_data = ud
502     return ud.value
503
504   def _set_user_data(self, value):
505     '''
506     Set arbitrary data to user_data.
507
508     @param value: Value to set in user_data
509     @type value: object
510     '''
511     global _ACCESSIBLE_USER_DATA
512     h = hash(self)
513     
514     try:
515       ud = self._user_data
516     except AttributeError:
517       try:
518         ud = _ACCESSIBLE_USER_DATA[h]
519       except KeyError:
520         # no cached info for this object yet
521         ud = _UserData()
522         _ACCESSIBLE_USER_DATA[h] = ud
523
524     self._user_data = ud
525     ud.value = value
526
527   user_data = property(_get_user_data, _set_user_data)
528
529   def _get_name(self):
530     '''
531     Gets the name of the accessible from the cache if it is available, 
532     otherwise, fetches it remotely.
533     
534     @return: Name of the accessible
535     @rtype: string
536     '''
537     return _getAndCache(self, 'name', self._get_name)
538
539   name = property(_get_name, Accessibility.Accessible._set_name)
540
541   def _get_parent(self):
542     '''
543     Gets the parent of the accessible from the cache if it is available, 
544     otherwise, fetches it remotely.
545     
546     @return: Parent of the accessible
547     @rtype: Accessibility.Accessible
548     '''
549     return _getAndCache(self, 'parent', self._get_parent)
550
551   parent = property(_get_parent)
552   
553   def getRoleName(self):
554     '''
555     Gets the unlocalized role name of the accessible from the cache if it is 
556     available, otherwise, fetches it remotely.
557     
558     @return: Role name of the accessible
559     @rtype: string
560     '''
561     return _getAndCache(self, 'rolename', self._mix_getRoleName)
562
563   def getRole(self):
564     '''
565     Gets the role of the accessible from the cache if it is 
566     available, otherwise, fetches it remotely.
567     
568     @return: Role of the accessible
569     @rtype: Accessibility.Role
570     '''
571     return _getAndCache(self, 'role', self._mix_getRole)
572
573   def _get_description(self):
574     '''    
575     Gets the description of the accessible from the cache if it is available,
576     otherwise, fetches it remotely.
577     
578     @return: Description of the accessible
579     @rtype: string
580     '''
581     return _getAndCache(self, 'description', self._get_description)
582     
583   description = property(_get_description, 
584                          Accessibility.Accessible._set_description)
585   
586   def getIndexInParent(self):
587     '''
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.
592     
593     @return: Index of this accessible in its parent
594     @rtype: integer
595     '''
596     i = self._mix_getIndexInParent()
597     try:
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
602       return -1
603
604   def getApplication(self):
605     '''
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.
609     
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
614     '''
615     try:
616       app = self._mix_getApplication()
617     except AttributeError:
618       app = None
619
620     # Some toolkits (e.g., Java) do not support getApplication yet and
621     # will return None as a result.
622     #
623     if app:
624       return app
625
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.
628     #
629     curr = self
630     try:
631       while curr.parent is not None and (not curr.parent == curr):
632         curr = curr.parent
633       curr.ref()
634       return curr._narrow(Accessibility.Application)
635     except:
636       return None
637
638 class _RelationMixin(object):
639   '''
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.
645   '''
646   def getTarget(self, index):
647     '''
648     Overrides the regular getTarget to return Accessibility.Accessible
649     objects.
650
651     @return: The 'nth' target of this Relation.
652     @rtype: Accessibility.Accessible
653     '''
654     target = self._mix_getTarget(index)
655     target.ref()
656     return target._narrow(Accessibility.Accessible)
657
658 class _UnrefMixin(object):
659   '''
660   This mixin addresses the issue we have with unreferencing non-primitives.
661   '''
662   def __del__(self):
663     '''
664     Unrefence the instance when Python GCs it. Why do we need this twice?
665     '''
666     try:
667       self.unref()
668     except Exception:
669       pass
670
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))