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