Avoid reentering spi_dbus_update_cache
[platform/core/uifw/at-spi2-atk.git] / python / 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_ACTION = 'org.freedesktop.atspi.Action'
23 ATSPI_APPLICATION = 'org.freedesktop.atspi.Application'
24 ATSPI_COMPONENT = 'org.freedesktop.atspi.Component'
25 ATSPI_DEVICE_EVENT_CONTROLLER = 'org.freedesktop.atspi.DeviceEventController'
26 ATSPI_DEVICE_EVENT_LISTENER = 'org.freedesktop.atspi.DeviceEventListener'
27 ATSPI_DOCUMENT = 'org.freedesktop.atspi.Document'
28 ATSPI_EDITABLE_TEXT = 'org.freedesktop.atspi.EditableText'
29 ATSPI_HYPERLINK = 'org.freedesktop.atspi.Hyperlink'
30 ATSPI_HYPERTEXT = 'org.freedesktop.atspi.Hypertext'
31 ATSPI_IMAGE = 'org.freedesktop.atspi.Image'
32 ATSPI_LOGIN_HELPER = 'org.freedesktop.atspi.LoginHelper'
33 ATSPI_REGISTRY = 'org.freedesktop.atspi.Registry'
34 ATSPI_SELECTION = 'org.freedesktop.atspi.Selection'
35 ATSPI_SELECTOR = 'org.freedesktop.atspi.Selector'
36 ATSPI_STREAMABLE_CONTENT = 'org.freedesktop.atspi.Content'
37 ATSPI_TABLE = 'org.freedesktop.atspi.Table'
38 ATSPI_TEXT = 'org.freedesktop.atspi.Text'
39 ATSPI_TREE = 'org.freedesktop.atspi.Tree'
40 ATSPI_VALUE = 'org.freedesktop.atspi.Value'
41
42 DBUS_INTROSPECTABLE = 'org.freedesktop.DBus.Introspectable'
43
44 class AccessibleObjectDoesNotExist(Exception):
45         pass
46
47 class AccessibleInterfaceNotSupported(Exception):
48         pass
49
50 class _CacheData(object):
51         __slots__ = [
52                         'parent',
53                         'interfaces',
54                         'children',
55                         'role',
56                         'name',
57                         'description',
58                         'proxy'
59                 ]
60
61         def __init__(self, data):
62                 self.update(data)
63                 self.proxy = None
64
65         def update(self, data):
66                 #Don't cache the path here, used as lookup in cache object dict.
67                 (path,
68                 self.parent,
69                 self.children,
70                 self.interfaces,
71                 self.name,
72                 self.role,
73                 self.description) = data
74
75 class AccessibleCache(object):
76         """
77         Caches data for a collection of accessible object proxies.
78
79         The cache only holds a weak reference to any D-Bus proxies,
80         they are created on demand.
81         """
82
83         _UPDATE_SIGNAL = 'updateTree'
84
85         def __init__(self, connection, busName, objectStorePath):
86                 """
87                 Creates a cache for accessible object data.
88
89                 All accessible object proxies are created and accessed through this cache.
90
91                 connection - DBus connection.
92                 busName - DBus bus name where accessible tree resides.
93                 objectStorePath - Path where the accessible tree can be accessed.
94                 """
95                 storeObject = connection.get_object(busName, objectStorePath)
96
97                 self._connection = connection
98                 self._busName = busName
99                 self._accessibleStore = dbus.Interface(storeObject, ATSPI_TREE)
100                 self._objects = {}
101                 #TODO are we caching the root accessible or not?
102                 #Do we need a roundtrip to access this?
103                 #Does the root accessible ever change?
104                 #Probably not as the root accessible is the 'Application'
105                 self._root = self._accessibleStore.getRoot()
106
107                 self._updateObjects(self._accessibleStore.getTree())
108
109                 #Connect to update signal
110                 self._signalMatch = self._accessibleStore.connect_to_signal(self._UPDATE_SIGNAL,
111                                                                             self._updateHandler)
112
113         def getRootAccessible(self):
114                 """
115                 Gets the accessible object at the root of the tree.
116                 """
117                 return self.getAccessible(self._root)
118
119         def getAccessible(self, path):
120                 """
121                 Gets a D-Bus proxy object for the given object path.
122
123                 path - The D-Bus path of the remote object.
124                 """
125                 if path in self._objects:
126                         cachedata = self._objects[path]
127                         proxy = None
128                         if cachedata.proxy is not None:
129                                 proxy = cachedata.proxy()
130                         if proxy is None:
131                                 proxy = AccessibleObjectProxy(self,
132                                                         cachedata,
133                                                         self._connection,
134                                                         self._busName,
135                                                         path)
136                                 cachedata.proxy = ref(proxy)
137                         return proxy
138                 else:
139                         raise AccessibleObjectDoesNotExist, "D-Bus reference not found in cache"
140
141         def _updateObjects(self, objects):
142                 """
143                 Updates the object cache from an
144                 array of accessible object cache data.
145                 """
146                 for data in objects:
147                         #First element is the object path.
148                         path = data[0]
149                         if path in self._objects:
150                                 cachedata = self._objects[path]
151                                 cachedata.update(data)
152                         else:
153                                 self._objects[path] = _CacheData(data)
154
155         def _removeObjects(self, paths):
156                 """
157                 Removes the object data from the cache.
158                 """
159                 for path in paths:
160                         del(self._objects[path])
161
162         def _updateHandler(self, updates):
163                 objects, paths = updates
164                 self._removeObjects(paths)
165                 self._updateObjects(objects)
166
167 class AccessibleObjectProxy(ProxyObject):
168         """
169         A D-Bus proxy for a remote object that implements one or more of the AT-SPI
170         Accessibility interfaces.
171         """
172
173         def __init__(self, cache, data, connection, busName, path):
174                 """
175                 Create an accessible object.
176
177                 cache - The accessible cache that this object is owned by.
178                 connection - The D-Bus connection
179                 busName - The location on the bus where the remote object is located.
180                 path - The object path of the remote object.
181                 """
182                 ProxyObject.__init__(self, connection, busName, path, introspect=False)
183
184                 self._cache = cache
185                 self._path = path
186         
187         @property
188         def _data(self):
189                 try:
190                         data = self._cache._objects[self._path]
191                 except KeyError:
192                         raise AccessibleObjectDoesNotExist(
193                                 'Cache data cannot be found for path %s' % (self._path,))
194                 return data
195
196         @property
197         def path(self):
198                 return self._path
199
200         @property
201         def parent(self):
202                 return self._cache.getAccessible(self._data.parent)
203
204         @property
205         def numChildren(self):
206                 return len(self._data.children)
207
208         def getChild(self, index):
209                 return self._cache.getAccessible(self._data.children[index])
210
211         @property
212         def interfaces(self):
213                 return self._data.interfaces
214
215         def getInterface(self, interface):
216                 if interface in self._data.interfaces:
217                         return Interface(self, interface)
218                 else:
219                         raise AccessibleInterfaceNotSupported(
220                                 "%s not supported by accessible object at path %s"
221                                 % (interface, self.path))
222
223         @property
224         def name(self):
225                 return self._data.name
226
227         @property
228         def role(self):
229                 return self._data.role
230
231         @property
232         def description(self):
233                 return self._data.description