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