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 g_signal_connect (accessible, "focus-event", G_CALLBACK (focus_event), NULL);
136 g_object_set_data (G_OBJECT (obj), "atk-component-layer", GINT_TO_POINTER (ATK_LAYER_WIDGET));
138 obj->role = ATK_ROLE_UNKNOWN;
142 gtk_widget_accessible_destroyed (GtkWidget *widget,
143 GtkAccessible *accessible)
145 gtk_accessible_set_widget (accessible, NULL);
146 atk_object_notify_state_change (ATK_OBJECT (accessible), ATK_STATE_DEFUNCT, TRUE);
150 gtk_widget_accessible_connect_widget_destroyed (GtkAccessible *accessible)
154 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
156 g_signal_connect_after (widget, "destroy",
157 G_CALLBACK (gtk_widget_accessible_destroyed), accessible);
161 gtk_widget_accessible_get_description (AtkObject *accessible)
165 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
169 if (accessible->description)
170 return accessible->description;
172 return gtk_widget_get_tooltip_text (widget);
176 gtk_widget_accessible_get_parent (AtkObject *accessible)
179 GtkWidget *widget, *parent_widget;
181 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
185 parent = accessible->accessible_parent;
189 parent_widget = gtk_widget_get_parent (widget);
190 if (parent_widget == NULL)
193 /* For a widget whose parent is a GtkNoteBook, we return the
194 * accessible object corresponding the GtkNotebookPage containing
195 * the widget as the accessible parent.
197 if (GTK_IS_NOTEBOOK (parent_widget))
201 GtkNotebook *notebook;
204 notebook = GTK_NOTEBOOK (parent_widget);
207 child = gtk_notebook_get_nth_page (notebook, page_num);
212 parent = gtk_widget_get_accessible (parent_widget);
213 parent = atk_object_ref_accessible_child (parent, page_num);
214 g_object_unref (parent);
220 parent = gtk_widget_get_accessible (parent_widget);
225 find_label (GtkWidget *widget)
229 GtkWidget *temp_widget;
231 labels = gtk_widget_list_mnemonic_labels (widget);
238 g_warning ("Widget (%s) has more than one label", G_OBJECT_TYPE_NAME (widget));
240 label = labels->data;
242 g_list_free (labels);
245 /* Ignore a label within a button; bug #136602 */
246 if (label && GTK_IS_BUTTON (widget))
251 if (temp_widget == widget)
256 temp_widget = gtk_widget_get_parent (temp_widget);
262 static AtkRelationSet *
263 gtk_widget_accessible_ref_relation_set (AtkObject *obj)
266 AtkRelationSet *relation_set;
269 AtkRelation* relation;
271 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
275 relation_set = ATK_OBJECT_CLASS (gtk_widget_accessible_parent_class)->ref_relation_set (obj);
277 if (GTK_IS_BOX (widget))
280 if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABELLED_BY))
282 label = find_label (widget);
285 if (GTK_IS_BUTTON (widget))
287 * Handle the case where GnomeIconEntry is the mnemonic widget.
288 * The GtkButton which is a grandchild of the GnomeIconEntry
289 * should really be the mnemonic widget. See bug #133967.
292 GtkWidget *temp_widget;
294 temp_widget = gtk_widget_get_parent (widget);
296 if (GTK_IS_ALIGNMENT (temp_widget))
298 temp_widget = gtk_widget_get_parent (temp_widget);
299 if (GTK_IS_BOX (temp_widget))
301 label = find_label (temp_widget);
303 label = find_label (gtk_widget_get_parent (temp_widget));
307 else if (GTK_IS_COMBO_BOX (widget))
309 * Handle the case when GtkFileChooserButton is the mnemonic
310 * widget. The GtkComboBox which is a child of the
311 * GtkFileChooserButton should be the mnemonic widget.
315 GtkWidget *temp_widget;
317 temp_widget = gtk_widget_get_parent (widget);
318 if (GTK_IS_BOX (temp_widget))
320 label = find_label (temp_widget);
327 array[0] = gtk_widget_get_accessible (label);
329 relation = atk_relation_new (array, 1, ATK_RELATION_LABELLED_BY);
330 atk_relation_set_add (relation_set, relation);
331 g_object_unref (relation);
339 gtk_widget_accessible_ref_state_set (AtkObject *accessible)
342 AtkStateSet *state_set;
344 state_set = ATK_OBJECT_CLASS (gtk_widget_accessible_parent_class)->ref_state_set (accessible);
346 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
348 atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
351 if (gtk_widget_is_sensitive (widget))
353 atk_state_set_add_state (state_set, ATK_STATE_SENSITIVE);
354 atk_state_set_add_state (state_set, ATK_STATE_ENABLED);
357 if (gtk_widget_get_can_focus (widget))
359 atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE);
362 * We do not currently generate notifications when an ATK object
363 * corresponding to a GtkWidget changes visibility by being scrolled
364 * on or off the screen. The testcase for this is the main window
365 * of the testgtk application in which a set of buttons in a GtkVBox
366 * is in a scrolled window with a viewport.
368 * To generate the notifications we would need to do the following:
369 * 1) Find the GtkViewport among the ancestors of the objects
370 * 2) Create an accessible for the viewport
371 * 3) Connect to the value-changed signal on the viewport
372 * 4) When the signal is received we need to traverse the children
373 * of the viewport and check whether the children are visible or not
374 * visible; we may want to restrict this to the widgets for which
375 * accessible objects have been created.
376 * 5) We probably need to store a variable on_screen in the
377 * GtkWidgetAccessible data structure so we can determine whether
378 * the value has changed.
380 if (gtk_widget_get_visible (widget))
382 atk_state_set_add_state (state_set, ATK_STATE_VISIBLE);
383 if (gtk_widget_accessible_on_screen (widget) &&
384 gtk_widget_get_mapped (widget) &&
385 gtk_widget_accessible_all_parents_visible (widget))
386 atk_state_set_add_state (state_set, ATK_STATE_SHOWING);
389 if (gtk_widget_has_focus (widget) && (widget == focus_widget))
391 AtkObject *focus_obj;
393 focus_obj = g_object_get_data (G_OBJECT (accessible), "gail-focus-object");
394 if (focus_obj == NULL)
395 atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
398 if (gtk_widget_has_default (widget))
399 atk_state_set_add_state (state_set, ATK_STATE_DEFAULT);
401 if (GTK_IS_ORIENTABLE (widget))
403 if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
404 atk_state_set_add_state (state_set, ATK_STATE_HORIZONTAL);
406 atk_state_set_add_state (state_set, ATK_STATE_VERTICAL);
413 gtk_widget_accessible_get_index_in_parent (AtkObject *accessible)
416 GtkWidget *parent_widget;
420 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
425 if (accessible->accessible_parent)
429 parent = accessible->accessible_parent;
431 if (GTK_IS_NOTEBOOK_PAGE_ACCESSIBLE (parent))
436 gboolean found = FALSE;
438 n_children = atk_object_get_n_accessible_children (parent);
439 for (i = 0; i < n_children; i++)
443 child = atk_object_ref_accessible_child (parent, i);
444 if (child == accessible)
447 g_object_unref (child);
454 if (!GTK_IS_WIDGET (widget))
456 parent_widget = gtk_widget_get_parent (widget);
457 if (!GTK_IS_CONTAINER (parent_widget))
460 children = gtk_container_get_children (GTK_CONTAINER (parent_widget));
462 index = g_list_index (children, widget);
463 g_list_free (children);
467 /* This function is the default implementation for the notify_gtk
468 * vfunc which gets called when a property changes value on the
469 * GtkWidget associated with a GtkWidgetAccessible. It constructs
470 * an AtkPropertyValues structure and emits a "property_changed"
471 * signal which causes the user specified AtkPropertyChangeHandler
475 gtk_widget_accessible_notify_gtk (GObject *obj,
478 GtkWidget* widget = GTK_WIDGET (obj);
479 AtkObject* atk_obj = gtk_widget_get_accessible (widget);
483 if (strcmp (pspec->name, "has-focus") == 0)
485 * We use focus-in-event and focus-out-event signals to catch
486 * focus changes so we ignore this.
489 else if (strcmp (pspec->name, "visible") == 0)
491 state = ATK_STATE_VISIBLE;
492 value = gtk_widget_get_visible (widget);
494 else if (strcmp (pspec->name, "sensitive") == 0)
496 state = ATK_STATE_SENSITIVE;
497 value = gtk_widget_get_sensitive (widget);
499 else if (strcmp (pspec->name, "orientation") == 0)
501 GtkOrientable *orientable;
503 orientable = GTK_ORIENTABLE (widget);
505 state = ATK_STATE_HORIZONTAL;
506 value = (gtk_orientable_get_orientation (orientable) == GTK_ORIENTATION_HORIZONTAL);
511 atk_object_notify_state_change (atk_obj, state, value);
512 if (state == ATK_STATE_SENSITIVE)
513 atk_object_notify_state_change (atk_obj, ATK_STATE_ENABLED, value);
515 if (state == ATK_STATE_HORIZONTAL)
516 atk_object_notify_state_change (atk_obj, ATK_STATE_VERTICAL, !value);
519 /* This function is the default implementation for the focus_gtk
520 * vfunc which gets called for focus_in/out_event.
522 * It emits a focus-event signal on the GtkWidgetAccessible.
525 gtk_widget_accessible_focus_gtk (GtkWidget *widget,
526 GdkEventFocus *event)
528 AtkObject* accessible;
532 accessible = gtk_widget_get_accessible (widget);
534 g_signal_emit_by_name (accessible, "focus_event", event->in, &return_val);
538 static AtkAttributeSet *
539 gtk_widget_accessible_get_attributes (AtkObject *obj)
541 AtkAttributeSet *attributes;
542 AtkAttribute *toolkit;
544 toolkit = g_new (AtkAttribute, 1);
545 toolkit->name = g_strdup ("toolkit");
546 toolkit->value = g_strdup ("gtk");
548 attributes = g_slist_append (NULL, toolkit);
554 gtk_widget_accessible_class_init (GtkWidgetAccessibleClass *klass)
556 AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
557 GtkAccessibleClass *accessible_class = GTK_ACCESSIBLE_CLASS (klass);
559 klass->notify_gtk = gtk_widget_accessible_notify_gtk;
560 klass->focus_gtk = gtk_widget_accessible_focus_gtk;
562 accessible_class->connect_widget_destroyed = gtk_widget_accessible_connect_widget_destroyed;
564 class->get_description = gtk_widget_accessible_get_description;
565 class->get_parent = gtk_widget_accessible_get_parent;
566 class->ref_relation_set = gtk_widget_accessible_ref_relation_set;
567 class->ref_state_set = gtk_widget_accessible_ref_state_set;
568 class->get_index_in_parent = gtk_widget_accessible_get_index_in_parent;
569 class->initialize = gtk_widget_accessible_initialize;
570 class->get_attributes = gtk_widget_accessible_get_attributes;
574 gtk_widget_accessible_init (GtkWidgetAccessible *accessible)
579 gtk_widget_accessible_get_extents (AtkComponent *component,
584 AtkCoordType coord_type)
587 gint x_window, y_window;
588 gint x_toplevel, y_toplevel;
590 GtkAllocation allocation;
592 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
596 gtk_widget_get_allocation (widget, &allocation);
597 *width = allocation.width;
598 *height = allocation.height;
599 if (!gtk_widget_accessible_on_screen (widget) || (!gtk_widget_is_drawable (widget)))
606 if (gtk_widget_get_parent (widget))
610 window = gtk_widget_get_parent_window (widget);
616 window = gtk_widget_get_window (widget);
618 gdk_window_get_origin (window, &x_window, &y_window);
622 if (coord_type == ATK_XY_WINDOW)
624 window = gdk_window_get_toplevel (gtk_widget_get_window (widget));
625 gdk_window_get_origin (window, &x_toplevel, &y_toplevel);
633 gtk_widget_accessible_get_size (AtkComponent *component,
639 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
643 *width = gtk_widget_get_allocated_width (widget);
644 *height = gtk_widget_get_allocated_height (widget);
648 gtk_widget_accessible_get_layer (AtkComponent *component)
652 layer = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (component), "atk-component-layer"));
654 return (AtkLayer) layer;
658 gtk_widget_accessible_grab_focus (AtkComponent *component)
663 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
667 if (!gtk_widget_get_can_focus (widget))
670 gtk_widget_grab_focus (widget);
671 toplevel = gtk_widget_get_toplevel (widget);
672 if (gtk_widget_is_toplevel (toplevel))
674 #ifdef GDK_WINDOWING_X11
675 gtk_window_present_with_time (GTK_WINDOW (toplevel),
676 gdk_x11_get_server_time (gtk_widget_get_window (widget)));
678 gtk_window_present (GTK_WINDOW (toplevel));
685 gtk_widget_accessible_set_extents (AtkComponent *component,
690 AtkCoordType coord_type)
694 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
698 if (!gtk_widget_is_toplevel (widget))
701 if (coord_type == ATK_XY_WINDOW)
703 gint x_current, y_current;
704 GdkWindow *window = gtk_widget_get_window (widget);
706 gdk_window_get_origin (window, &x_current, &y_current);
709 if (x_current < 0 || y_current < 0)
713 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
714 gtk_widget_set_size_request (widget, width, height);
718 else if (coord_type == ATK_XY_SCREEN)
720 gtk_window_move (GTK_WINDOW (widget), x, y);
721 gtk_widget_set_size_request (widget, width, height);
728 gtk_widget_accessible_set_position (AtkComponent *component,
731 AtkCoordType coord_type)
735 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
739 if (gtk_widget_is_toplevel (widget))
741 if (coord_type == ATK_XY_WINDOW)
743 gint x_current, y_current;
744 GdkWindow *window = gtk_widget_get_window (widget);
746 gdk_window_get_origin (window, &x_current, &y_current);
749 if (x_current < 0 || y_current < 0)
753 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
757 else if (coord_type == ATK_XY_SCREEN)
759 gtk_window_move (GTK_WINDOW (widget), x, y);
767 gtk_widget_accessible_set_size (AtkComponent *component,
773 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
777 if (gtk_widget_is_toplevel (widget))
779 gtk_widget_set_size_request (widget, width, height);
787 atk_component_interface_init (AtkComponentIface *iface)
789 iface->get_extents = gtk_widget_accessible_get_extents;
790 iface->get_size = gtk_widget_accessible_get_size;
791 iface->get_layer = gtk_widget_accessible_get_layer;
792 iface->grab_focus = gtk_widget_accessible_grab_focus;
793 iface->set_extents = gtk_widget_accessible_set_extents;
794 iface->set_position = gtk_widget_accessible_set_position;
795 iface->set_size = gtk_widget_accessible_set_size;
798 /* This function checks whether the widget has an ancestor which is
799 * a GtkViewport and, if so, whether any part of the widget intersects
800 * the visible rectangle of the GtkViewport.
803 gtk_widget_accessible_on_screen (GtkWidget *widget)
805 GtkAllocation allocation;
807 gboolean return_value;
809 gtk_widget_get_allocation (widget, &allocation);
811 viewport = gtk_widget_get_ancestor (widget, GTK_TYPE_VIEWPORT);
814 GtkAllocation viewport_allocation;
815 GtkAdjustment *adjustment;
816 GdkRectangle visible_rect;
818 gtk_widget_get_allocation (viewport, &viewport_allocation);
820 adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (viewport));
821 visible_rect.y = gtk_adjustment_get_value (adjustment);
822 adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (viewport));
823 visible_rect.x = gtk_adjustment_get_value (adjustment);
824 visible_rect.width = viewport_allocation.width;
825 visible_rect.height = viewport_allocation.height;
827 if (((allocation.x + allocation.width) < visible_rect.x) ||
828 ((allocation.y + allocation.height) < visible_rect.y) ||
829 (allocation.x > (visible_rect.x + visible_rect.width)) ||
830 (allocation.y > (visible_rect.y + visible_rect.height)))
831 return_value = FALSE;
837 /* Check whether the widget has been placed of the screen.
838 * The widget may be MAPPED as when toolbar items do not
839 * fit on the toolbar.
841 if (allocation.x + allocation.width <= 0 &&
842 allocation.y + allocation.height <= 0)
843 return_value = FALSE;
851 /* Checks if all the predecessors (the parent widget, his parent, etc)
852 * are visible Used to check properly the SHOWING state.
855 gtk_widget_accessible_all_parents_visible (GtkWidget *widget)
857 GtkWidget *iter_parent = NULL;
858 gboolean result = TRUE;
860 for (iter_parent = gtk_widget_get_parent (widget); iter_parent;
861 iter_parent = gtk_widget_get_parent (iter_parent))
863 if (!gtk_widget_get_visible (iter_parent))