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, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
25 #ifdef GDK_WINDOWING_X11
26 #include <gdk/x11/gdkx.h>
28 #include "gtkwidgetaccessible.h"
29 #include "gtknotebookpageaccessible.h"
31 extern GtkWidget *focus_widget;
34 static gboolean gtk_widget_accessible_on_screen (GtkWidget *widget);
35 static gboolean gtk_widget_accessible_all_parents_visible (GtkWidget *widget);
37 static void atk_component_interface_init (AtkComponentIface *iface);
39 G_DEFINE_TYPE_WITH_CODE (GtkWidgetAccessible, gtk_widget_accessible, GTK_TYPE_ACCESSIBLE,
40 G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, atk_component_interface_init))
42 /* Translate GtkWidget::focus-in/out-event to the focus_gtk vfunc */
44 focus_cb (GtkWidget *widget,
47 GtkWidgetAccessible *accessible;
48 GtkWidgetAccessibleClass *klass;
50 accessible = GTK_WIDGET_ACCESSIBLE (gtk_widget_get_accessible (widget));
51 klass = GTK_WIDGET_ACCESSIBLE_GET_CLASS (accessible);
53 return klass->focus_gtk (widget, event);
58 /* Translate GtkWidget property change notification to the notify_gtk vfunc */
60 notify_cb (GObject *obj,
63 GtkWidgetAccessible *widget;
64 GtkWidgetAccessibleClass *klass;
66 widget = GTK_WIDGET_ACCESSIBLE (gtk_widget_get_accessible (GTK_WIDGET (obj)));
67 klass = GTK_WIDGET_ACCESSIBLE_GET_CLASS (widget);
68 if (klass->notify_gtk)
69 klass->notify_gtk (obj, pspec);
72 /* Translate GtkWidget::size-allocate to AtkComponent::bounds-changed */
74 size_allocate_cb (GtkWidget *widget,
75 GtkAllocation *allocation)
77 AtkObject* accessible;
80 accessible = gtk_widget_get_accessible (widget);
81 if (ATK_IS_COMPONENT (accessible))
83 rect.x = allocation->x;
84 rect.y = allocation->y;
85 rect.width = allocation->width;
86 rect.height = allocation->height;
87 g_signal_emit_by_name (accessible, "bounds_changed", &rect);
91 /* Translate GtkWidget mapped state into AtkObject showing */
93 map_cb (GtkWidget *widget)
95 AtkObject *accessible;
97 accessible = gtk_widget_get_accessible (widget);
98 atk_object_notify_state_change (accessible, ATK_STATE_SHOWING,
99 gtk_widget_get_mapped (widget));
104 focus_event (AtkObject *obj,
107 AtkObject *focus_obj;
109 focus_obj = g_object_get_data (G_OBJECT (obj), "gail-focus-object");
110 if (focus_obj == NULL)
112 atk_object_notify_state_change (focus_obj, ATK_STATE_FOCUSED, focus_in);
116 gtk_widget_accessible_initialize (AtkObject *obj,
119 GtkAccessible *accessible;
122 widget = GTK_WIDGET (data);
124 accessible = GTK_ACCESSIBLE (obj);
125 gtk_accessible_set_widget (accessible, widget);
126 gtk_accessible_connect_widget_destroyed (accessible);
127 g_signal_connect_after (widget, "focus-in-event", G_CALLBACK (focus_cb), NULL);
128 g_signal_connect_after (widget, "focus-out-event", G_CALLBACK (focus_cb), NULL);
129 g_signal_connect (widget, "notify", G_CALLBACK (notify_cb), NULL);
130 g_signal_connect (widget, "size-allocate", G_CALLBACK (size_allocate_cb), NULL);
131 g_signal_connect (widget, "map", G_CALLBACK (map_cb), NULL);
132 g_signal_connect (widget, "unmap", G_CALLBACK (map_cb), NULL);
134 atk_component_add_focus_handler (ATK_COMPONENT (accessible), focus_event);
135 g_object_set_data (G_OBJECT (obj), "atk-component-layer", GINT_TO_POINTER (ATK_LAYER_WIDGET));
137 obj->role = ATK_ROLE_UNKNOWN;
141 gtk_widget_accessible_destroyed (GtkWidget *widget,
142 GtkAccessible *accessible)
144 gtk_accessible_set_widget (accessible, NULL);
145 atk_object_notify_state_change (ATK_OBJECT (accessible), ATK_STATE_DEFUNCT, TRUE);
149 gtk_widget_accessible_connect_widget_destroyed (GtkAccessible *accessible)
153 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
155 g_signal_connect_after (widget, "destroy",
156 G_CALLBACK (gtk_widget_accessible_destroyed), accessible);
160 gtk_widget_accessible_get_description (AtkObject *accessible)
164 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
168 if (accessible->description)
169 return accessible->description;
171 return gtk_widget_get_tooltip_text (widget);
175 gtk_widget_accessible_get_parent (AtkObject *accessible)
178 GtkWidget *widget, *parent_widget;
180 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
184 parent = accessible->accessible_parent;
188 parent_widget = gtk_widget_get_parent (widget);
189 if (parent_widget == NULL)
192 /* For a widget whose parent is a GtkNoteBook, we return the
193 * accessible object corresponding the GtkNotebookPage containing
194 * the widget as the accessible parent.
196 if (GTK_IS_NOTEBOOK (parent_widget))
200 GtkNotebook *notebook;
203 notebook = GTK_NOTEBOOK (parent_widget);
206 child = gtk_notebook_get_nth_page (notebook, page_num);
211 parent = gtk_widget_get_accessible (parent_widget);
212 parent = atk_object_ref_accessible_child (parent, page_num);
213 g_object_unref (parent);
219 parent = gtk_widget_get_accessible (parent_widget);
224 find_label (GtkWidget *widget)
228 GtkWidget *temp_widget;
230 labels = gtk_widget_list_mnemonic_labels (widget);
237 g_warning ("Widget (%s) has more than one label", G_OBJECT_TYPE_NAME (widget));
239 label = labels->data;
241 g_list_free (labels);
244 /* Ignore a label within a button; bug #136602 */
245 if (label && GTK_IS_BUTTON (widget))
250 if (temp_widget == widget)
255 temp_widget = gtk_widget_get_parent (temp_widget);
261 static AtkRelationSet *
262 gtk_widget_accessible_ref_relation_set (AtkObject *obj)
265 AtkRelationSet *relation_set;
268 AtkRelation* relation;
270 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
274 relation_set = ATK_OBJECT_CLASS (gtk_widget_accessible_parent_class)->ref_relation_set (obj);
276 if (GTK_IS_BOX (widget))
279 if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABELLED_BY))
281 label = find_label (widget);
284 if (GTK_IS_BUTTON (widget))
286 * Handle the case where GnomeIconEntry is the mnemonic widget.
287 * The GtkButton which is a grandchild of the GnomeIconEntry
288 * should really be the mnemonic widget. See bug #133967.
291 GtkWidget *temp_widget;
293 temp_widget = gtk_widget_get_parent (widget);
295 if (GTK_IS_ALIGNMENT (temp_widget))
297 temp_widget = gtk_widget_get_parent (temp_widget);
298 if (GTK_IS_BOX (temp_widget))
300 label = find_label (temp_widget);
302 label = find_label (gtk_widget_get_parent (temp_widget));
306 else if (GTK_IS_COMBO_BOX (widget))
308 * Handle the case when GtkFileChooserButton is the mnemonic
309 * widget. The GtkComboBox which is a child of the
310 * GtkFileChooserButton should be the mnemonic widget.
314 GtkWidget *temp_widget;
316 temp_widget = gtk_widget_get_parent (widget);
317 if (GTK_IS_BOX (temp_widget))
319 label = find_label (temp_widget);
326 array[0] = gtk_widget_get_accessible (label);
328 relation = atk_relation_new (array, 1, ATK_RELATION_LABELLED_BY);
329 atk_relation_set_add (relation_set, relation);
330 g_object_unref (relation);
338 gtk_widget_accessible_ref_state_set (AtkObject *accessible)
341 AtkStateSet *state_set;
343 state_set = ATK_OBJECT_CLASS (gtk_widget_accessible_parent_class)->ref_state_set (accessible);
345 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
347 atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
350 if (gtk_widget_is_sensitive (widget))
352 atk_state_set_add_state (state_set, ATK_STATE_SENSITIVE);
353 atk_state_set_add_state (state_set, ATK_STATE_ENABLED);
356 if (gtk_widget_get_can_focus (widget))
358 atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE);
361 * We do not currently generate notifications when an ATK object
362 * corresponding to a GtkWidget changes visibility by being scrolled
363 * on or off the screen. The testcase for this is the main window
364 * of the testgtk application in which a set of buttons in a GtkVBox
365 * is in a scrolled window with a viewport.
367 * To generate the notifications we would need to do the following:
368 * 1) Find the GtkViewport among the ancestors of the objects
369 * 2) Create an accessible for the viewport
370 * 3) Connect to the value-changed signal on the viewport
371 * 4) When the signal is received we need to traverse the children
372 * of the viewport and check whether the children are visible or not
373 * visible; we may want to restrict this to the widgets for which
374 * accessible objects have been created.
375 * 5) We probably need to store a variable on_screen in the
376 * GtkWidgetAccessible data structure so we can determine whether
377 * the value has changed.
379 if (gtk_widget_get_visible (widget))
381 atk_state_set_add_state (state_set, ATK_STATE_VISIBLE);
382 if (gtk_widget_accessible_on_screen (widget) &&
383 gtk_widget_get_mapped (widget) &&
384 gtk_widget_accessible_all_parents_visible (widget))
385 atk_state_set_add_state (state_set, ATK_STATE_SHOWING);
388 if (gtk_widget_has_focus (widget) && (widget == focus_widget))
390 AtkObject *focus_obj;
392 focus_obj = g_object_get_data (G_OBJECT (accessible), "gail-focus-object");
393 if (focus_obj == NULL)
394 atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
397 if (gtk_widget_has_default (widget))
398 atk_state_set_add_state (state_set, ATK_STATE_DEFAULT);
400 if (GTK_IS_ORIENTABLE (widget))
402 if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
403 atk_state_set_add_state (state_set, ATK_STATE_HORIZONTAL);
405 atk_state_set_add_state (state_set, ATK_STATE_VERTICAL);
412 gtk_widget_accessible_get_index_in_parent (AtkObject *accessible)
415 GtkWidget *parent_widget;
419 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
424 if (accessible->accessible_parent)
428 parent = accessible->accessible_parent;
430 if (GTK_IS_NOTEBOOK_PAGE_ACCESSIBLE (parent))
435 gboolean found = FALSE;
437 n_children = atk_object_get_n_accessible_children (parent);
438 for (i = 0; i < n_children; i++)
442 child = atk_object_ref_accessible_child (parent, i);
443 if (child == accessible)
446 g_object_unref (child);
453 if (!GTK_IS_WIDGET (widget))
455 parent_widget = gtk_widget_get_parent (widget);
456 if (!GTK_IS_CONTAINER (parent_widget))
459 children = gtk_container_get_children (GTK_CONTAINER (parent_widget));
461 index = g_list_index (children, widget);
462 g_list_free (children);
466 /* This function is the default implementation for the notify_gtk
467 * vfunc which gets called when a property changes value on the
468 * GtkWidget associated with a GtkWidgetAccessible. It constructs
469 * an AtkPropertyValues structure and emits a "property_changed"
470 * signal which causes the user specified AtkPropertyChangeHandler
474 gtk_widget_accessible_notify_gtk (GObject *obj,
477 GtkWidget* widget = GTK_WIDGET (obj);
478 AtkObject* atk_obj = gtk_widget_get_accessible (widget);
482 if (strcmp (pspec->name, "has-focus") == 0)
484 * We use focus-in-event and focus-out-event signals to catch
485 * focus changes so we ignore this.
488 else if (strcmp (pspec->name, "visible") == 0)
490 state = ATK_STATE_VISIBLE;
491 value = gtk_widget_get_visible (widget);
493 else if (strcmp (pspec->name, "sensitive") == 0)
495 state = ATK_STATE_SENSITIVE;
496 value = gtk_widget_get_sensitive (widget);
498 else if (strcmp (pspec->name, "orientation") == 0)
500 GtkOrientable *orientable;
502 orientable = GTK_ORIENTABLE (widget);
504 state = ATK_STATE_HORIZONTAL;
505 value = (gtk_orientable_get_orientation (orientable) == GTK_ORIENTATION_HORIZONTAL);
510 atk_object_notify_state_change (atk_obj, state, value);
511 if (state == ATK_STATE_SENSITIVE)
512 atk_object_notify_state_change (atk_obj, ATK_STATE_ENABLED, value);
514 if (state == ATK_STATE_HORIZONTAL)
515 atk_object_notify_state_change (atk_obj, ATK_STATE_VERTICAL, !value);
518 /* This function is the default implementation for the focus_gtk
519 * vfunc which gets called for focus_in/out_event.
521 * It emits a focus-event signal on the GtkWidgetAccessible.
524 gtk_widget_accessible_focus_gtk (GtkWidget *widget,
525 GdkEventFocus *event)
527 AtkObject* accessible;
531 accessible = gtk_widget_get_accessible (widget);
532 g_signal_emit_by_name (accessible, "focus_event", event->in, &return_val);
536 static AtkAttributeSet *
537 gtk_widget_accessible_get_attributes (AtkObject *obj)
539 AtkAttributeSet *attributes;
540 AtkAttribute *toolkit;
542 toolkit = g_new (AtkAttribute, 1);
543 toolkit->name = g_strdup ("toolkit");
544 toolkit->value = g_strdup ("gail");
546 attributes = g_slist_append (NULL, toolkit);
552 gtk_widget_accessible_class_init (GtkWidgetAccessibleClass *klass)
554 AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
555 GtkAccessibleClass *accessible_class = GTK_ACCESSIBLE_CLASS (klass);
557 klass->notify_gtk = gtk_widget_accessible_notify_gtk;
558 klass->focus_gtk = gtk_widget_accessible_focus_gtk;
560 accessible_class->connect_widget_destroyed = gtk_widget_accessible_connect_widget_destroyed;
562 class->get_description = gtk_widget_accessible_get_description;
563 class->get_parent = gtk_widget_accessible_get_parent;
564 class->ref_relation_set = gtk_widget_accessible_ref_relation_set;
565 class->ref_state_set = gtk_widget_accessible_ref_state_set;
566 class->get_index_in_parent = gtk_widget_accessible_get_index_in_parent;
567 class->initialize = gtk_widget_accessible_initialize;
568 class->get_attributes = gtk_widget_accessible_get_attributes;
572 gtk_widget_accessible_init (GtkWidgetAccessible *accessible)
577 gtk_widget_accessible_add_focus_handler (AtkComponent *component,
578 AtkFocusHandler handler)
580 GSignalMatchType match_type;
584 match_type = G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_FUNC;
585 signal_id = g_signal_lookup ("focus-event", ATK_TYPE_OBJECT);
587 ret = g_signal_handler_find (component, match_type, signal_id, 0, NULL,
588 (gpointer) handler, NULL);
590 return g_signal_connect_closure_by_id (component,
592 g_cclosure_new (G_CALLBACK (handler),
594 (GClosureNotify) NULL),
601 gtk_widget_accessible_get_extents (AtkComponent *component,
606 AtkCoordType coord_type)
609 gint x_window, y_window;
610 gint x_toplevel, y_toplevel;
612 GtkAllocation allocation;
614 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
618 gtk_widget_get_allocation (widget, &allocation);
619 *width = allocation.width;
620 *height = allocation.height;
621 if (!gtk_widget_accessible_on_screen (widget) || (!gtk_widget_is_drawable (widget)))
628 if (gtk_widget_get_parent (widget))
632 window = gtk_widget_get_parent_window (widget);
638 window = gtk_widget_get_window (widget);
640 gdk_window_get_origin (window, &x_window, &y_window);
644 if (coord_type == ATK_XY_WINDOW)
646 window = gdk_window_get_toplevel (gtk_widget_get_window (widget));
647 gdk_window_get_origin (window, &x_toplevel, &y_toplevel);
655 gtk_widget_accessible_get_size (AtkComponent *component,
661 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
665 *width = gtk_widget_get_allocated_width (widget);
666 *height = gtk_widget_get_allocated_height (widget);
670 gtk_widget_accessible_get_layer (AtkComponent *component)
674 layer = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (component), "atk-component-layer"));
676 return (AtkLayer) layer;
680 gtk_widget_accessible_grab_focus (AtkComponent *component)
685 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
689 if (!gtk_widget_get_can_focus (widget))
692 gtk_widget_grab_focus (widget);
693 toplevel = gtk_widget_get_toplevel (widget);
694 if (gtk_widget_is_toplevel (toplevel))
696 #ifdef GDK_WINDOWING_X11
697 gtk_window_present_with_time (GTK_WINDOW (toplevel),
698 gdk_x11_get_server_time (gtk_widget_get_window (widget)));
700 gtk_window_present (GTK_WINDOW (toplevel));
707 gtk_widget_accessible_remove_focus_handler (AtkComponent *component,
710 g_signal_handler_disconnect (component, handler_id);
714 gtk_widget_accessible_set_extents (AtkComponent *component,
719 AtkCoordType coord_type)
723 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
727 if (!gtk_widget_is_toplevel (widget))
730 if (coord_type == ATK_XY_WINDOW)
732 gint x_current, y_current;
733 GdkWindow *window = gtk_widget_get_window (widget);
735 gdk_window_get_origin (window, &x_current, &y_current);
738 if (x_current < 0 || y_current < 0)
742 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
743 gtk_widget_set_size_request (widget, width, height);
747 else if (coord_type == ATK_XY_SCREEN)
749 gtk_window_move (GTK_WINDOW (widget), x, y);
750 gtk_widget_set_size_request (widget, width, height);
757 gtk_widget_accessible_set_position (AtkComponent *component,
760 AtkCoordType coord_type)
764 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
768 if (gtk_widget_is_toplevel (widget))
770 if (coord_type == ATK_XY_WINDOW)
772 gint x_current, y_current;
773 GdkWindow *window = gtk_widget_get_window (widget);
775 gdk_window_get_origin (window, &x_current, &y_current);
778 if (x_current < 0 || y_current < 0)
782 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
786 else if (coord_type == ATK_XY_SCREEN)
788 gtk_window_move (GTK_WINDOW (widget), x, y);
796 gtk_widget_accessible_set_size (AtkComponent *component,
802 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
806 if (gtk_widget_is_toplevel (widget))
808 gtk_widget_set_size_request (widget, width, height);
816 atk_component_interface_init (AtkComponentIface *iface)
818 iface->add_focus_handler = gtk_widget_accessible_add_focus_handler;
819 iface->get_extents = gtk_widget_accessible_get_extents;
820 iface->get_size = gtk_widget_accessible_get_size;
821 iface->get_layer = gtk_widget_accessible_get_layer;
822 iface->grab_focus = gtk_widget_accessible_grab_focus;
823 iface->remove_focus_handler = gtk_widget_accessible_remove_focus_handler;
824 iface->set_extents = gtk_widget_accessible_set_extents;
825 iface->set_position = gtk_widget_accessible_set_position;
826 iface->set_size = gtk_widget_accessible_set_size;
829 /* This function checks whether the widget has an ancestor which is
830 * a GtkViewport and, if so, whether any part of the widget intersects
831 * the visible rectangle of the GtkViewport.
834 gtk_widget_accessible_on_screen (GtkWidget *widget)
836 GtkAllocation allocation;
838 gboolean return_value;
840 gtk_widget_get_allocation (widget, &allocation);
842 viewport = gtk_widget_get_ancestor (widget, GTK_TYPE_VIEWPORT);
845 GtkAllocation viewport_allocation;
846 GtkAdjustment *adjustment;
847 GdkRectangle visible_rect;
849 gtk_widget_get_allocation (viewport, &viewport_allocation);
851 adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (viewport));
852 visible_rect.y = gtk_adjustment_get_value (adjustment);
853 adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (viewport));
854 visible_rect.x = gtk_adjustment_get_value (adjustment);
855 visible_rect.width = viewport_allocation.width;
856 visible_rect.height = viewport_allocation.height;
858 if (((allocation.x + allocation.width) < visible_rect.x) ||
859 ((allocation.y + allocation.height) < visible_rect.y) ||
860 (allocation.x > (visible_rect.x + visible_rect.width)) ||
861 (allocation.y > (visible_rect.y + visible_rect.height)))
862 return_value = FALSE;
868 /* Check whether the widget has been placed of the screen.
869 * The widget may be MAPPED as when toolbar items do not
870 * fit on the toolbar.
872 if (allocation.x + allocation.width <= 0 &&
873 allocation.y + allocation.height <= 0)
874 return_value = FALSE;
882 /* Checks if all the predecessors (the parent widget, his parent, etc)
883 * are visible Used to check properly the SHOWING state.
886 gtk_widget_accessible_all_parents_visible (GtkWidget *widget)
888 GtkWidget *iter_parent = NULL;
889 gboolean result = TRUE;
891 for (iter_parent = gtk_widget_get_parent (widget); iter_parent;
892 iter_parent = gtk_widget_get_parent (iter_parent))
894 if (!gtk_widget_get_visible (iter_parent))