2 * AT-SPI - Assistive Technology Service Provider Interface
3 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
5 * Copyright 2009, 2010 Codethink Ltd.
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.
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.
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.
26 #include "accessible-cache.h"
27 #include "accessible-register.h"
31 SpiCache *spi_global_cache = NULL;
34 child_added_listener (GSignalInvocationHint * signal_hint,
36 const GValue * param_values, gpointer data);
39 toplevel_added_listener (AtkObject * accessible,
40 guint index, AtkObject * child);
43 remove_object (GObject * source, GObject * gobj, gpointer data);
46 add_object (SpiCache * cache, GObject * gobj);
49 add_subtree (SpiCache *cache, AtkObject * accessible);
52 add_pending_items (gpointer data);
54 /*---------------------------------------------------------------------------*/
57 spi_cache_finalize (GObject * object);
59 /*---------------------------------------------------------------------------*/
67 static guint cache_signals[LAST_SIGNAL] = { 0 };
69 /*---------------------------------------------------------------------------*/
71 G_DEFINE_TYPE (SpiCache, spi_cache, G_TYPE_OBJECT)
73 static void spi_cache_class_init (SpiCacheClass * klass)
75 GObjectClass *object_class = (GObjectClass *) klass;
77 spi_cache_parent_class = g_type_class_ref (G_TYPE_OBJECT);
79 object_class->finalize = spi_cache_finalize;
81 cache_signals [OBJECT_ADDED] = \
82 g_signal_new ("object-added",
88 g_cclosure_marshal_VOID__OBJECT,
93 cache_signals [OBJECT_REMOVED] = \
94 g_signal_new ("object-removed",
100 g_cclosure_marshal_VOID__OBJECT,
107 spi_cache_init (SpiCache * cache)
109 cache->objects = g_hash_table_new (g_direct_hash, g_direct_equal);
110 cache->add_traversal = g_queue_new ();
113 if (g_thread_supported ())
114 g_message ("AT-SPI: Threads enabled");
116 g_debug ("AT-SPI: Initial Atk tree regisration");
119 g_signal_connect (spi_global_register,
120 "object-deregistered",
121 (GCallback) remove_object, cache);
123 add_subtree (cache, spi_global_app_data->root);
125 cache->child_added_listener = atk_add_global_event_listener (child_added_listener,
126 "Gtk:AtkObject:children-changed");
128 g_signal_connect (G_OBJECT (spi_global_app_data->root),
129 "children-changed::add",
130 (GCallback) toplevel_added_listener, NULL);
134 spi_cache_finalize (GObject * object)
136 SpiCache *cache = SPI_CACHE (object);
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);
143 g_signal_handlers_disconnect_by_func (spi_global_register,
144 (GCallback) remove_object, cache);
146 g_signal_handlers_disconnect_by_func (G_OBJECT (spi_global_app_data->root),
147 (GCallback) toplevel_added_listener, NULL);
149 atk_remove_global_event_listener (cache->child_added_listener);
151 G_OBJECT_CLASS (spi_cache_parent_class)->finalize (object);
154 /*---------------------------------------------------------------------------*/
157 remove_object (GObject * source, GObject * gobj, gpointer data)
159 SpiCache *cache = SPI_CACHE (data);
161 if (spi_cache_in (cache, gobj))
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));
168 g_signal_emit (cache, cache_signals [OBJECT_REMOVED], 0, gobj);
169 g_hash_table_remove (cache->objects, gobj);
171 else if (g_queue_remove (cache->add_traversal, gobj))
173 g_object_unref (gobj);
178 add_object (SpiCache * cache, GObject * gobj)
180 g_return_if_fail (G_IS_OBJECT (gobj));
182 g_hash_table_insert (cache->objects, gobj, NULL);
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));
190 g_signal_emit (cache, cache_signals [OBJECT_ADDED], 0, gobj);
193 /*---------------------------------------------------------------------------*/
195 static GRecMutex cache_mutex;
198 static GStaticMutex recursion_check_guard = G_STATIC_MUTEX_INIT;
199 static gboolean recursion_check = FALSE;
202 recursion_check_and_set ()
205 g_static_mutex_lock (&recursion_check_guard);
206 ret = recursion_check;
207 recursion_check = TRUE;
208 g_static_mutex_unlock (&recursion_check_guard);
213 recursion_check_unset ()
215 g_static_mutex_lock (&recursion_check_guard);
216 recursion_check = FALSE;
217 g_static_mutex_unlock (&recursion_check_guard);
219 #endif /* SPI_ATK_DEBUG */
221 /*---------------------------------------------------------------------------*/
224 append_children (AtkObject * accessible, GQueue * traversal)
228 gint count = atk_object_get_n_accessible_children (accessible);
232 for (i = 0; i < count; i++)
234 current = atk_object_ref_accessible_child (accessible, i);
237 g_queue_push_tail (traversal, current);
243 * Adds a subtree of accessible objects
244 * to the cache at the accessible object provided.
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.
252 add_subtree (SpiCache *cache, AtkObject * accessible)
254 g_return_if_fail (ATK_IS_OBJECT (accessible));
256 g_object_ref (accessible);
257 g_queue_push_tail (cache->add_traversal, accessible);
258 add_pending_items (cache);
262 add_pending_items (gpointer data)
264 SpiCache *cache = SPI_CACHE (data);
268 to_add = g_queue_new ();
270 while (!g_queue_is_empty (cache->add_traversal))
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);
278 if (set && !atk_state_set_contains_state (set, ATK_STATE_TRANSIENT))
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))
286 append_children (current, cache->add_traversal);
291 /* drop the ref for the removed object */
292 g_object_unref (current);
296 g_object_unref (set);
299 while (!g_queue_is_empty (to_add))
301 current = g_queue_pop_head (to_add);
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)));
307 add_object (cache, G_OBJECT(current));
308 g_object_unref (G_OBJECT (current));
311 g_queue_free (to_add);
312 cache->add_pending_idle = 0;
316 /*---------------------------------------------------------------------------*/
319 child_added_listener (GSignalInvocationHint * signal_hint,
320 guint n_param_values,
321 const GValue * param_values, gpointer data)
323 SpiCache *cache = spi_global_cache;
324 AtkObject *accessible;
326 const gchar *detail = NULL;
329 * Ensure that only accessibles already in the cache
330 * have their signals processed.
332 accessible = ATK_OBJECT (g_value_get_object (¶m_values[0]));
333 g_return_val_if_fail (ATK_IS_OBJECT (accessible), TRUE);
335 g_rec_mutex_lock (&cache_mutex);
337 if (spi_cache_in (cache, G_OBJECT(accessible)))
340 if (recursion_check_and_set ())
341 g_warning ("AT-SPI: Recursive use of cache module");
343 g_debug ("AT-SPI: Tree update listener");
345 if (signal_hint->detail)
346 detail = g_quark_to_string (signal_hint->detail);
348 if (detail && !strncmp (detail, "add", 3))
351 child = g_value_get_pointer (param_values + 2);
354 g_rec_mutex_unlock (&cache_mutex);
358 g_object_ref (child);
359 g_queue_push_tail (cache->add_traversal, child);
361 if (cache->add_pending_idle == 0)
362 cache->add_pending_idle = spi_idle_add (add_pending_items, cache);
365 recursion_check_unset ();
369 g_rec_mutex_unlock (&cache_mutex);
374 /*---------------------------------------------------------------------------*/
377 toplevel_added_listener (AtkObject * accessible,
378 guint index, AtkObject * child)
380 SpiCache *cache = spi_global_cache;
382 g_return_if_fail (ATK_IS_OBJECT (accessible));
384 g_rec_mutex_lock (&cache_mutex);
386 if (spi_cache_in (cache, G_OBJECT(accessible)))
389 if (recursion_check_and_set ())
390 g_warning ("AT-SPI: Recursive use of registration module");
392 g_debug ("AT-SPI: Toplevel added listener");
394 if (!ATK_IS_OBJECT (child))
396 child = atk_object_ref_accessible_child (accessible, index);
399 g_object_ref (child);
401 g_queue_push_tail (cache->add_traversal, child);
403 if (cache->add_pending_idle == 0)
404 cache->add_pending_idle = spi_idle_add (add_pending_items, cache);
406 recursion_check_unset ();
410 g_rec_mutex_unlock (&cache_mutex);
413 /*---------------------------------------------------------------------------*/
416 spi_cache_foreach (SpiCache * cache, GHFunc func, gpointer data)
418 g_hash_table_foreach (cache->objects, func, data);
422 spi_cache_in (SpiCache * cache, GObject * object)
427 if (g_hash_table_lookup_extended (cache->objects,
438 spi_cache_print_info (GObject * obj)
440 char * path = spi_register_object_to_path (spi_global_register, obj);
442 if (spi_cache_in (spi_global_cache, obj))
443 g_printf ("%s IC\n", path);
445 g_printf ("%s NC\n", path);
452 /*END------------------------------------------------------------------------*/