2008-08-15 Mark Doffman <mark.doffman@codethink.co.uk>
[platform/core/uifw/at-spi2-atk.git] / pyatspi / base.py
index 8f23713..8ff71d3 100644 (file)
 #along with this program; if not, write to the Free Software
 #Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
-from weakref import ref
+import dbus as _dbus
+from dbus.proxies import Interface as _Interface
 
-from dbus.proxies import Interface, ProxyObject
 from dbus.exceptions import *
+from factory import create_accessible, add_accessible_class
 
-from factory import interfaceFactory
+import constants
 
 class AccessibleObjectNoLongerExists(Exception):
        pass
 
 #------------------------------------------------------------------------------
 
-class Enum(object):
+class _Enum(int):
        def __str__(self):
                return self._enum_lookup(int(self))
 
 #------------------------------------------------------------------------------
 
-class BaseProxyMeta(type):
-       def __init__(cls):
-               type.__init__(cls)
+
+class _BaseProxyMeta(type):
+       def __init__(cls, *args, **kwargs):
+               type.__init__(cls, *args, **kwargs)
 
                queryable_interfaces = { 
-                       'Accessible':ATSPI_ACCESSIBLE,
-                       'Action':ATSPI_ACTION,
-                       'Application':ATSPI_APPLICATION,
-                       'Collection':ATSPI_COLLECTION,
-                       'Component':ATSPI_COMPONENT,
-                       'Desktop':ATSPI_DESKTOP,
-                       'Document':ATSPI_DOCUMENT,
-                       'EditableText':ATSPI_EDITABLE_TEXT,
-                       'Hypertext':ATSPI_HYPERTEXT,
-                       'Hyperlink':ATSPI_HYPERLINK,
-                       'Image':ATSPI_IMAGE,
-                       'Selection':ATSPI_SELECTION,
-                       'StreamableContent':ATSPI_STREAMABLE_CONTENT,
-                       'Table':ATSPI_TABLE,
-                       'Text':ATSPI_TEXT,
-                       'Value':ATSPI_VALUE,
+                       'Accessible':constants.ATSPI_ACCESSIBLE,
+                       'Action':constants.ATSPI_ACTION,
+                       'Application':constants.ATSPI_APPLICATION,
+                       'Collection':constants.ATSPI_COLLECTION,
+                       'Component':constants.ATSPI_COMPONENT,
+                       'Desktop':constants.ATSPI_DESKTOP,
+                       'Document':constants.ATSPI_DOCUMENT,
+                       'EditableText':constants.ATSPI_EDITABLE_TEXT,
+                       'Hypertext':constants.ATSPI_HYPERTEXT,
+                       'Hyperlink':constants.ATSPI_HYPERLINK,
+                       'Image':constants.ATSPI_IMAGE,
+                       'Selection':constants.ATSPI_SELECTION,
+                       'StreamableContent':constants.ATSPI_STREAMABLE_CONTENT,
+                       'Table':constants.ATSPI_TABLE,
+                       'Text':constants.ATSPI_TEXT,
+                       'Value':constants.ATSPI_VALUE,
                }
 
                for interface in queryable_interfaces.keys():
@@ -61,66 +63,265 @@ class BaseProxyMeta(type):
 
 #------------------------------------------------------------------------------
 
-class BaseProxy(Interface):
+class _BaseProxy(_Interface):
        """
-       A D-Bus proxy for a remote object that implements one or more of the AT-SPI
-       Accessibility interfaces.
+       The base D-Bus proxy for a remote object that implements one or more
+       of the AT-SPI interfaces.
        """
-       
-       __metaclass__ = BaseProxyMeta
 
-       def __init__(self, busobject, cache, app, path, interface):
+       __metaclass__ = _BaseProxyMeta
+
+       def __init__(self, cache, app_name, acc_path, parent, interface, dbus_object=None, connection=None):
                """
                Create a D-Bus Proxy for an ATSPI interface.
 
-               busobject - The D-Bus proxy object this interface uses for D-Bus calls.
-               cache - The accessible cache that this object is owned by.
-               path - The object path of the remote object.
-               app - The bus name of the application this object belongs to.
-               interface - The name of the ATSPI interface that this proxy implements.
+               cache - ApplicationCache, where the cached data for the accessible can be obtained.
+               app_name - D-Bus bus name of the application this accessible belongs to.
+               acc_path - D-Bus object path of the server side accessible object.
+               parent - Parent accessible.
+               interface - D-Bus interface of the object. Used to decide which accessible class to instanciate.
+               dbus_object(kwarg) - The D-Bus proxy object used by the accessible for D-Bus method calls.
                """
-               Interface.__init__(self, busobject, interface)
-
                self._cache = cache
+               self._app_name = app_name
+               self._acc_path = acc_path
+               self._parent = parent
+
+               if not dbus_object:
+                       dbus_object = connection.get_object(self._app_name, self._acc_path, introspect=False)
+               self._dbus_object = dbus_object
 
-               self._path = path
-               self._app = app
+               _Interface.__init__(self, self._dbus_object, interface)
 
                self._pgetter = self.get_dbus_method("Get", dbus_interface="org.freedesktop.DBus.Properties")
                self._psetter = self.get_dbus_method("Set", dbus_interface="org.freedesktop.DBus.Properties")
 
        def __getattr__(self, *args, **kwargs):
-               """
-               The __getattr__ function must return the D-Bus method wrapped in a
-               method to translate exceptions.
-               """
-               # Need to throw an AccessibleObjectNoLongerExists exception
-               # on D-Bus error of the same type.
-               try:
-                       return Interface.__getattr__(self, *args, **kwargs)
-               except UnknownMethodException, e:
-                       raise NotImplementedError(e)
-               except DBusException, e:
-                       raise LookupError(e)
+               method =  _Interface.__getattr__(self, *args, **kwargs)
+
+               def dbus_method_func(*args, **kwargs):
+                       # TODO Need to throw an AccessibleObjectNoLongerExists exception
+                       # on D-Bus error of the same type.
+                       try:
+                               method(*args, **kwargs)
+                       except UnknownMethodException, e:
+                               raise NotImplementedError(e)
+                       except DBusException, e:
+                               raise LookupError(e)
+
+               return dbus_method_func
 
        @property
-       def _data(self):
+       def cached_data(self):
                try:
-                       data = self._cache._objects[self._path]
+                       return self._cache[self._app_name][self._acc_path]
                except KeyError:
-                       raise AccessibleObjectNoLongerExists, 'Cache data cannot be found for path %s' % (self._path,)
-               return data
+                       raise AccessibleObjectNoLongerExists, \
+                               'Cache data cannot be found for path %s in app %s' % (self._acc_path, self._app_name)
 
        @property
        def interfaces(self):
                return self._data.interfaces
 
        def queryInterface(self, interface):
+               """
+               Gets a different accessible interface for this object
+               or raises a NotImplemented error if the given interface
+               is not supported.
+               """
                if interface in self._data.interfaces:
-                       return interfaceFactory(self._obj, self._cache, self._app, self._path, interface)
+                       return create_accessible(self._cache,
+                                                self._app_name,
+                                                self._acc_path,
+                                                self._parent,
+                                                interface,
+                                                dbus_object=self._dbus_object)
                else:
                        raise NotImplementedError(
                                "%s not supported by accessible object at path %s"
                                % (interface, self.path))
 
+#------------------------------------------------------------------------------
+
+class Accessible(_BaseProxy):
+    """
+    The base interface which is implemented by all accessible objects.
+    All objects support interfaces for querying their contained
+    'children' and position in the accessible-object hierarchy,
+    whether or not they actually have children.
+    """
+    
+    def getApplication(self):
+        """
+        Get the containing Application for this object.
+        @return the Application instance to which this object belongs.
+        """
+       application_root = self._cache.get_application_root(self._app_name)
+       return create_accessible(self._cache,
+                                self._app_name,
+                                application_root,
+                                constants.NULL_BUS_NAME,
+                                constants.NULL_OBJECT_PATH,
+                                constants.ATSPI_ACCESSIBLE,
+                                dbus_object=self._dbus_object)
+    
+    def getAttributes(self, *args, **kwargs):
+        """
+        Get a list of properties applied to this object as a whole, as
+        an AttributeSet consisting of name-value pairs. As such these
+        attributes may be considered weakly-typed properties or annotations,
+        as distinct from the strongly-typed interface instance data declared
+        using the IDL "attribute" keyword.
+        Not all objects have explicit "name-value pair" AttributeSet
+        properties.
+        Attribute names and values may have any UTF-8 string value, however
+        where possible, in order to facilitate consistent use and exposure
+        of "attribute" properties by applications and AT clients, attribute
+        names and values should chosen from a publicly-specified namespace
+        where appropriate.
+        Where possible, the names and values in the name-value pairs
+        should be chosen from well-established attribute namespaces using
+        standard semantics. For example, attributes of Accessible objects
+        corresponding to XHTML content elements should correspond to
+        attribute names and values specified in the w3c XHTML specification,
+        at http://www.w3.org/TR/xhtml2, where such values are not already
+        exposed via a more strongly-typed aspect of the AT-SPI API. Metadata
+        names and values should be chosen from the 'Dublin Core' Metadata
+        namespace using Dublin Core semantics: http://dublincore.org/dcregistry/
+        Similarly, relevant structural metadata should be exposed using
+        attribute names and values chosen from the CSS2 and WICD specification:
+        http://www.w3.org/TR/1998/REC-CSS2-19980512 WICD (http://www.w3.org/TR/2005/WD-WICD-20051121/).
+        @return : an AttributeSet encapsulating any "attribute values"
+        currently defined for the object.
+        """
+        func = self.get_dbus_method("getAttributes")
+        return func(*args, **kwargs)
+    
+    def getChildAtIndex(self, index):
+        """
+        Get the accessible child of this object at index. 
+        @param : index
+        an in parameter indicating which child is requested (zero-indexed).
+        @return : the 'nth' Accessible child of this object.
+        """
+       path = self.cached_data.children[index]
+       return create_accessible(self._cache,
+                                self._app_name,
+                                path,
+                                self,
+                                constants.ATSPI_ACCESSIBLE,
+                                dbus_object=self._dbus_object)
+    
+    def getIndexInParent(self, *args, **kwargs):
+        """
+        Get the index of this object in its parent's child list. 
+        @return : a long integer indicating this object's index in the
+        parent's list.
+        """
+        func = self.get_dbus_method("getIndexInParent")
+        return func(*args, **kwargs)
+    
+    def getLocalizedRoleName(self, *args, **kwargs):
+        """
+        Get a string indicating the type of UI role played by this object,
+        translated to the current locale.
+        @return : a UTF-8 string indicating the type of UI role played
+        by this object.
+        """
+        func = self.get_dbus_method("getLocalizedRoleName")
+        return func(*args, **kwargs)
+    
+    def getRelationSet(self, *args, **kwargs):
+        """
+        Get a set defining this object's relationship to other accessible
+        objects. 
+        @return : a RelationSet defining this object's relationships.
+        """
+        func = self.get_dbus_method("getRelationSet")
+        return func(*args, **kwargs)
+    
+    def getRole(self):
+        """
+        Get the Role indicating the type of UI role played by this object.
+        @return : a Role indicating the type of UI role played by this
+        object.
+        """
+        return self.cached_data.role
+    
+    def getRoleName(self, *args, **kwargs):
+        """
+        Get a string indicating the type of UI role played by this object.
+        @return : a UTF-8 string indicating the type of UI role played
+        by this object.
+        """
+        func = self.get_dbus_method("getRoleName")
+        return func(*args, **kwargs)
+    
+    def getState(self, *args, **kwargs):
+        """
+        Get the current state of the object as a StateSet. 
+        @return : a StateSet encapsulating the currently true states
+        of the object.
+        """
+        func = self.get_dbus_method("getState")
+        return func(*args, **kwargs)
+    
+    def isEqual(self, accessible):
+        """
+        Determine whether an Accessible refers to the same object as
+        another. This method should be used rather than brute-force comparison
+        of object references (i.e. "by-value" comparison), as two object
+        references may have different apparent values yet refer to the
+        same object.
+        @param : obj
+        an Accessible object reference to compare to 
+        @return : a boolean indicating whether the two object references
+        point to the same object.
+        """
+        return  (self._app_name == accessible._app_name) and \
+               (self._acc_path == accessible._acc_path)        
+    
+    def unimplemented(self, *args, **kwargs):
+        """
+        /cond future expansion
+        """
+        func = self.get_dbus_method("unimplemented")
+        return func(*args, **kwargs)
+    
+    def get_childCount(self):
+        return len(self.cached_data.children)
+    _childCountDoc = \
+        """
+        childCount: the number of children contained by this object.
+        """
+    childCount = property(fget=get_childCount, doc=_childCountDoc)
+    
+    def get_description(self):
+        return self.cached_data.description
+    _descriptionDoc = \
+        """
+        a string describing the object in more detail than name.
+        """
+    description = property(fget=get_description, doc=_descriptionDoc)
+    
+    def get_name(self):
+        return self.cached_data.name
+    _nameDoc = \
+        """
+        a (short) string representing the object's name.
+        """
+    name = property(fget=get_name, doc=_nameDoc)
+    
+    def get_parent(self):
+       # The parent attribute is part of the base proxy
+       return self._parent
+    _parentDoc = \
+        """
+        an Accessible object which is this object's containing object.
+        """
+    parent = property(fget=get_parent, doc=_parentDoc)
+
+# ATTENTION - Register the Accessible class with the accessible factory.
+add_accessible_class(constants.ATSPI_ACCESSIBLE, Accessible)
+
 #END----------------------------------------------------------------------------