ebcf9fe8b7f0cbbfe5c01bfc5911443416eb27e5
[platform/core/uifw/at-spi2-atk.git] / tests / accessible_cache.py
1 #Copyright (C) 2008 Codethink Ltd
2
3 #his program is free software; you can redistribute it and/or modify
4 #it under the terms of the GNU General Public License as published by
5 #the Free Software Foundation; either version 2 of the License, or
6 #(at your option) any later version.
7
8 #This program is distributed in the hope that it will be useful,
9 #but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #GNU General Public License for more details.
12 #You should have received a copy of the GNU General Public License
13 #along with this program; if not, write to the Free Software
14 #Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15
16 import dbus
17
18 from weakref import ref
19 from dbus.proxies import Interface, ProxyObject
20
21 ATSPI_ACCESSIBLE = 'org.freedesktop.atspi.Accessible'
22 ATSPI_ACCESSIBLE_TREE = 'org.freedesktop.atspi.Tree'
23
24 class AccessibleObjectDoesNotExist(Exception):
25         pass
26
27 class AccessibleInterfaceNotSupported(Exception):
28         pass
29
30 class _CacheData(object):
31         __slots__ = [
32                         'parent',
33                         'interfaces',
34                         'children',
35                         'role',
36                         'name',
37                         'description',
38                         'proxy'
39                 ]
40
41         def __init__(self, data):
42                 self.update(data)
43                 self.proxy = None
44
45         def update(self, data):
46                 #Don't cache the path here, used as lookup in cache object dict.
47                 (path,
48                 self.parent,
49                 self.children,
50                 self.interfaces,
51                 self.name,
52                 self.role,
53                 self.description) = data
54
55 class AccessibleCache(object):
56         """
57         Caches data for a collection of accessible object proxies.
58
59         The cache only holds a weak reference to any D-Bus proxies,
60         they are created on demand.
61         """
62
63         _UPDATE_SIGNAL = 'updateTree'
64
65         def __init__(self, connection, busName, objectStorePath):
66                 """
67                 Creates a cache for accessible object data.
68
69                 All accessible object proxies are created and accessed through this cache.
70
71                 connection - DBus connection.
72                 busName - DBus bus name where accessible tree resides.
73                 objectStorePath - Path where the accessible tree can be accessed.
74                 """
75                 storeObject = connection.get_object(busName, objectStorePath)
76
77                 self._connection = connection
78                 self._busName = busName
79                 self._accessibleStore = dbus.Interface(storeObject, ATSPI_ACCESSIBLE_TREE)
80                 self._objects = {}
81                 #TODO are we caching the root accessible or not?
82                 #Do we need a roundtrip to access this?
83                 #Does the root accessible ever change?
84                 #Probably not as the root accessible is the 'Application'
85                 self._root = self._accessibleStore.getRoot()
86
87                 self._updateObjects(self._accessibleStore.getTree())
88
89                 #Connect to update signal
90                 self._signalMatch = self._accessibleStore.connect_to_signal(self._UPDATE_SIGNAL,
91                                                                             self._updateHandler)
92
93         def getRootAccessible(self):
94                 """
95                 Gets the accessible object at the root of the tree.
96                 """
97                 return self.getAccessible(self._root)
98
99         def getAccessible(self, path):
100                 """
101                 Gets a D-Bus proxy object for the given object path.
102
103                 path - The D-Bus path of the remote object.
104                 """
105                 if path in self._objects:
106                         cachedata = self._objects[path]
107                         proxy = None
108                         if cachedata.proxy is not None:
109                                 proxy = cachedata.proxy()
110                         if proxy is None:
111                                 proxy = AccessibleObjectProxy(self,
112                                                         cachedata,
113                                                         self._connection,
114                                                         self._busName,
115                                                         path)
116                                 cachedata.proxy = ref(proxy)
117                         return proxy
118                 else:
119                         raise AccessibleObjectDoesNotExist, "D-Bus reference not found in cache"
120
121         def _updateObjects(self, objects):
122                 """
123                 Updates the object cache from an
124                 array of accessible object cache data.
125                 """
126                 for data in objects:
127                         #First element is the object path.
128                         path = data[0]
129                         if path in self._objects:
130                                 cachedata = self._objects[path]
131                                 cachedata.update(data)
132                         else:
133                                 self._objects[path] = _CacheData(data)
134
135         def _removeObjects(self, paths):
136                 """
137                 Removes the object data from the cache.
138                 """
139                 for path in paths:
140                         del(self._objects[path])
141
142         def _updateHandler(self, updates):
143                 objects, paths = updates
144                 self._removeObjects(paths)
145                 self._updateObjects(objects)
146
147 class AccessibleObjectProxy(ProxyObject):
148         """
149         A D-Bus proxy for a remote object that implements one or more of the AT-SPI
150         Accessibility interfaces.
151         """
152
153         def __init__(self, cache, data, connection, busName, path):
154                 """
155                 Create an accessible object.
156
157                 cache - The accessible cache that this object is owned by.
158                 connection - The D-Bus connection
159                 busName - The location on the bus where the remote object is located.
160                 path - The object path of the remote object.
161                 """
162                 ProxyObject.__init__(self, connection, busName, path, introspect=False)
163
164                 self._cache = cache
165                 self._path = path
166         
167         @property
168         def _data(self):
169                 try:
170                         data = self._cache._objects[self._path]
171                 except KeyError:
172                         raise AccessibleObjectDoesNotExist(
173                                 'Cache data cannot be found for path %s' % (self._path,))
174                 return data
175
176         @property
177         def path(self):
178                 return self._path
179
180         @property
181         def parent(self):
182                 return self._cache.getAccessible(self._data.parent)
183
184         @property
185         def numChildren(self):
186                 return len(self._data.children)
187
188         def getChild(self, index):
189                 return self._cache.getAccessible(self._data.children[index])
190
191         @property
192         def interfaces(self):
193                 return self._data.interfaces
194
195         def getInterface(self, interface):
196                 if interface in self._data.interfaces:
197                         return Interface(self, interface)
198                 else:
199                         raise AccessibleInterfaceNotSupported(
200                                 "%s not supported by accessible object at path %s"
201                                 % (interface, self.path))
202
203         @property
204         def name(self):
205                 return self._data.name
206
207         @property
208         def role(self):
209                 return self._data.role
210
211         @property
212         def description(self):
213                 return self._data.description