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