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