From a382dfd3bd7d4de1e2285a6d822a3c99506e6f75 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Wed, 8 Jul 2020 16:51:57 +0100 Subject: [PATCH] Add GtkATContext MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ATContext type is meant to be used as the base class for implementations of the assistive technology API—the actual mechanism needed to communicate to components like the screen reader, or any other AT. Every time the widget state changes, the ATContext is meant to broadcast the state change; and every time the AT queries the state of a UI element, the ATContext is meant to provide that information. We also have a "test" ATContext implementation, which is meant to be used to write tests to verify that changes are propagated without requiring a whole desktop session. --- gtk/gtk.h | 1 + gtk/gtkatcontext.c | 285 ++++++++++++++++++++++++++++++++++ gtk/gtkatcontext.h | 39 +++++ gtk/gtkatcontextprivate.h | 69 ++++++++ gtk/gtktestatcontext.c | 73 +++++++++ gtk/gtktestatcontextprivate.h | 47 ++++++ gtk/meson.build | 3 + 7 files changed, 517 insertions(+) create mode 100644 gtk/gtkatcontext.c create mode 100644 gtk/gtkatcontext.h create mode 100644 gtk/gtkatcontextprivate.h create mode 100644 gtk/gtktestatcontext.c create mode 100644 gtk/gtktestatcontextprivate.h diff --git a/gtk/gtk.h b/gtk/gtk.h index 2277e1136d..d56ad2ace7 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkatcontext.c b/gtk/gtkatcontext.c new file mode 100644 index 0000000000..5627160b40 --- /dev/null +++ b/gtk/gtkatcontext.c @@ -0,0 +1,285 @@ +/* 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 . + */ + +/** + * SECTION:gtkatcontext + * @Title: GtkATContext + * @Short_description: An object communicating to Assistive Technologies + * + * GtkATContext is an abstract class provided by GTK to communicate to + * platform-specific assistive technologies API. + * + * Each platform supported by GTK implements a #GtkATContext subclass, and + * is responsible for updating the accessible state in response to state + * changes in #GtkAccessible. + */ + +#include "config.h" + +#include "gtkatcontextprivate.h" + +#include "gtkaccessiblevalueprivate.h" +#include "gtktestatcontextprivate.h" +#include "gtktypebuiltins.h" + +G_DEFINE_ABSTRACT_TYPE (GtkATContext, gtk_at_context, G_TYPE_OBJECT) + +enum +{ + PROP_ACCESSIBLE_ROLE = 1, + PROP_ACCESSIBLE, + + N_PROPS +}; + +static GParamSpec *obj_props[N_PROPS]; + +static void +gtk_at_context_finalize (GObject *gobject) +{ + GtkATContext *self = GTK_AT_CONTEXT (gobject); + + gtk_accessible_state_set_unref (self->states); + + G_OBJECT_CLASS (gtk_at_context_parent_class)->finalize (gobject); +} + +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: + self->accessible_role = g_value_get_enum (value); + break; + + case PROP_ACCESSIBLE: + self->accessible = g_value_get_object (value); + break; + + 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; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +gtk_at_context_real_state_change (GtkATContext *self, + GtkAccessibleStateChange change, + GtkAccessibleStateSet *states) +{ +} + +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; + gobject_class->finalize = gtk_at_context_finalize; + + klass->state_change = gtk_at_context_real_state_change; + + /** + * GtkATContext:accessible-role: + * + * 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, + GTK_ACCESSIBLE_ROLE_WIDGET, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + /** + * GtkATContext:accessible: + * + * The #GtkAccessible that created the #GtkATContext instance. + */ + 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); + + g_object_class_install_properties (gobject_class, N_PROPS, obj_props); +} + +static void +gtk_at_context_init (GtkATContext *self) +{ + self->accessible_role = GTK_ACCESSIBLE_ROLE_WIDGET; + + self->states = gtk_accessible_state_set_new (); +} + +/** + * gtk_at_context_get_accessible: + * @self: a #GtkATContext + * + * Retrieves the #GtkAccessible using this context. + * + * Returns: (transfer none): a #GtkAccessible + */ +GtkAccessible * +gtk_at_context_get_accessible (GtkATContext *self) +{ + g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL); + + return self->accessible; +} + +/** + * gtk_at_context_get_accessible_role: + * @self: a #GtkATContext + * + * Retrieves the accessible role of this context. + * + * Returns: a #GtkAccessibleRole + */ +GtkAccessibleRole +gtk_at_context_get_accessible_role (GtkATContext *self) +{ + g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), GTK_ACCESSIBLE_ROLE_WIDGET); + + return self->accessible_role; +} + +/*< private > + * gtk_at_context_create: + * @accessible_role: the accessible role used by the #GtkATContext + * @accessible: the #GtkAccessible implementation using the #GtkATContext + * + * Creates a new #GtkATContext instance for the given accessible role and + * accessible instance. + * + * The #GtkATContext implementation being instantiated will depend on the + * platform. + * + * Returns: (nullable): the #GtkATContext + */ +GtkATContext * +gtk_at_context_create (GtkAccessibleRole accessible_role, + GtkAccessible *accessible) +{ + static const char *gtk_test_accessible; + static const char *gtk_no_a11y; + + if (G_UNLIKELY (gtk_test_accessible == NULL)) + { + const char *env = g_getenv ("GTK_TEST_ACCESSIBLE"); + + if (env != NULL && *env !='\0') + gtk_test_accessible = "1"; + else + gtk_test_accessible = "0"; + } + + if (G_UNLIKELY (gtk_no_a11y == NULL)) + { + const char *env = g_getenv ("GTK_NO_A11Y"); + + if (env != NULL && *env != '\0') + gtk_no_a11y = "1"; + else + gtk_no_a11y = "0"; + } + + /* Shortcut everything if we're running with the test AT context */ + if (gtk_test_accessible[0] == '1') + return gtk_test_at_context_new (accessible_role, accessible); + + if (gtk_no_a11y[0] == '1') + return NULL; + + /* FIXME: Add GIOExtension for AT contexts */ + return gtk_test_at_context_new (accessible_role, accessible); +} + +/*< private > + * gtk_at_context_update_state: + * @self: a #GtkATContext + * + * Notifies the AT connected to this #GtkATContext that the accessible + * state has changed. + */ +void +gtk_at_context_update_state (GtkATContext *self) +{ + g_return_if_fail (GTK_IS_AT_CONTEXT (self)); + + GtkAccessibleStateChange change = 0; + + for (int i = 0; i < GTK_ACCESSIBLE_STATE_SELECTED; i++) + { + if (gtk_accessible_state_set_contains (self->states, i)) + change |= (1 << i); + } + + GTK_AT_CONTEXT_GET_CLASS (self)->state_change (self, change, self->states); +} + +void +gtk_at_context_set_state (GtkATContext *self, + GtkAccessibleState state, + GtkAccessibleValue *value) +{ + g_return_if_fail (GTK_IS_AT_CONTEXT (self)); + + gtk_accessible_state_set_add (self->states, state, value); +} diff --git a/gtk/gtkatcontext.h b/gtk/gtkatcontext.h new file mode 100644 index 0000000000..e2fca0f564 --- /dev/null +++ b/gtk/gtkatcontext.h @@ -0,0 +1,39 @@ +/* gtkatcontext.h: 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 . + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_AT_CONTEXT (gtk_at_context_get_type()) + +GDK_AVAILABLE_IN_ALL +GDK_DECLARE_INTERNAL_TYPE (GtkATContext, gtk_at_context, GTK, AT_CONTEXT, GObject) + +GDK_AVAILABLE_IN_ALL +GtkAccessible * gtk_at_context_get_accessible (GtkATContext *self); +GDK_AVAILABLE_IN_ALL +GtkAccessibleRole gtk_at_context_get_accessible_role (GtkATContext *self); + +G_END_DECLS diff --git a/gtk/gtkatcontextprivate.h b/gtk/gtkatcontextprivate.h new file mode 100644 index 0000000000..8ee334ca86 --- /dev/null +++ b/gtk/gtkatcontextprivate.h @@ -0,0 +1,69 @@ +/* gtkatcontextprivate.h: Private header for GtkATContext + * + * 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 . + */ + +#pragma once + +#include "gtkatcontext.h" + +#include "gtkaccessiblestatesetprivate.h" + +G_BEGIN_DECLS + +typedef enum { + GTK_ACCESSIBLE_STATE_CHANGE_BUSY = 1 << GTK_ACCESSIBLE_STATE_BUSY, + GTK_ACCESSIBLE_STATE_CHANGE_CHECKED = 1 << GTK_ACCESSIBLE_STATE_CHECKED, + GTK_ACCESSIBLE_STATE_CHANGE_DISABLED = 1 << GTK_ACCESSIBLE_STATE_DISABLED, + GTK_ACCESSIBLE_STATE_CHANGE_EXPANDED = 1 << GTK_ACCESSIBLE_STATE_EXPANDED, + GTK_ACCESSIBLE_STATE_CHANGE_GRABBED = 1 << GTK_ACCESSIBLE_STATE_GRABBED, + GTK_ACCESSIBLE_STATE_CHANGE_HIDDEN = 1 << GTK_ACCESSIBLE_STATE_HIDDEN, + GTK_ACCESSIBLE_STATE_CHANGE_INVALID = 1 << GTK_ACCESSIBLE_STATE_INVALID, + GTK_ACCESSIBLE_STATE_CHANGE_PRESSED = 1 << GTK_ACCESSIBLE_STATE_PRESSED, + GTK_ACCESSIBLE_STATE_CHANGE_SELECTED = 1 << GTK_ACCESSIBLE_STATE_SELECTED +} GtkAccessibleStateChange; + +struct _GtkATContext +{ + GObject parent_instance; + + GtkAccessibleRole accessible_role; + GtkAccessible *accessible; + + GtkAccessibleStateSet *states; +}; + +struct _GtkATContextClass +{ + GObjectClass parent_class; + + void (* state_change) (GtkATContext *self, + GtkAccessibleStateChange changed, + GtkAccessibleStateSet *states); +}; + +GtkATContext * gtk_at_context_create (GtkAccessibleRole accessible_role, + GtkAccessible *accessible); + +void gtk_at_context_update_state (GtkATContext *self); + +void gtk_at_context_set_state (GtkATContext *self, + GtkAccessibleState state, + GtkAccessibleValue *value); + +G_END_DECLS diff --git a/gtk/gtktestatcontext.c b/gtk/gtktestatcontext.c new file mode 100644 index 0000000000..8324d44375 --- /dev/null +++ b/gtk/gtktestatcontext.c @@ -0,0 +1,73 @@ +/* gtktestatcontext.c: Test AT 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 . + */ + +#include "config.h" + +#include "gtktestatcontextprivate.h" + +#include "gtkatcontextprivate.h" +#include "gtkenums.h" + +struct _GtkTestATContext +{ + GtkATContext parent_instance; +}; + +G_DEFINE_TYPE (GtkTestATContext, gtk_test_at_context, GTK_TYPE_AT_CONTEXT) + +static void +gtk_test_at_context_state_change (GtkATContext *self, + GtkAccessibleStateChange change, + GtkAccessibleStateSet *states) +{ + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkAccessibleRole role = gtk_at_context_get_accessible_role (self); + char *state = gtk_accessible_state_set_to_string (states); + + g_print ("Accessible state changed for accessible “%s”, with role %d: %s\n", + G_OBJECT_TYPE_NAME (accessible), + role, + state); + + g_free (state); +} + +static void +gtk_test_at_context_class_init (GtkTestATContextClass *klass) +{ + GtkATContextClass *context_class = GTK_AT_CONTEXT_CLASS (klass); + + context_class->state_change = gtk_test_at_context_state_change; +} + +static void +gtk_test_at_context_init (GtkTestATContext *self) +{ +} + +GtkATContext * +gtk_test_at_context_new (GtkAccessibleRole accessible_role, + GtkAccessible *accessible) +{ + return g_object_new (GTK_TYPE_TEST_AT_CONTEXT, + "accessible-role", accessible_role, + "accessible", accessible, + NULL); +} diff --git a/gtk/gtktestatcontextprivate.h b/gtk/gtktestatcontextprivate.h new file mode 100644 index 0000000000..2b5cc108e3 --- /dev/null +++ b/gtk/gtktestatcontextprivate.h @@ -0,0 +1,47 @@ +/* gtktestatcontextprivate.h: Private test AT 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 . + */ + +#pragma once + +#include "gtkatcontextprivate.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_TEST_AT_CONTEXT (gtk_test_at_context_get_type()) + +G_DECLARE_FINAL_TYPE (GtkTestATContext, gtk_test_at_context, GTK, TEST_AT_CONTEXT, GtkATContext) + +GtkATContext * +gtk_test_at_context_new (GtkAccessibleRole accessible_role, + GtkAccessible *accessible); + +void +gtk_test_at_context_assert_role (GtkTestATContext *self, + GtkAccessibleRole role); + +void +gtk_test_at_context_assert_state_added (GtkTestATContext *self, + GtkAccessibleState state); + +void +gtk_test_at_context_assert_state_removed (GtkTestATContext *self, + GtkAccessibleState state); + +G_END_DECLS diff --git a/gtk/meson.build b/gtk/meson.build index a53425b027..eb0bd421e6 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -139,6 +139,7 @@ gtk_private_sources = files([ 'gtkstyleanimation.c', 'gtkstylecascade.c', 'gtkstyleproperty.c', + 'gtktestatcontext.c', 'gtktextbtree.c', 'gtktexthistory.c', 'gtktextviewchild.c', @@ -164,6 +165,7 @@ gtk_public_sources = files([ 'gtkapplicationwindow.c', 'gtkaspectframe.c', 'gtkassistant.c', + 'gtkatcontext.c', 'gtkbinlayout.c', 'gtkbitset.c', 'gtkboolfilter.c', @@ -452,6 +454,7 @@ gtk_public_headers = files([ 'gtkapplicationwindow.h', 'gtkaspectframe.h', 'gtkassistant.h', + 'gtkatcontext.h', 'gtkbinlayout.h', 'gtkbitset.h', 'gtkbookmarklist.h',