b19079316bc6ab6804e2b411371c62b355367727
[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                 self._tree_itf = _dbus.Interface(obj, self._INTERFACE)
94
95                 self._objects = {}
96
97                 get_method = self._tree_itf.get_dbus_method(self._GET_METHOD)
98                 self._update_objects(get_method())
99
100                 self._updateMatch = self._tree_itf.connect_to_signal(self._UPDATE_SIGNAL, self._update_single)
101                 self._removeMatch = self._tree_itf.connect_to_signal(self._REMOVE_SIGNAL, self._remove_object)
102
103                 self._root = self._tree_itf.getRoot()
104
105         def __getitem__(self, key):
106                 return self._objects[key]
107
108         def __contains__(self, key):
109                 return key in self._objects
110
111         def _dispatch_event(self, olddata, newdata):
112                 if olddata.name != newdata.name:
113                         event = _Event(self._registry.cache,
114                                        newdata.path,
115                                        self._bus_name,
116                                        "org.freedesktop.atspi.Event.Object",
117                                        "property-change",
118                                        ("accessible-name", 0, 0, newdata.name))
119                         self._registry._notifyNameChange(event)
120
121                 if olddata.description != newdata.description:
122                         event = _Event(self._registry.cache,
123                                        newdata.path,
124                                        self._bus_name,
125                                        "org.freedesktop.atspi.Event.Object",
126                                        "property-change",
127                                        ("accessible-description", 0, 0, newdata.description))
128                         self._registry._notifyDescriptionChange(event)
129
130                 if olddata.parent != newdata.parent:
131                         event = _Event(self._registry.cache,
132                                        newdata.path,
133                                        self._bus_name,
134                                        "org.freedesktop.atspi.Event.Object",
135                                        "property-change",
136                                        ("accessible-parent", 0, 0, ""))
137                         self._registry._notifyParentChange(event)
138
139                 removed, added = _list_items_added_removed (olddata.children, newdata.children)
140
141                 if added:
142                         event = _Event(self._registry.cache,
143                                        newdata.path,
144                                        self._bus_name,
145                                        "org.freedesktop.atspi.Event.Object",
146                                        "children-changed",
147                                        ("add", 0, 0, ""))
148                         self._registry._notifyChildrenChange(event)
149
150                 if removed:
151                         event = _Event(self._registry.cache,
152                                        newdata.path,
153                                        self._bus_name,
154                                        "org.freedesktop.atspi.Event.Object",
155                                        "children-changed",
156                                        ("remove", 0, 0, ""))
157                         self._registry._notifyChildrenChange(event)
158
159         # TODO This should be the other way around. Single is more common than many.
160         def _update_single(self, object):
161                 self._update_objects ([object])
162
163         def _update_objects(self, objects):
164                 cache_update_objects = []
165                 for data in objects:
166                         #First element is the object path.
167                         path = data[0]
168                         if path in self._objects:
169                                 olddata = self._objects[path]
170                                 newdata = _CacheData(data)
171                                 cache_update_objects.append((olddata, newdata))
172                                 self._objects[path] = newdata
173                         else:
174                                 self._objects[path] = _CacheData(data)
175                 for old, new in cache_update_objects:
176                         self._dispatch_event(old, new)
177
178         def _remove_object(self, path):
179                 # TODO I'm squashing a possible error here
180                 # I've seen things appear to be deleted twice
181                 # which needs investigation
182                 try:
183                         del(self._objects[path])
184                 except KeyError:
185                         pass
186
187         def _get_root(self):
188                 return self._root
189
190         def _refresh(self):
191                 get_method = self._tree_itf.get_dbus_method(self._GET_METHOD)
192                 self._update_objects(get_method())
193
194         root = property(fget=_get_root)
195
196 #END---------------------------------------------------------------------------