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.
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>
39 static gboolean gail_focus_watcher (GSignalInvocationHint *ihint,
41 const GValue *param_values,
43 static gboolean gail_select_watcher (GSignalInvocationHint *ihint,
45 const GValue *param_values,
47 static gboolean gail_deselect_watcher (GSignalInvocationHint *ihint,
49 const GValue *param_values,
51 static gboolean gail_switch_page_watcher(GSignalInvocationHint *ihint,
53 const GValue *param_values,
55 static void gail_finish_select (GtkWidget *widget);
56 static void gail_map_cb (GtkWidget *widget);
57 static void gail_map_submenu_cb (GtkWidget *widget);
58 static gint gail_focus_idle_handler (gpointer data);
59 static void gail_focus_notify (GtkWidget *widget);
60 static void gail_focus_notify_when_idle (GtkWidget *widget);
62 static void gail_focus_tracker_init (void);
63 static void gail_focus_object_destroyed (gpointer data);
64 static void gail_focus_tracker (AtkObject *object);
65 static void gail_set_focus_widget (GtkWidget *focus_widget,
67 static void gail_set_focus_object (AtkObject *focus_obj,
70 GtkWidget* _focus_widget = NULL;
71 static GtkWidget* next_focus_widget = NULL;
72 static gboolean was_deselect = FALSE;
73 static GtkWidget* subsequent_focus_widget = NULL;
74 static GtkWidget* focus_before_menu = NULL;
75 static guint focus_notify_handler = 0;
76 static guint focus_tracker_id = 0;
77 static GQuark quark_focus_object = 0;
80 gail_get_accessible_for_widget (GtkWidget *widget,
83 AtkObject *obj = NULL;
89 if (GTK_IS_ENTRY (widget))
91 else if (GTK_IS_NOTEBOOK (widget))
93 GtkNotebook *notebook;
96 notebook = GTK_NOTEBOOK (widget);
97 page_num = gtk_notebook_get_current_page (notebook);
100 obj = gtk_widget_get_accessible (widget);
101 obj = atk_object_ref_accessible_child (obj, page_num);
102 g_object_unref (obj);
105 else if (GTK_IS_TOGGLE_BUTTON (widget))
107 GtkWidget *other_widget = gtk_widget_get_parent (widget);
108 if (GTK_IS_COMBO_BOX (other_widget))
110 gail_set_focus_widget (other_widget, widget);
111 widget = other_widget;
116 AtkObject *focus_object;
118 obj = gtk_widget_get_accessible (widget);
119 focus_object = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
121 * We check whether the object for this focus_object has been deleted.
122 * This can happen when navigating to an empty directory in nautilus.
125 if (ATK_IS_GOBJECT_ACCESSIBLE (focus_object))
127 if (!atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (focus_object)))
138 gail_focus_watcher (GSignalInvocationHint *ihint,
139 guint n_param_values,
140 const GValue *param_values,
147 object = g_value_get_object (param_values + 0);
148 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
150 event = g_value_get_boxed (param_values + 1);
151 widget = GTK_WIDGET (object);
153 if (event->type == GDK_FOCUS_CHANGE)
155 if (event->focus_change.in)
157 if (GTK_IS_WINDOW (widget))
159 GtkWidget *focus_widget;
163 window = GTK_WINDOW (widget);
164 focus_widget = gtk_window_get_focus (window);
165 g_object_get (window, "type", &type, NULL);
170 * If we already have a potential focus widget set this
171 * windows's focus widget to focus_before_menu so that
172 * it will be reported when menu item is unset.
174 if (next_focus_widget)
176 if (GTK_IS_MENU_ITEM (next_focus_widget) &&
179 void *vp_focus_before_menu = &focus_before_menu;
180 focus_before_menu = focus_widget;
181 g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
186 widget = focus_widget;
188 else if (type == GTK_WINDOW_POPUP)
190 if (GTK_IS_BIN (widget))
192 GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
194 if (GTK_IS_WIDGET (child) && gtk_widget_has_grab (child))
196 if (GTK_IS_MENU_SHELL (child))
198 if (gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (child)))
201 * We have a menu which has a menu item selected
202 * so we do not report focus on the menu.
210 else /* popup window has no children; this edge case occurs in some custom code (OOo for instance) */
215 else /* Widget is a non-popup toplevel with no focus children;
216 don't emit for this case either, as it's useless */
224 if (next_focus_widget)
228 toplevel = gtk_widget_get_toplevel (next_focus_widget);
229 if (toplevel == widget)
230 next_focus_widget = NULL;
238 if (event->type == GDK_MOTION_NOTIFY && gtk_widget_has_focus (widget))
240 if (widget == _focus_widget)
251 #ifdef GDK_WINDOWING_X11
253 * If the focus widget is a GtkSocket without a plug
254 * then ignore the focus notification as the embedded
255 * plug will report a focus notification.
257 if (GTK_IS_SOCKET (widget) &&
258 gtk_socket_get_plug_window (GTK_SOCKET (widget)) != NULL)
263 * The widget may not yet be visible on the screen so we wait until it is.
265 gail_focus_notify_when_idle (widget);
270 gail_select_watcher (GSignalInvocationHint *ihint,
271 guint n_param_values,
272 const GValue *param_values,
278 object = g_value_get_object (param_values + 0);
279 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
281 widget = GTK_WIDGET (object);
283 if (!gtk_widget_get_mapped (widget))
285 g_signal_connect (widget, "map",
286 G_CALLBACK (gail_map_cb),
290 gail_finish_select (widget);
296 gail_finish_select (GtkWidget *widget)
298 if (GTK_IS_MENU_ITEM (widget))
300 GtkMenuItem* menu_item;
303 menu_item = GTK_MENU_ITEM (widget);
304 submenu = gtk_menu_item_get_submenu (menu_item);
306 !gtk_widget_get_mapped (submenu))
309 * If the submenu is not visble, wait until it is before
310 * reporting focus on the menu item.
314 handler_id = g_signal_handler_find (submenu,
316 g_signal_lookup ("map",
320 (gpointer) gail_map_submenu_cb,
323 g_signal_connect (submenu, "map",
324 G_CALLBACK (gail_map_submenu_cb),
329 * If we are waiting to report focus on a menubar or a menu item
330 * because of a previous deselect, cancel it.
333 focus_notify_handler &&
335 (GTK_IS_MENU_BAR (next_focus_widget) ||
336 GTK_IS_MENU_ITEM (next_focus_widget)))
338 void *vp_next_focus_widget = &next_focus_widget;
339 g_source_remove (focus_notify_handler);
340 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
341 next_focus_widget = NULL;
342 focus_notify_handler = 0;
343 was_deselect = FALSE;
347 * If previously focused widget is not a GtkMenuItem or a GtkMenu,
348 * keep track of it so we can return to it after menubar is deactivated
351 !GTK_IS_MENU_ITEM (_focus_widget) &&
352 !GTK_IS_MENU (_focus_widget))
354 void *vp_focus_before_menu = &focus_before_menu;
355 focus_before_menu = _focus_widget;
356 g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
359 gail_focus_notify_when_idle (widget);
365 gail_map_cb (GtkWidget *widget)
367 gail_finish_select (widget);
371 gail_map_submenu_cb (GtkWidget *widget)
373 if (GTK_IS_MENU (widget))
375 GtkWidget *parent_menu_item;
377 parent_menu_item = gtk_menu_get_attach_widget (GTK_MENU (widget));
378 if (parent_menu_item)
379 gail_finish_select (parent_menu_item);
385 gail_deselect_watcher (GSignalInvocationHint *ihint,
386 guint n_param_values,
387 const GValue *param_values,
392 GtkWidget *menu_shell;
394 object = g_value_get_object (param_values + 0);
395 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
397 widget = GTK_WIDGET (object);
399 if (!GTK_IS_MENU_ITEM (widget))
402 if (subsequent_focus_widget == widget)
403 subsequent_focus_widget = NULL;
405 menu_shell = gtk_widget_get_parent (widget);
406 if (GTK_IS_MENU_SHELL (menu_shell))
408 GtkWidget *parent_menu_shell;
410 parent_menu_shell = gtk_menu_shell_get_parent_shell (GTK_MENU_SHELL (menu_shell));
411 if (parent_menu_shell)
413 GtkWidget *active_menu_item;
415 active_menu_item = gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (parent_menu_shell));
416 if (active_menu_item)
418 gail_focus_notify_when_idle (active_menu_item);
423 if (!GTK_IS_MENU_BAR (menu_shell))
425 gail_focus_notify_when_idle (menu_shell);
434 gail_switch_page_watcher (GSignalInvocationHint *ihint,
435 guint n_param_values,
436 const GValue *param_values,
442 object = g_value_get_object (param_values + 0);
443 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
445 widget = GTK_WIDGET (object);
447 if (!GTK_IS_NOTEBOOK (widget))
450 if (gtk_notebook_get_current_page (GTK_NOTEBOOK (widget)) == -1)
453 gail_focus_notify_when_idle (widget);
458 gail_focus_idle_handler (gpointer data)
460 focus_notify_handler = 0;
462 * The widget which was to receive focus may have been removed
464 if (!next_focus_widget)
466 if (next_focus_widget != data)
471 void *vp_next_focus_widget = &next_focus_widget;
472 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
473 next_focus_widget = NULL;
476 gail_focus_notify (data);
482 gail_focus_notify (GtkWidget *widget)
487 if (widget != _focus_widget)
491 void *vp_focus_widget = &_focus_widget;
492 g_object_remove_weak_pointer (G_OBJECT (_focus_widget), vp_focus_widget);
494 _focus_widget = widget;
497 void *vp_focus_widget = &_focus_widget;
498 g_object_add_weak_pointer (G_OBJECT (_focus_widget), vp_focus_widget);
500 * The UI may not have been updated yet; e.g. in gtkhtml2
501 * html_view_layout() is called in a idle handler
503 if (_focus_widget == focus_before_menu)
505 void *vp_focus_before_menu = &focus_before_menu;
506 g_object_remove_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
507 focus_before_menu = NULL;
510 gail_focus_notify_when_idle (_focus_widget);
515 atk_obj = gail_get_accessible_for_widget (_focus_widget, &transient);
519 * Do not report focus on redundant object
522 (atk_object_get_role(atk_obj) != ATK_ROLE_REDUNDANT_OBJECT))
523 atk_focus_tracker_notify (atk_obj);
524 if (atk_obj && transient)
525 g_object_unref (atk_obj);
526 if (subsequent_focus_widget)
528 GtkWidget *tmp_widget = subsequent_focus_widget;
529 subsequent_focus_widget = NULL;
530 gail_focus_notify_when_idle (tmp_widget);
536 gail_focus_notify_when_idle (GtkWidget *widget)
538 if (focus_notify_handler)
543 * Ignore focus request when menu item is going to be focused.
546 if (GTK_IS_MENU_ITEM (next_focus_widget) && !GTK_IS_MENU_ITEM (widget))
549 if (next_focus_widget)
551 if (GTK_IS_MENU_ITEM (next_focus_widget) && GTK_IS_MENU_ITEM (widget))
553 if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (next_focus_widget)) == gtk_widget_get_parent (widget))
555 if (subsequent_focus_widget)
556 g_assert_not_reached ();
557 subsequent_focus_widget = widget;
562 g_source_remove (focus_notify_handler);
563 if (next_focus_widget)
565 void *vp_next_focus_widget = &next_focus_widget;
566 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
567 next_focus_widget = NULL;
572 * Ignore if focus is being set to NULL and we are waiting to set focus
579 void *vp_next_focus_widget = &next_focus_widget;
580 next_focus_widget = widget;
581 g_object_add_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
586 * We are about to report focus as NULL so remove the weak pointer
587 * for the widget we were waiting to report focus on.
589 if (next_focus_widget)
591 void *vp_next_focus_widget = &next_focus_widget;
592 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
593 next_focus_widget = NULL;
597 focus_notify_handler = gdk_threads_add_idle (gail_focus_idle_handler, widget);
601 gail_deactivate_watcher (GSignalInvocationHint *ihint,
602 guint n_param_values,
603 const GValue *param_values,
609 GtkWidget *focus = NULL;
611 object = g_value_get_object (param_values + 0);
612 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
613 widget = GTK_WIDGET (object);
615 g_return_val_if_fail (GTK_IS_MENU_SHELL(widget), TRUE);
616 shell = GTK_MENU_SHELL(widget);
617 if (! gtk_menu_shell_get_parent_shell (shell))
618 focus = focus_before_menu;
621 * If we are waiting to report focus on a menubar or a menu item
622 * because of a previous deselect, cancel it.
625 focus_notify_handler &&
627 (GTK_IS_MENU_BAR (next_focus_widget) ||
628 GTK_IS_MENU_ITEM (next_focus_widget)))
630 void *vp_next_focus_widget = &next_focus_widget;
631 g_source_remove (focus_notify_handler);
632 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
633 next_focus_widget = NULL;
634 focus_notify_handler = 0;
635 was_deselect = FALSE;
637 gail_focus_notify_when_idle (focus);
643 gail_focus_tracker_init (void)
645 static gboolean emission_hooks_added = FALSE;
647 if (!emission_hooks_added)
650 * We cannot be sure that the classes exist so we make sure that they do.
652 g_type_class_ref (GTK_TYPE_WIDGET);
653 g_type_class_ref (GTK_TYPE_MENU_ITEM);
654 g_type_class_ref (GTK_TYPE_MENU_SHELL);
655 g_type_class_ref (GTK_TYPE_NOTEBOOK);
658 * We listen for event_after signal and then check that the
659 * event was a focus in event so we get called after the event.
661 g_signal_add_emission_hook (
662 g_signal_lookup ("event-after", GTK_TYPE_WIDGET), 0,
663 gail_focus_watcher, NULL, (GDestroyNotify) NULL);
665 * A "select" signal is emitted when arrow key is used to
666 * move to a list item in the popup window of a GtkCombo or
667 * a menu item in a menu.
669 g_signal_add_emission_hook (
670 g_signal_lookup ("select", GTK_TYPE_MENU_ITEM), 0,
671 gail_select_watcher, NULL, (GDestroyNotify) NULL);
674 * A "deselect" signal is emitted when arrow key is used to
675 * move from a menu item in a menu to the parent menu.
677 g_signal_add_emission_hook (
678 g_signal_lookup ("deselect", GTK_TYPE_MENU_ITEM), 0,
679 gail_deselect_watcher, NULL, (GDestroyNotify) NULL);
682 * We listen for deactivate signals on menushells to determine
683 * when the "focus" has left the menus.
685 g_signal_add_emission_hook (
686 g_signal_lookup ("deactivate", GTK_TYPE_MENU_SHELL), 0,
687 gail_deactivate_watcher, NULL, (GDestroyNotify) NULL);
690 * We listen for "switch-page" signal on a GtkNotebook to notify
691 * when page has changed because of clicking on a notebook tab.
693 g_signal_add_emission_hook (
694 g_signal_lookup ("switch-page", GTK_TYPE_NOTEBOOK), 0,
695 gail_switch_page_watcher, NULL, (GDestroyNotify) NULL);
696 emission_hooks_added = TRUE;
701 gail_focus_object_destroyed (gpointer data)
705 obj = G_OBJECT (data);
706 g_object_set_qdata (obj, quark_focus_object, NULL);
707 g_object_unref (obj);
711 gail_focus_tracker (AtkObject *focus_object)
714 * Do not report focus on redundant object
717 (atk_object_get_role(focus_object) != ATK_ROLE_REDUNDANT_OBJECT))
719 AtkObject *old_focus_object;
721 if (!GTK_IS_ACCESSIBLE (focus_object))
725 parent = focus_object;
728 parent = atk_object_get_parent (parent);
731 if (GTK_IS_ACCESSIBLE (parent))
737 gail_set_focus_object (focus_object, parent);
742 old_focus_object = g_object_get_qdata (G_OBJECT (focus_object), quark_focus_object);
743 if (old_focus_object)
745 g_object_weak_unref (G_OBJECT (old_focus_object),
746 (GWeakNotify) gail_focus_object_destroyed,
748 g_object_set_qdata (G_OBJECT (focus_object), quark_focus_object, NULL);
749 g_object_unref (G_OBJECT (focus_object));
756 gail_set_focus_widget (GtkWidget *focus_widget,
759 AtkObject *focus_obj;
762 focus_obj = gtk_widget_get_accessible (focus_widget);
763 obj = gtk_widget_get_accessible (widget);
764 gail_set_focus_object (focus_obj, obj);
768 gail_set_focus_object (AtkObject *focus_obj,
771 AtkObject *old_focus_obj;
773 old_focus_obj = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
774 if (old_focus_obj != obj)
777 g_object_weak_unref (G_OBJECT (old_focus_obj),
778 (GWeakNotify) gail_focus_object_destroyed,
782 * We call g_object_ref as if obj is destroyed
783 * while the weak reference exists then destroying the
784 * focus_obj would cause gail_focus_object_destroyed to be
785 * called when obj is not a valid GObject.
789 g_object_weak_ref (G_OBJECT (focus_obj),
790 (GWeakNotify) gail_focus_object_destroyed,
792 g_object_set_qdata (G_OBJECT (obj), quark_focus_object, focus_obj);
797 _gtk_accessibility_init (void)
799 static int initialized = FALSE;
805 quark_focus_object = g_quark_from_static_string ("gail-focus-object");
807 atk_focus_tracker_init (gail_focus_tracker_init);
808 focus_tracker_id = atk_add_focus_tracker (gail_focus_tracker);
810 _gail_util_install ();
812 atk_misc_instance = g_object_new (GAIL_TYPE_MISC, NULL);