X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=pyatspi%2Faccessible.py;h=80a75899bd1eceea23e229772a77d72afeb9beb2;hb=4a674680808b73499b947cf95d36012eef5fb394;hp=a2a047be02c134bf0399e271c995bd0bab4cc5fd;hpb=5e43c51b4e5324b3bebdc29a4d8ed26a8a4d1bb4;p=platform%2Fcore%2Fuifw%2Fat-spi2-atk.git diff --git a/pyatspi/accessible.py b/pyatspi/accessible.py index a2a047b..80a7589 100644 --- a/pyatspi/accessible.py +++ b/pyatspi/accessible.py @@ -45,13 +45,17 @@ import Accessibility import constants import utils import registry +import weakref -_ACCESSIBLE_CACHE = {} +_ACCESSIBLE_CACHE = weakref.WeakValueDictionary() +_ACCESSIBLE_USER_DATA = weakref.WeakValueDictionary() _CACHE_LEVEL = None -class _PropertyCache(object): - '''Fixed-size bag class for holding cached values.''' - __slots__ = ('name', 'description', 'rolename') +class _PropertyCache: + pass + +class _UserData: + value = None def getCacheLevel(): ''' @@ -115,12 +119,53 @@ def _updateCache(event): except KeyError: return -def _makeQuery(iid): +def _getAndCache(acc, value_name, get_method): + ''' + If property caching is enabled, use the cached proprty, or get the + property and cache it. If property caching is disabled, simply get the + property. + + @param value_name: The name of the value, like 'role' or 'description'. + @type value_name: string + @param get_method: Method used to get the property, should not have any + arguments. + @type get_method: callable + @return: Value of property we are retrieving. + @rtype: object + ''' + if _CACHE_LEVEL != constants.CACHE_PROPERTIES: + return get_method() + + cache = _ACCESSIBLE_CACHE + h = hash(acc) + + try: + pc = acc._property_cache + except AttributeError: + try: + pc = cache[h] + except KeyError: + # no cached info for this accessible yet + pc = _PropertyCache() + cache[h] = pc + acc._property_cache = pc + + try: + value = getattr(pc, value_name) + except AttributeError: + # no cached property of this type + value = get_method() + setattr(pc, value_name, value) + + return value + + +def _makeQuery(interface): ''' Builds a function querying to a specific interface and returns it. - @param iid: Interface identifier to use when querying - @type iid: string + @param interface: Class representing an AT-SPI interface + @type interface: class @return: Function querying to the given interface @rtype: function ''' @@ -132,12 +177,13 @@ def _makeQuery(iid): @rtype: object @raise NotImplementedError: When the desired interface is not supported ''' + iid = utils.getInterfaceIID(interface) try: i = self._icache[iid] except KeyError: # interface not cached caching = True - except TypeError: + except AttributeError: # determine if we're caching caching = _CACHE_LEVEL is not None if caching: @@ -154,17 +200,16 @@ def _makeQuery(iid): try: # do the query remotely i = self.queryInterface(iid) + if i is not None: + i = i._narrow(interface) except Exception, e: - raise LookupError(e) + raise LookupError(e) if i is None: # cache that the interface is not supported if caching: self._icache[iid] = None raise NotImplementedError - # not needed according to ORBit2 spec, but makes Java queries work - # more reliably according to Orca experience - i._narrow(i.__class__) if caching: # cache the narrow'ed result, but only if we're caching for this object self._icache[iid] = i @@ -207,7 +252,7 @@ def _mixInterfaces(cls, interfaces): # build name of converter from the name of the interface name = 'query%s' % utils.getInterfaceName(interface) # build a function that queries to the given interface - func = _makeQuery(utils.getInterfaceIID(interface)) + func = _makeQuery(interface) # build a new method that is a clone of the original function method = new.function(func.func_code, func.func_globals, name, func.func_defaults, func.func_closure) @@ -222,6 +267,8 @@ def _mixExceptions(cls): @param cls: Class to mix interface methods into @type cls: class ''' + # get a method type as a reference from a known method + method_type = Accessibility.Accessible.getRole.__class__ # loop over all names in the new class for name in cls.__dict__.keys(): obj = cls.__dict__[name] @@ -229,10 +276,10 @@ def _mixExceptions(cls): if name.startswith('_'): continue # check if we're on a method - elif isinstance(obj, types.FunctionType): + elif isinstance(obj, method_type): # wrap the function in an exception handler method = _makeExceptionHandler(obj) - # add the wrpped function to the class + # add the wrapped function to the class setattr(cls, name, method) # check if we're on a property elif isinstance(obj, property): @@ -249,7 +296,7 @@ def _mixExceptions(cls): setter = None setattr(cls, name, property(getter, setter)) -def _mixClass(cls, new_cls): +def _mixClass(cls, new_cls, ignore=[]): ''' Adds the methods in new_cls to cls. After mixing, all instances of cls will have the new methods. If there is a method name clash, the method already in @@ -264,10 +311,12 @@ def _mixClass(cls, new_cls): @type cls: class @param new_cls: Class containing features to add @type new_cls: class + @param ignore: Ignore these methods from the mixin + @type ignore: iterable ''' # loop over all names in the new class for name, func in new_cls.__dict__.items(): - if name in ['_get_name', '_get_description']: + if name in ignore: continue if isinstance(func, types.FunctionType): # build a new function that is a clone of the one from new_cls @@ -323,16 +372,22 @@ class _AccessibleMixin(object): @type SLOTS: tuple ''' SLOTTED_CLASSES = {} - SLOTS = ('_icache',) + SLOTS = ('_icache', '_property_cache', '_user_data') def __new__(cls): ''' - Creates a new class mimicking the one requested, but with an extra _cache - attribute set in the __slots__ tuple. This field can be set to a dictionary - or other object to allow caching to occur. + Creates a new class mimicking the one requested, but with extra named + defined in __slots__. The _cache attribute is used internally for interface + caching. The user_data field may be populated with whatever data structure + a client wishes to use. Neither is set to a default value by default. Note that we can't simply mix __slots__ into this class because __slots__ - has an effect only at class creation time. + has an effect only at class creation time. + + We also do not completely obliterate __slots__ to allow __dict__ to be + instantiated as normal as reducing the initialization and memory overhead + of the millions of accessible objects that are created is a good thing for + many clients. @param cls: Accessibility object class @type cls: class @@ -349,8 +404,6 @@ class _AccessibleMixin(object): '__slots__' : _AccessibleMixin.SLOTS}) _AccessibleMixin.SLOTTED_CLASSES[cls] = new_cls obj = cls._mix___new__(new_cls) - # don't create the interface cache until we need it - obj._icache = None return obj def __del__(self): @@ -361,10 +414,6 @@ class _AccessibleMixin(object): we're caching properties. ''' try: - del _ACCESSIBLE_CACHE[hash(self)] - except KeyError: - pass - try: self.unref() except Exception: pass @@ -380,7 +429,7 @@ class _AccessibleMixin(object): for i in xrange(self.childCount): try: yield self.getChildAtIndex(i) - except constants.CORBAException: + except LookupError: yield None def __str__(self): @@ -411,6 +460,13 @@ class _AccessibleMixin(object): @return: Accessible child @rtype: Accessibility.Accessible ''' + n = self.childCount + if index >= n: + raise IndexError + elif index < -n: + raise IndexError + elif index < 0: + index += n return self.getChildAtIndex(index) def __len__(self): @@ -422,6 +478,54 @@ class _AccessibleMixin(object): ''' return self.childCount + def _get_user_data(self): + ''' + Get user_data from global dictionay fo this accessible. + + @return: Any data the user assigned, or None. + @rtype: object + ''' + global _ACCESSIBLE_USER_DATA + h = hash(self) + + try: + ud = self._user_data + except AttributeError: + try: + ud = _ACCESSIBLE_USER_DATA[h] + except KeyError: + # no cached info for this object yet + ud = _UserData() + _ACCESSIBLE_USER_DATA[h] = ud + + self._user_data = ud + return ud.value + + def _set_user_data(self, value): + ''' + Set arbitrary data to user_data. + + @param value: Value to set in user_data + @type value: object + ''' + global _ACCESSIBLE_USER_DATA + h = hash(self) + + try: + ud = self._user_data + except AttributeError: + try: + ud = _ACCESSIBLE_USER_DATA[h] + except KeyError: + # no cached info for this object yet + ud = _UserData() + _ACCESSIBLE_USER_DATA[h] = ud + + self._user_data = ud + ud.value = value + + user_data = property(_get_user_data, _set_user_data) + def _get_name(self): ''' Gets the name of the accessible from the cache if it is available, @@ -430,27 +534,21 @@ class _AccessibleMixin(object): @return: Name of the accessible @rtype: string ''' - if _CACHE_LEVEL != constants.CACHE_PROPERTIES: - return self._get_name() - - cache = _ACCESSIBLE_CACHE - h = hash(self) - try: - return cache[h].name - except KeyError: - # no cached info for this object yet - name = self._get_name() - pc = _PropertyCache() - pc.name = name - cache[h] = pc - return name - except AttributeError: - # no cached name for this object yet - name = self._get_name() - cache[h].name = name - return name - + return _getAndCache(self, 'name', self._get_name) + name = property(_get_name, Accessibility.Accessible._set_name) + + def _get_parent(self): + ''' + Gets the parent of the accessible from the cache if it is available, + otherwise, fetches it remotely. + + @return: Parent of the accessible + @rtype: Accessibility.Accessible + ''' + return _getAndCache(self, 'parent', self._get_parent) + + parent = property(_get_parent) def getRoleName(self): ''' @@ -460,26 +558,18 @@ class _AccessibleMixin(object): @return: Role name of the accessible @rtype: string ''' - if _CACHE_LEVEL != constants.CACHE_PROPERTIES: - return self._mix_getRoleName() + return _getAndCache(self, 'rolename', self._mix_getRoleName) + + def getRole(self): + ''' + Gets the role of the accessible from the cache if it is + available, otherwise, fetches it remotely. + + @return: Role of the accessible + @rtype: Accessibility.Role + ''' + return _getAndCache(self, 'role', self._mix_getRole) - cache = _ACCESSIBLE_CACHE - h = hash(self) - try: - return cache[h].rolename - except KeyError, e: - # no cached info for this object yet - rolename = self._mix_getRoleName() - pc = _PropertyCache() - pc.rolename = rolename - cache[h] = pc - return rolename - except AttributeError, e: - # no cached name for this object yet - rolename = self._mix_getRoleName() - cache[h].rolename = rolename - return rolename - def _get_description(self): ''' Gets the description of the accessible from the cache if it is available, @@ -488,25 +578,7 @@ class _AccessibleMixin(object): @return: Description of the accessible @rtype: string ''' - if _CACHE_LEVEL != constants.CACHE_PROPERTIES: - return self._get_description() - - cache = _ACCESSIBLE_CACHE - h = hash(self) - try: - return cache[h].description - except KeyError: - # no cached info for this object yet - description = self._get_description() - pc = _PropertyCache() - pc.description = description - cache[h] = pc - return description - except AttributeError: - # no cached name for this object yet - description = self._get_description() - cache[h].description = description - return description + return _getAndCache(self, 'description', self._get_description) description = property(_get_description, Accessibility.Accessible._set_description) @@ -531,9 +603,9 @@ class _AccessibleMixin(object): def getApplication(self): ''' - Gets the most-parent accessible (the application) of this accessible. Tries - using the getApplication method introduced in AT-SPI 1.7.0 first before - resorting to traversing parent links. + Gets the most-parent accessible (the application) of this + accessible. Tries using the getApplication method introduced in + AT-SPI 1.7.0 first before resorting to traversing parent links. @warning: Cycles involving more than the previously traversed accessible are not detected by this code. @@ -541,24 +613,72 @@ class _AccessibleMixin(object): @rtype: Accessibility.Application ''' try: - return self._mix_getApplication() + app = self._mix_getApplication() except AttributeError: - pass + app = None + + # Some toolkits (e.g., Java) do not support getApplication yet and + # will return None as a result. + # + if app: + return app + + # If we didn't find anything, traverse up the tree, making sure to + # attempt to turn the thing we found into an Application object. + # curr = self try: while curr.parent is not None and (not curr.parent == curr): curr = curr.parent - return curr + curr.ref() + return curr._narrow(Accessibility.Application) + except: + return None + +class _RelationMixin(object): + ''' + Defines methods to be added to the Relation class. At this time it only + overrides L{_RelationMixin.getTarget} which by the IDL's standard is + supposed to return CORBA.Objects but we expect LAccessibility.Accessible + objects (see http://bugzilla.gnome.org/show_bug.cgi?id=435833). + This seems to be a problem especially with the Java implementation of CORBA. + ''' + def getTarget(self, index): + ''' + Overrides the regular getTarget to return Accessibility.Accessible + objects. + + @return: The 'nth' target of this Relation. + @rtype: Accessibility.Accessible + ''' + target = self._mix_getTarget(index) + target.ref() + return target._narrow(Accessibility.Accessible) + +class _UnrefMixin(object): + ''' + This mixin addresses the issue we have with unreferencing non-primitives. + ''' + def __del__(self): + ''' + Unrefence the instance when Python GCs it. Why do we need this twice? + ''' + try: + self.unref() except Exception: pass - # return None if the application isn't reachable for any reason - return None # 1. mix the exception handlers into all queryable interfaces map(_mixExceptions, constants.ALL_INTERFACES) # 2. mix the exception handlers into other Accessibility objects map(_mixExceptions, [Accessibility.StateSet]) # 3. mix the new functions -_mixClass(Accessibility.Accessible, _AccessibleMixin) +_mixClass(Accessibility.Accessible, _AccessibleMixin, + ['_get_name', '_get_description', '_get_parent']) # 4. mix queryInterface convenience methods _mixInterfaces(Accessibility.Accessible, constants.ALL_INTERFACES) +# 5. mix Relation class +_mixClass(Accessibility.Relation, _RelationMixin) +# 6. mix in neccessary unrefs +map(lambda cls: _mixClass(cls, _UnrefMixin), + (Accessibility.StateSet,Accessibility.Relation))