Commit 0c49f734 authored by Matthias Clasen's avatar Matthias Clasen

Merge branch 'matthiasc/a11y-buildable' into 'master'

Implement accessible attributes for ui files

See merge request !2751
parents 08004d4e c783e9aa
Pipeline #224920 passed with stages
in 16 minutes and 16 seconds
......@@ -45,10 +45,13 @@
#include "gtkaccessiblevalueprivate.h"
#include "gtkaccessible.h"
#include "gtkbuilderprivate.h"
#include "gtkenums.h"
#include "gtktypebuiltins.h"
#include <math.h>
#include <float.h>
#include <errno.h>
G_DEFINE_QUARK (gtk-accessible-value-error-quark, gtk_accessible_value_error)
......@@ -637,6 +640,7 @@ typedef struct {
*/
GCallback ctor;
GCallback getter;
GCallback parser;
} GtkAccessibleCollect;
static const GtkAccessibleCollect collect_states[] = {
......@@ -670,7 +674,8 @@ static const GtkAccessibleCollect collect_states[] = {
.ctype = GTK_ACCESSIBLE_COLLECT_TOKEN,
.name = "invalid",
.ctor = (GCallback) gtk_invalid_accessible_value_new,
.getter = (GCallback) gtk_invalid_accessible_value_get
.getter = (GCallback) gtk_invalid_accessible_value_get,
.parser = (GCallback) gtk_invalid_accessible_value_parse,
},
[GTK_ACCESSIBLE_STATE_PRESSED] = {
.value = GTK_ACCESSIBLE_STATE_PRESSED,
......@@ -691,7 +696,8 @@ static const GtkAccessibleCollect collect_props[] = {
.ctype = GTK_ACCESSIBLE_COLLECT_TOKEN,
.name = "autocomplete",
.ctor = (GCallback) gtk_autocomplete_accessible_value_new,
.getter = (GCallback) gtk_autocomplete_accessible_value_get
.getter = (GCallback) gtk_autocomplete_accessible_value_get,
.parser = (GCallback) gtk_autocomplete_accessible_value_parse,
},
[GTK_ACCESSIBLE_PROPERTY_DESCRIPTION] = {
.value = GTK_ACCESSIBLE_PROPERTY_DESCRIPTION,
......@@ -742,7 +748,8 @@ static const GtkAccessibleCollect collect_props[] = {
.ctype = GTK_ACCESSIBLE_COLLECT_TOKEN | GTK_ACCESSIBLE_COLLECT_UNDEFINED,
.name = "orientation",
.ctor = (GCallback) gtk_orientation_accessible_value_new,
.getter = (GCallback) gtk_orientation_accessible_value_get
.getter = (GCallback) gtk_orientation_accessible_value_get,
.parser = (GCallback) gtk_orientation_accessible_value_parse,
},
[GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER] = {
.value = GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER,
......@@ -769,7 +776,8 @@ static const GtkAccessibleCollect collect_props[] = {
.ctype = GTK_ACCESSIBLE_COLLECT_TOKEN,
.name = "sort",
.ctor = (GCallback) gtk_sort_accessible_value_new,
.getter = (GCallback) gtk_sort_accessible_value_get
.getter = (GCallback) gtk_sort_accessible_value_get,
.parser = (GCallback) gtk_sort_accessible_value_parse,
},
[GTK_ACCESSIBLE_PROPERTY_VALUE_MAX] = {
.value = GTK_ACCESSIBLE_PROPERTY_VALUE_MAX,
......@@ -896,6 +904,10 @@ typedef GtkAccessibleValue * (* GtkAccessibleValueStringCtor) (const char *val
typedef GtkAccessibleValue * (* GtkAccessibleValueRefCtor) (GtkAccessible *value);
typedef GtkAccessibleValue * (* GtkAccessibleValueRefListCtor) (GList *value);
typedef GtkAccessibleValue * (* GtkAccessibleValueEnumParser) (const char *str,
gsize len,
GError **error);
/*< private >
* gtk_accessible_value_get_default_for_state:
* @state: a #GtkAccessibleState
......@@ -1314,6 +1326,128 @@ gtk_accessible_value_collect_value (const GtkAccessibleCollect *cstate,
return res;
}
static GtkAccessibleValue *
gtk_accessible_value_parse (const GtkAccessibleCollect *cstate,
const char *str,
gsize len,
GError **error)
{
GtkAccessibleValue *res = NULL;
GtkAccessibleCollectType ctype = cstate->ctype;
gboolean collects_undef = (ctype & GTK_ACCESSIBLE_COLLECT_UNDEFINED) != 0;
ctype &= (GTK_ACCESSIBLE_COLLECT_UNDEFINED - 1);
/* Tristate values include "undefined" by definition */
if (ctype == GTK_ACCESSIBLE_COLLECT_TRISTATE)
collects_undef = TRUE;
switch (ctype)
{
case GTK_ACCESSIBLE_COLLECT_BOOLEAN:
{
gboolean b;
if (collects_undef && strncmp (str, "undefined", 9) == 0)
res = gtk_undefined_accessible_value_new ();
else if (_gtk_builder_boolean_from_string (str, &b, error))
res = gtk_boolean_accessible_value_new (b);
}
break;
case GTK_ACCESSIBLE_COLLECT_TRISTATE:
{
int value;
if (collects_undef && strncmp (str, "undefined", 9) == 0)
res = gtk_undefined_accessible_value_new ();
else if (_gtk_builder_enum_from_string (GTK_TYPE_ACCESSIBLE_TRISTATE, str, &value, error))
res = gtk_boolean_accessible_value_new (value);
}
break;
case GTK_ACCESSIBLE_COLLECT_TOKEN:
{
GtkAccessibleValueEnumParser parser =
(GtkAccessibleValueEnumParser) cstate->parser;
if (collects_undef && strncmp (str, "undefined", 9) == 0)
{
res = gtk_undefined_accessible_value_new ();
}
else
{
/* Token collection requires a constructor */
g_assert (parser != NULL);
res = (* parser) (str, len, error);
}
}
break;
case GTK_ACCESSIBLE_COLLECT_INTEGER:
{
char *end = NULL;
gint64 value = g_ascii_strtoll (str, &end, 10);
if (str == end)
{
int saved_errno = errno;
g_set_error (error, GTK_ACCESSIBLE_VALUE_ERROR,
GTK_ACCESSIBLE_VALUE_ERROR_INVALID_VALUE,
"Invalid integer value “%s”: %s",
str, g_strerror (saved_errno));
return NULL;
}
else
res = gtk_int_accessible_value_new ((int) value);
}
break;
case GTK_ACCESSIBLE_COLLECT_NUMBER:
{
char *end = NULL;
double value = g_ascii_strtod (str, &end);
if (str == end || isnan (value) || isinf (value))
{
g_set_error (error, GTK_ACCESSIBLE_VALUE_ERROR,
GTK_ACCESSIBLE_VALUE_ERROR_INVALID_VALUE,
"Invalid numeric value “%s”",
str);
return NULL;
}
res = gtk_number_accessible_value_new (value);
}
break;
case GTK_ACCESSIBLE_COLLECT_STRING:
{
res = gtk_string_accessible_value_new (str);
}
break;
case GTK_ACCESSIBLE_COLLECT_REFERENCE:
case GTK_ACCESSIBLE_COLLECT_REFERENCE_LIST:
{
/* We do not error out, to let the caller code deal
* with the references themselves
*/
res = NULL;
}
break;
case GTK_ACCESSIBLE_COLLECT_UNDEFINED:
case GTK_ACCESSIBLE_COLLECT_INVALID:
default:
g_assert_not_reached ();
break;
}
return res;
}
/*< private >
* gtk_accessible_value_collect_for_state:
* @state: a #GtkAccessibleState
......@@ -1370,6 +1504,19 @@ gtk_accessible_value_collect_for_state_value (GtkAccessibleState state,
return gtk_accessible_value_collect_value (cstate, value, error);
}
GtkAccessibleValue *
gtk_accessible_value_parse_for_state (GtkAccessibleState state,
const char *str,
gsize len,
GError **error)
{
const GtkAccessibleCollect *cstate = &collect_states[state];
g_return_val_if_fail (state <= GTK_ACCESSIBLE_STATE_SELECTED, NULL);
return gtk_accessible_value_parse (cstate, str, len, error);
}
/*< private >
* gtk_accessible_value_get_default_for_property:
* @property: a #GtkAccessibleProperty
......@@ -1487,6 +1634,19 @@ gtk_accessible_value_collect_for_property_value (GtkAccessibleProperty propert
return gtk_accessible_value_collect_value (cstate, value, error);
}
GtkAccessibleValue *
gtk_accessible_value_parse_for_property (GtkAccessibleProperty property,
const char *str,
gsize len,
GError **error)
{
const GtkAccessibleCollect *cstate = &collect_props[property];
g_return_val_if_fail (property <= GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT, NULL);
return gtk_accessible_value_parse (cstate, str, len, error);
}
/*< private >
* gtk_accessible_value_get_default_for_relation:
* @relation: a #GtkAccessibleRelation
......@@ -1596,4 +1756,17 @@ gtk_accessible_value_collect_for_relation_value (GtkAccessibleRelation relatio
return gtk_accessible_value_collect_value (cstate, value, error);
}
GtkAccessibleValue *
gtk_accessible_value_parse_for_relation (GtkAccessibleRelation relation,
const char *str,
gsize len,
GError **error)
{
const GtkAccessibleCollect *cstate = &collect_rels[relation];
g_return_val_if_fail (relation <= GTK_ACCESSIBLE_RELATION_SET_SIZE, NULL);
return gtk_accessible_value_parse (cstate, str, len, error);
}
/* }}} */
......@@ -98,6 +98,10 @@ GtkAccessibleValue * gtk_accessible_value_collect_for_state (GtkAcce
GtkAccessibleValue * gtk_accessible_value_collect_for_state_value (GtkAccessibleState state,
const GValue *value,
GError **error);
GtkAccessibleValue * gtk_accessible_value_parse_for_state (GtkAccessibleState state,
const char *str,
gsize len,
GError **error);
GtkAccessibleValue * gtk_accessible_value_get_default_for_property (GtkAccessibleProperty property);
GtkAccessibleValue * gtk_accessible_value_collect_for_property (GtkAccessibleProperty property,
......@@ -106,6 +110,10 @@ GtkAccessibleValue * gtk_accessible_value_collect_for_property (GtkAcce
GtkAccessibleValue * gtk_accessible_value_collect_for_property_value (GtkAccessibleProperty property,
const GValue *value,
GError **error);
GtkAccessibleValue * gtk_accessible_value_parse_for_property (GtkAccessibleProperty property,
const char *str,
gsize len,
GError **error);
GtkAccessibleValue * gtk_accessible_value_get_default_for_relation (GtkAccessibleRelation relation);
GtkAccessibleValue * gtk_accessible_value_collect_for_relation (GtkAccessibleRelation relation,
......@@ -114,6 +122,10 @@ GtkAccessibleValue * gtk_accessible_value_collect_for_relation (GtkAcce
GtkAccessibleValue * gtk_accessible_value_collect_for_relation_value (GtkAccessibleRelation relation,
const GValue *value,
GError **error);
GtkAccessibleValue * gtk_accessible_value_parse_for_relation (GtkAccessibleRelation relation,
const char *str,
gsize len,
GError **error);
/* Basic values */
GtkAccessibleValue * gtk_undefined_accessible_value_new (void);
......@@ -143,14 +155,26 @@ GList * gtk_reference_list_accessible_value_get (const G
/* Token values */
GtkAccessibleValue * gtk_invalid_accessible_value_new (GtkAccessibleInvalidState value);
GtkAccessibleInvalidState gtk_invalid_accessible_value_get (const GtkAccessibleValue *value);
GtkAccessibleValue * gtk_invalid_accessible_value_parse (const char *str,
gsize len,
GError **error);
GtkAccessibleValue * gtk_autocomplete_accessible_value_new (GtkAccessibleAutocomplete value);
GtkAccessibleAutocomplete gtk_autocomplete_accessible_value_get (const GtkAccessibleValue *value);
GtkAccessibleValue * gtk_autocomplete_accessible_value_parse (const char *str,
gsize len,
GError **error);
GtkAccessibleValue * gtk_orientation_accessible_value_new (GtkOrientation value);
GtkOrientation gtk_orientation_accessible_value_get (const GtkAccessibleValue *value);
GtkAccessibleValue * gtk_orientation_accessible_value_parse (const char *str,
gsize len,
GError **error);
GtkAccessibleValue * gtk_sort_accessible_value_new (GtkAccessibleSort value);
GtkAccessibleSort gtk_sort_accessible_value_get (const GtkAccessibleValue *value);
GtkAccessibleValue * gtk_sort_accessible_value_parse (const char *str,
gsize len,
GError **error);
G_END_DECLS
......@@ -21,6 +21,9 @@
#include "config.h"
#include "gtkaccessiblevalueprivate.h"
#include "gtkbuilderprivate.h"
#include "gtkenums.h"
#include "gtktypebuiltins.h"
/* {{{ Undefined value */
......@@ -282,6 +285,19 @@ gtk_invalid_accessible_value_get (const GtkAccessibleValue *value)
return self->value;
}
GtkAccessibleValue *
gtk_invalid_accessible_value_parse (const char *str,
gsize len,
GError **error)
{
int value;
if (_gtk_builder_enum_from_string (GTK_TYPE_ACCESSIBLE_INVALID_STATE, str, &value, error))
return gtk_invalid_accessible_value_new (value);
return NULL;
}
static const GtkAccessibleValueClass GTK_AUTOCOMPLETE_ACCESSIBLE_VALUE = {
.type = GTK_ACCESSIBLE_VALUE_TYPE_TOKEN,
.type_name = "GtkAutocompleteAccessibleValue",
......@@ -327,6 +343,19 @@ gtk_autocomplete_accessible_value_get (const GtkAccessibleValue *value)
return self->value;
}
GtkAccessibleValue *
gtk_autocomplete_accessible_value_parse (const char *str,
gsize len,
GError **error)
{
int value;
if (_gtk_builder_enum_from_string (GTK_TYPE_ACCESSIBLE_AUTOCOMPLETE, str, &value, error))
return gtk_autocomplete_accessible_value_new (value);
return NULL;
}
static const GtkAccessibleValueClass GTK_ORIENTATION_ACCESSIBLE_VALUE = {
.type = GTK_ACCESSIBLE_VALUE_TYPE_TOKEN,
.type_name = "GtkOrientationAccessibleValue",
......@@ -366,6 +395,19 @@ gtk_orientation_accessible_value_get (const GtkAccessibleValue *value)
return self->value;
}
GtkAccessibleValue *
gtk_orientation_accessible_value_parse (const char *str,
gsize len,
GError **error)
{
int value;
if (_gtk_builder_enum_from_string (GTK_TYPE_ORIENTATION, str, &value, error))
return gtk_orientation_accessible_value_new (value);
return NULL;
}
static const GtkAccessibleValueClass GTK_SORT_ACCESSIBLE_VALUE = {
.type = GTK_ACCESSIBLE_VALUE_TYPE_TOKEN,
.type_name = "GtkSortAccessibleValue",
......@@ -411,4 +453,17 @@ gtk_sort_accessible_value_get (const GtkAccessibleValue *value)
return self->value;
}
GtkAccessibleValue *
gtk_sort_accessible_value_parse (const char *str,
gsize len,
GError **error)
{
int value;
if (_gtk_builder_enum_from_string (GTK_TYPE_ACCESSIBLE_SORT, str, &value, error))
return gtk_sort_accessible_value_new (value);
return NULL;
}
/* }}} */
......@@ -1997,7 +1997,7 @@ gtk_builder_value_from_string (GtkBuilder *builder,
string, value, error);
}
static gboolean
gboolean
_gtk_builder_boolean_from_string (const char *string,
gboolean *value,
GError **error)
......
......@@ -242,6 +242,10 @@ gboolean _gtk_builder_flags_from_string (GType type,
const char *string,
guint *value,
GError **error);
gboolean _gtk_builder_boolean_from_string (const char *string,
gboolean *value,
GError **error);
const char * _gtk_builder_parser_translate (const char *domain,
const char *context,
const char *text);
......
......@@ -248,8 +248,8 @@
*
* # GtkWidget as GtkBuildable
*
* The GtkWidget implementation of the GtkBuildable interface supports a
* custom <accelerator> element, which has attributes named ”key”, ”modifiers”
* The GtkWidget implementation of the #GtkBuildable interface supports a
* custom `<accelerator>` element, which has attributes named ”key”, ”modifiers”
* and ”signal” and allows to specify accelerators.
*
* An example of a UI definition fragment specifying an accelerator:
......@@ -260,7 +260,7 @@
* ]|
*
* If the parent widget uses a #GtkLayoutManager, #GtkWidget supports a
* custom <layout> element, used to define layout properties:
* custom `<layout>` element, used to define layout properties:
*
* |[
* <object class="MyGrid" id="grid1">
......@@ -288,8 +288,8 @@
* </object>
* ]|
*
* Finally, GtkWidget allows style information such as style classes to
* be associated with widgets, using the custom <style> element:
* GtkWidget allows style information such as style classes to
* be associated with widgets, using the custom `<style>` element:
* |[
* <object class="GtkButton" id="button1">
* <style>
......@@ -299,6 +299,17 @@
* </object>
* ]|
*
* GtkWidget allows defining accessibility information, such as properties,
* relations, and states, using the custom `<accessibility>` element:
* |[
* <object class="GtkButton" id="button1">
* <accessibility>
* <property name="label">Download</property>
* <relation name="labelled-by">label1</relation>
* </accessibility>
* </object>
* ]|
*
* # Building composite widgets from template XML ## {#composite-templates}
*
* GtkWidget exposes some facilities to automate the procedure
......@@ -8441,6 +8452,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,
......@@ -8476,6 +8642,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