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 extern GtkWidget *_focus_widget;
30 static gboolean gtk_widget_accessible_on_screen (GtkWidget *widget);
31 static gboolean gtk_widget_accessible_all_parents_visible (GtkWidget *widget);
33 static void atk_component_interface_init (AtkComponentIface *iface);
35 G_DEFINE_TYPE_WITH_CODE (GtkWidgetAccessible, _gtk_widget_accessible, GTK_TYPE_ACCESSIBLE,
36 G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, atk_component_interface_init))
38 /* Translate GtkWidget::focus-in/out-event to AtkObject::focus-event */
40 focus_cb (GtkWidget *widget,
45 obj = gtk_widget_get_accessible (widget);
47 g_signal_emit_by_name (obj, "focus-event", event->in);
52 /* Translate GtkWidget property change notification to the notify_gtk vfunc */
54 notify_cb (GObject *obj,
57 GtkWidgetAccessible *widget;
58 GtkWidgetAccessibleClass *klass;
60 widget = GTK_WIDGET_ACCESSIBLE (gtk_widget_get_accessible (GTK_WIDGET (obj)));
61 klass = GTK_WIDGET_ACCESSIBLE_GET_CLASS (widget);
62 if (klass->notify_gtk)
63 klass->notify_gtk (obj, pspec);
66 /* Translate GtkWidget::size-allocate to AtkComponent::bounds-changed */
68 size_allocate_cb (GtkWidget *widget,
69 GtkAllocation *allocation)
71 AtkObject* accessible;
74 accessible = gtk_widget_get_accessible (widget);
75 if (ATK_IS_COMPONENT (accessible))
77 rect.x = allocation->x;
78 rect.y = allocation->y;
79 rect.width = allocation->width;
80 rect.height = allocation->height;
81 g_signal_emit_by_name (accessible, "bounds-changed", &rect);
85 /* Translate GtkWidget mapped state into AtkObject showing */
87 map_cb (GtkWidget *widget)
89 AtkObject *accessible;
91 accessible = gtk_widget_get_accessible (widget);
92 atk_object_notify_state_change (accessible, ATK_STATE_SHOWING,
93 gtk_widget_get_mapped (widget));
98 gtk_widget_accessible_focus_event (AtkObject *obj,
101 AtkObject *focus_obj;
103 focus_obj = g_object_get_data (G_OBJECT (obj), "gail-focus-object");
104 if (focus_obj == NULL)
106 atk_object_notify_state_change (focus_obj, ATK_STATE_FOCUSED, focus_in);
110 gtk_widget_accessible_initialize (AtkObject *obj,
115 widget = GTK_WIDGET (data);
117 g_signal_connect_after (widget, "focus-in-event", G_CALLBACK (focus_cb), NULL);
118 g_signal_connect_after (widget, "focus-out-event", G_CALLBACK (focus_cb), NULL);
119 g_signal_connect (widget, "notify", G_CALLBACK (notify_cb), NULL);
120 g_signal_connect (widget, "size-allocate", G_CALLBACK (size_allocate_cb), NULL);
121 g_signal_connect (widget, "map", G_CALLBACK (map_cb), NULL);
122 g_signal_connect (widget, "unmap", G_CALLBACK (map_cb), NULL);
124 GTK_WIDGET_ACCESSIBLE (obj)->layer = ATK_LAYER_WIDGET;
125 obj->role = ATK_ROLE_UNKNOWN;
129 gtk_widget_accessible_get_description (AtkObject *accessible)
133 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
137 if (accessible->description)
138 return accessible->description;
140 return gtk_widget_get_tooltip_text (widget);
144 gtk_widget_accessible_get_parent (AtkObject *accessible)
147 GtkWidget *widget, *parent_widget;
149 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
153 parent = accessible->accessible_parent;
157 parent_widget = gtk_widget_get_parent (widget);
158 if (parent_widget == NULL)
161 /* For a widget whose parent is a GtkNoteBook, we return the
162 * accessible object corresponding the GtkNotebookPage containing
163 * the widget as the accessible parent.
165 if (GTK_IS_NOTEBOOK (parent_widget))
169 GtkNotebook *notebook;
172 notebook = GTK_NOTEBOOK (parent_widget);
175 child = gtk_notebook_get_nth_page (notebook, page_num);
180 parent = gtk_widget_get_accessible (parent_widget);
181 parent = atk_object_ref_accessible_child (parent, page_num);
182 g_object_unref (parent);
188 parent = gtk_widget_get_accessible (parent_widget);
193 find_label (GtkWidget *widget)
197 GtkWidget *temp_widget;
200 labels = gtk_widget_list_mnemonic_labels (widget);
212 g_list_free (labels);
214 /* Ignore a label within a button; bug #136602 */
215 if (label && GTK_IS_BUTTON (widget))
220 if (temp_widget == widget)
225 temp_widget = gtk_widget_get_parent (temp_widget);
231 static AtkRelationSet *
232 gtk_widget_accessible_ref_relation_set (AtkObject *obj)
235 AtkRelationSet *relation_set;
238 AtkRelation* relation;
240 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
244 relation_set = ATK_OBJECT_CLASS (_gtk_widget_accessible_parent_class)->ref_relation_set (obj);
246 if (GTK_IS_BOX (widget))
249 if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABELLED_BY))
251 label = find_label (widget);
254 if (GTK_IS_BUTTON (widget))
256 * Handle the case where GnomeIconEntry is the mnemonic widget.
257 * The GtkButton which is a grandchild of the GnomeIconEntry
258 * should really be the mnemonic widget. See bug #133967.
261 GtkWidget *temp_widget;
263 temp_widget = gtk_widget_get_parent (widget);
265 if (GTK_IS_ALIGNMENT (temp_widget))
267 temp_widget = gtk_widget_get_parent (temp_widget);
268 if (GTK_IS_BOX (temp_widget))
270 label = find_label (temp_widget);
272 label = find_label (gtk_widget_get_parent (temp_widget));
276 else if (GTK_IS_COMBO_BOX (widget))
278 * Handle the case when GtkFileChooserButton is the mnemonic
279 * widget. The GtkComboBox which is a child of the
280 * GtkFileChooserButton should be the mnemonic widget.
284 GtkWidget *temp_widget;
286 temp_widget = gtk_widget_get_parent (widget);
287 if (GTK_IS_BOX (temp_widget))
289 label = find_label (temp_widget);
296 array[0] = gtk_widget_get_accessible (label);
298 relation = atk_relation_new (array, 1, ATK_RELATION_LABELLED_BY);
299 atk_relation_set_add (relation_set, relation);
300 g_object_unref (relation);
308 gtk_widget_accessible_ref_state_set (AtkObject *accessible)
311 AtkStateSet *state_set;
313 state_set = ATK_OBJECT_CLASS (_gtk_widget_accessible_parent_class)->ref_state_set (accessible);
315 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
317 atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
320 if (gtk_widget_is_sensitive (widget))
322 atk_state_set_add_state (state_set, ATK_STATE_SENSITIVE);
323 atk_state_set_add_state (state_set, ATK_STATE_ENABLED);
326 if (gtk_widget_get_can_focus (widget))
328 atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE);
331 * We do not currently generate notifications when an ATK object
332 * corresponding to a GtkWidget changes visibility by being scrolled
333 * on or off the screen. The testcase for this is the main window
334 * of the testgtk application in which a set of buttons in a GtkVBox
335 * is in a scrolled window with a viewport.
337 * To generate the notifications we would need to do the following:
338 * 1) Find the GtkViewport among the ancestors of the objects
339 * 2) Create an accessible for the viewport
340 * 3) Connect to the value-changed signal on the viewport
341 * 4) When the signal is received we need to traverse the children
342 * of the viewport and check whether the children are visible or not
343 * visible; we may want to restrict this to the widgets for which
344 * accessible objects have been created.
345 * 5) We probably need to store a variable on_screen in the
346 * GtkWidgetAccessible data structure so we can determine whether
347 * the value has changed.
349 if (gtk_widget_get_visible (widget))
351 atk_state_set_add_state (state_set, ATK_STATE_VISIBLE);
352 if (gtk_widget_accessible_on_screen (widget) &&
353 gtk_widget_get_mapped (widget) &&
354 gtk_widget_accessible_all_parents_visible (widget))
355 atk_state_set_add_state (state_set, ATK_STATE_SHOWING);
358 if (gtk_widget_has_focus (widget) && (widget == _focus_widget))
360 AtkObject *focus_obj;
362 focus_obj = g_object_get_data (G_OBJECT (accessible), "gail-focus-object");
363 if (focus_obj == NULL)
364 atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
367 if (gtk_widget_has_default (widget))
368 atk_state_set_add_state (state_set, ATK_STATE_DEFAULT);
370 if (GTK_IS_ORIENTABLE (widget))
372 if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
373 atk_state_set_add_state (state_set, ATK_STATE_HORIZONTAL);
375 atk_state_set_add_state (state_set, ATK_STATE_VERTICAL);
382 gtk_widget_accessible_get_index_in_parent (AtkObject *accessible)
385 GtkWidget *parent_widget;
389 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
394 if (accessible->accessible_parent)
398 parent = accessible->accessible_parent;
400 if (GTK_IS_NOTEBOOK_PAGE_ACCESSIBLE (parent))
405 gboolean found = FALSE;
407 n_children = atk_object_get_n_accessible_children (parent);
408 for (i = 0; i < n_children; i++)
412 child = atk_object_ref_accessible_child (parent, i);
413 if (child == accessible)
416 g_object_unref (child);
423 if (!GTK_IS_WIDGET (widget))
425 parent_widget = gtk_widget_get_parent (widget);
426 if (!GTK_IS_CONTAINER (parent_widget))
429 children = gtk_container_get_children (GTK_CONTAINER (parent_widget));
431 index = g_list_index (children, widget);
432 g_list_free (children);
436 /* This function is the default implementation for the notify_gtk
437 * vfunc which gets called when a property changes value on the
438 * GtkWidget associated with a GtkWidgetAccessible. It constructs
439 * an AtkPropertyValues structure and emits a "property_changed"
440 * signal which causes the user specified AtkPropertyChangeHandler
444 gtk_widget_accessible_notify_gtk (GObject *obj,
447 GtkWidget* widget = GTK_WIDGET (obj);
448 AtkObject* atk_obj = gtk_widget_get_accessible (widget);
452 if (g_strcmp0 (pspec->name, "has-focus") == 0)
454 * We use focus-in-event and focus-out-event signals to catch
455 * focus changes so we ignore this.
458 else if (g_strcmp0 (pspec->name, "visible") == 0)
460 state = ATK_STATE_VISIBLE;
461 value = gtk_widget_get_visible (widget);
463 else if (g_strcmp0 (pspec->name, "sensitive") == 0)
465 state = ATK_STATE_SENSITIVE;
466 value = gtk_widget_get_sensitive (widget);
468 else if (g_strcmp0 (pspec->name, "orientation") == 0)
470 GtkOrientable *orientable;
472 orientable = GTK_ORIENTABLE (widget);
474 state = ATK_STATE_HORIZONTAL;
475 value = (gtk_orientable_get_orientation (orientable) == GTK_ORIENTATION_HORIZONTAL);
480 atk_object_notify_state_change (atk_obj, state, value);
481 if (state == ATK_STATE_SENSITIVE)
482 atk_object_notify_state_change (atk_obj, ATK_STATE_ENABLED, value);
484 if (state == ATK_STATE_HORIZONTAL)
485 atk_object_notify_state_change (atk_obj, ATK_STATE_VERTICAL, !value);
488 static AtkAttributeSet *
489 gtk_widget_accessible_get_attributes (AtkObject *obj)
491 AtkAttributeSet *attributes;
492 AtkAttribute *toolkit;
494 toolkit = g_new (AtkAttribute, 1);
495 toolkit->name = g_strdup ("toolkit");
496 toolkit->value = g_strdup ("gtk");
498 attributes = g_slist_append (NULL, toolkit);
504 _gtk_widget_accessible_class_init (GtkWidgetAccessibleClass *klass)
506 AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
508 klass->notify_gtk = gtk_widget_accessible_notify_gtk;
510 class->get_description = gtk_widget_accessible_get_description;
511 class->get_parent = gtk_widget_accessible_get_parent;
512 class->ref_relation_set = gtk_widget_accessible_ref_relation_set;
513 class->ref_state_set = gtk_widget_accessible_ref_state_set;
514 class->get_index_in_parent = gtk_widget_accessible_get_index_in_parent;
515 class->initialize = gtk_widget_accessible_initialize;
516 class->get_attributes = gtk_widget_accessible_get_attributes;
517 class->focus_event = gtk_widget_accessible_focus_event;
521 _gtk_widget_accessible_init (GtkWidgetAccessible *accessible)
526 gtk_widget_accessible_get_extents (AtkComponent *component,
531 AtkCoordType coord_type)
534 gint x_window, y_window;
535 gint x_toplevel, y_toplevel;
537 GtkAllocation allocation;
539 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
543 gtk_widget_get_allocation (widget, &allocation);
544 *width = allocation.width;
545 *height = allocation.height;
546 if (!gtk_widget_accessible_on_screen (widget) || (!gtk_widget_is_drawable (widget)))
553 if (gtk_widget_get_parent (widget))
557 window = gtk_widget_get_parent_window (widget);
563 window = gtk_widget_get_window (widget);
565 gdk_window_get_origin (window, &x_window, &y_window);
569 if (coord_type == ATK_XY_WINDOW)
571 window = gdk_window_get_toplevel (gtk_widget_get_window (widget));
572 gdk_window_get_origin (window, &x_toplevel, &y_toplevel);
580 gtk_widget_accessible_get_size (AtkComponent *component,
586 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
590 *width = gtk_widget_get_allocated_width (widget);
591 *height = gtk_widget_get_allocated_height (widget);
595 gtk_widget_accessible_get_layer (AtkComponent *component)
597 GtkWidgetAccessible *accessible = GTK_WIDGET_ACCESSIBLE (component);
599 return accessible->layer;
603 gtk_widget_accessible_grab_focus (AtkComponent *component)
608 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
612 if (!gtk_widget_get_can_focus (widget))
615 gtk_widget_grab_focus (widget);
616 toplevel = gtk_widget_get_toplevel (widget);
617 if (gtk_widget_is_toplevel (toplevel))
619 #ifdef GDK_WINDOWING_X11
620 gtk_window_present_with_time (GTK_WINDOW (toplevel),
621 gdk_x11_get_server_time (gtk_widget_get_window (widget)));
623 gtk_window_present (GTK_WINDOW (toplevel));
630 gtk_widget_accessible_set_extents (AtkComponent *component,
635 AtkCoordType coord_type)
639 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
643 if (!gtk_widget_is_toplevel (widget))
646 if (coord_type == ATK_XY_WINDOW)
648 gint x_current, y_current;
649 GdkWindow *window = gtk_widget_get_window (widget);
651 gdk_window_get_origin (window, &x_current, &y_current);
654 if (x_current < 0 || y_current < 0)
658 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
659 gtk_widget_set_size_request (widget, width, height);
663 else if (coord_type == ATK_XY_SCREEN)
665 gtk_window_move (GTK_WINDOW (widget), x, y);
666 gtk_widget_set_size_request (widget, width, height);
673 gtk_widget_accessible_set_position (AtkComponent *component,
676 AtkCoordType coord_type)
680 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
684 if (gtk_widget_is_toplevel (widget))
686 if (coord_type == ATK_XY_WINDOW)
688 gint x_current, y_current;
689 GdkWindow *window = gtk_widget_get_window (widget);
691 gdk_window_get_origin (window, &x_current, &y_current);
694 if (x_current < 0 || y_current < 0)
698 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
702 else if (coord_type == ATK_XY_SCREEN)
704 gtk_window_move (GTK_WINDOW (widget), x, y);
712 gtk_widget_accessible_set_size (AtkComponent *component,
718 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
722 if (gtk_widget_is_toplevel (widget))
724 gtk_widget_set_size_request (widget, width, height);
732 atk_component_interface_init (AtkComponentIface *iface)
734 iface->get_extents = gtk_widget_accessible_get_extents;
735 iface->get_size = gtk_widget_accessible_get_size;
736 iface->get_layer = gtk_widget_accessible_get_layer;
737 iface->grab_focus = gtk_widget_accessible_grab_focus;
738 iface->set_extents = gtk_widget_accessible_set_extents;
739 iface->set_position = gtk_widget_accessible_set_position;
740 iface->set_size = gtk_widget_accessible_set_size;
743 /* This function checks whether the widget has an ancestor which is
744 * a GtkViewport and, if so, whether any part of the widget intersects
745 * the visible rectangle of the GtkViewport.
748 gtk_widget_accessible_on_screen (GtkWidget *widget)
750 GtkAllocation allocation;
752 gboolean return_value;
754 gtk_widget_get_allocation (widget, &allocation);
756 viewport = gtk_widget_get_ancestor (widget, GTK_TYPE_VIEWPORT);
759 GtkAllocation viewport_allocation;
760 GtkAdjustment *adjustment;
761 GdkRectangle visible_rect;
763 gtk_widget_get_allocation (viewport, &viewport_allocation);
765 adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (viewport));
766 visible_rect.y = gtk_adjustment_get_value (adjustment);
767 adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (viewport));
768 visible_rect.x = gtk_adjustment_get_value (adjustment);
769 visible_rect.width = viewport_allocation.width;
770 visible_rect.height = viewport_allocation.height;
772 if (((allocation.x + allocation.width) < visible_rect.x) ||
773 ((allocation.y + allocation.height) < visible_rect.y) ||
774 (allocation.x > (visible_rect.x + visible_rect.width)) ||
775 (allocation.y > (visible_rect.y + visible_rect.height)))
776 return_value = FALSE;
782 /* Check whether the widget has been placed of the screen.
783 * The widget may be MAPPED as when toolbar items do not
784 * fit on the toolbar.
786 if (allocation.x + allocation.width <= 0 &&
787 allocation.y + allocation.height <= 0)
788 return_value = FALSE;
796 /* Checks if all the predecessors (the parent widget, his parent, etc)
797 * are visible Used to check properly the SHOWING state.
800 gtk_widget_accessible_all_parents_visible (GtkWidget *widget)
802 GtkWidget *iter_parent = NULL;
803 gboolean result = TRUE;
805 for (iter_parent = gtk_widget_get_parent (widget); iter_parent;
806 iter_parent = gtk_widget_get_parent (iter_parent))
808 if (!gtk_widget_get_visible (iter_parent))