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
cache_weak_ref (gpointer data, GObject *gobj)
138
{
139
  SpiCache *cache = SPI_CACHE (data);
140

            
141
  g_hash_table_remove (cache->objects, gobj);
142
}
143

            
144
static void
145
1595
spi_cache_remove_weak_ref (gpointer key, gpointer val, gpointer cache)
146
{
147
1595
  g_object_weak_unref (val, cache_weak_ref, cache);
148
1595
}
149

            
150
static void
151
161
spi_cache_finalize (GObject *object)
152
{
153
161
  SpiCache *cache = SPI_CACHE (object);
154

            
155
161
  while (!g_queue_is_empty (cache->add_traversal))
156
    g_object_unref (G_OBJECT (g_queue_pop_head (cache->add_traversal)));
157
161
  g_queue_free (cache->add_traversal);
158
161
  g_hash_table_foreach (cache->objects, spi_cache_remove_weak_ref, cache);
159
161
  g_hash_table_unref (cache->objects);
160

            
161
161
  g_signal_handlers_disconnect_by_func (spi_global_register,
162
                                        (GCallback) remove_object, cache);
163

            
164
161
  g_signal_handlers_disconnect_by_func (G_OBJECT (spi_global_app_data->root),
165
                                        (GCallback) toplevel_added_listener, NULL);
166

            
167
161
  atk_remove_global_event_listener (cache->child_added_listener);
168

            
169
161
  G_OBJECT_CLASS (spi_cache_parent_class)->finalize (object);
170
161
}
171

            
172
/*---------------------------------------------------------------------------*/
173

            
174
static void
175
remove_object (GObject *source, GObject *gobj, gpointer data)
176
{
177
  SpiCache *cache = SPI_CACHE (data);
178

            
179
  if (spi_cache_in (cache, gobj))
180
    {
181
#ifdef SPI_ATK_DEBUG
182
      g_debug ("CACHE REM - %s - %d - %s\n", atk_object_get_name (ATK_OBJECT (gobj)),
183
               atk_object_get_role (ATK_OBJECT (gobj)),
184
               spi_register_object_to_path (spi_global_register, gobj));
185
#endif
186
      g_signal_emit (cache, cache_signals[OBJECT_REMOVED], 0, gobj);
187
      g_object_weak_unref (G_OBJECT (gobj), cache_weak_ref, cache);
188
      g_hash_table_remove (cache->objects, gobj);
189
    }
190
  else if (g_queue_remove (cache->add_traversal, gobj))
191
    {
192
      g_object_unref (gobj);
193
    }
194
}
195

            
196
static void
197
1595
add_object (SpiCache *cache, GObject *gobj)
198
{
199
1595
  g_return_if_fail (G_IS_OBJECT (gobj));
200

            
201
1595
  g_hash_table_insert (cache->objects, gobj, NULL);
202
1595
  g_object_weak_ref (G_OBJECT (gobj), cache_weak_ref, cache);
203

            
204
#ifdef SPI_ATK_DEBUG
205
  g_debug ("CACHE ADD - %s - %d - %s\n", atk_object_get_name (ATK_OBJECT (gobj)),
206
           atk_object_get_role (ATK_OBJECT (gobj)),
207
           spi_register_object_to_path (spi_global_register, gobj));
208
#endif
209

            
210
1595
  g_signal_emit (cache, cache_signals[OBJECT_ADDED], 0, gobj);
211
}
212

            
213
/*---------------------------------------------------------------------------*/
214

            
215
static GRecMutex cache_mutex;
216

            
217
#ifdef SPI_ATK_DEBUG
218
static GStaticMutex recursion_check_guard = G_STATIC_MUTEX_INIT;
219
static gboolean recursion_check = FALSE;
220

            
221
static gboolean
222
recursion_check_and_set ()
223
{
224
  gboolean ret;
225
  g_static_mutex_lock (&recursion_check_guard);
226
  ret = recursion_check;
227
  recursion_check = TRUE;
228
  g_static_mutex_unlock (&recursion_check_guard);
229
  return ret;
230
}
231

            
232
static void
233
recursion_check_unset ()
234
{
235
  g_static_mutex_lock (&recursion_check_guard);
236
  recursion_check = FALSE;
237
  g_static_mutex_unlock (&recursion_check_guard);
238
}
239
#endif /* SPI_ATK_DEBUG */
240

            
241
/*---------------------------------------------------------------------------*/
242

            
243
static void
244
1595
append_children (AtkObject *accessible, GQueue *traversal)
245
{
246
  AtkObject *current;
247
  guint i;
248
1595
  gint count = atk_object_get_n_accessible_children (accessible);
249

            
250
1595
  if (count < 0)
251
    count = 0;
252
3029
  for (i = 0; i < count; i++)
253
    {
254
1434
      current = atk_object_ref_accessible_child (accessible, i);
255
1434
      if (current)
256
        {
257
1434
          g_queue_push_tail (traversal, current);
258
        }
259
    }
260
1595
}
261

            
262
/*
263
 * Adds a subtree of accessible objects
264
 * to the cache at the accessible object provided.
265
 *
266
 * The leaf nodes do not have their children
267
 * registered. A node is considered a leaf
268
 * if it has the state "manages-descendants"
269
 * or if it has already been registered.
270
 */
271
static void
272
161
add_subtree (SpiCache *cache, AtkObject *accessible)
273
{
274
161
  g_return_if_fail (ATK_IS_OBJECT (accessible));
275

            
276
161
  g_object_ref (accessible);
277
161
  g_queue_push_tail (cache->add_traversal, accessible);
278
161
  add_pending_items (cache);
279
}
280

            
281
static gboolean
282
161
add_pending_items (gpointer data)
283
{
284
161
  SpiCache *cache = SPI_CACHE (data);
285
  AtkObject *current;
286
  GQueue *to_add;
287

            
288
161
  to_add = g_queue_new ();
289

            
290
1917
  while (!g_queue_is_empty (cache->add_traversal))
291
    {
292
      AtkStateSet *set;
293

            
294
      /* cache->add_traversal holds a ref to current */
295
1595
      current = g_queue_pop_head (cache->add_traversal);
296
1595
      set = atk_object_ref_state_set (current);
297

            
298
1595
      if (set && !atk_state_set_contains_state (set, ATK_STATE_TRANSIENT))
299
        {
300
          /* transfer the ref into to_add */
301
1595
          g_queue_push_tail (to_add, current);
302
4785
          if (!spi_cache_in (cache, G_OBJECT (current)) &&
303
3190
              !atk_state_set_contains_state (set, ATK_STATE_MANAGES_DESCENDANTS) &&
304
1595
              !atk_state_set_contains_state (set, ATK_STATE_DEFUNCT))
305
            {
306
1595
              append_children (current, cache->add_traversal);
307
            }
308
        }
309
      else
310
        {
311
          /* drop the ref for the removed object */
312
          g_object_unref (current);
313
        }
314

            
315
1595
      if (set)
316
1595
        g_object_unref (set);
317
    }
318

            
319
1756
  while (!g_queue_is_empty (to_add))
320
    {
321
1595
      current = g_queue_pop_head (to_add);
322

            
323
1595
      add_object (cache, G_OBJECT (current));
324
1595
      g_object_unref (G_OBJECT (current));
325
    }
326

            
327
161
  g_queue_free (to_add);
328
161
  cache->add_pending_idle = 0;
329
161
  return FALSE;
330
}
331

            
332
/*---------------------------------------------------------------------------*/
333

            
334
static gboolean
335
child_added_listener (GSignalInvocationHint *signal_hint,
336
                      guint n_param_values,
337
                      const GValue *param_values,
338
                      gpointer data)
339
{
340
  SpiCache *cache = spi_global_cache;
341
  AtkObject *accessible;
342

            
343
  const gchar *detail = NULL;
344

            
345
  /*
346
   * Ensure that only accessibles already in the cache
347
   * have their signals processed.
348
   */
349
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
350
  g_return_val_if_fail (ATK_IS_OBJECT (accessible), TRUE);
351

            
352
  g_rec_mutex_lock (&cache_mutex);
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 cache module");
359

            
360
      g_debug ("AT-SPI: Tree update listener");
361
#endif
362
      if (signal_hint->detail)
363
        detail = g_quark_to_string (signal_hint->detail);
364

            
365
      if (detail && !strncmp (detail, "add", 3))
366
        {
367
          gpointer child;
368
          child = g_value_get_pointer (param_values + 2);
369
          if (!child)
370
            {
371
              g_rec_mutex_unlock (&cache_mutex);
372
              return TRUE;
373
            }
374

            
375
          g_object_ref (child);
376
          g_queue_push_tail (cache->add_traversal, child);
377

            
378
          if (cache->add_pending_idle == 0)
379
            cache->add_pending_idle = spi_idle_add (add_pending_items, cache);
380
        }
381
#ifdef SPI_ATK_DEBUG
382
      recursion_check_unset ();
383
#endif
384
    }
385

            
386
  g_rec_mutex_unlock (&cache_mutex);
387

            
388
  return TRUE;
389
}
390

            
391
/*---------------------------------------------------------------------------*/
392

            
393
static void
394
toplevel_added_listener (AtkObject *accessible,
395
                         guint index,
396
                         AtkObject *child)
397
{
398
  SpiCache *cache = spi_global_cache;
399

            
400
  g_return_if_fail (ATK_IS_OBJECT (accessible));
401

            
402
  g_rec_mutex_lock (&cache_mutex);
403

            
404
  if (spi_cache_in (cache, G_OBJECT (accessible)))
405
    {
406
#ifdef SPI_ATK_DEBUG
407
      if (recursion_check_and_set ())
408
        g_warning ("AT-SPI: Recursive use of registration module");
409

            
410
      g_debug ("AT-SPI: Toplevel added listener");
411
#endif
412
      if (!ATK_IS_OBJECT (child))
413
        {
414
          child = atk_object_ref_accessible_child (accessible, index);
415
        }
416
      else
417
        g_object_ref (child);
418

            
419
      g_queue_push_tail (cache->add_traversal, child);
420

            
421
      if (cache->add_pending_idle == 0)
422
        cache->add_pending_idle = spi_idle_add (add_pending_items, cache);
423
#ifdef SPI_ATK_DEBUG
424
      recursion_check_unset ();
425
#endif
426
    }
427

            
428
  g_rec_mutex_unlock (&cache_mutex);
429
}
430

            
431
/*---------------------------------------------------------------------------*/
432

            
433
void
434
393
spi_cache_foreach (SpiCache *cache, GHFunc func, gpointer data)
435
{
436
393
  g_hash_table_foreach (cache->objects, func, data);
437
393
}
438

            
439
gboolean
440
6294
spi_cache_in (SpiCache *cache, GObject *object)
441
{
442
6294
  if (!cache)
443
161
    return FALSE;
444

            
445
6133
  if (g_hash_table_lookup_extended (cache->objects,
446
                                    object,
447
                                    NULL,
448
                                    NULL))
449
4528
    return TRUE;
450
  else
451
1605
    return FALSE;
452
}
453

            
454
#ifdef SPI_ATK_DEBUG
455
void
456
spi_cache_print_info (GObject *obj)
457
{
458
  char *path = spi_register_object_to_path (spi_global_register, obj);
459

            
460
  if (spi_cache_in (spi_global_cache, obj))
461
    g_printf ("%s IC\n", path);
462
  else
463
    g_printf ("%s NC\n", path);
464

            
465
  if (path)
466
    g_free (path);
467
}
468
#endif
469

            
470
/*END------------------------------------------------------------------------*/