Add GtkATContext

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.
This commit is contained in:
Emmanuele Bassi 2020-07-08 16:51:57 +01:00
parent 823ee58332
commit a382dfd3bd
7 changed files with 517 additions and 0 deletions

View File

@ -46,6 +46,7 @@
#include <gtk/gtkapplicationwindow.h> #include <gtk/gtkapplicationwindow.h>
#include <gtk/gtkaspectframe.h> #include <gtk/gtkaspectframe.h>
#include <gtk/gtkassistant.h> #include <gtk/gtkassistant.h>
#include <gtk/gtkatcontext.h>
#include <gtk/gtkbinlayout.h> #include <gtk/gtkbinlayout.h>
#include <gtk/gtkbitset.h> #include <gtk/gtkbitset.h>
#include <gtk/gtkbookmarklist.h> #include <gtk/gtkbookmarklist.h>

285
gtk/gtkatcontext.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* 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);
}

39
gtk/gtkatcontext.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <gtk/gtktypes.h>
#include <gtk/gtkenums.h>
#include <gtk/gtkaccessible.h>
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

69
gtk/gtkatcontextprivate.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

73
gtk/gtktestatcontext.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -139,6 +139,7 @@ gtk_private_sources = files([
'gtkstyleanimation.c', 'gtkstyleanimation.c',
'gtkstylecascade.c', 'gtkstylecascade.c',
'gtkstyleproperty.c', 'gtkstyleproperty.c',
'gtktestatcontext.c',
'gtktextbtree.c', 'gtktextbtree.c',
'gtktexthistory.c', 'gtktexthistory.c',
'gtktextviewchild.c', 'gtktextviewchild.c',
@ -164,6 +165,7 @@ gtk_public_sources = files([
'gtkapplicationwindow.c', 'gtkapplicationwindow.c',
'gtkaspectframe.c', 'gtkaspectframe.c',
'gtkassistant.c', 'gtkassistant.c',
'gtkatcontext.c',
'gtkbinlayout.c', 'gtkbinlayout.c',
'gtkbitset.c', 'gtkbitset.c',
'gtkboolfilter.c', 'gtkboolfilter.c',
@ -452,6 +454,7 @@ gtk_public_headers = files([
'gtkapplicationwindow.h', 'gtkapplicationwindow.h',
'gtkaspectframe.h', 'gtkaspectframe.h',
'gtkassistant.h', 'gtkassistant.h',
'gtkatcontext.h',
'gtkbinlayout.h', 'gtkbinlayout.h',
'gtkbitset.h', 'gtkbitset.h',
'gtkbookmarklist.h', 'gtkbookmarklist.h',