Merge branch 'ebassi/for-master' into 'master'

Ebassi/for master

See merge request GNOME/gtk!2720
This commit is contained in:
Emmanuele Bassi 2020-10-19 20:26:57 +00:00
commit 51f5690ae3
5 changed files with 267 additions and 48 deletions

View File

@ -220,3 +220,71 @@ which acts as a proxy to the specific platform's accessibility API:
Additionally, an ad hoc accessibility backend is available for the GTK
testsuite, to ensure reproducibility of issues in the CI pipeline.
## Authoring practices {#authoring-practices}
The authoring practices are aimed at application developers, as well as
developers of GUI elements based on GTK.
Functionally, #GtkAccessible roles, states, properties, and relations are
analogous to a CSS for assistive technologies. For screen reader users, for
instance, the various accessible attributes control the rendering of their
non-visual experience. Incorrect roles and attributes may result in a
completely inaccessible user interface.
### A role is a promise
The following code:
```c
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_BUTTON);
```
is a promise that the widget being created will provide the same keyboard
interactions expected for a button. An accessible role of a button will not
turn automatically any widget into a #GtkButton; but if your widget behaves
like a button, using the %GTK_ACCESSIBLE_ROLE_BUTTON will allow any
assistive technology to handle it like they would a #GtkButton.
### Attributes can both hide and enhance
Accessible attributes can be used to override the content of a UI element,
for instance:
```c
gtk_label_set_text (GTK_LABEL (label), "Some text");
gtk_accessible_update_property (GTK_ACCESSIBLE (label),
GTK_ACCESSIBLE_PROPERTY_LABEL,
"Assistive technologies users will perceive "
"this text, not the contents of the label",
-1);
```
In the example above, the "label" property will override the contents of the
label widget.
The attributes can also enhance the UI:
```c
gtk_button_set_label (GTK_BUTTON (button), "Download");
gtk_box_append (GTK_BOX (button), button);
gtk_label_set_text (GTK_LABEL (label), "Final report.pdf");
gtk_box_append (GTK_BOX (box), label);
gtk_accessible_update_relation (GTK_ACCESSIBLE (button),
GTK_ACCESSIBLE_RELATION_LABELLED_BY,
g_list_append (NULL, label),
-1);
```
In the example above, an assistive technology will read the button's
accessible label as "Download Final report.pdf".
The power of hiding and enhancing can be a double-edged sword, as it can
lead to inadvertently overriding the accessible semantics of existing
widgets.
## Design patterns and custom widgets
...

View File

@ -593,22 +593,14 @@ handle_accessible_get_property (GDBusConnection *connection,
if (g_strcmp0 (property_name, "Name") == 0)
{
if (GTK_IS_WIDGET (accessible))
res = g_variant_new_string (gtk_widget_get_name (GTK_WIDGET (accessible)));
else if (GTK_IS_STACK_PAGE (accessible))
{
const char *name = gtk_stack_page_get_name (GTK_STACK_PAGE (accessible));
if (name == NULL)
name = G_OBJECT_TYPE_NAME (accessible);
res = g_variant_new_string (name);
}
else
res = g_variant_new_string (G_OBJECT_TYPE_NAME (accessible));
char *label = gtk_at_context_get_name (GTK_AT_CONTEXT (self));
res = g_variant_new_string (label ? label : "");
g_free (label);
}
else if (g_strcmp0 (property_name, "Description") == 0)
{
char *label = gtk_at_context_get_label (GTK_AT_CONTEXT (self));
res = g_variant_new_string (label);
char *label = gtk_at_context_get_description (GTK_AT_CONTEXT (self));
res = g_variant_new_string (label ? label : "");
g_free (label);
}
else if (g_strcmp0 (property_name, "Locale") == 0)
@ -953,7 +945,14 @@ gtk_at_spi_context_state_change (GtkATContext *ctx,
if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_LABEL)
{
char *label = gtk_at_context_get_label (GTK_AT_CONTEXT (self));
char *label = gtk_at_context_get_name (GTK_AT_CONTEXT (self));
GVariant *v = g_variant_new_take_string (label);
emit_property_changed (self, "accessible-name", v);
}
if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_DESCRIPTION)
{
char *label = gtk_at_context_get_description (GTK_AT_CONTEXT (self));
GVariant *v = g_variant_new_take_string (label);
emit_property_changed (self, "accessible-description", v);
}

View File

@ -739,47 +739,34 @@ gtk_at_context_get_accessible_relation (GtkATContext *self,
return gtk_accessible_attribute_set_get_value (self->relations, relation);
}
/*< private >
* gtk_at_context_get_label:
* @self: a #GtkATContext
*
* Retrieves the accessible label of the #GtkATContext.
*
* This is a convenience function meant to be used by #GtkATContext implementations.
*
* Returns: (transfer full): the label of the #GtkATContext
*/
char *
gtk_at_context_get_label (GtkATContext *self)
/* 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)
{
g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
GtkAccessibleValue *value = NULL;
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 g_strdup ("");
}
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);
return g_strdup (gtk_string_accessible_value_get (value));
g_ptr_array_add (names, (char *) gtk_string_accessible_value_get (value));
}
if (gtk_accessible_attribute_set_contains (self->relations, GTK_ACCESSIBLE_RELATION_LABELLED_BY))
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);
GtkAccessible *rel = GTK_ACCESSIBLE (list->data);
GtkATContext *rel_context = gtk_accessible_get_at_context (rel);
return gtk_at_context_get_label (rel_context);
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_name_accumulate (rel_context, names, FALSE);
}
}
GtkAccessibleRole role = gtk_at_context_get_accessible_role (self);
@ -793,6 +780,7 @@ gtk_at_context_get_label (GtkATContext *self)
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]))
@ -803,7 +791,7 @@ gtk_at_context_get_label (GtkATContext *self)
}
if (value != NULL)
return g_strdup (gtk_string_accessible_value_get (value));
g_ptr_array_add (names, (char *) gtk_string_accessible_value_get (value));
}
break;
@ -811,13 +799,172 @@ gtk_at_context_get_label (GtkATContext *self)
break;
}
GEnumClass *enum_class = g_type_class_peek (GTK_TYPE_ACCESSIBLE_ROLE);
GEnumValue *enum_value = g_enum_get_value (enum_class, role);
/* If there is no label or labelled-by attribute, hidden elements
* have no name
*/
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 (enum_value != NULL)
return g_strdup (enum_value->value_nick);
if (gtk_boolean_accessible_value_get (value))
return;
}
return g_strdup ("widget");
/* This fallback is in place only for unlabelled elements */
if (names->len != 0)
return;
if (self->accessible)
g_ptr_array_add (names, (char *)G_OBJECT_TYPE_NAME (self->accessible));
}
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))
{
value = gtk_accessible_attribute_set_get_value (self->properties, GTK_ACCESSIBLE_PROPERTY_DESCRIPTION);
g_ptr_array_add (labels, (char *) gtk_string_accessible_value_get (value));
}
if (recurse && gtk_accessible_attribute_set_contains (self->relations, GTK_ACCESSIBLE_RELATION_DESCRIBED_BY))
{
value = gtk_accessible_attribute_set_get_value (self->relations, GTK_ACCESSIBLE_RELATION_DESCRIBED_BY);
GList *list = gtk_reference_list_accessible_value_get (value);
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);
}
}
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 (labels, (char *) gtk_string_accessible_value_get (value));
}
break;
default:
break;
}
/* If there is no description or described-by attribute, hidden elements
* have no description
*/
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:
* @self: a #GtkATContext
*
* Retrieves the accessible name of the #GtkATContext.
*
* This is a convenience function meant to be used by #GtkATContext implementations.
*
* Returns: (transfer full): the label of the #GtkATContext
*/
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:
* @self: a #GtkATContext
*
* Retrieves the accessible description of the #GtkATContext.
*
* This is a convenience function meant to be used by #GtkATContext implementations.
*
* Returns: (transfer full): the label of the #GtkATContext
*/
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));
}
g_ptr_array_unref (names);
return g_string_free (res, FALSE);
}
void

View File

@ -152,7 +152,8 @@ gboolean gtk_at_context_has_accessible_relation (GtkATContext
GtkAccessibleValue * gtk_at_context_get_accessible_relation (GtkATContext *self,
GtkAccessibleRelation relation);
char * gtk_at_context_get_label (GtkATContext *self);
char * gtk_at_context_get_name (GtkATContext *self);
char * gtk_at_context_get_description (GtkATContext *self);
void gtk_at_context_platform_changed (GtkATContext *self,
GtkAccessiblePlatformChange change);

View File

@ -1696,6 +1696,10 @@ gtk_label_set_text_internal (GtkLabel *self,
g_free (self->text);
self->text = str;
gtk_accessible_update_property (GTK_ACCESSIBLE (self),
GTK_ACCESSIBLE_PROPERTY_LABEL, str,
-1);
gtk_label_select_region_index (self, 0, 0);
}