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 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.
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.
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.
26 #include "accessible-cache.h"
27 #include "accessible-register.h"
30 SpiCache *spi_global_cache = NULL;
33 child_added_listener (GSignalInvocationHint * signal_hint,
35 const GValue * param_values, gpointer data);
38 toplevel_added_listener (AtkObject * accessible,
39 guint index, AtkObject * child);
42 remove_object (GObject * source, GObject * gobj, gpointer data);
45 add_object (SpiCache * cache, GObject * gobj);
48 add_subtree (SpiCache *cache, AtkObject * accessible);
51 add_pending_items (gpointer data);
53 /*---------------------------------------------------------------------------*/
56 spi_cache_finalize (GObject * object);
59 spi_cache_dispose (GObject * object);
61 /*---------------------------------------------------------------------------*/
69 static guint cache_signals[LAST_SIGNAL] = { 0 };
71 /*---------------------------------------------------------------------------*/
73 G_DEFINE_TYPE (SpiCache, spi_cache, G_TYPE_OBJECT)
75 static void spi_cache_class_init (SpiCacheClass * klass)
77 GObjectClass *object_class = (GObjectClass *) klass;
79 spi_cache_parent_class = g_type_class_ref (G_TYPE_OBJECT);
81 object_class->finalize = spi_cache_finalize;
82 object_class->dispose = spi_cache_dispose;
84 cache_signals [OBJECT_ADDED] = \
85 g_signal_new ("object-added",
91 g_cclosure_marshal_VOID__OBJECT,
96 cache_signals [OBJECT_REMOVED] = \
97 g_signal_new ("object-removed",
103 g_cclosure_marshal_VOID__OBJECT,
110 spi_cache_init (SpiCache * cache)
112 cache->objects = g_hash_table_new (g_direct_hash, g_direct_equal);
113 cache->add_traversal = g_queue_new ();
116 if (g_thread_supported ())
117 g_message ("AT-SPI: Threads enabled");
119 g_debug ("AT-SPI: Initial Atk tree regisration");
122 g_signal_connect (spi_global_register,
123 "object-deregistered",
124 (GCallback) remove_object, cache);
126 add_subtree (cache, spi_global_app_data->root);
128 atk_add_global_event_listener (child_added_listener,
129 "Gtk:AtkObject:children-changed");
131 g_signal_connect (G_OBJECT (spi_global_app_data->root),
132 "children-changed::add",
133 (GCallback) toplevel_added_listener, NULL);
137 spi_cache_finalize (GObject * object)
139 SpiCache *cache = SPI_CACHE (object);
141 while (!g_queue_is_empty (cache->add_traversal))
142 g_object_unref (G_OBJECT (g_queue_pop_head (cache->add_traversal)));
143 g_queue_free (cache->add_traversal);
144 g_free (cache->objects);
146 G_OBJECT_CLASS (spi_cache_parent_class)->finalize (object);
150 spi_cache_dispose (GObject * object)
152 SpiCache *cache = SPI_CACHE (object);
154 G_OBJECT_CLASS (spi_cache_parent_class)->dispose (object);
157 /*---------------------------------------------------------------------------*/
160 remove_object (GObject * source, GObject * gobj, gpointer data)
162 SpiCache *cache = SPI_CACHE (data);
164 if (spi_cache_in (cache, gobj))
167 g_debug ("CACHE REM - %s - %d - %s\n", atk_object_get_name (ATK_OBJECT (gobj)),
168 atk_object_get_role (ATK_OBJECT (gobj)),
169 spi_register_object_to_path (spi_global_register, gobj));
171 g_signal_emit (cache, cache_signals [OBJECT_REMOVED], 0, gobj);
172 g_hash_table_remove (cache->objects, gobj);
177 add_object (SpiCache * cache, GObject * gobj)
179 g_return_if_fail (G_IS_OBJECT (gobj));
181 g_hash_table_insert (cache->objects, gobj, NULL);
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));
189 g_signal_emit (cache, cache_signals [OBJECT_ADDED], 0, gobj);
192 /*---------------------------------------------------------------------------*/
194 static GStaticRecMutex cache_mutex = G_STATIC_REC_MUTEX_INIT;
195 static GStaticMutex recursion_check_guard = G_STATIC_MUTEX_INIT;
197 static gboolean recursion_check = FALSE;
200 recursion_check_and_set ()
203 g_static_mutex_lock (&recursion_check_guard);
204 ret = recursion_check;
205 recursion_check = TRUE;
206 g_static_mutex_unlock (&recursion_check_guard);
211 recursion_check_unset ()
213 g_static_mutex_lock (&recursion_check_guard);
214 recursion_check = FALSE;
215 g_static_mutex_unlock (&recursion_check_guard);
218 /*---------------------------------------------------------------------------*/
221 append_children (AtkObject * accessible, GQueue * traversal)
225 gint count = atk_object_get_n_accessible_children (accessible);
229 for (i = 0; i < count; i++)
231 current = atk_object_ref_accessible_child (accessible, i);
234 g_queue_push_tail (traversal, current);
240 * Adds a subtree of accessible objects
241 * to the cache at the accessible object provided.
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.
249 add_subtree (SpiCache *cache, AtkObject * accessible)
251 g_return_if_fail (ATK_IS_OBJECT (accessible));
253 g_object_ref (accessible);
254 g_queue_push_tail (cache->add_traversal, accessible);
255 add_pending_items (cache);
259 add_pending_items (gpointer data)
261 SpiCache *cache = SPI_CACHE (data);
265 to_add = g_queue_new ();
267 while (!g_queue_is_empty (cache->add_traversal))
271 current = g_queue_pop_head (cache->add_traversal);
272 set = atk_object_ref_state_set (current);
274 if (!atk_state_set_contains_state (set, ATK_STATE_TRANSIENT))
276 g_queue_push_tail (to_add, current);
277 if (!spi_cache_in (cache, G_OBJECT (current)) &&
278 !atk_state_set_contains_state (set, ATK_STATE_MANAGES_DESCENDANTS))
280 append_children (current, cache->add_traversal);
284 g_object_unref (set);
287 while (!g_queue_is_empty (to_add))
289 current = g_queue_pop_head (to_add);
291 /* Make sure object is registerd so we are notified if it goes away */
292 g_free (spi_register_object_to_path (spi_global_register,
293 G_OBJECT (current)));
295 add_object (cache, G_OBJECT(current));
296 g_object_unref (G_OBJECT (current));
299 g_queue_free (to_add);
300 cache->add_pending_idle = 0;
304 /*---------------------------------------------------------------------------*/
307 child_added_listener (GSignalInvocationHint * signal_hint,
308 guint n_param_values,
309 const GValue * param_values, gpointer data)
311 SpiCache *cache = spi_global_cache;
313 AtkObject *accessible;
316 const gchar *detail = NULL;
318 g_static_rec_mutex_lock (&cache_mutex);
321 * Ensure that only accessibles already in the cache
322 * have their signals processed.
324 accessible = ATK_OBJECT (g_value_get_object (¶m_values[0]));
325 g_return_val_if_fail (ATK_IS_OBJECT (accessible), TRUE);
327 if (spi_cache_in (cache, G_OBJECT(accessible)))
330 if (recursion_check_and_set ())
331 g_warning ("AT-SPI: Recursive use of cache module");
333 g_debug ("AT-SPI: Tree update listener");
335 if (signal_hint->detail)
336 detail = g_quark_to_string (signal_hint->detail);
338 if (detail && !strncmp (detail, "add", 3))
341 int index = g_value_get_uint (param_values + 1);
342 child = g_value_get_pointer (param_values + 2);
344 if (!ATK_IS_OBJECT (child))
346 child = atk_object_ref_accessible_child (accessible, index);
348 g_object_ref (child);
349 g_queue_push_tail (cache->add_traversal, child);
351 if (cache->add_pending_idle == 0)
352 cache->add_pending_idle = g_idle_add (add_pending_items, cache);
355 recursion_check_unset ();
359 g_static_rec_mutex_unlock (&cache_mutex);
364 /*---------------------------------------------------------------------------*/
367 toplevel_added_listener (AtkObject * accessible,
368 guint index, AtkObject * child)
370 SpiCache *cache = spi_global_cache;
372 g_static_rec_mutex_lock (&cache_mutex);
374 g_return_if_fail (ATK_IS_OBJECT (accessible));
376 if (spi_cache_in (cache, G_OBJECT(accessible)))
379 if (recursion_check_and_set ())
380 g_warning ("AT-SPI: Recursive use of registration module");
382 g_debug ("AT-SPI: Toplevel added listener");
384 if (!ATK_IS_OBJECT (child))
386 child = atk_object_ref_accessible_child (accessible, index);
389 g_object_ref (child);
391 g_queue_push_tail (cache->add_traversal, child);
393 if (cache->add_pending_idle == 0)
394 cache->add_pending_idle = g_idle_add (add_pending_items, cache);
396 recursion_check_unset ();
400 g_static_rec_mutex_unlock (&cache_mutex);
403 /*---------------------------------------------------------------------------*/
406 spi_cache_foreach (SpiCache * cache, GHFunc func, gpointer data)
408 g_hash_table_foreach (cache->objects, func, data);
412 spi_cache_in (SpiCache * cache, GObject * object)
414 if (g_hash_table_lookup_extended (cache->objects,
425 spi_cache_print_info (GObject * obj)
427 char * path = spi_register_object_to_path (spi_global_register, obj);
429 if (spi_cache_in (spi_global_cache, obj))
430 g_printf ("%s IC\n", path);
432 g_printf ("%s NC\n", path);
439 /*END------------------------------------------------------------------------*/