[gtk/ebassi/for-master: 1/2] a11y: Rework accessible name/description computation




commit 7c7dabae8c4ee5655fe0c9b12b318675b41ac11f
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Fri Oct 16 17:03:50 2020 +0100

    a11y: Rework accessible name/description computation
    
    The ARIA spec determines the name and description of accessible elements
    in a more complex way that simply mapping to a single property; instead,
    it will chain up multiple definitions (if it finds them). For instance,
    let's assume we have a button that saves a file selected from a file
    selection widget; the widgets have the following attributes:
    
     - the file selection widget has a "label" attribute set to the
       selected file, e.g. "Final paper.pdf"
     - the "download" button has a "label" attribute set to the
       "Download" string
     - the "download" button has a "labelled-by" attribute set to
       reference the file selection widget
    
    The ARIA spec says that the accessible name of the "Download" button
    should be computed as "Download Final paper.pdf".
    
    The algorithm defined in section 4.3 of the WAI-ARIA specification
    applies to both accessible names (using the "label" and "labelled-by"
    attributes), and to accessible descriptions (using the "description" and
    "described-by" attributes).

 gtk/a11y/gtkatspicontext.c |  18 +++-
 gtk/gtkatcontext.c         | 212 +++++++++++++++++++++++++++++++++++++++------
 gtk/gtkatcontextprivate.h  |   3 +-
 3 files changed, 204 insertions(+), 29 deletions(-)
---
diff --git a/gtk/a11y/gtkatspicontext.c b/gtk/a11y/gtkatspicontext.c
index 4c3746ba24..1a2434ff7c 100644
--- a/gtk/a11y/gtkatspicontext.c
+++ b/gtk/a11y/gtkatspicontext.c
@@ -606,7 +606,14 @@ handle_accessible_get_property (GDBusConnection       *connection,
     }
   else if (g_strcmp0 (property_name, "Description") == 0)
     {
-      char *label = gtk_at_context_get_label (GTK_AT_CONTEXT (self));
+      char *label = gtk_at_context_get_description (GTK_AT_CONTEXT (self));
+
+      if (label == NULL || *label == '\0')
+        {
+          g_free (label);
+          label = gtk_at_context_get_name (GTK_AT_CONTEXT (self));
+        }
+
       res = g_variant_new_string (label);
       g_free (label);
     }
@@ -932,7 +939,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);
     }
diff --git a/gtk/gtkatcontext.c b/gtk/gtkatcontext.c
index de9dc79275..7bd1398ebb 100644
--- a/gtk/gtkatcontext.c
+++ b/gtk/gtkatcontext.c
@@ -730,47 +730,115 @@ 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->properties, GTK_ACCESSIBLE_PROPERTY_LABEL))
+    {
+      value = gtk_accessible_attribute_set_get_value (self->properties, GTK_ACCESSIBLE_PROPERTY_LABEL);
+
+      g_ptr_array_add (names, (char *) gtk_string_accessible_value_get (value));
+    }
+
+  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);
+
+      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);
+
+  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 (names, (char *) gtk_string_accessible_value_get (value));
+      }
+      break;
+
+    default:
+      break;
+    }
+
+  /* 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 (gtk_boolean_accessible_value_get (value))
-        return g_strdup ("");
+        return;
     }
 
-  if (gtk_accessible_attribute_set_contains (self->properties, GTK_ACCESSIBLE_PROPERTY_LABEL))
+  /* This fallback is in place only for unlabelled elements */
+  if (names->len != 0)
+    return;
+
+  GEnumClass *enum_class = g_type_class_peek (GTK_TYPE_ACCESSIBLE_ROLE);
+  GEnumValue *enum_value = g_enum_get_value (enum_class, role);
+
+  if (enum_value != NULL)
+    g_ptr_array_add (names, (char *) enum_value->value_nick);
+}
+
+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_LABEL);
+      value = gtk_accessible_attribute_set_get_value (self->properties, GTK_ACCESSIBLE_PROPERTY_DESCRIPTION);
 
-      return g_strdup (gtk_string_accessible_value_get (value));
+      g_ptr_array_add (labels, (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_DESCRIBED_BY))
     {
-      value = gtk_accessible_attribute_set_get_value (self->relations, GTK_ACCESSIBLE_RELATION_LABELLED_BY);
+      value = gtk_accessible_attribute_set_get_value (self->relations, GTK_ACCESSIBLE_RELATION_DESCRIBED_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_description_accumulate (rel_context, labels, FALSE);
+        }
     }
 
   GtkAccessibleRole role = gtk_at_context_get_accessible_role (self);
@@ -784,6 +852,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]))
@@ -794,7 +863,7 @@ gtk_at_context_get_label (GtkATContext *self)
           }
 
         if (value != NULL)
-          return g_strdup (gtk_string_accessible_value_get (value));
+          g_ptr_array_add (labels, (char *) gtk_string_accessible_value_get (value));
       }
       break;
 
@@ -802,13 +871,104 @@ gtk_at_context_get_label (GtkATContext *self)
       break;
     }
 
+  /* 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 (gtk_boolean_accessible_value_get (value))
+        return;
+    }
+
+  /* This fallback is in place only for unlabelled elements */
+  if (labels->len != 0)
+    return;
+
   GEnumClass *enum_class = g_type_class_peek (GTK_TYPE_ACCESSIBLE_ROLE);
   GEnumValue *enum_value = g_enum_get_value (enum_class, role);
 
   if (enum_value != NULL)
-    return g_strdup (enum_value->value_nick);
+    g_ptr_array_add (labels, (char *) enum_value->value_nick);
+}
+
+/*< 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_strdup ("widget");
+  return g_string_free (res, FALSE);
 }
 
 void
diff --git a/gtk/gtkatcontextprivate.h b/gtk/gtkatcontextprivate.h
index 4fec1444c8..ae276e1137 100644
--- a/gtk/gtkatcontextprivate.h
+++ b/gtk/gtkatcontextprivate.h
@@ -148,7 +148,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);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]