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