1
/*
2
 * AT-SPI - Assistive Technology Service Provider Interface
3
 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4
 *
5
 * Copyright 2007 IBM Corp.
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
/* collection.c: implements the Collection interface */
24

            
25
#include <string.h>
26
#include <strings.h>
27

            
28
#include "bridge.h"
29
#include <atk/atk.h>
30
#include <droute/droute.h>
31

            
32
#include "accessible-stateset.h"
33
#include "bitarray.h"
34
#include "spi-dbus.h"
35

            
36
#include "accessible-register.h"
37
#include "introspection.h"
38
#include "object.h"
39

            
40
#define MAX_CHILDREN 65536
41

            
42
typedef struct _MatchRulePrivate MatchRulePrivate;
43
struct _MatchRulePrivate
44
{
45
  gint *states;
46
  AtspiCollectionMatchType statematchtype;
47
  AtkAttributeSet *attributes;
48
  AtspiCollectionMatchType attributematchtype;
49
  gint *roles;
50
  AtspiCollectionMatchType rolematchtype;
51
  gchar **ifaces;
52
  AtspiCollectionMatchType interfacematchtype;
53
  gboolean invert;
54
};
55

            
56
static gboolean
57
15
child_interface_p (AtkObject *child, gchar *repo_id)
58
{
59
15
  if (!strncasecmp (repo_id, "action", 6))
60
    {
61
      AtkAction *iface;
62
      gint i, count;
63
      char *p;
64
      char name[64];
65
15
      if (!ATK_IS_ACTION (child))
66
9
        return FALSE;
67
6
      iface = ATK_ACTION (child);
68
6
      count = atk_action_get_n_actions (iface);
69
6
      if (count <= 0)
70
3
        return FALSE;
71
3
      if (repo_id[6] == '\0')
72
1
        return TRUE;
73
2
      p = strchr (repo_id, '(');
74
2
      if (!p)
75
        return FALSE;
76
2
      strncpy (name, p + 1, sizeof (name));
77
2
      name[sizeof (name) - 1] = '\0';
78
2
      p = strchr (name, ')');
79
2
      if (p)
80
2
        *p = '\0';
81
3
      for (i = 0; i < count; i++)
82
        {
83
2
          const char *action = atk_action_get_name (iface, i);
84
2
          if (!strcasecmp (name, action))
85
1
            return TRUE;
86
        }
87
1
      return FALSE;
88
    }
89
  if (!strcasecmp (repo_id, "component"))
90
    return ATK_IS_COMPONENT (child);
91
  if (!strcasecmp (repo_id, "editabletext"))
92
    return ATK_IS_EDITABLE_TEXT (child);
93
  if (!strcasecmp (repo_id, "text"))
94
    return ATK_IS_TEXT (child);
95
  if (!strcasecmp (repo_id, "hypertext"))
96
    return ATK_IS_HYPERTEXT (child);
97
  if (!strcasecmp (repo_id, "image"))
98
    return ATK_IS_IMAGE (child);
99
  if (!strcasecmp (repo_id, "selection"))
100
    return ATK_IS_SELECTION (child);
101
  if (!strcasecmp (repo_id, "table"))
102
    return ATK_IS_TABLE (child);
103
  if (!strcasecmp (repo_id, "value"))
104
    return ATK_IS_VALUE (child);
105
  if (!strcasecmp (repo_id, "streamablecontent"))
106
    return ATK_IS_STREAMABLE_CONTENT (child);
107
  if (!strcasecmp (repo_id, "document"))
108
    return ATK_IS_DOCUMENT (child);
109
  return FALSE;
110
}
111

            
112
#define child_collection_p(ch) (TRUE)
113

            
114
static gboolean
115
21
match_states_all_p (AtkObject *child, gint *set)
116
{
117
  AtkStateSet *chs;
118
  gint i;
119
21
  gboolean ret = TRUE;
120

            
121
21
  if (set == NULL || set[0] == BITARRAY_SEQ_TERM)
122
12
    return TRUE;
123

            
124
9
  chs = atk_object_ref_state_set (child);
125

            
126
  // TODO: use atk-state_set_contains_states; would be more efficient
127
35
  for (i = 0; set[i] != BITARRAY_SEQ_TERM; i++)
128
    {
129
29
      if (!atk_state_set_contains_state (chs, set[i]))
130
        {
131
3
          ret = FALSE;
132
3
          break;
133
        }
134
    }
135

            
136
9
  g_object_unref (chs);
137
9
  return ret;
138
}
139

            
140
static gboolean
141
match_states_any_p (AtkObject *child, gint *set)
142
{
143
  AtkStateSet *chs;
144
  gint i;
145
  gboolean ret = FALSE;
146

            
147
  if (set == NULL || set[0] == BITARRAY_SEQ_TERM)
148
    return TRUE;
149

            
150
  chs = atk_object_ref_state_set (child);
151

            
152
  for (i = 0; set[i] != BITARRAY_SEQ_TERM; i++)
153
    {
154
      if (atk_state_set_contains_state (chs, set[i]))
155
        {
156
          ret = TRUE;
157
          break;
158
        }
159
    }
160

            
161
  g_object_unref (chs);
162
  return ret;
163
}
164

            
165
static gboolean
166
match_states_none_p (AtkObject *child, gint *set)
167
{
168
  AtkStateSet *chs;
169
  gint i;
170
  gboolean ret = TRUE;
171

            
172
  if (set == NULL || set[0] == BITARRAY_SEQ_TERM)
173
    return TRUE;
174

            
175
  chs = atk_object_ref_state_set (child);
176

            
177
  for (i = 0; set[i] != BITARRAY_SEQ_TERM; i++)
178
    {
179
      if (atk_state_set_contains_state (chs, set[i]))
180
        {
181
          ret = FALSE;
182
          break;
183
        }
184
    }
185

            
186
  g_object_unref (chs);
187
  return ret;
188
}
189

            
190
// TODO: need to convert at-spi roles/states to atk roles/states */
191
static gboolean
192
21
match_states_lookup (AtkObject *child, MatchRulePrivate *mrp)
193
{
194
21
  switch (mrp->statematchtype)
195
    {
196
21
    case ATSPI_Collection_MATCH_ALL:
197
21
      if (match_states_all_p (child, mrp->states))
198
18
        return TRUE;
199
3
      break;
200

            
201
    case ATSPI_Collection_MATCH_ANY:
202
      if (match_states_any_p (child, mrp->states))
203
        return TRUE;
204
      break;
205

            
206
    case ATSPI_Collection_MATCH_NONE:
207
      if (match_states_none_p (child, mrp->states))
208
        return TRUE;
209
      break;
210

            
211
    default:
212
      break;
213
    }
214

            
215
3
  return FALSE;
216
}
217

            
218
// TODO: Map at-spi -> atk roles at mrp creation instead of doing this;
219
// would be more efficient
220
#define spi_get_role(obj) spi_accessible_role_from_atk_role (atk_object_get_role (obj))
221

            
222
static gboolean
223
13
match_roles_all_p (AtkObject *child, gint *roles)
224
{
225
13
  if (roles == NULL || roles[0] == BITARRAY_SEQ_TERM)
226
13
    return TRUE;
227
  else if (roles[1] != BITARRAY_SEQ_TERM)
228
    return FALSE;
229

            
230
  return (spi_get_role (child) == roles[0]);
231
}
232

            
233
static gboolean
234
match_roles_any_p (AtkObject *child, gint *roles)
235
{
236
  AtspiRole role;
237
  int i;
238

            
239
  if (roles == NULL || roles[0] == BITARRAY_SEQ_TERM)
240
    return TRUE;
241

            
242
  role = spi_accessible_role_from_atk_role (atk_object_get_role (child));
243

            
244
  for (i = 0; roles[i] != BITARRAY_SEQ_TERM; i++)
245
    if (role == roles[i])
246
      return TRUE;
247

            
248
  return FALSE;
249
}
250

            
251
static gboolean
252
5
match_roles_none_p (AtkObject *child, gint *roles)
253
{
254
  AtspiRole role;
255
  int i;
256

            
257
5
  if (roles == NULL || roles[0] == BITARRAY_SEQ_TERM)
258
    return TRUE;
259

            
260
5
  role = spi_get_role (child);
261

            
262
9
  for (i = 0; roles[i] != BITARRAY_SEQ_TERM; i++)
263
5
    if (role == roles[i])
264
1
      return FALSE;
265

            
266
4
  return TRUE;
267
}
268

            
269
static gboolean
270
18
match_roles_lookup (AtkObject *child, MatchRulePrivate *mrp)
271
{
272
18
  switch (mrp->rolematchtype)
273
    {
274
13
    case ATSPI_Collection_MATCH_ALL:
275
13
      if (match_roles_all_p (child, mrp->roles))
276
13
        return TRUE;
277
      break;
278

            
279
    case ATSPI_Collection_MATCH_ANY:
280
      if (match_roles_any_p (child, mrp->roles))
281
        return TRUE;
282
      break;
283

            
284
5
    case ATSPI_Collection_MATCH_NONE:
285
5
      if (match_roles_none_p (child, mrp->roles))
286
4
        return TRUE;
287
1
      break;
288

            
289
    default:
290
      break;
291
    }
292
1
  return FALSE;
293
}
294

            
295
static gboolean
296
34
match_interfaces_all_p (AtkObject *obj, gchar **ifaces)
297
{
298
  gint i;
299

            
300
34
  if (ifaces == NULL)
301
    return TRUE;
302

            
303
36
  for (i = 0; ifaces[i]; i++)
304
15
    if (!child_interface_p (obj, ifaces[i]))
305
      {
306
13
        return FALSE;
307
      }
308
21
  return TRUE;
309
}
310

            
311
static gboolean
312
match_interfaces_any_p (AtkObject *obj, gchar **ifaces)
313
{
314
  gint i;
315

            
316
  if (ifaces == NULL)
317
    return TRUE;
318

            
319
  for (i = 0; ifaces[i]; i++)
320
    if (child_interface_p (obj, ifaces[i]))
321
      {
322
        return TRUE;
323
      }
324
  return FALSE;
325
}
326

            
327
static gboolean
328
match_interfaces_none_p (AtkObject *obj, gchar **ifaces)
329
{
330
  gint i;
331

            
332
  for (i = 0; ifaces[i]; i++)
333
    if (child_interface_p (obj, ifaces[i]))
334
      return FALSE;
335

            
336
  return TRUE;
337
}
338

            
339
static gboolean
340
34
match_interfaces_lookup (AtkObject *child, MatchRulePrivate *mrp)
341
{
342
34
  switch (mrp->interfacematchtype)
343
    {
344

            
345
34
    case ATSPI_Collection_MATCH_ALL:
346
34
      if (match_interfaces_all_p (child, mrp->ifaces))
347
21
        return TRUE;
348
13
      break;
349

            
350
    case ATSPI_Collection_MATCH_ANY:
351
      if (match_interfaces_any_p (child, mrp->ifaces))
352
        return TRUE;
353
      break;
354

            
355
    case ATSPI_Collection_MATCH_NONE:
356
      if (match_interfaces_none_p (child, mrp->ifaces))
357
        return TRUE;
358
      break;
359

            
360
    default:
361
      break;
362
    }
363

            
364
13
  return FALSE;
365
}
366

            
367
#define split_attributes(attributes) (g_strsplit (attributes, ";", 0))
368

            
369
static gboolean
370
12
match_attributes_all_p (AtkObject *child, AtkAttributeSet *attributes)
371
{
372
  int i, k;
373
  AtkAttributeSet *oa;
374
  gint length, oa_length;
375
12
  gboolean flag = FALSE;
376

            
377
12
  if (attributes == NULL || g_slist_length (attributes) == 0)
378
12
    return TRUE;
379

            
380
  oa = atk_object_get_attributes (child);
381
  length = g_slist_length (attributes);
382
  oa_length = g_slist_length (oa);
383

            
384
  for (i = 0; i < length; i++)
385
    {
386
      AtkAttribute *attr = g_slist_nth_data (attributes, i);
387
      for (k = 0; k < oa_length; k++)
388
        {
389
          AtkAttribute *oa_attr = g_slist_nth_data (oa, k);
390
          if (!g_ascii_strcasecmp (oa_attr->name, attr->name) &&
391
              !g_ascii_strcasecmp (oa_attr->value, attr->value))
392
            {
393
              flag = TRUE;
394
              break;
395
            }
396
          else
397
            flag = FALSE;
398
        }
399
      if (!flag)
400
        {
401
          atk_attribute_set_free (oa);
402
          return FALSE;
403
        }
404
    }
405
  atk_attribute_set_free (oa);
406
  return TRUE;
407
}
408

            
409
static gboolean
410
match_attributes_any_p (AtkObject *child, AtkAttributeSet *attributes)
411
{
412
  int i, k;
413

            
414
  AtkAttributeSet *oa;
415
  gint length, oa_length;
416

            
417
  length = g_slist_length (attributes);
418
  if (length == 0)
419
    return TRUE;
420

            
421
  oa = atk_object_get_attributes (child);
422
  oa_length = g_slist_length (oa);
423

            
424
  for (i = 0; i < length; i++)
425
    {
426
      AtkAttribute *attr = g_slist_nth_data (attributes, i);
427
      for (k = 0; k < oa_length; k++)
428
        {
429
          AtkAttribute *oa_attr = g_slist_nth_data (oa, k);
430
          if (!g_ascii_strcasecmp (oa_attr->name, attr->name) &&
431
              !g_ascii_strcasecmp (oa_attr->value, attr->value))
432
            {
433
              atk_attribute_set_free (oa);
434
              return TRUE;
435
            }
436
        }
437
    }
438
  atk_attribute_set_free (oa);
439
  return FALSE;
440
}
441

            
442
static gboolean
443
5
match_attributes_none_p (AtkObject *child, AtkAttributeSet *attributes)
444
{
445
  int i, k;
446

            
447
  AtkAttributeSet *oa;
448
  gint length, oa_length;
449

            
450
5
  length = g_slist_length (attributes);
451
5
  if (length == 0)
452
    return TRUE;
453

            
454
5
  oa = atk_object_get_attributes (child);
455
5
  oa_length = g_slist_length (oa);
456

            
457
10
  for (i = 0; i < length; i++)
458
    {
459
5
      AtkAttribute *attr = g_slist_nth_data (attributes, i);
460
15
      for (k = 0; k < oa_length; k++)
461
        {
462
10
          AtkAttribute *oa_attr = g_slist_nth_data (oa, k);
463
10
          if (!g_ascii_strcasecmp (oa_attr->name, attr->name) &&
464
              !g_ascii_strcasecmp (oa_attr->value, attr->value))
465
            {
466
              atk_attribute_set_free (oa);
467
              return FALSE;
468
            }
469
        }
470
    }
471
5
  atk_attribute_set_free (oa);
472
5
  return TRUE;
473
}
474

            
475
static gboolean
476
17
match_attributes_lookup (AtkObject *child, MatchRulePrivate *mrp)
477
{
478
17
  switch (mrp->attributematchtype)
479
    {
480

            
481
12
    case ATSPI_Collection_MATCH_ALL:
482
12
      if (match_attributes_all_p (child, mrp->attributes))
483
12
        return TRUE;
484
      break;
485

            
486
    case ATSPI_Collection_MATCH_ANY:
487
      if (match_attributes_any_p (child, mrp->attributes))
488
        return TRUE;
489
      break;
490

            
491
5
    case ATSPI_Collection_MATCH_NONE:
492
5
      if (match_attributes_none_p (child, mrp->attributes))
493
5
        return TRUE;
494
      break;
495

            
496
    default:
497
      break;
498
    }
499
  return FALSE;
500
}
501

            
502
static gboolean
503
33
traverse_p (AtkObject *child, const gboolean traverse)
504
{
505
33
  if (traverse)
506
30
    return TRUE;
507
  else
508
3
    return !child_collection_p (child);
509
}
510

            
511
static int
512
43
sort_order_canonical (MatchRulePrivate *mrp, GList *ls, gint kount, gint max, AtkObject *obj, glong index, gboolean flag, AtkObject *pobj, gboolean recurse, gboolean traverse)
513
{
514
43
  gint i = index;
515
43
  glong acount = atk_object_get_n_accessible_children (obj);
516
43
  gboolean prev = pobj ? TRUE : FALSE;
517

            
518
43
  if (acount > MAX_CHILDREN)
519
    acount = MAX_CHILDREN;
520
76
  for (; i < acount && (max == 0 || kount < max); i++)
521
    {
522
33
      AtkObject *child = atk_object_ref_accessible_child (obj, i);
523

            
524
33
      if (!child)
525
        continue;
526

            
527
33
      if (prev && child == pobj)
528
        {
529
          g_object_unref (child);
530
          return kount;
531
        }
532

            
533
33
      if (flag && match_interfaces_lookup (child, mrp) && match_states_lookup (child, mrp) && match_roles_lookup (child, mrp) && match_attributes_lookup (child, mrp))
534
        {
535

            
536
16
          ls = g_list_append (ls, child);
537
16
          kount++;
538
        }
539

            
540
33
      if (!flag)
541
        flag = TRUE;
542

            
543
33
      if (recurse && traverse_p (child, traverse))
544
30
        kount = sort_order_canonical (mrp, ls, kount,
545
                                      max, child, 0, TRUE,
546
                                      pobj, recurse, traverse);
547
33
      g_object_unref (child);
548
    }
549
43
  return kount;
550
}
551

            
552
static int
553
3
sort_order_rev_canonical (MatchRulePrivate *mrp, GList *ls, gint kount, gint max, AtkObject *obj, gboolean flag, AtkObject *pobj)
554
{
555
  AtkObject *nextobj;
556
  AtkObject *parent;
557
  glong indexinparent;
558

            
559
  /* This breaks us out of the recursion. */
560
3
  if (!obj || obj == pobj)
561
    {
562
1
      return kount;
563
    }
564

            
565
  /* Add to the list if it matches */
566
2
  if (flag && match_interfaces_lookup (obj, mrp) && match_states_lookup (obj, mrp) && match_roles_lookup (obj, mrp) && match_attributes_lookup (obj, mrp) && (max == 0 || kount < max))
567
    {
568
1
      ls = g_list_append (ls, obj);
569
1
      kount++;
570
    }
571

            
572
  /* Get the current nodes index in it's parent and the parent object. */
573
2
  indexinparent = atk_object_get_index_in_parent (obj);
574
2
  parent = atk_object_get_parent (obj);
575

            
576
2
  if (indexinparent > 0 && (max == 0 || kount < max))
577
    {
578
      /* there are still some siblings to visit so get the previous sibling
579
         and get it's last descendant.
580
         First, get the previous sibling */
581
1
      nextobj = atk_object_ref_accessible_child (parent, indexinparent - 1);
582

            
583
      /* Now, drill down the right side to the last descendant */
584
1
      while (nextobj && atk_object_get_n_accessible_children (nextobj) > 0)
585
        {
586
          AtkObject *follow;
587
          gint count = atk_object_get_n_accessible_children (nextobj);
588
          if (count > MAX_CHILDREN)
589
            count = MAX_CHILDREN;
590

            
591
          follow = atk_object_ref_accessible_child (nextobj, count - 1);
592
          g_object_unref (nextobj);
593
          nextobj = follow;
594
        }
595
      /* recurse with the last descendant */
596
1
      kount = sort_order_rev_canonical (mrp, ls, kount, max,
597
                                        nextobj, TRUE, pobj);
598
1
      if (nextobj)
599
1
        g_object_unref (nextobj);
600
    }
601
1
  else if (max == 0 || kount < max)
602
    {
603
      /* no more siblings so next node must be the parent */
604
1
      kount = sort_order_rev_canonical (mrp, ls, kount, max,
605
                                        parent, TRUE, pobj);
606
    }
607
2
  return kount;
608
}
609

            
610
static int
611
1
query_exec (MatchRulePrivate *mrp, AtspiCollectionSortOrder sortby, GList *ls, gint kount, gint max, AtkObject *obj, glong index, gboolean flag, AtkObject *pobj, gboolean recurse, gboolean traverse)
612
{
613
1
  switch (sortby)
614
    {
615
1
    case ATSPI_Collection_SORT_ORDER_CANONICAL:
616
1
      kount = sort_order_canonical (mrp, ls, 0, max, obj, index, flag,
617
                                    pobj, recurse, traverse);
618
1
      break;
619
    case ATSPI_Collection_SORT_ORDER_REVERSE_CANONICAL:
620
      kount = sort_order_canonical (mrp, ls, 0, max, obj, index, flag,
621
                                    pobj, recurse, traverse);
622
      break;
623
    default:
624
      kount = 0;
625
      g_warning ("Sort method not implemented yet");
626
      break;
627
    }
628

            
629
1
  return kount;
630
}
631

            
632
static gboolean
633
16
bitarray_to_seq (dbus_uint32_t *array, int array_count, int **ret)
634
{
635
16
  int out_size = 4;
636
16
  int out_count = 0;
637
  int i, j;
638
16
  int *out = (int *) g_malloc (out_size * sizeof (int));
639

            
640
64
  for (i = 0; i < array_count; i++)
641
    {
642
1584
      for (j = 0; j < 32; j++)
643
        {
644
1536
          if (array[i] & (1 << j))
645
            {
646
15
              if (out_count == out_size - 2)
647
                {
648
3
                  out_size <<= 1;
649
3
                  out = (int *) g_realloc (out, out_size * sizeof (int));
650
                }
651
15
              out[out_count++] = i * 32 + j;
652
            }
653
        }
654
    }
655
16
  out[out_count] = BITARRAY_SEQ_TERM;
656
16
  *ret = out;
657
16
  return TRUE;
658
}
659

            
660
static dbus_bool_t
661
8
read_mr (DBusMessageIter *iter, MatchRulePrivate *mrp)
662
{
663
  DBusMessageIter iter_struct, iter_array, iter_dict, iter_dict_entry;
664
  dbus_uint32_t *array;
665
  dbus_int32_t matchType;
666
  int array_count;
667
  AtkAttribute *attr;
668
  int i;
669

            
670
8
  dbus_message_iter_recurse (iter, &iter_struct);
671

            
672
  /* states */
673
8
  dbus_message_iter_recurse (&iter_struct, &iter_array);
674
8
  dbus_message_iter_get_fixed_array (&iter_array, &array, &array_count);
675
8
  bitarray_to_seq (array, array_count, &mrp->states);
676
22
  for (i = 0; mrp->states[i] != BITARRAY_SEQ_TERM; i++)
677
    {
678
14
      mrp->states[i] = spi_atk_state_from_spi_state (mrp->states[i]);
679
    }
680
8
  dbus_message_iter_next (&iter_struct);
681
8
  dbus_message_iter_get_basic (&iter_struct, &matchType);
682
8
  dbus_message_iter_next (&iter_struct);
683
8
  mrp->statematchtype = matchType;
684
  ;
685

            
686
  /* attributes */
687
8
  mrp->attributes = NULL;
688
8
  dbus_message_iter_recurse (&iter_struct, &iter_dict);
689
9
  while (dbus_message_iter_get_arg_type (&iter_dict) != DBUS_TYPE_INVALID)
690
    {
691
      const char *key, *val;
692
      const char *p, *q;
693
1
      dbus_message_iter_recurse (&iter_dict, &iter_dict_entry);
694
1
      dbus_message_iter_get_basic (&iter_dict_entry, &key);
695
1
      dbus_message_iter_next (&iter_dict_entry);
696
1
      dbus_message_iter_get_basic (&iter_dict_entry, &val);
697
1
      p = q = val;
698
      for (;;)
699
        {
700
5
          if (*q == '\0' || (*q == ':' && (q == val || q[-1] != '\\')))
701
            {
702
              char *tmp;
703
1
              attr = g_new (AtkAttribute, 1);
704
2
              attr->name = g_strdup (key);
705
1
              attr->value = g_strdup (p);
706
1
              attr->value[q - p] = '\0';
707
1
              tmp = attr->value;
708
5
              while (*tmp != '\0')
709
                {
710
4
                  if (*tmp == '\\')
711
                    memmove (tmp, tmp + 1, strlen (tmp));
712
                  else
713
4
                    tmp++;
714
                }
715
1
              mrp->attributes = g_slist_prepend (mrp->attributes, attr);
716
1
              if (*q == '\0')
717
1
                break;
718
              else
719
                p = ++q;
720
            }
721
          else
722
4
            q++;
723
        }
724
1
      dbus_message_iter_next (&iter_dict);
725
    }
726
8
  dbus_message_iter_next (&iter_struct);
727
8
  dbus_message_iter_get_basic (&iter_struct, &matchType);
728
8
  mrp->attributematchtype = matchType;
729
  ;
730
8
  dbus_message_iter_next (&iter_struct);
731

            
732
  /* Get roles and role match */
733
8
  dbus_message_iter_recurse (&iter_struct, &iter_array);
734
8
  dbus_message_iter_get_fixed_array (&iter_array, &array, &array_count);
735
8
  bitarray_to_seq (array, array_count, &mrp->roles);
736
8
  dbus_message_iter_next (&iter_struct);
737
8
  dbus_message_iter_get_basic (&iter_struct, &matchType);
738
8
  mrp->rolematchtype = matchType;
739
  ;
740
8
  dbus_message_iter_next (&iter_struct);
741

            
742
  /* Get interfaces and interface match */
743
8
  dbus_message_iter_recurse (&iter_struct, &iter_array);
744
8
  mrp->ifaces = g_new0 (gchar *, 16);
745
8
  i = 0;
746
11
  while (i < 15 && dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
747
    {
748
      char *iface;
749
3
      dbus_message_iter_get_basic (&iter_array, &iface);
750
3
      mrp->ifaces[i] = g_strdup (iface);
751
3
      i++;
752
3
      dbus_message_iter_next (&iter_array);
753
    }
754
8
  dbus_message_iter_next (&iter_struct);
755
8
  dbus_message_iter_get_basic (&iter_struct, &matchType);
756
8
  mrp->interfacematchtype = matchType;
757
  ;
758
8
  dbus_message_iter_next (&iter_struct);
759
  /* get invert */
760
8
  dbus_message_iter_get_basic (&iter_struct, &mrp->invert);
761
8
  dbus_message_iter_next (iter);
762
8
  return TRUE;
763
}
764

            
765
static DBusMessage *
766
8
return_and_free_list (DBusMessage *message, GList *ls)
767
{
768
  DBusMessage *reply;
769
  DBusMessageIter iter, iter_array;
770
  GList *item;
771

            
772
8
  reply = dbus_message_new_method_return (message);
773
8
  if (!reply)
774
    return NULL;
775
8
  dbus_message_iter_init_append (reply, &iter);
776
8
  if (!dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "(so)", &iter_array))
777
    goto oom;
778
25
  for (item = ls; item; item = g_list_next (item))
779
    {
780
17
      spi_object_append_reference (&iter_array, ATK_OBJECT (item->data));
781
    }
782
8
  if (!dbus_message_iter_close_container (&iter, &iter_array))
783
    goto oom;
784
8
  g_list_free (ls);
785
8
  return reply;
786
oom:
787
  // TODO: Handle out of memory
788
  g_list_free (ls);
789
  return reply;
790
}
791

            
792
static void
793
8
free_mrp_data (MatchRulePrivate *mrp)
794
{
795
8
  g_free (mrp->states);
796
8
  atk_attribute_set_free (mrp->attributes);
797
8
  g_free (mrp->roles);
798
8
  g_strfreev (mrp->ifaces);
799
8
}
800

            
801
static DBusMessage *
802
GetMatchesFrom (DBusMessage *message,
803
                AtkObject *current_object,
804
                MatchRulePrivate *mrp,
805
                const AtspiCollectionSortOrder sortby,
806
                const dbus_bool_t isrestrict,
807
                dbus_int32_t count,
808
                const dbus_bool_t traverse)
809
{
810
  GList *ls = NULL;
811
  AtkObject *parent;
812
  glong index = atk_object_get_index_in_parent (current_object);
813

            
814
  ls = g_list_append (ls, current_object);
815

            
816
  if (!isrestrict)
817
    {
818
      parent = atk_object_get_parent (current_object);
819
      query_exec (mrp, sortby, ls, 0, count, parent, index,
820
                  FALSE, NULL, TRUE, traverse);
821
    }
822
  else
823
    query_exec (mrp, sortby, ls, 0, count,
824
                current_object, 0, FALSE, NULL, TRUE, traverse);
825

            
826
  ls = g_list_remove (ls, ls->data);
827

            
828
  if (sortby == ATSPI_Collection_SORT_ORDER_REVERSE_CANONICAL)
829
    ls = g_list_reverse (ls);
830

            
831
  free_mrp_data (mrp);
832
  return return_and_free_list (message, ls);
833
}
834

            
835
/*
836
  inorder traversal from a given object in the hierarchy
837
*/
838

            
839
static int
840
6
inorder (AtkObject *collection, MatchRulePrivate *mrp, GList *ls, gint kount, gint max, AtkObject *obj, gboolean flag, AtkObject *pobj, dbus_bool_t traverse)
841
{
842
6
  int i = 0;
843

            
844
  /* First, look through the children recursively. */
845
6
  kount = sort_order_canonical (mrp, ls, kount, max, obj, 0, TRUE,
846
                                NULL, TRUE, TRUE);
847

            
848
  /* Next, we look through the right subtree */
849
12
  while ((max == 0 || kount < max) && obj && obj != collection)
850
    {
851
6
      AtkObject *parent = atk_object_get_parent (obj);
852
6
      i = atk_object_get_index_in_parent (obj);
853
6
      kount = sort_order_canonical (mrp, ls, kount, max, parent,
854
6
                                    i + 1, TRUE, FALSE, TRUE, TRUE);
855
6
      obj = parent;
856
    }
857

            
858
6
  return kount;
859
}
860

            
861
/*
862
  GetMatchesInOrder: get matches from a given object in an inorder traversal.
863
*/
864

            
865
static DBusMessage *
866
6
GetMatchesInOrder (DBusMessage *message,
867
                   AtkObject *current_object,
868
                   MatchRulePrivate *mrp,
869
                   const AtspiCollectionSortOrder sortby,
870
                   const dbus_bool_t recurse,
871
                   dbus_int32_t count,
872
                   const dbus_bool_t traverse)
873
{
874
6
  GList *ls = NULL;
875
  AtkObject *obj;
876

            
877
6
  ls = g_list_append (ls, current_object);
878

            
879
6
  obj = ATK_OBJECT (spi_register_path_to_object (spi_global_register, dbus_message_get_path (message)));
880

            
881
6
  inorder (obj, mrp, ls, 0, count,
882
           current_object, TRUE, NULL, traverse);
883

            
884
6
  ls = g_list_remove (ls, ls->data);
885

            
886
6
  if (sortby == ATSPI_Collection_SORT_ORDER_REVERSE_CANONICAL)
887
    ls = g_list_reverse (ls);
888

            
889
6
  free_mrp_data (mrp);
890
6
  return return_and_free_list (message, ls);
891
}
892

            
893
/*
894
  GetMatchesInBackOrder: get matches from a given object in a
895
  reverse order traversal.
896
*/
897

            
898
static DBusMessage *
899
1
GetMatchesInBackOrder (DBusMessage *message,
900
                       AtkObject *current_object,
901
                       MatchRulePrivate *mrp,
902
                       const AtspiCollectionSortOrder sortby,
903
                       dbus_int32_t count)
904
{
905
1
  GList *ls = NULL;
906
  AtkObject *collection;
907

            
908
1
  ls = g_list_append (ls, current_object);
909

            
910
1
  collection = ATK_OBJECT (spi_register_path_to_object (spi_global_register, dbus_message_get_path (message)));
911

            
912
1
  sort_order_rev_canonical (mrp, ls, 0, count, current_object,
913
                            FALSE, collection);
914

            
915
1
  ls = g_list_remove (ls, ls->data);
916

            
917
1
  if (sortby == ATSPI_Collection_SORT_ORDER_REVERSE_CANONICAL)
918
    ls = g_list_reverse (ls);
919

            
920
1
  free_mrp_data (mrp);
921
1
  return return_and_free_list (message, ls);
922
}
923

            
924
static DBusMessage *
925
GetMatchesTo (DBusMessage *message,
926
              AtkObject *current_object,
927
              MatchRulePrivate *mrp,
928
              const AtspiCollectionSortOrder sortby,
929
              const dbus_bool_t recurse,
930
              const dbus_bool_t isrestrict,
931
              dbus_int32_t count,
932
              const dbus_bool_t traverse)
933
{
934
  GList *ls = NULL;
935
  AtkObject *obj;
936
  ls = g_list_append (ls, current_object);
937

            
938
  if (recurse)
939
    {
940
      obj = ATK_OBJECT (atk_object_get_parent (current_object));
941
      query_exec (mrp, sortby, ls, 0, count,
942
                  obj, 0, TRUE, current_object, TRUE, traverse);
943
    }
944
  else
945
    {
946
      obj = ATK_OBJECT (spi_register_path_to_object (spi_global_register, dbus_message_get_path (message)));
947
      query_exec (mrp, sortby, ls, 0, count,
948
                  obj, 0, TRUE, current_object, TRUE, traverse);
949
    }
950

            
951
  ls = g_list_remove (ls, ls->data);
952

            
953
  if (sortby != ATSPI_Collection_SORT_ORDER_REVERSE_CANONICAL)
954
    ls = g_list_reverse (ls);
955

            
956
  free_mrp_data (mrp);
957
  return return_and_free_list (message, ls);
958
}
959

            
960
static DBusMessage *
961
6
impl_GetMatchesFrom (DBusConnection *bus, DBusMessage *message, void *user_data)
962
{
963
6
  char *current_object_path = NULL;
964
  AtkObject *current_object;
965
  DBusMessageIter iter;
966
  MatchRulePrivate rule;
967
  dbus_uint32_t sortby;
968
  dbus_uint32_t tree;
969
  dbus_int32_t count;
970
  dbus_bool_t traverse;
971
  const char *signature;
972

            
973
6
  signature = dbus_message_get_signature (message);
974
6
  if (strcmp (signature, "o(aiia{ss}iaiiasib)uuib") != 0)
975
    {
976
      return droute_invalid_arguments_error (message);
977
    }
978

            
979
6
  dbus_message_iter_init (message, &iter);
980
6
  dbus_message_iter_get_basic (&iter, &current_object_path);
981
6
  current_object = ATK_OBJECT (spi_register_path_to_object (spi_global_register, current_object_path));
982
6
  if (!current_object)
983
    {
984
      // TODO: object-not-found error
985
      return spi_dbus_general_error (message);
986
    }
987
6
  dbus_message_iter_next (&iter);
988
6
  if (!read_mr (&iter, &rule))
989
    {
990
      return spi_dbus_general_error (message);
991
    }
992
6
  dbus_message_iter_get_basic (&iter, &sortby);
993
6
  dbus_message_iter_next (&iter);
994
6
  dbus_message_iter_get_basic (&iter, &tree);
995
6
  dbus_message_iter_next (&iter);
996
6
  dbus_message_iter_get_basic (&iter, &count);
997
6
  dbus_message_iter_next (&iter);
998
6
  dbus_message_iter_get_basic (&iter, &traverse);
999
6
  dbus_message_iter_next (&iter);
6
  switch (tree)
    {
    case ATSPI_Collection_TREE_RESTRICT_CHILDREN:
      return GetMatchesFrom (message, current_object,
                             &rule, sortby, TRUE, count, traverse);
      break;
    case ATSPI_Collection_TREE_RESTRICT_SIBLING:
      return GetMatchesFrom (message, current_object,
                             &rule, sortby, FALSE, count, traverse);
      break;
6
    case ATSPI_Collection_TREE_INORDER:
6
      return GetMatchesInOrder (message, current_object,
                                &rule, sortby, TRUE, count, traverse);
      break;
    default:
      return NULL;
    }
}
static DBusMessage *
1
impl_GetMatchesTo (DBusConnection *bus, DBusMessage *message, void *user_data)
{
1
  char *current_object_path = NULL;
  AtkObject *current_object;
  DBusMessageIter iter;
  MatchRulePrivate rule;
  dbus_uint32_t sortby;
  dbus_uint32_t tree;
  dbus_bool_t recurse;
  dbus_int32_t count;
  dbus_bool_t traverse;
  const char *signature;
1
  signature = dbus_message_get_signature (message);
1
  if (strcmp (signature, "o(aiia{ss}iaiiasib)uubib") != 0)
    {
      return droute_invalid_arguments_error (message);
    }
1
  dbus_message_iter_init (message, &iter);
1
  dbus_message_iter_get_basic (&iter, &current_object_path);
1
  current_object = ATK_OBJECT (spi_register_path_to_object (spi_global_register, current_object_path));
1
  if (!current_object)
    {
      // TODO: object-not-found error
      return spi_dbus_general_error (message);
    }
1
  dbus_message_iter_next (&iter);
1
  if (!read_mr (&iter, &rule))
    {
      return spi_dbus_general_error (message);
    }
1
  dbus_message_iter_get_basic (&iter, &sortby);
1
  dbus_message_iter_next (&iter);
1
  dbus_message_iter_get_basic (&iter, &tree);
1
  dbus_message_iter_next (&iter);
1
  dbus_message_iter_get_basic (&iter, &recurse);
1
  dbus_message_iter_next (&iter);
1
  dbus_message_iter_get_basic (&iter, &count);
1
  dbus_message_iter_next (&iter);
1
  dbus_message_iter_get_basic (&iter, &traverse);
1
  dbus_message_iter_next (&iter);
1
  switch (tree)
    {
    case ATSPI_Collection_TREE_RESTRICT_CHILDREN:
      return GetMatchesTo (message, current_object,
                           &rule, sortby, recurse, TRUE, count, traverse);
      break;
    case ATSPI_Collection_TREE_RESTRICT_SIBLING:
      return GetMatchesTo (message, current_object,
                           &rule, sortby, recurse, FALSE, count, traverse);
      break;
1
    case ATSPI_Collection_TREE_INORDER:
1
      return GetMatchesInBackOrder (message, current_object,
                                    &rule, sortby, count);
      break;
    default:
      return NULL;
    }
}
static void
append_accessible_properties (DBusMessageIter *iter, AtkObject *obj, GArray *properties)
{
  DBusMessageIter iter_struct, iter_dict, iter_dict_entry;
  AtkStateSet *set;
  gint i;
  gint count;
  dbus_message_iter_open_container (iter, DBUS_TYPE_STRUCT, NULL, &iter_struct);
  spi_object_append_reference (&iter_struct, obj);
  dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "{sv}", &iter_dict);
  if (properties && properties->len)
    {
      gint i;
      for (i = 0; i < properties->len; i++)
        {
          gchar *prop = g_array_index (properties, char *, i);
          DRoutePropertyFunction func;
          GType type;
          func = _atk_bridge_find_property_func (prop, &type);
          if (func && G_TYPE_CHECK_INSTANCE_TYPE (obj, type))
            {
              dbus_message_iter_open_container (&iter_dict, DBUS_TYPE_DICT_ENTRY,
                                                NULL, &iter_dict_entry);
              dbus_message_iter_append_basic (&iter_dict_entry, DBUS_TYPE_STRING, &prop);
              func (&iter_dict_entry, obj);
              dbus_message_iter_close_container (&iter_dict, &iter_dict_entry);
            }
        }
    }
  else
    {
      GHashTableIter hi;
      gpointer key, value;
      g_hash_table_iter_init (&hi, spi_global_app_data->property_hash);
      while (g_hash_table_iter_next (&hi, &key, &value))
        {
          const DRouteProperty *prop = value;
          GType type = _atk_bridge_type_from_iface (key);
          if (!G_TYPE_CHECK_INSTANCE_TYPE (obj, type))
            continue;
          for (; prop->name; prop++)
            {
              const char *p = key + strlen (key);
              gchar *property_name;
              while (p[-1] != '.')
                p--;
              if (!strcmp (p, "Accessible"))
                property_name = g_strdup (prop->name);
              else
                property_name = g_strconcat (p, ".", prop->name, NULL);
              dbus_message_iter_open_container (&iter_dict, DBUS_TYPE_DICT_ENTRY,
                                                NULL, &iter_dict_entry);
              dbus_message_iter_append_basic (&iter_dict_entry, DBUS_TYPE_STRING, &property_name);
              g_free (property_name);
              prop->get (&iter_dict_entry, obj);
              dbus_message_iter_close_container (&iter_dict, &iter_dict_entry);
            }
        }
    }
  dbus_message_iter_close_container (&iter_struct, &iter_dict);
  dbus_message_iter_close_container (iter, &iter_struct);
  set = atk_object_ref_state_set (obj);
  if (set)
    {
      gboolean md = atk_state_set_contains_state (set, ATK_STATE_MANAGES_DESCENDANTS);
      g_object_unref (set);
      if (md)
        return;
    }
  count = atk_object_get_n_accessible_children (obj);
  if (count > MAX_CHILDREN)
    count = MAX_CHILDREN;
  for (i = 0; i < count; i++)
    {
      AtkObject *child = atk_object_ref_accessible_child (obj, i);
      if (child)
        {
          append_accessible_properties (iter, child, properties);
          g_object_unref (child);
        }
    }
}
static DBusMessage *
impl_GetTree (DBusConnection *bus,
              DBusMessage *message,
              void *user_data)
{
  AtkObject *object = (AtkObject *) user_data;
  DBusMessage *reply;
  DBusMessageIter iter, iter_array;
  MatchRulePrivate rule;
  GArray *properties;
  g_return_val_if_fail (ATK_IS_OBJECT (user_data),
                        droute_not_yet_handled_error (message));
  if (strcmp (dbus_message_get_signature (message), "(aiia{ss}iaiiasib)as") != 0)
    return droute_invalid_arguments_error (message);
  properties = g_array_new (TRUE, TRUE, sizeof (char *));
  dbus_message_iter_init (message, &iter);
  if (!read_mr (&iter, &rule))
    {
      return spi_dbus_general_error (message);
    }
  dbus_message_iter_recurse (&iter, &iter_array);
  while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
    {
      const char *prop;
      dbus_message_iter_get_basic (&iter_array, &prop);
      g_array_append_val (properties, prop);
      dbus_message_iter_next (&iter_array);
    }
  reply = dbus_message_new_method_return (message);
  if (reply)
    {
      dbus_message_iter_init_append (reply, &iter);
      dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "((so)a{sv})",
                                        &iter_array);
      append_accessible_properties (&iter_array, object, properties);
      dbus_message_iter_close_container (&iter, &iter_array);
    }
  return reply;
}
static DBusMessage *
1
impl_GetMatches (DBusConnection *bus, DBusMessage *message, void *user_data)
{
1
  AtkObject *obj = ATK_OBJECT (spi_register_path_to_object (spi_global_register, dbus_message_get_path (message)));
  DBusMessageIter iter;
  MatchRulePrivate rule;
  dbus_uint32_t sortby;
  dbus_int32_t count;
  dbus_bool_t traverse;
1
  GList *ls = NULL;
  const char *signature;
1
  signature = dbus_message_get_signature (message);
1
  if (strcmp (signature, "(aiia{ss}iaiiasib)uib") != 0)
    {
      return droute_invalid_arguments_error (message);
    }
1
  dbus_message_iter_init (message, &iter);
1
  if (!read_mr (&iter, &rule))
    {
      return spi_dbus_general_error (message);
    }
1
  dbus_message_iter_get_basic (&iter, &sortby);
1
  dbus_message_iter_next (&iter);
1
  dbus_message_iter_get_basic (&iter, &count);
1
  dbus_message_iter_next (&iter);
1
  dbus_message_iter_get_basic (&iter, &traverse);
1
  dbus_message_iter_next (&iter);
1
  ls = g_list_prepend (ls, obj);
1
  count = query_exec (&rule, sortby, ls, 0, count,
                      obj, 0, TRUE, NULL, TRUE, traverse);
1
  ls = g_list_remove (ls, ls->data);
1
  if (sortby == ATSPI_Collection_SORT_ORDER_REVERSE_CANONICAL)
    ls = g_list_reverse (ls);
1
  free_mrp_data (&rule);
1
  return return_and_free_list (message, ls);
}
static DRouteMethod methods[] = {
  { impl_GetMatchesFrom, "GetMatchesFrom" },
  { impl_GetMatchesTo, "GetMatchesTo" },
  { impl_GetTree, "GetTree" },
  { impl_GetMatches, "GetMatches" },
  { NULL, NULL }
};
void
161
spi_initialize_collection (DRoutePath *path)
{
161
  spi_atk_add_interface (path,
                         ATSPI_DBUS_INTERFACE_COLLECTION, spi_org_a11y_atspi_Collection, methods, NULL);
161
};