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.
30 static gboolean gail_focus_watcher (GSignalInvocationHint *ihint,
32 const GValue *param_values,
34 static gboolean gail_select_watcher (GSignalInvocationHint *ihint,
36 const GValue *param_values,
38 static gboolean gail_deselect_watcher (GSignalInvocationHint *ihint,
40 const GValue *param_values,
42 static gboolean gail_switch_page_watcher(GSignalInvocationHint *ihint,
44 const GValue *param_values,
46 static void gail_finish_select (GtkWidget *widget);
47 static void gail_map_cb (GtkWidget *widget);
48 static void gail_map_submenu_cb (GtkWidget *widget);
49 static gint gail_focus_idle_handler (gpointer data);
50 static void gail_focus_notify (GtkWidget *widget);
51 static void gail_focus_notify_when_idle (GtkWidget *widget);
53 static void gail_focus_tracker_init (void);
54 static void gail_focus_object_destroyed (gpointer data);
55 static void gail_focus_tracker (AtkObject *object);
56 static void gail_set_focus_widget (GtkWidget *focus_widget,
58 static void gail_set_focus_object (AtkObject *focus_obj,
61 GtkWidget* _focus_widget = NULL;
62 static GtkWidget* next_focus_widget = NULL;
63 static gboolean was_deselect = FALSE;
64 static GtkWidget* subsequent_focus_widget = NULL;
65 static GtkWidget* focus_before_menu = NULL;
66 static guint focus_notify_handler = 0;
67 static guint focus_tracker_id = 0;
68 static GQuark quark_focus_object = 0;
71 gail_get_accessible_for_widget (GtkWidget *widget,
74 AtkObject *obj = NULL;
80 if (GTK_IS_ENTRY (widget))
82 else if (GTK_IS_NOTEBOOK (widget))
84 GtkNotebook *notebook;
87 notebook = GTK_NOTEBOOK (widget);
88 page_num = gtk_notebook_get_current_page (notebook);
91 obj = gtk_widget_get_accessible (widget);
92 obj = atk_object_ref_accessible_child (obj, page_num);
96 else if (GTK_IS_TOGGLE_BUTTON (widget))
98 GtkWidget *other_widget = gtk_widget_get_parent (widget);
99 if (GTK_IS_COMBO_BOX (other_widget))
101 gail_set_focus_widget (other_widget, widget);
102 widget = other_widget;
107 AtkObject *focus_object;
109 obj = gtk_widget_get_accessible (widget);
110 focus_object = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
112 * We check whether the object for this focus_object has been deleted.
113 * This can happen when navigating to an empty directory in nautilus.
116 if (ATK_IS_GOBJECT_ACCESSIBLE (focus_object))
118 if (!atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (focus_object)))
129 gail_focus_watcher (GSignalInvocationHint *ihint,
130 guint n_param_values,
131 const GValue *param_values,
138 object = g_value_get_object (param_values + 0);
139 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
141 event = g_value_get_boxed (param_values + 1);
142 widget = GTK_WIDGET (object);
144 if (event->type == GDK_FOCUS_CHANGE)
146 if (event->focus_change.in)
148 if (GTK_IS_WINDOW (widget))
150 GtkWidget *focus_widget;
154 window = GTK_WINDOW (widget);
155 focus_widget = gtk_window_get_focus (window);
156 g_object_get (window, "type", &type, NULL);
161 * If we already have a potential focus widget set this
162 * windows's focus widget to focus_before_menu so that
163 * it will be reported when menu item is unset.
165 if (next_focus_widget)
167 if (GTK_IS_MENU_ITEM (next_focus_widget) &&
170 void *vp_focus_before_menu = &focus_before_menu;
171 focus_before_menu = focus_widget;
172 g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
177 widget = focus_widget;
179 else if (type == GTK_WINDOW_POPUP)
181 if (GTK_IS_BIN (widget))
183 GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
185 if (GTK_IS_WIDGET (child) && gtk_widget_has_grab (child))
187 if (GTK_IS_MENU_SHELL (child))
189 if (gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (child)))
192 * We have a menu which has a menu item selected
193 * so we do not report focus on the menu.
201 else /* popup window has no children; this edge case occurs in some custom code (OOo for instance) */
206 else /* Widget is a non-popup toplevel with no focus children;
207 don't emit for this case either, as it's useless */
215 if (next_focus_widget)
219 toplevel = gtk_widget_get_toplevel (next_focus_widget);
220 if (toplevel == widget)
221 next_focus_widget = NULL;
229 if (event->type == GDK_MOTION_NOTIFY && gtk_widget_has_focus (widget))
231 if (widget == _focus_widget)
242 #ifdef GDK_WINDOWING_X11
244 * If the focus widget is a GtkSocket without a plug
245 * then ignore the focus notification as the embedded
246 * plug will report a focus notification.
248 if (GTK_IS_SOCKET (widget) &&
249 gtk_socket_get_plug_window (GTK_SOCKET (widget)) != NULL)
254 * The widget may not yet be visible on the screen so we wait until it is.
256 gail_focus_notify_when_idle (widget);
261 gail_select_watcher (GSignalInvocationHint *ihint,
262 guint n_param_values,
263 const GValue *param_values,
269 object = g_value_get_object (param_values + 0);
270 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
272 widget = GTK_WIDGET (object);
274 if (!gtk_widget_get_mapped (widget))
276 g_signal_connect (widget, "map",
277 G_CALLBACK (gail_map_cb),
281 gail_finish_select (widget);
287 gail_finish_select (GtkWidget *widget)
289 if (GTK_IS_MENU_ITEM (widget))
291 GtkMenuItem* menu_item;
294 menu_item = GTK_MENU_ITEM (widget);
295 submenu = gtk_menu_item_get_submenu (menu_item);
297 !gtk_widget_get_mapped (submenu))
300 * If the submenu is not visble, wait until it is before
301 * reporting focus on the menu item.
305 handler_id = g_signal_handler_find (submenu,
307 g_signal_lookup ("map",
311 (gpointer) gail_map_submenu_cb,
314 g_signal_connect (submenu, "map",
315 G_CALLBACK (gail_map_submenu_cb),
320 * If we are waiting to report focus on a menubar or a menu item
321 * because of a previous deselect, cancel it.
324 focus_notify_handler &&
326 (GTK_IS_MENU_BAR (next_focus_widget) ||
327 GTK_IS_MENU_ITEM (next_focus_widget)))
329 void *vp_next_focus_widget = &next_focus_widget;
330 g_source_remove (focus_notify_handler);
331 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
332 next_focus_widget = NULL;
333 focus_notify_handler = 0;
334 was_deselect = FALSE;
338 * If previously focused widget is not a GtkMenuItem or a GtkMenu,
339 * keep track of it so we can return to it after menubar is deactivated
342 !GTK_IS_MENU_ITEM (_focus_widget) &&
343 !GTK_IS_MENU (_focus_widget))
345 void *vp_focus_before_menu = &focus_before_menu;
346 focus_before_menu = _focus_widget;
347 g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
350 gail_focus_notify_when_idle (widget);
356 gail_map_cb (GtkWidget *widget)
358 gail_finish_select (widget);
362 gail_map_submenu_cb (GtkWidget *widget)
364 if (GTK_IS_MENU (widget))
366 GtkWidget *parent_menu_item;
368 parent_menu_item = gtk_menu_get_attach_widget (GTK_MENU (widget));
369 if (parent_menu_item)
370 gail_finish_select (parent_menu_item);
376 gail_deselect_watcher (GSignalInvocationHint *ihint,
377 guint n_param_values,
378 const GValue *param_values,
383 GtkWidget *menu_shell;
385 object = g_value_get_object (param_values + 0);
386 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
388 widget = GTK_WIDGET (object);
390 if (!GTK_IS_MENU_ITEM (widget))
393 if (subsequent_focus_widget == widget)
394 subsequent_focus_widget = NULL;
396 menu_shell = gtk_widget_get_parent (widget);
397 if (GTK_IS_MENU_SHELL (menu_shell))
399 GtkWidget *parent_menu_shell;
401 parent_menu_shell = gtk_menu_shell_get_parent_shell (GTK_MENU_SHELL (menu_shell));
402 if (parent_menu_shell)
404 GtkWidget *active_menu_item;
406 active_menu_item = gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (parent_menu_shell));
407 if (active_menu_item)
409 gail_focus_notify_when_idle (active_menu_item);
414 if (!GTK_IS_MENU_BAR (menu_shell))
416 gail_focus_notify_when_idle (menu_shell);
425 gail_switch_page_watcher (GSignalInvocationHint *ihint,
426 guint n_param_values,
427 const GValue *param_values,
433 object = g_value_get_object (param_values + 0);
434 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
436 widget = GTK_WIDGET (object);
438 if (!GTK_IS_NOTEBOOK (widget))
441 if (gtk_notebook_get_current_page (GTK_NOTEBOOK (widget)) == -1)
444 gail_focus_notify_when_idle (widget);
449 gail_focus_idle_handler (gpointer data)
451 focus_notify_handler = 0;
453 * The widget which was to receive focus may have been removed
455 if (!next_focus_widget)
457 if (next_focus_widget != data)
462 void *vp_next_focus_widget = &next_focus_widget;
463 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
464 next_focus_widget = NULL;
467 gail_focus_notify (data);
473 gail_focus_notify (GtkWidget *widget)
478 if (widget != _focus_widget)
482 void *vp_focus_widget = &_focus_widget;
483 g_object_remove_weak_pointer (G_OBJECT (_focus_widget), vp_focus_widget);
485 _focus_widget = widget;
488 void *vp_focus_widget = &_focus_widget;
489 g_object_add_weak_pointer (G_OBJECT (_focus_widget), vp_focus_widget);
491 * The UI may not have been updated yet; e.g. in gtkhtml2
492 * html_view_layout() is called in a idle handler
494 if (_focus_widget == focus_before_menu)
496 void *vp_focus_before_menu = &focus_before_menu;
497 g_object_remove_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
498 focus_before_menu = NULL;
501 gail_focus_notify_when_idle (_focus_widget);
506 atk_obj = gail_get_accessible_for_widget (_focus_widget, &transient);
510 * Do not report focus on redundant object
513 (atk_object_get_role(atk_obj) != ATK_ROLE_REDUNDANT_OBJECT))
514 atk_focus_tracker_notify (atk_obj);
515 if (atk_obj && transient)
516 g_object_unref (atk_obj);
517 if (subsequent_focus_widget)
519 GtkWidget *tmp_widget = subsequent_focus_widget;
520 subsequent_focus_widget = NULL;
521 gail_focus_notify_when_idle (tmp_widget);
527 gail_focus_notify_when_idle (GtkWidget *widget)
529 if (focus_notify_handler)
534 * Ignore focus request when menu item is going to be focused.
537 if (GTK_IS_MENU_ITEM (next_focus_widget) && !GTK_IS_MENU_ITEM (widget))
540 if (next_focus_widget)
542 if (GTK_IS_MENU_ITEM (next_focus_widget) && GTK_IS_MENU_ITEM (widget))
544 if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (next_focus_widget)) == gtk_widget_get_parent (widget))
546 if (subsequent_focus_widget)
547 g_assert_not_reached ();
548 subsequent_focus_widget = widget;
553 g_source_remove (focus_notify_handler);
554 if (next_focus_widget)
556 void *vp_next_focus_widget = &next_focus_widget;
557 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
558 next_focus_widget = NULL;
563 * Ignore if focus is being set to NULL and we are waiting to set focus
570 void *vp_next_focus_widget = &next_focus_widget;
571 next_focus_widget = widget;
572 g_object_add_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
577 * We are about to report focus as NULL so remove the weak pointer
578 * for the widget we were waiting to report focus on.
580 if (next_focus_widget)
582 void *vp_next_focus_widget = &next_focus_widget;
583 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
584 next_focus_widget = NULL;
588 focus_notify_handler = gdk_threads_add_idle (gail_focus_idle_handler, widget);
592 gail_deactivate_watcher (GSignalInvocationHint *ihint,
593 guint n_param_values,
594 const GValue *param_values,
600 GtkWidget *focus = NULL;
602 object = g_value_get_object (param_values + 0);
603 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
604 widget = GTK_WIDGET (object);
606 g_return_val_if_fail (GTK_IS_MENU_SHELL(widget), TRUE);
607 shell = GTK_MENU_SHELL(widget);
608 if (! gtk_menu_shell_get_parent_shell (shell))
609 focus = focus_before_menu;
612 * If we are waiting to report focus on a menubar or a menu item
613 * because of a previous deselect, cancel it.
616 focus_notify_handler &&
618 (GTK_IS_MENU_BAR (next_focus_widget) ||
619 GTK_IS_MENU_ITEM (next_focus_widget)))
621 void *vp_next_focus_widget = &next_focus_widget;
622 g_source_remove (focus_notify_handler);
623 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
624 next_focus_widget = NULL;
625 focus_notify_handler = 0;
626 was_deselect = FALSE;
628 gail_focus_notify_when_idle (focus);
634 gail_focus_tracker_init (void)
636 static gboolean emission_hooks_added = FALSE;
638 if (!emission_hooks_added)
641 * We cannot be sure that the classes exist so we make sure that they do.
643 g_type_class_ref (GTK_TYPE_WIDGET);
644 g_type_class_ref (GTK_TYPE_MENU_ITEM);
645 g_type_class_ref (GTK_TYPE_MENU_SHELL);
646 g_type_class_ref (GTK_TYPE_NOTEBOOK);
649 * We listen for event_after signal and then check that the
650 * event was a focus in event so we get called after the event.
652 g_signal_add_emission_hook (
653 g_signal_lookup ("event-after", GTK_TYPE_WIDGET), 0,
654 gail_focus_watcher, NULL, (GDestroyNotify) NULL);
656 * A "select" signal is emitted when arrow key is used to
657 * move to a list item in the popup window of a GtkCombo or
658 * a menu item in a menu.
660 g_signal_add_emission_hook (
661 g_signal_lookup ("select", GTK_TYPE_MENU_ITEM), 0,
662 gail_select_watcher, NULL, (GDestroyNotify) NULL);
665 * A "deselect" signal is emitted when arrow key is used to
666 * move from a menu item in a menu to the parent menu.
668 g_signal_add_emission_hook (
669 g_signal_lookup ("deselect", GTK_TYPE_MENU_ITEM), 0,
670 gail_deselect_watcher, NULL, (GDestroyNotify) NULL);
673 * We listen for deactivate signals on menushells to determine
674 * when the "focus" has left the menus.
676 g_signal_add_emission_hook (
677 g_signal_lookup ("deactivate", GTK_TYPE_MENU_SHELL), 0,
678 gail_deactivate_watcher, NULL, (GDestroyNotify) NULL);
681 * We listen for "switch-page" signal on a GtkNotebook to notify
682 * when page has changed because of clicking on a notebook tab.
684 g_signal_add_emission_hook (
685 g_signal_lookup ("switch-page", GTK_TYPE_NOTEBOOK), 0,
686 gail_switch_page_watcher, NULL, (GDestroyNotify) NULL);
687 emission_hooks_added = TRUE;
692 gail_focus_object_destroyed (gpointer data)
696 obj = G_OBJECT (data);
697 g_object_set_qdata (obj, quark_focus_object, NULL);
698 g_object_unref (obj);
702 gail_focus_tracker (AtkObject *focus_object)
705 * Do not report focus on redundant object
708 (atk_object_get_role(focus_object) != ATK_ROLE_REDUNDANT_OBJECT))
710 AtkObject *old_focus_object;
712 if (!GTK_IS_ACCESSIBLE (focus_object))
716 parent = focus_object;
719 parent = atk_object_get_parent (parent);
722 if (GTK_IS_ACCESSIBLE (parent))
728 gail_set_focus_object (focus_object, parent);
733 old_focus_object = g_object_get_qdata (G_OBJECT (focus_object), quark_focus_object);
734 if (old_focus_object)
736 g_object_weak_unref (G_OBJECT (old_focus_object),
737 (GWeakNotify) gail_focus_object_destroyed,
739 g_object_set_qdata (G_OBJECT (focus_object), quark_focus_object, NULL);
740 g_object_unref (G_OBJECT (focus_object));
747 gail_set_focus_widget (GtkWidget *focus_widget,
750 AtkObject *focus_obj;
753 focus_obj = gtk_widget_get_accessible (focus_widget);
754 obj = gtk_widget_get_accessible (widget);
755 gail_set_focus_object (focus_obj, obj);
759 gail_set_focus_object (AtkObject *focus_obj,
762 AtkObject *old_focus_obj;
764 old_focus_obj = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
765 if (old_focus_obj != obj)
768 g_object_weak_unref (G_OBJECT (old_focus_obj),
769 (GWeakNotify) gail_focus_object_destroyed,
773 * We call g_object_ref as if obj is destroyed
774 * while the weak reference exists then destroying the
775 * focus_obj would cause gail_focus_object_destroyed to be
776 * called when obj is not a valid GObject.
780 g_object_weak_ref (G_OBJECT (focus_obj),
781 (GWeakNotify) gail_focus_object_destroyed,
783 g_object_set_qdata (G_OBJECT (obj), quark_focus_object, focus_obj);
788 _gtk_accessibility_init (void)
790 static int initialized = FALSE;
796 quark_focus_object = g_quark_from_static_string ("gail-focus-object");
798 atk_focus_tracker_init (gail_focus_tracker_init);
799 focus_tracker_id = atk_add_focus_tracker (gail_focus_tracker);
801 _gail_util_install ();
803 atk_misc_instance = g_object_new (GAIL_TYPE_MISC, NULL);