2.34.1
[platform/upstream/at-spi2-atk.git] / atk-adaptor / accessible-cache.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2009, 2010 Codethink Ltd.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #include <atk/atk.h>
24 #include <string.h>
25
26 #include "accessible-cache.h"
27 #include "accessible-register.h"
28 #include "bridge.h"
29 #include "event.h"
30
31 SpiCache *spi_global_cache = NULL;
32
33 static gboolean
34 child_added_listener (GSignalInvocationHint * signal_hint,
35                       guint n_param_values,
36                       const GValue * param_values, gpointer data);
37
38 static void
39 toplevel_added_listener (AtkObject * accessible,
40                          guint index, AtkObject * child);
41
42 static void
43 remove_object (GObject * source, GObject * gobj, gpointer data);
44
45 static void
46 add_object (SpiCache * cache, GObject * gobj);
47
48 static void
49 add_subtree (SpiCache *cache, AtkObject * accessible);
50
51 static gboolean
52 add_pending_items (gpointer data);
53
54 /*---------------------------------------------------------------------------*/
55
56 static void
57 spi_cache_finalize (GObject * object);
58
59 /*---------------------------------------------------------------------------*/
60
61 enum
62 {
63   OBJECT_ADDED,
64   OBJECT_REMOVED,
65   LAST_SIGNAL
66 };
67 static guint cache_signals[LAST_SIGNAL] = { 0 };
68
69 /*---------------------------------------------------------------------------*/
70
71 G_DEFINE_TYPE (SpiCache, spi_cache, G_TYPE_OBJECT)
72
73 static void spi_cache_class_init (SpiCacheClass * klass)
74 {
75   GObjectClass *object_class = (GObjectClass *) klass;
76
77   spi_cache_parent_class = g_type_class_ref (G_TYPE_OBJECT);
78
79   object_class->finalize = spi_cache_finalize;
80
81   cache_signals [OBJECT_ADDED] = \
82       g_signal_new ("object-added",
83                     SPI_CACHE_TYPE,
84                     G_SIGNAL_ACTION,
85                     0,
86                     NULL,
87                     NULL,
88                     g_cclosure_marshal_VOID__OBJECT,
89                     G_TYPE_NONE,
90                     1,
91                     G_TYPE_OBJECT);
92
93   cache_signals [OBJECT_REMOVED] = \
94       g_signal_new ("object-removed",
95                     SPI_CACHE_TYPE,
96                     G_SIGNAL_ACTION,
97                     0,
98                     NULL,
99                     NULL,
100                     g_cclosure_marshal_VOID__OBJECT,
101                     G_TYPE_NONE,
102                     1,
103                     G_TYPE_OBJECT);
104 }
105
106 static void
107 spi_cache_init (SpiCache * cache)
108 {
109   cache->objects = g_hash_table_new (g_direct_hash, g_direct_equal);
110   cache->add_traversal = g_queue_new ();
111
112 #ifdef SPI_ATK_DEBUG
113   if (g_thread_supported ())
114     g_message ("AT-SPI: Threads enabled");
115
116   g_debug ("AT-SPI: Initial Atk tree regisration");
117 #endif
118
119   g_signal_connect (spi_global_register,
120                     "object-deregistered",
121                     (GCallback) remove_object, cache);
122
123   add_subtree (cache, spi_global_app_data->root);
124
125   cache->child_added_listener = atk_add_global_event_listener (child_added_listener,
126                                                                "Gtk:AtkObject:children-changed"); 
127
128   g_signal_connect (G_OBJECT (spi_global_app_data->root),
129                     "children-changed::add",
130                     (GCallback) toplevel_added_listener, NULL);
131 }
132
133 static void
134 spi_cache_finalize (GObject * object)
135 {
136   SpiCache *cache = SPI_CACHE (object);
137
138   while (!g_queue_is_empty (cache->add_traversal))
139     g_object_unref (G_OBJECT (g_queue_pop_head (cache->add_traversal)));
140   g_queue_free (cache->add_traversal);
141   g_hash_table_unref (cache->objects);
142
143   g_signal_handlers_disconnect_by_func (spi_global_register,
144                                         (GCallback) remove_object, cache);
145
146   g_signal_handlers_disconnect_by_func (G_OBJECT (spi_global_app_data->root),
147                                         (GCallback) toplevel_added_listener, NULL);
148
149   atk_remove_global_event_listener (cache->child_added_listener);
150
151   G_OBJECT_CLASS (spi_cache_parent_class)->finalize (object);
152 }
153
154 /*---------------------------------------------------------------------------*/
155
156 static void
157 remove_object (GObject * source, GObject * gobj, gpointer data)
158 {
159   SpiCache *cache = SPI_CACHE (data);
160   
161   if (spi_cache_in (cache, gobj))
162     {
163 #ifdef SPI_ATK_DEBUG
164   g_debug ("CACHE REM - %s - %d - %s\n", atk_object_get_name (ATK_OBJECT (gobj)),
165             atk_object_get_role (ATK_OBJECT (gobj)),
166             spi_register_object_to_path (spi_global_register, gobj));
167 #endif
168       g_signal_emit (cache, cache_signals [OBJECT_REMOVED], 0, gobj);
169       g_hash_table_remove (cache->objects, gobj);
170     }
171   else if (g_queue_remove (cache->add_traversal, gobj))
172     {
173       g_object_unref (gobj);
174     }
175 }
176
177 static void
178 add_object (SpiCache * cache, GObject * gobj)
179 {
180   g_return_if_fail (G_IS_OBJECT (gobj));
181
182   g_hash_table_insert (cache->objects, gobj, NULL);
183
184 #ifdef SPI_ATK_DEBUG
185   g_debug ("CACHE ADD - %s - %d - %s\n", atk_object_get_name (ATK_OBJECT (gobj)),
186             atk_object_get_role (ATK_OBJECT (gobj)),
187             spi_register_object_to_path (spi_global_register, gobj));
188 #endif
189
190   g_signal_emit (cache, cache_signals [OBJECT_ADDED], 0, gobj);
191 }
192
193 /*---------------------------------------------------------------------------*/
194
195 static GRecMutex cache_mutex;
196
197 #ifdef SPI_ATK_DEBUG
198 static GStaticMutex recursion_check_guard = G_STATIC_MUTEX_INIT;
199 static gboolean recursion_check = FALSE;
200
201 static gboolean
202 recursion_check_and_set ()
203 {
204   gboolean ret;
205   g_static_mutex_lock (&recursion_check_guard);
206   ret = recursion_check;
207   recursion_check = TRUE;
208   g_static_mutex_unlock (&recursion_check_guard);
209   return ret;
210 }
211
212 static void
213 recursion_check_unset ()
214 {
215   g_static_mutex_lock (&recursion_check_guard);
216   recursion_check = FALSE;
217   g_static_mutex_unlock (&recursion_check_guard);
218 }
219 #endif /* SPI_ATK_DEBUG */
220
221 /*---------------------------------------------------------------------------*/
222
223 static void
224 append_children (AtkObject * accessible, GQueue * traversal)
225 {
226   AtkObject *current;
227   guint i;
228   gint count = atk_object_get_n_accessible_children (accessible);
229
230   if (count < 0)
231     count = 0;
232   for (i = 0; i < count; i++)
233     {
234       current = atk_object_ref_accessible_child (accessible, i);
235       if (current)
236         {
237           g_queue_push_tail (traversal, current);
238         }
239     }
240 }
241
242 /*
243  * Adds a subtree of accessible objects
244  * to the cache at the accessible object provided.
245  *
246  * The leaf nodes do not have their children
247  * registered. A node is considered a leaf
248  * if it has the state "manages-descendants"
249  * or if it has already been registered.
250  */
251 static void
252 add_subtree (SpiCache *cache, AtkObject * accessible)
253 {
254   g_return_if_fail (ATK_IS_OBJECT (accessible));
255
256   g_object_ref (accessible);
257   g_queue_push_tail (cache->add_traversal, accessible);
258   add_pending_items (cache);
259 }
260
261 static gboolean
262 add_pending_items (gpointer data)
263 {
264   SpiCache *cache = SPI_CACHE (data);
265   AtkObject *current;
266   GQueue *to_add;
267
268   to_add = g_queue_new ();
269
270   while (!g_queue_is_empty (cache->add_traversal))
271     {
272       AtkStateSet *set;
273
274       /* cache->add_traversal holds a ref to current */
275       current = g_queue_pop_head (cache->add_traversal);
276       set = atk_object_ref_state_set (current);
277
278       if (set && !atk_state_set_contains_state (set, ATK_STATE_TRANSIENT))
279         {
280           /* transfer the ref into to_add */
281           g_queue_push_tail (to_add, current);
282           if (!spi_cache_in (cache, G_OBJECT (current)) &&
283               !atk_state_set_contains_state  (set, ATK_STATE_MANAGES_DESCENDANTS) &&
284               !atk_state_set_contains_state  (set, ATK_STATE_DEFUNCT))
285             {
286               append_children (current, cache->add_traversal);
287             }
288         }
289       else
290         {
291           /* drop the ref for the removed object */
292           g_object_unref (current);
293         }
294
295       if (set)
296         g_object_unref (set);
297     }
298
299   while (!g_queue_is_empty (to_add))
300     {
301       current = g_queue_pop_head (to_add);
302
303       /* Make sure object is registerd so we are notified if it goes away */
304       g_free (spi_register_object_to_path (spi_global_register,
305               G_OBJECT (current)));
306
307       add_object (cache, G_OBJECT(current));
308       g_object_unref (G_OBJECT (current));
309     }
310
311   g_queue_free (to_add);
312   cache->add_pending_idle = 0;
313   return FALSE;
314 }
315
316 /*---------------------------------------------------------------------------*/
317
318 static gboolean
319 child_added_listener (GSignalInvocationHint * signal_hint,
320                       guint n_param_values,
321                       const GValue * param_values, gpointer data)
322 {
323   SpiCache *cache = spi_global_cache;
324   AtkObject *accessible;
325
326   const gchar *detail = NULL;
327
328   /* 
329    * Ensure that only accessibles already in the cache
330    * have their signals processed.
331    */
332   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
333   g_return_val_if_fail (ATK_IS_OBJECT (accessible), TRUE);
334
335   g_rec_mutex_lock (&cache_mutex);
336
337   if (spi_cache_in (cache, G_OBJECT(accessible)))
338     {
339 #ifdef SPI_ATK_DEBUG
340       if (recursion_check_and_set ())
341         g_warning ("AT-SPI: Recursive use of cache module");
342
343       g_debug ("AT-SPI: Tree update listener");
344 #endif
345       if (signal_hint->detail)
346         detail = g_quark_to_string (signal_hint->detail);
347
348       if (detail && !strncmp (detail, "add", 3))
349         {
350           gpointer child;
351           child = g_value_get_pointer (param_values + 2);
352           if (!child)
353             {
354               g_rec_mutex_unlock (&cache_mutex);
355               return TRUE;
356             }
357
358           g_object_ref (child);
359           g_queue_push_tail (cache->add_traversal, child);
360
361           if (cache->add_pending_idle == 0)
362             cache->add_pending_idle = spi_idle_add (add_pending_items, cache);
363         }
364 #ifdef SPI_ATK_DEBUG
365       recursion_check_unset ();
366 #endif
367     }
368
369   g_rec_mutex_unlock (&cache_mutex);
370
371   return TRUE;
372 }
373
374 /*---------------------------------------------------------------------------*/
375
376 static void
377 toplevel_added_listener (AtkObject * accessible,
378                          guint index, AtkObject * child)
379 {
380   SpiCache *cache = spi_global_cache;
381
382   g_return_if_fail (ATK_IS_OBJECT (accessible));
383
384   g_rec_mutex_lock (&cache_mutex);
385
386   if (spi_cache_in (cache, G_OBJECT(accessible)))
387     {
388 #ifdef SPI_ATK_DEBUG
389       if (recursion_check_and_set ())
390         g_warning ("AT-SPI: Recursive use of registration module");
391
392       g_debug ("AT-SPI: Toplevel added listener");
393 #endif
394       if (!ATK_IS_OBJECT (child))
395         {
396           child = atk_object_ref_accessible_child (accessible, index);
397         }
398       else
399         g_object_ref (child);
400
401       g_queue_push_tail (cache->add_traversal, child);
402
403       if (cache->add_pending_idle == 0)
404         cache->add_pending_idle = spi_idle_add (add_pending_items, cache);
405 #ifdef SPI_ATK_DEBUG
406       recursion_check_unset ();
407 #endif
408     }
409
410   g_rec_mutex_unlock (&cache_mutex);
411 }
412
413 /*---------------------------------------------------------------------------*/
414
415 void
416 spi_cache_foreach (SpiCache * cache, GHFunc func, gpointer data)
417 {
418   g_hash_table_foreach (cache->objects, func, data);
419 }
420
421 gboolean
422 spi_cache_in (SpiCache * cache, GObject * object)
423 {
424   if (!cache)
425     return FALSE;
426
427   if (g_hash_table_lookup_extended (cache->objects,
428                                     object,
429                                     NULL,
430                                     NULL))
431     return TRUE;
432   else
433     return FALSE;
434 }
435
436 #ifdef SPI_ATK_DEBUG
437 void
438 spi_cache_print_info (GObject * obj)
439 {
440   char * path = spi_register_object_to_path (spi_global_register, obj);
441  
442   if (spi_cache_in (spi_global_cache, obj))
443       g_printf ("%s IC\n", path);
444   else
445       g_printf ("%s NC\n", path);
446
447   if (path)
448       g_free (path);
449 }
450 #endif
451
452 /*END------------------------------------------------------------------------*/