gtkatcontext.c 35.8 KB
Newer Older
Emmanuele Bassi's avatar
Emmanuele Bassi committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* gtkatcontext.c: Assistive technology context
 *
 * Copyright 2020  GNOME Foundation
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

/**
Matthias Clasen's avatar
Matthias Clasen committed
22
 * GtkATContext:
Emmanuele Bassi's avatar
Emmanuele Bassi committed
23
 *
Matthias Clasen's avatar
Matthias Clasen committed
24
 * `GtkATContext` is an abstract class provided by GTK to communicate to
Emmanuele Bassi's avatar
Emmanuele Bassi committed
25
26
 * platform-specific assistive technologies API.
 *
Matthias Clasen's avatar
Matthias Clasen committed
27
 * Each platform supported by GTK implements a `GtkATContext` subclass, and
Emmanuele Bassi's avatar
Emmanuele Bassi committed
28
 * is responsible for updating the accessible state in response to state
Matthias Clasen's avatar
Matthias Clasen committed
29
 * changes in `GtkAccessible`.
Emmanuele Bassi's avatar
Emmanuele Bassi committed
30
31
32
33
34
35
36
 */

#include "config.h"

#include "gtkatcontextprivate.h"

#include "gtkaccessiblevalueprivate.h"
37
#include "gtkaccessibleprivate.h"
38
#include "gtkdebug.h"
Emmanuele Bassi's avatar
Emmanuele Bassi committed
39
40
41
#include "gtktestatcontextprivate.h"
#include "gtktypebuiltins.h"

42
43
44
45
#if defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND)
#include "a11y/gtkatspicontextprivate.h"
#endif

Emmanuele Bassi's avatar
Emmanuele Bassi committed
46
47
48
49
50
51
G_DEFINE_ABSTRACT_TYPE (GtkATContext, gtk_at_context, G_TYPE_OBJECT)

enum
{
  PROP_ACCESSIBLE_ROLE = 1,
  PROP_ACCESSIBLE,
52
  PROP_DISPLAY,
Emmanuele Bassi's avatar
Emmanuele Bassi committed
53
54
55
56

  N_PROPS
};

57
58
59
60
61
62
63
enum
{
  STATE_CHANGE,

  LAST_SIGNAL
};

Emmanuele Bassi's avatar
Emmanuele Bassi committed
64
65
static GParamSpec *obj_props[N_PROPS];

66
67
static guint obj_signals[LAST_SIGNAL];

Emmanuele Bassi's avatar
Emmanuele Bassi committed
68
69
70
71
72
static void
gtk_at_context_finalize (GObject *gobject)
{
  GtkATContext *self = GTK_AT_CONTEXT (gobject);

73
74
75
  gtk_accessible_attribute_set_unref (self->properties);
  gtk_accessible_attribute_set_unref (self->relations);
  gtk_accessible_attribute_set_unref (self->states);
Emmanuele Bassi's avatar
Emmanuele Bassi committed
76
77
78
79

  G_OBJECT_CLASS (gtk_at_context_parent_class)->finalize (gobject);
}

80
81
82
83
84
85
86
87
88
89
static void
gtk_at_context_dispose (GObject *gobject)
{
  GtkATContext *self = GTK_AT_CONTEXT (gobject);

  gtk_at_context_unrealize (self);

  G_OBJECT_CLASS (gtk_at_context_parent_class)->dispose (gobject);
}

Emmanuele Bassi's avatar
Emmanuele Bassi committed
90
91
92
93
94
95
96
97
98
99
100
static void
gtk_at_context_set_property (GObject      *gobject,
                             guint         prop_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  GtkATContext *self = GTK_AT_CONTEXT (gobject);

  switch (prop_id)
    {
    case PROP_ACCESSIBLE_ROLE:
101
102
103
104
      if (!self->realized)
        self->accessible_role = g_value_get_enum (value);
      else
        g_critical ("The accessible role cannot be set on a realized AT context");
Emmanuele Bassi's avatar
Emmanuele Bassi committed
105
106
107
108
109
110
      break;

    case PROP_ACCESSIBLE:
      self->accessible = g_value_get_object (value);
      break;

111
    case PROP_DISPLAY:
112
      gtk_at_context_set_display (self, g_value_get_object (value));
113
114
      break;

Emmanuele Bassi's avatar
Emmanuele Bassi committed
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
    }
}

static void
gtk_at_context_get_property (GObject    *gobject,
                             guint       prop_id,
                             GValue     *value,
                             GParamSpec *pspec)
{
  GtkATContext *self = GTK_AT_CONTEXT (gobject);

  switch (prop_id)
    {
    case PROP_ACCESSIBLE_ROLE:
      g_value_set_enum (value, self->accessible_role);
      break;

    case PROP_ACCESSIBLE:
      g_value_set_object (value, self->accessible);
      break;

138
139
140
141
    case PROP_DISPLAY:
      g_value_set_object (value, self->display);
      break;

Emmanuele Bassi's avatar
Emmanuele Bassi committed
142
143
144
145
146
147
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
    }
}

static void
148
149
gtk_at_context_real_state_change (GtkATContext                *self,
                                  GtkAccessibleStateChange     changed_states,
Emmanuele Bassi's avatar
Emmanuele Bassi committed
150
151
                                  GtkAccessiblePropertyChange  changed_properties,
                                  GtkAccessibleRelationChange  changed_relations,
152
153
154
                                  GtkAccessibleAttributeSet   *states,
                                  GtkAccessibleAttributeSet   *properties,
                                  GtkAccessibleAttributeSet   *relations)
Emmanuele Bassi's avatar
Emmanuele Bassi committed
155
156
157
{
}

158
159
160
161
162
163
static void
gtk_at_context_real_platform_change (GtkATContext                *self,
                                     GtkAccessiblePlatformChange  change)
{
}

Matthias Clasen's avatar
Matthias Clasen committed
164
165
166
167
168
static void
gtk_at_context_real_bounds_change (GtkATContext *self)
{
}

169
170
171
172
173
174
175
static void
gtk_at_context_real_child_change (GtkATContext             *self,
                                  GtkAccessibleChildChange  change,
                                  GtkAccessible            *child)
{
}

176
177
178
179
180
181
182
183
184
185
static void
gtk_at_context_real_realize (GtkATContext *self)
{
}

static void
gtk_at_context_real_unrealize (GtkATContext *self)
{
}

Emmanuele Bassi's avatar
Emmanuele Bassi committed
186
187
188
189
190
191
192
static void
gtk_at_context_class_init (GtkATContextClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->set_property = gtk_at_context_set_property;
  gobject_class->get_property = gtk_at_context_get_property;
193
  gobject_class->dispose = gtk_at_context_dispose;
Emmanuele Bassi's avatar
Emmanuele Bassi committed
194
195
  gobject_class->finalize = gtk_at_context_finalize;

196
197
  klass->realize = gtk_at_context_real_realize;
  klass->unrealize = gtk_at_context_real_unrealize;
Emmanuele Bassi's avatar
Emmanuele Bassi committed
198
  klass->state_change = gtk_at_context_real_state_change;
199
  klass->platform_change = gtk_at_context_real_platform_change;
Matthias Clasen's avatar
Matthias Clasen committed
200
  klass->bounds_change = gtk_at_context_real_bounds_change;
201
  klass->child_change = gtk_at_context_real_child_change;
Emmanuele Bassi's avatar
Emmanuele Bassi committed
202
203

  /**
Matthias Clasen's avatar
Matthias Clasen committed
204
   * GtkATContext:accessible-role: (attributes org.gtk.Property.get=gtk_at_context_get_accessible_role)
Emmanuele Bassi's avatar
Emmanuele Bassi committed
205
206
207
208
209
210
211
212
213
214
215
   *
   * The accessible role used by the AT context.
   *
   * Depending on the given role, different states and properties can be
   * set or retrieved.
   */
  obj_props[PROP_ACCESSIBLE_ROLE] =
    g_param_spec_enum ("accessible-role",
                       "Accessible Role",
                       "The accessible role of the AT context",
                       GTK_TYPE_ACCESSIBLE_ROLE,
216
                       GTK_ACCESSIBLE_ROLE_NONE,
Emmanuele Bassi's avatar
Emmanuele Bassi committed
217
                       G_PARAM_READWRITE |
218
                       G_PARAM_CONSTRUCT |
Emmanuele Bassi's avatar
Emmanuele Bassi committed
219
220
221
                       G_PARAM_STATIC_STRINGS);

  /**
Matthias Clasen's avatar
Matthias Clasen committed
222
   * GtkATContext:accessible: (attributes org.gtk.Property.get=gtk_at_context_get_accessible)
Emmanuele Bassi's avatar
Emmanuele Bassi committed
223
   *
Matthias Clasen's avatar
Matthias Clasen committed
224
   * The `GtkAccessible` that created the `GtkATContext` instance.
Emmanuele Bassi's avatar
Emmanuele Bassi committed
225
226
227
228
229
230
231
232
233
234
   */
  obj_props[PROP_ACCESSIBLE] =
    g_param_spec_object ("accessible",
                         "Accessible",
                         "The accessible implementation",
                         GTK_TYPE_ACCESSIBLE,
                         G_PARAM_READWRITE |
                         G_PARAM_CONSTRUCT_ONLY |
                         G_PARAM_STATIC_STRINGS);

235
236
237
  /**
   * GtkATContext:display:
   *
Matthias Clasen's avatar
Matthias Clasen committed
238
   * The `GdkDisplay` for the `GtkATContext`.
239
240
241
242
243
244
245
   */
  obj_props[PROP_DISPLAY] =
    g_param_spec_object ("display",
                         "Display",
                         "The display connection",
                         GDK_TYPE_DISPLAY,
                         G_PARAM_READWRITE |
246
247
                         G_PARAM_STATIC_STRINGS |
                         G_PARAM_EXPLICIT_NOTIFY);
248

249
250
  /**
   * GtkATContext::state-change:
Matthias Clasen's avatar
Matthias Clasen committed
251
   * @self: the `GtkATContext`
252
253
   *
   * Emitted when the attributes of the accessible for the
Matthias Clasen's avatar
Matthias Clasen committed
254
   * `GtkATContext` instance change.
255
   */
256
257
258
259
  obj_signals[STATE_CHANGE] =
    g_signal_new ("state-change",
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_FIRST,
260
                  0,
261
262
                  NULL, NULL,
                  NULL,
263
                  G_TYPE_NONE, 0);
264

Emmanuele Bassi's avatar
Emmanuele Bassi committed
265
266
267
  g_object_class_install_properties (gobject_class, N_PROPS, obj_props);
}

268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
#define N_PROPERTIES    (GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT + 1)
#define N_RELATIONS     (GTK_ACCESSIBLE_RELATION_SET_SIZE + 1)
#define N_STATES        (GTK_ACCESSIBLE_STATE_SELECTED + 1)

static const char *property_attrs[] = {
  [GTK_ACCESSIBLE_PROPERTY_AUTOCOMPLETE]        = "autocomplete",
  [GTK_ACCESSIBLE_PROPERTY_DESCRIPTION]         = "description",
  [GTK_ACCESSIBLE_PROPERTY_HAS_POPUP]           = "haspopup",
  [GTK_ACCESSIBLE_PROPERTY_KEY_SHORTCUTS]       = "keyshortcuts",
  [GTK_ACCESSIBLE_PROPERTY_LABEL]               = "label",
  [GTK_ACCESSIBLE_PROPERTY_LEVEL]               = "level",
  [GTK_ACCESSIBLE_PROPERTY_MODAL]               = "modal",
  [GTK_ACCESSIBLE_PROPERTY_MULTI_LINE]          = "multiline",
  [GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE]    = "multiselectable",
  [GTK_ACCESSIBLE_PROPERTY_ORIENTATION]         = "orientation",
  [GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER]         = "placeholder",
  [GTK_ACCESSIBLE_PROPERTY_READ_ONLY]           = "readonly",
  [GTK_ACCESSIBLE_PROPERTY_REQUIRED]            = "required",
  [GTK_ACCESSIBLE_PROPERTY_ROLE_DESCRIPTION]    = "roledescription",
  [GTK_ACCESSIBLE_PROPERTY_SORT]                = "sort",
  [GTK_ACCESSIBLE_PROPERTY_VALUE_MAX]           = "valuemax",
  [GTK_ACCESSIBLE_PROPERTY_VALUE_MIN]           = "valuemin",
  [GTK_ACCESSIBLE_PROPERTY_VALUE_NOW]           = "valuenow",
  [GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT]          = "valuetext",
};

Emmanuele Bassi's avatar
Emmanuele Bassi committed
294
295
/*< private >
 * gtk_accessible_property_get_attribute_name:
Matthias Clasen's avatar
Matthias Clasen committed
296
 * @property: a `GtkAccessibleProperty`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
297
 *
Matthias Clasen's avatar
Matthias Clasen committed
298
 * Retrieves the name of a `GtkAccessibleProperty`.
Emmanuele Bassi's avatar
Emmanuele Bassi committed
299
300
301
302
303
304
305
306
307
308
309
310
311
 *
 * Returns: (transfer none): the name of the accessible property
 */
const char *
gtk_accessible_property_get_attribute_name (GtkAccessibleProperty property)
{
  g_return_val_if_fail (property >= GTK_ACCESSIBLE_PROPERTY_AUTOCOMPLETE &&
                        property <= GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT,
                        "<none>");

  return property_attrs[property];
}

312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
static const char *relation_attrs[] = {
  [GTK_ACCESSIBLE_RELATION_ACTIVE_DESCENDANT]   = "activedescendant",
  [GTK_ACCESSIBLE_RELATION_COL_COUNT]           = "colcount",
  [GTK_ACCESSIBLE_RELATION_COL_INDEX]           = "colindex",
  [GTK_ACCESSIBLE_RELATION_COL_INDEX_TEXT]      = "colindextext",
  [GTK_ACCESSIBLE_RELATION_COL_SPAN]            = "colspan",
  [GTK_ACCESSIBLE_RELATION_CONTROLS]            = "controls",
  [GTK_ACCESSIBLE_RELATION_DESCRIBED_BY]        = "describedby",
  [GTK_ACCESSIBLE_RELATION_DETAILS]             = "details",
  [GTK_ACCESSIBLE_RELATION_ERROR_MESSAGE]       = "errormessage",
  [GTK_ACCESSIBLE_RELATION_FLOW_TO]             = "flowto",
  [GTK_ACCESSIBLE_RELATION_LABELLED_BY]         = "labelledby",
  [GTK_ACCESSIBLE_RELATION_OWNS]                = "owns",
  [GTK_ACCESSIBLE_RELATION_POS_IN_SET]          = "posinset",
  [GTK_ACCESSIBLE_RELATION_ROW_COUNT]           = "rowcount",
  [GTK_ACCESSIBLE_RELATION_ROW_INDEX]           = "rowindex",
  [GTK_ACCESSIBLE_RELATION_ROW_INDEX_TEXT]      = "rowindextext",
  [GTK_ACCESSIBLE_RELATION_ROW_SPAN]            = "rowspan",
  [GTK_ACCESSIBLE_RELATION_SET_SIZE]            = "setsize",
};

Emmanuele Bassi's avatar
Emmanuele Bassi committed
333
334
/*< private >
 * gtk_accessible_relation_get_attribute_name:
Matthias Clasen's avatar
Matthias Clasen committed
335
 * @relation: a `GtkAccessibleRelation`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
336
 *
Matthias Clasen's avatar
Matthias Clasen committed
337
 * Retrieves the name of a `GtkAccessibleRelation`.
Emmanuele Bassi's avatar
Emmanuele Bassi committed
338
339
340
341
342
343
344
345
346
347
348
349
350
 *
 * Returns: (transfer none): the name of the accessible relation
 */
const char *
gtk_accessible_relation_get_attribute_name (GtkAccessibleRelation relation)
{
  g_return_val_if_fail (relation >= GTK_ACCESSIBLE_RELATION_ACTIVE_DESCENDANT &&
                        relation <= GTK_ACCESSIBLE_RELATION_SET_SIZE,
                        "<none>");

  return relation_attrs[relation];
}

351
352
353
354
355
356
357
358
359
360
361
static const char *state_attrs[] = {
  [GTK_ACCESSIBLE_STATE_BUSY]           = "busy",
  [GTK_ACCESSIBLE_STATE_CHECKED]        = "checked",
  [GTK_ACCESSIBLE_STATE_DISABLED]       = "disabled",
  [GTK_ACCESSIBLE_STATE_EXPANDED]       = "expanded",
  [GTK_ACCESSIBLE_STATE_HIDDEN]         = "hidden",
  [GTK_ACCESSIBLE_STATE_INVALID]        = "invalid",
  [GTK_ACCESSIBLE_STATE_PRESSED]        = "pressed",
  [GTK_ACCESSIBLE_STATE_SELECTED]       = "selected",
};

Emmanuele Bassi's avatar
Emmanuele Bassi committed
362
363
/*< private >
 * gtk_accessible_state_get_attribute_name:
Matthias Clasen's avatar
Matthias Clasen committed
364
 * @state: a `GtkAccessibleState`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
365
 *
Matthias Clasen's avatar
Matthias Clasen committed
366
 * Retrieves the name of a `GtkAccessibleState`.
Emmanuele Bassi's avatar
Emmanuele Bassi committed
367
368
369
370
371
372
373
374
375
376
377
378
379
 *
 * Returns: (transfer none): the name of the accessible state
 */
const char *
gtk_accessible_state_get_attribute_name (GtkAccessibleState state)
{
  g_return_val_if_fail (state >= GTK_ACCESSIBLE_STATE_BUSY &&
                        state <= GTK_ACCESSIBLE_STATE_SELECTED,
                        "<none>");

  return state_attrs[state];
}

Emmanuele Bassi's avatar
Emmanuele Bassi committed
380
381
382
static void
gtk_at_context_init (GtkATContext *self)
{
383
  self->accessible_role = GTK_ACCESSIBLE_ROLE_NONE;
Emmanuele Bassi's avatar
Emmanuele Bassi committed
384

385
  self->properties =
386
    gtk_accessible_attribute_set_new (G_N_ELEMENTS (property_attrs),
387
                                      (GtkAccessibleAttributeNameFunc) gtk_accessible_property_get_attribute_name,
388
389
                                      (GtkAccessibleAttributeDefaultFunc) gtk_accessible_value_get_default_for_property);
  self->relations =
390
    gtk_accessible_attribute_set_new (G_N_ELEMENTS (relation_attrs),
391
                                      (GtkAccessibleAttributeNameFunc) gtk_accessible_relation_get_attribute_name,
392
393
                                      (GtkAccessibleAttributeDefaultFunc) gtk_accessible_value_get_default_for_relation);
  self->states =
394
    gtk_accessible_attribute_set_new (G_N_ELEMENTS (state_attrs),
395
                                      (GtkAccessibleAttributeNameFunc) gtk_accessible_state_get_attribute_name,
396
                                      (GtkAccessibleAttributeDefaultFunc) gtk_accessible_value_get_default_for_state);
Emmanuele Bassi's avatar
Emmanuele Bassi committed
397
398
399
}

/**
Matthias Clasen's avatar
Matthias Clasen committed
400
401
 * gtk_at_context_get_accessible: (attributes org.gtk.Method.get_property=accessible)
 * @self: a `GtkATContext`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
402
 *
Matthias Clasen's avatar
Matthias Clasen committed
403
 * Retrieves the `GtkAccessible` using this context.
Emmanuele Bassi's avatar
Emmanuele Bassi committed
404
 *
Matthias Clasen's avatar
Matthias Clasen committed
405
 * Returns: (transfer none): a `GtkAccessible`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
406
407
408
409
410
411
412
413
414
 */
GtkAccessible *
gtk_at_context_get_accessible (GtkATContext *self)
{
  g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);

  return self->accessible;
}

415
416
/*< private >
 * gtk_at_context_set_accessible_role:
Matthias Clasen's avatar
Matthias Clasen committed
417
 * @self: a `GtkATContext`
418
419
 * @role: the accessible role for the context
 *
Matthias Clasen's avatar
Matthias Clasen committed
420
 * Sets the accessible role for the given `GtkATContext`.
421
 *
Matthias Clasen's avatar
Matthias Clasen committed
422
 * This function can only be called if the `GtkATContext` is unrealized.
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
 */
void
gtk_at_context_set_accessible_role (GtkATContext      *self,
                                    GtkAccessibleRole  role)
{
  g_return_if_fail (GTK_IS_AT_CONTEXT (self));
  g_return_if_fail (!self->realized);

  if (self->accessible_role == role)
    return;

  self->accessible_role = role;

  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_ACCESSIBLE_ROLE]);
}

Emmanuele Bassi's avatar
Emmanuele Bassi committed
439
/**
Matthias Clasen's avatar
Matthias Clasen committed
440
441
 * gtk_at_context_get_accessible_role: (attributes org.gtk.Method.get_property=accessible-role)
 * @self: a `GtkATContext`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
442
443
444
 *
 * Retrieves the accessible role of this context.
 *
Matthias Clasen's avatar
Matthias Clasen committed
445
 * Returns: a `GtkAccessibleRole`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
446
447
448
449
 */
GtkAccessibleRole
gtk_at_context_get_accessible_role (GtkATContext *self)
{
450
  g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), GTK_ACCESSIBLE_ROLE_NONE);
Emmanuele Bassi's avatar
Emmanuele Bassi committed
451
452
453
454

  return self->accessible_role;
}

455
456
/*< private >
 * gtk_at_context_set_display:
Matthias Clasen's avatar
Matthias Clasen committed
457
458
 * @self: a `GtkATContext`
 * @display: a `GdkDisplay`
459
 *
Matthias Clasen's avatar
Matthias Clasen committed
460
 * Sets the `GdkDisplay` used by the `GtkATContext`.
461
 *
Matthias Clasen's avatar
Matthias Clasen committed
462
 * This function can only be called if the `GtkATContext` is
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
 * not realized.
 */
void
gtk_at_context_set_display (GtkATContext *self,
                            GdkDisplay   *display)
{
  g_return_if_fail (GTK_IS_AT_CONTEXT (self));
  g_return_if_fail (display == NULL || GDK_IS_DISPLAY (display));

  if (self->display == display)
    return;

  if (self->realized)
    return;

  self->display = display;

  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_DISPLAY]);
}

483
484
/*< private >
 * gtk_at_context_get_display:
Matthias Clasen's avatar
Matthias Clasen committed
485
 * @self: a `GtkATContext`
486
 *
Matthias Clasen's avatar
Matthias Clasen committed
487
 * Retrieves the `GdkDisplay` used to create the context.
488
 *
Matthias Clasen's avatar
Matthias Clasen committed
489
 * Returns: (transfer none): a `GdkDisplay`
490
491
492
493
494
495
496
497
498
 */
GdkDisplay *
gtk_at_context_get_display (GtkATContext *self)
{
  g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);

  return self->display;
}

499
500
static const struct {
  const char *name;
501
  const char *env_name;
502
  GtkATContext * (* create_context) (GtkAccessibleRole accessible_role,
503
504
                                     GtkAccessible    *accessible,
                                     GdkDisplay       *display);
505
506
} a11y_backends[] = {
#if defined(GDK_WINDOWING_WAYLAND)
507
  { "AT-SPI (Wayland)", "atspi", gtk_at_spi_create_context },
508
509
#endif
#if defined(GDK_WINDOWING_X11)
510
  { "AT-SPI (X11)", "atspi", gtk_at_spi_create_context },
511
#endif
512
513
  { "Test", "test", gtk_test_at_context_new },
  { NULL, NULL, NULL },
514
515
};

516
517
/**
 * gtk_at_context_create: (constructor)
Matthias Clasen's avatar
Matthias Clasen committed
518
519
520
 * @accessible_role: the accessible role used by the `GtkATContext`
 * @accessible: the `GtkAccessible` implementation using the `GtkATContext`
 * @display: the `GdkDisplay` used by the `GtkATContext`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
521
 *
Matthias Clasen's avatar
Matthias Clasen committed
522
 * Creates a new `GtkATContext` instance for the given accessible role,
523
 * accessible instance, and display connection.
Emmanuele Bassi's avatar
Emmanuele Bassi committed
524
 *
Matthias Clasen's avatar
Matthias Clasen committed
525
 * The `GtkATContext` implementation being instantiated will depend on the
Emmanuele Bassi's avatar
Emmanuele Bassi committed
526
527
 * platform.
 *
Matthias Clasen's avatar
Matthias Clasen committed
528
 * Returns: (nullable) (transfer full): the `GtkATContext`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
529
530
531
 */
GtkATContext *
gtk_at_context_create (GtkAccessibleRole  accessible_role,
532
533
                       GtkAccessible     *accessible,
                       GdkDisplay        *display)
Emmanuele Bassi's avatar
Emmanuele Bassi committed
534
{
535
  static const char *gtk_a11y_env;
Emmanuele Bassi's avatar
Emmanuele Bassi committed
536

537
  if (gtk_a11y_env == NULL)
Emmanuele Bassi's avatar
Emmanuele Bassi committed
538
    {
539
540
541
542
      gtk_a11y_env = g_getenv ("GTK_A11Y");
      if (gtk_a11y_env == NULL)
        gtk_a11y_env = "0";

Matthias Clasen's avatar
Matthias Clasen committed
543
544
545
      if (g_ascii_strcasecmp (gtk_a11y_env, "help") == 0)
        {
          g_print ("Supported arguments for GTK_A11Y environment variable:\n");
Emmanuele Bassi's avatar
Emmanuele Bassi committed
546

547
#if defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND)
Matthias Clasen's avatar
Matthias Clasen committed
548
          g_print ("   atspi - Use the AT-SPI accessibility backend\n");
549
#endif
Matthias Clasen's avatar
Matthias Clasen committed
550
551
552
553
554
555
556
          g_print ("    test - Use the test accessibility backend\n");
          g_print ("    none - Disable the accessibility backend\n");
          g_print ("    help - Print this help\n\n");
          g_print ("Other arguments will cause a warning and be ignored.\n");

          gtk_a11y_env = "0";
        }
Emmanuele Bassi's avatar
Emmanuele Bassi committed
557
558
    }

Matthias Clasen's avatar
Matthias Clasen committed
559
560
561
562
  /* Short-circuit disabling the accessibility support */
  if (g_ascii_strcasecmp (gtk_a11y_env, "none") == 0)
    return NULL;

563
564
565
566
  GtkATContext *res = NULL;

  for (guint i = 0; i < G_N_ELEMENTS (a11y_backends); i++)
    {
567
568
569
      if (a11y_backends[i].name == NULL)
        break;

570
571
      if (a11y_backends[i].create_context != NULL &&
          (*gtk_a11y_env == '0' || g_ascii_strcasecmp (a11y_backends[i].env_name, gtk_a11y_env) == 0))
572
        {
573
574
575
          res = a11y_backends[i].create_context (accessible_role, accessible, display);
          if (res != NULL)
            break;
576
577
578
        }
    }

579
580
581
  if (*gtk_a11y_env != '0' && res == NULL)
    g_warning ("Unrecognized accessibility backend \"%s\". Try GTK_A11Y=help", gtk_a11y_env);

582
  /* Fall back to the test context, so we can get debugging data */
583
  if (res == NULL)
584
585
586
587
588
    res = g_object_new (GTK_TYPE_TEST_AT_CONTEXT,
                        "accessible_role", accessible_role,
                        "accessible", accessible,
                        "display", display,
                        NULL);
589
590

  return res;
Emmanuele Bassi's avatar
Emmanuele Bassi committed
591
592
}

593
594
/*< private >
 * gtk_at_context_clone: (constructor)
Matthias Clasen's avatar
Matthias Clasen committed
595
 * @self: the `GtkATContext` to clone
596
597
598
 * @role: the accessible role of the clone, or %GTK_ACCESSIBLE_ROLE_NONE to
 *   use the same accessible role of @self
 * @accessible: (nullable): the accessible creating the context, or %NULL to
Matthias Clasen's avatar
Matthias Clasen committed
599
 *   use the same `GtkAccessible` of @self
600
 * @display: (nullable): the display connection, or %NULL to use the same
Matthias Clasen's avatar
Matthias Clasen committed
601
 *   `GdkDisplay` of @self
602
 *
Matthias Clasen's avatar
Matthias Clasen committed
603
 * Clones the state of the given `GtkATContext`, using @role, @accessible,
604
605
 * and @display.
 *
Matthias Clasen's avatar
Matthias Clasen committed
606
 * If @self is realized, the returned `GtkATContext` will also be realized.
607
 *
Matthias Clasen's avatar
Matthias Clasen committed
608
 * Returns: (transfer full): the newly created `GtkATContext`
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
 */
GtkATContext *
gtk_at_context_clone (GtkATContext      *self,
                      GtkAccessibleRole  role,
                      GtkAccessible     *accessible,
                      GdkDisplay        *display)
{
  g_return_val_if_fail (self == NULL || GTK_IS_AT_CONTEXT (self), NULL);
  g_return_val_if_fail (accessible == NULL || GTK_IS_ACCESSIBLE (accessible), NULL);
  g_return_val_if_fail (display == NULL || GDK_IS_DISPLAY (display), NULL);

  if (self != NULL && role == GTK_ACCESSIBLE_ROLE_NONE)
    role = self->accessible_role;

  if (self != NULL && accessible == NULL)
    accessible = self->accessible;

  if (self != NULL && display == NULL)
    display = self->display;

  GtkATContext *res = gtk_at_context_create (role, accessible, display);

  if (self != NULL)
    {
      g_clear_pointer (&res->states, gtk_accessible_attribute_set_unref);
      g_clear_pointer (&res->properties, gtk_accessible_attribute_set_unref);
      g_clear_pointer (&res->relations, gtk_accessible_attribute_set_unref);

      res->states = gtk_accessible_attribute_set_ref (self->states);
      res->properties = gtk_accessible_attribute_set_ref (self->properties);
      res->relations = gtk_accessible_attribute_set_ref (self->relations);

      if (self->realized)
        gtk_at_context_realize (res);
    }

  return res;
}

648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
gboolean
gtk_at_context_is_realized (GtkATContext *self)
{
  return self->realized;
}

void
gtk_at_context_realize (GtkATContext *self)
{
  if (self->realized)
    return;

  GTK_NOTE (A11Y, g_message ("Realizing AT context '%s'", G_OBJECT_TYPE_NAME (self)));
  GTK_AT_CONTEXT_GET_CLASS (self)->realize (self);

  self->realized = TRUE;
}

void
gtk_at_context_unrealize (GtkATContext *self)
{
  if (!self->realized)
    return;

  GTK_NOTE (A11Y, g_message ("Unrealizing AT context '%s'", G_OBJECT_TYPE_NAME (self)));
  GTK_AT_CONTEXT_GET_CLASS (self)->unrealize (self);

  self->realized = FALSE;
}

Emmanuele Bassi's avatar
Emmanuele Bassi committed
678
/*< private >
679
 * gtk_at_context_update:
Matthias Clasen's avatar
Matthias Clasen committed
680
 * @self: a `GtkATContext`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
681
 *
Matthias Clasen's avatar
Matthias Clasen committed
682
 * Notifies the AT connected to this `GtkATContext` that the accessible
683
 * state and its properties have changed.
Emmanuele Bassi's avatar
Emmanuele Bassi committed
684
685
 */
void
686
gtk_at_context_update (GtkATContext *self)
Emmanuele Bassi's avatar
Emmanuele Bassi committed
687
688
689
{
  g_return_if_fail (GTK_IS_AT_CONTEXT (self));

690
691
692
  if (!self->realized)
    return;

693
694
695
  /* There's no point in notifying of state changes if there weren't any */
  if (self->updated_properties == 0 &&
      self->updated_relations == 0 &&
696
      self->updated_states == 0)
697
698
    return;

699
  GTK_AT_CONTEXT_GET_CLASS (self)->state_change (self,
700
                                                 self->updated_states, self->updated_properties, self->updated_relations,
701
702
                                                 self->states, self->properties, self->relations);
  g_signal_emit (self, obj_signals[STATE_CHANGE], 0);
703
704
705
706

  self->updated_properties = 0;
  self->updated_relations = 0;
  self->updated_states = 0;
Emmanuele Bassi's avatar
Emmanuele Bassi committed
707
708
}

709
/*< private >
Emmanuele Bassi's avatar
Emmanuele Bassi committed
710
 * gtk_at_context_set_accessible_state:
Matthias Clasen's avatar
Matthias Clasen committed
711
712
 * @self: a `GtkATContext`
 * @state: a `GtkAccessibleState`
Matthias Clasen's avatar
Matthias Clasen committed
713
 * @value: (nullable): `GtkAccessibleValue`
714
 *
Matthias Clasen's avatar
Matthias Clasen committed
715
 * Sets the @value for the given @state of a `GtkATContext`.
716
717
718
 *
 * If @value is %NULL, the state is unset.
 *
719
 * This function will accumulate state changes until gtk_at_context_update()
720
721
722
723
724
725
726
727
728
 * is called.
 */
void
gtk_at_context_set_accessible_state (GtkATContext       *self,
                                     GtkAccessibleState  state,
                                     GtkAccessibleValue *value)
{
  g_return_if_fail (GTK_IS_AT_CONTEXT (self));

729
730
  gboolean res = FALSE;

731
  if (value != NULL)
732
    res = gtk_accessible_attribute_set_add (self->states, state, value);
733
  else
734
735
736
737
    res = gtk_accessible_attribute_set_remove (self->states, state);

  if (res)
    self->updated_states |= (1 << state);
738
739
}

Emmanuele Bassi's avatar
Emmanuele Bassi committed
740
741
/*< private >
 * gtk_at_context_has_accessible_state:
Matthias Clasen's avatar
Matthias Clasen committed
742
743
 * @self: a `GtkATContext`
 * @state: a `GtkAccessibleState`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
744
 *
Matthias Clasen's avatar
Matthias Clasen committed
745
 * Checks whether a `GtkATContext` has the given @state set.
Emmanuele Bassi's avatar
Emmanuele Bassi committed
746
747
748
749
750
751
752
753
754
755
756
757
 *
 * Returns: %TRUE, if the accessible state is set
 */
gboolean
gtk_at_context_has_accessible_state (GtkATContext       *self,
                                     GtkAccessibleState  state)
{
  g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), FALSE);

  return gtk_accessible_attribute_set_contains (self->states, state);
}

758
759
/*< private >
 * gtk_at_context_get_accessible_state:
Matthias Clasen's avatar
Matthias Clasen committed
760
761
 * @self: a `GtkATContext`
 * @state: a `GtkAccessibleState`
762
 *
Matthias Clasen's avatar
Matthias Clasen committed
763
 * Retrieves the value for the accessible state of a `GtkATContext`.
764
765
766
767
768
769
770
771
772
773
774
775
 *
 * Returns: (transfer none): the value for the given state
 */
GtkAccessibleValue *
gtk_at_context_get_accessible_state (GtkATContext       *self,
                                     GtkAccessibleState  state)
{
  g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);

  return gtk_accessible_attribute_set_get_value (self->states, state);
}

776
777
/*< private >
 * gtk_at_context_set_accessible_property:
Matthias Clasen's avatar
Matthias Clasen committed
778
779
780
 * @self: a `GtkATContext`
 * @property: a `GtkAccessibleProperty`
 * @value: (nullable): `GtkAccessibleValue`
781
 *
Matthias Clasen's avatar
Matthias Clasen committed
782
 * Sets the @value for the given @property of a `GtkATContext`.
783
784
785
 *
 * If @value is %NULL, the property is unset.
 *
786
 * This function will accumulate property changes until gtk_at_context_update()
787
788
 * is called.
 */
Emmanuele Bassi's avatar
Emmanuele Bassi committed
789
void
790
791
792
gtk_at_context_set_accessible_property (GtkATContext          *self,
                                        GtkAccessibleProperty  property,
                                        GtkAccessibleValue    *value)
Emmanuele Bassi's avatar
Emmanuele Bassi committed
793
794
795
{
  g_return_if_fail (GTK_IS_AT_CONTEXT (self));

796
797
  gboolean res = FALSE;

798
  if (value != NULL)
799
    res = gtk_accessible_attribute_set_add (self->properties, property, value);
800
  else
801
802
803
804
    res = gtk_accessible_attribute_set_remove (self->properties, property);

  if (res)
    self->updated_properties |= (1 << property);
Emmanuele Bassi's avatar
Emmanuele Bassi committed
805
}
Emmanuele Bassi's avatar
Emmanuele Bassi committed
806

Emmanuele Bassi's avatar
Emmanuele Bassi committed
807
808
/*< private >
 * gtk_at_context_has_accessible_property:
Matthias Clasen's avatar
Matthias Clasen committed
809
810
 * @self: a `GtkATContext`
 * @property: a `GtkAccessibleProperty`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
811
 *
Matthias Clasen's avatar
Matthias Clasen committed
812
 * Checks whether a `GtkATContext` has the given @property set.
Emmanuele Bassi's avatar
Emmanuele Bassi committed
813
814
815
816
817
818
819
820
821
822
823
824
 *
 * Returns: %TRUE, if the accessible property is set
 */
gboolean
gtk_at_context_has_accessible_property (GtkATContext          *self,
                                        GtkAccessibleProperty  property)
{
  g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), FALSE);

  return gtk_accessible_attribute_set_contains (self->properties, property);
}

825
826
/*< private >
 * gtk_at_context_get_accessible_property:
Matthias Clasen's avatar
Matthias Clasen committed
827
828
 * @self: a `GtkATContext`
 * @property: a `GtkAccessibleProperty`
829
 *
Matthias Clasen's avatar
Matthias Clasen committed
830
 * Retrieves the value for the accessible property of a `GtkATContext`.
831
832
833
834
835
836
837
838
839
840
841
842
 *
 * Returns: (transfer none): the value for the given property
 */
GtkAccessibleValue *
gtk_at_context_get_accessible_property (GtkATContext          *self,
                                        GtkAccessibleProperty  property)
{
  g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);

  return gtk_accessible_attribute_set_get_value (self->properties, property);
}

Emmanuele Bassi's avatar
Emmanuele Bassi committed
843
844
/*< private >
 * gtk_at_context_set_accessible_relation:
Matthias Clasen's avatar
Matthias Clasen committed
845
846
847
 * @self: a `GtkATContext`
 * @relation: a `GtkAccessibleRelation`
 * @value: (nullable): `GtkAccessibleValue`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
848
 *
Matthias Clasen's avatar
Matthias Clasen committed
849
 * Sets the @value for the given @relation of a `GtkATContext`.
Emmanuele Bassi's avatar
Emmanuele Bassi committed
850
851
852
 *
 * If @value is %NULL, the relation is unset.
 *
853
 * This function will accumulate relation changes until gtk_at_context_update()
Emmanuele Bassi's avatar
Emmanuele Bassi committed
854
855
856
857
858
859
860
861
862
 * is called.
 */
void
gtk_at_context_set_accessible_relation (GtkATContext          *self,
                                        GtkAccessibleRelation  relation,
                                        GtkAccessibleValue    *value)
{
  g_return_if_fail (GTK_IS_AT_CONTEXT (self));

863
864
  gboolean res = FALSE;

Emmanuele Bassi's avatar
Emmanuele Bassi committed
865
  if (value != NULL)
866
    res = gtk_accessible_attribute_set_add (self->relations, relation, value);
Emmanuele Bassi's avatar
Emmanuele Bassi committed
867
  else
868
869
870
871
    res = gtk_accessible_attribute_set_remove (self->relations, relation);

  if (res)
    self->updated_relations |= (1 << relation);
Emmanuele Bassi's avatar
Emmanuele Bassi committed
872
}
Emmanuele Bassi's avatar
Emmanuele Bassi committed
873
874
875

/*< private >
 * gtk_at_context_has_accessible_relation:
Matthias Clasen's avatar
Matthias Clasen committed
876
877
 * @self: a `GtkATContext`
 * @relation: a `GtkAccessibleRelation`
Emmanuele Bassi's avatar
Emmanuele Bassi committed
878
 *
Matthias Clasen's avatar
Matthias Clasen committed
879
 * Checks whether a `GtkATContext` has the given @relation set.
Emmanuele Bassi's avatar
Emmanuele Bassi committed
880
881
882
883
884
885
886
887
888
889
890
 *
 * Returns: %TRUE, if the accessible relation is set
 */
gboolean
gtk_at_context_has_accessible_relation (GtkATContext          *self,
                                        GtkAccessibleRelation  relation)
{
  g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), FALSE);

  return gtk_accessible_attribute_set_contains (self->relations, relation);
}
891
892
893

/*< private >
 * gtk_at_context_get_accessible_relation:
Matthias Clasen's avatar
Matthias Clasen committed
894
895
 * @self: a `GtkATContext`
 * @relation: a `GtkAccessibleRelation`
896
 *
Matthias Clasen's avatar
Matthias Clasen committed
897
 * Retrieves the value for the accessible relation of a `GtkATContext`.
898
899
900
901
902
903
904
905
906
907
908
 *
 * Returns: (transfer none): the value for the given relation
 */
GtkAccessibleValue *
gtk_at_context_get_accessible_relation (GtkATContext          *self,
                                        GtkAccessibleRelation  relation)
{
  g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);

  return gtk_accessible_attribute_set_get_value (self->relations, relation);
}
909

910
911
912
913
914
915
916
917
918
919
920
static gboolean
is_structural_role (GtkAccessibleRole role)
{
  /* Keep the switch small while avoiding the compiler warning for
   * unhandled enumeration values
   */
  switch ((int) role)
    {
    case GTK_ACCESSIBLE_ROLE_FORM:
    case GTK_ACCESSIBLE_ROLE_GROUP:
    case GTK_ACCESSIBLE_ROLE_GENERIC:
921
922
    case GTK_ACCESSIBLE_ROLE_LANDMARK:
    case GTK_ACCESSIBLE_ROLE_LIST_ITEM:
923
    case GTK_ACCESSIBLE_ROLE_REGION:
924
925
    case GTK_ACCESSIBLE_ROLE_SEARCH:
    case GTK_ACCESSIBLE_ROLE_SEPARATOR:
926
927
928
929
930
931
932
933
934
      return TRUE;

    default:
      break;
    }

  return FALSE;
}

935
936
937
938
939
/* See the WAI-ARIA § 4.3, "Accessible Name and Description Computation" */
static void
gtk_at_context_get_name_accumulate (GtkATContext *self,
                                    GPtrArray    *names,
                                    gboolean      recurse)
940
941
942
{
  GtkAccessibleValue *value = NULL;

943
944
945
946
947
948
949
950
951
952
953
954
955
  if (gtk_accessible_attribute_set_contains (self->properties, GTK_ACCESSIBLE_PROPERTY_LABEL))
    {
      value = gtk_accessible_attribute_set_get_value (self->properties, GTK_ACCESSIBLE_PROPERTY_LABEL);

      g_ptr_array_add (names, (char *) gtk_string_accessible_value_get (value));
    }

  if (recurse && gtk_accessible_attribute_set_contains (self->relations, GTK_ACCESSIBLE_RELATION_LABELLED_BY))
    {
      value = gtk_accessible_attribute_set_get_value (self->relations, GTK_ACCESSIBLE_RELATION_LABELLED_BY);

      GList *list = gtk_reference_list_accessible_value_get (value);

956
      for (GList *l = list; l != NULL; l = l->next)
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
        {
          GtkAccessible *rel = GTK_ACCESSIBLE (l->data);
          GtkATContext *rel_context = gtk_accessible_get_at_context (rel);

          gtk_at_context_get_name_accumulate (rel_context, names, FALSE);
        }
    }

  GtkAccessibleRole role = gtk_at_context_get_accessible_role (self);

  switch ((int) role)
    {
    case GTK_ACCESSIBLE_ROLE_RANGE:
      {
        int range_attrs[] = {
          GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT,
          GTK_ACCESSIBLE_PROPERTY_VALUE_NOW,
        };

        value = NULL;
        for (int i = 0; i < G_N_ELEMENTS (range_attrs); i++)
          {
            if (gtk_accessible_attribute_set_contains (self->properties, range_attrs[i]))
              {
                value = gtk_accessible_attribute_set_get_value (self->properties, range_attrs[i]);
                break;
              }
          }

        if (value != NULL)
          g_ptr_array_add (names, (char *) gtk_string_accessible_value_get (value));
      }
      break;

    default:
      break;
    }

  /* If there is no label or labelled-by attribute, hidden elements
   * have no name
   */
998
999
1000
1001
1002
  if (gtk_accessible_attribute_set_contains (self->states, GTK_ACCESSIBLE_STATE_HIDDEN))
    {
      value = gtk_accessible_attribute_set_get_value (self->states, GTK_ACCESSIBLE_STATE_HIDDEN);

      if (gtk_boolean_accessible_value_get (value))
1003
        return;
1004
1005
    }

1006
1007
1008
1009
  /* This fallback is in place only for unlabelled elements */
  if (names->len != 0)
    return;

1010
1011
  /* Ignore structural elements, namely: generic containers */
  if (self->accessible != NULL && !is_structural_role (role))
1012
    g_ptr_array_add (names, (char *)G_OBJECT_TYPE_NAME (self->accessible));
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
}

static void
gtk_at_context_get_description_accumulate (GtkATContext *self,
                                           GPtrArray    *labels,
                                           gboolean      recurse)
{
  GtkAccessibleValue *value = NULL;

  if (gtk_accessible_attribute_set_contains (self->properties, GTK_ACCESSIBLE_PROPERTY_DESCRIPTION))
1023
    {
1024
      value = gtk_accessible_attribute_set_get_value (self->properties, GTK_ACCESSIBLE_PROPERTY_DESCRIPTION);
1025

1026
      g_ptr_array_add (labels, (char *) gtk_string_accessible_value_get (value));
1027
1028
    }

1029
  if (recurse && gtk_accessible_attribute_set_contains (self->relations, GTK_ACCESSIBLE_RELATION_DESCRIBED_BY))
1030
    {
1031
      value = gtk_accessible_attribute_set_get_value (self->relations, GTK_ACCESSIBLE_RELATION_DESCRIBED_BY);
1032

1033
      GList *list = gtk_reference_list_accessible_value_get (value);
1034

1035
1036
1037
1038
1039
1040
1041
      for (GList *l = list; l != NULL; l = l->data)
        {
          GtkAccessible *rel = GTK_ACCESSIBLE (l->data);
          GtkATContext *rel_context = gtk_accessible_get_at_context (rel);

          gtk_at_context_get_description_accumulate (rel_context, labels, FALSE);
        }
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
    }

  GtkAccessibleRole role = gtk_at_context_get_accessible_role (self);

  switch ((int) role)
    {
    case GTK_ACCESSIBLE_ROLE_RANGE:
      {
        int range_attrs[] = {
          GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT,
          GTK_ACCESSIBLE_PROPERTY_VALUE_NOW,
        };

1055
        value = NULL;
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
        for (int i = 0; i < G_N_ELEMENTS (range_attrs); i++)
          {
            if (gtk_accessible_attribute_set_contains (self->properties, range_attrs[i]))
              {
                value = gtk_accessible_attribute_set_get_value (self->properties, range_attrs[i]);
                break;
              }
          }

        if (value != NULL)
1066
          g_ptr_array_add (labels, (char *) gtk_string_accessible_value_get (value));
1067
1068
1069
1070
1071
1072
1073
      }
      break;

    default:
      break;
    }

1074
1075
  /* If there is no description or described-by attribute, hidden elements
   * have no description
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
   */
  if (gtk_accessible_attribute_set_contains (self->states, GTK_ACCESSIBLE_STATE_HIDDEN))
    {
      value = gtk_accessible_attribute_set_get_value (self->states, GTK_ACCESSIBLE_STATE_HIDDEN);

      if (gtk_boolean_accessible_value_get (value))
        return;
    }
}

/*< private >
 * gtk_at_context_get_name:
Matthias Clasen's avatar
Matthias Clasen committed
1088
 * @self: a `GtkATContext`
1089
 *
Matthias Clasen's avatar
Matthias Clasen committed
1090
 * Retrieves the accessible name of the `GtkATContext`.
1091
 *
Matthias Clasen's avatar
Matthias Clasen committed
1092
 * This is a convenience function meant to be used by `GtkATContext` implementations.
1093
 *
Matthias Clasen's avatar
Matthias Clasen committed
1094
 * Returns: (transfer full): the label of the `GtkATContext`
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
 */
char *
gtk_at_context_get_name (GtkATContext *self)
{
  g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);

  GPtrArray *names = g_ptr_array_new ();

  gtk_at_context_get_name_accumulate (self, names, TRUE);

  if (names->len == 0)
    {
      g_ptr_array_unref (names);
      return g_strdup ("");
    }

  GString *res = g_string_new ("");
  g_string_append (res, g_ptr_array_index (names, 0));

  for (guint i = 1; i < names->len; i++)
    {
      g_string_append (res, " ");
      g_string_append (res, g_ptr_array_index (names, i));
    }

  g_ptr_array_unref (names);

  return g_string_free (res, FALSE);
}

/*< private >
 * gtk_at_context_get_description:
Matthias Clasen's avatar
Matthias Clasen committed
1127
 * @self: a `GtkATContext`
1128
 *
Matthias Clasen's avatar
Matthias Clasen committed
1129
 * Retrieves the accessible description of the `GtkATContext`.
1130
 *
Matthias Clasen's avatar
Matthias Clasen committed
1131
 * This is a convenience function meant to be used by `GtkATContext` implementations.
1132
 *
Matthias Clasen's avatar
Matthias Clasen committed
1133
 * Returns: (transfer full): the label of the `GtkATContext`
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
 */
char *
gtk_at_context_get_description (GtkATContext *self)
{
  g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);

  GPtrArray *names = g_ptr_array_new ();

  gtk_at_context_get_description_accumulate (self, names, TRUE);

  if (names->len == 0)
    {
      g_ptr_array_unref (names);
      return g_strdup ("");
    }

  GString *res = g_string_new ("");
  g_string_append (res, g_ptr_array_index (names, 0));

  for (guint i = 1; i < names->len; i++)
    {
      g_string_append (res, " ");
      g_string_append (res, g_ptr_array_index (names, i));
    }
1158

1159
  g_ptr_array_unref (names);
1160

1161
  return g_string_free (res, FALSE);
1162
}
1163
1164
1165
1166
1167

void
gtk_at_context_platform_changed (GtkATContext                *self,
                                 GtkAccessiblePlatformChange  change)
{
1168
  gtk_at_context_realize (self);
1169

1170
  GTK_AT_CONTEXT_GET_CLASS (self)->platform_change (self, change);
1171
}
Matthias Clasen's avatar
Matthias Clasen committed
1172
1173
1174
1175

void
gtk_at_context_bounds_changed (GtkATContext *self)
{
1176
1177
1178
  if (!self->realized)
    return;

Matthias Clasen's avatar
Matthias Clasen committed
1179
1180
  GTK_AT_CONTEXT_GET_CLASS (self)->bounds_change (self);
}
1181
1182
1183
1184
1185
1186

void
gtk_at_context_child_changed (GtkATContext             *self,
                              GtkAccessibleChildChange  change,
                              GtkAccessible            *child)
{
1187
1188
1189
  if (!self->realized)
    return;

1190
1191
  GTK_AT_CONTEXT_GET_CLASS (self)->child_change (self, change, child);
}