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 "gailbooleancell.h"
28 #include "gailcontainercell.h"
29 #include "gailimagecell.h"
30 #include "gailrenderercell.h"
31 #include "gailtextcell.h"
32 #include "gailtoplevel.h"
34 #include "gailwidget.h"
36 #include "gailfactory.h"
38 #define GNOME_ACCESSIBILITY_ENV "GNOME_ACCESSIBILITY"
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;
80 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_WIDGET, GailWidget, gail_widget, GTK_TYPE_WIDGET)
81 GAIL_IMPLEMENT_FACTORY_WITH_FUNC_DUMMY (GAIL_TYPE_RENDERER_CELL, GailRendererCell, gail_renderer_cell, GTK_TYPE_CELL_RENDERER, gail_renderer_cell_new)
82 GAIL_IMPLEMENT_FACTORY_WITH_FUNC_DUMMY (GAIL_TYPE_BOOLEAN_CELL, GailBooleanCell, gail_boolean_cell, GTK_TYPE_CELL_RENDERER_TOGGLE, gail_boolean_cell_new)
83 GAIL_IMPLEMENT_FACTORY_WITH_FUNC_DUMMY (GAIL_TYPE_IMAGE_CELL, GailImageCell, gail_image_cell, GTK_TYPE_CELL_RENDERER_PIXBUF, gail_image_cell_new)
84 GAIL_IMPLEMENT_FACTORY_WITH_FUNC_DUMMY (GAIL_TYPE_TEXT_CELL, GailTextCell, gail_text_cell, GTK_TYPE_CELL_RENDERER_TEXT, gail_text_cell_new)
87 gail_get_accessible_for_widget (GtkWidget *widget,
90 AtkObject *obj = NULL;
96 if (GTK_IS_ENTRY (widget))
98 else if (GTK_IS_NOTEBOOK (widget))
100 GtkNotebook *notebook;
103 notebook = GTK_NOTEBOOK (widget);
104 page_num = gtk_notebook_get_current_page (notebook);
107 obj = gtk_widget_get_accessible (widget);
108 obj = atk_object_ref_accessible_child (obj, page_num);
109 g_object_unref (obj);
112 else if (GTK_IS_TOGGLE_BUTTON (widget))
114 GtkWidget *other_widget = gtk_widget_get_parent (widget);
115 if (GTK_IS_COMBO_BOX (other_widget))
117 gail_set_focus_widget (other_widget, widget);
118 widget = other_widget;
123 AtkObject *focus_object;
125 obj = gtk_widget_get_accessible (widget);
126 focus_object = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
128 * We check whether the object for this focus_object has been deleted.
129 * This can happen when navigating to an empty directory in nautilus.
132 if (ATK_IS_GOBJECT_ACCESSIBLE (focus_object))
134 if (!atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (focus_object)))
145 gail_focus_watcher (GSignalInvocationHint *ihint,
146 guint n_param_values,
147 const GValue *param_values,
154 object = g_value_get_object (param_values + 0);
155 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
157 event = g_value_get_boxed (param_values + 1);
158 widget = GTK_WIDGET (object);
160 if (event->type == GDK_FOCUS_CHANGE)
162 if (event->focus_change.in)
164 if (GTK_IS_WINDOW (widget))
166 GtkWidget *focus_widget;
170 window = GTK_WINDOW (widget);
171 focus_widget = gtk_window_get_focus (window);
172 g_object_get (window, "type", &type, NULL);
177 * If we already have a potential focus widget set this
178 * windows's focus widget to focus_before_menu so that
179 * it will be reported when menu item is unset.
181 if (next_focus_widget)
183 if (GTK_IS_MENU_ITEM (next_focus_widget) &&
186 void *vp_focus_before_menu = &focus_before_menu;
187 focus_before_menu = focus_widget;
188 g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
193 widget = focus_widget;
195 else if (type == GTK_WINDOW_POPUP)
197 if (GTK_IS_BIN (widget))
199 GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
201 if (GTK_IS_WIDGET (child) && gtk_widget_has_grab (child))
203 if (GTK_IS_MENU_SHELL (child))
205 if (gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (child)))
208 * We have a menu which has a menu item selected
209 * so we do not report focus on the menu.
217 else /* popup window has no children; this edge case occurs in some custom code (OOo for instance) */
222 else /* Widget is a non-popup toplevel with no focus children;
223 don't emit for this case either, as it's useless */
231 if (next_focus_widget)
235 toplevel = gtk_widget_get_toplevel (next_focus_widget);
236 if (toplevel == widget)
237 next_focus_widget = NULL;
245 if (event->type == GDK_MOTION_NOTIFY && gtk_widget_has_focus (widget))
247 if (widget == focus_widget)
258 #ifdef GDK_WINDOWING_X11
260 * If the focus widget is a GtkSocket without a plug
261 * then ignore the focus notification as the embedded
262 * plug will report a focus notification.
264 if (GTK_IS_SOCKET (widget) &&
265 gtk_socket_get_plug_window (GTK_SOCKET (widget)) != NULL)
270 * The widget may not yet be visible on the screen so we wait until it is.
272 gail_focus_notify_when_idle (widget);
277 gail_select_watcher (GSignalInvocationHint *ihint,
278 guint n_param_values,
279 const GValue *param_values,
285 object = g_value_get_object (param_values + 0);
286 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
288 widget = GTK_WIDGET (object);
290 if (!gtk_widget_get_mapped (widget))
292 g_signal_connect (widget, "map",
293 G_CALLBACK (gail_map_cb),
297 gail_finish_select (widget);
303 gail_finish_select (GtkWidget *widget)
305 if (GTK_IS_MENU_ITEM (widget))
307 GtkMenuItem* menu_item;
310 menu_item = GTK_MENU_ITEM (widget);
311 submenu = gtk_menu_item_get_submenu (menu_item);
313 !gtk_widget_get_mapped (submenu))
316 * If the submenu is not visble, wait until it is before
317 * reporting focus on the menu item.
321 handler_id = g_signal_handler_find (submenu,
323 g_signal_lookup ("map",
327 (gpointer) gail_map_submenu_cb,
330 g_signal_connect (submenu, "map",
331 G_CALLBACK (gail_map_submenu_cb),
336 * If we are waiting to report focus on a menubar or a menu item
337 * because of a previous deselect, cancel it.
340 focus_notify_handler &&
342 (GTK_IS_MENU_BAR (next_focus_widget) ||
343 GTK_IS_MENU_ITEM (next_focus_widget)))
345 void *vp_next_focus_widget = &next_focus_widget;
346 g_source_remove (focus_notify_handler);
347 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
348 next_focus_widget = NULL;
349 focus_notify_handler = 0;
350 was_deselect = FALSE;
354 * If previously focused widget is not a GtkMenuItem or a GtkMenu,
355 * keep track of it so we can return to it after menubar is deactivated
358 !GTK_IS_MENU_ITEM (focus_widget) &&
359 !GTK_IS_MENU (focus_widget))
361 void *vp_focus_before_menu = &focus_before_menu;
362 focus_before_menu = focus_widget;
363 g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
366 gail_focus_notify_when_idle (widget);
372 gail_map_cb (GtkWidget *widget)
374 gail_finish_select (widget);
378 gail_map_submenu_cb (GtkWidget *widget)
380 if (GTK_IS_MENU (widget))
382 GtkWidget *parent_menu_item;
384 parent_menu_item = gtk_menu_get_attach_widget (GTK_MENU (widget));
385 if (parent_menu_item)
386 gail_finish_select (parent_menu_item);
392 gail_deselect_watcher (GSignalInvocationHint *ihint,
393 guint n_param_values,
394 const GValue *param_values,
399 GtkWidget *menu_shell;
401 object = g_value_get_object (param_values + 0);
402 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
404 widget = GTK_WIDGET (object);
406 if (!GTK_IS_MENU_ITEM (widget))
409 if (subsequent_focus_widget == widget)
410 subsequent_focus_widget = NULL;
412 menu_shell = gtk_widget_get_parent (widget);
413 if (GTK_IS_MENU_SHELL (menu_shell))
415 GtkWidget *parent_menu_shell;
417 parent_menu_shell = gtk_menu_shell_get_parent_shell (GTK_MENU_SHELL (menu_shell));
418 if (parent_menu_shell)
420 GtkWidget *active_menu_item;
422 active_menu_item = gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (parent_menu_shell));
423 if (active_menu_item)
425 gail_focus_notify_when_idle (active_menu_item);
430 if (!GTK_IS_MENU_BAR (menu_shell))
432 gail_focus_notify_when_idle (menu_shell);
441 gail_switch_page_watcher (GSignalInvocationHint *ihint,
442 guint n_param_values,
443 const GValue *param_values,
449 object = g_value_get_object (param_values + 0);
450 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
452 widget = GTK_WIDGET (object);
454 if (!GTK_IS_NOTEBOOK (widget))
457 if (gtk_notebook_get_current_page (GTK_NOTEBOOK (widget)) == -1)
460 gail_focus_notify_when_idle (widget);
465 gail_focus_idle_handler (gpointer data)
467 focus_notify_handler = 0;
469 * The widget which was to receive focus may have been removed
471 if (!next_focus_widget)
473 if (next_focus_widget != data)
478 void *vp_next_focus_widget = &next_focus_widget;
479 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
480 next_focus_widget = NULL;
483 gail_focus_notify (data);
489 gail_focus_notify (GtkWidget *widget)
494 if (widget != focus_widget)
498 void *vp_focus_widget = &focus_widget;
499 g_object_remove_weak_pointer (G_OBJECT (focus_widget), vp_focus_widget);
501 focus_widget = widget;
504 void *vp_focus_widget = &focus_widget;
505 g_object_add_weak_pointer (G_OBJECT (focus_widget), vp_focus_widget);
507 * The UI may not have been updated yet; e.g. in gtkhtml2
508 * html_view_layout() is called in a idle handler
510 if (focus_widget == focus_before_menu)
512 void *vp_focus_before_menu = &focus_before_menu;
513 g_object_remove_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
514 focus_before_menu = NULL;
517 gail_focus_notify_when_idle (focus_widget);
522 atk_obj = gail_get_accessible_for_widget (focus_widget, &transient);
526 * Do not report focus on redundant object
529 (atk_object_get_role(atk_obj) != ATK_ROLE_REDUNDANT_OBJECT))
530 atk_focus_tracker_notify (atk_obj);
531 if (atk_obj && transient)
532 g_object_unref (atk_obj);
533 if (subsequent_focus_widget)
535 GtkWidget *tmp_widget = subsequent_focus_widget;
536 subsequent_focus_widget = NULL;
537 gail_focus_notify_when_idle (tmp_widget);
543 gail_focus_notify_when_idle (GtkWidget *widget)
545 if (focus_notify_handler)
550 * Ignore focus request when menu item is going to be focused.
553 if (GTK_IS_MENU_ITEM (next_focus_widget) && !GTK_IS_MENU_ITEM (widget))
556 if (next_focus_widget)
558 if (GTK_IS_MENU_ITEM (next_focus_widget) && GTK_IS_MENU_ITEM (widget))
560 if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (next_focus_widget)) == gtk_widget_get_parent (widget))
562 if (subsequent_focus_widget)
563 g_assert_not_reached ();
564 subsequent_focus_widget = widget;
569 g_source_remove (focus_notify_handler);
570 if (next_focus_widget)
572 void *vp_next_focus_widget = &next_focus_widget;
573 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
574 next_focus_widget = NULL;
579 * Ignore if focus is being set to NULL and we are waiting to set focus
586 void *vp_next_focus_widget = &next_focus_widget;
587 next_focus_widget = widget;
588 g_object_add_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
593 * We are about to report focus as NULL so remove the weak pointer
594 * for the widget we were waiting to report focus on.
596 if (next_focus_widget)
598 void *vp_next_focus_widget = &next_focus_widget;
599 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
600 next_focus_widget = NULL;
604 focus_notify_handler = gdk_threads_add_idle (gail_focus_idle_handler, widget);
608 gail_deactivate_watcher (GSignalInvocationHint *ihint,
609 guint n_param_values,
610 const GValue *param_values,
616 GtkWidget *focus = NULL;
618 object = g_value_get_object (param_values + 0);
619 g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
620 widget = GTK_WIDGET (object);
622 g_return_val_if_fail (GTK_IS_MENU_SHELL(widget), TRUE);
623 shell = GTK_MENU_SHELL(widget);
624 if (! gtk_menu_shell_get_parent_shell (shell))
625 focus = focus_before_menu;
628 * If we are waiting to report focus on a menubar or a menu item
629 * because of a previous deselect, cancel it.
632 focus_notify_handler &&
634 (GTK_IS_MENU_BAR (next_focus_widget) ||
635 GTK_IS_MENU_ITEM (next_focus_widget)))
637 void *vp_next_focus_widget = &next_focus_widget;
638 g_source_remove (focus_notify_handler);
639 g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
640 next_focus_widget = NULL;
641 focus_notify_handler = 0;
642 was_deselect = FALSE;
644 gail_focus_notify_when_idle (focus);
650 gail_focus_tracker_init (void)
652 static gboolean emission_hooks_added = FALSE;
654 if (!emission_hooks_added)
657 * We cannot be sure that the classes exist so we make sure that they do.
659 g_type_class_ref (GTK_TYPE_WIDGET);
660 g_type_class_ref (GTK_TYPE_MENU_ITEM);
661 g_type_class_ref (GTK_TYPE_MENU_SHELL);
662 g_type_class_ref (GTK_TYPE_NOTEBOOK);
665 * We listen for event_after signal and then check that the
666 * event was a focus in event so we get called after the event.
668 g_signal_add_emission_hook (
669 g_signal_lookup ("event-after", GTK_TYPE_WIDGET), 0,
670 gail_focus_watcher, NULL, (GDestroyNotify) NULL);
672 * A "select" signal is emitted when arrow key is used to
673 * move to a list item in the popup window of a GtkCombo or
674 * a menu item in a menu.
676 g_signal_add_emission_hook (
677 g_signal_lookup ("select", GTK_TYPE_MENU_ITEM), 0,
678 gail_select_watcher, NULL, (GDestroyNotify) NULL);
681 * A "deselect" signal is emitted when arrow key is used to
682 * move from a menu item in a menu to the parent menu.
684 g_signal_add_emission_hook (
685 g_signal_lookup ("deselect", GTK_TYPE_MENU_ITEM), 0,
686 gail_deselect_watcher, NULL, (GDestroyNotify) NULL);
689 * We listen for deactivate signals on menushells to determine
690 * when the "focus" has left the menus.
692 g_signal_add_emission_hook (
693 g_signal_lookup ("deactivate", GTK_TYPE_MENU_SHELL), 0,
694 gail_deactivate_watcher, NULL, (GDestroyNotify) NULL);
697 * We listen for "switch-page" signal on a GtkNotebook to notify
698 * when page has changed because of clicking on a notebook tab.
700 g_signal_add_emission_hook (
701 g_signal_lookup ("switch-page", GTK_TYPE_NOTEBOOK), 0,
702 gail_switch_page_watcher, NULL, (GDestroyNotify) NULL);
703 emission_hooks_added = TRUE;
708 gail_focus_object_destroyed (gpointer data)
712 obj = G_OBJECT (data);
713 g_object_set_qdata (obj, quark_focus_object, NULL);
714 g_object_unref (obj);
718 gail_focus_tracker (AtkObject *focus_object)
721 * Do not report focus on redundant object
724 (atk_object_get_role(focus_object) != ATK_ROLE_REDUNDANT_OBJECT))
726 AtkObject *old_focus_object;
728 if (!GTK_IS_ACCESSIBLE (focus_object))
732 parent = focus_object;
735 parent = atk_object_get_parent (parent);
738 if (GTK_IS_ACCESSIBLE (parent))
744 gail_set_focus_object (focus_object, parent);
749 old_focus_object = g_object_get_qdata (G_OBJECT (focus_object), quark_focus_object);
750 if (old_focus_object)
752 g_object_weak_unref (G_OBJECT (old_focus_object),
753 (GWeakNotify) gail_focus_object_destroyed,
755 g_object_set_qdata (G_OBJECT (focus_object), quark_focus_object, NULL);
756 g_object_unref (G_OBJECT (focus_object));
763 gail_set_focus_widget (GtkWidget *focus_widget,
766 AtkObject *focus_obj;
769 focus_obj = gtk_widget_get_accessible (focus_widget);
770 obj = gtk_widget_get_accessible (widget);
771 gail_set_focus_object (focus_obj, obj);
775 gail_set_focus_object (AtkObject *focus_obj,
778 AtkObject *old_focus_obj;
780 old_focus_obj = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
781 if (old_focus_obj != obj)
784 g_object_weak_unref (G_OBJECT (old_focus_obj),
785 (GWeakNotify) gail_focus_object_destroyed,
789 * We call g_object_ref as if obj is destroyed
790 * while the weak reference exists then destroying the
791 * focus_obj would cause gail_focus_object_destroyed to be
792 * called when obj is not a valid GObject.
796 g_object_weak_ref (G_OBJECT (focus_obj),
797 (GWeakNotify) gail_focus_object_destroyed,
799 g_object_set_qdata (G_OBJECT (obj), quark_focus_object, focus_obj);
803 static int gail_initialized = FALSE;
806 gail_accessibility_module_init (void)
808 const char *env_a_t_support;
809 gboolean a_t_support = FALSE;
811 if (gail_initialized)
815 gail_initialized = TRUE;
816 quark_focus_object = g_quark_from_static_string ("gail-focus-object");
818 env_a_t_support = g_getenv (GNOME_ACCESSIBILITY_ENV);
821 a_t_support = atoi (env_a_t_support);
823 fprintf (stderr, "GTK Accessibility Module initialized\n");
825 GAIL_WIDGET_SET_FACTORY (GTK_TYPE_WIDGET, gail_widget);
826 GAIL_WIDGET_SET_FACTORY (GTK_TYPE_CELL_RENDERER_TEXT, gail_text_cell);
827 GAIL_WIDGET_SET_FACTORY (GTK_TYPE_CELL_RENDERER_TOGGLE, gail_boolean_cell);
828 GAIL_WIDGET_SET_FACTORY (GTK_TYPE_CELL_RENDERER_PIXBUF, gail_image_cell);
829 GAIL_WIDGET_SET_FACTORY (GTK_TYPE_CELL_RENDERER, gail_renderer_cell);
831 atk_focus_tracker_init (gail_focus_tracker_init);
832 focus_tracker_id = atk_add_focus_tracker (gail_focus_tracker);
834 /* Initialize the GailUtility class */
835 g_type_class_unref (g_type_class_ref (GAIL_TYPE_UTIL));
836 g_type_class_unref (g_type_class_ref (GAIL_TYPE_MISC));