gtk2/gtk/a11y/gtkwidgetaccessible.c
2016-10-18 00:29:19 +02:00

842 lines
24 KiB
C

/* GTK+ - accessibility implementations
* Copyright 2001, 2002, 2003 Sun Microsystems Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gtk/gtk.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/x11/gdkx.h>
#endif
#include "gtkwidgetaccessibleprivate.h"
#include "gtknotebookpageaccessible.h"
struct _GtkWidgetAccessiblePrivate
{
AtkLayer layer;
};
#define TOOLTIP_KEY "tooltip"
extern GtkWidget *_focus_widget;
static gboolean gtk_widget_accessible_on_screen (GtkWidget *widget);
static gboolean gtk_widget_accessible_all_parents_visible (GtkWidget *widget);
static void atk_component_interface_init (AtkComponentIface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkWidgetAccessible, gtk_widget_accessible, GTK_TYPE_ACCESSIBLE,
G_ADD_PRIVATE (GtkWidgetAccessible)
G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, atk_component_interface_init))
/* Translate GtkWidget::focus-in/out-event to AtkObject::focus-event */
static gboolean
focus_cb (GtkWidget *widget,
GdkEventFocus *event)
{
AtkObject *obj;
obj = gtk_widget_get_accessible (widget);
g_signal_emit_by_name (obj, "focus-event", event->in);
return FALSE;
}
/* Translate GtkWidget property change notification to the notify_gtk vfunc */
static void
notify_cb (GObject *obj,
GParamSpec *pspec)
{
GtkWidgetAccessible *widget;
GtkWidgetAccessibleClass *klass;
widget = GTK_WIDGET_ACCESSIBLE (gtk_widget_get_accessible (GTK_WIDGET (obj)));
klass = GTK_WIDGET_ACCESSIBLE_GET_CLASS (widget);
if (klass->notify_gtk)
klass->notify_gtk (obj, pspec);
}
/* Translate GtkWidget::size-allocate to AtkComponent::bounds-changed */
static void
size_allocate_cb (GtkWidget *widget,
GtkAllocation *allocation)
{
AtkObject* accessible;
AtkRectangle rect;
accessible = gtk_widget_get_accessible (widget);
if (ATK_IS_COMPONENT (accessible))
{
rect.x = allocation->x;
rect.y = allocation->y;
rect.width = allocation->width;
rect.height = allocation->height;
g_signal_emit_by_name (accessible, "bounds-changed", &rect);
}
}
/* Translate GtkWidget mapped state into AtkObject showing */
static gint
map_cb (GtkWidget *widget)
{
AtkObject *accessible;
accessible = gtk_widget_get_accessible (widget);
atk_object_notify_state_change (accessible, ATK_STATE_SHOWING,
gtk_widget_get_mapped (widget));
return 1;
}
static void
gtk_widget_accessible_focus_event (AtkObject *obj,
gboolean focus_in)
{
AtkObject *focus_obj;
focus_obj = g_object_get_data (G_OBJECT (obj), "gail-focus-object");
if (focus_obj == NULL)
focus_obj = obj;
atk_object_notify_state_change (focus_obj, ATK_STATE_FOCUSED, focus_in);
}
static void
gtk_widget_accessible_update_tooltip (GtkWidgetAccessible *accessible,
GtkWidget *widget)
{
g_object_set_data_full (G_OBJECT (accessible),
TOOLTIP_KEY,
gtk_widget_get_tooltip_text (widget),
g_free);
}
static void
gtk_widget_accessible_initialize (AtkObject *obj,
gpointer data)
{
GtkWidget *widget;
widget = GTK_WIDGET (data);
g_signal_connect_after (widget, "focus-in-event", G_CALLBACK (focus_cb), NULL);
g_signal_connect_after (widget, "focus-out-event", G_CALLBACK (focus_cb), NULL);
g_signal_connect (widget, "notify", G_CALLBACK (notify_cb), NULL);
g_signal_connect (widget, "size-allocate", G_CALLBACK (size_allocate_cb), NULL);
g_signal_connect (widget, "map", G_CALLBACK (map_cb), NULL);
g_signal_connect (widget, "unmap", G_CALLBACK (map_cb), NULL);
GTK_WIDGET_ACCESSIBLE (obj)->priv->layer = ATK_LAYER_WIDGET;
obj->role = ATK_ROLE_UNKNOWN;
gtk_widget_accessible_update_tooltip (GTK_WIDGET_ACCESSIBLE (obj), widget);
}
static const gchar *
gtk_widget_accessible_get_description (AtkObject *accessible)
{
GtkWidget *widget;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
if (widget == NULL)
return NULL;
if (accessible->description)
return accessible->description;
return g_object_get_data (G_OBJECT (accessible), TOOLTIP_KEY);
}
static AtkObject *
gtk_widget_accessible_get_parent (AtkObject *accessible)
{
AtkObject *parent;
GtkWidget *widget, *parent_widget;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
if (widget == NULL)
return NULL;
parent = accessible->accessible_parent;
if (parent != NULL)
return parent;
parent_widget = gtk_widget_get_parent (widget);
if (parent_widget == NULL)
return NULL;
/* For a widget whose parent is a GtkNoteBook, we return the
* accessible object corresponding the GtkNotebookPage containing
* the widget as the accessible parent.
*/
if (GTK_IS_NOTEBOOK (parent_widget))
{
gint page_num;
GtkWidget *child;
GtkNotebook *notebook;
page_num = 0;
notebook = GTK_NOTEBOOK (parent_widget);
while (TRUE)
{
child = gtk_notebook_get_nth_page (notebook, page_num);
if (!child)
break;
if (child == widget)
{
parent = gtk_widget_get_accessible (parent_widget);
parent = atk_object_ref_accessible_child (parent, page_num);
g_object_unref (parent);
return parent;
}
page_num++;
}
}
parent = gtk_widget_get_accessible (parent_widget);
return parent;
}
static GtkWidget *
find_label (GtkWidget *widget)
{
GList *labels;
GtkWidget *label;
GtkWidget *temp_widget;
GList *ptr;
labels = gtk_widget_list_mnemonic_labels (widget);
label = NULL;
ptr = labels;
while (ptr)
{
if (ptr->data)
{
label = ptr->data;
break;
}
ptr = ptr->next;
}
g_list_free (labels);
/* Ignore a label within a button; bug #136602 */
if (label && GTK_IS_BUTTON (widget))
{
temp_widget = label;
while (temp_widget)
{
if (temp_widget == widget)
{
label = NULL;
break;
}
temp_widget = gtk_widget_get_parent (temp_widget);
}
}
return label;
}
static AtkRelationSet *
gtk_widget_accessible_ref_relation_set (AtkObject *obj)
{
GtkWidget *widget;
AtkRelationSet *relation_set;
GtkWidget *label;
AtkObject *array[1];
AtkRelation* relation;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
if (widget == NULL)
return NULL;
relation_set = ATK_OBJECT_CLASS (gtk_widget_accessible_parent_class)->ref_relation_set (obj);
if (GTK_IS_BOX (widget))
return relation_set;
if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABELLED_BY))
{
label = find_label (widget);
if (label == NULL)
{
if (GTK_IS_BUTTON (widget) && gtk_widget_get_mapped (widget))
/*
* Handle the case where GnomeIconEntry is the mnemonic widget.
* The GtkButton which is a grandchild of the GnomeIconEntry
* should really be the mnemonic widget. See bug #133967.
*/
{
GtkWidget *temp_widget;
temp_widget = gtk_widget_get_parent (widget);
if (GTK_IS_BOX (temp_widget))
{
label = find_label (temp_widget);
if (!label)
label = find_label (gtk_widget_get_parent (temp_widget));
}
}
else if (GTK_IS_COMBO_BOX (widget))
/*
* Handle the case when GtkFileChooserButton is the mnemonic
* widget. The GtkComboBox which is a child of the
* GtkFileChooserButton should be the mnemonic widget.
* See bug #359843.
*/
{
GtkWidget *temp_widget;
temp_widget = gtk_widget_get_parent (widget);
if (GTK_IS_BOX (temp_widget))
{
label = find_label (temp_widget);
}
}
}
if (label)
{
array[0] = gtk_widget_get_accessible (label);
relation = atk_relation_new (array, 1, ATK_RELATION_LABELLED_BY);
atk_relation_set_add (relation_set, relation);
g_object_unref (relation);
}
}
return relation_set;
}
static AtkStateSet *
gtk_widget_accessible_ref_state_set (AtkObject *accessible)
{
GtkWidget *widget;
AtkStateSet *state_set;
state_set = ATK_OBJECT_CLASS (gtk_widget_accessible_parent_class)->ref_state_set (accessible);
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
if (widget == NULL)
atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
else
{
if (gtk_widget_is_sensitive (widget))
{
atk_state_set_add_state (state_set, ATK_STATE_SENSITIVE);
atk_state_set_add_state (state_set, ATK_STATE_ENABLED);
}
if (gtk_widget_get_can_focus (widget))
{
atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE);
}
/*
* We do not currently generate notifications when an ATK object
* corresponding to a GtkWidget changes visibility by being scrolled
* on or off the screen. The testcase for this is the main window
* of the testgtk application in which a set of buttons in a GtkVBox
* is in a scrolled window with a viewport.
*
* To generate the notifications we would need to do the following:
* 1) Find the GtkViewport among the ancestors of the objects
* 2) Create an accessible for the viewport
* 3) Connect to the value-changed signal on the viewport
* 4) When the signal is received we need to traverse the children
* of the viewport and check whether the children are visible or not
* visible; we may want to restrict this to the widgets for which
* accessible objects have been created.
* 5) We probably need to store a variable on_screen in the
* GtkWidgetAccessible data structure so we can determine whether
* the value has changed.
*/
if (gtk_widget_get_visible (widget))
{
atk_state_set_add_state (state_set, ATK_STATE_VISIBLE);
if (gtk_widget_accessible_on_screen (widget) &&
gtk_widget_get_mapped (widget) &&
gtk_widget_accessible_all_parents_visible (widget))
atk_state_set_add_state (state_set, ATK_STATE_SHOWING);
}
if (gtk_widget_has_focus (widget) && (widget == _focus_widget))
{
AtkObject *focus_obj;
focus_obj = g_object_get_data (G_OBJECT (accessible), "gail-focus-object");
if (focus_obj == NULL)
atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
}
if (gtk_widget_has_default (widget))
atk_state_set_add_state (state_set, ATK_STATE_DEFAULT);
if (GTK_IS_ORIENTABLE (widget))
{
if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
atk_state_set_add_state (state_set, ATK_STATE_HORIZONTAL);
else
atk_state_set_add_state (state_set, ATK_STATE_VERTICAL);
}
if (gtk_widget_get_has_tooltip (widget))
atk_state_set_add_state (state_set, ATK_STATE_HAS_TOOLTIP);
}
return state_set;
}
static gint
gtk_widget_accessible_get_index_in_parent (AtkObject *accessible)
{
GtkWidget *widget;
GtkWidget *parent_widget;
gint index;
GList *children;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
if (widget == NULL)
return -1;
if (accessible->accessible_parent)
{
AtkObject *parent;
parent = accessible->accessible_parent;
if (GTK_IS_NOTEBOOK_PAGE_ACCESSIBLE (parent))
return 0;
else
{
gint n_children, i;
gboolean found = FALSE;
n_children = atk_object_get_n_accessible_children (parent);
for (i = 0; i < n_children; i++)
{
AtkObject *child;
child = atk_object_ref_accessible_child (parent, i);
if (child == accessible)
found = TRUE;
g_object_unref (child);
if (found)
return i;
}
}
}
if (!GTK_IS_WIDGET (widget))
return -1;
parent_widget = gtk_widget_get_parent (widget);
if (!GTK_IS_CONTAINER (parent_widget))
return -1;
children = gtk_container_get_children (GTK_CONTAINER (parent_widget));
index = g_list_index (children, widget);
g_list_free (children);
return index;
}
/* This function is the default implementation for the notify_gtk
* vfunc which gets called when a property changes value on the
* GtkWidget associated with a GtkWidgetAccessible. It constructs
* an AtkPropertyValues structure and emits a “property_changed”
* signal which causes the user specified AtkPropertyChangeHandler
* to be called.
*/
static void
gtk_widget_accessible_notify_gtk (GObject *obj,
GParamSpec *pspec)
{
GtkWidget* widget = GTK_WIDGET (obj);
AtkObject* atk_obj = gtk_widget_get_accessible (widget);
AtkState state;
gboolean value;
if (g_strcmp0 (pspec->name, "has-focus") == 0)
/*
* We use focus-in-event and focus-out-event signals to catch
* focus changes so we ignore this.
*/
return;
else if (g_strcmp0 (pspec->name, "tooltip-text") == 0)
{
gtk_widget_accessible_update_tooltip (GTK_WIDGET_ACCESSIBLE (atk_obj),
widget);
return;
}
else if (g_strcmp0 (pspec->name, "visible") == 0)
{
state = ATK_STATE_VISIBLE;
value = gtk_widget_get_visible (widget);
}
else if (g_strcmp0 (pspec->name, "sensitive") == 0)
{
state = ATK_STATE_SENSITIVE;
value = gtk_widget_get_sensitive (widget);
}
else if (g_strcmp0 (pspec->name, "orientation") == 0 &&
GTK_IS_ORIENTABLE (widget))
{
GtkOrientable *orientable;
orientable = GTK_ORIENTABLE (widget);
state = ATK_STATE_HORIZONTAL;
value = (gtk_orientable_get_orientation (orientable) == GTK_ORIENTATION_HORIZONTAL);
}
else if (g_strcmp0 (pspec->name, "has-tooltip") == 0)
{
state = ATK_STATE_HAS_TOOLTIP;
value = gtk_widget_get_has_tooltip (widget);
}
else
return;
atk_object_notify_state_change (atk_obj, state, value);
if (state == ATK_STATE_SENSITIVE)
atk_object_notify_state_change (atk_obj, ATK_STATE_ENABLED, value);
if (state == ATK_STATE_HORIZONTAL)
atk_object_notify_state_change (atk_obj, ATK_STATE_VERTICAL, !value);
}
static AtkAttributeSet *
gtk_widget_accessible_get_attributes (AtkObject *obj)
{
AtkAttributeSet *attributes;
AtkAttribute *toolkit;
toolkit = g_new (AtkAttribute, 1);
toolkit->name = g_strdup ("toolkit");
toolkit->value = g_strdup ("gtk");
attributes = g_slist_append (NULL, toolkit);
return attributes;
}
static void
gtk_widget_accessible_class_init (GtkWidgetAccessibleClass *klass)
{
AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
klass->notify_gtk = gtk_widget_accessible_notify_gtk;
class->get_description = gtk_widget_accessible_get_description;
class->get_parent = gtk_widget_accessible_get_parent;
class->ref_relation_set = gtk_widget_accessible_ref_relation_set;
class->ref_state_set = gtk_widget_accessible_ref_state_set;
class->get_index_in_parent = gtk_widget_accessible_get_index_in_parent;
class->initialize = gtk_widget_accessible_initialize;
class->get_attributes = gtk_widget_accessible_get_attributes;
class->focus_event = gtk_widget_accessible_focus_event;
}
static void
gtk_widget_accessible_init (GtkWidgetAccessible *accessible)
{
accessible->priv = gtk_widget_accessible_get_instance_private (accessible);
}
static void
gtk_widget_accessible_get_extents (AtkComponent *component,
gint *x,
gint *y,
gint *width,
gint *height,
AtkCoordType coord_type)
{
GdkWindow *window;
gint x_window, y_window;
gint x_toplevel, y_toplevel;
GtkWidget *widget;
GtkAllocation allocation;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
if (widget == NULL)
return;
gtk_widget_get_allocation (widget, &allocation);
*width = allocation.width;
*height = allocation.height;
if (!gtk_widget_accessible_on_screen (widget) || (!gtk_widget_is_drawable (widget)))
{
*x = G_MININT;
*y = G_MININT;
return;
}
if (gtk_widget_get_parent (widget))
{
*x = allocation.x;
*y = allocation.y;
window = gtk_widget_get_parent_window (widget);
}
else
{
*x = 0;
*y = 0;
window = gtk_widget_get_window (widget);
}
gdk_window_get_origin (window, &x_window, &y_window);
*x += x_window;
*y += y_window;
if (coord_type == ATK_XY_WINDOW)
{
window = gdk_window_get_toplevel (gtk_widget_get_window (widget));
gdk_window_get_origin (window, &x_toplevel, &y_toplevel);
*x -= x_toplevel;
*y -= y_toplevel;
}
}
static AtkLayer
gtk_widget_accessible_get_layer (AtkComponent *component)
{
GtkWidgetAccessible *accessible = GTK_WIDGET_ACCESSIBLE (component);
return accessible->priv->layer;
}
static gboolean
gtk_widget_accessible_grab_focus (AtkComponent *component)
{
GtkWidget *widget;
GtkWidget *toplevel;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
if (!widget)
return FALSE;
if (!gtk_widget_get_can_focus (widget))
return FALSE;
gtk_widget_grab_focus (widget);
toplevel = gtk_widget_get_toplevel (widget);
if (gtk_widget_is_toplevel (toplevel))
{
#ifdef GDK_WINDOWING_X11
gtk_window_present_with_time (GTK_WINDOW (toplevel),
gdk_x11_get_server_time (gtk_widget_get_window (widget)));
#else
gtk_window_present (GTK_WINDOW (toplevel));
#endif
}
return TRUE;
}
static gboolean
gtk_widget_accessible_set_extents (AtkComponent *component,
gint x,
gint y,
gint width,
gint height,
AtkCoordType coord_type)
{
GtkWidget *widget;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
if (widget == NULL)
return FALSE;
if (!gtk_widget_is_toplevel (widget))
return FALSE;
if (coord_type == ATK_XY_WINDOW)
{
gint x_current, y_current;
GdkWindow *window = gtk_widget_get_window (widget);
gdk_window_get_origin (window, &x_current, &y_current);
x_current += x;
y_current += y;
if (x_current < 0 || y_current < 0)
return FALSE;
else
{
gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
gtk_widget_set_size_request (widget, width, height);
return TRUE;
}
}
else if (coord_type == ATK_XY_SCREEN)
{
gtk_window_move (GTK_WINDOW (widget), x, y);
gtk_widget_set_size_request (widget, width, height);
return TRUE;
}
return FALSE;
}
static gboolean
gtk_widget_accessible_set_position (AtkComponent *component,
gint x,
gint y,
AtkCoordType coord_type)
{
GtkWidget *widget;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
if (widget == NULL)
return FALSE;
if (gtk_widget_is_toplevel (widget))
{
if (coord_type == ATK_XY_WINDOW)
{
gint x_current, y_current;
GdkWindow *window = gtk_widget_get_window (widget);
gdk_window_get_origin (window, &x_current, &y_current);
x_current += x;
y_current += y;
if (x_current < 0 || y_current < 0)
return FALSE;
else
{
gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
return TRUE;
}
}
else if (coord_type == ATK_XY_SCREEN)
{
gtk_window_move (GTK_WINDOW (widget), x, y);
return TRUE;
}
}
return FALSE;
}
static gboolean
gtk_widget_accessible_set_size (AtkComponent *component,
gint width,
gint height)
{
GtkWidget *widget;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
if (widget == NULL)
return FALSE;
if (gtk_widget_is_toplevel (widget))
{
gtk_widget_set_size_request (widget, width, height);
return TRUE;
}
else
return FALSE;
}
static void
atk_component_interface_init (AtkComponentIface *iface)
{
iface->get_extents = gtk_widget_accessible_get_extents;
iface->get_layer = gtk_widget_accessible_get_layer;
iface->grab_focus = gtk_widget_accessible_grab_focus;
iface->set_extents = gtk_widget_accessible_set_extents;
iface->set_position = gtk_widget_accessible_set_position;
iface->set_size = gtk_widget_accessible_set_size;
}
/* This function checks whether the widget has an ancestor which is
* a GtkViewport and, if so, whether any part of the widget intersects
* the visible rectangle of the GtkViewport.
*/
static gboolean
gtk_widget_accessible_on_screen (GtkWidget *widget)
{
GtkAllocation allocation;
GtkWidget *viewport;
gboolean return_value;
gtk_widget_get_allocation (widget, &allocation);
if (!gtk_widget_get_mapped (widget))
return FALSE;
viewport = gtk_widget_get_ancestor (widget, GTK_TYPE_VIEWPORT);
if (viewport)
{
GtkAllocation viewport_allocation;
GtkAdjustment *adjustment;
GdkRectangle visible_rect;
gtk_widget_get_allocation (viewport, &viewport_allocation);
adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (viewport));
visible_rect.y = gtk_adjustment_get_value (adjustment);
adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (viewport));
visible_rect.x = gtk_adjustment_get_value (adjustment);
visible_rect.width = viewport_allocation.width;
visible_rect.height = viewport_allocation.height;
if (((allocation.x + allocation.width) < visible_rect.x) ||
((allocation.y + allocation.height) < visible_rect.y) ||
(allocation.x > (visible_rect.x + visible_rect.width)) ||
(allocation.y > (visible_rect.y + visible_rect.height)))
return_value = FALSE;
else
return_value = TRUE;
}
else
{
/* Check whether the widget has been placed of the screen.
* The widget may be MAPPED as when toolbar items do not
* fit on the toolbar.
*/
if (allocation.x + allocation.width <= 0 &&
allocation.y + allocation.height <= 0)
return_value = FALSE;
else
return_value = TRUE;
}
return return_value;
}
/* Checks if all the predecessors (the parent widget, his parent, etc)
* are visible Used to check properly the SHOWING state.
*/
static gboolean
gtk_widget_accessible_all_parents_visible (GtkWidget *widget)
{
GtkWidget *iter_parent = NULL;
gboolean result = TRUE;
for (iter_parent = gtk_widget_get_parent (widget); iter_parent;
iter_parent = gtk_widget_get_parent (iter_parent))
{
if (!gtk_widget_get_visible (iter_parent))
{
result = FALSE;
break;
}
}
return result;
}
void
_gtk_widget_accessible_set_layer (GtkWidgetAccessible *accessible,
AtkLayer layer)
{
accessible->priv->layer = layer;
}