]> Pileus Git - ~andy/gtk/blob - gtk/a11y/gail.c
b9e25f6ce94cde160f78cd1f766cc3522c0499de
[~andy/gtk] / gtk / a11y / gail.c
1 /* GAIL - The GNOME Accessibility Implementation Library
2  * Copyright 2001 Sun Microsystems Inc.
3  *
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.
8  *
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.
13  *
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.
18  */
19
20 #include "config.h"
21
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include <gtk/gtkx.h>
26 #include "gailadjustment.h"
27 #include "gailarrow.h"
28 #include "gailbooleancell.h"
29 #include "gailbox.h"
30 #include "gailbutton.h"
31 #include "gailcalendar.h"
32 #include "gailcell.h"
33 #include "gailcheckmenuitem.h"
34 #include "gailcombobox.h"
35 #include "gailcontainer.h"
36 #include "gailcontainercell.h"
37 #include "gailexpander.h"
38 #include "gailframe.h"
39 #include "gailimage.h"
40 #include "gailimagecell.h"
41 #include "gaillinkbutton.h"
42 #include "gailmenu.h"
43 #include "gailmenushell.h"
44 #include "gailmenuitem.h"
45 #include "gailnotebook.h"
46 #include "gailpaned.h"
47 #include "gailprogressbar.h"
48 #include "gailradiobutton.h"
49 #include "gailradiomenuitem.h"
50 #include "gailrenderercell.h"
51 #include "gailrange.h"
52 #include "gailscalebutton.h"
53 #include "gailscrollbar.h"
54 #include "gailscrolledwindow.h"
55 #include "gailseparator.h"
56 #include "gailstatusbar.h"
57 #include "gailtextcell.h"
58 #include "gailtogglebutton.h"
59 #include "gailtoplevel.h"
60 #include "gailtreeview.h"
61 #include "gailutil.h"
62 #include "gailwidget.h"
63 #include "gailwindow.h"
64
65 #include "gailfactory.h"
66
67 #define GNOME_ACCESSIBILITY_ENV "GNOME_ACCESSIBILITY"
68
69 static gboolean gail_focus_watcher      (GSignalInvocationHint *ihint,
70                                          guint                  n_param_values,
71                                          const GValue          *param_values,
72                                          gpointer               data);
73 static gboolean gail_select_watcher     (GSignalInvocationHint *ihint,
74                                          guint                  n_param_values,
75                                          const GValue          *param_values,
76                                          gpointer               data);
77 static gboolean gail_deselect_watcher   (GSignalInvocationHint *ihint,
78                                          guint                  n_param_values,
79                                          const GValue          *param_values,
80                                          gpointer               data);
81 static gboolean gail_switch_page_watcher(GSignalInvocationHint *ihint,
82                                          guint                  n_param_values,
83                                          const GValue          *param_values,
84                                          gpointer               data);
85 static AtkObject* gail_get_accessible_for_widget (GtkWidget    *widget,
86                                                   gboolean     *transient);
87 static void     gail_finish_select       (GtkWidget            *widget);
88 static void     gail_map_cb              (GtkWidget            *widget);
89 static void     gail_map_submenu_cb      (GtkWidget            *widget);
90 static gint     gail_focus_idle_handler  (gpointer             data);
91 static void     gail_focus_notify        (GtkWidget            *widget);
92 static void     gail_focus_notify_when_idle (GtkWidget            *widget);
93
94 static void     gail_focus_tracker_init (void);
95 static void     gail_focus_object_destroyed (gpointer data);
96 static void     gail_focus_tracker (AtkObject *object);
97 static void     gail_set_focus_widget (GtkWidget *focus_widget,
98                                        GtkWidget *widget); 
99 static void     gail_set_focus_object (AtkObject *focus_obj,
100                                        AtkObject *obj);
101
102 GtkWidget* focus_widget = NULL;
103 static GtkWidget* next_focus_widget = NULL;
104 static gboolean was_deselect = FALSE;
105 static GtkWidget* subsequent_focus_widget = NULL;
106 static GtkWidget* focus_before_menu = NULL;
107 static guint focus_notify_handler = 0;    
108 static guint focus_tracker_id = 0;
109 static GQuark quark_focus_object = 0;
110
111 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_WIDGET, GailWidget, gail_widget, GTK_TYPE_WIDGET)
112 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_CONTAINER, GailContainer, gail_container, GTK_TYPE_CONTAINER)
113 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_BUTTON, GailButton, gail_button, GTK_TYPE_BUTTON)
114 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_LINK_BUTTON, GailLinkButton, gail_link_button, GTK_TYPE_LINK_BUTTON)
115 GAIL_IMPLEMENT_FACTORY_WITH_FUNC (GAIL_TYPE_MENU_ITEM, GailMenuItem, gail_menu_item, gail_menu_item_new)
116 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_TOGGLE_BUTTON, GailToggleButton, gail_toggle_button, GTK_TYPE_TOGGLE_BUTTON)
117 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_IMAGE, GailImage, gail_image, GTK_TYPE_IMAGE)
118 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_COMBO_BOX, GailComboBox, gail_combo_box, GTK_TYPE_COMBO_BOX)
119 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_MENU_SHELL, GailMenuShell, gail_menu_shell, GTK_TYPE_MENU_SHELL)
120 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_MENU, GailMenu, gail_menu, GTK_TYPE_MENU)
121 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_WINDOW, GailWindow, gail_window, GTK_TYPE_BIN)
122 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_RANGE, GailRange, gail_range, GTK_TYPE_RANGE)
123 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_SCALE_BUTTON, GailScaleButton, gail_scale_button, GTK_TYPE_SCALE_BUTTON)
124 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_STATUSBAR, GailStatusbar, gail_statusbar, GTK_TYPE_STATUSBAR)
125 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_NOTEBOOK, GailNotebook, gail_notebook, GTK_TYPE_NOTEBOOK)
126 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_CALENDAR, GailCalendar, gail_calendar, GTK_TYPE_CALENDAR)
127 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_PROGRESS_BAR, GailProgressBar, gail_progress_bar, GTK_TYPE_PROGRESS_BAR)
128 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_TREE_VIEW, GailTreeView, gail_tree_view, GTK_TYPE_TREE_VIEW)
129 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_FRAME, GailFrame, gail_frame, GTK_TYPE_FRAME)
130 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_RADIO_BUTTON, GailRadioButton, gail_radio_button, GTK_TYPE_RADIO_BUTTON)
131 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_ARROW, GailArrow, gail_arrow, GTK_TYPE_ARROW)
132 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_SEPARATOR, GailSeparator, gail_separator, GTK_TYPE_SEPARATOR)
133 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_BOX, GailBox, gail_box, GTK_TYPE_BOX)
134 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_SCROLLED_WINDOW, GailScrolledWindow, gail_scrolled_window, GTK_TYPE_SCROLLED_WINDOW)
135 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_PANED, GailPaned, gail_paned, GTK_TYPE_PANED)
136 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_SCROLLBAR, GailScrollbar, gail_scrollbar, GTK_TYPE_SCROLLBAR)
137 GAIL_IMPLEMENT_FACTORY_WITH_FUNC (GAIL_TYPE_CHECK_MENU_ITEM, GailCheckMenuItem, gail_check_menu_item, gail_check_menu_item_new)
138 GAIL_IMPLEMENT_FACTORY_WITH_FUNC (GAIL_TYPE_RADIO_MENU_ITEM, GailRadioMenuItem, gail_radio_menu_item, gail_radio_menu_item_new)
139 GAIL_IMPLEMENT_FACTORY (GAIL_TYPE_EXPANDER, GailExpander, gail_expander, GTK_TYPE_EXPANDER)
140 GAIL_IMPLEMENT_FACTORY_WITH_FUNC_DUMMY (GAIL_TYPE_RENDERER_CELL, GailRendererCell, gail_renderer_cell, GTK_TYPE_CELL_RENDERER, gail_renderer_cell_new)
141 GAIL_IMPLEMENT_FACTORY_WITH_FUNC_DUMMY (GAIL_TYPE_BOOLEAN_CELL, GailBooleanCell, gail_boolean_cell, GTK_TYPE_CELL_RENDERER_TOGGLE, gail_boolean_cell_new)
142 GAIL_IMPLEMENT_FACTORY_WITH_FUNC_DUMMY (GAIL_TYPE_IMAGE_CELL, GailImageCell, gail_image_cell, GTK_TYPE_CELL_RENDERER_PIXBUF, gail_image_cell_new)
143 GAIL_IMPLEMENT_FACTORY_WITH_FUNC_DUMMY (GAIL_TYPE_TEXT_CELL, GailTextCell, gail_text_cell, GTK_TYPE_CELL_RENDERER_TEXT, gail_text_cell_new)
144
145 static AtkObject*
146 gail_get_accessible_for_widget (GtkWidget *widget,
147                                 gboolean  *transient)
148 {
149   AtkObject *obj = NULL;
150
151   *transient = FALSE;
152   if (!widget)
153     return NULL;
154
155   if (GTK_IS_ENTRY (widget))
156     ;
157   else if (GTK_IS_NOTEBOOK (widget)) 
158     {
159       GtkNotebook *notebook;
160       gint page_num = -1;
161
162       notebook = GTK_NOTEBOOK (widget);
163       page_num = gtk_notebook_get_current_page (notebook);
164       if (page_num != -1)
165         {
166           obj = gtk_widget_get_accessible (widget);
167           obj = atk_object_ref_accessible_child (obj, page_num);
168           g_object_unref (obj);
169         }
170     }
171   else if (GTK_IS_TOGGLE_BUTTON (widget))
172     {
173       GtkWidget *other_widget = gtk_widget_get_parent (widget);
174       if (GTK_IS_COMBO_BOX (other_widget))
175         {
176           gail_set_focus_widget (other_widget, widget);
177           widget = other_widget;
178         }
179     }
180   if (obj == NULL)
181     {
182       AtkObject *focus_object;
183
184       obj = gtk_widget_get_accessible (widget);
185       focus_object = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
186       /*
187        * We check whether the object for this focus_object has been deleted.
188        * This can happen when navigating to an empty directory in nautilus. 
189        * See bug #141907.
190        */
191       if (ATK_IS_GOBJECT_ACCESSIBLE (focus_object))
192         {
193           if (!atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (focus_object)))
194             focus_object = NULL;
195         }
196       if (focus_object)
197         obj = focus_object;
198     }
199
200   return obj;
201 }
202
203 static gboolean
204 gail_focus_watcher (GSignalInvocationHint *ihint,
205                     guint                  n_param_values,
206                     const GValue          *param_values,
207                     gpointer               data)
208 {
209   GObject *object;
210   GtkWidget *widget;
211   GdkEvent *event;
212
213   object = g_value_get_object (param_values + 0);
214   g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
215
216   event = g_value_get_boxed (param_values + 1);
217   widget = GTK_WIDGET (object);
218
219   if (event->type == GDK_FOCUS_CHANGE) 
220     {
221       if (event->focus_change.in)
222         {
223           if (GTK_IS_WINDOW (widget))
224             {
225               GtkWidget *focus_widget;
226               GtkWindow *window;
227               GtkWindowType type;
228
229               window = GTK_WINDOW (widget);
230               focus_widget = gtk_window_get_focus (window);
231               g_object_get (window, "type", &type, NULL);
232
233               if (focus_widget)
234                 {
235                   /*
236                    * If we already have a potential focus widget set this
237                    * windows's focus widget to focus_before_menu so that 
238                    * it will be reported when menu item is unset.
239                    */
240                   if (next_focus_widget)
241                     {
242                       if (GTK_IS_MENU_ITEM (next_focus_widget) &&
243                           !focus_before_menu)
244                         {
245                           void *vp_focus_before_menu = &focus_before_menu;
246                           focus_before_menu = focus_widget;
247                           g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
248                         }
249
250                       return TRUE;
251                     }
252                   widget = focus_widget;
253                 }
254               else if (type == GTK_WINDOW_POPUP)
255                 {
256                   if (GTK_IS_BIN (widget))
257                     {
258                       GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
259
260                       if (GTK_IS_WIDGET (child) && gtk_widget_has_grab (child))
261                         {
262                           if (GTK_IS_MENU_SHELL (child))
263                             {
264                               if (gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (child)))
265                                 {
266                                   /*
267                                    * We have a menu which has a menu item selected
268                                    * so we do not report focus on the menu.
269                                    */ 
270                                   return TRUE; 
271                                 }
272                             }
273                           widget = child;
274                         } 
275                     }
276                   else /* popup window has no children; this edge case occurs in some custom code (OOo for instance) */
277                     {
278                       return TRUE;
279                     }
280                 }
281               else /* Widget is a non-popup toplevel with no focus children; 
282                       don't emit for this case either, as it's useless */
283                 {
284                   return TRUE;
285                 }
286             }
287         }
288       else
289         {
290           if (next_focus_widget)
291             {
292                GtkWidget *toplevel;
293
294                toplevel = gtk_widget_get_toplevel (next_focus_widget);
295                if (toplevel == widget)
296                  next_focus_widget = NULL; 
297             }
298           /* focus out */
299           widget = NULL;
300         }
301     }
302   else
303     {
304       if (event->type == GDK_MOTION_NOTIFY && gtk_widget_has_focus (widget))
305         {
306           if (widget == focus_widget)
307             {
308               return TRUE;
309             }
310         }
311       else
312         {
313           return TRUE;
314         }
315     }
316
317 #ifdef GDK_WINDOWING_X11
318   /*
319    * If the focus widget is a GtkSocket without a plug
320    * then ignore the focus notification as the embedded
321    * plug will report a focus notification.
322    */
323   if (GTK_IS_SOCKET (widget) &&
324       gtk_socket_get_plug_window (GTK_SOCKET (widget)) != NULL)
325     return TRUE;
326 #endif
327
328   /*
329    * The widget may not yet be visible on the screen so we wait until it is.
330    */
331   gail_focus_notify_when_idle (widget);
332   return TRUE; 
333 }
334
335 static gboolean
336 gail_select_watcher (GSignalInvocationHint *ihint,
337                      guint                  n_param_values,
338                      const GValue          *param_values,
339                      gpointer               data)
340 {
341   GObject *object;
342   GtkWidget *widget;
343
344   object = g_value_get_object (param_values + 0);
345   g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
346
347   widget = GTK_WIDGET (object);
348
349   if (!gtk_widget_get_mapped (widget))
350     {
351       g_signal_connect (widget, "map",
352                         G_CALLBACK (gail_map_cb),
353                         NULL);
354     }
355   else
356     gail_finish_select (widget);
357
358   return TRUE;
359 }
360
361 static void
362 gail_finish_select (GtkWidget *widget)
363 {
364   if (GTK_IS_MENU_ITEM (widget))
365     {
366       GtkMenuItem* menu_item;
367       GtkWidget *submenu;
368
369       menu_item = GTK_MENU_ITEM (widget);
370       submenu = gtk_menu_item_get_submenu (menu_item);
371       if (submenu &&
372           !gtk_widget_get_mapped (submenu))
373         {
374           /*
375            * If the submenu is not visble, wait until it is before
376            * reporting focus on the menu item.
377            */
378           gulong handler_id;
379
380           handler_id = g_signal_handler_find (submenu,
381                                               G_SIGNAL_MATCH_FUNC,
382                                               g_signal_lookup ("map",
383                                                                GTK_TYPE_WINDOW),
384                                               0,
385                                               NULL,
386                                               (gpointer) gail_map_submenu_cb,
387                                               NULL); 
388           if (!handler_id)
389             g_signal_connect (submenu, "map",
390                               G_CALLBACK (gail_map_submenu_cb),
391                               NULL);
392             return;
393         }
394       /*
395        * If we are waiting to report focus on a menubar or a menu item
396        * because of a previous deselect, cancel it.
397        */
398       if (was_deselect &&
399           focus_notify_handler &&
400           next_focus_widget &&
401           (GTK_IS_MENU_BAR (next_focus_widget) ||
402            GTK_IS_MENU_ITEM (next_focus_widget)))
403         {
404           void *vp_next_focus_widget = &next_focus_widget;
405           g_source_remove (focus_notify_handler);
406           g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
407           next_focus_widget = NULL;
408           focus_notify_handler = 0;
409           was_deselect = FALSE;
410         }
411     } 
412   /*
413    * If previously focused widget is not a GtkMenuItem or a GtkMenu,
414    * keep track of it so we can return to it after menubar is deactivated
415    */
416   if (focus_widget && 
417       !GTK_IS_MENU_ITEM (focus_widget) && 
418       !GTK_IS_MENU (focus_widget))
419     {
420       void *vp_focus_before_menu = &focus_before_menu;
421       focus_before_menu = focus_widget;
422       g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
423
424     } 
425   gail_focus_notify_when_idle (widget);
426
427   return; 
428 }
429
430 static void
431 gail_map_cb (GtkWidget *widget)
432 {
433   gail_finish_select (widget);
434 }
435
436 static void
437 gail_map_submenu_cb (GtkWidget *widget)
438 {
439   if (GTK_IS_MENU (widget))
440     {
441       GtkWidget *parent_menu_item;
442
443       parent_menu_item = gtk_menu_get_attach_widget (GTK_MENU (widget));
444       if (parent_menu_item)
445         gail_finish_select (parent_menu_item);
446     }
447 }
448
449
450 static gboolean
451 gail_deselect_watcher (GSignalInvocationHint *ihint,
452                        guint                  n_param_values,
453                        const GValue          *param_values,
454                        gpointer               data)
455 {
456   GObject *object;
457   GtkWidget *widget;
458   GtkWidget *menu_shell;
459
460   object = g_value_get_object (param_values + 0);
461   g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
462
463   widget = GTK_WIDGET (object);
464
465   if (!GTK_IS_MENU_ITEM (widget))
466     return TRUE;
467
468   if (subsequent_focus_widget == widget)
469     subsequent_focus_widget = NULL;
470
471   menu_shell = gtk_widget_get_parent (widget);
472   if (GTK_IS_MENU_SHELL (menu_shell))
473     {
474       GtkWidget *parent_menu_shell;
475
476       parent_menu_shell = gtk_menu_shell_get_parent_shell (GTK_MENU_SHELL (menu_shell));
477       if (parent_menu_shell)
478         {
479           GtkWidget *active_menu_item;
480
481           active_menu_item = gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (parent_menu_shell));
482           if (active_menu_item)
483             {
484               gail_focus_notify_when_idle (active_menu_item);
485             }
486         }
487       else
488         {
489           if (!GTK_IS_MENU_BAR (menu_shell))
490             {
491               gail_focus_notify_when_idle (menu_shell);
492             }
493         }
494     }
495   was_deselect = TRUE;
496   return TRUE; 
497 }
498
499 static gboolean 
500 gail_switch_page_watcher (GSignalInvocationHint *ihint,
501                           guint                  n_param_values,
502                           const GValue          *param_values,
503                           gpointer               data)
504 {
505   GObject *object;
506   GtkWidget *widget;
507
508   object = g_value_get_object (param_values + 0);
509   g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
510
511   widget = GTK_WIDGET (object);
512
513   if (!GTK_IS_NOTEBOOK (widget))
514     return TRUE;
515
516   if (gtk_notebook_get_current_page (GTK_NOTEBOOK (widget)) == -1)
517     return TRUE;
518
519   gail_focus_notify_when_idle (widget);
520   return TRUE;
521 }
522
523 static gboolean
524 gail_focus_idle_handler (gpointer data)
525 {
526   focus_notify_handler = 0;
527   /*
528    * The widget which was to receive focus may have been removed
529    */
530   if (!next_focus_widget)
531     {
532       if (next_focus_widget != data)
533         return FALSE;
534     }
535   else
536     {
537       void *vp_next_focus_widget = &next_focus_widget;
538       g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
539       next_focus_widget = NULL;
540     }
541     
542   gail_focus_notify (data);
543
544   return FALSE;
545 }
546
547 static void
548 gail_focus_notify (GtkWidget *widget)
549 {
550   AtkObject *atk_obj;
551   gboolean transient;
552
553   if (widget != focus_widget)
554     {
555       if (focus_widget)
556         {
557           void *vp_focus_widget = &focus_widget;
558           g_object_remove_weak_pointer (G_OBJECT (focus_widget), vp_focus_widget);
559         }
560       focus_widget = widget;
561       if (focus_widget)
562         {
563           void *vp_focus_widget = &focus_widget;
564           g_object_add_weak_pointer (G_OBJECT (focus_widget), vp_focus_widget);
565           /*
566            * The UI may not have been updated yet; e.g. in gtkhtml2
567            * html_view_layout() is called in a idle handler
568            */
569           if (focus_widget == focus_before_menu)
570             {
571               void *vp_focus_before_menu = &focus_before_menu;
572               g_object_remove_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
573               focus_before_menu = NULL;
574             }
575         }
576       gail_focus_notify_when_idle (focus_widget);
577     }
578   else
579     {
580       if (focus_widget)
581         atk_obj  = gail_get_accessible_for_widget (focus_widget, &transient);
582       else
583         atk_obj = NULL;
584       /*
585        * Do not report focus on redundant object
586        */
587       if (atk_obj && 
588           (atk_object_get_role(atk_obj) != ATK_ROLE_REDUNDANT_OBJECT))
589           atk_focus_tracker_notify (atk_obj);
590       if (atk_obj && transient)
591         g_object_unref (atk_obj);
592       if (subsequent_focus_widget)
593         {
594           GtkWidget *tmp_widget = subsequent_focus_widget;
595           subsequent_focus_widget = NULL;
596           gail_focus_notify_when_idle (tmp_widget);
597         }
598     }
599 }
600
601 static void
602 gail_focus_notify_when_idle (GtkWidget *widget)
603 {
604   if (focus_notify_handler)
605     {
606       if (widget)
607         {
608           /*
609            * Ignore focus request when menu item is going to be focused.
610            * See bug #124232.
611            */
612           if (GTK_IS_MENU_ITEM (next_focus_widget) && !GTK_IS_MENU_ITEM (widget))
613             return;
614
615           if (next_focus_widget)
616             {
617               if (GTK_IS_MENU_ITEM (next_focus_widget) && GTK_IS_MENU_ITEM (widget))
618                 {
619                   if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (next_focus_widget)) == gtk_widget_get_parent (widget))
620                     {
621                       if (subsequent_focus_widget)
622                         g_assert_not_reached ();
623                       subsequent_focus_widget = widget;
624                       return;
625                     } 
626                 }
627             }
628           g_source_remove (focus_notify_handler);
629           if (next_focus_widget)
630             {
631               void *vp_next_focus_widget = &next_focus_widget;
632               g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
633               next_focus_widget = NULL;
634             }
635         }
636       else
637         /*
638          * Ignore if focus is being set to NULL and we are waiting to set focus
639          */
640         return;
641     }
642
643   if (widget)
644     {
645       void *vp_next_focus_widget = &next_focus_widget;
646       next_focus_widget = widget;
647       g_object_add_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
648     }
649   else
650     {
651       /*
652        * We are about to report focus as NULL so remove the weak pointer
653        * for the widget we were waiting to report focus on.
654        */ 
655       if (next_focus_widget)
656         {
657           void *vp_next_focus_widget = &next_focus_widget;
658           g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
659           next_focus_widget = NULL;
660         }
661     }
662
663   focus_notify_handler = gdk_threads_add_idle (gail_focus_idle_handler, widget);
664 }
665
666 static gboolean
667 gail_deactivate_watcher (GSignalInvocationHint *ihint,
668                          guint                  n_param_values,
669                          const GValue          *param_values,
670                          gpointer               data)
671 {
672   GObject *object;
673   GtkWidget *widget;
674   GtkMenuShell *shell;
675   GtkWidget *focus = NULL;
676
677   object = g_value_get_object (param_values + 0);
678   g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
679   widget = GTK_WIDGET (object);
680
681   g_return_val_if_fail (GTK_IS_MENU_SHELL(widget), TRUE);
682   shell = GTK_MENU_SHELL(widget);
683   if (! gtk_menu_shell_get_parent_shell (shell))
684     focus = focus_before_menu;
685       
686   /*
687    * If we are waiting to report focus on a menubar or a menu item
688    * because of a previous deselect, cancel it.
689    */
690   if (was_deselect &&
691       focus_notify_handler &&
692       next_focus_widget &&
693       (GTK_IS_MENU_BAR (next_focus_widget) ||
694        GTK_IS_MENU_ITEM (next_focus_widget)))
695     {
696       void *vp_next_focus_widget = &next_focus_widget;
697       g_source_remove (focus_notify_handler);
698       g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
699       next_focus_widget = NULL;
700       focus_notify_handler = 0;
701       was_deselect = FALSE;
702     }
703   gail_focus_notify_when_idle (focus);
704
705   return TRUE; 
706 }
707
708 static void
709 gail_focus_tracker_init (void)
710 {
711   static gboolean  emission_hooks_added = FALSE;
712
713   if (!emission_hooks_added)
714     {
715       /*
716        * We cannot be sure that the classes exist so we make sure that they do.
717        */
718       g_type_class_ref (GTK_TYPE_WIDGET);
719       g_type_class_ref (GTK_TYPE_MENU_ITEM);
720       g_type_class_ref (GTK_TYPE_MENU_SHELL);
721       g_type_class_ref (GTK_TYPE_NOTEBOOK);
722
723       /*
724        * We listen for event_after signal and then check that the
725        * event was a focus in event so we get called after the event.
726        */
727       g_signal_add_emission_hook (
728              g_signal_lookup ("event-after", GTK_TYPE_WIDGET), 0,
729              gail_focus_watcher, NULL, (GDestroyNotify) NULL);
730       /*
731        * A "select" signal is emitted when arrow key is used to
732        * move to a list item in the popup window of a GtkCombo or
733        * a menu item in a menu.
734        */
735       g_signal_add_emission_hook (
736              g_signal_lookup ("select", GTK_TYPE_MENU_ITEM), 0,
737              gail_select_watcher, NULL, (GDestroyNotify) NULL);
738
739       /*
740        * A "deselect" signal is emitted when arrow key is used to
741        * move from a menu item in a menu to the parent menu.
742        */
743       g_signal_add_emission_hook (
744              g_signal_lookup ("deselect", GTK_TYPE_MENU_ITEM), 0,
745              gail_deselect_watcher, NULL, (GDestroyNotify) NULL);
746
747       /*
748        * We listen for deactivate signals on menushells to determine
749        * when the "focus" has left the menus.
750        */
751       g_signal_add_emission_hook (
752              g_signal_lookup ("deactivate", GTK_TYPE_MENU_SHELL), 0,
753              gail_deactivate_watcher, NULL, (GDestroyNotify) NULL);
754
755       /*
756        * We listen for "switch-page" signal on a GtkNotebook to notify
757        * when page has changed because of clicking on a notebook tab.
758        */
759       g_signal_add_emission_hook (
760              g_signal_lookup ("switch-page", GTK_TYPE_NOTEBOOK), 0,
761              gail_switch_page_watcher, NULL, (GDestroyNotify) NULL);
762       emission_hooks_added = TRUE;
763     }
764 }
765
766 static void
767 gail_focus_object_destroyed (gpointer data)
768 {
769   GObject *obj;
770
771   obj = G_OBJECT (data);
772   g_object_set_qdata (obj, quark_focus_object, NULL);
773   g_object_unref (obj); 
774 }
775
776 static void
777 gail_focus_tracker (AtkObject *focus_object)
778 {
779   /*
780    * Do not report focus on redundant object
781    */
782   if (focus_object && 
783       (atk_object_get_role(focus_object) != ATK_ROLE_REDUNDANT_OBJECT))
784     {
785       AtkObject *old_focus_object;
786
787       if (!GTK_IS_ACCESSIBLE (focus_object))
788         {
789           AtkObject *parent;
790
791           parent = focus_object;
792           while (1)
793             {
794               parent = atk_object_get_parent (parent);
795               if (parent == NULL)
796                 break;
797               if (GTK_IS_ACCESSIBLE (parent))
798                 break;
799             }
800
801           if (parent)
802             {
803               gail_set_focus_object (focus_object, parent);
804             }
805         }
806       else
807         {
808           old_focus_object = g_object_get_qdata (G_OBJECT (focus_object), quark_focus_object);
809           if (old_focus_object)
810             {
811               g_object_weak_unref (G_OBJECT (old_focus_object),
812                                    (GWeakNotify) gail_focus_object_destroyed,
813                                    focus_object);
814               g_object_set_qdata (G_OBJECT (focus_object), quark_focus_object, NULL);
815               g_object_unref (G_OBJECT (focus_object));
816             }
817         }
818     }
819 }
820
821 static void 
822 gail_set_focus_widget (GtkWidget *focus_widget,
823                        GtkWidget *widget)
824 {
825   AtkObject *focus_obj;
826   AtkObject *obj;
827
828   focus_obj = gtk_widget_get_accessible (focus_widget);
829   obj = gtk_widget_get_accessible (widget);
830   gail_set_focus_object (focus_obj, obj);
831 }
832
833 static void 
834 gail_set_focus_object (AtkObject *focus_obj,
835                        AtkObject *obj)
836 {
837   AtkObject *old_focus_obj;
838
839   old_focus_obj = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
840   if (old_focus_obj != obj)
841     {
842       if (old_focus_obj)
843         g_object_weak_unref (G_OBJECT (old_focus_obj),
844                              (GWeakNotify) gail_focus_object_destroyed,
845                              obj);
846       else
847         /*
848          * We call g_object_ref as if obj is destroyed 
849          * while the weak reference exists then destroying the 
850          * focus_obj would cause gail_focus_object_destroyed to be 
851          * called when obj is not a valid GObject.
852          */
853         g_object_ref (obj);
854
855       g_object_weak_ref (G_OBJECT (focus_obj),
856                          (GWeakNotify) gail_focus_object_destroyed,
857                          obj);
858       g_object_set_qdata (G_OBJECT (obj), quark_focus_object, focus_obj);
859     }
860 }
861
862 static int gail_initialized = FALSE;
863
864 void
865 gail_accessibility_module_init (void)
866 {
867   const char *env_a_t_support;
868   gboolean a_t_support = FALSE;
869
870   if (gail_initialized)
871     {
872       return;
873     }
874   gail_initialized = TRUE;
875   quark_focus_object = g_quark_from_static_string ("gail-focus-object");
876
877   env_a_t_support = g_getenv (GNOME_ACCESSIBILITY_ENV);
878
879   if (env_a_t_support)
880     a_t_support = atoi (env_a_t_support);
881   if (a_t_support)
882     fprintf (stderr, "GTK Accessibility Module initialized\n");
883
884   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_WIDGET, gail_widget);
885   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_CONTAINER, gail_container);
886   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_BUTTON, gail_button);
887   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_LINK_BUTTON, gail_link_button);
888   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_MENU_ITEM, gail_menu_item);
889   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_TOGGLE_BUTTON, gail_toggle_button);
890   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_IMAGE, gail_image);
891   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_COMBO_BOX, gail_combo_box);
892   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_MENU_BAR, gail_menu_shell);
893   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_MENU, gail_menu);
894   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_WINDOW, gail_window);
895   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_RANGE, gail_range);
896   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_SCALE_BUTTON, gail_scale_button);
897   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_STATUSBAR, gail_statusbar);
898   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_NOTEBOOK, gail_notebook);
899   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_CALENDAR, gail_calendar);
900   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_PROGRESS_BAR, gail_progress_bar);
901   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_TREE_VIEW, gail_tree_view);
902   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_FRAME, gail_frame);
903   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_CELL_RENDERER_TEXT, gail_text_cell);
904   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_CELL_RENDERER_TOGGLE, gail_boolean_cell);
905   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_CELL_RENDERER_PIXBUF, gail_image_cell);
906   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_CELL_RENDERER, gail_renderer_cell);
907   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_RADIO_BUTTON, gail_radio_button);
908   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_ARROW, gail_arrow);
909   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_SEPARATOR, gail_separator);
910   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_BOX, gail_box);
911   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_SCROLLED_WINDOW, gail_scrolled_window);
912   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_PANED, gail_paned);
913   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_SCROLLBAR, gail_scrollbar);
914   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_CHECK_MENU_ITEM, gail_check_menu_item);
915   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_RADIO_MENU_ITEM, gail_radio_menu_item);
916   GAIL_WIDGET_SET_FACTORY (GTK_TYPE_EXPANDER, gail_expander);
917
918   atk_focus_tracker_init (gail_focus_tracker_init);
919   focus_tracker_id = atk_add_focus_tracker (gail_focus_tracker);
920
921   /* Initialize the GailUtility class */
922   g_type_class_unref (g_type_class_ref (GAIL_TYPE_UTIL));
923   g_type_class_unref (g_type_class_ref (GAIL_TYPE_MISC));
924 }