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 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.
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
 * Lesser General Public License for more details.
16
 *
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.
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
#include "event.h"
30

            
31
SpiCache *spi_global_cache = NULL;
32

            
33
static gboolean
34
child_added_listener (GSignalInvocationHint *signal_hint,
35
                      guint n_param_values,
36
                      const GValue *param_values,
37
                      gpointer data);
38

            
39
static void
40
toplevel_added_listener (AtkObject *accessible,
41
                         guint index,
42
                         AtkObject *child);
43

            
44
static void
45
remove_object (GObject *source, GObject *gobj, gpointer data);
46

            
47
static void
48
add_object (SpiCache *cache, GObject *gobj);
49

            
50
static void
51
add_subtree (SpiCache *cache, AtkObject *accessible);
52

            
53
static gboolean
54
add_pending_items (gpointer data);
55

            
56
/*---------------------------------------------------------------------------*/
57

            
58
static void
59
spi_cache_finalize (GObject *object);
60

            
61
/*---------------------------------------------------------------------------*/
62

            
63
enum
64
{
65
  OBJECT_ADDED,
66
  OBJECT_REMOVED,
67
  LAST_SIGNAL
68
};
69
static guint cache_signals[LAST_SIGNAL] = { 0 };
70

            
71
/*---------------------------------------------------------------------------*/
72

            
73
161
G_DEFINE_TYPE (SpiCache, spi_cache, G_TYPE_OBJECT)
74

            
75
static void
76
161
spi_cache_class_init (SpiCacheClass *klass)
77
{
78
161
  GObjectClass *object_class = (GObjectClass *) klass;
79

            
80
161
  spi_cache_parent_class = g_type_class_ref (G_TYPE_OBJECT);
81

            
82
161
  object_class->finalize = spi_cache_finalize;
83

            
84
161
  cache_signals[OBJECT_ADDED] =
85
161
      g_signal_new ("object-added",
86
                    SPI_CACHE_TYPE,
87
                    G_SIGNAL_ACTION,
88
                    0,
89
                    NULL,
90
                    NULL,
91
                    g_cclosure_marshal_VOID__OBJECT,
92
                    G_TYPE_NONE,
93
                    1,
94
                    G_TYPE_OBJECT);
95

            
96
161
  cache_signals[OBJECT_REMOVED] =
97
161
      g_signal_new ("object-removed",
98
                    SPI_CACHE_TYPE,
99
                    G_SIGNAL_ACTION,
100
                    0,
101
                    NULL,
102
                    NULL,
103
                    g_cclosure_marshal_VOID__OBJECT,
104
                    G_TYPE_NONE,
105
                    1,
106
                    G_TYPE_OBJECT);
107
161
}
108

            
109
static void
110
161
spi_cache_init (SpiCache *cache)
111
{
112
161
  cache->objects = g_hash_table_new (g_direct_hash, g_direct_equal);
113
161
  cache->add_traversal = g_queue_new ();
114

            
115
#ifdef SPI_ATK_DEBUG
116
  if (g_thread_supported ())
117
    g_message ("AT-SPI: Threads enabled");
118

            
119
  g_debug ("AT-SPI: Initial Atk tree regisration");
120
#endif
121

            
122
161
  g_signal_connect (spi_global_register,
123
                    "object-deregistered",
124
                    (GCallback) remove_object, cache);
125

            
126
161
  add_subtree (cache, spi_global_app_data->root);
127

            
128
161
  cache->child_added_listener = atk_add_global_event_listener (child_added_listener,
129
                                                               "Gtk:AtkObject:children-changed");
130

            
131
161
  g_signal_connect (G_OBJECT (spi_global_app_data->root),
132
                    "children-changed::add",
133
                    (GCallback) toplevel_added_listener, NULL);
134
161
}
135

            
136
static void
137
161
spi_cache_finalize (GObject *object)
138
{
139
161
  SpiCache *cache = SPI_CACHE (object);
140

            
141
161
  while (!g_queue_is_empty (cache->add_traversal))
142
    g_object_unref (G_OBJECT (g_queue_pop_head (cache->add_traversal)));
143
161
  g_queue_free (cache->add_traversal);
144
161
  g_hash_table_unref (cache->objects);
145

            
146
161
  g_signal_handlers_disconnect_by_func (spi_global_register,
147
                                        (GCallback) remove_object, cache);
148

            
149
161
  g_signal_handlers_disconnect_by_func (G_OBJECT (spi_global_app_data->root),
150
                                        (GCallback) toplevel_added_listener, NULL);
151

            
152
161
  atk_remove_global_event_listener (cache->child_added_listener);
153

            
154
161
  G_OBJECT_CLASS (spi_cache_parent_class)->finalize (object);
155
161
}
156

            
157
/*---------------------------------------------------------------------------*/
158

            
159
static void
160
remove_object (GObject *source, GObject *gobj, gpointer data)
161
{
162
  SpiCache *cache = SPI_CACHE (data);
163

            
164
  if (spi_cache_in (cache, gobj))
165
    {
166
#ifdef SPI_ATK_DEBUG
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));
170
#endif
171
      g_signal_emit (cache, cache_signals[OBJECT_REMOVED], 0, gobj);
172
      g_hash_table_remove (cache->objects, gobj);
173
    }
174
  else if (g_queue_remove (cache->add_traversal, gobj))
175
    {
176
      g_object_unref (gobj);
177
    }
178
}
179

            
180
static void
181
1595
add_object (SpiCache *cache, GObject *gobj)
182
{
183
1595
  g_return_if_fail (G_IS_OBJECT (gobj));
184

            
185
1595
  g_hash_table_insert (cache->objects, gobj, NULL);
186

            
187
#ifdef SPI_ATK_DEBUG
188
  g_debug ("CACHE ADD - %s - %d - %s\n", atk_object_get_name (ATK_OBJECT (gobj)),
189
           atk_object_get_role (ATK_OBJECT (gobj)),
190
           spi_register_object_to_path (spi_global_register, gobj));
191
#endif
192

            
193
1595
  g_signal_emit (cache, cache_signals[OBJECT_ADDED], 0, gobj);
194
}
195

            
196
/*---------------------------------------------------------------------------*/
197

            
198
static GRecMutex cache_mutex;
199

            
200
#ifdef SPI_ATK_DEBUG
201
static GStaticMutex recursion_check_guard = G_STATIC_MUTEX_INIT;
202
static gboolean recursion_check = FALSE;
203

            
204
static gboolean
205
recursion_check_and_set ()
206
{
207
  gboolean ret;
208
  g_static_mutex_lock (&recursion_check_guard);
209
  ret = recursion_check;
210
  recursion_check = TRUE;
211
  g_static_mutex_unlock (&recursion_check_guard);
212
  return ret;
213
}
214

            
215
static void
216
recursion_check_unset ()
217
{
218
  g_static_mutex_lock (&recursion_check_guard);
219
  recursion_check = FALSE;
220
  g_static_mutex_unlock (&recursion_check_guard);
221
}
222
#endif /* SPI_ATK_DEBUG */
223

            
224
/*---------------------------------------------------------------------------*/
225

            
226
static void
227
1595
append_children (AtkObject *accessible, GQueue *traversal)
228
{
229
  AtkObject *current;
230
  guint i;
231
1595
  gint count = atk_object_get_n_accessible_children (accessible);
232

            
233
1595
  if (count < 0)
234
    count = 0;
235
3029
  for (i = 0; i < count; i++)
236
    {
237
1434
      current = atk_object_ref_accessible_child (accessible, i);
238
1434
      if (current)
239
        {
240
1434
          g_queue_push_tail (traversal, current);
241
        }
242
    }
243
1595
}
244

            
245
/*
246
 * Adds a subtree of accessible objects
247
 * to the cache at the accessible object provided.
248
 *
249
 * The leaf nodes do not have their children
250
 * registered. A node is considered a leaf
251
 * if it has the state "manages-descendants"
252
 * or if it has already been registered.
253
 */
254
static void
255
161
add_subtree (SpiCache *cache, AtkObject *accessible)
256
{
257
161
  g_return_if_fail (ATK_IS_OBJECT (accessible));
258

            
259
161
  g_object_ref (accessible);
260
161
  g_queue_push_tail (cache->add_traversal, accessible);
261
161
  add_pending_items (cache);
262
}
263

            
264
static gboolean
265
161
add_pending_items (gpointer data)
266
{
267
161
  SpiCache *cache = SPI_CACHE (data);
268
  AtkObject *current;
269
  GQueue *to_add;
270

            
271
161
  to_add = g_queue_new ();
272

            
273
1917
  while (!g_queue_is_empty (cache->add_traversal))
274
    {
275
      AtkStateSet *set;
276

            
277
      /* cache->add_traversal holds a ref to current */
278
1595
      current = g_queue_pop_head (cache->add_traversal);
279
1595
      set = atk_object_ref_state_set (current);
280

            
281
1595
      if (set && !atk_state_set_contains_state (set, ATK_STATE_TRANSIENT))
282
        {
283
          /* transfer the ref into to_add */
284
1595
          g_queue_push_tail (to_add, current);
285
4785
          if (!spi_cache_in (cache, G_OBJECT (current)) &&
286
3190
              !atk_state_set_contains_state (set, ATK_STATE_MANAGES_DESCENDANTS) &&
287
1595
              !atk_state_set_contains_state (set, ATK_STATE_DEFUNCT))
288
            {
289
1595
              append_children (current, cache->add_traversal);
290
            }
291
        }
292
      else
293
        {
294
          /* drop the ref for the removed object */
295
          g_object_unref (current);
296
        }
297

            
298
1595
      if (set)
299
1595
        g_object_unref (set);
300
    }
301

            
302
1756
  while (!g_queue_is_empty (to_add))
303
    {
304
1595
      current = g_queue_pop_head (to_add);
305

            
306
      /* Make sure object is registerd so we are notified if it goes away */
307
1595
      g_free (spi_register_object_to_path (spi_global_register,
308
1595
                                           G_OBJECT (current)));
309

            
310
1595
      add_object (cache, G_OBJECT (current));
311
1595
      g_object_unref (G_OBJECT (current));
312
    }
313

            
314
161
  g_queue_free (to_add);
315
161
  cache->add_pending_idle = 0;
316
161
  return FALSE;
317
}
318

            
319
/*---------------------------------------------------------------------------*/
320

            
321
static gboolean
322
child_added_listener (GSignalInvocationHint *signal_hint,
323
                      guint n_param_values,
324
                      const GValue *param_values,
325
                      gpointer data)
326
{
327
  SpiCache *cache = spi_global_cache;
328
  AtkObject *accessible;
329

            
330
  const gchar *detail = NULL;
331

            
332
  /*
333
   * Ensure that only accessibles already in the cache
334
   * have their signals processed.
335
   */
336
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
337
  g_return_val_if_fail (ATK_IS_OBJECT (accessible), TRUE);
338

            
339
  g_rec_mutex_lock (&cache_mutex);
340

            
341
  if (spi_cache_in (cache, G_OBJECT (accessible)))
342
    {
343
#ifdef SPI_ATK_DEBUG
344
      if (recursion_check_and_set ())
345
        g_warning ("AT-SPI: Recursive use of cache module");
346

            
347
      g_debug ("AT-SPI: Tree update listener");
348
#endif
349
      if (signal_hint->detail)
350
        detail = g_quark_to_string (signal_hint->detail);
351

            
352
      if (detail && !strncmp (detail, "add", 3))
353
        {
354
          gpointer child;
355
          child = g_value_get_pointer (param_values + 2);
356
          if (!child)
357
            {
358
              g_rec_mutex_unlock (&cache_mutex);
359
              return TRUE;
360
            }
361

            
362
          g_object_ref (child);
363
          g_queue_push_tail (cache->add_traversal, child);
364

            
365
          if (cache->add_pending_idle == 0)
366
            cache->add_pending_idle = spi_idle_add (add_pending_items, cache);
367
        }
368
#ifdef SPI_ATK_DEBUG
369
      recursion_check_unset ();
370
#endif
371
    }
372

            
373
  g_rec_mutex_unlock (&cache_mutex);
374

            
375
  return TRUE;
376
}
377

            
378
/*---------------------------------------------------------------------------*/
379

            
380
static void
381
toplevel_added_listener (AtkObject *accessible,
382
                         guint index,
383
                         AtkObject *child)
384
{
385
  SpiCache *cache = spi_global_cache;
386

            
387
  g_return_if_fail (ATK_IS_OBJECT (accessible));
388

            
389
  g_rec_mutex_lock (&cache_mutex);
390

            
391
  if (spi_cache_in (cache, G_OBJECT (accessible)))
392
    {
393
#ifdef SPI_ATK_DEBUG
394
      if (recursion_check_and_set ())
395
        g_warning ("AT-SPI: Recursive use of registration module");
396

            
397
      g_debug ("AT-SPI: Toplevel added listener");
398
#endif
399
      if (!ATK_IS_OBJECT (child))
400
        {
401
          child = atk_object_ref_accessible_child (accessible, index);
402
        }
403
      else
404
        g_object_ref (child);
405

            
406
      g_queue_push_tail (cache->add_traversal, child);
407

            
408
      if (cache->add_pending_idle == 0)
409
        cache->add_pending_idle = spi_idle_add (add_pending_items, cache);
410
#ifdef SPI_ATK_DEBUG
411
      recursion_check_unset ();
412
#endif
413
    }
414

            
415
  g_rec_mutex_unlock (&cache_mutex);
416
}
417

            
418
/*---------------------------------------------------------------------------*/
419

            
420
void
421
408
spi_cache_foreach (SpiCache *cache, GHFunc func, gpointer data)
422
{
423
408
  g_hash_table_foreach (cache->objects, func, data);
424
408
}
425

            
426
gboolean
427
6396
spi_cache_in (SpiCache *cache, GObject *object)
428
{
429
6396
  if (!cache)
430
161
    return FALSE;
431

            
432
6235
  if (g_hash_table_lookup_extended (cache->objects,
433
                                    object,
434
                                    NULL,
435
                                    NULL))
436
4630
    return TRUE;
437
  else
438
1605
    return FALSE;
439
}
440

            
441
#ifdef SPI_ATK_DEBUG
442
void
443
spi_cache_print_info (GObject *obj)
444
{
445
  char *path = spi_register_object_to_path (spi_global_register, obj);
446

            
447
  if (spi_cache_in (spi_global_cache, obj))
448
    g_printf ("%s IC\n", path);
449
  else
450
    g_printf ("%s NC\n", path);
451

            
452
  if (path)
453
    g_free (path);
454
}
455
#endif
456

            
457
/*END------------------------------------------------------------------------*/