Partial re-refactor of the accessibles registration code.
[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                         'parent',
24                         'interfaces',
25                         'children',
26                         'role',
27                         'name',
28                         'description',
29                         'state',
30                     ]
31
32         def __init__(self, data):
33                 self._update(data)
34
35         def _update(self, data):
36                 #Don't cache the path here, used as lookup in cache object dict.
37                 (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_objects)
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                                        path,
118                                        self._bus_name,
119                                        "org.freedesktop.atspi.Event.Object",
120                                        "property-change",
121                                        ("name", 0, 0, newdata.name))
122                         self._registry._notifyNameChange(event)
123
124                 if olddata.description != newdata.description:
125                         event = _Event(self._registry.cache,
126                                        path,
127                                        self._bus_name,
128                                        "org.freedesktop.atspi.Event.Object",
129                                        "property-change",
130                                        ("description", 0, 0, description))
131                         self._registry._notifyDescriptionChange(event)
132
133                 if olddata.parent != newdata.parent:
134                         event = _Event(self._registry.cache,
135                                        path,
136                                        self._bus_name,
137                                        "org.freedesktop.atspi.Event.Object",
138                                        "property-change",
139                                        ("parent", 0, 0, ""))
140                         self._registry._notifyParentChange(event)
141
142                 added, removed = _list_items_added_removed (olddata.children, newdata.children)
143
144                 if added:
145                         event = _Event(self._registry.cache,
146                                        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                                        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         def _update_objects(self, objects):
163                 cache_update_objects = []
164                 for data in objects:
165                         #First element is the object path.
166                         path = data[0]
167                         if path in self._objects:
168                                 olddata = self._objects[path]
169                                 newdata = _CacheData(data)
170                                 cache_update_objects.append((olddata, newdata))
171                                 self._objects[path] = newdata
172                         else:
173                                 self._objects[path] = _CacheData(data)
174                 for old, new in cache_update_objects:
175                         self._dispatch_event(old, new)
176
177         def _remove_object(self, paths):
178                 # TODO I'm squashing a possible error here
179                 # I've seen things appear to be deleted twice
180                 # which needs investigation
181                 try:
182                         del(self._objects[path])
183                 except KeyError:
184                         pass
185
186         def _get_root(self):
187                 return self._root
188
189         root = property(fget=_get_root)
190
191 #END---------------------------------------------------------------------------