1/* gtkatcontext.c: Assistive technology context
2 *
3 * Copyright 2020 GNOME Foundation
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
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, see <http://www.gnu.org/licenses/>.
19 */
20
21/**
22 * GtkATContext:
23 *
24 * `GtkATContext` is an abstract class provided by GTK to communicate to
25 * platform-specific assistive technologies API.
26 *
27 * Each platform supported by GTK implements a `GtkATContext` subclass, and
28 * is responsible for updating the accessible state in response to state
29 * changes in `GtkAccessible`.
30 */
31
32#include "config.h"
33
34#include "gtkatcontextprivate.h"
35
36#include "gtkaccessiblevalueprivate.h"
37#include "gtkaccessibleprivate.h"
38#include "gtkdebug.h"
39#include "gtktestatcontextprivate.h"
40#include "gtktypebuiltins.h"
41
42#if defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND)
43#include "a11y/gtkatspicontextprivate.h"
44#endif
45
46G_DEFINE_ABSTRACT_TYPE (GtkATContext, gtk_at_context, G_TYPE_OBJECT)
47
48enum
49{
50 PROP_ACCESSIBLE_ROLE = 1,
51 PROP_ACCESSIBLE,
52 PROP_DISPLAY,
53
54 N_PROPS
55};
56
57enum
58{
59 STATE_CHANGE,
60
61 LAST_SIGNAL
62};
63
64static GParamSpec *obj_props[N_PROPS];
65
66static guint obj_signals[LAST_SIGNAL];
67
68static void
69gtk_at_context_finalize (GObject *gobject)
70{
71 GtkATContext *self = GTK_AT_CONTEXT (ptr: gobject);
72
73 gtk_accessible_attribute_set_unref (self: self->properties);
74 gtk_accessible_attribute_set_unref (self: self->relations);
75 gtk_accessible_attribute_set_unref (self: self->states);
76
77 G_OBJECT_CLASS (gtk_at_context_parent_class)->finalize (gobject);
78}
79
80static void
81gtk_at_context_dispose (GObject *gobject)
82{
83 GtkATContext *self = GTK_AT_CONTEXT (ptr: gobject);
84
85 gtk_at_context_unrealize (self);
86
87 G_OBJECT_CLASS (gtk_at_context_parent_class)->dispose (gobject);
88}
89
90static void
91gtk_at_context_set_property (GObject *gobject,
92 guint prop_id,
93 const GValue *value,
94 GParamSpec *pspec)
95{
96 GtkATContext *self = GTK_AT_CONTEXT (ptr: gobject);
97
98 switch (prop_id)
99 {
100 case PROP_ACCESSIBLE_ROLE:
101 if (!self->realized)
102 self->accessible_role = g_value_get_enum (value);
103 else
104 g_critical ("The accessible role cannot be set on a realized AT context");
105 break;
106
107 case PROP_ACCESSIBLE:
108 self->accessible = g_value_get_object (value);
109 break;
110
111 case PROP_DISPLAY:
112 gtk_at_context_set_display (self, display: g_value_get_object (value));
113 break;
114
115 default:
116 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
117 }
118}
119
120static void
121gtk_at_context_get_property (GObject *gobject,
122 guint prop_id,
123 GValue *value,
124 GParamSpec *pspec)
125{
126 GtkATContext *self = GTK_AT_CONTEXT (ptr: gobject);
127
128 switch (prop_id)
129 {
130 case PROP_ACCESSIBLE_ROLE:
131 g_value_set_enum (value, v_enum: self->accessible_role);
132 break;
133
134 case PROP_ACCESSIBLE:
135 g_value_set_object (value, v_object: self->accessible);
136 break;
137
138 case PROP_DISPLAY:
139 g_value_set_object (value, v_object: self->display);
140 break;
141
142 default:
143 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
144 }
145}
146
147static void
148gtk_at_context_real_state_change (GtkATContext *self,
149 GtkAccessibleStateChange changed_states,
150 GtkAccessiblePropertyChange changed_properties,
151 GtkAccessibleRelationChange changed_relations,
152 GtkAccessibleAttributeSet *states,
153 GtkAccessibleAttributeSet *properties,
154 GtkAccessibleAttributeSet *relations)
155{
156}
157
158static void
159gtk_at_context_real_platform_change (GtkATContext *self,
160 GtkAccessiblePlatformChange change)
161{
162}
163
164static void
165gtk_at_context_real_bounds_change (GtkATContext *self)
166{
167}
168
169static void
170gtk_at_context_real_child_change (GtkATContext *self,
171 GtkAccessibleChildChange change,
172 GtkAccessible *child)
173{
174}
175
176static void
177gtk_at_context_real_realize (GtkATContext *self)
178{
179}
180
181static void
182gtk_at_context_real_unrealize (GtkATContext *self)
183{
184}
185
186static void
187gtk_at_context_class_init (GtkATContextClass *klass)
188{
189 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
190
191 gobject_class->set_property = gtk_at_context_set_property;
192 gobject_class->get_property = gtk_at_context_get_property;
193 gobject_class->dispose = gtk_at_context_dispose;
194 gobject_class->finalize = gtk_at_context_finalize;
195
196 klass->realize = gtk_at_context_real_realize;
197 klass->unrealize = gtk_at_context_real_unrealize;
198 klass->state_change = gtk_at_context_real_state_change;
199 klass->platform_change = gtk_at_context_real_platform_change;
200 klass->bounds_change = gtk_at_context_real_bounds_change;
201 klass->child_change = gtk_at_context_real_child_change;
202
203 /**
204 * GtkATContext:accessible-role: (attributes org.gtk.Property.get=gtk_at_context_get_accessible_role)
205 *
206 * The accessible role used by the AT context.
207 *
208 * Depending on the given role, different states and properties can be
209 * set or retrieved.
210 */
211 obj_props[PROP_ACCESSIBLE_ROLE] =
212 g_param_spec_enum (name: "accessible-role",
213 nick: "Accessible Role",
214 blurb: "The accessible role of the AT context",
215 enum_type: GTK_TYPE_ACCESSIBLE_ROLE,
216 default_value: GTK_ACCESSIBLE_ROLE_NONE,
217 flags: G_PARAM_READWRITE |
218 G_PARAM_CONSTRUCT |
219 G_PARAM_STATIC_STRINGS);
220
221 /**
222 * GtkATContext:accessible: (attributes org.gtk.Property.get=gtk_at_context_get_accessible)
223 *
224 * The `GtkAccessible` that created the `GtkATContext` instance.
225 */
226 obj_props[PROP_ACCESSIBLE] =
227 g_param_spec_object (name: "accessible",
228 nick: "Accessible",
229 blurb: "The accessible implementation",
230 GTK_TYPE_ACCESSIBLE,
231 flags: G_PARAM_READWRITE |
232 G_PARAM_CONSTRUCT_ONLY |
233 G_PARAM_STATIC_STRINGS);
234
235 /**
236 * GtkATContext:display:
237 *
238 * The `GdkDisplay` for the `GtkATContext`.
239 */
240 obj_props[PROP_DISPLAY] =
241 g_param_spec_object (name: "display",
242 nick: "Display",
243 blurb: "The display connection",
244 GDK_TYPE_DISPLAY,
245 flags: G_PARAM_READWRITE |
246 G_PARAM_STATIC_STRINGS |
247 G_PARAM_EXPLICIT_NOTIFY);
248
249 /**
250 * GtkATContext::state-change:
251 * @self: the `GtkATContext`
252 *
253 * Emitted when the attributes of the accessible for the
254 * `GtkATContext` instance change.
255 */
256 obj_signals[STATE_CHANGE] =
257 g_signal_new (signal_name: "state-change",
258 G_TYPE_FROM_CLASS (gobject_class),
259 signal_flags: G_SIGNAL_RUN_FIRST,
260 class_offset: 0,
261 NULL, NULL,
262 NULL,
263 G_TYPE_NONE, n_params: 0);
264
265 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: obj_props);
266}
267
268#define N_PROPERTIES (GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT + 1)
269#define N_RELATIONS (GTK_ACCESSIBLE_RELATION_SET_SIZE + 1)
270#define N_STATES (GTK_ACCESSIBLE_STATE_SELECTED + 1)
271
272static const char *property_attrs[] = {
273 [GTK_ACCESSIBLE_PROPERTY_AUTOCOMPLETE] = "autocomplete",
274 [GTK_ACCESSIBLE_PROPERTY_DESCRIPTION] = "description",
275 [GTK_ACCESSIBLE_PROPERTY_HAS_POPUP] = "haspopup",
276 [GTK_ACCESSIBLE_PROPERTY_KEY_SHORTCUTS] = "keyshortcuts",
277 [GTK_ACCESSIBLE_PROPERTY_LABEL] = "label",
278 [GTK_ACCESSIBLE_PROPERTY_LEVEL] = "level",
279 [GTK_ACCESSIBLE_PROPERTY_MODAL] = "modal",
280 [GTK_ACCESSIBLE_PROPERTY_MULTI_LINE] = "multiline",
281 [GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE] = "multiselectable",
282 [GTK_ACCESSIBLE_PROPERTY_ORIENTATION] = "orientation",
283 [GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER] = "placeholder",
284 [GTK_ACCESSIBLE_PROPERTY_READ_ONLY] = "readonly",
285 [GTK_ACCESSIBLE_PROPERTY_REQUIRED] = "required",
286 [GTK_ACCESSIBLE_PROPERTY_ROLE_DESCRIPTION] = "roledescription",
287 [GTK_ACCESSIBLE_PROPERTY_SORT] = "sort",
288 [GTK_ACCESSIBLE_PROPERTY_VALUE_MAX] = "valuemax",
289 [GTK_ACCESSIBLE_PROPERTY_VALUE_MIN] = "valuemin",
290 [GTK_ACCESSIBLE_PROPERTY_VALUE_NOW] = "valuenow",
291 [GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT] = "valuetext",
292};
293
294/*< private >
295 * gtk_accessible_property_get_attribute_name:
296 * @property: a `GtkAccessibleProperty`
297 *
298 * Retrieves the name of a `GtkAccessibleProperty`.
299 *
300 * Returns: (transfer none): the name of the accessible property
301 */
302const char *
303gtk_accessible_property_get_attribute_name (GtkAccessibleProperty property)
304{
305 g_return_val_if_fail (property >= GTK_ACCESSIBLE_PROPERTY_AUTOCOMPLETE &&
306 property <= GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT,
307 "<none>");
308
309 return property_attrs[property];
310}
311
312static const char *relation_attrs[] = {
313 [GTK_ACCESSIBLE_RELATION_ACTIVE_DESCENDANT] = "activedescendant",
314 [GTK_ACCESSIBLE_RELATION_COL_COUNT] = "colcount",
315 [GTK_ACCESSIBLE_RELATION_COL_INDEX] = "colindex",
316 [GTK_ACCESSIBLE_RELATION_COL_INDEX_TEXT] = "colindextext",
317 [GTK_ACCESSIBLE_RELATION_COL_SPAN] = "colspan",
318 [GTK_ACCESSIBLE_RELATION_CONTROLS] = "controls",
319 [GTK_ACCESSIBLE_RELATION_DESCRIBED_BY] = "describedby",
320 [GTK_ACCESSIBLE_RELATION_DETAILS] = "details",
321 [GTK_ACCESSIBLE_RELATION_ERROR_MESSAGE] = "errormessage",
322 [GTK_ACCESSIBLE_RELATION_FLOW_TO] = "flowto",
323 [GTK_ACCESSIBLE_RELATION_LABELLED_BY] = "labelledby",
324 [GTK_ACCESSIBLE_RELATION_OWNS] = "owns",
325 [GTK_ACCESSIBLE_RELATION_POS_IN_SET] = "posinset",
326 [GTK_ACCESSIBLE_RELATION_ROW_COUNT] = "rowcount",
327 [GTK_ACCESSIBLE_RELATION_ROW_INDEX] = "rowindex",
328 [GTK_ACCESSIBLE_RELATION_ROW_INDEX_TEXT] = "rowindextext",
329 [GTK_ACCESSIBLE_RELATION_ROW_SPAN] = "rowspan",
330 [GTK_ACCESSIBLE_RELATION_SET_SIZE] = "setsize",
331};
332
333/*< private >
334 * gtk_accessible_relation_get_attribute_name:
335 * @relation: a `GtkAccessibleRelation`
336 *
337 * Retrieves the name of a `GtkAccessibleRelation`.
338 *
339 * Returns: (transfer none): the name of the accessible relation
340 */
341const char *
342gtk_accessible_relation_get_attribute_name (GtkAccessibleRelation relation)
343{
344 g_return_val_if_fail (relation >= GTK_ACCESSIBLE_RELATION_ACTIVE_DESCENDANT &&
345 relation <= GTK_ACCESSIBLE_RELATION_SET_SIZE,
346 "<none>");
347
348 return relation_attrs[relation];
349}
350
351static const char *state_attrs[] = {
352 [GTK_ACCESSIBLE_STATE_BUSY] = "busy",
353 [GTK_ACCESSIBLE_STATE_CHECKED] = "checked",
354 [GTK_ACCESSIBLE_STATE_DISABLED] = "disabled",
355 [GTK_ACCESSIBLE_STATE_EXPANDED] = "expanded",
356 [GTK_ACCESSIBLE_STATE_HIDDEN] = "hidden",
357 [GTK_ACCESSIBLE_STATE_INVALID] = "invalid",
358 [GTK_ACCESSIBLE_STATE_PRESSED] = "pressed",
359 [GTK_ACCESSIBLE_STATE_SELECTED] = "selected",
360};
361
362/*< private >
363 * gtk_accessible_state_get_attribute_name:
364 * @state: a `GtkAccessibleState`
365 *
366 * Retrieves the name of a `GtkAccessibleState`.
367 *
368 * Returns: (transfer none): the name of the accessible state
369 */
370const char *
371gtk_accessible_state_get_attribute_name (GtkAccessibleState state)
372{
373 g_return_val_if_fail (state >= GTK_ACCESSIBLE_STATE_BUSY &&
374 state <= GTK_ACCESSIBLE_STATE_SELECTED,
375 "<none>");
376
377 return state_attrs[state];
378}
379
380static void
381gtk_at_context_init (GtkATContext *self)
382{
383 self->accessible_role = GTK_ACCESSIBLE_ROLE_NONE;
384
385 self->properties =
386 gtk_accessible_attribute_set_new (G_N_ELEMENTS (property_attrs),
387 name_func: (GtkAccessibleAttributeNameFunc) gtk_accessible_property_get_attribute_name,
388 default_func: (GtkAccessibleAttributeDefaultFunc) gtk_accessible_value_get_default_for_property);
389 self->relations =
390 gtk_accessible_attribute_set_new (G_N_ELEMENTS (relation_attrs),
391 name_func: (GtkAccessibleAttributeNameFunc) gtk_accessible_relation_get_attribute_name,
392 default_func: (GtkAccessibleAttributeDefaultFunc) gtk_accessible_value_get_default_for_relation);
393 self->states =
394 gtk_accessible_attribute_set_new (G_N_ELEMENTS (state_attrs),
395 name_func: (GtkAccessibleAttributeNameFunc) gtk_accessible_state_get_attribute_name,
396 default_func: (GtkAccessibleAttributeDefaultFunc) gtk_accessible_value_get_default_for_state);
397}
398
399/**
400 * gtk_at_context_get_accessible: (attributes org.gtk.Method.get_property=accessible)
401 * @self: a `GtkATContext`
402 *
403 * Retrieves the `GtkAccessible` using this context.
404 *
405 * Returns: (transfer none): a `GtkAccessible`
406 */
407GtkAccessible *
408gtk_at_context_get_accessible (GtkATContext *self)
409{
410 g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
411
412 return self->accessible;
413}
414
415/*< private >
416 * gtk_at_context_set_accessible_role:
417 * @self: a `GtkATContext`
418 * @role: the accessible role for the context
419 *
420 * Sets the accessible role for the given `GtkATContext`.
421 *
422 * This function can only be called if the `GtkATContext` is unrealized.
423 */
424void
425gtk_at_context_set_accessible_role (GtkATContext *self,
426 GtkAccessibleRole role)
427{
428 g_return_if_fail (GTK_IS_AT_CONTEXT (self));
429 g_return_if_fail (!self->realized);
430
431 if (self->accessible_role == role)
432 return;
433
434 self->accessible_role = role;
435
436 g_object_notify_by_pspec (G_OBJECT (self), pspec: obj_props[PROP_ACCESSIBLE_ROLE]);
437}
438
439/**
440 * gtk_at_context_get_accessible_role: (attributes org.gtk.Method.get_property=accessible-role)
441 * @self: a `GtkATContext`
442 *
443 * Retrieves the accessible role of this context.
444 *
445 * Returns: a `GtkAccessibleRole`
446 */
447GtkAccessibleRole
448gtk_at_context_get_accessible_role (GtkATContext *self)
449{
450 g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), GTK_ACCESSIBLE_ROLE_NONE);
451
452 return self->accessible_role;
453}
454
455/*< private >
456 * gtk_at_context_set_display:
457 * @self: a `GtkATContext`
458 * @display: a `GdkDisplay`
459 *
460 * Sets the `GdkDisplay` used by the `GtkATContext`.
461 *
462 * This function can only be called if the `GtkATContext` is
463 * not realized.
464 */
465void
466gtk_at_context_set_display (GtkATContext *self,
467 GdkDisplay *display)
468{
469 g_return_if_fail (GTK_IS_AT_CONTEXT (self));
470 g_return_if_fail (display == NULL || GDK_IS_DISPLAY (display));
471
472 if (self->display == display)
473 return;
474
475 if (self->realized)
476 return;
477
478 self->display = display;
479
480 g_object_notify_by_pspec (G_OBJECT (self), pspec: obj_props[PROP_DISPLAY]);
481}
482
483/*< private >
484 * gtk_at_context_get_display:
485 * @self: a `GtkATContext`
486 *
487 * Retrieves the `GdkDisplay` used to create the context.
488 *
489 * Returns: (transfer none): a `GdkDisplay`
490 */
491GdkDisplay *
492gtk_at_context_get_display (GtkATContext *self)
493{
494 g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
495
496 return self->display;
497}
498
499static const struct {
500 const char *name;
501 const char *env_name;
502 GtkATContext * (* create_context) (GtkAccessibleRole accessible_role,
503 GtkAccessible *accessible,
504 GdkDisplay *display);
505} a11y_backends[] = {
506#if defined(GDK_WINDOWING_WAYLAND)
507 { "AT-SPI (Wayland)", "atspi", gtk_at_spi_create_context },
508#endif
509#if defined(GDK_WINDOWING_X11)
510 { "AT-SPI (X11)", "atspi", gtk_at_spi_create_context },
511#endif
512 { "Test", "test", gtk_test_at_context_new },
513 { NULL, NULL, NULL },
514};
515
516/**
517 * gtk_at_context_create: (constructor)
518 * @accessible_role: the accessible role used by the `GtkATContext`
519 * @accessible: the `GtkAccessible` implementation using the `GtkATContext`
520 * @display: the `GdkDisplay` used by the `GtkATContext`
521 *
522 * Creates a new `GtkATContext` instance for the given accessible role,
523 * accessible instance, and display connection.
524 *
525 * The `GtkATContext` implementation being instantiated will depend on the
526 * platform.
527 *
528 * Returns: (nullable) (transfer full): the `GtkATContext`
529 */
530GtkATContext *
531gtk_at_context_create (GtkAccessibleRole accessible_role,
532 GtkAccessible *accessible,
533 GdkDisplay *display)
534{
535 static const char *gtk_a11y_env;
536
537 if (gtk_a11y_env == NULL)
538 {
539 gtk_a11y_env = g_getenv (variable: "GTK_A11Y");
540 if (gtk_a11y_env == NULL)
541 gtk_a11y_env = "0";
542
543 if (g_ascii_strcasecmp (s1: gtk_a11y_env, s2: "help") == 0)
544 {
545 g_print (format: "Supported arguments for GTK_A11Y environment variable:\n");
546
547#if defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND)
548 g_print (format: " atspi - Use the AT-SPI accessibility backend\n");
549#endif
550 g_print (format: " test - Use the test accessibility backend\n");
551 g_print (format: " none - Disable the accessibility backend\n");
552 g_print (format: " help - Print this help\n\n");
553 g_print (format: "Other arguments will cause a warning and be ignored.\n");
554
555 gtk_a11y_env = "0";
556 }
557 }
558
559 /* Short-circuit disabling the accessibility support */
560 if (g_ascii_strcasecmp (s1: gtk_a11y_env, s2: "none") == 0)
561 return NULL;
562
563 GtkATContext *res = NULL;
564
565 for (guint i = 0; i < G_N_ELEMENTS (a11y_backends); i++)
566 {
567 if (a11y_backends[i].name == NULL)
568 break;
569
570 if (a11y_backends[i].create_context != NULL &&
571 (*gtk_a11y_env == '0' || g_ascii_strcasecmp (s1: a11y_backends[i].env_name, s2: gtk_a11y_env) == 0))
572 {
573 res = a11y_backends[i].create_context (accessible_role, accessible, display);
574 if (res != NULL)
575 break;
576 }
577 }
578
579 if (*gtk_a11y_env != '0' && res == NULL)
580 g_warning ("Unrecognized accessibility backend \"%s\". Try GTK_A11Y=help", gtk_a11y_env);
581
582 /* Fall back to the test context, so we can get debugging data */
583 if (res == NULL)
584 res = g_object_new (GTK_TYPE_TEST_AT_CONTEXT,
585 first_property_name: "accessible_role", accessible_role,
586 "accessible", accessible,
587 "display", display,
588 NULL);
589
590 return res;
591}
592
593/*< private >
594 * gtk_at_context_clone: (constructor)
595 * @self: the `GtkATContext` to clone
596 * @role: the accessible role of the clone, or %GTK_ACCESSIBLE_ROLE_NONE to
597 * use the same accessible role of @self
598 * @accessible: (nullable): the accessible creating the context, or %NULL to
599 * use the same `GtkAccessible` of @self
600 * @display: (nullable): the display connection, or %NULL to use the same
601 * `GdkDisplay` of @self
602 *
603 * Clones the state of the given `GtkATContext`, using @role, @accessible,
604 * and @display.
605 *
606 * If @self is realized, the returned `GtkATContext` will also be realized.
607 *
608 * Returns: (transfer full): the newly created `GtkATContext`
609 */
610GtkATContext *
611gtk_at_context_clone (GtkATContext *self,
612 GtkAccessibleRole role,
613 GtkAccessible *accessible,
614 GdkDisplay *display)
615{
616 g_return_val_if_fail (self == NULL || GTK_IS_AT_CONTEXT (self), NULL);
617 g_return_val_if_fail (accessible == NULL || GTK_IS_ACCESSIBLE (accessible), NULL);
618 g_return_val_if_fail (display == NULL || GDK_IS_DISPLAY (display), NULL);
619
620 if (self != NULL && role == GTK_ACCESSIBLE_ROLE_NONE)
621 role = self->accessible_role;
622
623 if (self != NULL && accessible == NULL)
624 accessible = self->accessible;
625
626 if (self != NULL && display == NULL)
627 display = self->display;
628
629 GtkATContext *res = gtk_at_context_create (accessible_role: role, accessible, display);
630
631 if (self != NULL)
632 {
633 g_clear_pointer (&res->states, gtk_accessible_attribute_set_unref);
634 g_clear_pointer (&res->properties, gtk_accessible_attribute_set_unref);
635 g_clear_pointer (&res->relations, gtk_accessible_attribute_set_unref);
636
637 res->states = gtk_accessible_attribute_set_ref (self: self->states);
638 res->properties = gtk_accessible_attribute_set_ref (self: self->properties);
639 res->relations = gtk_accessible_attribute_set_ref (self: self->relations);
640
641 if (self->realized)
642 gtk_at_context_realize (self: res);
643 }
644
645 return res;
646}
647
648gboolean
649gtk_at_context_is_realized (GtkATContext *self)
650{
651 return self->realized;
652}
653
654void
655gtk_at_context_realize (GtkATContext *self)
656{
657 if (self->realized)
658 return;
659
660 GTK_NOTE (A11Y, g_message ("Realizing AT context '%s'", G_OBJECT_TYPE_NAME (self)));
661 GTK_AT_CONTEXT_GET_CLASS (ptr: self)->realize (self);
662
663 self->realized = TRUE;
664}
665
666void
667gtk_at_context_unrealize (GtkATContext *self)
668{
669 if (!self->realized)
670 return;
671
672 GTK_NOTE (A11Y, g_message ("Unrealizing AT context '%s'", G_OBJECT_TYPE_NAME (self)));
673 GTK_AT_CONTEXT_GET_CLASS (ptr: self)->unrealize (self);
674
675 self->realized = FALSE;
676}
677
678/*< private >
679 * gtk_at_context_update:
680 * @self: a `GtkATContext`
681 *
682 * Notifies the AT connected to this `GtkATContext` that the accessible
683 * state and its properties have changed.
684 */
685void
686gtk_at_context_update (GtkATContext *self)
687{
688 g_return_if_fail (GTK_IS_AT_CONTEXT (self));
689
690 if (!self->realized)
691 return;
692
693 /* There's no point in notifying of state changes if there weren't any */
694 if (self->updated_properties == 0 &&
695 self->updated_relations == 0 &&
696 self->updated_states == 0)
697 return;
698
699 GTK_AT_CONTEXT_GET_CLASS (ptr: self)->state_change (self,
700 self->updated_states, self->updated_properties, self->updated_relations,
701 self->states, self->properties, self->relations);
702 g_signal_emit (instance: self, signal_id: obj_signals[STATE_CHANGE], detail: 0);
703
704 self->updated_properties = 0;
705 self->updated_relations = 0;
706 self->updated_states = 0;
707}
708
709/*< private >
710 * gtk_at_context_set_accessible_state:
711 * @self: a `GtkATContext`
712 * @state: a `GtkAccessibleState`
713 * @value: (nullable): `GtkAccessibleValue`
714 *
715 * Sets the @value for the given @state of a `GtkATContext`.
716 *
717 * If @value is %NULL, the state is unset.
718 *
719 * This function will accumulate state changes until gtk_at_context_update()
720 * is called.
721 */
722void
723gtk_at_context_set_accessible_state (GtkATContext *self,
724 GtkAccessibleState state,
725 GtkAccessibleValue *value)
726{
727 g_return_if_fail (GTK_IS_AT_CONTEXT (self));
728
729 gboolean res = FALSE;
730
731 if (value != NULL)
732 res = gtk_accessible_attribute_set_add (self: self->states, attribute: state, value);
733 else
734 res = gtk_accessible_attribute_set_remove (self: self->states, state);
735
736 if (res)
737 self->updated_states |= (1 << state);
738}
739
740/*< private >
741 * gtk_at_context_has_accessible_state:
742 * @self: a `GtkATContext`
743 * @state: a `GtkAccessibleState`
744 *
745 * Checks whether a `GtkATContext` has the given @state set.
746 *
747 * Returns: %TRUE, if the accessible state is set
748 */
749gboolean
750gtk_at_context_has_accessible_state (GtkATContext *self,
751 GtkAccessibleState state)
752{
753 g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), FALSE);
754
755 return gtk_accessible_attribute_set_contains (self: self->states, state);
756}
757
758/*< private >
759 * gtk_at_context_get_accessible_state:
760 * @self: a `GtkATContext`
761 * @state: a `GtkAccessibleState`
762 *
763 * Retrieves the value for the accessible state of a `GtkATContext`.
764 *
765 * Returns: (transfer none): the value for the given state
766 */
767GtkAccessibleValue *
768gtk_at_context_get_accessible_state (GtkATContext *self,
769 GtkAccessibleState state)
770{
771 g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
772
773 return gtk_accessible_attribute_set_get_value (self: self->states, state);
774}
775
776/*< private >
777 * gtk_at_context_set_accessible_property:
778 * @self: a `GtkATContext`
779 * @property: a `GtkAccessibleProperty`
780 * @value: (nullable): `GtkAccessibleValue`
781 *
782 * Sets the @value for the given @property of a `GtkATContext`.
783 *
784 * If @value is %NULL, the property is unset.
785 *
786 * This function will accumulate property changes until gtk_at_context_update()
787 * is called.
788 */
789void
790gtk_at_context_set_accessible_property (GtkATContext *self,
791 GtkAccessibleProperty property,
792 GtkAccessibleValue *value)
793{
794 g_return_if_fail (GTK_IS_AT_CONTEXT (self));
795
796 gboolean res = FALSE;
797
798 if (value != NULL)
799 res = gtk_accessible_attribute_set_add (self: self->properties, attribute: property, value);
800 else
801 res = gtk_accessible_attribute_set_remove (self: self->properties, state: property);
802
803 if (res)
804 self->updated_properties |= (1 << property);
805}
806
807/*< private >
808 * gtk_at_context_has_accessible_property:
809 * @self: a `GtkATContext`
810 * @property: a `GtkAccessibleProperty`
811 *
812 * Checks whether a `GtkATContext` has the given @property set.
813 *
814 * Returns: %TRUE, if the accessible property is set
815 */
816gboolean
817gtk_at_context_has_accessible_property (GtkATContext *self,
818 GtkAccessibleProperty property)
819{
820 g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), FALSE);
821
822 return gtk_accessible_attribute_set_contains (self: self->properties, state: property);
823}
824
825/*< private >
826 * gtk_at_context_get_accessible_property:
827 * @self: a `GtkATContext`
828 * @property: a `GtkAccessibleProperty`
829 *
830 * Retrieves the value for the accessible property of a `GtkATContext`.
831 *
832 * Returns: (transfer none): the value for the given property
833 */
834GtkAccessibleValue *
835gtk_at_context_get_accessible_property (GtkATContext *self,
836 GtkAccessibleProperty property)
837{
838 g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
839
840 return gtk_accessible_attribute_set_get_value (self: self->properties, state: property);
841}
842
843/*< private >
844 * gtk_at_context_set_accessible_relation:
845 * @self: a `GtkATContext`
846 * @relation: a `GtkAccessibleRelation`
847 * @value: (nullable): `GtkAccessibleValue`
848 *
849 * Sets the @value for the given @relation of a `GtkATContext`.
850 *
851 * If @value is %NULL, the relation is unset.
852 *
853 * This function will accumulate relation changes until gtk_at_context_update()
854 * is called.
855 */
856void
857gtk_at_context_set_accessible_relation (GtkATContext *self,
858 GtkAccessibleRelation relation,
859 GtkAccessibleValue *value)
860{
861 g_return_if_fail (GTK_IS_AT_CONTEXT (self));
862
863 gboolean res = FALSE;
864
865 if (value != NULL)
866 res = gtk_accessible_attribute_set_add (self: self->relations, attribute: relation, value);
867 else
868 res = gtk_accessible_attribute_set_remove (self: self->relations, state: relation);
869
870 if (res)
871 self->updated_relations |= (1 << relation);
872}
873
874/*< private >
875 * gtk_at_context_has_accessible_relation:
876 * @self: a `GtkATContext`
877 * @relation: a `GtkAccessibleRelation`
878 *
879 * Checks whether a `GtkATContext` has the given @relation set.
880 *
881 * Returns: %TRUE, if the accessible relation is set
882 */
883gboolean
884gtk_at_context_has_accessible_relation (GtkATContext *self,
885 GtkAccessibleRelation relation)
886{
887 g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), FALSE);
888
889 return gtk_accessible_attribute_set_contains (self: self->relations, state: relation);
890}
891
892/*< private >
893 * gtk_at_context_get_accessible_relation:
894 * @self: a `GtkATContext`
895 * @relation: a `GtkAccessibleRelation`
896 *
897 * Retrieves the value for the accessible relation of a `GtkATContext`.
898 *
899 * Returns: (transfer none): the value for the given relation
900 */
901GtkAccessibleValue *
902gtk_at_context_get_accessible_relation (GtkATContext *self,
903 GtkAccessibleRelation relation)
904{
905 g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
906
907 return gtk_accessible_attribute_set_get_value (self: self->relations, state: relation);
908}
909
910static gboolean
911is_structural_role (GtkAccessibleRole role)
912{
913 /* Keep the switch small while avoiding the compiler warning for
914 * unhandled enumeration values
915 */
916 switch ((int) role)
917 {
918 case GTK_ACCESSIBLE_ROLE_FORM:
919 case GTK_ACCESSIBLE_ROLE_GROUP:
920 case GTK_ACCESSIBLE_ROLE_GENERIC:
921 case GTK_ACCESSIBLE_ROLE_LANDMARK:
922 case GTK_ACCESSIBLE_ROLE_LIST_ITEM:
923 case GTK_ACCESSIBLE_ROLE_REGION:
924 case GTK_ACCESSIBLE_ROLE_SEARCH:
925 case GTK_ACCESSIBLE_ROLE_SEPARATOR:
926 return TRUE;
927
928 default:
929 break;
930 }
931
932 return FALSE;
933}
934
935/* See the WAI-ARIA ยง 4.3, "Accessible Name and Description Computation" */
936static void
937gtk_at_context_get_name_accumulate (GtkATContext *self,
938 GPtrArray *names,
939 gboolean recurse)
940{
941 GtkAccessibleValue *value = NULL;
942
943 if (gtk_accessible_attribute_set_contains (self: self->properties, state: GTK_ACCESSIBLE_PROPERTY_LABEL))
944 {
945 value = gtk_accessible_attribute_set_get_value (self: self->properties, state: GTK_ACCESSIBLE_PROPERTY_LABEL);
946
947 g_ptr_array_add (array: names, data: (char *) gtk_string_accessible_value_get (value));
948 }
949
950 if (recurse && gtk_accessible_attribute_set_contains (self: self->relations, state: GTK_ACCESSIBLE_RELATION_LABELLED_BY))
951 {
952 value = gtk_accessible_attribute_set_get_value (self: self->relations, state: GTK_ACCESSIBLE_RELATION_LABELLED_BY);
953
954 GList *list = gtk_reference_list_accessible_value_get (value);
955
956 for (GList *l = list; l != NULL; l = l->next)
957 {
958 GtkAccessible *rel = GTK_ACCESSIBLE (ptr: l->data);
959 GtkATContext *rel_context = gtk_accessible_get_at_context (self: rel);
960
961 gtk_at_context_get_name_accumulate (self: rel_context, names, FALSE);
962 }
963 }
964
965 GtkAccessibleRole role = gtk_at_context_get_accessible_role (self);
966
967 switch ((int) role)
968 {
969 case GTK_ACCESSIBLE_ROLE_RANGE:
970 {
971 int range_attrs[] = {
972 GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT,
973 GTK_ACCESSIBLE_PROPERTY_VALUE_NOW,
974 };
975
976 value = NULL;
977 for (int i = 0; i < G_N_ELEMENTS (range_attrs); i++)
978 {
979 if (gtk_accessible_attribute_set_contains (self: self->properties, state: range_attrs[i]))
980 {
981 value = gtk_accessible_attribute_set_get_value (self: self->properties, state: range_attrs[i]);
982 break;
983 }
984 }
985
986 if (value != NULL)
987 g_ptr_array_add (array: names, data: (char *) gtk_string_accessible_value_get (value));
988 }
989 break;
990
991 default:
992 break;
993 }
994
995 /* If there is no label or labelled-by attribute, hidden elements
996 * have no name
997 */
998 if (gtk_accessible_attribute_set_contains (self: self->states, state: GTK_ACCESSIBLE_STATE_HIDDEN))
999 {
1000 value = gtk_accessible_attribute_set_get_value (self: self->states, state: GTK_ACCESSIBLE_STATE_HIDDEN);
1001
1002 if (gtk_boolean_accessible_value_get (value))
1003 return;
1004 }
1005
1006 /* This fallback is in place only for unlabelled elements */
1007 if (names->len != 0)
1008 return;
1009
1010 /* Ignore structural elements, namely: generic containers */
1011 if (self->accessible != NULL && !is_structural_role (role))
1012 g_ptr_array_add (array: names, data: (char *)G_OBJECT_TYPE_NAME (self->accessible));
1013}
1014
1015static void
1016gtk_at_context_get_description_accumulate (GtkATContext *self,
1017 GPtrArray *labels,
1018 gboolean recurse)
1019{
1020 GtkAccessibleValue *value = NULL;
1021
1022 if (gtk_accessible_attribute_set_contains (self: self->properties, state: GTK_ACCESSIBLE_PROPERTY_DESCRIPTION))
1023 {
1024 value = gtk_accessible_attribute_set_get_value (self: self->properties, state: GTK_ACCESSIBLE_PROPERTY_DESCRIPTION);
1025
1026 g_ptr_array_add (array: labels, data: (char *) gtk_string_accessible_value_get (value));
1027 }
1028
1029 if (recurse && gtk_accessible_attribute_set_contains (self: self->relations, state: GTK_ACCESSIBLE_RELATION_DESCRIBED_BY))
1030 {
1031 value = gtk_accessible_attribute_set_get_value (self: self->relations, state: GTK_ACCESSIBLE_RELATION_DESCRIBED_BY);
1032
1033 GList *list = gtk_reference_list_accessible_value_get (value);
1034
1035 for (GList *l = list; l != NULL; l = l->next)
1036 {
1037 GtkAccessible *rel = GTK_ACCESSIBLE (ptr: l->data);
1038 GtkATContext *rel_context = gtk_accessible_get_at_context (self: rel);
1039
1040 gtk_at_context_get_description_accumulate (self: rel_context, labels, FALSE);
1041 }
1042 }
1043
1044 GtkAccessibleRole role = gtk_at_context_get_accessible_role (self);
1045
1046 switch ((int) role)
1047 {
1048 case GTK_ACCESSIBLE_ROLE_RANGE:
1049 {
1050 int range_attrs[] = {
1051 GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT,
1052 GTK_ACCESSIBLE_PROPERTY_VALUE_NOW,
1053 };
1054
1055 value = NULL;
1056 for (int i = 0; i < G_N_ELEMENTS (range_attrs); i++)
1057 {
1058 if (gtk_accessible_attribute_set_contains (self: self->properties, state: range_attrs[i]))
1059 {
1060 value = gtk_accessible_attribute_set_get_value (self: self->properties, state: range_attrs[i]);
1061 break;
1062 }
1063 }
1064
1065 if (value != NULL)
1066 g_ptr_array_add (array: labels, data: (char *) gtk_string_accessible_value_get (value));
1067 }
1068 break;
1069
1070 default:
1071 break;
1072 }
1073
1074 /* If there is no description or described-by attribute, hidden elements
1075 * have no description
1076 */
1077 if (gtk_accessible_attribute_set_contains (self: self->states, state: GTK_ACCESSIBLE_STATE_HIDDEN))
1078 {
1079 value = gtk_accessible_attribute_set_get_value (self: self->states, state: GTK_ACCESSIBLE_STATE_HIDDEN);
1080
1081 if (gtk_boolean_accessible_value_get (value))
1082 return;
1083 }
1084}
1085
1086/*< private >
1087 * gtk_at_context_get_name:
1088 * @self: a `GtkATContext`
1089 *
1090 * Retrieves the accessible name of the `GtkATContext`.
1091 *
1092 * This is a convenience function meant to be used by `GtkATContext` implementations.
1093 *
1094 * Returns: (transfer full): the label of the `GtkATContext`
1095 */
1096char *
1097gtk_at_context_get_name (GtkATContext *self)
1098{
1099 g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
1100
1101 GPtrArray *names = g_ptr_array_new ();
1102
1103 gtk_at_context_get_name_accumulate (self, names, TRUE);
1104
1105 if (names->len == 0)
1106 {
1107 g_ptr_array_unref (array: names);
1108 return g_strdup (str: "");
1109 }
1110
1111 GString *res = g_string_new (init: "");
1112 g_string_append (string: res, g_ptr_array_index (names, 0));
1113
1114 for (guint i = 1; i < names->len; i++)
1115 {
1116 g_string_append (string: res, val: " ");
1117 g_string_append (string: res, g_ptr_array_index (names, i));
1118 }
1119
1120 g_ptr_array_unref (array: names);
1121
1122 return g_string_free (string: res, FALSE);
1123}
1124
1125/*< private >
1126 * gtk_at_context_get_description:
1127 * @self: a `GtkATContext`
1128 *
1129 * Retrieves the accessible description of the `GtkATContext`.
1130 *
1131 * This is a convenience function meant to be used by `GtkATContext` implementations.
1132 *
1133 * Returns: (transfer full): the label of the `GtkATContext`
1134 */
1135char *
1136gtk_at_context_get_description (GtkATContext *self)
1137{
1138 g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
1139
1140 GPtrArray *names = g_ptr_array_new ();
1141
1142 gtk_at_context_get_description_accumulate (self, labels: names, TRUE);
1143
1144 if (names->len == 0)
1145 {
1146 g_ptr_array_unref (array: names);
1147 return g_strdup (str: "");
1148 }
1149
1150 GString *res = g_string_new (init: "");
1151 g_string_append (string: res, g_ptr_array_index (names, 0));
1152
1153 for (guint i = 1; i < names->len; i++)
1154 {
1155 g_string_append (string: res, val: " ");
1156 g_string_append (string: res, g_ptr_array_index (names, i));
1157 }
1158
1159 g_ptr_array_unref (array: names);
1160
1161 return g_string_free (string: res, FALSE);
1162}
1163
1164void
1165gtk_at_context_platform_changed (GtkATContext *self,
1166 GtkAccessiblePlatformChange change)
1167{
1168 gtk_at_context_realize (self);
1169
1170 GTK_AT_CONTEXT_GET_CLASS (ptr: self)->platform_change (self, change);
1171}
1172
1173void
1174gtk_at_context_bounds_changed (GtkATContext *self)
1175{
1176 if (!self->realized)
1177 return;
1178
1179 GTK_AT_CONTEXT_GET_CLASS (ptr: self)->bounds_change (self);
1180}
1181
1182void
1183gtk_at_context_child_changed (GtkATContext *self,
1184 GtkAccessibleChildChange change,
1185 GtkAccessible *child)
1186{
1187 if (!self->realized)
1188 return;
1189
1190 GTK_AT_CONTEXT_GET_CLASS (ptr: self)->child_change (self, change, child);
1191}
1192

source code of gtk/gtk/gtkatcontext.c