Change the keystroke delivery methid re-entrancy from
[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 /*---------------------------------------------------------------------------*/
50
51 static void
52 spi_cache_finalize (GObject * object);
53
54 static void
55 spi_cache_dispose (GObject * object);
56
57 /*---------------------------------------------------------------------------*/
58
59 enum
60 {
61   OBJECT_ADDED,
62   OBJECT_REMOVED,
63   LAST_SIGNAL
64 };
65 static guint cache_signals[LAST_SIGNAL] = { 0 };
66
67 /*---------------------------------------------------------------------------*/
68
69 G_DEFINE_TYPE (SpiCache, spi_cache, G_TYPE_OBJECT)
70
71 static void spi_cache_class_init (SpiCacheClass * klass)
72 {
73   GObjectClass *object_class = (GObjectClass *) klass;
74
75   spi_cache_parent_class = g_type_class_ref (G_TYPE_OBJECT);
76
77   object_class->finalize = spi_cache_finalize;
78   object_class->dispose = spi_cache_dispose;
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
110 #ifdef SPI_ATK_DEBUG
111   if (g_thread_supported ())
112     g_message ("AT-SPI: Threads enabled");
113
114   g_debug ("AT-SPI: Initial Atk tree regisration");
115 #endif
116
117   g_signal_connect (spi_global_register,
118                     "object-deregistered",
119                     (GCallback) remove_object, cache);
120
121   add_subtree (cache, spi_global_app_data->root);
122
123   atk_add_global_event_listener (child_added_listener,
124                                  "Gtk:AtkObject:children-changed");
125
126   g_signal_connect (G_OBJECT (spi_global_app_data->root),
127                     "children-changed::add",
128                     (GCallback) toplevel_added_listener, NULL);
129 }
130
131 static void
132 spi_cache_finalize (GObject * object)
133 {
134   SpiCache *cache = SPI_CACHE (object);
135
136   g_free (cache->objects);
137
138   G_OBJECT_CLASS (spi_cache_parent_class)->finalize (object);
139 }
140
141 static void
142 spi_cache_dispose (GObject * object)
143 {
144   SpiCache *cache = SPI_CACHE (object);
145
146   G_OBJECT_CLASS (spi_cache_parent_class)->dispose (object);
147 }
148
149 /*---------------------------------------------------------------------------*/
150
151 static void
152 remove_object (GObject * source, GObject * gobj, gpointer data)
153 {
154   SpiCache *cache = SPI_CACHE (data);
155   
156   if (spi_cache_in (cache, gobj))
157     {
158 #ifdef SPI_ATK_DEBUG
159   g_debug ("CACHE REM - %s - %d - %s\n", atk_object_get_name (ATK_OBJECT (gobj)),
160             atk_object_get_role (ATK_OBJECT (gobj)),
161             spi_register_object_to_path (spi_global_register, gobj));
162 #endif
163       g_signal_emit (cache, cache_signals [OBJECT_REMOVED], 0, gobj);
164       g_hash_table_remove (cache->objects, gobj);
165     }
166 }
167
168 static void
169 add_object (SpiCache * cache, GObject * gobj)
170 {
171   g_return_if_fail (G_IS_OBJECT (gobj));
172
173   g_hash_table_insert (cache->objects, gobj, NULL);
174
175 #ifdef SPI_ATK_DEBUG
176   g_debug ("CACHE ADD - %s - %d - %s\n", atk_object_get_name (ATK_OBJECT (gobj)),
177             atk_object_get_role (ATK_OBJECT (gobj)),
178             spi_register_object_to_path (spi_global_register, gobj));
179 #endif
180
181   g_signal_emit (cache, cache_signals [OBJECT_ADDED], 0, gobj);
182 }
183
184 /*---------------------------------------------------------------------------*/
185
186 static GStaticRecMutex cache_mutex        = G_STATIC_REC_MUTEX_INIT;
187 static GStaticMutex recursion_check_guard = G_STATIC_MUTEX_INIT;
188
189 static gboolean recursion_check = FALSE;
190
191 static gboolean
192 recursion_check_and_set ()
193 {
194   gboolean ret;
195   g_static_mutex_lock (&recursion_check_guard);
196   ret = recursion_check;
197   recursion_check = TRUE;
198   g_static_mutex_unlock (&recursion_check_guard);
199   return ret;
200 }
201
202 static void
203 recursion_check_unset ()
204 {
205   g_static_mutex_lock (&recursion_check_guard);
206   recursion_check = FALSE;
207   g_static_mutex_unlock (&recursion_check_guard);
208 }
209
210 /*---------------------------------------------------------------------------*/
211
212 static void
213 append_children (AtkObject * accessible, GQueue * traversal)
214 {
215   AtkObject *current;
216   guint i;
217   gint count = atk_object_get_n_accessible_children (accessible);
218
219   if (count < 0)
220     count = 0;
221   for (i = 0; i < count; i++)
222     {
223       current = atk_object_ref_accessible_child (accessible, i);
224       if (current)
225         {
226           g_queue_push_tail (traversal, current);
227         }
228     }
229 }
230
231 /*
232  * Adds a subtree of accessible objects
233  * to the cache at the accessible object provided.
234  *
235  * The leaf nodes do not have their children
236  * registered. A node is considered a leaf
237  * if it has the state "manages-descendants"
238  * or if it has already been registered.
239  */
240 static void
241 add_subtree (SpiCache *cache, AtkObject * accessible)
242 {
243   AtkObject *current;
244   GQueue *traversal;
245   GQueue *to_add;
246
247   g_return_if_fail (ATK_IS_OBJECT (accessible));
248
249   traversal = g_queue_new ();
250   to_add = g_queue_new ();
251
252   g_object_ref (accessible);
253   g_queue_push_tail (traversal, accessible);
254
255   while (!g_queue_is_empty (traversal))
256     {
257       AtkStateSet *set;
258       
259       current = g_queue_pop_head (traversal);
260       set = atk_object_ref_state_set (current);
261
262       if (!atk_state_set_contains_state (set, ATK_STATE_TRANSIENT))
263         {
264           g_queue_push_tail (to_add, current);
265           if (!spi_cache_in (cache, G_OBJECT (current)) &&
266               !atk_state_set_contains_state  (set, ATK_STATE_MANAGES_DESCENDANTS))
267             {
268               append_children (current, traversal);
269             }
270         }
271
272       g_object_unref (set);
273     }
274
275   while (!g_queue_is_empty (to_add))
276     {
277       current = g_queue_pop_head (to_add);
278       add_object (cache, G_OBJECT(current));
279       g_object_unref (G_OBJECT (current));
280     }
281
282   g_queue_free (traversal);
283   g_queue_free (to_add);
284 }
285
286 /*---------------------------------------------------------------------------*/
287
288 static gboolean
289 child_added_listener (GSignalInvocationHint * signal_hint,
290                       guint n_param_values,
291                       const GValue * param_values, gpointer data)
292 {
293   SpiCache *cache = spi_global_cache;
294
295   AtkObject *accessible;
296   AtkObject *child;
297
298   const gchar *detail = NULL;
299
300   g_static_rec_mutex_lock (&cache_mutex);
301
302   /* 
303    * Ensure that only accessibles already in the cache
304    * have their signals processed.
305    */
306   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
307   g_return_val_if_fail (ATK_IS_OBJECT (accessible), TRUE);
308
309   if (spi_cache_in (cache, G_OBJECT(accessible)))
310     {
311 #ifdef SPI_ATK_DEBUG
312       if (recursion_check_and_set ())
313         g_warning ("AT-SPI: Recursive use of cache module");
314
315       g_debug ("AT-SPI: Tree update listener");
316 #endif
317       if (signal_hint->detail)
318         detail = g_quark_to_string (signal_hint->detail);
319
320       if (!g_strcmp0 (detail, "add"))
321         {
322           gpointer child;
323           int index = g_value_get_uint (param_values + 1);
324           child = g_value_get_pointer (param_values + 2);
325
326           if (!ATK_IS_OBJECT (child))
327            {
328              child = atk_object_ref_accessible_child (accessible, index);
329            }
330           add_subtree (cache, child);
331         }
332 #ifdef SPI_ATK_DEBUG
333       recursion_check_unset ();
334 #endif
335     }
336
337   g_static_rec_mutex_unlock (&cache_mutex);
338
339   return TRUE;
340 }
341
342 /*---------------------------------------------------------------------------*/
343
344 static void
345 toplevel_added_listener (AtkObject * accessible,
346                          guint index, AtkObject * child)
347 {
348   SpiCache *cache = spi_global_cache;
349
350   g_static_rec_mutex_lock (&cache_mutex);
351
352   g_return_if_fail (ATK_IS_OBJECT (accessible));
353
354   if (spi_cache_in (cache, G_OBJECT(accessible)))
355     {
356 #ifdef SPI_ATK_DEBUG
357       if (recursion_check_and_set ())
358         g_warning ("AT-SPI: Recursive use of registration module");
359
360       g_debug ("AT-SPI: Toplevel added listener");
361 #endif
362       if (!ATK_IS_OBJECT (child))
363         {
364           child = atk_object_ref_accessible_child (accessible, index);
365         }
366       add_subtree (cache, child);
367 #ifdef SPI_ATK_DEBUG
368       recursion_check_unset ();
369 #endif
370     }
371
372   g_static_rec_mutex_unlock (&cache_mutex);
373 }
374
375 /*---------------------------------------------------------------------------*/
376
377 void
378 spi_cache_foreach (SpiCache * cache, GHFunc func, gpointer data)
379 {
380   g_hash_table_foreach (cache->objects, func, data);
381 }
382
383 gboolean
384 spi_cache_in (SpiCache * cache, GObject * object)
385 {
386   if (g_hash_table_lookup_extended (cache->objects,
387                                     object,
388                                     NULL,
389                                     NULL))
390     return TRUE;
391   else
392     return FALSE;
393 }
394
395 #ifdef SPI_ATK_DEBUG
396 void
397 spi_cache_print_info (GObject * obj)
398 {
399   char * path = spi_register_object_to_path (spi_global_register, obj);
400  
401   if (spi_cache_in (spi_global_cache, obj))
402       g_printf ("%s IC\n", path);
403   else
404       g_printf ("%s NC\n", path);
405
406   if (path)
407       g_free (path);
408 }
409 #endif
410
411 /*END------------------------------------------------------------------------*/