[gtk/a11y/buildable: 2/3] a11y: Add an "accessibility" section to the builder XML tree




commit 9e4316bf96cf53d56eac441541906c51a470c4d1
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Wed Oct 21 13:24:22 2020 +0100

    a11y: Add an "accessibility" section to the builder XML tree
    
    Accessible attributes are not GObject properties. This means that we
    need a custom parser for setting attributes in our UI description files.
    
    The new section is defined as a sub-tree with the `<accessibility>`
    element at its root, and elements for each type of accessible
    attributes, i.e. properties, relations, and states:
    
    ```xml
      <object class="..." id="...">
        <accessibility>
          <property name="label">The accessible label</property>
          <state name="pressed">false</state>
          <relation name="labelled-by">label1</relation>
        </accessibility>
      </object>
    ```
    
    The name of the attribute is the enumeration value; the value is defined
    by the WAI-ARIA specification.

 gtk/gtkwidget.c | 327 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 327 insertions(+)
---
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 49a16937cb..126712b9c8 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -8446,6 +8446,161 @@ static const GtkBuildableParser layout_parser =
     layout_text,
   };
 
+typedef struct
+{
+  char *name;
+  GString *value;
+  char *context;
+  gboolean translatable;
+} AccessibilityAttributeInfo;
+
+typedef struct
+{
+  GObject *object;
+  GtkBuilder *builder;
+
+  AccessibilityAttributeInfo *cur_attribute;
+
+  /* SList<AccessibilityAttributeInfo> */
+  GSList *properties;
+  GSList *states;
+  GSList *relations;
+} AccessibilityParserData;
+
+static void
+accessibility_attribute_info_free (gpointer data)
+{
+  AccessibilityAttributeInfo *pinfo = data;
+
+  if (pinfo == NULL)
+    return;
+
+  g_free (pinfo->name);
+  g_free (pinfo->context);
+  g_string_free (pinfo->value, TRUE);
+  g_free (pinfo);
+}
+
+static void
+accessibility_start_element (GtkBuildableParseContext  *context,
+                             const char                *element_name,
+                             const char               **names,
+                             const char               **values,
+                             gpointer                   user_data,
+                             GError                   **error)
+{
+  AccessibilityParserData *accessibility_data = user_data;
+
+  if (strcmp (element_name, "property") == 0 ||
+      strcmp (element_name, "relation") == 0 ||
+      strcmp (element_name, "state") == 0)
+    {
+      const char *name = NULL;
+      const char *ctx = NULL;
+      gboolean translatable = FALSE;
+      AccessibilityAttributeInfo *pinfo;
+
+      if (!_gtk_builder_check_parent (accessibility_data->builder,
+                                      context,
+                                      "accessibility",
+                                      error))
+        return;
+
+      if (!g_markup_collect_attributes (element_name, names, values, error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_BOOLEAN | G_MARKUP_COLLECT_OPTIONAL, 
"translatable", &translatable,
+                                        G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "context", &ctx,
+                                        G_MARKUP_COLLECT_INVALID))
+        {
+          _gtk_builder_prefix_error (accessibility_data->builder, context, error);
+          return;
+        }
+
+      pinfo = g_new0 (AccessibilityAttributeInfo, 1);
+      pinfo->name = g_strdup (name);
+      pinfo->translatable = translatable;
+      pinfo->context = g_strdup (ctx);
+      pinfo->value = g_string_new (NULL);
+
+      accessibility_data->cur_attribute = pinfo;
+    }
+  else if (strcmp (element_name, "accessibility") == 0)
+    {
+      if (!_gtk_builder_check_parent (accessibility_data->builder,
+                                      context,
+                                      "object",
+                                      error))
+        return;
+    }
+  else
+    {
+      _gtk_builder_error_unhandled_tag (accessibility_data->builder, context,
+                                        "GtkWidget", element_name,
+                                        error);
+    }
+}
+
+static void
+accessibility_text (GtkBuildableParseContext  *context,
+                    const char                *text,
+                    gsize                      text_len,
+                    gpointer                   user_data,
+                    GError                   **error)
+{
+  AccessibilityParserData *accessibility_data = user_data;
+
+  if (accessibility_data->cur_attribute != NULL)
+    g_string_append_len (accessibility_data->cur_attribute->value, text, text_len);
+}
+
+static void
+accessibility_end_element (GtkBuildableParseContext  *context,
+                           const char                *element_name,
+                           gpointer                   user_data,
+                           GError                   **error)
+{
+  AccessibilityParserData *accessibility_data = user_data;
+
+  if (accessibility_data->cur_attribute != NULL)
+    {
+      AccessibilityAttributeInfo *pinfo = g_steal_pointer (&accessibility_data->cur_attribute);
+
+      /* Translate the string, if needed */
+      if (pinfo->value->len != 0 && pinfo->translatable)
+        {
+          const char *translated;
+          const char *domain;
+
+          domain = gtk_builder_get_translation_domain (accessibility_data->builder);
+
+          translated = _gtk_builder_parser_translate (domain, pinfo->context, pinfo->value->str);
+
+          g_string_assign (pinfo->value, translated);
+        }
+
+      /* We assign all properties at the end of the `accessibility` section */
+      if (strcmp (element_name, "property") == 0)
+        accessibility_data->properties = g_slist_prepend (accessibility_data->properties, pinfo);
+      else if (strcmp (element_name, "relation") == 0)
+        accessibility_data->relations = g_slist_prepend (accessibility_data->relations, pinfo);
+      else if (strcmp (element_name, "state") == 0)
+        accessibility_data->states = g_slist_prepend (accessibility_data->states, pinfo);
+      else
+        {
+          _gtk_builder_error_unhandled_tag (accessibility_data->builder, context,
+                                            "GtkWidget", element_name,
+                                            error);
+          accessibility_attribute_info_free (pinfo);
+        }
+    }
+}
+
+static const GtkBuildableParser accessibility_parser = {
+  accessibility_start_element,
+  accessibility_end_element,
+  accessibility_text,
+};
+
 static gboolean
 gtk_widget_buildable_custom_tag_start (GtkBuildable       *buildable,
                                        GtkBuilder         *builder,
@@ -8481,6 +8636,20 @@ gtk_widget_buildable_custom_tag_start (GtkBuildable       *buildable,
       return TRUE;
     }
 
+  if (strcmp (tagname, "accessibility") == 0)
+    {
+      AccessibilityParserData *data;
+
+      data = g_slice_new0 (AccessibilityParserData);
+      data->builder = builder;
+      data->object = (GObject *) g_object_ref (buildable);
+
+      *parser = accessibility_parser;
+      *parser_data = data;
+
+      return TRUE;
+    }
+
   return FALSE;
 }
 
@@ -8559,6 +8728,148 @@ gtk_widget_buildable_finish_layout_properties (GtkWidget *widget,
   g_slist_free_full (layout_properties, layout_property_info_free);
 }
 
+static void
+gtk_widget_buildable_finish_accessibility_properties (GtkWidget *widget,
+                                                      gpointer   data)
+{
+  AccessibilityParserData *accessibility_data = data;
+  GSList *attributes, *l;
+  GtkATContext *context;
+
+  context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (widget));
+  if (context == NULL)
+    return;
+
+  attributes = g_slist_reverse (accessibility_data->properties);
+  accessibility_data->properties = NULL;
+
+  for (l = attributes; l != NULL; l = l->next)
+    {
+      AccessibilityAttributeInfo *pinfo = l->data;
+      int property;
+      GError *error = NULL;
+      GtkAccessibleValue *value;
+
+      _gtk_builder_enum_from_string (GTK_TYPE_ACCESSIBLE_PROPERTY,
+                                     pinfo->name,
+                                     &property,
+                                     &error);
+      if (error != NULL)
+        {
+          g_warning ("Failed to find accessible property “%s”: %s",
+                     pinfo->name,
+                     error->message);
+          g_error_free (error);
+          continue;
+        }
+
+      value = gtk_accessible_value_parse_for_property (property,
+                                                       pinfo->value->str,
+                                                       pinfo->value->len,
+                                                       &error);
+      if (error != NULL)
+        {
+          g_warning ("Failed to set accessible property “%s” to “%s”: %s",
+                     pinfo->name,
+                     pinfo->value->str,
+                     error->message);
+          g_error_free (error);
+          continue;
+        }
+
+      gtk_at_context_set_accessible_property (context, property, value);
+      gtk_accessible_value_unref (value);
+    }
+
+  g_slist_free_full (attributes, accessibility_attribute_info_free);
+
+  attributes = g_slist_reverse (accessibility_data->relations);
+  accessibility_data->relations = NULL;
+
+  for (l = attributes; l != NULL; l = l->next)
+    {
+      AccessibilityAttributeInfo *pinfo = l->data;
+      int relation;
+      GError *error = NULL;
+      GtkAccessibleValue *value;
+
+      _gtk_builder_enum_from_string (GTK_TYPE_ACCESSIBLE_RELATION,
+                                     pinfo->name,
+                                     &relation,
+                                     &error);
+      if (error != NULL)
+        {
+          g_warning ("Failed to find accessible relation “%s”: %s",
+                     pinfo->name,
+                     error->message);
+          g_error_free (error);
+          continue;
+        }
+
+      value = gtk_accessible_value_parse_for_relation (relation,
+                                                       pinfo->value->str,
+                                                       pinfo->value->len,
+                                                       &error);
+      if (error != NULL)
+        {
+          g_warning ("Failed to set accessible relation “%s” to “%s”: %s",
+                     pinfo->name,
+                     pinfo->value->str,
+                     error->message);
+          g_error_free (error);
+          continue;
+        }
+
+      gtk_at_context_set_accessible_relation (context, relation, value);
+      gtk_accessible_value_unref (value);
+    }
+
+  g_slist_free_full (attributes, accessibility_attribute_info_free);
+
+  attributes = g_slist_reverse (accessibility_data->states);
+  accessibility_data->states = NULL;
+
+  for (l = attributes; l != NULL; l = l->next)
+    {
+      AccessibilityAttributeInfo *pinfo = l->data;
+      int state;
+      GError *error = NULL;
+      GtkAccessibleValue *value;
+
+      _gtk_builder_enum_from_string (GTK_TYPE_ACCESSIBLE_STATE,
+                                     pinfo->name,
+                                     &state,
+                                     &error);
+      if (error != NULL)
+        {
+          g_warning ("Failed to find accessible state “%s”: %s",
+                     pinfo->name,
+                     error->message);
+          g_error_free (error);
+          continue;
+        }
+
+      value = gtk_accessible_value_parse_for_state (state,
+                                                    pinfo->value->str,
+                                                    pinfo->value->len,
+                                                    &error);
+      if (error != NULL)
+        {
+          g_warning ("Failed to set accessible state “%s” to “%s”: %s",
+                     pinfo->name,
+                     pinfo->value->str,
+                     error->message);
+          g_error_free (error);
+          continue;
+        }
+
+      gtk_at_context_set_accessible_state (context, state, value);
+      gtk_accessible_value_unref (value);
+    }
+
+  g_slist_free_full (attributes, accessibility_attribute_info_free);
+}
+
 static void
 gtk_widget_buildable_custom_finished (GtkBuildable *buildable,
                                       GtkBuilder   *builder,
@@ -8592,6 +8903,22 @@ gtk_widget_buildable_custom_finished (GtkBuildable *buildable,
       g_object_unref (layout_data->object);
       g_slice_free (LayoutParserData, layout_data);
     }
+  else if (strcmp (tagname, "accessibility") == 0)
+    {
+      AccessibilityParserData *accessibility_data = user_data;
+
+      gtk_widget_buildable_finish_accessibility_properties (GTK_WIDGET (buildable),
+                                                            accessibility_data);
+
+      g_slist_free_full (accessibility_data->properties,
+                         accessibility_attribute_info_free);
+      g_slist_free_full (accessibility_data->relations,
+                         accessibility_attribute_info_free);
+      g_slist_free_full (accessibility_data->states,
+                         accessibility_attribute_info_free);
+      g_object_unref (accessibility_data->object);
+      g_slice_free (AccessibilityParserData, accessibility_data);
+    }
 }
 
 static GtkSizeRequestMode


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