Commit 5845356d authored by Matthias Clasen's avatar Matthias Clasen

GtkLabelAccessible: Implement AtkHypertext

This commit makes the label accessible implement AtkHypertext,
which returns a AtkHyperlink object for each link in the text.
At the same time, add AtkHyperlinkImpl objects as children
to the label accessible.

Also some private API to indicate that links have changed, and
call that from GtkLabel when needed.

Adjust expected output of the affected a11y tests.

https://bugzilla.gnome.org/show_bug.cgi?id=721410
https://bugzilla.gnome.org/show_bug.cgi?id=721421
parent 0d9efde3
......@@ -22,18 +22,272 @@
#include "gtkwidgetprivate.h"
#include "gtklabelprivate.h"
#include "gtklabelaccessible.h"
#include "gtklabelaccessibleprivate.h"
struct _GtkLabelAccessiblePrivate
{
gint cursor_position;
gint selection_bound;
GList *links;
};
typedef struct _GtkLabelAccessibleLink GtkLabelAccessibleLink;
typedef struct _GtkLabelAccessibleLinkClass GtkLabelAccessibleLinkClass;
struct _GtkLabelAccessibleLink
{
AtkHyperlink parent;
GtkLabelAccessible *label;
gint index;
};
struct _GtkLabelAccessibleLinkClass
{
AtkHyperlinkClass parent_class;
};
static void atk_text_interface_init (AtkTextIface *iface);
typedef struct _GtkLabelAccessibleLinkImpl GtkLabelAccessibleLinkImpl;
typedef struct _GtkLabelAccessibleLinkImplClass GtkLabelAccessibleLinkImplClass;
struct _GtkLabelAccessibleLinkImpl
{
AtkObject parent;
GtkLabelAccessibleLink *link;
};
struct _GtkLabelAccessibleLinkImplClass
{
AtkObjectClass parent_class;
};
static void atk_action_interface_init (AtkActionIface *iface);
static void atk_hyperlink_impl_interface_init (AtkHyperlinkImplIface *iface);
GType _gtk_label_accessible_link_get_type (void);
GType _gtk_label_accessible_link_impl_get_type (void);
G_DEFINE_TYPE_WITH_CODE (GtkLabelAccessibleLink, _gtk_label_accessible_link, ATK_TYPE_HYPERLINK,
G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, atk_action_interface_init))
G_DEFINE_TYPE_WITH_CODE (GtkLabelAccessibleLinkImpl, _gtk_label_accessible_link_impl, ATK_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (ATK_TYPE_HYPERLINK_IMPL, atk_hyperlink_impl_interface_init))
static GtkLabelAccessibleLink *
gtk_label_accessible_link_new (GtkLabelAccessible *label,
gint idx)
{
GtkLabelAccessibleLink *link;
link = g_object_new (_gtk_label_accessible_link_get_type (), NULL);
link->label = label;
link->index = idx;
return link;
}
static GtkLabelAccessibleLinkImpl *
gtk_label_accessible_link_impl_new (GtkLabelAccessible *label,
gint idx)
{
GtkLabelAccessibleLinkImpl *impl;
impl = g_object_new (_gtk_label_accessible_link_impl_get_type (), NULL);
atk_object_set_parent (ATK_OBJECT (impl), ATK_OBJECT (label));
impl->link = gtk_label_accessible_link_new (label, idx);
return impl;
}
static AtkHyperlink *
gtk_label_accessible_link_impl_get_hyperlink (AtkHyperlinkImpl *atk_impl)
{
GtkLabelAccessibleLinkImpl *impl = (GtkLabelAccessibleLinkImpl *)atk_impl;
return g_object_ref (impl->link);
}
static void
atk_hyperlink_impl_interface_init (AtkHyperlinkImplIface *iface)
{
iface->get_hyperlink = gtk_label_accessible_link_impl_get_hyperlink;
}
static void
_gtk_label_accessible_link_impl_init (GtkLabelAccessibleLinkImpl *impl)
{
atk_object_set_role (ATK_OBJECT (impl), ATK_ROLE_LINK);
}
static void
_gtk_label_accessible_link_impl_finalize (GObject *obj)
{
GtkLabelAccessibleLinkImpl *impl = (GtkLabelAccessibleLinkImpl *)obj;
g_object_unref (impl->link);
G_OBJECT_CLASS (_gtk_label_accessible_link_impl_parent_class)->finalize (obj);
}
static void
_gtk_label_accessible_link_impl_class_init (GtkLabelAccessibleLinkImplClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = _gtk_label_accessible_link_impl_finalize;
}
static gchar *
gtk_label_accessible_link_get_uri (AtkHyperlink *atk_link,
gint i)
{
GtkLabelAccessibleLink *link = (GtkLabelAccessibleLink *)atk_link;
GtkWidget *widget;
const gchar *uri;
g_return_val_if_fail (i == 0, NULL);
if (link->label == NULL)
return NULL;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (link->label));
uri = _gtk_label_get_link_uri (GTK_LABEL (widget), link->index);
return g_strdup (uri);
}
static gint
gtk_label_accessible_link_get_n_anchors (AtkHyperlink *atk_link)
{
return 1;
}
static gboolean
gtk_label_accessible_link_is_valid (AtkHyperlink *atk_link)
{
return TRUE;
}
static AtkObject *
gtk_label_accessible_link_get_object (AtkHyperlink *atk_link,
gint i)
{
GtkLabelAccessibleLink *link = (GtkLabelAccessibleLink *)atk_link;
g_return_val_if_fail (i == 0, NULL);
return ATK_OBJECT (link->label);
}
static gint
gtk_label_accessible_link_get_start_index (AtkHyperlink *atk_link)
{
GtkLabelAccessibleLink *link = (GtkLabelAccessibleLink *)atk_link;
GtkWidget *widget;
gint start, end;
if (link->label == NULL)
return 0;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (link->label));
_gtk_label_get_link_extent (GTK_LABEL (widget), link->index, &start, &end);
return start;
}
static gint
gtk_label_accessible_link_get_end_index (AtkHyperlink *atk_link)
{
GtkLabelAccessibleLink *link = (GtkLabelAccessibleLink *)atk_link;
GtkWidget *widget;
gint start, end;
if (link->label == NULL)
return 0;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (link->label));
_gtk_label_get_link_extent (GTK_LABEL (widget), link->index, &start, &end);
return end;
}
static void
_gtk_label_accessible_link_init (GtkLabelAccessibleLink *link)
{
}
static void
_gtk_label_accessible_link_class_init (GtkLabelAccessibleLinkClass *class)
{
AtkHyperlinkClass *atk_link_class = ATK_HYPERLINK_CLASS (class);
atk_link_class->get_uri = gtk_label_accessible_link_get_uri;
atk_link_class->get_n_anchors = gtk_label_accessible_link_get_n_anchors;
atk_link_class->is_valid = gtk_label_accessible_link_is_valid;
atk_link_class->get_object = gtk_label_accessible_link_get_object;
atk_link_class->get_start_index = gtk_label_accessible_link_get_start_index;
atk_link_class->get_end_index = gtk_label_accessible_link_get_end_index;
}
static gboolean
gtk_label_accessible_link_do_action (AtkAction *action,
gint i)
{
GtkLabelAccessibleLink *link = (GtkLabelAccessibleLink *)action;
GtkWidget *widget;
if (i != 0)
return FALSE;
if (link->label == NULL)
return FALSE;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (link->label));
if (widget == NULL)
return FALSE;
if (!gtk_widget_is_sensitive (widget) || !gtk_widget_get_visible (widget))
return FALSE;
_gtk_label_activate_link (GTK_LABEL (widget), link->index);
return TRUE;
}
static gint
gtk_label_accessible_link_get_n_actions (AtkAction *action)
{
return 1;
}
static const gchar *
gtk_label_accessible_link_get_name (AtkAction *action,
gint i)
{
if (i != 0)
return NULL;
return "activate";
}
static void
atk_action_interface_init (AtkActionIface *iface)
{
iface->do_action = gtk_label_accessible_link_do_action;
iface->get_n_actions = gtk_label_accessible_link_get_n_actions;
iface->get_name = gtk_label_accessible_link_get_name;
}
static void atk_text_interface_init (AtkTextIface *iface);
static void atk_hypertext_interface_init (AtkHypertextIface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkLabelAccessible, gtk_label_accessible, GTK_TYPE_WIDGET_ACCESSIBLE,
G_ADD_PRIVATE (GtkLabelAccessible)
G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init))
G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init)
G_IMPLEMENT_INTERFACE (ATK_TYPE_HYPERTEXT, atk_hypertext_interface_init))
static void
gtk_label_accessible_init (GtkLabelAccessible *label)
......@@ -51,9 +305,10 @@ gtk_label_accessible_initialize (AtkObject *obj,
widget = GTK_WIDGET (data);
/*
* Check whether ancestor of GtkLabel is a GtkButton and if so
* set accessible parent for GtkLabelAccessible
_gtk_label_accessible_update_links (GTK_LABEL (widget));
/* Check whether ancestor of GtkLabel is a GtkButton
* and if so set accessible parent for GtkLabelAccessible
*/
while (widget != NULL)
{
......@@ -267,18 +522,59 @@ gtk_label_accessible_get_name (AtkObject *accessible)
}
}
static gint
gtk_label_accessible_get_n_children (AtkObject *obj)
{
GtkLabelAccessible *accessible = GTK_LABEL_ACCESSIBLE (obj);
return g_list_length (accessible->priv->links);
}
static AtkObject *
gtk_label_accessible_ref_child (AtkObject *obj,
gint idx)
{
GtkLabelAccessible *accessible = GTK_LABEL_ACCESSIBLE (obj);
AtkObject *child;
child = g_list_nth_data (accessible->priv->links, idx);
if (child)
g_object_ref (child);
return child;
}
static void clear_links (GtkLabelAccessible *accessible);
static void
gtk_label_accessible_finalize (GObject *obj)
{
GtkLabelAccessible *accessible = GTK_LABEL_ACCESSIBLE (obj);
clear_links (accessible);
G_OBJECT_CLASS (gtk_label_accessible_parent_class)->finalize (obj);
}
static void
gtk_label_accessible_class_init (GtkLabelAccessibleClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
GtkWidgetAccessibleClass *widget_class = GTK_WIDGET_ACCESSIBLE_CLASS (klass);
widget_class->notify_gtk = gtk_label_accessible_notify_gtk;
object_class->finalize = gtk_label_accessible_finalize;
class->get_name = gtk_label_accessible_get_name;
class->ref_state_set = gtk_label_accessible_ref_state_set;
class->ref_relation_set = gtk_label_accessible_ref_relation_set;
class->initialize = gtk_label_accessible_initialize;
class->get_n_children = gtk_label_accessible_get_n_children;
class->ref_child = gtk_label_accessible_ref_child;
widget_class->notify_gtk = gtk_label_accessible_notify_gtk;
}
/* atktext.h */
......@@ -765,3 +1061,95 @@ atk_text_interface_init (AtkTextIface *iface)
iface->get_default_attributes = gtk_label_accessible_get_default_attributes;
}
static void
clear_links (GtkLabelAccessible *accessible)
{
GList *l;
gint i;
GtkLabelAccessibleLinkImpl *impl;
for (l = accessible->priv->links, i = 0; l; l = l->next, i++)
{
impl = l->data;
g_signal_emit_by_name (accessible, "children-changed::remove", i, impl, NULL);
atk_object_set_parent (ATK_OBJECT (impl), NULL);
impl->link->label = NULL;
}
g_list_free_full (accessible->priv->links, g_object_unref);
accessible->priv->links = NULL;
}
static void
create_links (GtkLabelAccessible *accessible)
{
GtkWidget *widget;
gint n, i;
GtkLabelAccessibleLinkImpl *impl;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
n = _gtk_label_get_n_links (GTK_LABEL (widget));
for (i = 0; i < n; i++)
{
impl = gtk_label_accessible_link_impl_new (accessible, i);
accessible->priv->links = g_list_append (accessible->priv->links, impl);
g_signal_emit_by_name (accessible, "children-changed::add", i, impl, NULL);
}
}
void
_gtk_label_accessible_update_links (GtkLabel *label)
{
AtkObject *obj;
obj = _gtk_widget_peek_accessible (GTK_WIDGET (label));
if (obj == NULL)
return;
clear_links (GTK_LABEL_ACCESSIBLE (obj));
create_links (GTK_LABEL_ACCESSIBLE (obj));
}
static AtkHyperlink *
gtk_label_accessible_get_link (AtkHypertext *hypertext,
gint idx)
{
GtkLabelAccessible *label = GTK_LABEL_ACCESSIBLE (hypertext);
GtkLabelAccessibleLinkImpl *impl;
impl = (GtkLabelAccessibleLinkImpl *)g_list_nth_data (label->priv->links, idx);
if (impl)
return ATK_HYPERLINK (impl->link);
return NULL;
}
static gint
gtk_label_accessible_get_n_links (AtkHypertext *hypertext)
{
GtkWidget *widget;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (hypertext));
return _gtk_label_get_n_links (GTK_LABEL (widget));
}
static gint
gtk_label_accessible_get_link_index (AtkHypertext *hypertext,
gint char_index)
{
GtkWidget *widget;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (hypertext));
return _gtk_label_get_link_at (GTK_LABEL (widget), char_index);
}
static void
atk_hypertext_interface_init (AtkHypertextIface *iface)
{
iface->get_link = gtk_label_accessible_get_link;
iface->get_n_links = gtk_label_accessible_get_n_links;
iface->get_link_index = gtk_label_accessible_get_link_index;
}
......@@ -22,8 +22,9 @@
G_BEGIN_DECLS
void _gtk_label_accessible_text_deleted (GtkLabel *label);
void _gtk_label_accessible_text_inserted (GtkLabel *label);
void _gtk_label_accessible_text_deleted (GtkLabel *label);
void _gtk_label_accessible_text_inserted (GtkLabel *label);
void _gtk_label_accessible_update_links (GtkLabel *label);
G_END_DECLS
......
......@@ -2529,6 +2529,7 @@ gtk_label_set_markup_internal (GtkLabel *label,
{
gtk_label_ensure_select_info (label);
priv->select_info->links = g_list_reverse (links);
_gtk_label_accessible_update_links (label);
gtk_label_ensure_has_tooltip (label);
}
......@@ -6287,6 +6288,8 @@ gtk_label_clear_links (GtkLabel *label)
g_list_free_full (priv->select_info->links, (GDestroyNotify) link_free);
priv->select_info->links = NULL;
priv->select_info->active_link = NULL;
_gtk_label_accessible_update_links (label);
}
static gboolean
......
......@@ -76,6 +76,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
stack
"panel"
parent: box
......@@ -133,6 +134,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
comments_label
"label"
parent: page_vbox
......@@ -172,6 +174,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
hbox
"filler"
parent: page_vbox
......@@ -220,6 +223,20 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
<AtkHyperlink>
start index: 0
end index: 7
anchors: http://www.gtk.org
unnamed-GtkLabelAccessibleLinkImpl-0
"link"
parent: website_label
state: enabled focusable focused multi-line sensitive showing visible
<AtkHyperlinkImpl>
<AtkHyperlink>
start index: 0
end index: 7
anchors: http://www.gtk.org
copyright_label
"label"
parent: page_vbox
......@@ -259,6 +276,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
license_label
"label"
parent: page_vbox
......@@ -300,6 +318,20 @@ See the GNU General Public License, version 3 or later for details.
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
<AtkHyperlink>
start index: 56
end index: 102
anchors: http://www.gnu.org/licenses/gpl.html
unnamed-GtkLabelAccessibleLinkImpl-1
"link"
parent: license_label
state: enabled focusable multi-line sensitive showing visible
<AtkHyperlinkImpl>
<AtkHyperlink>
start index: 56
end index: 102
anchors: http://www.gnu.org/licenses/gpl.html
credits_page
"filler"
parent: stack
......@@ -336,7 +368,7 @@ See the GNU General Public License, version 3 or later for details.
<AtkComponent>
layer: widget
alpha: 1
unnamed-GtkRangeAccessible-0
unnamed-GtkRangeAccessible-2
"scroll bar"
parent: scrolledwindow1
state: enabled horizontal sensitive visible
......@@ -348,7 +380,7 @@ See the GNU General Public License, version 3 or later for details.
minimum value: 0.000000
maximum value: 9.000000
current value: 0.000000
unnamed-GtkRangeAccessible-1
unnamed-GtkRangeAccessible-3
"scroll bar"
parent: scrolledwindow1
state: enabled sensitive vertical visible
......@@ -418,7 +450,7 @@ See the GNU General Public License, version 3 or later for details.
wrap-mode: none
<AtkStreamableContent>
mime types: application/x-gtk-text-buffer-rich-text text/plain
unnamed-GtkRangeAccessible-2
unnamed-GtkRangeAccessible-4
"scroll bar"
parent: scrolledwindow2
state: enabled horizontal sensitive visible
......@@ -430,7 +462,7 @@ See the GNU General Public License, version 3 or later for details.
minimum value: 0.000000
maximum value: 0.000000
current value: 0.000000
unnamed-GtkRangeAccessible-3
unnamed-GtkRangeAccessible-5
"scroll bar"
parent: scrolledwindow2
state: enabled sensitive vertical visible
......
......@@ -519,7 +519,6 @@ dump_atk_hypertext (AtkHypertext *hypertext,
{
link = atk_hypertext_get_link (hypertext, i);
dump_atk_hyperlink (link, depth + DEPTH_INCREMENT, string);
g_object_unref (link);
}
}
......
......@@ -225,6 +225,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
255
"text"
parent: unnamed-GtkContainerAccessible-11
......@@ -322,6 +323,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
unnamed-GtkWidgetAccessible-13
"separator"
parent: unnamed-GtkContainerAccessible-11
......@@ -420,6 +422,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
255
"spin button"
parent: unnamed-GtkContainerAccessible-11
......@@ -509,6 +512,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
255
"spin button"
parent: unnamed-GtkContainerAccessible-11
......@@ -598,6 +602,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
100
"spin button"
parent: unnamed-GtkContainerAccessible-11
......@@ -687,6 +692,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
0
"spin button"
parent: unnamed-GtkContainerAccessible-11
......@@ -776,6 +782,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
0
"spin button"
parent: unnamed-GtkContainerAccessible-11
......@@ -865,6 +872,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
unnamed-GtkContainerAccessible-14
"filler"
parent: unnamed-GtkContainerAccessible-10
......@@ -914,6 +922,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
unnamed-GtkContainerAccessible-16
"panel"
parent: unnamed-GtkContainerAccessible-14
......
......@@ -55,6 +55,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
combo1
"combo box"
parent: box1
......
......@@ -55,6 +55,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
label2
"label"
parent: box1
......@@ -95,6 +96,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
label3
"label"
parent: box1
......@@ -135,6 +137,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
entry1
"text"
parent: box1
......
......@@ -58,3 +58,4 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
......@@ -82,6 +82,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
action_area
"filler"
parent: content
......
......@@ -73,6 +73,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
unnamed-GtkListBoxRowAccessible-1
"list item"
parent: listbox1
......@@ -121,6 +122,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
listbox2
"list box"
parent: box1
......@@ -179,3 +181,4 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
......@@ -86,6 +86,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
secondary_label
"label"
parent: message_area
......@@ -125,6 +126,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
dialog-action_area1
"filler"
parent: dialog-vbox1
......
......@@ -55,6 +55,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
entry1
"text"
parent: box1
......
......@@ -58,6 +58,7 @@ window1
variant: <omitted>
weight: <omitted>
wrap-mode: word
<AtkHypertext>
label2
"label"
parent: paned1
......@@ -97,3 +98,4 @@ window1