2008-12-04 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                         '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 = 'updateTree'
79
80         def __init__(self, registry, connection, bus_name):
81                 """
82                 Creates a cache.
83
84                 connection - DBus connection.
85                 busName    - Name of DBus connection where cache interface resides.
86                 """
87                 self._registry = registry
88                 self._connection = connection
89                 self._bus_name = bus_name
90
91                 obj = connection.get_object(bus_name, self._PATH, introspect=False)
92                 itf = _dbus.Interface(obj, self._INTERFACE)
93
94                 self._objects = {}
95
96                 get_method = itf.get_dbus_method(self._GET_METHOD)
97                 self._update_objects(get_method())
98
99                 self._signalMatch = itf.connect_to_signal(self._UPDATE_SIGNAL, self._update_handler)
100
101                 obj = connection.get_object(self._bus_name, self._PATH, introspect=False)
102                 itf = _dbus.Interface(obj, self._INTERFACE)
103
104                 self._root = itf.getRoot()
105
106         def __getitem__(self, key):
107                 return self._objects[key]
108
109         def __contains__(self, key):
110                 return key in self._objects
111
112         def _dispatch_event(self, olddata, newdata):
113                 if olddata.name != newdata.name:
114                         event = _Event(self._registry._cache,
115                                        path,
116                                        self._bus_name,
117                                        "org.freedesktop.atspi.Event.Object",
118                                        "property-change",
119                                        ("name", 0, 0, newdata.name))
120                         self._registry._notifyNameChange(event)
121
122                 if olddata.description != newdata.description:
123                         event = _Event(self._registry._cache,
124                                        path,
125                                        self._bus_name,
126                                        "org.freedesktop.atspi.Event.Object",
127                                        "property-change",
128                                        ("description", 0, 0, description))
129                         self._registry._notifyDescriptionChange(event)
130
131                 if olddata.parent != newdata.parent:
132                         event = _Event(self._registry._cache,
133                                        path,
134                                        self._bus_name,
135                                        "org.freedesktop.atspi.Event.Object",
136                                        "property-change",
137                                        ("parent", 0, 0, ""))
138                         self._registry._notifyParentChange(event)
139
140                 added, removed = _list_items_added_removed (olddata.children, newdata.children)
141
142                 if added:
143                         event = _Event(self._registry._cache,
144                                        path,
145                                        self._bus_name,
146                                        "org.freedesktop.atspi.Event.Object",
147                                        "children-changed",
148                                        ("add", 0, 0, ""))
149                         self._registry._notifyChildrenChange(event)
150
151                 if removed:
152                         event = _Event(self._registry._cache,
153                                        path,
154                                        self._bus_name,
155                                        "org.freedesktop.atspi.Event.Object",
156                                        "children-changed",
157                                        ("remove", 0, 0, ""))
158                         self._registry._notifyChildrenChange(event)
159
160         def _update_handler(self, update, remove):
161                 self._remove_objects(remove)
162                 self._update_objects(update)
163
164         def _update_objects(self, objects):
165                 cache_update_objects = []
166                 for data in objects:
167                         #First element is the object path.
168                         path = data[0]
169                         if path in self._objects:
170                                 olddata = self._objects[path]
171                                 newdata = _CacheData(data)
172                                 cache_update_objects.append((olddata, newdata))
173                                 self._objects[path] = newdata
174                         else:
175                                 self._objects[path] = _CacheData(data)
176                 for old, new in cache_update_objects:
177                         self._dispatch_event(old, new)
178
179         def _remove_objects(self, paths):
180                 for path in paths:
181                         # TODO I'm squashing a possible error here
182                         # I've seen things appear to be deleted twice
183                         # which needs investigation
184                         try:
185                                 del(self._objects[path])
186                         except KeyError:
187                                 pass
188
189         def _get_root(self):
190                 return self._root
191
192         root = property(fget=_get_root)
193
194 #END---------------------------------------------------------------------------