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, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
26 #include "gailtoplevel.h"
29 #define GNOME_ACCESSIBILITY_ENV "GNOME_ACCESSIBILITY"
31 static gboolean gail_focus_watcher (GSignalInvocationHint *ihint,
33 const GValue *param_values,
35 static gboolean gail_select_watcher (GSignalInvocationHint *ihint,
37 const GValue *param_values,
39 static gboolean gail_deselect_watcher (GSignalInvocationHint *ihint,
41 const GValue *param_values,
43 static gboolean gail_switch_page_watcher(GSignalInvocationHint *ihint,
45 const GValue *param_values,
47 static void gail_finish_select (GtkWidget *widget);
48 static void gail_map_cb (GtkWidget *widget);
49 static void gail_map_submenu_cb (GtkWidget *widget);
50 static gint gail_focus_idle_handler (gpointer data);
51 static void gail_focus_notify (GtkWidget *widget);
52 static void gail_focus_notify_when_idle (GtkWidget *widget);
54 static void gail_focus_tracker_init (void);
55 static void gail_focus_object_destroyed (gpointer data);
56 static void gail_focus_tracker (AtkObject *object);
57 static void gail_set_focus_widget (GtkWidget *focus_widget,
59 static void gail_set_focus_object (AtkObject *focus_obj,
62 GtkWidget* focus_widget = NULL;
63 static GtkWidget* next_focus_widget = NULL;
64 static gboolean was_deselect = FALSE;
65 static GtkWidget* subsequent_focus_widget = NULL;
66 static GtkWidget* focus_before_menu = NULL;
67 static guint focus_notify_handler = 0;
68 static guint focus_tracker_id = 0;
69 static GQuark quark_focus_object = 0;
72 gail_get_accessible_for_widget (GtkWidget *widget,
75 AtkObject *obj = NULL;
81 if (GTK_IS_ENTRY (widget))
83 else if (GTK_IS_NOTEBOOK (widget))
85 GtkNotebook *notebook;
88 notebook = GTK_NOTEBOOK (widget);
89 page_num = gtk_notebook_get_current_page (notebook);
92 obj = gtk_widget_get_accessible (widget);
93 obj = atk_object_ref_accessible_child (obj, page_num);
97 else if (GTK_IS_TOGGLE_BUTTON (widget))
99 GtkWidget *other_widget = gtk_widget_get_parent (widget);
100 if (GTK_IS_COMBO_BOX (other_widget))
102 gail_set_focus_widget (other_widget, widget);
103 widget = other_widget;
108 AtkObject *focus_object;
110 obj = gtk_widget_get_accessible (widget);
111 focus_object = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
113 * We check whether the object for this focus_object has been deleted.
114 * This can happen when navigating to an empty directory in nautilus.
117 if (ATK_IS_GOBJECT_ACCESSIBLE (focus_object))
119 if (!atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (focus_object)))
130 gail_focus_watcher (GSignalInvocationHint *ihint,
131 guint n_param_values,
132 const GValue *param_values,
139 object = g_value_get_object (param_values + 0);
140 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
142 event = g_value_get_boxed (param_values + 1);
143 widget = GTK_WIDGET (object);
145 if (event->type == GDK_FOCUS_CHANGE)
147 if (event->focus_change.in)
149 if (GTK_IS_WINDOW (widget))
151 GtkWidget *focus_widget;
155 window = GTK_WINDOW (widget);
156 focus_widget = gtk_window_get_focus (window);
157 g_object_get (window, "type", &type, NULL);
162 * If we already have a potential focus widget set this
163 * windows's focus widget to focus_before_menu so that
164 * it will be reported when menu item is unset.
166 if (next_focus_widget)
168 if (GTK_IS_MENU_ITEM (next_focus_widget) &&
171 void *vp_focus_before_menu = &focus_before_menu;
172 focus_before_menu = focus_widget;
173 g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
178 widget = focus_widget;
180 else if (type == GTK_WINDOW_POPUP)
182 if (GTK_IS_BIN (widget))
184 GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
186 if (GTK_IS_WIDGET (child) && gtk_widget_has_grab (child))
188 if (GTK_IS_MENU_SHELL (child))
190 if (gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (child)))
193 * We have a menu which has a menu item selected
194 * so we do not report focus on the menu.
202 else /* popup window has no children; this edge case occurs in some custom code (OOo for instance) */
207 else /* Widget is a non-popup toplevel with no focus children;
208 don't emit for this case either, as it's useless */
216 if (next_focus_widget)
220 toplevel = gtk_widget_get_toplevel (next_focus_widget);
221 if (toplevel == widget)
222 next_focus_widget = NULL;
230 if (event->type == GDK_MOTION_NOTIFY && gtk_widget_has_focus (widget))
232 if (widget == focus_widget)
243 #ifdef GDK_WINDOWING_X11
245 * If the focus widget is a GtkSocket without a plug
246 * then ignore the focus notification as the embedded
247 * plug will report a focus notification.
249 if (GTK_IS_SOCKET (widget) &&
250 gtk_socket_get_plug_window (GTK_SOCKET (widget)) != NULL)
255 * The widget may not yet be visible on the screen so we wait until it is.
257 gail_focus_notify_when_idle (widget);
262 gail_select_watcher (GSignalInvocationHint *ihint,
263 guint n_param_values,
264 const GValue *param_values,
270 object = g_value_get_object (param_values + 0);
271 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
273 widget = GTK_WIDGET (object);
275 if (!gtk_widget_get_mapped (widget))
277 g_signal_connect (widget, "map",
278 G_CALLBACK (gail_map_cb),
282 gail_finish_select (widget);
288 gail_finish_select (GtkWidget *widget)
290 if (GTK_IS_MENU_ITEM (widget))
292 GtkMenuItem* menu_item;
295 menu_item = GTK_MENU_ITEM (widget);
296 submenu = gtk_menu_item_get_submenu (menu_item);
298 !gtk_widget_get_mapped (submenu))
301 * If the submenu is not visble, wait until it is before
302 * reporting focus on the menu item.
306 handler_id = g_signal_handler_find (submenu,
308 g_signal_lookup ("map",
312 (gpointer) gail_map_submenu_cb,
315 g_signal_connect (submenu, "map",
316 G_CALLBACK (gail_map_submenu_cb),
321 * If we are waiting to report focus on a menubar or a menu item
322 * because of a previous deselect, cancel it.
325 focus_notify_handler &&
327 (GTK_IS_MENU_BAR (next_focus_widget) ||
328 GTK_IS_MENU_ITEM (next_focus_widget)))
330 void *vp_next_focus_widget = &next_focus_widget;
331 g_source_remove (focus_notify_handler);
332 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
333 next_focus_widget = NULL;
334 focus_notify_handler = 0;
335 was_deselect = FALSE;
339 * If previously focused widget is not a GtkMenuItem or a GtkMenu,
340 * keep track of it so we can return to it after menubar is deactivated
343 !GTK_IS_MENU_ITEM (focus_widget) &&
344 !GTK_IS_MENU (focus_widget))
346 void *vp_focus_before_menu = &focus_before_menu;
347 focus_before_menu = focus_widget;
348 g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
351 gail_focus_notify_when_idle (widget);
357 gail_map_cb (GtkWidget *widget)
359 gail_finish_select (widget);
363 gail_map_submenu_cb (GtkWidget *widget)
365 if (GTK_IS_MENU (widget))
367 GtkWidget *parent_menu_item;
369 parent_menu_item = gtk_menu_get_attach_widget (GTK_MENU (widget));
370 if (parent_menu_item)
371 gail_finish_select (parent_menu_item);
377 gail_deselect_watcher (GSignalInvocationHint *ihint,
378 guint n_param_values,
379 const GValue *param_values,
384 GtkWidget *menu_shell;
386 object = g_value_get_object (param_values + 0);
387 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
389 widget = GTK_WIDGET (object);
391 if (!GTK_IS_MENU_ITEM (widget))
394 if (subsequent_focus_widget == widget)
395 subsequent_focus_widget = NULL;
397 menu_shell = gtk_widget_get_parent (widget);
398 if (GTK_IS_MENU_SHELL (menu_shell))
400 GtkWidget *parent_menu_shell;
402 parent_menu_shell = gtk_menu_shell_get_parent_shell (GTK_MENU_SHELL (menu_shell));
403 if (parent_menu_shell)
405 GtkWidget *active_menu_item;
407 active_menu_item = gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (parent_menu_shell));
408 if (active_menu_item)
410 gail_focus_notify_when_idle (active_menu_item);
415 if (!GTK_IS_MENU_BAR (menu_shell))
417 gail_focus_notify_when_idle (menu_shell);
426 gail_switch_page_watcher (GSignalInvocationHint *ihint,
427 guint n_param_values,
428 const GValue *param_values,
434 object = g_value_get_object (param_values + 0);
435 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
437 widget = GTK_WIDGET (object);
439 if (!GTK_IS_NOTEBOOK (widget))
442 if (gtk_notebook_get_current_page (GTK_NOTEBOOK (widget)) == -1)
445 gail_focus_notify_when_idle (widget);
450 gail_focus_idle_handler (gpointer data)
452 focus_notify_handler = 0;
454 * The widget which was to receive focus may have been removed
456 if (!next_focus_widget)
458 if (next_focus_widget != data)
463 void *vp_next_focus_widget = &next_focus_widget;
464 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
465 next_focus_widget = NULL;
468 gail_focus_notify (data);
474 gail_focus_notify (GtkWidget *widget)
479 if (widget != focus_widget)
483 void *vp_focus_widget = &focus_widget;
484 g_object_remove_weak_pointer (G_OBJECT (focus_widget), vp_focus_widget);
486 focus_widget = widget;
489 void *vp_focus_widget = &focus_widget;
490 g_object_add_weak_pointer (G_OBJECT (focus_widget), vp_focus_widget);
492 * The UI may not have been updated yet; e.g. in gtkhtml2
493 * html_view_layout() is called in a idle handler
495 if (focus_widget == focus_before_menu)
497 void *vp_focus_before_menu = &focus_before_menu;
498 g_object_remove_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
499 focus_before_menu = NULL;
502 gail_focus_notify_when_idle (focus_widget);
507 atk_obj = gail_get_accessible_for_widget (focus_widget, &transient);
511 * Do not report focus on redundant object
514 (atk_object_get_role(atk_obj) != ATK_ROLE_REDUNDANT_OBJECT))
515 atk_focus_tracker_notify (atk_obj);
516 if (atk_obj && transient)
517 g_object_unref (atk_obj);
518 if (subsequent_focus_widget)
520 GtkWidget *tmp_widget = subsequent_focus_widget;
521 subsequent_focus_widget = NULL;
522 gail_focus_notify_when_idle (tmp_widget);
528 gail_focus_notify_when_idle (GtkWidget *widget)
530 if (focus_notify_handler)
535 * Ignore focus request when menu item is going to be focused.
538 if (GTK_IS_MENU_ITEM (next_focus_widget) && !GTK_IS_MENU_ITEM (widget))
541 if (next_focus_widget)
543 if (GTK_IS_MENU_ITEM (next_focus_widget) && GTK_IS_MENU_ITEM (widget))
545 if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (next_focus_widget)) == gtk_widget_get_parent (widget))
547 if (subsequent_focus_widget)
548 g_assert_not_reached ();
549 subsequent_focus_widget = widget;
554 g_source_remove (focus_notify_handler);
555 if (next_focus_widget)
557 void *vp_next_focus_widget = &next_focus_widget;
558 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
559 next_focus_widget = NULL;
564 * Ignore if focus is being set to NULL and we are waiting to set focus
571 void *vp_next_focus_widget = &next_focus_widget;
572 next_focus_widget = widget;
573 g_object_add_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
578 * We are about to report focus as NULL so remove the weak pointer
579 * for the widget we were waiting to report focus on.
581 if (next_focus_widget)
583 void *vp_next_focus_widget = &next_focus_widget;
584 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
585 next_focus_widget = NULL;
589 focus_notify_handler = gdk_threads_add_idle (gail_focus_idle_handler, widget);
593 gail_deactivate_watcher (GSignalInvocationHint *ihint,
594 guint n_param_values,
595 const GValue *param_values,
601 GtkWidget *focus = NULL;
603 object = g_value_get_object (param_values + 0);
604 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
605 widget = GTK_WIDGET (object);
607 g_return_val_if_fail (GTK_IS_MENU_SHELL(widget), TRUE);
608 shell = GTK_MENU_SHELL(widget);
609 if (! gtk_menu_shell_get_parent_shell (shell))
610 focus = focus_before_menu;
613 * If we are waiting to report focus on a menubar or a menu item
614 * because of a previous deselect, cancel it.
617 focus_notify_handler &&
619 (GTK_IS_MENU_BAR (next_focus_widget) ||
620 GTK_IS_MENU_ITEM (next_focus_widget)))
622 void *vp_next_focus_widget = &next_focus_widget;
623 g_source_remove (focus_notify_handler);
624 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
625 next_focus_widget = NULL;
626 focus_notify_handler = 0;
627 was_deselect = FALSE;
629 gail_focus_notify_when_idle (focus);
635 gail_focus_tracker_init (void)
637 static gboolean emission_hooks_added = FALSE;
639 if (!emission_hooks_added)
642 * We cannot be sure that the classes exist so we make sure that they do.
644 g_type_class_ref (GTK_TYPE_WIDGET);
645 g_type_class_ref (GTK_TYPE_MENU_ITEM);
646 g_type_class_ref (GTK_TYPE_MENU_SHELL);
647 g_type_class_ref (GTK_TYPE_NOTEBOOK);
650 * We listen for event_after signal and then check that the
651 * event was a focus in event so we get called after the event.
653 g_signal_add_emission_hook (
654 g_signal_lookup ("event-after", GTK_TYPE_WIDGET), 0,
655 gail_focus_watcher, NULL, (GDestroyNotify) NULL);
657 * A "select" signal is emitted when arrow key is used to
658 * move to a list item in the popup window of a GtkCombo or
659 * a menu item in a menu.
661 g_signal_add_emission_hook (
662 g_signal_lookup ("select", GTK_TYPE_MENU_ITEM), 0,
663 gail_select_watcher, NULL, (GDestroyNotify) NULL);
666 * A "deselect" signal is emitted when arrow key is used to
667 * move from a menu item in a menu to the parent menu.
669 g_signal_add_emission_hook (
670 g_signal_lookup ("deselect", GTK_TYPE_MENU_ITEM), 0,
671 gail_deselect_watcher, NULL, (GDestroyNotify) NULL);
674 * We listen for deactivate signals on menushells to determine
675 * when the "focus" has left the menus.
677 g_signal_add_emission_hook (
678 g_signal_lookup ("deactivate", GTK_TYPE_MENU_SHELL), 0,
679 gail_deactivate_watcher, NULL, (GDestroyNotify) NULL);
682 * We listen for "switch-page" signal on a GtkNotebook to notify
683 * when page has changed because of clicking on a notebook tab.
685 g_signal_add_emission_hook (
686 g_signal_lookup ("switch-page", GTK_TYPE_NOTEBOOK), 0,
687 gail_switch_page_watcher, NULL, (GDestroyNotify) NULL);
688 emission_hooks_added = TRUE;
693 gail_focus_object_destroyed (gpointer data)
697 obj = G_OBJECT (data);
698 g_object_set_qdata (obj, quark_focus_object, NULL);
699 g_object_unref (obj);
703 gail_focus_tracker (AtkObject *focus_object)
706 * Do not report focus on redundant object
709 (atk_object_get_role(focus_object) != ATK_ROLE_REDUNDANT_OBJECT))
711 AtkObject *old_focus_object;
713 if (!GTK_IS_ACCESSIBLE (focus_object))
717 parent = focus_object;
720 parent = atk_object_get_parent (parent);
723 if (GTK_IS_ACCESSIBLE (parent))
729 gail_set_focus_object (focus_object, parent);
734 old_focus_object = g_object_get_qdata (G_OBJECT (focus_object), quark_focus_object);
735 if (old_focus_object)
737 g_object_weak_unref (G_OBJECT (old_focus_object),
738 (GWeakNotify) gail_focus_object_destroyed,
740 g_object_set_qdata (G_OBJECT (focus_object), quark_focus_object, NULL);
741 g_object_unref (G_OBJECT (focus_object));
748 gail_set_focus_widget (GtkWidget *focus_widget,
751 AtkObject *focus_obj;
754 focus_obj = gtk_widget_get_accessible (focus_widget);
755 obj = gtk_widget_get_accessible (widget);
756 gail_set_focus_object (focus_obj, obj);
760 gail_set_focus_object (AtkObject *focus_obj,
763 AtkObject *old_focus_obj;
765 old_focus_obj = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
766 if (old_focus_obj != obj)
769 g_object_weak_unref (G_OBJECT (old_focus_obj),
770 (GWeakNotify) gail_focus_object_destroyed,
774 * We call g_object_ref as if obj is destroyed
775 * while the weak reference exists then destroying the
776 * focus_obj would cause gail_focus_object_destroyed to be
777 * called when obj is not a valid GObject.
781 g_object_weak_ref (G_OBJECT (focus_obj),
782 (GWeakNotify) gail_focus_object_destroyed,
784 g_object_set_qdata (G_OBJECT (obj), quark_focus_object, focus_obj);
788 static int gail_initialized = FALSE;
791 gail_accessibility_module_init (void)
793 const char *env_a_t_support;
794 gboolean a_t_support = FALSE;
796 if (gail_initialized)
800 gail_initialized = TRUE;
801 quark_focus_object = g_quark_from_static_string ("gail-focus-object");
803 env_a_t_support = g_getenv (GNOME_ACCESSIBILITY_ENV);
806 a_t_support = atoi (env_a_t_support);
808 fprintf (stderr, "GTK Accessibility Module initialized\n");
810 atk_focus_tracker_init (gail_focus_tracker_init);
811 focus_tracker_id = atk_add_focus_tracker (gail_focus_tracker);
813 /* Initialize the GailUtility class */
814 g_type_class_unref (g_type_class_ref (GAIL_TYPE_UTIL));
815 g_type_class_unref (g_type_class_ref (GAIL_TYPE_MISC));