68fa65ab65b8e8e444a620140bdf732970b4c2e4
[platform/core/uifw/at-spi2-atk.git] / pyatspi / applicationcache.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
16
17 from accessiblecache import AccessibleCache
18 from desktop import Desktop, DESKTOP_PATH
19 from factory import accessible_factory
20 from event import Event as _Event
21 from base import AccessibleObjectNotAvailable
22
23 from interfaces import *
24
25 __all__ = [
26            "ApplicationCache",
27            "TestApplicationCache",
28           ]
29
30 #------------------------------------------------------------------------------
31
32 ROOT_PATH    = '/org/freedesktop/atspi/accessible/root'
33
34 #------------------------------------------------------------------------------
35
36 class TestApplicationCache(object):
37
38         """
39         Test application store, accesses a single application.
40
41         The store object acts as a central class for creating accessible objects.
42         It interfaces with the ATSPI registry to keep account of all accessible
43         applications. It contains the accessible cache objects from each application.
44
45         @registry:   Each accessible cache object must have a reference to the registry
46                      object to send update events.
47
48         @connection: D-Bus connection used to access applications.
49
50         @bus_name:   The test store only accesses one accessible application, this is its
51                      D-Bus path.
52         """
53
54         def __init__(self, registry, connection, bus_name):
55                 self._connection = connection
56
57                 self.application_list = [bus_name]
58                 self.application_cache = {bus_name:AccessibleCache(registry, connection, bus_name)}
59
60         def get_cache_data(self, app_name, acc_path):
61                 """
62                 Returns the cache tuple for the given application and accessible
63                 object path. Throws an IndexError if the cache data is not found.
64                 """
65                 return self.application_cache[app_name][acc_path]
66
67         def create_application(self, app_name):
68                 """
69                 Creates an accessible object for the root of the application
70                 available at the given D-Bus name.
71                 """
72                 cls = accessible_factory.get_accessible_class(ATSPI_APPLICATION)
73                 try:
74                         return cls(app_name, self.application_cache[app_name].root, self, ATSPI_APPLICATION)
75                 except KeyError:
76                         raise AccessibleObjectNotAvailable ()
77
78         def create_accessible(self, app_name, acc_path, interface, dbus_object=None):
79                 """
80                 Creates an accessible object.
81
82                 @app_name: D-Bus name of the application where the accessible object resides.
83
84                 @acc_path: D-Bus path of the object within the application.
85
86                 @interface: D-Bus interface of the requested object. A different accessible object
87                             class will be created depending on this. Making the function much like 
88                             an accessible object factory.
89
90                 @dbus_object: If a D-Bus object already exists for the accessible object it can be
91                               provided here so that another one is not created.
92                 """
93                 # An acc_path of '/' implies the desktop object, whatever the app_name.
94                 if acc_path == DESKTOP_PATH:
95                         return Desktop(self)
96                 if acc_path == ROOT_PATH:
97                         return None
98                 else:
99                         cls = accessible_factory.get_accessible_class(interface)
100                         try:
101                                 return cls(app_name, acc_path, self, interface, dbus_object=dbus_object)
102                         except KeyError:
103                                 raise AccessibleObjectNotAvailable ()
104
105         @property
106         def connection(self):
107                 """
108                 D-Bus connection used by the store.
109                 """
110                 return self._connection
111
112 #------------------------------------------------------------------------------
113
114 class ApplicationCache(object):
115         """
116         Test application store, accesses a single application.
117
118         The store object acts as a central class for creating accessible objects.
119         It interfaces with the ATSPI registry to keep account of all accessible
120         applications. It contains the accessible cache objects from each application.
121
122         @registry:   Each accessible cache object must have a reference to the registry
123                      object to send update events.
124
125         @connection: D-Bus connection used to access applications.
126
127         @bus_name:   The test store only accesses one accessible application, this is its
128                      D-Bus path.
129         """
130
131         _APPLICATIONS_ADD = 1
132         _APPLICATIONS_REMOVE = 0
133
134         def __init__(self, registry, connection):
135                 self._connection = connection
136                 self._registry = registry
137
138                 self.application_list = []
139                 self.application_cache = {}
140
141                 self._regsig = connection.add_signal_receiver(self.update_handler,
142                                                               dbus_interface=ATSPI_REGISTRY_INTERFACE,
143                                                               signal_name="updateApplications")
144
145                 obj = connection.get_object(ATSPI_REGISTRY_NAME,
146                                             ATSPI_REGISTRY_PATH,
147                                             introspect=False)
148                 self._app_register = dbus.Interface(obj, ATSPI_REGISTRY_INTERFACE)
149
150                 self.application_list.extend(self._app_register.getApplications())
151                 for bus_name in self.application_list:
152                         self.application_cache[bus_name] = AccessibleCache(self._registry, self._connection, bus_name)
153
154         def update_handler (self, update_type, bus_name):
155                 if update_type == ApplicationCache._APPLICATIONS_ADD:
156                         #TODO Check that app does not already exist
157                         #TODO Excuding this app is a hack, need to have re-entrant method calls.
158                         if bus_name != self._connection.get_unique_name ():
159                                 self.application_list.append(bus_name)
160                                 self.application_cache[bus_name] = AccessibleCache(self._registry,
161                                                                                    self._connection,
162                                                                                    bus_name)
163                                 event = _Event(self,
164                                                DESKTOP_PATH,
165                                                ATSPI_REGISTRY_NAME,
166                                                "org.freedesktop.atspi.Event.Object",
167                                                "children-changed",
168                                                ("add", 0, 0, ""))
169                 elif update_type == ApplicationCache._APPLICATIONS_REMOVE:
170                         #TODO Fail safely if app does not exist
171                         self.application_list.remove(bus_name)
172                         del(self.application_cache[bus_name])
173                         event = _Event(self,
174                                        DESKTOP_PATH,
175                                        ATSPI_REGISTRY_NAME,
176                                        "org.freedesktop.atspi.Event.Object",
177                                        "children-changed",
178                                        ("remove", 0, 0, ""))
179
180                 self._registry._notifyChildrenChange(event)
181
182         def get_cache_data(self, app_name, acc_path):
183                 """
184                 Returns the cache tuple for the given application and accessible
185                 object path. Throws an IndexError if the cache data is not found.
186                 """
187                 return self.application_cache[app_name][acc_path]
188
189         def create_application(self, app_name):
190                 """
191                 Creates an accessible object for the root of the application
192                 available at the given D-Bus name.
193                 """
194                 if app_name == ATSPI_REGISTRY_NAME:
195                         return Desktop(self)
196                 else:
197                         cls = accessible_factory.get_accessible_class(ATSPI_APPLICATION)
198                         try:
199                                 return cls(app_name, self.application_cache[app_name].root, self, ATSPI_APPLICATION)
200                         except KeyError:
201                                 raise AccessibleObjectNotAvailable ()
202
203         def create_accessible(self, app_name, acc_path, interface, dbus_object=None):
204                 """
205                 Creates an accessible object.
206
207                 @app_name: D-Bus name of the application where the accessible object resides.
208
209                 @acc_path: D-Bus path of the object within the application.
210
211                 @interface: D-Bus interface of the requested object. A different accessible object
212                             class will be created depending on this. Making the function much like 
213                             an accessible object factory.
214
215                 @dbus_object: If a D-Bus object already exists for the accessible object it can be
216                               provided here so that another one is not created.
217                 """
218                 if acc_path == DESKTOP_PATH:
219                         return Desktop(self)
220                 if acc_path == ROOT_PATH:
221                         return None
222                 else:
223                         cls = accessible_factory.get_accessible_class(interface)
224                         try:
225                                 return cls(app_name, acc_path, self, interface, dbus_object=dbus_object)
226                         except KeyError:
227                                 raise AccessibleObjectNotAvailable ()
228
229         @property
230         def connection(self):
231                 """
232                 D-Bus connection used by the store.
233                 """
234                 return self._connection
235
236         def _refresh(self):
237                 new = self._app_register.getApplications()
238                 removed = [item for item in self.application_list if item not in new]
239                 added   = [item for item in new if item not in self.application_list]
240                 for item in added:
241                         self.update_handler (self._APPLICATIONS_ADD, item)
242                 for item in removed:
243                         self.update_handler (self._APPLICATIONS_REMOVE, item)
244
245                 for item in self.application_cache.values():
246                         item._refresh()
247
248 #END----------------------------------------------------------------------------