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.
23 #ifdef GDK_WINDOWING_X11
24 #include <gdk/x11/gdkx.h>
26 #include "gtkwidgetaccessible.h"
27 #include "gtknotebookpageaccessible.h"
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_initialize (AtkObject *obj,
117 widget = GTK_WIDGET (data);
119 g_signal_connect_after (widget, "focus-in-event", G_CALLBACK (focus_cb), NULL);
120 g_signal_connect_after (widget, "focus-out-event", G_CALLBACK (focus_cb), NULL);
121 g_signal_connect (widget, "notify", G_CALLBACK (notify_cb), NULL);
122 g_signal_connect (widget, "size-allocate", G_CALLBACK (size_allocate_cb), NULL);
123 g_signal_connect (widget, "map", G_CALLBACK (map_cb), NULL);
124 g_signal_connect (widget, "unmap", G_CALLBACK (map_cb), NULL);
126 GTK_WIDGET_ACCESSIBLE (obj)->layer = ATK_LAYER_WIDGET;
127 obj->role = ATK_ROLE_UNKNOWN;
131 gtk_widget_accessible_get_description (AtkObject *accessible)
135 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
139 if (accessible->description)
140 return accessible->description;
142 return gtk_widget_get_tooltip_text (widget);
146 gtk_widget_accessible_get_parent (AtkObject *accessible)
149 GtkWidget *widget, *parent_widget;
151 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
155 parent = accessible->accessible_parent;
159 parent_widget = gtk_widget_get_parent (widget);
160 if (parent_widget == NULL)
163 /* For a widget whose parent is a GtkNoteBook, we return the
164 * accessible object corresponding the GtkNotebookPage containing
165 * the widget as the accessible parent.
167 if (GTK_IS_NOTEBOOK (parent_widget))
171 GtkNotebook *notebook;
174 notebook = GTK_NOTEBOOK (parent_widget);
177 child = gtk_notebook_get_nth_page (notebook, page_num);
182 parent = gtk_widget_get_accessible (parent_widget);
183 parent = atk_object_ref_accessible_child (parent, page_num);
184 g_object_unref (parent);
190 parent = gtk_widget_get_accessible (parent_widget);
195 find_label (GtkWidget *widget)
199 GtkWidget *temp_widget;
202 labels = gtk_widget_list_mnemonic_labels (widget);
214 g_list_free (labels);
216 /* Ignore a label within a button; bug #136602 */
217 if (label && GTK_IS_BUTTON (widget))
222 if (temp_widget == widget)
227 temp_widget = gtk_widget_get_parent (temp_widget);
233 static AtkRelationSet *
234 gtk_widget_accessible_ref_relation_set (AtkObject *obj)
237 AtkRelationSet *relation_set;
240 AtkRelation* relation;
242 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
246 relation_set = ATK_OBJECT_CLASS (_gtk_widget_accessible_parent_class)->ref_relation_set (obj);
248 if (GTK_IS_BOX (widget))
251 if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABELLED_BY))
253 label = find_label (widget);
256 if (GTK_IS_BUTTON (widget))
258 * Handle the case where GnomeIconEntry is the mnemonic widget.
259 * The GtkButton which is a grandchild of the GnomeIconEntry
260 * should really be the mnemonic widget. See bug #133967.
263 GtkWidget *temp_widget;
265 temp_widget = gtk_widget_get_parent (widget);
267 if (GTK_IS_ALIGNMENT (temp_widget))
269 temp_widget = gtk_widget_get_parent (temp_widget);
270 if (GTK_IS_BOX (temp_widget))
272 label = find_label (temp_widget);
274 label = find_label (gtk_widget_get_parent (temp_widget));
278 else if (GTK_IS_COMBO_BOX (widget))
280 * Handle the case when GtkFileChooserButton is the mnemonic
281 * widget. The GtkComboBox which is a child of the
282 * GtkFileChooserButton should be the mnemonic widget.
286 GtkWidget *temp_widget;
288 temp_widget = gtk_widget_get_parent (widget);
289 if (GTK_IS_BOX (temp_widget))
291 label = find_label (temp_widget);
298 array[0] = gtk_widget_get_accessible (label);
300 relation = atk_relation_new (array, 1, ATK_RELATION_LABELLED_BY);
301 atk_relation_set_add (relation_set, relation);
302 g_object_unref (relation);
310 gtk_widget_accessible_ref_state_set (AtkObject *accessible)
313 AtkStateSet *state_set;
315 state_set = ATK_OBJECT_CLASS (_gtk_widget_accessible_parent_class)->ref_state_set (accessible);
317 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
319 atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
322 if (gtk_widget_is_sensitive (widget))
324 atk_state_set_add_state (state_set, ATK_STATE_SENSITIVE);
325 atk_state_set_add_state (state_set, ATK_STATE_ENABLED);
328 if (gtk_widget_get_can_focus (widget))
330 atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE);
333 * We do not currently generate notifications when an ATK object
334 * corresponding to a GtkWidget changes visibility by being scrolled
335 * on or off the screen. The testcase for this is the main window
336 * of the testgtk application in which a set of buttons in a GtkVBox
337 * is in a scrolled window with a viewport.
339 * To generate the notifications we would need to do the following:
340 * 1) Find the GtkViewport among the ancestors of the objects
341 * 2) Create an accessible for the viewport
342 * 3) Connect to the value-changed signal on the viewport
343 * 4) When the signal is received we need to traverse the children
344 * of the viewport and check whether the children are visible or not
345 * visible; we may want to restrict this to the widgets for which
346 * accessible objects have been created.
347 * 5) We probably need to store a variable on_screen in the
348 * GtkWidgetAccessible data structure so we can determine whether
349 * the value has changed.
351 if (gtk_widget_get_visible (widget))
353 atk_state_set_add_state (state_set, ATK_STATE_VISIBLE);
354 if (gtk_widget_accessible_on_screen (widget) &&
355 gtk_widget_get_mapped (widget) &&
356 gtk_widget_accessible_all_parents_visible (widget))
357 atk_state_set_add_state (state_set, ATK_STATE_SHOWING);
360 if (gtk_widget_has_focus (widget) && (widget == _focus_widget))
362 AtkObject *focus_obj;
364 focus_obj = g_object_get_data (G_OBJECT (accessible), "gail-focus-object");
365 if (focus_obj == NULL)
366 atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
369 if (gtk_widget_has_default (widget))
370 atk_state_set_add_state (state_set, ATK_STATE_DEFAULT);
372 if (GTK_IS_ORIENTABLE (widget))
374 if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
375 atk_state_set_add_state (state_set, ATK_STATE_HORIZONTAL);
377 atk_state_set_add_state (state_set, ATK_STATE_VERTICAL);
384 gtk_widget_accessible_get_index_in_parent (AtkObject *accessible)
387 GtkWidget *parent_widget;
391 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
396 if (accessible->accessible_parent)
400 parent = accessible->accessible_parent;
402 if (GTK_IS_NOTEBOOK_PAGE_ACCESSIBLE (parent))
407 gboolean found = FALSE;
409 n_children = atk_object_get_n_accessible_children (parent);
410 for (i = 0; i < n_children; i++)
414 child = atk_object_ref_accessible_child (parent, i);
415 if (child == accessible)
418 g_object_unref (child);
425 if (!GTK_IS_WIDGET (widget))
427 parent_widget = gtk_widget_get_parent (widget);
428 if (!GTK_IS_CONTAINER (parent_widget))
431 children = gtk_container_get_children (GTK_CONTAINER (parent_widget));
433 index = g_list_index (children, widget);
434 g_list_free (children);
438 /* This function is the default implementation for the notify_gtk
439 * vfunc which gets called when a property changes value on the
440 * GtkWidget associated with a GtkWidgetAccessible. It constructs
441 * an AtkPropertyValues structure and emits a "property_changed"
442 * signal which causes the user specified AtkPropertyChangeHandler
446 gtk_widget_accessible_notify_gtk (GObject *obj,
449 GtkWidget* widget = GTK_WIDGET (obj);
450 AtkObject* atk_obj = gtk_widget_get_accessible (widget);
454 if (g_strcmp0 (pspec->name, "has-focus") == 0)
456 * We use focus-in-event and focus-out-event signals to catch
457 * focus changes so we ignore this.
460 else if (g_strcmp0 (pspec->name, "visible") == 0)
462 state = ATK_STATE_VISIBLE;
463 value = gtk_widget_get_visible (widget);
465 else if (g_strcmp0 (pspec->name, "sensitive") == 0)
467 state = ATK_STATE_SENSITIVE;
468 value = gtk_widget_get_sensitive (widget);
470 else if (g_strcmp0 (pspec->name, "orientation") == 0)
472 GtkOrientable *orientable;
474 orientable = GTK_ORIENTABLE (widget);
476 state = ATK_STATE_HORIZONTAL;
477 value = (gtk_orientable_get_orientation (orientable) == GTK_ORIENTATION_HORIZONTAL);
482 atk_object_notify_state_change (atk_obj, state, value);
483 if (state == ATK_STATE_SENSITIVE)
484 atk_object_notify_state_change (atk_obj, ATK_STATE_ENABLED, value);
486 if (state == ATK_STATE_HORIZONTAL)
487 atk_object_notify_state_change (atk_obj, ATK_STATE_VERTICAL, !value);
490 static AtkAttributeSet *
491 gtk_widget_accessible_get_attributes (AtkObject *obj)
493 AtkAttributeSet *attributes;
494 AtkAttribute *toolkit;
496 toolkit = g_new (AtkAttribute, 1);
497 toolkit->name = g_strdup ("toolkit");
498 toolkit->value = g_strdup ("gtk");
500 attributes = g_slist_append (NULL, toolkit);
506 _gtk_widget_accessible_class_init (GtkWidgetAccessibleClass *klass)
508 AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
510 klass->notify_gtk = gtk_widget_accessible_notify_gtk;
512 class->get_description = gtk_widget_accessible_get_description;
513 class->get_parent = gtk_widget_accessible_get_parent;
514 class->ref_relation_set = gtk_widget_accessible_ref_relation_set;
515 class->ref_state_set = gtk_widget_accessible_ref_state_set;
516 class->get_index_in_parent = gtk_widget_accessible_get_index_in_parent;
517 class->initialize = gtk_widget_accessible_initialize;
518 class->get_attributes = gtk_widget_accessible_get_attributes;
519 class->focus_event = gtk_widget_accessible_focus_event;
523 _gtk_widget_accessible_init (GtkWidgetAccessible *accessible)
528 gtk_widget_accessible_get_extents (AtkComponent *component,
533 AtkCoordType coord_type)
536 gint x_window, y_window;
537 gint x_toplevel, y_toplevel;
539 GtkAllocation allocation;
541 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
545 gtk_widget_get_allocation (widget, &allocation);
546 *width = allocation.width;
547 *height = allocation.height;
548 if (!gtk_widget_accessible_on_screen (widget) || (!gtk_widget_is_drawable (widget)))
555 if (gtk_widget_get_parent (widget))
559 window = gtk_widget_get_parent_window (widget);
565 window = gtk_widget_get_window (widget);
567 gdk_window_get_origin (window, &x_window, &y_window);
571 if (coord_type == ATK_XY_WINDOW)
573 window = gdk_window_get_toplevel (gtk_widget_get_window (widget));
574 gdk_window_get_origin (window, &x_toplevel, &y_toplevel);
582 gtk_widget_accessible_get_size (AtkComponent *component,
588 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
592 *width = gtk_widget_get_allocated_width (widget);
593 *height = gtk_widget_get_allocated_height (widget);
597 gtk_widget_accessible_get_layer (AtkComponent *component)
599 GtkWidgetAccessible *accessible = GTK_WIDGET_ACCESSIBLE (component);
601 return accessible->layer;
605 gtk_widget_accessible_grab_focus (AtkComponent *component)
610 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
614 if (!gtk_widget_get_can_focus (widget))
617 gtk_widget_grab_focus (widget);
618 toplevel = gtk_widget_get_toplevel (widget);
619 if (gtk_widget_is_toplevel (toplevel))
621 #ifdef GDK_WINDOWING_X11
622 gtk_window_present_with_time (GTK_WINDOW (toplevel),
623 gdk_x11_get_server_time (gtk_widget_get_window (widget)));
625 gtk_window_present (GTK_WINDOW (toplevel));
632 gtk_widget_accessible_set_extents (AtkComponent *component,
637 AtkCoordType coord_type)
641 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
645 if (!gtk_widget_is_toplevel (widget))
648 if (coord_type == ATK_XY_WINDOW)
650 gint x_current, y_current;
651 GdkWindow *window = gtk_widget_get_window (widget);
653 gdk_window_get_origin (window, &x_current, &y_current);
656 if (x_current < 0 || y_current < 0)
660 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
661 gtk_widget_set_size_request (widget, width, height);
665 else if (coord_type == ATK_XY_SCREEN)
667 gtk_window_move (GTK_WINDOW (widget), x, y);
668 gtk_widget_set_size_request (widget, width, height);
675 gtk_widget_accessible_set_position (AtkComponent *component,
678 AtkCoordType coord_type)
682 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
686 if (gtk_widget_is_toplevel (widget))
688 if (coord_type == ATK_XY_WINDOW)
690 gint x_current, y_current;
691 GdkWindow *window = gtk_widget_get_window (widget);
693 gdk_window_get_origin (window, &x_current, &y_current);
696 if (x_current < 0 || y_current < 0)
700 gtk_window_move (GTK_WINDOW (widget), x_current, y_current);
704 else if (coord_type == ATK_XY_SCREEN)
706 gtk_window_move (GTK_WINDOW (widget), x, y);
714 gtk_widget_accessible_set_size (AtkComponent *component,
720 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
724 if (gtk_widget_is_toplevel (widget))
726 gtk_widget_set_size_request (widget, width, height);
734 atk_component_interface_init (AtkComponentIface *iface)
736 iface->get_extents = gtk_widget_accessible_get_extents;
737 iface->get_size = gtk_widget_accessible_get_size;
738 iface->get_layer = gtk_widget_accessible_get_layer;
739 iface->grab_focus = gtk_widget_accessible_grab_focus;
740 iface->set_extents = gtk_widget_accessible_set_extents;
741 iface->set_position = gtk_widget_accessible_set_position;
742 iface->set_size = gtk_widget_accessible_set_size;
745 /* This function checks whether the widget has an ancestor which is
746 * a GtkViewport and, if so, whether any part of the widget intersects
747 * the visible rectangle of the GtkViewport.
750 gtk_widget_accessible_on_screen (GtkWidget *widget)
752 GtkAllocation allocation;
754 gboolean return_value;
756 gtk_widget_get_allocation (widget, &allocation);
758 viewport = gtk_widget_get_ancestor (widget, GTK_TYPE_VIEWPORT);
761 GtkAllocation viewport_allocation;
762 GtkAdjustment *adjustment;
763 GdkRectangle visible_rect;
765 gtk_widget_get_allocation (viewport, &viewport_allocation);
767 adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (viewport));
768 visible_rect.y = gtk_adjustment_get_value (adjustment);
769 adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (viewport));
770 visible_rect.x = gtk_adjustment_get_value (adjustment);
771 visible_rect.width = viewport_allocation.width;
772 visible_rect.height = viewport_allocation.height;
774 if (((allocation.x + allocation.width) < visible_rect.x) ||
775 ((allocation.y + allocation.height) < visible_rect.y) ||
776 (allocation.x > (visible_rect.x + visible_rect.width)) ||
777 (allocation.y > (visible_rect.y + visible_rect.height)))
778 return_value = FALSE;
784 /* Check whether the widget has been placed of the screen.
785 * The widget may be MAPPED as when toolbar items do not
786 * fit on the toolbar.
788 if (allocation.x + allocation.width <= 0 &&
789 allocation.y + allocation.height <= 0)
790 return_value = FALSE;
798 /* Checks if all the predecessors (the parent widget, his parent, etc)
799 * are visible Used to check properly the SHOWING state.
802 gtk_widget_accessible_all_parents_visible (GtkWidget *widget)
804 GtkWidget *iter_parent = NULL;
805 gboolean result = TRUE;
807 for (iter_parent = gtk_widget_get_parent (widget); iter_parent;
808 iter_parent = gtk_widget_get_parent (iter_parent))
810 if (!gtk_widget_get_visible (iter_parent))