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/>.
27 #include <gtk/gtkentry.h>
28 #include <gtk/gtknotebook.h>
29 #include <gtk/gtkmenuitem.h>
30 #include <gtk/gtkmenu.h>
31 #include <gtk/gtkmenubar.h>
32 #include <gtk/gtktogglebutton.h>
33 #include <gtk/gtkcombobox.h>
34 #include <gtk/gtkaccessible.h>
38 #ifdef GDK_WINDOWING_X11
39 #include <atk-bridge.h>
42 static gboolean gail_focus_watcher (GSignalInvocationHint *ihint,
44 const GValue *param_values,
46 static gboolean gail_select_watcher (GSignalInvocationHint *ihint,
48 const GValue *param_values,
50 static gboolean gail_deselect_watcher (GSignalInvocationHint *ihint,
52 const GValue *param_values,
54 static gboolean gail_switch_page_watcher(GSignalInvocationHint *ihint,
56 const GValue *param_values,
58 static void gail_finish_select (GtkWidget *widget);
59 static void gail_map_cb (GtkWidget *widget);
60 static void gail_map_submenu_cb (GtkWidget *widget);
61 static gint gail_focus_idle_handler (gpointer data);
62 static void gail_focus_notify (GtkWidget *widget);
63 static void gail_focus_notify_when_idle (GtkWidget *widget);
65 static void gail_focus_tracker_init (void);
66 static void gail_focus_object_destroyed (gpointer data);
67 static void gail_focus_tracker (AtkObject *object);
68 static void gail_set_focus_widget (GtkWidget *focus_widget,
70 static void gail_set_focus_object (AtkObject *focus_obj,
73 GtkWidget* _focus_widget = NULL;
74 static GtkWidget* next_focus_widget = NULL;
75 static gboolean was_deselect = FALSE;
76 static GtkWidget* subsequent_focus_widget = NULL;
77 static GtkWidget* focus_before_menu = NULL;
78 static guint focus_notify_handler = 0;
79 static guint focus_tracker_id = 0;
80 static GQuark quark_focus_object = 0;
81 static int initialized = FALSE;
84 gail_get_accessible_for_widget (GtkWidget *widget,
87 AtkObject *obj = NULL;
93 if (GTK_IS_ENTRY (widget))
95 else if (GTK_IS_NOTEBOOK (widget))
97 GtkNotebook *notebook;
100 notebook = GTK_NOTEBOOK (widget);
101 page_num = gtk_notebook_get_current_page (notebook);
104 obj = gtk_widget_get_accessible (widget);
105 obj = atk_object_ref_accessible_child (obj, page_num);
106 g_object_unref (obj);
109 else if (GTK_IS_TOGGLE_BUTTON (widget))
111 GtkWidget *other_widget = gtk_widget_get_parent (widget);
112 if (GTK_IS_COMBO_BOX (other_widget))
114 gail_set_focus_widget (other_widget, widget);
115 widget = other_widget;
120 AtkObject *focus_object;
122 obj = gtk_widget_get_accessible (widget);
123 focus_object = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
125 * We check whether the object for this focus_object has been deleted.
126 * This can happen when navigating to an empty directory in nautilus.
129 if (ATK_IS_GOBJECT_ACCESSIBLE (focus_object))
131 if (!atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (focus_object)))
142 gail_focus_watcher (GSignalInvocationHint *ihint,
143 guint n_param_values,
144 const GValue *param_values,
151 object = g_value_get_object (param_values + 0);
152 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
154 event = g_value_get_boxed (param_values + 1);
155 widget = GTK_WIDGET (object);
157 if (event->type == GDK_FOCUS_CHANGE)
159 if (event->focus_change.in)
161 if (GTK_IS_WINDOW (widget))
163 GtkWidget *focus_widget;
167 window = GTK_WINDOW (widget);
168 focus_widget = gtk_window_get_focus (window);
169 g_object_get (window, "type", &type, NULL);
174 * If we already have a potential focus widget set this
175 * windows's focus widget to focus_before_menu so that
176 * it will be reported when menu item is unset.
178 if (next_focus_widget)
180 if (GTK_IS_MENU_ITEM (next_focus_widget) &&
183 void *vp_focus_before_menu = &focus_before_menu;
184 focus_before_menu = focus_widget;
185 g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
190 widget = focus_widget;
192 else if (type == GTK_WINDOW_POPUP)
194 if (GTK_IS_BIN (widget))
196 GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
198 if (GTK_IS_WIDGET (child) && gtk_widget_has_grab (child))
200 if (GTK_IS_MENU_SHELL (child))
202 if (gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (child)))
205 * We have a menu which has a menu item selected
206 * so we do not report focus on the menu.
214 else /* popup window has no children; this edge case occurs in some custom code (OOo for instance) */
219 else /* Widget is a non-popup toplevel with no focus children;
220 don't emit for this case either, as it's useless */
228 if (next_focus_widget)
232 toplevel = gtk_widget_get_toplevel (next_focus_widget);
233 if (toplevel == widget)
234 next_focus_widget = NULL;
242 if (event->type == GDK_MOTION_NOTIFY && gtk_widget_has_focus (widget))
244 if (widget == _focus_widget)
255 #ifdef GDK_WINDOWING_X11
257 * If the focus widget is a GtkSocket without a plug
258 * then ignore the focus notification as the embedded
259 * plug will report a focus notification.
261 if (GTK_IS_SOCKET (widget) &&
262 gtk_socket_get_plug_window (GTK_SOCKET (widget)) != NULL)
267 * The widget may not yet be visible on the screen so we wait until it is.
269 gail_focus_notify_when_idle (widget);
274 gail_select_watcher (GSignalInvocationHint *ihint,
275 guint n_param_values,
276 const GValue *param_values,
282 object = g_value_get_object (param_values + 0);
283 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
285 widget = GTK_WIDGET (object);
287 if (!gtk_widget_get_mapped (widget))
289 g_signal_connect (widget, "map",
290 G_CALLBACK (gail_map_cb),
294 gail_finish_select (widget);
300 gail_finish_select (GtkWidget *widget)
302 if (GTK_IS_MENU_ITEM (widget))
304 GtkMenuItem* menu_item;
307 menu_item = GTK_MENU_ITEM (widget);
308 submenu = gtk_menu_item_get_submenu (menu_item);
310 !gtk_widget_get_mapped (submenu))
313 * If the submenu is not visble, wait until it is before
314 * reporting focus on the menu item.
318 handler_id = g_signal_handler_find (submenu,
320 g_signal_lookup ("map",
324 (gpointer) gail_map_submenu_cb,
327 g_signal_connect (submenu, "map",
328 G_CALLBACK (gail_map_submenu_cb),
333 * If we are waiting to report focus on a menubar or a menu item
334 * because of a previous deselect, cancel it.
337 focus_notify_handler &&
339 (GTK_IS_MENU_BAR (next_focus_widget) ||
340 GTK_IS_MENU_ITEM (next_focus_widget)))
342 void *vp_next_focus_widget = &next_focus_widget;
343 g_source_remove (focus_notify_handler);
344 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
345 next_focus_widget = NULL;
346 focus_notify_handler = 0;
347 was_deselect = FALSE;
351 * If previously focused widget is not a GtkMenuItem or a GtkMenu,
352 * keep track of it so we can return to it after menubar is deactivated
355 !GTK_IS_MENU_ITEM (_focus_widget) &&
356 !GTK_IS_MENU (_focus_widget))
358 void *vp_focus_before_menu = &focus_before_menu;
359 focus_before_menu = _focus_widget;
360 g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
363 gail_focus_notify_when_idle (widget);
369 gail_map_cb (GtkWidget *widget)
371 gail_finish_select (widget);
375 gail_map_submenu_cb (GtkWidget *widget)
377 if (GTK_IS_MENU (widget))
379 GtkWidget *parent_menu_item;
381 parent_menu_item = gtk_menu_get_attach_widget (GTK_MENU (widget));
382 if (parent_menu_item)
383 gail_finish_select (parent_menu_item);
389 gail_deselect_watcher (GSignalInvocationHint *ihint,
390 guint n_param_values,
391 const GValue *param_values,
396 GtkWidget *menu_shell;
398 object = g_value_get_object (param_values + 0);
399 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
401 widget = GTK_WIDGET (object);
403 if (!GTK_IS_MENU_ITEM (widget))
406 if (subsequent_focus_widget == widget)
407 subsequent_focus_widget = NULL;
409 menu_shell = gtk_widget_get_parent (widget);
410 if (GTK_IS_MENU_SHELL (menu_shell))
412 GtkWidget *parent_menu_shell;
414 parent_menu_shell = gtk_menu_shell_get_parent_shell (GTK_MENU_SHELL (menu_shell));
415 if (parent_menu_shell)
417 GtkWidget *active_menu_item;
419 active_menu_item = gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (parent_menu_shell));
420 if (active_menu_item)
422 gail_focus_notify_when_idle (active_menu_item);
427 if (!GTK_IS_MENU_BAR (menu_shell))
429 gail_focus_notify_when_idle (menu_shell);
438 gail_switch_page_watcher (GSignalInvocationHint *ihint,
439 guint n_param_values,
440 const GValue *param_values,
446 object = g_value_get_object (param_values + 0);
447 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
449 widget = GTK_WIDGET (object);
451 if (!GTK_IS_NOTEBOOK (widget))
454 if (gtk_notebook_get_current_page (GTK_NOTEBOOK (widget)) == -1)
457 gail_focus_notify_when_idle (widget);
462 gail_focus_idle_handler (gpointer data)
464 focus_notify_handler = 0;
466 * The widget which was to receive focus may have been removed
468 if (!next_focus_widget)
470 if (next_focus_widget != data)
475 void *vp_next_focus_widget = &next_focus_widget;
476 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
477 next_focus_widget = NULL;
480 gail_focus_notify (data);
486 gail_focus_notify (GtkWidget *widget)
491 if (widget != _focus_widget)
495 void *vp_focus_widget = &_focus_widget;
496 g_object_remove_weak_pointer (G_OBJECT (_focus_widget), vp_focus_widget);
498 _focus_widget = widget;
501 void *vp_focus_widget = &_focus_widget;
502 g_object_add_weak_pointer (G_OBJECT (_focus_widget), vp_focus_widget);
504 * The UI may not have been updated yet; e.g. in gtkhtml2
505 * html_view_layout() is called in a idle handler
507 if (_focus_widget == focus_before_menu)
509 void *vp_focus_before_menu = &focus_before_menu;
510 g_object_remove_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
511 focus_before_menu = NULL;
514 gail_focus_notify_when_idle (_focus_widget);
519 atk_obj = gail_get_accessible_for_widget (_focus_widget, &transient);
523 * Do not report focus on redundant object
526 (atk_object_get_role(atk_obj) != ATK_ROLE_REDUNDANT_OBJECT))
527 atk_focus_tracker_notify (atk_obj);
528 if (atk_obj && transient)
529 g_object_unref (atk_obj);
530 if (subsequent_focus_widget)
532 GtkWidget *tmp_widget = subsequent_focus_widget;
533 subsequent_focus_widget = NULL;
534 gail_focus_notify_when_idle (tmp_widget);
540 gail_focus_notify_when_idle (GtkWidget *widget)
542 if (focus_notify_handler)
547 * Ignore focus request when menu item is going to be focused.
550 if (GTK_IS_MENU_ITEM (next_focus_widget) && !GTK_IS_MENU_ITEM (widget))
553 if (next_focus_widget)
555 if (GTK_IS_MENU_ITEM (next_focus_widget) && GTK_IS_MENU_ITEM (widget))
557 if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (next_focus_widget)) == gtk_widget_get_parent (widget))
559 if (subsequent_focus_widget)
560 g_assert_not_reached ();
561 subsequent_focus_widget = widget;
566 g_source_remove (focus_notify_handler);
567 if (next_focus_widget)
569 void *vp_next_focus_widget = &next_focus_widget;
570 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
571 next_focus_widget = NULL;
576 * Ignore if focus is being set to NULL and we are waiting to set focus
583 void *vp_next_focus_widget = &next_focus_widget;
584 next_focus_widget = widget;
585 g_object_add_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
590 * We are about to report focus as NULL so remove the weak pointer
591 * for the widget we were waiting to report focus on.
593 if (next_focus_widget)
595 void *vp_next_focus_widget = &next_focus_widget;
596 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
597 next_focus_widget = NULL;
601 focus_notify_handler = gdk_threads_add_idle (gail_focus_idle_handler, widget);
605 gail_deactivate_watcher (GSignalInvocationHint *ihint,
606 guint n_param_values,
607 const GValue *param_values,
613 GtkWidget *focus = NULL;
615 object = g_value_get_object (param_values + 0);
616 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
617 widget = GTK_WIDGET (object);
619 g_return_val_if_fail (GTK_IS_MENU_SHELL(widget), TRUE);
620 shell = GTK_MENU_SHELL(widget);
621 if (! gtk_menu_shell_get_parent_shell (shell))
622 focus = focus_before_menu;
625 * If we are waiting to report focus on a menubar or a menu item
626 * because of a previous deselect, cancel it.
629 focus_notify_handler &&
631 (GTK_IS_MENU_BAR (next_focus_widget) ||
632 GTK_IS_MENU_ITEM (next_focus_widget)))
634 void *vp_next_focus_widget = &next_focus_widget;
635 g_source_remove (focus_notify_handler);
636 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
637 next_focus_widget = NULL;
638 focus_notify_handler = 0;
639 was_deselect = FALSE;
641 gail_focus_notify_when_idle (focus);
647 gail_focus_tracker_init (void)
649 static gboolean emission_hooks_added = FALSE;
651 if (!emission_hooks_added)
654 * We cannot be sure that the classes exist so we make sure that they do.
656 g_type_class_ref (GTK_TYPE_WIDGET);
657 g_type_class_ref (GTK_TYPE_MENU_ITEM);
658 g_type_class_ref (GTK_TYPE_MENU_SHELL);
659 g_type_class_ref (GTK_TYPE_NOTEBOOK);
662 * We listen for event_after signal and then check that the
663 * event was a focus in event so we get called after the event.
665 g_signal_add_emission_hook (
666 g_signal_lookup ("event-after", GTK_TYPE_WIDGET), 0,
667 gail_focus_watcher, NULL, (GDestroyNotify) NULL);
669 * A "select" signal is emitted when arrow key is used to
670 * move to a list item in the popup window of a GtkCombo or
671 * a menu item in a menu.
673 g_signal_add_emission_hook (
674 g_signal_lookup ("select", GTK_TYPE_MENU_ITEM), 0,
675 gail_select_watcher, NULL, (GDestroyNotify) NULL);
678 * A "deselect" signal is emitted when arrow key is used to
679 * move from a menu item in a menu to the parent menu.
681 g_signal_add_emission_hook (
682 g_signal_lookup ("deselect", GTK_TYPE_MENU_ITEM), 0,
683 gail_deselect_watcher, NULL, (GDestroyNotify) NULL);
686 * We listen for deactivate signals on menushells to determine
687 * when the "focus" has left the menus.
689 g_signal_add_emission_hook (
690 g_signal_lookup ("deactivate", GTK_TYPE_MENU_SHELL), 0,
691 gail_deactivate_watcher, NULL, (GDestroyNotify) NULL);
694 * We listen for "switch-page" signal on a GtkNotebook to notify
695 * when page has changed because of clicking on a notebook tab.
697 g_signal_add_emission_hook (
698 g_signal_lookup ("switch-page", GTK_TYPE_NOTEBOOK), 0,
699 gail_switch_page_watcher, NULL, (GDestroyNotify) NULL);
700 emission_hooks_added = TRUE;
705 gail_focus_object_destroyed (gpointer data)
709 obj = G_OBJECT (data);
710 g_object_set_qdata (obj, quark_focus_object, NULL);
711 g_object_unref (obj);
715 gail_focus_tracker (AtkObject *focus_object)
718 * Do not report focus on redundant object
721 (atk_object_get_role(focus_object) != ATK_ROLE_REDUNDANT_OBJECT))
723 AtkObject *old_focus_object;
725 if (!GTK_IS_ACCESSIBLE (focus_object))
729 parent = focus_object;
732 parent = atk_object_get_parent (parent);
735 if (GTK_IS_ACCESSIBLE (parent))
741 gail_set_focus_object (focus_object, parent);
746 old_focus_object = g_object_get_qdata (G_OBJECT (focus_object), quark_focus_object);
747 if (old_focus_object)
749 g_object_weak_unref (G_OBJECT (old_focus_object),
750 (GWeakNotify) gail_focus_object_destroyed,
752 g_object_set_qdata (G_OBJECT (focus_object), quark_focus_object, NULL);
753 g_object_unref (G_OBJECT (focus_object));
760 gail_set_focus_widget (GtkWidget *focus_widget,
763 AtkObject *focus_obj;
766 focus_obj = gtk_widget_get_accessible (focus_widget);
767 obj = gtk_widget_get_accessible (widget);
768 gail_set_focus_object (focus_obj, obj);
772 gail_set_focus_object (AtkObject *focus_obj,
775 AtkObject *old_focus_obj;
777 old_focus_obj = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
778 if (old_focus_obj != obj)
781 g_object_weak_unref (G_OBJECT (old_focus_obj),
782 (GWeakNotify) gail_focus_object_destroyed,
786 * We call g_object_ref as if obj is destroyed
787 * while the weak reference exists then destroying the
788 * focus_obj would cause gail_focus_object_destroyed to be
789 * called when obj is not a valid GObject.
793 g_object_weak_ref (G_OBJECT (focus_obj),
794 (GWeakNotify) gail_focus_object_destroyed,
796 g_object_set_qdata (G_OBJECT (obj), quark_focus_object, focus_obj);
801 _gtk_accessibility_shutdown (void)
808 g_clear_object (&atk_misc_instance);
810 #ifdef GDK_WINDOWING_X11
811 atk_bridge_adaptor_cleanup ();
813 _gail_util_uninstall ();
817 _gtk_accessibility_init (void)
824 quark_focus_object = g_quark_from_static_string ("gail-focus-object");
826 atk_focus_tracker_init (gail_focus_tracker_init);
827 focus_tracker_id = atk_add_focus_tracker (gail_focus_tracker);
829 _gail_util_install ();
830 #ifdef GDK_WINDOWING_X11
831 atk_bridge_adaptor_init (NULL, NULL);
834 atk_misc_instance = g_object_new (GAIL_TYPE_MISC, NULL);