1 /* GAIL - The GNOME Accessibility Implementation Library
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 "gtkwidgetaccessible.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, "visible") == 0)
479 state = ATK_STATE_VISIBLE;
480 value = gtk_widget_get_visible (widget);
482 else if (g_strcmp0 (pspec->name, "sensitive") == 0)
484 state = ATK_STATE_SENSITIVE;
485 value = gtk_widget_get_sensitive (widget);
487 else if (g_strcmp0 (pspec->name, "orientation") == 0 &&
488 GTK_IS_ORIENTABLE (widget))
490 GtkOrientable *orientable;
492 orientable = GTK_ORIENTABLE (widget);
494 state = ATK_STATE_HORIZONTAL;
495 value = (gtk_orientable_get_orientation (orientable) == GTK_ORIENTATION_HORIZONTAL);
497 else if (g_strcmp0 (pspec->name, "tooltip-text") == 0)
499 gtk_widget_accessible_update_tooltip (GTK_WIDGET_ACCESSIBLE (atk_obj),
504 atk_object_notify_state_change (atk_obj, state, value);
505 if (state == ATK_STATE_SENSITIVE)
506 atk_object_notify_state_change (atk_obj, ATK_STATE_ENABLED, value);
508 if (state == ATK_STATE_HORIZONTAL)
509 atk_object_notify_state_change (atk_obj, ATK_STATE_VERTICAL, !value);
512 static AtkAttributeSet *
513 gtk_widget_accessible_get_attributes (AtkObject *obj)
515 AtkAttributeSet *attributes;
516 AtkAttribute *toolkit;
518 toolkit = g_new (AtkAttribute, 1);
519 toolkit->name = g_strdup ("toolkit");
520 toolkit->value = g_strdup ("gtk");
522 attributes = g_slist_append (NULL, toolkit);
528 _gtk_widget_accessible_class_init (GtkWidgetAccessibleClass *klass)
530 AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
532 klass->notify_gtk = gtk_widget_accessible_notify_gtk;
534 class->get_description = gtk_widget_accessible_get_description;
535 class->get_parent = gtk_widget_accessible_get_parent;
536 class->ref_relation_set = gtk_widget_accessible_ref_relation_set;
537 class->ref_state_set = gtk_widget_accessible_ref_state_set;
538 class->get_index_in_parent = gtk_widget_accessible_get_index_in_parent;
539 class->initialize = gtk_widget_accessible_initialize;
540 class->get_attributes = gtk_widget_accessible_get_attributes;
541 class->focus_event = gtk_widget_accessible_focus_event;
543 g_type_class_add_private (klass, sizeof (GtkWidgetAccessiblePrivate));
547 _gtk_widget_accessible_init (GtkWidgetAccessible *accessible)
549 accessible->priv = G_TYPE_INSTANCE_GET_PRIVATE (accessible,
550 GTK_TYPE_WIDGET_ACCESSIBLE,
551 GtkWidgetAccessiblePrivate);
555 gtk_widget_accessible_get_extents (AtkComponent *component,
560 AtkCoordType coord_type)
563 gint x_window, y_window;
564 gint x_toplevel, y_toplevel;
566 GtkAllocation allocation;
568 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
572 gtk_widget_get_allocation (widget, &allocation);
573 *width = allocation.width;
574 *height = allocation.height;
575 if (!gtk_widget_accessible_on_screen (widget) || (!gtk_widget_is_drawable (widget)))
582 if (gtk_widget_get_parent (widget))
586 window = gtk_widget_get_parent_window (widget);
592 window = gtk_widget_get_window (widget);
594 gdk_window_get_origin (window, &x_window, &y_window);
598 if (coord_type == ATK_XY_WINDOW)
600 window = gdk_window_get_toplevel (gtk_widget_get_window (widget));
601 gdk_window_get_origin (window, &x_toplevel, &y_toplevel);
609 gtk_widget_accessible_get_size (AtkComponent *component,
615 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
619 *width = gtk_widget_get_allocated_width (widget);
620 *height = gtk_widget_get_allocated_height (widget);
624 gtk_widget_accessible_get_layer (AtkComponent *component)
626 GtkWidgetAccessible *accessible = GTK_WIDGET_ACCESSIBLE (component);
628 return accessible->priv->layer;
632 gtk_widget_accessible_grab_focus (AtkComponent *component)
637 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
641 if (!gtk_widget_get_can_focus (widget))
644 gtk_widget_grab_focus (widget);
645 toplevel = gtk_widget_get_toplevel (widget);
646 if (gtk_widget_is_toplevel (toplevel))
648 #ifdef GDK_WINDOWING_X11
649 gtk_window_present_with_time (GTK_WINDOW (toplevel),
650 gdk_x11_get_server_time (gtk_widget_get_window (widget)));
652 gtk_window_present (GTK_WINDOW (toplevel));
659 gtk_widget_accessible_set_extents (AtkComponent *component,
664 AtkCoordType coord_type)
668 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
672 if (!gtk_widget_is_toplevel (widget))
675 if (coord_type == ATK_XY_WINDOW)
677 gint x_current, y_current;
678 GdkWindow *window = gtk_widget_get_window (widget);
680 gdk_window_get_origin (window, &x_current, &y_current);
683 if (x_current < 0 || y_current < 0)
687 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
688 gtk_widget_set_size_request (widget, width, height);
692 else if (coord_type == ATK_XY_SCREEN)
694 gtk_window_move (GTK_WINDOW (widget), x, y);
695 gtk_widget_set_size_request (widget, width, height);
702 gtk_widget_accessible_set_position (AtkComponent *component,
705 AtkCoordType coord_type)
709 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
713 if (gtk_widget_is_toplevel (widget))
715 if (coord_type == ATK_XY_WINDOW)
717 gint x_current, y_current;
718 GdkWindow *window = gtk_widget_get_window (widget);
720 gdk_window_get_origin (window, &x_current, &y_current);
723 if (x_current < 0 || y_current < 0)
727 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
731 else if (coord_type == ATK_XY_SCREEN)
733 gtk_window_move (GTK_WINDOW (widget), x, y);
741 gtk_widget_accessible_set_size (AtkComponent *component,
747 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
751 if (gtk_widget_is_toplevel (widget))
753 gtk_widget_set_size_request (widget, width, height);
761 atk_component_interface_init (AtkComponentIface *iface)
763 iface->get_extents = gtk_widget_accessible_get_extents;
764 iface->get_size = gtk_widget_accessible_get_size;
765 iface->get_layer = gtk_widget_accessible_get_layer;
766 iface->grab_focus = gtk_widget_accessible_grab_focus;
767 iface->set_extents = gtk_widget_accessible_set_extents;
768 iface->set_position = gtk_widget_accessible_set_position;
769 iface->set_size = gtk_widget_accessible_set_size;
772 /* This function checks whether the widget has an ancestor which is
773 * a GtkViewport and, if so, whether any part of the widget intersects
774 * the visible rectangle of the GtkViewport.
777 gtk_widget_accessible_on_screen (GtkWidget *widget)
779 GtkAllocation allocation;
781 gboolean return_value;
783 gtk_widget_get_allocation (widget, &allocation);
785 viewport = gtk_widget_get_ancestor (widget, GTK_TYPE_VIEWPORT);
788 GtkAllocation viewport_allocation;
789 GtkAdjustment *adjustment;
790 GdkRectangle visible_rect;
792 gtk_widget_get_allocation (viewport, &viewport_allocation);
794 adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (viewport));
795 visible_rect.y = gtk_adjustment_get_value (adjustment);
796 adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (viewport));
797 visible_rect.x = gtk_adjustment_get_value (adjustment);
798 visible_rect.width = viewport_allocation.width;
799 visible_rect.height = viewport_allocation.height;
801 if (((allocation.x + allocation.width) < visible_rect.x) ||
802 ((allocation.y + allocation.height) < visible_rect.y) ||
803 (allocation.x > (visible_rect.x + visible_rect.width)) ||
804 (allocation.y > (visible_rect.y + visible_rect.height)))
805 return_value = FALSE;
811 /* Check whether the widget has been placed of the screen.
812 * The widget may be MAPPED as when toolbar items do not
813 * fit on the toolbar.
815 if (allocation.x + allocation.width <= 0 &&
816 allocation.y + allocation.height <= 0)
817 return_value = FALSE;
825 /* Checks if all the predecessors (the parent widget, his parent, etc)
826 * are visible Used to check properly the SHOWING state.
829 gtk_widget_accessible_all_parents_visible (GtkWidget *widget)
831 GtkWidget *iter_parent = NULL;
832 gboolean result = TRUE;
834 for (iter_parent = gtk_widget_get_parent (widget); iter_parent;
835 iter_parent = gtk_widget_get_parent (iter_parent))
837 if (!gtk_widget_get_visible (iter_parent))
848 _gtk_widget_accessible_set_layer (GtkWidgetAccessible *accessible,
851 accessible->priv->layer = layer;