2009-04-21 Mark Doffman <mark.doffman@codethink.co.uk>
[platform/core/uifw/at-spi2-atk.git] / pyatspi / accessiblecache.py
1 #Copyright (C) 2008 Codethink Ltd
2
3 #This library is free software; you can redistribute it and/or
4 #modify it under the terms of the GNU Lesser General Public
5 #License version 2 as published by the Free Software Foundation.
6
7 #This program is distributed in the hope that it will be useful,
8 #but WITHOUT ANY WARRANTY; without even the implied warranty of
9 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 #GNU General Public License for more details.
11 #You should have received a copy of the GNU Lesser General Public License
12 #along with this program; if not, write to the Free Software
13 #Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
14
15 import dbus as _dbus
16
17 from event import Event as _Event
18
19 #------------------------------------------------------------------------------
20
21 class _CacheData(object):
22         __slots__ = [
23                         'path',
24                         'parent',
25                         'interfaces',
26                         'children',
27                         'role',
28                         'name',
29                         'description',
30                         'state',
31                     ]
32
33         def __init__(self, data):
34                 self._update(data)
35
36         def _update(self, data):
37                 (self.path,
38                  self.parent,
39                  self.children,
40                  self.interfaces,
41                  self.name,
42                  self.role,
43                  self.description,
44                  self.state) = data
45
46 #------------------------------------------------------------------------------
47
48 def _list_items_added_removed (l1, l2):
49         """
50         Returns a tuple (boolean, boolean).
51         The first value indicates if, when
52         moving from l1 to l2, any items have been added.
53         The second value indicates whether any items have
54         been removed.
55         """
56         l1notl2 = [item for item in l1 if item not in l2]
57         l2notl1 = [item for item in l2 if item not in l1]
58         return ((len(l1notl2) > 0), (len(l2notl1) > 0))
59
60 #------------------------------------------------------------------------------
61
62 class AccessibleCache(object):
63         """
64         There is one accessible cache per application.
65         For each application the accessible cache stores
66         data on every accessible object within the app.
67
68         It also acts as the factory for creating client
69         side proxies for these accessible objects.
70
71         connection - DBus connection.
72         busName    - Name of DBus connection where cache interface resides.
73         """
74
75         _PATH = '/org/freedesktop/atspi/tree'
76         _INTERFACE = 'org.freedesktop.atspi.Tree'
77         _GET_METHOD = 'getTree'
78         _UPDATE_SIGNAL = 'updateAccessible'
79         _REMOVE_SIGNAL = 'removeAccessible'
80
81         def __init__(self, registry, connection, bus_name):
82                 """
83                 Creates a cache.
84
85                 connection - DBus connection.
86                 busName    - Name of DBus connection where cache interface resides.
87                 """
88                 self._registry = registry
89                 self._connection = connection
90                 self._bus_name = bus_name
91
92                 obj = connection.get_object(bus_name, self._PATH, introspect=False)
93                 itf = _dbus.Interface(obj, self._INTERFACE)
94
95                 self._objects = {}
96
97                 get_method = itf.get_dbus_method(self._GET_METHOD)
98                 self._update_objects(get_method())
99
100                 self._updateMatch = itf.connect_to_signal(self._UPDATE_SIGNAL, self._update_single)
101                 self._removeMatch = itf.connect_to_signal(self._REMOVE_SIGNAL, self._remove_object)
102
103                 obj = connection.get_object(self._bus_name, self._PATH, introspect=False)
104                 itf = _dbus.Interface(obj, self._INTERFACE)
105
106                 self._root = itf.getRoot()
107
108         def __getitem__(self, key):
109                 return self._objects[key]
110
111         def __contains__(self, key):
112                 return key in self._objects
113
114         def _dispatch_event(self, olddata, newdata):
115                 if olddata.name != newdata.name:
116                         event = _Event(self._registry.cache,
117                                        newdata.path,
118                                        self._bus_name,
119                                        "org.freedesktop.atspi.Event.Object",
120                                        "property-change",
121                                        ("accessible-name", 0, 0, newdata.name))
122                         self._registry._notifyNameChange(event)
123
124                 if olddata.description != newdata.description:
125                         event = _Event(self._registry.cache,
126                                        newdata.path,
127                                        self._bus_name,
128                                        "org.freedesktop.atspi.Event.Object",
129                                        "property-change",
130                                        ("accessible-description", 0, 0, description))
131                         self._registry._notifyDescriptionChange(event)
132
133                 if olddata.parent != newdata.parent:
134                         event = _Event(self._registry.cache,
135                                        newdata.path,
136                                        self._bus_name,
137                                        "org.freedesktop.atspi.Event.Object",
138                                        "property-change",
139                                        ("accessible-parent", 0, 0, ""))
140                         self._registry._notifyParentChange(event)
141
142                 removed, added = _list_items_added_removed (olddata.children, newdata.children)
143
144                 if added:
145                         event = _Event(self._registry.cache,
146                                        newdata.path,
147                                        self._bus_name,
148                                        "org.freedesktop.atspi.Event.Object",
149                                        "children-changed",
150                                        ("add", 0, 0, ""))
151                         self._registry._notifyChildrenChange(event)
152
153                 if removed:
154                         event = _Event(self._registry.cache,
155                                        newdata.path,
156                                        self._bus_name,
157                                        "org.freedesktop.atspi.Event.Object",
158                                        "children-changed",
159                                        ("remove", 0, 0, ""))
160                         self._registry._notifyChildrenChange(event)
161
162         # TODO This should be the other way around. Single is more common than many.
163         def _update_single(self, object):
164                 self._update_objects ([object])
165
166         def _update_objects(self, objects):
167                 cache_update_objects = []
168                 for data in objects:
169                         #First element is the object path.
170                         path = data[0]
171                         if path in self._objects:
172                                 olddata = self._objects[path]
173                                 newdata = _CacheData(data)
174                                 cache_update_objects.append((olddata, newdata))
175                                 self._objects[path] = newdata
176                         else:
177                                 self._objects[path] = _CacheData(data)
178                 for old, new in cache_update_objects:
179                         self._dispatch_event(old, new)
180
181         def _remove_object(self, paths):
182                 # TODO I'm squashing a possible error here
183                 # I've seen things appear to be deleted twice
184                 # which needs investigation
185                 try:
186                         del(self._objects[path])
187                 except KeyError:
188                         pass
189
190         def _get_root(self):
191                 return self._root
192
193         root = property(fget=_get_root)
194
195 #END---------------------------------------------------------------------------