1 /* GAIL - The GNOME Accessibility Implementation Library
2 * Copyright 2001 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/>.
25 #include <gtk/gtkentry.h>
26 #include <gtk/gtknotebook.h>
27 #include <gtk/gtkmenuitem.h>
28 #include <gtk/gtkmenu.h>
29 #include <gtk/gtkmenubar.h>
30 #include <gtk/gtktogglebutton.h>
31 #include <gtk/gtkcombobox.h>
32 #include <gtk/gtkaccessible.h>
36 #ifdef GDK_WINDOWING_X11
37 #include <atk-bridge.h>
40 static gboolean gail_focus_watcher (GSignalInvocationHint *ihint,
42 const GValue *param_values,
44 static gboolean gail_select_watcher (GSignalInvocationHint *ihint,
46 const GValue *param_values,
48 static gboolean gail_deselect_watcher (GSignalInvocationHint *ihint,
50 const GValue *param_values,
52 static gboolean gail_switch_page_watcher(GSignalInvocationHint *ihint,
54 const GValue *param_values,
56 static void gail_finish_select (GtkWidget *widget);
57 static void gail_map_cb (GtkWidget *widget);
58 static void gail_map_submenu_cb (GtkWidget *widget);
59 static gint gail_focus_idle_handler (gpointer data);
60 static void gail_focus_notify (GtkWidget *widget);
61 static void gail_focus_notify_when_idle (GtkWidget *widget);
63 static void gail_focus_tracker_init (void);
64 static void gail_focus_object_destroyed (gpointer data);
65 static void gail_focus_tracker (AtkObject *object);
66 static void gail_set_focus_widget (GtkWidget *focus_widget,
68 static void gail_set_focus_object (AtkObject *focus_obj,
71 GtkWidget* _focus_widget = NULL;
72 static GtkWidget* next_focus_widget = NULL;
73 static gboolean was_deselect = FALSE;
74 static GtkWidget* subsequent_focus_widget = NULL;
75 static GtkWidget* focus_before_menu = NULL;
76 static guint focus_notify_handler = 0;
77 static guint focus_tracker_id = 0;
78 static GQuark quark_focus_object = 0;
79 static int initialized = FALSE;
82 gail_get_accessible_for_widget (GtkWidget *widget,
85 AtkObject *obj = NULL;
91 if (GTK_IS_ENTRY (widget))
93 else if (GTK_IS_NOTEBOOK (widget))
95 GtkNotebook *notebook;
98 notebook = GTK_NOTEBOOK (widget);
99 page_num = gtk_notebook_get_current_page (notebook);
102 obj = gtk_widget_get_accessible (widget);
103 obj = atk_object_ref_accessible_child (obj, page_num);
104 g_object_unref (obj);
107 else if (GTK_IS_TOGGLE_BUTTON (widget))
109 GtkWidget *other_widget = gtk_widget_get_parent (widget);
110 if (GTK_IS_COMBO_BOX (other_widget))
112 gail_set_focus_widget (other_widget, widget);
113 widget = other_widget;
118 AtkObject *focus_object;
120 obj = gtk_widget_get_accessible (widget);
121 focus_object = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
123 * We check whether the object for this focus_object has been deleted.
124 * This can happen when navigating to an empty directory in nautilus.
127 if (ATK_IS_GOBJECT_ACCESSIBLE (focus_object))
129 if (!atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (focus_object)))
140 gail_focus_watcher (GSignalInvocationHint *ihint,
141 guint n_param_values,
142 const GValue *param_values,
149 object = g_value_get_object (param_values + 0);
150 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
152 event = g_value_get_boxed (param_values + 1);
153 widget = GTK_WIDGET (object);
155 if (event->type == GDK_FOCUS_CHANGE)
157 if (event->focus_change.in)
159 if (GTK_IS_WINDOW (widget))
161 GtkWidget *focus_widget;
165 window = GTK_WINDOW (widget);
166 focus_widget = gtk_window_get_focus (window);
167 g_object_get (window, "type", &type, NULL);
172 * If we already have a potential focus widget set this
173 * windows's focus widget to focus_before_menu so that
174 * it will be reported when menu item is unset.
176 if (next_focus_widget)
178 if (GTK_IS_MENU_ITEM (next_focus_widget) &&
181 void *vp_focus_before_menu = &focus_before_menu;
182 focus_before_menu = focus_widget;
183 g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
188 widget = focus_widget;
190 else if (type == GTK_WINDOW_POPUP)
192 if (GTK_IS_BIN (widget))
194 GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
196 if (GTK_IS_WIDGET (child) && gtk_widget_has_grab (child))
198 if (GTK_IS_MENU_SHELL (child))
200 if (gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (child)))
203 * We have a menu which has a menu item selected
204 * so we do not report focus on the menu.
212 else /* popup window has no children; this edge case occurs in some custom code (OOo for instance) */
217 else /* Widget is a non-popup toplevel with no focus children;
218 don't emit for this case either, as it's useless */
226 if (next_focus_widget)
230 toplevel = gtk_widget_get_toplevel (next_focus_widget);
231 if (toplevel == widget)
232 next_focus_widget = NULL;
240 if (event->type == GDK_MOTION_NOTIFY && gtk_widget_has_focus (widget))
242 if (widget == _focus_widget)
253 #ifdef GDK_WINDOWING_X11
255 * If the focus widget is a GtkSocket without a plug
256 * then ignore the focus notification as the embedded
257 * plug will report a focus notification.
259 if (GTK_IS_SOCKET (widget) &&
260 gtk_socket_get_plug_window (GTK_SOCKET (widget)) != NULL)
265 * The widget may not yet be visible on the screen so we wait until it is.
267 gail_focus_notify_when_idle (widget);
272 gail_select_watcher (GSignalInvocationHint *ihint,
273 guint n_param_values,
274 const GValue *param_values,
280 object = g_value_get_object (param_values + 0);
281 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
283 widget = GTK_WIDGET (object);
285 if (!gtk_widget_get_mapped (widget))
287 g_signal_connect (widget, "map",
288 G_CALLBACK (gail_map_cb),
292 gail_finish_select (widget);
298 gail_finish_select (GtkWidget *widget)
300 if (GTK_IS_MENU_ITEM (widget))
302 GtkMenuItem* menu_item;
305 menu_item = GTK_MENU_ITEM (widget);
306 submenu = gtk_menu_item_get_submenu (menu_item);
308 !gtk_widget_get_mapped (submenu))
311 * If the submenu is not visble, wait until it is before
312 * reporting focus on the menu item.
316 handler_id = g_signal_handler_find (submenu,
318 g_signal_lookup ("map",
322 (gpointer) gail_map_submenu_cb,
325 g_signal_connect (submenu, "map",
326 G_CALLBACK (gail_map_submenu_cb),
331 * If we are waiting to report focus on a menubar or a menu item
332 * because of a previous deselect, cancel it.
335 focus_notify_handler &&
337 (GTK_IS_MENU_BAR (next_focus_widget) ||
338 GTK_IS_MENU_ITEM (next_focus_widget)))
340 void *vp_next_focus_widget = &next_focus_widget;
341 g_source_remove (focus_notify_handler);
342 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
343 next_focus_widget = NULL;
344 focus_notify_handler = 0;
345 was_deselect = FALSE;
349 * If previously focused widget is not a GtkMenuItem or a GtkMenu,
350 * keep track of it so we can return to it after menubar is deactivated
353 !GTK_IS_MENU_ITEM (_focus_widget) &&
354 !GTK_IS_MENU (_focus_widget))
356 void *vp_focus_before_menu = &focus_before_menu;
357 focus_before_menu = _focus_widget;
358 g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
361 gail_focus_notify_when_idle (widget);
367 gail_map_cb (GtkWidget *widget)
369 gail_finish_select (widget);
373 gail_map_submenu_cb (GtkWidget *widget)
375 if (GTK_IS_MENU (widget))
377 GtkWidget *parent_menu_item;
379 parent_menu_item = gtk_menu_get_attach_widget (GTK_MENU (widget));
380 if (parent_menu_item)
381 gail_finish_select (parent_menu_item);
387 gail_deselect_watcher (GSignalInvocationHint *ihint,
388 guint n_param_values,
389 const GValue *param_values,
394 GtkWidget *menu_shell;
396 object = g_value_get_object (param_values + 0);
397 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
399 widget = GTK_WIDGET (object);
401 if (!GTK_IS_MENU_ITEM (widget))
404 if (subsequent_focus_widget == widget)
405 subsequent_focus_widget = NULL;
407 menu_shell = gtk_widget_get_parent (widget);
408 if (GTK_IS_MENU_SHELL (menu_shell))
410 GtkWidget *parent_menu_shell;
412 parent_menu_shell = gtk_menu_shell_get_parent_shell (GTK_MENU_SHELL (menu_shell));
413 if (parent_menu_shell)
415 GtkWidget *active_menu_item;
417 active_menu_item = gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (parent_menu_shell));
418 if (active_menu_item)
420 gail_focus_notify_when_idle (active_menu_item);
425 if (!GTK_IS_MENU_BAR (menu_shell))
427 gail_focus_notify_when_idle (menu_shell);
436 gail_switch_page_watcher (GSignalInvocationHint *ihint,
437 guint n_param_values,
438 const GValue *param_values,
444 object = g_value_get_object (param_values + 0);
445 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
447 widget = GTK_WIDGET (object);
449 if (!GTK_IS_NOTEBOOK (widget))
452 if (gtk_notebook_get_current_page (GTK_NOTEBOOK (widget)) == -1)
455 gail_focus_notify_when_idle (widget);
460 gail_focus_idle_handler (gpointer data)
462 focus_notify_handler = 0;
464 * The widget which was to receive focus may have been removed
466 if (!next_focus_widget)
468 if (next_focus_widget != data)
473 void *vp_next_focus_widget = &next_focus_widget;
474 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
475 next_focus_widget = NULL;
478 gail_focus_notify (data);
484 gail_focus_notify (GtkWidget *widget)
489 if (widget != _focus_widget)
493 void *vp_focus_widget = &_focus_widget;
494 g_object_remove_weak_pointer (G_OBJECT (_focus_widget), vp_focus_widget);
496 _focus_widget = widget;
499 void *vp_focus_widget = &_focus_widget;
500 g_object_add_weak_pointer (G_OBJECT (_focus_widget), vp_focus_widget);
502 * The UI may not have been updated yet; e.g. in gtkhtml2
503 * html_view_layout() is called in a idle handler
505 if (_focus_widget == focus_before_menu)
507 void *vp_focus_before_menu = &focus_before_menu;
508 g_object_remove_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
509 focus_before_menu = NULL;
512 gail_focus_notify_when_idle (_focus_widget);
517 atk_obj = gail_get_accessible_for_widget (_focus_widget, &transient);
521 * Do not report focus on redundant object
524 (atk_object_get_role(atk_obj) != ATK_ROLE_REDUNDANT_OBJECT))
525 atk_focus_tracker_notify (atk_obj);
526 if (atk_obj && transient)
527 g_object_unref (atk_obj);
528 if (subsequent_focus_widget)
530 GtkWidget *tmp_widget = subsequent_focus_widget;
531 subsequent_focus_widget = NULL;
532 gail_focus_notify_when_idle (tmp_widget);
538 gail_focus_notify_when_idle (GtkWidget *widget)
540 if (focus_notify_handler)
545 * Ignore focus request when menu item is going to be focused.
548 if (GTK_IS_MENU_ITEM (next_focus_widget) && !GTK_IS_MENU_ITEM (widget))
551 if (next_focus_widget)
553 if (GTK_IS_MENU_ITEM (next_focus_widget) && GTK_IS_MENU_ITEM (widget))
555 if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (next_focus_widget)) == gtk_widget_get_parent (widget))
557 if (subsequent_focus_widget)
558 g_assert_not_reached ();
559 subsequent_focus_widget = widget;
564 g_source_remove (focus_notify_handler);
565 if (next_focus_widget)
567 void *vp_next_focus_widget = &next_focus_widget;
568 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
569 next_focus_widget = NULL;
574 * Ignore if focus is being set to NULL and we are waiting to set focus
581 void *vp_next_focus_widget = &next_focus_widget;
582 next_focus_widget = widget;
583 g_object_add_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
588 * We are about to report focus as NULL so remove the weak pointer
589 * for the widget we were waiting to report focus on.
591 if (next_focus_widget)
593 void *vp_next_focus_widget = &next_focus_widget;
594 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
595 next_focus_widget = NULL;
599 focus_notify_handler = gdk_threads_add_idle (gail_focus_idle_handler, widget);
603 gail_deactivate_watcher (GSignalInvocationHint *ihint,
604 guint n_param_values,
605 const GValue *param_values,
611 GtkWidget *focus = NULL;
613 object = g_value_get_object (param_values + 0);
614 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
615 widget = GTK_WIDGET (object);
617 g_return_val_if_fail (GTK_IS_MENU_SHELL(widget), TRUE);
618 shell = GTK_MENU_SHELL(widget);
619 if (! gtk_menu_shell_get_parent_shell (shell))
620 focus = focus_before_menu;
623 * If we are waiting to report focus on a menubar or a menu item
624 * because of a previous deselect, cancel it.
627 focus_notify_handler &&
629 (GTK_IS_MENU_BAR (next_focus_widget) ||
630 GTK_IS_MENU_ITEM (next_focus_widget)))
632 void *vp_next_focus_widget = &next_focus_widget;
633 g_source_remove (focus_notify_handler);
634 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
635 next_focus_widget = NULL;
636 focus_notify_handler = 0;
637 was_deselect = FALSE;
639 gail_focus_notify_when_idle (focus);
645 gail_focus_tracker_init (void)
647 static gboolean emission_hooks_added = FALSE;
649 if (!emission_hooks_added)
652 * We cannot be sure that the classes exist so we make sure that they do.
654 g_type_class_ref (GTK_TYPE_WIDGET);
655 g_type_class_ref (GTK_TYPE_MENU_ITEM);
656 g_type_class_ref (GTK_TYPE_MENU_SHELL);
657 g_type_class_ref (GTK_TYPE_NOTEBOOK);
660 * We listen for event_after signal and then check that the
661 * event was a focus in event so we get called after the event.
663 g_signal_add_emission_hook (
664 g_signal_lookup ("event-after", GTK_TYPE_WIDGET), 0,
665 gail_focus_watcher, NULL, (GDestroyNotify) NULL);
667 * A "select" signal is emitted when arrow key is used to
668 * move to a list item in the popup window of a GtkCombo or
669 * a menu item in a menu.
671 g_signal_add_emission_hook (
672 g_signal_lookup ("select", GTK_TYPE_MENU_ITEM), 0,
673 gail_select_watcher, NULL, (GDestroyNotify) NULL);
676 * A "deselect" signal is emitted when arrow key is used to
677 * move from a menu item in a menu to the parent menu.
679 g_signal_add_emission_hook (
680 g_signal_lookup ("deselect", GTK_TYPE_MENU_ITEM), 0,
681 gail_deselect_watcher, NULL, (GDestroyNotify) NULL);
684 * We listen for deactivate signals on menushells to determine
685 * when the "focus" has left the menus.
687 g_signal_add_emission_hook (
688 g_signal_lookup ("deactivate", GTK_TYPE_MENU_SHELL), 0,
689 gail_deactivate_watcher, NULL, (GDestroyNotify) NULL);
692 * We listen for "switch-page" signal on a GtkNotebook to notify
693 * when page has changed because of clicking on a notebook tab.
695 g_signal_add_emission_hook (
696 g_signal_lookup ("switch-page", GTK_TYPE_NOTEBOOK), 0,
697 gail_switch_page_watcher, NULL, (GDestroyNotify) NULL);
698 emission_hooks_added = TRUE;
703 gail_focus_object_destroyed (gpointer data)
707 obj = G_OBJECT (data);
708 g_object_set_qdata (obj, quark_focus_object, NULL);
709 g_object_unref (obj);
713 gail_focus_tracker (AtkObject *focus_object)
716 * Do not report focus on redundant object
719 (atk_object_get_role(focus_object) != ATK_ROLE_REDUNDANT_OBJECT))
721 AtkObject *old_focus_object;
723 if (!GTK_IS_ACCESSIBLE (focus_object))
727 parent = focus_object;
730 parent = atk_object_get_parent (parent);
733 if (GTK_IS_ACCESSIBLE (parent))
739 gail_set_focus_object (focus_object, parent);
744 old_focus_object = g_object_get_qdata (G_OBJECT (focus_object), quark_focus_object);
745 if (old_focus_object)
747 g_object_weak_unref (G_OBJECT (old_focus_object),
748 (GWeakNotify) gail_focus_object_destroyed,
750 g_object_set_qdata (G_OBJECT (focus_object), quark_focus_object, NULL);
751 g_object_unref (G_OBJECT (focus_object));
758 gail_set_focus_widget (GtkWidget *focus_widget,
761 AtkObject *focus_obj;
764 focus_obj = gtk_widget_get_accessible (focus_widget);
765 obj = gtk_widget_get_accessible (widget);
766 gail_set_focus_object (focus_obj, obj);
770 gail_set_focus_object (AtkObject *focus_obj,
773 AtkObject *old_focus_obj;
775 old_focus_obj = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
776 if (old_focus_obj != obj)
779 g_object_weak_unref (G_OBJECT (old_focus_obj),
780 (GWeakNotify) gail_focus_object_destroyed,
784 * We call g_object_ref as if obj is destroyed
785 * while the weak reference exists then destroying the
786 * focus_obj would cause gail_focus_object_destroyed to be
787 * called when obj is not a valid GObject.
791 g_object_weak_ref (G_OBJECT (focus_obj),
792 (GWeakNotify) gail_focus_object_destroyed,
794 g_object_set_qdata (G_OBJECT (obj), quark_focus_object, focus_obj);
799 _gtk_accessibility_shutdown (void)
806 g_clear_object (&atk_misc_instance);
808 #ifdef GDK_WINDOWING_X11
809 atk_bridge_adaptor_cleanup ();
811 _gail_util_uninstall ();
815 _gtk_accessibility_init (void)
822 quark_focus_object = g_quark_from_static_string ("gail-focus-object");
824 atk_focus_tracker_init (gail_focus_tracker_init);
825 focus_tracker_id = atk_add_focus_tracker (gail_focus_tracker);
827 _gail_util_install ();
828 #ifdef GDK_WINDOWING_X11
829 atk_bridge_adaptor_init (NULL, NULL);
832 atk_misc_instance = g_object_new (GAIL_TYPE_MISC, NULL);