1 /* GTK+ - accessibility implementations
2 * Copyright 2001, 2002, 2003 Sun Microsystems Inc.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
21 #ifdef GDK_WINDOWING_X11
22 #include <gdk/x11/gdkx.h>
24 #include "gtkwidgetaccessibleprivate.h"
25 #include "gtknotebookpageaccessible.h"
27 struct _GtkWidgetAccessiblePrivate
32 #define TOOLTIP_KEY "tooltip"
34 extern GtkWidget *_focus_widget;
37 static gboolean gtk_widget_accessible_on_screen (GtkWidget *widget);
38 static gboolean gtk_widget_accessible_all_parents_visible (GtkWidget *widget);
40 static void atk_component_interface_init (AtkComponentIface *iface);
42 G_DEFINE_TYPE_WITH_CODE (GtkWidgetAccessible, gtk_widget_accessible, GTK_TYPE_ACCESSIBLE,
43 G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, atk_component_interface_init))
45 /* Translate GtkWidget::focus-in/out-event to AtkObject::focus-event */
47 focus_cb (GtkWidget *widget,
52 obj = gtk_widget_get_accessible (widget);
54 g_signal_emit_by_name (obj, "focus-event", event->in);
59 /* Translate GtkWidget property change notification to the notify_gtk vfunc */
61 notify_cb (GObject *obj,
64 GtkWidgetAccessible *widget;
65 GtkWidgetAccessibleClass *klass;
67 widget = GTK_WIDGET_ACCESSIBLE (gtk_widget_get_accessible (GTK_WIDGET (obj)));
68 klass = GTK_WIDGET_ACCESSIBLE_GET_CLASS (widget);
69 if (klass->notify_gtk)
70 klass->notify_gtk (obj, pspec);
73 /* Translate GtkWidget::size-allocate to AtkComponent::bounds-changed */
75 size_allocate_cb (GtkWidget *widget,
76 GtkAllocation *allocation)
78 AtkObject* accessible;
81 accessible = gtk_widget_get_accessible (widget);
82 if (ATK_IS_COMPONENT (accessible))
84 rect.x = allocation->x;
85 rect.y = allocation->y;
86 rect.width = allocation->width;
87 rect.height = allocation->height;
88 g_signal_emit_by_name (accessible, "bounds-changed", &rect);
92 /* Translate GtkWidget mapped state into AtkObject showing */
94 map_cb (GtkWidget *widget)
96 AtkObject *accessible;
98 accessible = gtk_widget_get_accessible (widget);
99 atk_object_notify_state_change (accessible, ATK_STATE_SHOWING,
100 gtk_widget_get_mapped (widget));
105 gtk_widget_accessible_focus_event (AtkObject *obj,
108 AtkObject *focus_obj;
110 focus_obj = g_object_get_data (G_OBJECT (obj), "gail-focus-object");
111 if (focus_obj == NULL)
113 atk_object_notify_state_change (focus_obj, ATK_STATE_FOCUSED, focus_in);
117 gtk_widget_accessible_update_tooltip (GtkWidgetAccessible *accessible,
120 g_object_set_data_full (G_OBJECT (accessible),
122 gtk_widget_get_tooltip_text (widget),
127 gtk_widget_accessible_initialize (AtkObject *obj,
132 widget = GTK_WIDGET (data);
134 g_signal_connect_after (widget, "focus-in-event", G_CALLBACK (focus_cb), NULL);
135 g_signal_connect_after (widget, "focus-out-event", G_CALLBACK (focus_cb), NULL);
136 g_signal_connect (widget, "notify", G_CALLBACK (notify_cb), NULL);
137 g_signal_connect (widget, "size-allocate", G_CALLBACK (size_allocate_cb), NULL);
138 g_signal_connect (widget, "map", G_CALLBACK (map_cb), NULL);
139 g_signal_connect (widget, "unmap", G_CALLBACK (map_cb), NULL);
141 GTK_WIDGET_ACCESSIBLE (obj)->priv->layer = ATK_LAYER_WIDGET;
142 obj->role = ATK_ROLE_UNKNOWN;
144 gtk_widget_accessible_update_tooltip (GTK_WIDGET_ACCESSIBLE (obj), widget);
148 gtk_widget_accessible_get_description (AtkObject *accessible)
152 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
156 if (accessible->description)
157 return accessible->description;
159 return g_object_get_data (G_OBJECT (accessible), TOOLTIP_KEY);
163 gtk_widget_accessible_get_parent (AtkObject *accessible)
166 GtkWidget *widget, *parent_widget;
168 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
172 parent = accessible->accessible_parent;
176 parent_widget = gtk_widget_get_parent (widget);
177 if (parent_widget == NULL)
180 /* For a widget whose parent is a GtkNoteBook, we return the
181 * accessible object corresponding the GtkNotebookPage containing
182 * the widget as the accessible parent.
184 if (GTK_IS_NOTEBOOK (parent_widget))
188 GtkNotebook *notebook;
191 notebook = GTK_NOTEBOOK (parent_widget);
194 child = gtk_notebook_get_nth_page (notebook, page_num);
199 parent = gtk_widget_get_accessible (parent_widget);
200 parent = atk_object_ref_accessible_child (parent, page_num);
201 g_object_unref (parent);
207 parent = gtk_widget_get_accessible (parent_widget);
212 find_label (GtkWidget *widget)
216 GtkWidget *temp_widget;
219 labels = gtk_widget_list_mnemonic_labels (widget);
231 g_list_free (labels);
233 /* Ignore a label within a button; bug #136602 */
234 if (label && GTK_IS_BUTTON (widget))
239 if (temp_widget == widget)
244 temp_widget = gtk_widget_get_parent (temp_widget);
250 static AtkRelationSet *
251 gtk_widget_accessible_ref_relation_set (AtkObject *obj)
254 AtkRelationSet *relation_set;
257 AtkRelation* relation;
259 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
263 relation_set = ATK_OBJECT_CLASS (gtk_widget_accessible_parent_class)->ref_relation_set (obj);
265 if (GTK_IS_BOX (widget))
268 if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABELLED_BY))
270 label = find_label (widget);
273 if (GTK_IS_BUTTON (widget))
275 * Handle the case where GnomeIconEntry is the mnemonic widget.
276 * The GtkButton which is a grandchild of the GnomeIconEntry
277 * should really be the mnemonic widget. See bug #133967.
280 GtkWidget *temp_widget;
282 temp_widget = gtk_widget_get_parent (widget);
284 if (GTK_IS_ALIGNMENT (temp_widget))
286 temp_widget = gtk_widget_get_parent (temp_widget);
287 if (GTK_IS_BOX (temp_widget))
289 label = find_label (temp_widget);
291 label = find_label (gtk_widget_get_parent (temp_widget));
295 else if (GTK_IS_COMBO_BOX (widget))
297 * Handle the case when GtkFileChooserButton is the mnemonic
298 * widget. The GtkComboBox which is a child of the
299 * GtkFileChooserButton should be the mnemonic widget.
303 GtkWidget *temp_widget;
305 temp_widget = gtk_widget_get_parent (widget);
306 if (GTK_IS_BOX (temp_widget))
308 label = find_label (temp_widget);
315 array[0] = gtk_widget_get_accessible (label);
317 relation = atk_relation_new (array, 1, ATK_RELATION_LABELLED_BY);
318 atk_relation_set_add (relation_set, relation);
319 g_object_unref (relation);
327 gtk_widget_accessible_ref_state_set (AtkObject *accessible)
330 AtkStateSet *state_set;
332 state_set = ATK_OBJECT_CLASS (gtk_widget_accessible_parent_class)->ref_state_set (accessible);
334 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
336 atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
339 if (gtk_widget_is_sensitive (widget))
341 atk_state_set_add_state (state_set, ATK_STATE_SENSITIVE);
342 atk_state_set_add_state (state_set, ATK_STATE_ENABLED);
345 if (gtk_widget_get_can_focus (widget))
347 atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE);
350 * We do not currently generate notifications when an ATK object
351 * corresponding to a GtkWidget changes visibility by being scrolled
352 * on or off the screen. The testcase for this is the main window
353 * of the testgtk application in which a set of buttons in a GtkVBox
354 * is in a scrolled window with a viewport.
356 * To generate the notifications we would need to do the following:
357 * 1) Find the GtkViewport among the ancestors of the objects
358 * 2) Create an accessible for the viewport
359 * 3) Connect to the value-changed signal on the viewport
360 * 4) When the signal is received we need to traverse the children
361 * of the viewport and check whether the children are visible or not
362 * visible; we may want to restrict this to the widgets for which
363 * accessible objects have been created.
364 * 5) We probably need to store a variable on_screen in the
365 * GtkWidgetAccessible data structure so we can determine whether
366 * the value has changed.
368 if (gtk_widget_get_visible (widget))
370 atk_state_set_add_state (state_set, ATK_STATE_VISIBLE);
371 if (gtk_widget_accessible_on_screen (widget) &&
372 gtk_widget_get_mapped (widget) &&
373 gtk_widget_accessible_all_parents_visible (widget))
374 atk_state_set_add_state (state_set, ATK_STATE_SHOWING);
377 if (gtk_widget_has_focus (widget) && (widget == _focus_widget))
379 AtkObject *focus_obj;
381 focus_obj = g_object_get_data (G_OBJECT (accessible), "gail-focus-object");
382 if (focus_obj == NULL)
383 atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
386 if (gtk_widget_has_default (widget))
387 atk_state_set_add_state (state_set, ATK_STATE_DEFAULT);
389 if (GTK_IS_ORIENTABLE (widget))
391 if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
392 atk_state_set_add_state (state_set, ATK_STATE_HORIZONTAL);
394 atk_state_set_add_state (state_set, ATK_STATE_VERTICAL);
401 gtk_widget_accessible_get_index_in_parent (AtkObject *accessible)
404 GtkWidget *parent_widget;
408 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
413 if (accessible->accessible_parent)
417 parent = accessible->accessible_parent;
419 if (GTK_IS_NOTEBOOK_PAGE_ACCESSIBLE (parent))
424 gboolean found = FALSE;
426 n_children = atk_object_get_n_accessible_children (parent);
427 for (i = 0; i < n_children; i++)
431 child = atk_object_ref_accessible_child (parent, i);
432 if (child == accessible)
435 g_object_unref (child);
442 if (!GTK_IS_WIDGET (widget))
444 parent_widget = gtk_widget_get_parent (widget);
445 if (!GTK_IS_CONTAINER (parent_widget))
448 children = gtk_container_get_children (GTK_CONTAINER (parent_widget));
450 index = g_list_index (children, widget);
451 g_list_free (children);
455 /* This function is the default implementation for the notify_gtk
456 * vfunc which gets called when a property changes value on the
457 * GtkWidget associated with a GtkWidgetAccessible. It constructs
458 * an AtkPropertyValues structure and emits a "property_changed"
459 * signal which causes the user specified AtkPropertyChangeHandler
463 gtk_widget_accessible_notify_gtk (GObject *obj,
466 GtkWidget* widget = GTK_WIDGET (obj);
467 AtkObject* atk_obj = gtk_widget_get_accessible (widget);
471 if (g_strcmp0 (pspec->name, "has-focus") == 0)
473 * We use focus-in-event and focus-out-event signals to catch
474 * focus changes so we ignore this.
477 else if (g_strcmp0 (pspec->name, "tooltip-text") == 0)
479 gtk_widget_accessible_update_tooltip (GTK_WIDGET_ACCESSIBLE (atk_obj),
483 else if (g_strcmp0 (pspec->name, "visible") == 0)
485 state = ATK_STATE_VISIBLE;
486 value = gtk_widget_get_visible (widget);
488 else if (g_strcmp0 (pspec->name, "sensitive") == 0)
490 state = ATK_STATE_SENSITIVE;
491 value = gtk_widget_get_sensitive (widget);
493 else if (g_strcmp0 (pspec->name, "orientation") == 0 &&
494 GTK_IS_ORIENTABLE (widget))
496 GtkOrientable *orientable;
498 orientable = GTK_ORIENTABLE (widget);
500 state = ATK_STATE_HORIZONTAL;
501 value = (gtk_orientable_get_orientation (orientable) == GTK_ORIENTATION_HORIZONTAL);
506 atk_object_notify_state_change (atk_obj, state, value);
507 if (state == ATK_STATE_SENSITIVE)
508 atk_object_notify_state_change (atk_obj, ATK_STATE_ENABLED, value);
510 if (state == ATK_STATE_HORIZONTAL)
511 atk_object_notify_state_change (atk_obj, ATK_STATE_VERTICAL, !value);
514 static AtkAttributeSet *
515 gtk_widget_accessible_get_attributes (AtkObject *obj)
517 AtkAttributeSet *attributes;
518 AtkAttribute *toolkit;
520 toolkit = g_new (AtkAttribute, 1);
521 toolkit->name = g_strdup ("toolkit");
522 toolkit->value = g_strdup ("gtk");
524 attributes = g_slist_append (NULL, toolkit);
530 gtk_widget_accessible_class_init (GtkWidgetAccessibleClass *klass)
532 AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
534 klass->notify_gtk = gtk_widget_accessible_notify_gtk;
536 class->get_description = gtk_widget_accessible_get_description;
537 class->get_parent = gtk_widget_accessible_get_parent;
538 class->ref_relation_set = gtk_widget_accessible_ref_relation_set;
539 class->ref_state_set = gtk_widget_accessible_ref_state_set;
540 class->get_index_in_parent = gtk_widget_accessible_get_index_in_parent;
541 class->initialize = gtk_widget_accessible_initialize;
542 class->get_attributes = gtk_widget_accessible_get_attributes;
543 class->focus_event = gtk_widget_accessible_focus_event;
545 g_type_class_add_private (klass, sizeof (GtkWidgetAccessiblePrivate));
549 gtk_widget_accessible_init (GtkWidgetAccessible *accessible)
551 accessible->priv = G_TYPE_INSTANCE_GET_PRIVATE (accessible,
552 GTK_TYPE_WIDGET_ACCESSIBLE,
553 GtkWidgetAccessiblePrivate);
557 gtk_widget_accessible_get_extents (AtkComponent *component,
562 AtkCoordType coord_type)
565 gint x_window, y_window;
566 gint x_toplevel, y_toplevel;
568 GtkAllocation allocation;
570 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
574 gtk_widget_get_allocation (widget, &allocation);
575 *width = allocation.width;
576 *height = allocation.height;
577 if (!gtk_widget_accessible_on_screen (widget) || (!gtk_widget_is_drawable (widget)))
584 if (gtk_widget_get_parent (widget))
588 window = gtk_widget_get_parent_window (widget);
594 window = gtk_widget_get_window (widget);
596 gdk_window_get_origin (window, &x_window, &y_window);
600 if (coord_type == ATK_XY_WINDOW)
602 window = gdk_window_get_toplevel (gtk_widget_get_window (widget));
603 gdk_window_get_origin (window, &x_toplevel, &y_toplevel);
611 gtk_widget_accessible_get_size (AtkComponent *component,
617 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
621 *width = gtk_widget_get_allocated_width (widget);
622 *height = gtk_widget_get_allocated_height (widget);
626 gtk_widget_accessible_get_layer (AtkComponent *component)
628 GtkWidgetAccessible *accessible = GTK_WIDGET_ACCESSIBLE (component);
630 return accessible->priv->layer;
634 gtk_widget_accessible_grab_focus (AtkComponent *component)
639 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
643 if (!gtk_widget_get_can_focus (widget))
646 gtk_widget_grab_focus (widget);
647 toplevel = gtk_widget_get_toplevel (widget);
648 if (gtk_widget_is_toplevel (toplevel))
650 #ifdef GDK_WINDOWING_X11
651 gtk_window_present_with_time (GTK_WINDOW (toplevel),
652 gdk_x11_get_server_time (gtk_widget_get_window (widget)));
654 gtk_window_present (GTK_WINDOW (toplevel));
661 gtk_widget_accessible_set_extents (AtkComponent *component,
666 AtkCoordType coord_type)
670 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
674 if (!gtk_widget_is_toplevel (widget))
677 if (coord_type == ATK_XY_WINDOW)
679 gint x_current, y_current;
680 GdkWindow *window = gtk_widget_get_window (widget);
682 gdk_window_get_origin (window, &x_current, &y_current);
685 if (x_current < 0 || y_current < 0)
689 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
690 gtk_widget_set_size_request (widget, width, height);
694 else if (coord_type == ATK_XY_SCREEN)
696 gtk_window_move (GTK_WINDOW (widget), x, y);
697 gtk_widget_set_size_request (widget, width, height);
704 gtk_widget_accessible_set_position (AtkComponent *component,
707 AtkCoordType coord_type)
711 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
715 if (gtk_widget_is_toplevel (widget))
717 if (coord_type == ATK_XY_WINDOW)
719 gint x_current, y_current;
720 GdkWindow *window = gtk_widget_get_window (widget);
722 gdk_window_get_origin (window, &x_current, &y_current);
725 if (x_current < 0 || y_current < 0)
729 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
733 else if (coord_type == ATK_XY_SCREEN)
735 gtk_window_move (GTK_WINDOW (widget), x, y);
743 gtk_widget_accessible_set_size (AtkComponent *component,
749 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
753 if (gtk_widget_is_toplevel (widget))
755 gtk_widget_set_size_request (widget, width, height);
763 atk_component_interface_init (AtkComponentIface *iface)
765 iface->get_extents = gtk_widget_accessible_get_extents;
766 iface->get_size = gtk_widget_accessible_get_size;
767 iface->get_layer = gtk_widget_accessible_get_layer;
768 iface->grab_focus = gtk_widget_accessible_grab_focus;
769 iface->set_extents = gtk_widget_accessible_set_extents;
770 iface->set_position = gtk_widget_accessible_set_position;
771 iface->set_size = gtk_widget_accessible_set_size;
774 /* This function checks whether the widget has an ancestor which is
775 * a GtkViewport and, if so, whether any part of the widget intersects
776 * the visible rectangle of the GtkViewport.
779 gtk_widget_accessible_on_screen (GtkWidget *widget)
781 GtkAllocation allocation;
783 gboolean return_value;
785 gtk_widget_get_allocation (widget, &allocation);
787 viewport = gtk_widget_get_ancestor (widget, GTK_TYPE_VIEWPORT);
790 GtkAllocation viewport_allocation;
791 GtkAdjustment *adjustment;
792 GdkRectangle visible_rect;
794 gtk_widget_get_allocation (viewport, &viewport_allocation);
796 adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (viewport));
797 visible_rect.y = gtk_adjustment_get_value (adjustment);
798 adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (viewport));
799 visible_rect.x = gtk_adjustment_get_value (adjustment);
800 visible_rect.width = viewport_allocation.width;
801 visible_rect.height = viewport_allocation.height;
803 if (((allocation.x + allocation.width) < visible_rect.x) ||
804 ((allocation.y + allocation.height) < visible_rect.y) ||
805 (allocation.x > (visible_rect.x + visible_rect.width)) ||
806 (allocation.y > (visible_rect.y + visible_rect.height)))
807 return_value = FALSE;
813 /* Check whether the widget has been placed of the screen.
814 * The widget may be MAPPED as when toolbar items do not
815 * fit on the toolbar.
817 if (allocation.x + allocation.width <= 0 &&
818 allocation.y + allocation.height <= 0)
819 return_value = FALSE;
827 /* Checks if all the predecessors (the parent widget, his parent, etc)
828 * are visible Used to check properly the SHOWING state.
831 gtk_widget_accessible_all_parents_visible (GtkWidget *widget)
833 GtkWidget *iter_parent = NULL;
834 gboolean result = TRUE;
836 for (iter_parent = gtk_widget_get_parent (widget); iter_parent;
837 iter_parent = gtk_widget_get_parent (iter_parent))
839 if (!gtk_widget_get_visible (iter_parent))
850 _gtk_widget_accessible_set_layer (GtkWidgetAccessible *accessible,
853 accessible->priv->layer = layer;