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',