1 /* GTK+ - accessibility implementations
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, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 
20 #include "gtkaccessibility.h"
21 #include "gtkaccessibilityutil.h"
22 #include "gtkaccessibilitymisc.h"
23 
24 #include "gtkwindowaccessible.h"
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 
29 #include <gdk/gdk.h>
30 #include <gtk/gtkcombobox.h>
31 #include <gtk/gtkentry.h>
32 #include <gtk/gtknotebook.h>
33 #include <gtk/gtkmenuitem.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenubar.h>
36 #include <gtk/gtksocket.h>
37 #include <gtk/gtktogglebutton.h>
38 #include <gtk/gtkaccessible.h>
39 
40 #ifdef HAVE_ATK_BRIDGE
41 #include <atk-bridge.h>
42 #endif
43 
44 static gboolean gail_focus_watcher      (GSignalInvocationHint *ihint,
45                                          guint                  n_param_values,
46                                          const GValue          *param_values,
47                                          gpointer               data);
48 static gboolean gail_select_watcher     (GSignalInvocationHint *ihint,
49                                          guint                  n_param_values,
50                                          const GValue          *param_values,
51                                          gpointer               data);
52 static gboolean gail_deselect_watcher   (GSignalInvocationHint *ihint,
53                                          guint                  n_param_values,
54                                          const GValue          *param_values,
55                                          gpointer               data);
56 static gboolean gail_switch_page_watcher(GSignalInvocationHint *ihint,
57                                          guint                  n_param_values,
58                                          const GValue          *param_values,
59                                          gpointer               data);
60 static void     gail_finish_select       (GtkWidget            *widget);
61 static void     gail_map_cb              (GtkWidget            *widget);
62 static void     gail_map_submenu_cb      (GtkWidget            *widget);
63 static gint     gail_focus_idle_handler  (gpointer             data);
64 static void     gail_focus_notify        (GtkWidget            *widget);
65 static void     gail_focus_notify_when_idle (GtkWidget            *widget);
66 
67 static void     gail_focus_tracker_init (void);
68 static void     gail_focus_object_destroyed (gpointer data);
69 static void     gail_focus_tracker (AtkObject *object);
70 static void     gail_set_focus_widget (GtkWidget *focus_widget,
71                                        GtkWidget *widget);
72 static void     gail_set_focus_object (AtkObject *focus_obj,
73                                        AtkObject *obj);
74 
75 GtkWidget* _focus_widget = NULL;
76 static GtkWidget* next_focus_widget = NULL;
77 static gboolean was_deselect = FALSE;
78 static GtkWidget* subsequent_focus_widget = NULL;
79 static GtkWidget* focus_before_menu = NULL;
80 static guint focus_notify_handler = 0;
81 static guint focus_tracker_id = 0;
82 static GQuark quark_focus_object = 0;
83 static int initialized = FALSE;
84 
85 static AtkObject*
get_accessible_for_widget(GtkWidget * widget,gboolean * transient)86 get_accessible_for_widget (GtkWidget *widget,
87                            gboolean  *transient)
88 {
89   AtkObject *obj = NULL;
90 
91   *transient = FALSE;
92   if (!widget)
93     return NULL;
94 
95   if (GTK_IS_ENTRY (widget))
96     ;
97   else if (GTK_IS_NOTEBOOK (widget))
98     {
99       GtkNotebook *notebook;
100       gint page_num = -1;
101 
102       notebook = GTK_NOTEBOOK (widget);
103       page_num = gtk_notebook_get_current_page (notebook);
104       if (page_num != -1)
105         {
106           obj = gtk_widget_get_accessible (widget);
107           obj = atk_object_ref_accessible_child (obj, page_num);
108           g_object_unref (obj);
109         }
110     }
111   else if (GTK_IS_TOGGLE_BUTTON (widget))
112     {
113       GtkWidget *other_widget = gtk_widget_get_parent (widget);
114       if (GTK_IS_COMBO_BOX (other_widget))
115         {
116           gail_set_focus_widget (other_widget, widget);
117           widget = other_widget;
118         }
119     }
120   if (obj == NULL)
121     {
122       AtkObject *focus_object;
123 
124       obj = gtk_widget_get_accessible (widget);
125       focus_object = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
126       /*
127        * We check whether the object for this focus_object has been deleted.
128        * This can happen when navigating to an empty directory in nautilus.
129        * See bug #141907.
130        */
131       if (ATK_IS_GOBJECT_ACCESSIBLE (focus_object))
132         {
133           if (!atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (focus_object)))
134             focus_object = NULL;
135         }
136       if (focus_object)
137         obj = focus_object;
138     }
139 
140   return obj;
141 }
142 
143 static gboolean
gail_focus_watcher(GSignalInvocationHint * ihint,guint n_param_values,const GValue * param_values,gpointer data)144 gail_focus_watcher (GSignalInvocationHint *ihint,
145                     guint                  n_param_values,
146                     const GValue          *param_values,
147                     gpointer               data)
148 {
149   GObject *object;
150   GtkWidget *widget;
151   GdkEvent *event;
152 
153   object = g_value_get_object (param_values + 0);
154   g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
155 
156   event = g_value_get_boxed (param_values + 1);
157   widget = GTK_WIDGET (object);
158 
159   if (event->type == GDK_FOCUS_CHANGE)
160     {
161       if (event->focus_change.in)
162         {
163           if (GTK_IS_WINDOW (widget))
164             {
165               GtkWidget *focus_widget;
166               GtkWindow *window;
167               GtkWindowType type;
168 
169               window = GTK_WINDOW (widget);
170               focus_widget = gtk_window_get_focus (window);
171               g_object_get (window, "type", &type, NULL);
172 
173               if (focus_widget)
174                 {
175                   /*
176                    * If we already have a potential focus widget set this
177                    * windows's focus widget to focus_before_menu so that
178                    * it will be reported when menu item is unset.
179                    */
180                   if (next_focus_widget)
181                     {
182                       if (GTK_IS_MENU_ITEM (next_focus_widget) &&
183                           !focus_before_menu)
184                         {
185                           void *vp_focus_before_menu = &focus_before_menu;
186                           focus_before_menu = focus_widget;
187                           g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
188                         }
189 
190                       return TRUE;
191                     }
192                   widget = focus_widget;
193                 }
194               else if (type == GTK_WINDOW_POPUP)
195                 {
196 	          if (GTK_IS_BIN (widget))
197 		    {
198 		      GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
199 
200 		      if (GTK_IS_WIDGET (child) && gtk_widget_has_grab (child))
201 			{
202 			  if (GTK_IS_MENU_SHELL (child))
203 			    {
204 			      if (gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (child)))
205 				{
206 				  /*
207 				   * We have a menu which has a menu item selected
208 				   * so we do not report focus on the menu.
209 				   */
210 				  return TRUE;
211 				}
212 			    }
213 			  widget = child;
214 			}
215 		    }
216 		  else /* popup window has no children; this edge case occurs in some custom code (OOo for instance) */
217 		    {
218 		      return TRUE;
219 		    }
220                 }
221 	      else /* Widget is a non-popup toplevel with no focus children;
222 		      don't emit for this case either, as it's useless */
223 		{
224 		  return TRUE;
225 		}
226             }
227         }
228       else
229         {
230           if (next_focus_widget)
231             {
232                GtkWidget *toplevel;
233 
234                toplevel = gtk_widget_get_toplevel (next_focus_widget);
235                if (toplevel == widget)
236                  next_focus_widget = NULL;
237             }
238           /* focus out */
239           widget = NULL;
240         }
241     }
242   else
243     {
244       if (event->type == GDK_MOTION_NOTIFY && gtk_widget_has_focus (widget))
245         {
246           if (widget == _focus_widget)
247             {
248               return TRUE;
249             }
250         }
251       else
252         {
253           return TRUE;
254         }
255     }
256 
257 #ifdef GDK_WINDOWING_X11
258   /*
259    * If the focus widget is a GtkSocket without a plug
260    * then ignore the focus notification as the embedded
261    * plug will report a focus notification.
262    */
263   if (GTK_IS_SOCKET (widget) &&
264       gtk_socket_get_plug_window (GTK_SOCKET (widget)) != NULL)
265     return TRUE;
266 #endif
267 
268   /*
269    * The widget may not yet be visible on the screen so we wait until it is.
270    */
271   gail_focus_notify_when_idle (widget);
272   return TRUE;
273 }
274 
275 static gboolean
gail_select_watcher(GSignalInvocationHint * ihint,guint n_param_values,const GValue * param_values,gpointer data)276 gail_select_watcher (GSignalInvocationHint *ihint,
277                      guint                  n_param_values,
278                      const GValue          *param_values,
279                      gpointer               data)
280 {
281   GObject *object;
282   GtkWidget *widget;
283 
284   object = g_value_get_object (param_values + 0);
285   g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
286 
287   widget = GTK_WIDGET (object);
288 
289   if (!gtk_widget_get_mapped (widget))
290     {
291       g_signal_connect (widget, "map",
292                         G_CALLBACK (gail_map_cb),
293                         NULL);
294     }
295   else
296     gail_finish_select (widget);
297 
298   return TRUE;
299 }
300 
301 static void
gail_finish_select(GtkWidget * widget)302 gail_finish_select (GtkWidget *widget)
303 {
304   if (GTK_IS_MENU_ITEM (widget))
305     {
306       GtkMenuItem* menu_item;
307       GtkWidget *submenu;
308 
309       menu_item = GTK_MENU_ITEM (widget);
310       submenu = gtk_menu_item_get_submenu (menu_item);
311       if (submenu &&
312           !gtk_widget_get_mapped (submenu))
313         {
314           /*
315            * If the submenu is not visble, wait until it is before
316            * reporting focus on the menu item.
317            */
318           gulong handler_id;
319 
320           handler_id = g_signal_handler_find (submenu,
321                                               G_SIGNAL_MATCH_FUNC,
322                                               g_signal_lookup ("map",
323                                                                GTK_TYPE_WINDOW),
324                                               0,
325                                               NULL,
326                                               (gpointer) gail_map_submenu_cb,
327                                               NULL);
328           if (!handler_id)
329             g_signal_connect (submenu, "map",
330                               G_CALLBACK (gail_map_submenu_cb),
331                               NULL);
332 
333           return;
334         }
335       /*
336        * If we are waiting to report focus on a menubar or a menu item
337        * because of a previous deselect, cancel it.
338        */
339       if (was_deselect &&
340           focus_notify_handler &&
341           next_focus_widget &&
342           (GTK_IS_MENU_BAR (next_focus_widget) ||
343            GTK_IS_MENU_ITEM (next_focus_widget)))
344         {
345           void *vp_next_focus_widget = &next_focus_widget;
346           g_source_remove (focus_notify_handler);
347           g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
348 	  next_focus_widget = NULL;
349           focus_notify_handler = 0;
350           was_deselect = FALSE;
351         }
352     }
353   /*
354    * If previously focused widget is not a GtkMenuItem or a GtkMenu,
355    * keep track of it so we can return to it after menubar is deactivated
356    */
357   if (_focus_widget &&
358       !GTK_IS_MENU_ITEM (_focus_widget) &&
359       !GTK_IS_MENU (_focus_widget))
360     {
361       void *vp_focus_before_menu = &focus_before_menu;
362       focus_before_menu = _focus_widget;
363       g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
364 
365     }
366   gail_focus_notify_when_idle (widget);
367 
368   return;
369 }
370 
371 static void
gail_map_cb(GtkWidget * widget)372 gail_map_cb (GtkWidget *widget)
373 {
374   gail_finish_select (widget);
375 }
376 
377 static void
gail_map_submenu_cb(GtkWidget * widget)378 gail_map_submenu_cb (GtkWidget *widget)
379 {
380   if (GTK_IS_MENU (widget))
381     {
382       GtkWidget *parent_menu_item;
383 
384       parent_menu_item = gtk_menu_get_attach_widget (GTK_MENU (widget));
385       if (parent_menu_item)
386         gail_finish_select (parent_menu_item);
387     }
388 }
389 
390 
391 static gboolean
gail_deselect_watcher(GSignalInvocationHint * ihint,guint n_param_values,const GValue * param_values,gpointer data)392 gail_deselect_watcher (GSignalInvocationHint *ihint,
393                        guint                  n_param_values,
394                        const GValue          *param_values,
395                        gpointer               data)
396 {
397   GObject *object;
398   GtkWidget *widget;
399   GtkWidget *menu_shell;
400 
401   object = g_value_get_object (param_values + 0);
402   g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
403 
404   widget = GTK_WIDGET (object);
405 
406   if (!GTK_IS_MENU_ITEM (widget))
407     return TRUE;
408 
409   if (subsequent_focus_widget == widget)
410     subsequent_focus_widget = NULL;
411 
412   menu_shell = gtk_widget_get_parent (widget);
413   if (GTK_IS_MENU_SHELL (menu_shell))
414     {
415       GtkWidget *parent_menu_shell;
416 
417       parent_menu_shell = gtk_menu_shell_get_parent_shell (GTK_MENU_SHELL (menu_shell));
418       if (parent_menu_shell)
419         {
420           GtkWidget *active_menu_item;
421 
422           active_menu_item = gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (parent_menu_shell));
423           if (active_menu_item)
424             {
425               gail_focus_notify_when_idle (active_menu_item);
426             }
427         }
428       else
429         {
430           if (!GTK_IS_MENU_BAR (menu_shell))
431             {
432               gail_focus_notify_when_idle (menu_shell);
433             }
434         }
435     }
436   was_deselect = TRUE;
437   return TRUE;
438 }
439 
440 static gboolean
gail_switch_page_watcher(GSignalInvocationHint * ihint,guint n_param_values,const GValue * param_values,gpointer data)441 gail_switch_page_watcher (GSignalInvocationHint *ihint,
442                           guint                  n_param_values,
443                           const GValue          *param_values,
444                           gpointer               data)
445 {
446   GObject *object;
447   GtkWidget *widget;
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_NOTEBOOK (widget))
455     return TRUE;
456 
457   if (gtk_notebook_get_current_page (GTK_NOTEBOOK (widget)) == -1)
458     return TRUE;
459 
460   gail_focus_notify_when_idle (widget);
461   return TRUE;
462 }
463 
464 static gboolean
gail_focus_idle_handler(gpointer data)465 gail_focus_idle_handler (gpointer data)
466 {
467   focus_notify_handler = 0;
468   /*
469    * The widget which was to receive focus may have been removed
470    */
471   if (!next_focus_widget)
472     {
473       if (next_focus_widget != data)
474         return FALSE;
475     }
476   else
477     {
478       void *vp_next_focus_widget = &next_focus_widget;
479       g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
480       next_focus_widget = NULL;
481     }
482 
483   gail_focus_notify (data);
484 
485   return FALSE;
486 }
487 
488 static void
gail_focus_notify(GtkWidget * widget)489 gail_focus_notify (GtkWidget *widget)
490 {
491   AtkObject *atk_obj;
492   gboolean transient;
493 
494   if (widget != _focus_widget)
495     {
496       if (_focus_widget)
497         {
498           void *vp_focus_widget = &_focus_widget;
499           g_object_remove_weak_pointer (G_OBJECT (_focus_widget), vp_focus_widget);
500         }
501       _focus_widget = widget;
502       if (_focus_widget)
503         {
504           void *vp_focus_widget = &_focus_widget;
505           g_object_add_weak_pointer (G_OBJECT (_focus_widget), vp_focus_widget);
506           /*
507            * The UI may not have been updated yet; e.g. in gtkhtml2
508            * html_view_layout() is called in a idle handler
509            */
510           if (_focus_widget == focus_before_menu)
511             {
512               void *vp_focus_before_menu = &focus_before_menu;
513               g_object_remove_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
514               focus_before_menu = NULL;
515             }
516         }
517       gail_focus_notify_when_idle (_focus_widget);
518     }
519   else
520     {
521       if (_focus_widget)
522         atk_obj  = get_accessible_for_widget (_focus_widget, &transient);
523       else
524         atk_obj = NULL;
525       /*
526        * Do not report focus on redundant object
527        */
528       G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
529       if (atk_obj &&
530 	  (atk_object_get_role(atk_obj) != ATK_ROLE_REDUNDANT_OBJECT))
531 	  atk_focus_tracker_notify (atk_obj);
532       G_GNUC_END_IGNORE_DEPRECATIONS;
533       if (atk_obj && transient)
534         g_object_unref (atk_obj);
535       if (subsequent_focus_widget)
536         {
537           GtkWidget *tmp_widget = subsequent_focus_widget;
538           subsequent_focus_widget = NULL;
539           gail_focus_notify_when_idle (tmp_widget);
540         }
541     }
542 }
543 
544 static void
gail_focus_notify_when_idle(GtkWidget * widget)545 gail_focus_notify_when_idle (GtkWidget *widget)
546 {
547   if (focus_notify_handler)
548     {
549       if (widget)
550         {
551           /*
552            * Ignore focus request when menu item is going to be focused.
553            * See bug #124232.
554            */
555           if (GTK_IS_MENU_ITEM (next_focus_widget) && !GTK_IS_MENU_ITEM (widget))
556             return;
557 
558           if (next_focus_widget)
559             {
560               if (GTK_IS_MENU_ITEM (next_focus_widget) && GTK_IS_MENU_ITEM (widget))
561                 {
562                   if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (next_focus_widget)) == gtk_widget_get_parent (widget))
563                     {
564                       if (subsequent_focus_widget)
565                         g_assert_not_reached ();
566                       subsequent_focus_widget = widget;
567                       return;
568                     }
569                 }
570             }
571           g_source_remove (focus_notify_handler);
572           if (next_focus_widget)
573 	    {
574 	      void *vp_next_focus_widget = &next_focus_widget;
575 	      g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
576 	      next_focus_widget = NULL;
577 	    }
578         }
579       else
580         /*
581          * Ignore if focus is being set to NULL and we are waiting to set focus
582          */
583         return;
584     }
585 
586   if (widget)
587     {
588       void *vp_next_focus_widget = &next_focus_widget;
589       next_focus_widget = widget;
590       g_object_add_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
591     }
592   else
593     {
594       /*
595        * We are about to report focus as NULL so remove the weak pointer
596        * for the widget we were waiting to report focus on.
597        */
598       if (next_focus_widget)
599         {
600           void *vp_next_focus_widget = &next_focus_widget;
601           g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
602           next_focus_widget = NULL;
603         }
604     }
605 
606   focus_notify_handler = gdk_threads_add_idle (gail_focus_idle_handler, widget);
607   g_source_set_name_by_id (focus_notify_handler, "[gtk+] gail_focus_idle_handler");
608 }
609 
610 static gboolean
gail_deactivate_watcher(GSignalInvocationHint * ihint,guint n_param_values,const GValue * param_values,gpointer data)611 gail_deactivate_watcher (GSignalInvocationHint *ihint,
612                          guint                  n_param_values,
613                          const GValue          *param_values,
614                          gpointer               data)
615 {
616   GObject *object;
617   GtkWidget *widget;
618   GtkMenuShell *shell;
619   GtkWidget *focus = NULL;
620 
621   object = g_value_get_object (param_values + 0);
622   g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
623   widget = GTK_WIDGET (object);
624 
625   g_return_val_if_fail (GTK_IS_MENU_SHELL(widget), TRUE);
626   shell = GTK_MENU_SHELL(widget);
627   if (! gtk_menu_shell_get_parent_shell (shell))
628     focus = focus_before_menu;
629 
630   /*
631    * If we are waiting to report focus on a menubar or a menu item
632    * because of a previous deselect, cancel it.
633    */
634   if (was_deselect &&
635       focus_notify_handler &&
636       next_focus_widget &&
637       (GTK_IS_MENU_BAR (next_focus_widget) ||
638        GTK_IS_MENU_ITEM (next_focus_widget)))
639     {
640       void *vp_next_focus_widget = &next_focus_widget;
641       g_source_remove (focus_notify_handler);
642       g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
643       next_focus_widget = NULL;
644       focus_notify_handler = 0;
645       was_deselect = FALSE;
646     }
647   gail_focus_notify_when_idle (focus);
648 
649   return TRUE;
650 }
651 
652 static void
gail_focus_tracker_init(void)653 gail_focus_tracker_init (void)
654 {
655   static gboolean  emission_hooks_added = FALSE;
656 
657   if (!emission_hooks_added)
658     {
659       /*
660        * We cannot be sure that the classes exist so we make sure that they do.
661        */
662       g_type_class_ref (GTK_TYPE_WIDGET);
663       g_type_class_ref (GTK_TYPE_MENU_ITEM);
664       g_type_class_ref (GTK_TYPE_MENU_SHELL);
665       g_type_class_ref (GTK_TYPE_NOTEBOOK);
666 
667       /*
668        * We listen for event_after signal and then check that the
669        * event was a focus in event so we get called after the event.
670        */
671       g_signal_add_emission_hook (
672              g_signal_lookup ("event-after", GTK_TYPE_WIDGET), 0,
673              gail_focus_watcher, NULL, (GDestroyNotify) NULL);
674       /*
675        * A "select" signal is emitted when arrow key is used to
676        * move to a list item in the popup window of a GtkCombo or
677        * a menu item in a menu.
678        */
679       g_signal_add_emission_hook (
680              g_signal_lookup ("select", GTK_TYPE_MENU_ITEM), 0,
681              gail_select_watcher, NULL, (GDestroyNotify) NULL);
682 
683       /*
684        * A "deselect" signal is emitted when arrow key is used to
685        * move from a menu item in a menu to the parent menu.
686        */
687       g_signal_add_emission_hook (
688              g_signal_lookup ("deselect", GTK_TYPE_MENU_ITEM), 0,
689              gail_deselect_watcher, NULL, (GDestroyNotify) NULL);
690 
691       /*
692        * We listen for deactivate signals on menushells to determine
693        * when the "focus" has left the menus.
694        */
695       g_signal_add_emission_hook (
696              g_signal_lookup ("deactivate", GTK_TYPE_MENU_SHELL), 0,
697              gail_deactivate_watcher, NULL, (GDestroyNotify) NULL);
698 
699       /*
700        * We listen for "switch-page" signal on a GtkNotebook to notify
701        * when page has changed because of clicking on a notebook tab.
702        */
703       g_signal_add_emission_hook (
704              g_signal_lookup ("switch-page", GTK_TYPE_NOTEBOOK), 0,
705              gail_switch_page_watcher, NULL, (GDestroyNotify) NULL);
706       emission_hooks_added = TRUE;
707     }
708 }
709 
710 static void
gail_focus_object_destroyed(gpointer data)711 gail_focus_object_destroyed (gpointer data)
712 {
713   GObject *obj;
714 
715   obj = G_OBJECT (data);
716   g_object_set_qdata (obj, quark_focus_object, NULL);
717   g_object_unref (obj);
718 }
719 
720 static void
gail_focus_tracker(AtkObject * focus_object)721 gail_focus_tracker (AtkObject *focus_object)
722 {
723   /*
724    * Do not report focus on redundant object
725    */
726   if (focus_object &&
727       (atk_object_get_role(focus_object) != ATK_ROLE_REDUNDANT_OBJECT))
728     {
729       AtkObject *old_focus_object;
730 
731       if (!GTK_IS_ACCESSIBLE (focus_object))
732         {
733           AtkObject *parent;
734 
735           parent = focus_object;
736           while (1)
737             {
738               parent = atk_object_get_parent (parent);
739               if (parent == NULL)
740                 break;
741               if (GTK_IS_ACCESSIBLE (parent))
742                 break;
743             }
744 
745           if (parent)
746             {
747               gail_set_focus_object (focus_object, parent);
748             }
749         }
750       else
751         {
752           old_focus_object = g_object_get_qdata (G_OBJECT (focus_object), quark_focus_object);
753           if (old_focus_object)
754             {
755               g_object_weak_unref (G_OBJECT (old_focus_object),
756                                    (GWeakNotify) gail_focus_object_destroyed,
757                                    focus_object);
758               g_object_set_qdata (G_OBJECT (focus_object), quark_focus_object, NULL);
759               g_object_unref (G_OBJECT (focus_object));
760             }
761         }
762     }
763 }
764 
765 static void
gail_set_focus_widget(GtkWidget * focus_widget,GtkWidget * widget)766 gail_set_focus_widget (GtkWidget *focus_widget,
767                        GtkWidget *widget)
768 {
769   AtkObject *focus_obj;
770   AtkObject *obj;
771 
772   focus_obj = gtk_widget_get_accessible (focus_widget);
773   obj = gtk_widget_get_accessible (widget);
774   gail_set_focus_object (focus_obj, obj);
775 }
776 
777 static void
gail_set_focus_object(AtkObject * focus_obj,AtkObject * obj)778 gail_set_focus_object (AtkObject *focus_obj,
779                        AtkObject *obj)
780 {
781   AtkObject *old_focus_obj;
782 
783   old_focus_obj = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
784   if (old_focus_obj != obj)
785     {
786       if (old_focus_obj)
787         g_object_weak_unref (G_OBJECT (old_focus_obj),
788                              (GWeakNotify) gail_focus_object_destroyed,
789                              obj);
790       else
791         /*
792          * We call g_object_ref as if obj is destroyed
793          * while the weak reference exists then destroying the
794          * focus_obj would cause gail_focus_object_destroyed to be
795          * called when obj is not a valid GObject.
796          */
797         g_object_ref (obj);
798 
799       g_object_weak_ref (G_OBJECT (focus_obj),
800                          (GWeakNotify) gail_focus_object_destroyed,
801                          obj);
802       g_object_set_qdata (G_OBJECT (obj), quark_focus_object, focus_obj);
803     }
804 }
805 
806 static gboolean
state_event_watcher(GSignalInvocationHint * hint,guint n_param_values,const GValue * param_values,gpointer data)807 state_event_watcher (GSignalInvocationHint *hint,
808                      guint                  n_param_values,
809                      const GValue          *param_values,
810                      gpointer               data)
811 {
812   GObject *object;
813   GtkWidget *widget;
814   AtkObject *atk_obj;
815   AtkObject *parent;
816   GdkEventWindowState *event;
817   gchar *signal_name;
818 
819   object = g_value_get_object (param_values + 0);
820   if (!GTK_IS_WINDOW (object))
821     return FALSE;
822 
823   event = g_value_get_boxed (param_values + 1);
824   if (event->type == GDK_WINDOW_STATE)
825     return FALSE;
826   widget = GTK_WIDGET (object);
827 
828   if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
829     signal_name = "maximize";
830   else if (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED)
831     signal_name = "minimize";
832   else if (event->new_window_state == 0)
833     signal_name = "restore";
834   else
835     return TRUE;
836 
837   atk_obj = gtk_widget_get_accessible (widget);
838   if (GTK_IS_WINDOW_ACCESSIBLE (atk_obj))
839     {
840       parent = atk_object_get_parent (atk_obj);
841       if (parent == atk_get_root ())
842         g_signal_emit_by_name (atk_obj, signal_name);
843 
844       return TRUE;
845     }
846 
847   return FALSE;
848 }
849 
850 static gboolean
configure_event_watcher(GSignalInvocationHint * hint,guint n_param_values,const GValue * param_values,gpointer data)851 configure_event_watcher (GSignalInvocationHint *hint,
852                          guint                  n_param_values,
853                          const GValue          *param_values,
854                          gpointer               data)
855 {
856   GtkAllocation allocation;
857   GObject *object;
858   GtkWidget *widget;
859   AtkObject *atk_obj;
860   AtkObject *parent;
861   GdkEvent *event;
862   gchar *signal_name;
863 
864   object = g_value_get_object (param_values + 0);
865   if (!GTK_IS_WINDOW (object))
866     return FALSE;
867 
868   event = g_value_get_boxed (param_values + 1);
869   if (event->type != GDK_CONFIGURE)
870     return FALSE;
871   widget = GTK_WIDGET (object);
872   gtk_widget_get_allocation (widget, &allocation);
873   if (allocation.x == ((GdkEventConfigure *)event)->x &&
874       allocation.y == ((GdkEventConfigure *)event)->y &&
875       allocation.width == ((GdkEventConfigure *)event)->width &&
876       allocation.height == ((GdkEventConfigure *)event)->height)
877     return TRUE;
878 
879   if (allocation.width != ((GdkEventConfigure *)event)->width ||
880       allocation.height != ((GdkEventConfigure *)event)->height)
881     signal_name = "resize";
882   else
883     signal_name = "move";
884 
885   atk_obj = gtk_widget_get_accessible (widget);
886   if (GTK_IS_WINDOW_ACCESSIBLE (atk_obj))
887     {
888       parent = atk_object_get_parent (atk_obj);
889       if (parent == atk_get_root ())
890         g_signal_emit_by_name (atk_obj, signal_name);
891 
892       return TRUE;
893     }
894 
895   return FALSE;
896 }
897 
898 static gboolean
window_focus(GtkWidget * widget,GdkEventFocus * event)899 window_focus (GtkWidget     *widget,
900               GdkEventFocus *event)
901 {
902   AtkObject *atk_obj;
903 
904   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
905 
906   atk_obj = gtk_widget_get_accessible (widget);
907   g_signal_emit_by_name (atk_obj, event->in ? "activate" : "deactivate");
908 
909   return FALSE;
910 }
911 
912 static void
window_added(AtkObject * atk_obj,guint index,AtkObject * child)913 window_added (AtkObject *atk_obj,
914               guint      index,
915               AtkObject *child)
916 {
917   GtkWidget *widget;
918 
919   if (!GTK_IS_WINDOW_ACCESSIBLE (child))
920     return;
921 
922   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (child));
923   if (!widget)
924     return;
925 
926   g_signal_connect (widget, "focus-in-event", (GCallback) window_focus, NULL);
927   g_signal_connect (widget, "focus-out-event", (GCallback) window_focus, NULL);
928   g_signal_emit_by_name (child, "create");
929 }
930 
931 static void
window_removed(AtkObject * atk_obj,guint index,AtkObject * child)932 window_removed (AtkObject *atk_obj,
933                 guint      index,
934                 AtkObject *child)
935 {
936   GtkWidget *widget;
937   GtkWindow *window;
938 
939   if (!GTK_IS_WINDOW_ACCESSIBLE (child))
940     return;
941 
942   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (child));
943   if (!widget)
944     return;
945 
946   window = GTK_WINDOW (widget);
947   /*
948    * Deactivate window if it is still focused and we are removing it. This
949    * can happen when a dialog displayed by gok is removed.
950    */
951   if (gtk_window_is_active (window) && gtk_window_has_toplevel_focus (window))
952     g_signal_emit_by_name (child, "deactivate");
953 
954   g_signal_handlers_disconnect_by_func (widget, (gpointer) window_focus, NULL);
955   g_signal_emit_by_name (child, "destroy");
956 }
957 
958 static void
do_window_event_initialization(void)959 do_window_event_initialization (void)
960 {
961   AtkObject *root;
962 
963   g_type_class_ref (GTK_TYPE_WINDOW_ACCESSIBLE);
964   g_signal_add_emission_hook (g_signal_lookup ("window-state-event", GTK_TYPE_WIDGET),
965                               0, state_event_watcher, NULL, (GDestroyNotify) NULL);
966   g_signal_add_emission_hook (g_signal_lookup ("configure-event", GTK_TYPE_WIDGET),
967                               0, configure_event_watcher, NULL, (GDestroyNotify) NULL);
968 
969   root = atk_get_root ();
970   g_signal_connect (root, "children-changed::add", (GCallback) window_added, NULL);
971   g_signal_connect (root, "children-changed::remove", (GCallback) window_removed, NULL);
972 }
973 
974 void
_gtk_accessibility_init(void)975 _gtk_accessibility_init (void)
976 {
977   if (initialized)
978     return;
979 
980   initialized = TRUE;
981   quark_focus_object = g_quark_from_static_string ("gail-focus-object");
982 
983   G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
984   atk_focus_tracker_init (gail_focus_tracker_init);
985   focus_tracker_id = atk_add_focus_tracker (gail_focus_tracker);
986   G_GNUC_END_IGNORE_DEPRECATIONS;
987 
988   _gtk_accessibility_override_atk_util ();
989   do_window_event_initialization ();
990 
991 #ifdef HAVE_ATK_BRIDGE
992   atk_bridge_adaptor_init (NULL, NULL);
993 #endif
994 
995   atk_misc_instance = g_object_new (GTK_TYPE_MISC_IMPL, NULL);
996 }
997