Commit fddc9b85 authored by Johan Dahlin's avatar Johan Dahlin Committed by Johan Dahlin

Implement accessible support, fixes #454653.

2008-01-25  Johan Dahlin  <johan@gnome.org>

        * gtk/gtkwidget.c: (gtk_widget_buildable_interface_init),
        (gtk_widget_buildable_get_internal_child), (free_action),
        (free_relation), (gtk_widget_buildable_parser_finished),
        (accessibility_start_element),
        (gtk_widget_buildable_custom_tag_start),
        (gtk_widget_buildable_custom_finished):
        Implement accessible support, fixes #454653.

        * gtk/gtk-builder-convert:
        Add support for migrating old glade files
        
        * tests/buildertest.c: (test_widget), (test_file):
        Add accessible tests and improve the test_file function to display
        toplevels and run dialogs.


svn path=/trunk/; revision=19403
parent 7b8050af
2008-01-25 Johan Dahlin <johan@gnome.org>
* gtk/gtkwidget.c: (gtk_widget_buildable_interface_init),
(gtk_widget_buildable_get_internal_child), (free_action),
(free_relation), (gtk_widget_buildable_parser_finished),
(accessibility_start_element),
(gtk_widget_buildable_custom_tag_start),
(gtk_widget_buildable_custom_finished):
Implement accessible support, fixes #454653.
* gtk/gtk-builder-convert:
Add support for migrating old glade files
* tests/buildertest.c: (test_widget), (test_file):
Add accessible tests and imprve the test_file function to display
toplevels and run dialogs.
2008-01-25 Richard Hult <richard@imendio.com>
* gdk/quartz/GdkQuartzView.c: Don't recreate a tracking rect if it
......
2008-01-24 Johan Dahlin <johan@gnome.org>
* gtk/tmpl/gtkwidget.sgml:
Add documentation for <accessible> buildable tag.
2008-01-14 Matthias Clasen <mclasen@redhat.com>
* gtk/gtk-docs.sgml:
......
......@@ -37,6 +37,31 @@ modifiers and signal and allows to specify accelerators.
</object>
]]></programlisting>
</example>
In addition to accelerators, <structname>GtkWidget</structname> also support a
custom &lt;accessible&gt; element, which supports actions and relations.
Properties on the accessible implementation of an object can be set by accessing the
internal child "accessible" of a <structname>GtkWidget</structname>.
<example>
<title>A UI definition fragment specifying an accessible</title>
<programlisting><![CDATA[
<object class="GtkButton" id="label1"/>
<property name="label">I am a Label for a Button</property>
</object>
<object class="GtkButton" id="button1">
<accessibility>
<action action_name="click" description="Click the button."/>
<relation target="label1" type="labelled-by"/>
</accessibility>
<child internal-child="accessible">
<object class="AtkObject" id="a11y-button1">
<property name="AtkObject::name">Clickable Button</property>
</object>
</child>
</object>
]]></programlisting>
</example>
</refsect2>
<!-- ##### SECTION See_Also ##### -->
......
#!/usr/bin/env python
#
# Copyright (C) 2006-2007 Async Open Source
# Copyright (C) 2006-2008 Async Open Source
# Henrique Romano <henrique@async.com.br>
# Johan Dahlin <jdahlin@async.com.br>
#
......@@ -124,7 +124,7 @@ def get_accelerator_nodes(node):
return accelerators
def get_object_node(child_node):
assert child_node.tagName == 'child'
assert child_node.tagName == 'child', child_node
nodes = []
for node in child_node.childNodes:
if node.nodeType == Node.TEXT_NODE:
......@@ -178,7 +178,7 @@ class GtkBuilderConverter(object):
return [w for w in self._dom.getElementsByTagName("object")
if w.getAttribute(attribute) == value]
def _create_object(self, obj_class, obj_id, template=None, **properties):
def _create_object(self, obj_class, obj_id, template=None, properties=None):
"""
Creates a new <object> tag.
Optionally a name template can be provided which will be used
......@@ -207,22 +207,23 @@ class GtkBuilderConverter(object):
obj = self._dom.createElement('object')
obj.setAttribute('class', obj_class)
obj.setAttribute('id', obj_id)
for name, value in properties.items():
if isinstance(value, Node):
# Reuse the node, so translatable and context still will be
# set when converting nodes. See also #509153
prop = value
else:
prop = self._dom.createElement('property')
prop.appendChild(self._dom.createTextNode(value))
prop.setAttribute('name', name)
obj.appendChild(prop)
if properties:
for name, value in properties.items():
if isinstance(value, Node):
# Reuse the node, so translatable and context still will be
# set when converting nodes. See also #509153
prop = value
else:
prop = self._dom.createElement('property')
prop.appendChild(self._dom.createTextNode(value))
prop.setAttribute('name', str(name))
obj.appendChild(prop)
self.objects[obj_id] = obj
return obj
def _create_root_object(self, obj_class, template, **properties):
obj = self._create_object(obj_class, None, template, **properties)
def _create_root_object(self, obj_class, template, properties=None):
obj = self._create_object(obj_class, None, template, properties)
self.root_objects.append(obj)
return obj
......@@ -261,6 +262,10 @@ class GtkBuilderConverter(object):
for node in self._dom.getElementsByTagName("ui"):
self._convert_ui(node)
# Convert accessibility tag
for node in self._dom.getElementsByTagName("accessibility"):
self._convert_accessibility(node)
# Output the newly created root objects and sort them
# by attribute id
for obj in sorted(self.root_objects,
......@@ -421,7 +426,7 @@ class GtkBuilderConverter(object):
properties['name'] = object_id
action = self._create_object(name,
object_id,
**properties)
properties=properties)
for signal in get_signal_nodes(node):
signal_name = signal.getAttribute('name')
......@@ -514,12 +519,12 @@ class GtkBuilderConverter(object):
value, lower, upper, step, page, page_size = data.split(' ')
adj = self._create_root_object("GtkAdjustment",
template='adjustment',
value=value,
lower=lower,
upper=upper,
step_increment=step,
page_increment=page,
page_size=page_size)
properties=dict(value=value,
lower=lower,
upper=upper,
step_increment=step,
page_increment=page,
page_size=page_size))
prop.childNodes[0].data = adj.getAttribute('id')
def _convert_combobox_items(self, node, prop):
......@@ -581,7 +586,7 @@ class GtkBuilderConverter(object):
prop.removeAttribute('translatable')
tbuffer = self._create_root_object("GtkTextBuffer",
template='textbuffer',
text=data)
properties=dict(text=data))
prop.childNodes[0].data = tbuffer.getAttribute('id')
def _packing_prop_to_child_attr(self, node, prop_name, prop_val,
......@@ -623,6 +628,35 @@ class GtkBuilderConverter(object):
widget.getAttributeNode("constructor").value = parent_id
node.removeAttribute("id")
def _convert_accessibility(self, node):
objectNode = node.parentNode
parent_id = objectNode.getAttribute("id")
properties = {}
for node in node.childNodes:
if node.nodeName == 'atkproperty':
node.tagName = 'property'
properties[node.getAttribute('name')] = node
node.parentNode.removeChild(node)
elif node.nodeName == 'atkrelation':
node.tagName = 'relation'
relation_type = node.getAttribute('type')
relation_type = relation_type.replace('_', '-')
node.setAttribute('type', relation_type)
elif node.nodeName == 'atkaction':
node.tagName = 'action'
if properties:
child = self._dom.createElement('child')
child.setAttribute("internal-child", "accessible")
atkobject = self._create_object(
"AtkObject", None,
template='a11y-%s' % (parent_id,),
properties=properties)
child.appendChild(atkobject)
objectNode.appendChild(child)
def _strip_root(self, root_name):
for widget in self._dom.getElementsByTagName("widget"):
if widget.getAttribute('id') == root_name:
......
......@@ -60,7 +60,6 @@ gtk_test_init (int *argcp,
* FUTURE TODO:
* - this function could install a mock object around GtkSettings
*/
g_setenv ("GTK_MODULES", "", TRUE);
g_setenv ("GTK2_RC_FILES", "/dev/null", TRUE);
gtk_disable_setlocale();
setlocale (LC_ALL, "C");
......
......@@ -251,6 +251,9 @@ static void gtk_widget_buildable_interface_init (GtkBuildableIfa
static void gtk_widget_buildable_set_name (GtkBuildable *buildable,
const gchar *name);
static const gchar * gtk_widget_buildable_get_name (GtkBuildable *buildable);
static GObject * gtk_widget_buildable_get_internal_child (GtkBuildable *buildable,
GtkBuilder *builder,
const gchar *childname);
static void gtk_widget_buildable_set_buildable_property (GtkBuildable *buildable,
GtkBuilder *builder,
const gchar *name,
......@@ -8786,17 +8789,20 @@ gtk_widget_ref_accessible (AtkImplementor *implementor)
/*
* GtkBuildable implementation
*/
static GQuark quark_builder_has_default = 0;
static GQuark quark_builder_has_focus = 0;
static GQuark quark_builder_has_default = 0;
static GQuark quark_builder_has_focus = 0;
static GQuark quark_builder_atk_relations = 0;
static void
gtk_widget_buildable_interface_init (GtkBuildableIface *iface)
{
quark_builder_has_default = g_quark_from_static_string ("gtk-builder-has-default");
quark_builder_has_focus = g_quark_from_static_string ("gtk-builder-has-focus");
quark_builder_atk_relations = g_quark_from_static_string ("gtk-builder-atk-relations");
iface->set_name = gtk_widget_buildable_set_name;
iface->get_name = gtk_widget_buildable_get_name;
iface->get_internal_child = gtk_widget_buildable_get_internal_child;
iface->set_buildable_property = gtk_widget_buildable_set_buildable_property;
iface->parser_finished = gtk_widget_buildable_parser_finished;
iface->custom_tag_start = gtk_widget_buildable_custom_tag_start;
......@@ -8816,6 +8822,17 @@ gtk_widget_buildable_get_name (GtkBuildable *buildable)
return gtk_widget_get_name (GTK_WIDGET (buildable));
}
static GObject *
gtk_widget_buildable_get_internal_child (GtkBuildable *buildable,
GtkBuilder *builder,
const gchar *childname)
{
if (strcmp (childname, "accessible") == 0)
return G_OBJECT (gtk_widget_get_accessible (GTK_WIDGET (buildable)));
return NULL;
}
static void
gtk_widget_buildable_set_buildable_property (GtkBuildable *buildable,
GtkBuilder *builder,
......@@ -8832,16 +8849,223 @@ gtk_widget_buildable_set_buildable_property (GtkBuildable *buildable,
g_object_set_property (G_OBJECT (buildable), name, value);
}
typedef struct {
gchar *action_name;
gchar *description;
} AtkActionData;
typedef struct {
gchar *target;
gchar *type;
} AtkRelationData;
static void
free_action (AtkActionData *data, gpointer user_data)
{
g_free (data->action_name);
g_free (data->description);
g_slice_free (AtkActionData, data);
}
static void
free_relation (AtkRelationData *data, gpointer user_data)
{
g_free (data->target);
g_free (data->type);
g_slice_free (AtkRelationData, data);
}
static void
gtk_widget_buildable_parser_finished (GtkBuildable *buildable,
GtkBuilder *builder)
{
GSList *atk_relations;
if (g_object_get_qdata (G_OBJECT (buildable), quark_builder_has_default))
gtk_widget_grab_default (GTK_WIDGET (buildable));
if (g_object_get_qdata (G_OBJECT (buildable), quark_builder_has_focus))
gtk_widget_grab_focus (GTK_WIDGET (buildable));
atk_relations = g_object_get_qdata (G_OBJECT (buildable),
quark_builder_atk_relations);
if (atk_relations)
{
AtkObject *accessible;
AtkRelationSet *relation_set;
GSList *l;
GObject *target;
AtkRelationType relation_type;
AtkObject *target_accessible;
accessible = gtk_widget_get_accessible (GTK_WIDGET (buildable));
relation_set = atk_object_ref_relation_set (accessible);
for (l = atk_relations; l; l = l->next)
{
AtkRelationData *relation = (AtkRelationData*)l->data;
target = gtk_builder_get_object (builder, relation->target);
if (!target)
{
g_warning ("Target object %s in <relation> does not exist",
relation->target);
continue;
}
target_accessible = gtk_widget_get_accessible (GTK_WIDGET (target));
g_assert (target_accessible != NULL);
relation_type = atk_relation_type_for_name (relation->type);
if (relation_type == ATK_RELATION_NULL)
{
g_warning ("<relation> type %s not found",
relation->type);
continue;
}
atk_relation_set_add_relation_by_type (relation_set, relation_type,
target_accessible);
}
g_object_unref (relation_set);
g_slist_foreach (atk_relations, (GFunc)free_relation, NULL);
g_slist_free (atk_relations);
g_object_set_qdata (G_OBJECT (buildable), quark_builder_atk_relations,
NULL);
}
}
typedef struct {
GSList *actions;
GSList *relations;
} AccessibilitySubParserData;
static void
accessibility_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **names,
const gchar **values,
gpointer user_data,
GError **error)
{
AccessibilitySubParserData *data = (AccessibilitySubParserData*)user_data;
guint i;
gint line_number, char_number;
if (strcmp (element_name, "relation") == 0)
{
gchar *target = NULL;
gchar *type = NULL;
AtkRelationData *relation;
for (i = 0; names[i]; i++)
{
if (strcmp (names[i], "target") == 0)
target = g_strdup (values[i]);
else if (strcmp (names[i], "type") == 0)
type = g_strdup (values[i]);
else
{
g_markup_parse_context_get_position (context,
&line_number,
&char_number);
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_ATTRIBUTE,
"%s:%d:%d '%s' is not a valid attribute of <%s>",
"<input>",
line_number, char_number, names[i], "relation");
g_free (target);
g_free (type);
return;
}
}
if (!target || !type)
{
g_markup_parse_context_get_position (context,
&line_number,
&char_number);
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
"%s:%d:%d <%s> requires attribute \"%s\"",
"<input>",
line_number, char_number, "relation",
type ? "target" : "type");
g_free (target);
g_free (type);
return;
}
relation = g_slice_new (AtkRelationData);
relation->target = target;
relation->type = type;
data->relations = g_slist_prepend (data->relations, relation);
}
else if (strcmp (element_name, "action") == 0)
{
gchar *action_name = NULL;
gchar *description = NULL;
AtkActionData *action;
for (i = 0; names[i]; i++)
{
if (strcmp (names[i], "action_name") == 0)
action_name = g_strdup (values[i]);
else if (strcmp (names[i], "description") == 0)
description = g_strdup (values[i]);
else
{
g_markup_parse_context_get_position (context,
&line_number,
&char_number);
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_ATTRIBUTE,
"%s:%d:%d '%s' is not a valid attribute of <%s>",
"<input>",
line_number, char_number, names[i], "action");
g_free (action_name);
g_free (description);
return;
}
}
if (!action_name || !description)
{
g_markup_parse_context_get_position (context,
&line_number,
&char_number);
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
"%s:%d:%d <%s> requires attribute \"%s\"",
"<input>",
line_number, char_number, "action",
description ? "action_name" : "description");
g_free (action_name);
g_free (description);
return;
}
action = g_slice_new (AtkActionData);
action->action_name = action_name;
action->description = description;
data->actions = g_slist_prepend (data->actions, action);
}
else if (strcmp (element_name, "accessibility") == 0)
;
else
g_warning ("Unsupported tag for GtkWidget: %s\n", element_name);
}
static const GMarkupParser accessibility_parser =
{
accessibility_start_element,
};
typedef struct {
GObject *object;
guint key;
......@@ -8894,12 +9118,6 @@ static const GMarkupParser accel_group_parser =
accel_group_start_element,
};
static const GMarkupParser accessibility_parser =
{
NULL,
};
static gboolean
gtk_widget_buildable_custom_tag_start (GtkBuildable *buildable,
GtkBuilder *builder,
......@@ -8908,31 +9126,25 @@ gtk_widget_buildable_custom_tag_start (GtkBuildable *buildable,
GMarkupParser *parser,
gpointer *data)
{
AccelGroupParserData *parser_data;
g_assert (buildable);
if (strcmp (tagname, "accelerator") == 0)
{
AccelGroupParserData *parser_data;
parser_data = g_slice_new0 (AccelGroupParserData);
parser_data->object = g_object_ref (buildable);
*parser = accel_group_parser;
*data = parser_data;
return TRUE;
}
else if (strcmp (tagname, "accessibility") == 0)
if (strcmp (tagname, "accessibility") == 0)
{
static gboolean warning_showed = FALSE;
if (!warning_showed)
{
g_warning ("<accessibility> is being ignored,\n"
"see http://bugzilla.gnome.org/show_bug.cgi?id=454653\n");
warning_showed = TRUE;
}
AccessibilitySubParserData *parser_data;
parser_data = g_slice_new0 (AccessibilitySubParserData);
*parser = accessibility_parser;
*data = NULL;
*data = parser_data;
return TRUE;
}
return FALSE;
......@@ -8945,17 +9157,18 @@ gtk_widget_buildable_custom_finished (GtkBuildable *buildable,
const gchar *tagname,
gpointer user_data)
{
AccelGroupParserData *data;
AccelGroupParserData *accel_data;
AccessibilitySubParserData *a11y_data;
GtkWidget *toplevel;
GSList *accel_groups;
GtkAccelGroup *accel_group;
if (strcmp (tagname, "accelerator") == 0)
{
data = (AccelGroupParserData*)user_data;
g_assert (data->object);
accel_data = (AccelGroupParserData*)user_data;
g_assert (accel_data->object);
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (data->object));
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (accel_data->object));
accel_groups = gtk_accel_groups_from_object (G_OBJECT (toplevel));
if (g_slist_length (accel_groups) == 0)
{
......@@ -8967,15 +9180,56 @@ gtk_widget_buildable_custom_finished (GtkBuildable *buildable,
g_assert (g_slist_length (accel_groups) == 1);
accel_group = g_slist_nth_data (accel_groups, 0);
}
gtk_widget_add_accelerator (GTK_WIDGET(data->object),
data->signal,
gtk_widget_add_accelerator (GTK_WIDGET (accel_data->object),
accel_data->signal,
accel_group,
data->key,
data->modifiers,
accel_data->key,
accel_data->modifiers,
GTK_ACCEL_VISIBLE);
g_object_unref (data->object);
g_free (data->signal);
g_slice_free (AccelGroupParserData, data);
g_object_unref (accel_data->object);
g_free (accel_data->signal);
g_slice_free (AccelGroupParserData, accel_data);
}
else if (strcmp (tagname, "accessibility") == 0)
{
a11y_data = (AccessibilitySubParserData*)user_data;
if (a11y_data->actions)
{
AtkObject *accessible;
AtkAction *action;
gint i, n_actions;
GSList *l;
accessible = gtk_widget_get_accessible (GTK_WIDGET (buildable));
action = ATK_ACTION (accessible);
n_actions = atk_action_get_n_actions (action);
for (l = a11y_data->actions; l; l = l->next)
{
AtkActionData *action_data = (AtkActionData*)l->data;
for (i = 0; i < n_actions; i++)
if (strcmp (atk_action_get_name (action, i),
action_data->action_name) == 0)
break;
if (i < n_actions)
atk_action_set_description (action, i,
action_data->description);
}
g_slist_foreach (a11y_data->actions, (GFunc)free_action, NULL);
g_slist_free (a11y_data->actions);
}
if (a11y_data->relations)
g_object_set_qdata (G_OBJECT (buildable), quark_builder_atk_relations,
a11y_data->relations);
g_slice_free (AccessibilitySubParserData, a11y_data);
}
}
......
......@@ -1468,14 +1468,37 @@ test_widget (void)
gchar *buffer3 =
"<interface>"
" <object class=\"GtkWindow\" id=\"window1\">"
" <accessibility>"
" <atkproperty name=\"AtkObject::accessible_name\" translatable=\"yes\">Contacts</atkproperty>"
" <atkrelation target=\"button1\" type=\"labelled-by\"/>"
" </accessibility>"
" <child>"
" <object class=\"GtkVBox\" id=\"vbox1\">"
" <child>"
" <object class=\"GtkLabel\" id=\"label1\">"
" <child internal-child=\"accessible\">"
" <object class=\"AtkObject\" id=\"a11y-label1\">"
" <property name=\"AtkObject::accessible-name\">A Label</property>"
" </object>"
" </child>"
" <accessibility>"
" <relation target=\"button1\" type=\"label-for\"/>"
" </accessibility>"
" </object>"