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 #define TOOLTIP_KEY "tooltip"
29 extern GtkWidget *_focus_widget;
32 static gboolean gtk_widget_accessible_on_screen (GtkWidget *widget);
33 static gboolean gtk_widget_accessible_all_parents_visible (GtkWidget *widget);
35 static void atk_component_interface_init (AtkComponentIface *iface);
37 G_DEFINE_TYPE_WITH_CODE (GtkWidgetAccessible, _gtk_widget_accessible, GTK_TYPE_ACCESSIBLE,
38 G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, atk_component_interface_init))
40 /* Translate GtkWidget::focus-in/out-event to AtkObject::focus-event */
42 focus_cb (GtkWidget *widget,
47 obj = gtk_widget_get_accessible (widget);
49 g_signal_emit_by_name (obj, "focus-event", event->in);
54 /* Translate GtkWidget property change notification to the notify_gtk vfunc */
56 notify_cb (GObject *obj,
59 GtkWidgetAccessible *widget;
60 GtkWidgetAccessibleClass *klass;
62 widget = GTK_WIDGET_ACCESSIBLE (gtk_widget_get_accessible (GTK_WIDGET (obj)));
63 klass = GTK_WIDGET_ACCESSIBLE_GET_CLASS (widget);
64 if (klass->notify_gtk)
65 klass->notify_gtk (obj, pspec);
68 /* Translate GtkWidget::size-allocate to AtkComponent::bounds-changed */
70 size_allocate_cb (GtkWidget *widget,
71 GtkAllocation *allocation)
73 AtkObject* accessible;
76 accessible = gtk_widget_get_accessible (widget);
77 if (ATK_IS_COMPONENT (accessible))
79 rect.x = allocation->x;
80 rect.y = allocation->y;
81 rect.width = allocation->width;
82 rect.height = allocation->height;
83 g_signal_emit_by_name (accessible, "bounds-changed", &rect);
87 /* Translate GtkWidget mapped state into AtkObject showing */
89 map_cb (GtkWidget *widget)
91 AtkObject *accessible;
93 accessible = gtk_widget_get_accessible (widget);
94 atk_object_notify_state_change (accessible, ATK_STATE_SHOWING,
95 gtk_widget_get_mapped (widget));
100 gtk_widget_accessible_focus_event (AtkObject *obj,
103 AtkObject *focus_obj;
105 focus_obj = g_object_get_data (G_OBJECT (obj), "gail-focus-object");
106 if (focus_obj == NULL)
108 atk_object_notify_state_change (focus_obj, ATK_STATE_FOCUSED, focus_in);
112 gtk_widget_accessible_update_tooltip (GtkWidgetAccessible *accessible,
115 g_object_set_data_full (G_OBJECT (accessible),
117 gtk_widget_get_tooltip_text (widget),
122 gtk_widget_accessible_initialize (AtkObject *obj,
127 widget = GTK_WIDGET (data);
129 g_signal_connect_after (widget, "focus-in-event", G_CALLBACK (focus_cb), NULL);
130 g_signal_connect_after (widget, "focus-out-event", G_CALLBACK (focus_cb), NULL);
131 g_signal_connect (widget, "notify", G_CALLBACK (notify_cb), NULL);
132 g_signal_connect (widget, "size-allocate", G_CALLBACK (size_allocate_cb), NULL);
133 g_signal_connect (widget, "map", G_CALLBACK (map_cb), NULL);
134 g_signal_connect (widget, "unmap", G_CALLBACK (map_cb), NULL);
136 GTK_WIDGET_ACCESSIBLE (obj)->layer = ATK_LAYER_WIDGET;
137 obj->role = ATK_ROLE_UNKNOWN;
139 gtk_widget_accessible_update_tooltip (GTK_WIDGET_ACCESSIBLE (obj), widget);
143 gtk_widget_accessible_get_description (AtkObject *accessible)
147 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
151 if (accessible->description)
152 return accessible->description;
154 return g_object_get_data (G_OBJECT (accessible), TOOLTIP_KEY);
158 gtk_widget_accessible_get_parent (AtkObject *accessible)
161 GtkWidget *widget, *parent_widget;
163 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
167 parent = accessible->accessible_parent;
171 parent_widget = gtk_widget_get_parent (widget);
172 if (parent_widget == NULL)
175 /* For a widget whose parent is a GtkNoteBook, we return the
176 * accessible object corresponding the GtkNotebookPage containing
177 * the widget as the accessible parent.
179 if (GTK_IS_NOTEBOOK (parent_widget))
183 GtkNotebook *notebook;
186 notebook = GTK_NOTEBOOK (parent_widget);
189 child = gtk_notebook_get_nth_page (notebook, page_num);
194 parent = gtk_widget_get_accessible (parent_widget);
195 parent = atk_object_ref_accessible_child (parent, page_num);
196 g_object_unref (parent);
202 parent = gtk_widget_get_accessible (parent_widget);
207 find_label (GtkWidget *widget)
211 GtkWidget *temp_widget;
214 labels = gtk_widget_list_mnemonic_labels (widget);
226 g_list_free (labels);
228 /* Ignore a label within a button; bug #136602 */
229 if (label && GTK_IS_BUTTON (widget))
234 if (temp_widget == widget)
239 temp_widget = gtk_widget_get_parent (temp_widget);
245 static AtkRelationSet *
246 gtk_widget_accessible_ref_relation_set (AtkObject *obj)
249 AtkRelationSet *relation_set;
252 AtkRelation* relation;
254 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
258 relation_set = ATK_OBJECT_CLASS (_gtk_widget_accessible_parent_class)->ref_relation_set (obj);
260 if (GTK_IS_BOX (widget))
263 if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABELLED_BY))
265 label = find_label (widget);
268 if (GTK_IS_BUTTON (widget))
270 * Handle the case where GnomeIconEntry is the mnemonic widget.
271 * The GtkButton which is a grandchild of the GnomeIconEntry
272 * should really be the mnemonic widget. See bug #133967.
275 GtkWidget *temp_widget;
277 temp_widget = gtk_widget_get_parent (widget);
279 if (GTK_IS_ALIGNMENT (temp_widget))
281 temp_widget = gtk_widget_get_parent (temp_widget);
282 if (GTK_IS_BOX (temp_widget))
284 label = find_label (temp_widget);
286 label = find_label (gtk_widget_get_parent (temp_widget));
290 else if (GTK_IS_COMBO_BOX (widget))
292 * Handle the case when GtkFileChooserButton is the mnemonic
293 * widget. The GtkComboBox which is a child of the
294 * GtkFileChooserButton should be the mnemonic widget.
298 GtkWidget *temp_widget;
300 temp_widget = gtk_widget_get_parent (widget);
301 if (GTK_IS_BOX (temp_widget))
303 label = find_label (temp_widget);
310 array[0] = gtk_widget_get_accessible (label);
312 relation = atk_relation_new (array, 1, ATK_RELATION_LABELLED_BY);
313 atk_relation_set_add (relation_set, relation);
314 g_object_unref (relation);
322 gtk_widget_accessible_ref_state_set (AtkObject *accessible)
325 AtkStateSet *state_set;
327 state_set = ATK_OBJECT_CLASS (_gtk_widget_accessible_parent_class)->ref_state_set (accessible);
329 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
331 atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
334 if (gtk_widget_is_sensitive (widget))
336 atk_state_set_add_state (state_set, ATK_STATE_SENSITIVE);
337 atk_state_set_add_state (state_set, ATK_STATE_ENABLED);
340 if (gtk_widget_get_can_focus (widget))
342 atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE);
345 * We do not currently generate notifications when an ATK object
346 * corresponding to a GtkWidget changes visibility by being scrolled
347 * on or off the screen. The testcase for this is the main window
348 * of the testgtk application in which a set of buttons in a GtkVBox
349 * is in a scrolled window with a viewport.
351 * To generate the notifications we would need to do the following:
352 * 1) Find the GtkViewport among the ancestors of the objects
353 * 2) Create an accessible for the viewport
354 * 3) Connect to the value-changed signal on the viewport
355 * 4) When the signal is received we need to traverse the children
356 * of the viewport and check whether the children are visible or not
357 * visible; we may want to restrict this to the widgets for which
358 * accessible objects have been created.
359 * 5) We probably need to store a variable on_screen in the
360 * GtkWidgetAccessible data structure so we can determine whether
361 * the value has changed.
363 if (gtk_widget_get_visible (widget))
365 atk_state_set_add_state (state_set, ATK_STATE_VISIBLE);
366 if (gtk_widget_accessible_on_screen (widget) &&
367 gtk_widget_get_mapped (widget) &&
368 gtk_widget_accessible_all_parents_visible (widget))
369 atk_state_set_add_state (state_set, ATK_STATE_SHOWING);
372 if (gtk_widget_has_focus (widget) && (widget == _focus_widget))
374 AtkObject *focus_obj;
376 focus_obj = g_object_get_data (G_OBJECT (accessible), "gail-focus-object");
377 if (focus_obj == NULL)
378 atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
381 if (gtk_widget_has_default (widget))
382 atk_state_set_add_state (state_set, ATK_STATE_DEFAULT);
384 if (GTK_IS_ORIENTABLE (widget))
386 if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
387 atk_state_set_add_state (state_set, ATK_STATE_HORIZONTAL);
389 atk_state_set_add_state (state_set, ATK_STATE_VERTICAL);
396 gtk_widget_accessible_get_index_in_parent (AtkObject *accessible)
399 GtkWidget *parent_widget;
403 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
408 if (accessible->accessible_parent)
412 parent = accessible->accessible_parent;
414 if (GTK_IS_NOTEBOOK_PAGE_ACCESSIBLE (parent))
419 gboolean found = FALSE;
421 n_children = atk_object_get_n_accessible_children (parent);
422 for (i = 0; i < n_children; i++)
426 child = atk_object_ref_accessible_child (parent, i);
427 if (child == accessible)
430 g_object_unref (child);
437 if (!GTK_IS_WIDGET (widget))
439 parent_widget = gtk_widget_get_parent (widget);
440 if (!GTK_IS_CONTAINER (parent_widget))
443 children = gtk_container_get_children (GTK_CONTAINER (parent_widget));
445 index = g_list_index (children, widget);
446 g_list_free (children);
450 /* This function is the default implementation for the notify_gtk
451 * vfunc which gets called when a property changes value on the
452 * GtkWidget associated with a GtkWidgetAccessible. It constructs
453 * an AtkPropertyValues structure and emits a "property_changed"
454 * signal which causes the user specified AtkPropertyChangeHandler
458 gtk_widget_accessible_notify_gtk (GObject *obj,
461 GtkWidget* widget = GTK_WIDGET (obj);
462 AtkObject* atk_obj = gtk_widget_get_accessible (widget);
466 if (g_strcmp0 (pspec->name, "has-focus") == 0)
468 * We use focus-in-event and focus-out-event signals to catch
469 * focus changes so we ignore this.
472 else if (g_strcmp0 (pspec->name, "visible") == 0)
474 state = ATK_STATE_VISIBLE;
475 value = gtk_widget_get_visible (widget);
477 else if (g_strcmp0 (pspec->name, "sensitive") == 0)
479 state = ATK_STATE_SENSITIVE;
480 value = gtk_widget_get_sensitive (widget);
482 else if (g_strcmp0 (pspec->name, "orientation") == 0 &&
483 GTK_IS_ORIENTABLE (widget))
485 GtkOrientable *orientable;
487 orientable = GTK_ORIENTABLE (widget);
489 state = ATK_STATE_HORIZONTAL;
490 value = (gtk_orientable_get_orientation (orientable) == GTK_ORIENTATION_HORIZONTAL);
492 else if (g_strcmp0 (pspec->name, "tooltip-text") == 0)
494 gtk_widget_accessible_update_tooltip (GTK_WIDGET_ACCESSIBLE (atk_obj),
499 atk_object_notify_state_change (atk_obj, state, value);
500 if (state == ATK_STATE_SENSITIVE)
501 atk_object_notify_state_change (atk_obj, ATK_STATE_ENABLED, value);
503 if (state == ATK_STATE_HORIZONTAL)
504 atk_object_notify_state_change (atk_obj, ATK_STATE_VERTICAL, !value);
507 static AtkAttributeSet *
508 gtk_widget_accessible_get_attributes (AtkObject *obj)
510 AtkAttributeSet *attributes;
511 AtkAttribute *toolkit;
513 toolkit = g_new (AtkAttribute, 1);
514 toolkit->name = g_strdup ("toolkit");
515 toolkit->value = g_strdup ("gtk");
517 attributes = g_slist_append (NULL, toolkit);
523 _gtk_widget_accessible_class_init (GtkWidgetAccessibleClass *klass)
525 AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
527 klass->notify_gtk = gtk_widget_accessible_notify_gtk;
529 class->get_description = gtk_widget_accessible_get_description;
530 class->get_parent = gtk_widget_accessible_get_parent;
531 class->ref_relation_set = gtk_widget_accessible_ref_relation_set;
532 class->ref_state_set = gtk_widget_accessible_ref_state_set;
533 class->get_index_in_parent = gtk_widget_accessible_get_index_in_parent;
534 class->initialize = gtk_widget_accessible_initialize;
535 class->get_attributes = gtk_widget_accessible_get_attributes;
536 class->focus_event = gtk_widget_accessible_focus_event;
540 _gtk_widget_accessible_init (GtkWidgetAccessible *accessible)
545 gtk_widget_accessible_get_extents (AtkComponent *component,
550 AtkCoordType coord_type)
553 gint x_window, y_window;
554 gint x_toplevel, y_toplevel;
556 GtkAllocation allocation;
558 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
562 gtk_widget_get_allocation (widget, &allocation);
563 *width = allocation.width;
564 *height = allocation.height;
565 if (!gtk_widget_accessible_on_screen (widget) || (!gtk_widget_is_drawable (widget)))
572 if (gtk_widget_get_parent (widget))
576 window = gtk_widget_get_parent_window (widget);
582 window = gtk_widget_get_window (widget);
584 gdk_window_get_origin (window, &x_window, &y_window);
588 if (coord_type == ATK_XY_WINDOW)
590 window = gdk_window_get_toplevel (gtk_widget_get_window (widget));
591 gdk_window_get_origin (window, &x_toplevel, &y_toplevel);
599 gtk_widget_accessible_get_size (AtkComponent *component,
605 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
609 *width = gtk_widget_get_allocated_width (widget);
610 *height = gtk_widget_get_allocated_height (widget);
614 gtk_widget_accessible_get_layer (AtkComponent *component)
616 GtkWidgetAccessible *accessible = GTK_WIDGET_ACCESSIBLE (component);
618 return accessible->layer;
622 gtk_widget_accessible_grab_focus (AtkComponent *component)
627 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
631 if (!gtk_widget_get_can_focus (widget))
634 gtk_widget_grab_focus (widget);
635 toplevel = gtk_widget_get_toplevel (widget);
636 if (gtk_widget_is_toplevel (toplevel))
638 #ifdef GDK_WINDOWING_X11
639 gtk_window_present_with_time (GTK_WINDOW (toplevel),
640 gdk_x11_get_server_time (gtk_widget_get_window (widget)));
642 gtk_window_present (GTK_WINDOW (toplevel));
649 gtk_widget_accessible_set_extents (AtkComponent *component,
654 AtkCoordType coord_type)
658 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
662 if (!gtk_widget_is_toplevel (widget))
665 if (coord_type == ATK_XY_WINDOW)
667 gint x_current, y_current;
668 GdkWindow *window = gtk_widget_get_window (widget);
670 gdk_window_get_origin (window, &x_current, &y_current);
673 if (x_current < 0 || y_current < 0)
677 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
678 gtk_widget_set_size_request (widget, width, height);
682 else if (coord_type == ATK_XY_SCREEN)
684 gtk_window_move (GTK_WINDOW (widget), x, y);
685 gtk_widget_set_size_request (widget, width, height);
692 gtk_widget_accessible_set_position (AtkComponent *component,
695 AtkCoordType coord_type)
699 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
703 if (gtk_widget_is_toplevel (widget))
705 if (coord_type == ATK_XY_WINDOW)
707 gint x_current, y_current;
708 GdkWindow *window = gtk_widget_get_window (widget);
710 gdk_window_get_origin (window, &x_current, &y_current);
713 if (x_current < 0 || y_current < 0)
717 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
721 else if (coord_type == ATK_XY_SCREEN)
723 gtk_window_move (GTK_WINDOW (widget), x, y);
731 gtk_widget_accessible_set_size (AtkComponent *component,
737 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
741 if (gtk_widget_is_toplevel (widget))
743 gtk_widget_set_size_request (widget, width, height);
751 atk_component_interface_init (AtkComponentIface *iface)
753 iface->get_extents = gtk_widget_accessible_get_extents;
754 iface->get_size = gtk_widget_accessible_get_size;
755 iface->get_layer = gtk_widget_accessible_get_layer;
756 iface->grab_focus = gtk_widget_accessible_grab_focus;
757 iface->set_extents = gtk_widget_accessible_set_extents;
758 iface->set_position = gtk_widget_accessible_set_position;
759 iface->set_size = gtk_widget_accessible_set_size;
762 /* This function checks whether the widget has an ancestor which is
763 * a GtkViewport and, if so, whether any part of the widget intersects
764 * the visible rectangle of the GtkViewport.
767 gtk_widget_accessible_on_screen (GtkWidget *widget)
769 GtkAllocation allocation;
771 gboolean return_value;
773 gtk_widget_get_allocation (widget, &allocation);
775 viewport = gtk_widget_get_ancestor (widget, GTK_TYPE_VIEWPORT);
778 GtkAllocation viewport_allocation;
779 GtkAdjustment *adjustment;
780 GdkRectangle visible_rect;
782 gtk_widget_get_allocation (viewport, &viewport_allocation);
784 adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (viewport));
785 visible_rect.y = gtk_adjustment_get_value (adjustment);
786 adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (viewport));
787 visible_rect.x = gtk_adjustment_get_value (adjustment);
788 visible_rect.width = viewport_allocation.width;
789 visible_rect.height = viewport_allocation.height;
791 if (((allocation.x + allocation.width) < visible_rect.x) ||
792 ((allocation.y + allocation.height) < visible_rect.y) ||
793 (allocation.x > (visible_rect.x + visible_rect.width)) ||
794 (allocation.y > (visible_rect.y + visible_rect.height)))
795 return_value = FALSE;
801 /* Check whether the widget has been placed of the screen.
802 * The widget may be MAPPED as when toolbar items do not
803 * fit on the toolbar.
805 if (allocation.x + allocation.width <= 0 &&
806 allocation.y + allocation.height <= 0)
807 return_value = FALSE;
815 /* Checks if all the predecessors (the parent widget, his parent, etc)
816 * are visible Used to check properly the SHOWING state.
819 gtk_widget_accessible_all_parents_visible (GtkWidget *widget)
821 GtkWidget *iter_parent = NULL;
822 gboolean result = TRUE;
824 for (iter_parent = gtk_widget_get_parent (widget); iter_parent;
825 iter_parent = gtk_widget_get_parent (iter_parent))
827 if (!gtk_widget_get_visible (iter_parent))