]> Pileus Git - ~andy/gtk/blob - gtk/a11y/gailutil.c
Change FSF Address
[~andy/gtk] / gtk / a11y / gailutil.c
1 /* GAIL - The GNOME Accessibility Implementation Library
2  * Copyright 2011, F123 Consulting & Mais Diferenças
3  * Copyright 2001, 2002, 2003 Sun Microsystems Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include "config.h"
20
21 #include <stdlib.h>
22 #include <gtk/gtk.h>
23 #include "gailutil.h"
24 #include "gtktoplevelaccessible.h"
25 #include "gtkwindowaccessible.h"
26
27
28 static GHashTable *listener_list = NULL;
29 static gint listener_idx = 1;
30 static GSList *key_listener_list = NULL;
31
32 typedef struct _GailUtilListenerInfo GailUtilListenerInfo;
33 typedef struct _GailKeyEventInfo GailKeyEventInfo;
34
35 struct _GailUtilListenerInfo
36 {
37    gint key;
38    guint signal_id;
39    gulong hook_id;
40 };
41
42 struct _GailKeyEventInfo
43 {
44   AtkKeyEventStruct *key_event;
45   gpointer func_data;
46 };
47
48 static guint
49 add_listener (GSignalEmissionHook  listener,
50               const gchar         *object_type,
51               const gchar         *signal_name,
52               const gchar         *hook_data)
53 {
54   GType type;
55   guint signal_id;
56   gint  rc = 0;
57
58   type = g_type_from_name (object_type);
59   if (type)
60     {
61       signal_id  = g_signal_lookup (signal_name, type);
62       if (signal_id > 0)
63         {
64           GailUtilListenerInfo *listener_info;
65
66           rc = listener_idx;
67
68           listener_info = g_new (GailUtilListenerInfo, 1);
69           listener_info->key = listener_idx;
70           listener_info->hook_id =
71                           g_signal_add_emission_hook (signal_id, 0, listener,
72                                                       g_strdup (hook_data),
73                                                       (GDestroyNotify) g_free);
74           listener_info->signal_id = signal_id;
75
76           g_hash_table_insert (listener_list, &(listener_info->key), listener_info);
77           listener_idx++;
78         }
79       else
80         {
81           g_warning ("Invalid signal type %s\n", signal_name);
82         }
83     }
84   else
85     {
86       g_warning ("Invalid object type %s\n", object_type);
87     }
88   return rc;
89 }
90
91 static gboolean
92 state_event_watcher (GSignalInvocationHint *hint,
93                      guint                  n_param_values,
94                      const GValue          *param_values,
95                      gpointer               data)
96 {
97   GObject *object;
98   GtkWidget *widget;
99   AtkObject *atk_obj;
100   AtkObject *parent;
101   GdkEventWindowState *event;
102   gchar *signal_name;
103
104   object = g_value_get_object (param_values + 0);
105   if (!GTK_IS_WINDOW (object))
106     return FALSE;
107
108   event = g_value_get_boxed (param_values + 1);
109   if (event->type == GDK_WINDOW_STATE)
110     return FALSE;
111   widget = GTK_WIDGET (object);
112
113   if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
114     signal_name = "maximize";
115   else if (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED)
116     signal_name = "minimize";
117   else if (event->new_window_state == 0)
118     signal_name = "restore";
119   else
120     return TRUE;
121
122   atk_obj = gtk_widget_get_accessible (widget);
123   if (GTK_IS_WINDOW_ACCESSIBLE (atk_obj))
124     {
125       parent = atk_object_get_parent (atk_obj);
126       if (parent == atk_get_root ())
127         g_signal_emit_by_name (atk_obj, signal_name);
128
129       return TRUE;
130     }
131
132   return FALSE;
133 }
134
135 static gboolean
136 configure_event_watcher (GSignalInvocationHint *hint,
137                          guint                  n_param_values,
138                          const GValue          *param_values,
139                          gpointer               data)
140 {
141   GtkAllocation allocation;
142   GObject *object;
143   GtkWidget *widget;
144   AtkObject *atk_obj;
145   AtkObject *parent;
146   GdkEvent *event;
147   gchar *signal_name;
148
149   object = g_value_get_object (param_values + 0);
150   if (!GTK_IS_WINDOW (object))
151     return FALSE;
152
153   event = g_value_get_boxed (param_values + 1);
154   if (event->type != GDK_CONFIGURE)
155     return FALSE;
156   widget = GTK_WIDGET (object);
157   gtk_widget_get_allocation (widget, &allocation);
158   if (allocation.x == ((GdkEventConfigure *)event)->x &&
159       allocation.y == ((GdkEventConfigure *)event)->y &&
160       allocation.width == ((GdkEventConfigure *)event)->width &&
161       allocation.height == ((GdkEventConfigure *)event)->height)
162     return TRUE;
163
164   if (allocation.width != ((GdkEventConfigure *)event)->width ||
165       allocation.height != ((GdkEventConfigure *)event)->height)
166     signal_name = "resize";
167   else
168     signal_name = "move";
169
170   atk_obj = gtk_widget_get_accessible (widget);
171   if (GTK_IS_WINDOW_ACCESSIBLE (atk_obj))
172     {
173       parent = atk_object_get_parent (atk_obj);
174       if (parent == atk_get_root ())
175         g_signal_emit_by_name (atk_obj, signal_name);
176
177       return TRUE;
178     }
179
180   return FALSE;
181 }
182
183 static gboolean
184 window_focus (GtkWidget     *widget,
185               GdkEventFocus *event)
186 {
187   AtkObject *atk_obj;
188
189   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
190
191   atk_obj = gtk_widget_get_accessible (widget);
192   g_signal_emit_by_name (atk_obj, event->in ? "activate" : "deactivate");
193
194   return FALSE;
195 }
196
197 static void
198 window_added (AtkObject *atk_obj,
199               guint      index,
200               AtkObject *child)
201 {
202   GtkWidget *widget;
203
204   if (!GTK_IS_WINDOW_ACCESSIBLE (child))
205     return;
206
207   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (child));
208   if (!widget)
209     return;
210
211   g_signal_connect (widget, "focus-in-event", (GCallback) window_focus, NULL);
212   g_signal_connect (widget, "focus-out-event", (GCallback) window_focus, NULL);
213   g_signal_emit_by_name (child, "create");
214 }
215
216
217 static void
218 window_removed (AtkObject *atk_obj,
219                 guint      index,
220                 AtkObject *child)
221 {
222   GtkWidget *widget;
223   GtkWindow *window;
224
225   if (!GTK_IS_WINDOW_ACCESSIBLE (child))
226     return;
227
228   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (child));
229   if (!widget)
230     return;
231
232   window = GTK_WINDOW (widget);
233   /*
234    * Deactivate window if it is still focused and we are removing it. This
235    * can happen when a dialog displayed by gok is removed.
236    */
237   if (gtk_window_is_active (window) &&
238       gtk_window_has_toplevel_focus (window))
239     g_signal_emit_by_name (child, "deactivate");
240
241   g_signal_handlers_disconnect_by_func (widget, (gpointer) window_focus, NULL);
242   g_signal_emit_by_name (child, "destroy");
243 }
244
245 static void
246 do_window_event_initialization (void)
247 {
248   AtkObject *root;
249
250   g_type_class_ref (GTK_TYPE_WINDOW_ACCESSIBLE);
251   g_signal_add_emission_hook (g_signal_lookup ("window-state-event", GTK_TYPE_WIDGET),
252                               0, state_event_watcher, NULL, (GDestroyNotify) NULL);
253   g_signal_add_emission_hook (g_signal_lookup ("configure-event", GTK_TYPE_WIDGET),
254                               0, configure_event_watcher, NULL, (GDestroyNotify) NULL);
255
256   root = atk_get_root ();
257   g_signal_connect (root, "children-changed::add",
258                     (GCallback) window_added, NULL);
259   g_signal_connect (root, "children-changed::remove",
260                     (GCallback) window_removed, NULL);
261 }
262 static guint
263 gail_util_add_global_event_listener (GSignalEmissionHook  listener,
264                                      const gchar         *event_type)
265 {
266   guint rc = 0;
267   gchar **split_string;
268
269   split_string = g_strsplit (event_type, ":", 3);
270
271   if (g_strv_length (split_string) == 3)
272     rc = add_listener (listener, split_string[1], split_string[2], event_type);
273
274   g_strfreev (split_string);
275
276   return rc;
277 }
278
279 static void
280 gail_util_remove_global_event_listener (guint remove_listener)
281 {
282   if (remove_listener > 0)
283   {
284     GailUtilListenerInfo *listener_info;
285     gint tmp_idx = remove_listener;
286
287     listener_info = (GailUtilListenerInfo *)
288       g_hash_table_lookup(listener_list, &tmp_idx);
289
290     if (listener_info != NULL)
291       {
292         /* Hook id of 0 and signal id of 0 are invalid */
293         if (listener_info->hook_id != 0 && listener_info->signal_id != 0)
294           {
295             /* Remove the emission hook */
296             g_signal_remove_emission_hook(listener_info->signal_id,
297               listener_info->hook_id);
298
299             /* Remove the element from the hash */
300             g_hash_table_remove(listener_list, &tmp_idx);
301           }
302         else
303           {
304             g_warning ("Invalid listener hook_id %ld or signal_id %d\n",
305                        listener_info->hook_id, listener_info->signal_id);
306           }
307       }
308     else
309       {
310         g_warning ("No listener with the specified listener id %d",
311                    remove_listener);
312       }
313   }
314   else
315   {
316     g_warning ("Invalid listener_id %d", remove_listener);
317   }
318 }
319
320 static AtkKeyEventStruct *
321 atk_key_event_from_gdk_event_key (GdkEventKey *key)
322 {
323   AtkKeyEventStruct *event = g_new0 (AtkKeyEventStruct, 1);
324   switch (key->type)
325     {
326     case GDK_KEY_PRESS:
327             event->type = ATK_KEY_EVENT_PRESS;
328             break;
329     case GDK_KEY_RELEASE:
330             event->type = ATK_KEY_EVENT_RELEASE;
331             break;
332     default:
333             g_assert_not_reached ();
334             return NULL;
335     }
336   event->state = key->state;
337   event->keyval = key->keyval;
338   event->length = key->length;
339   if (key->string && key->string [0] &&
340       (key->state & GDK_CONTROL_MASK ||
341        g_unichar_isgraph (g_utf8_get_char (key->string))))
342     {
343       event->string = key->string;
344     }
345   else if (key->type == GDK_KEY_PRESS ||
346            key->type == GDK_KEY_RELEASE)
347     {
348       event->string = gdk_keyval_name (key->keyval);
349     }
350   event->keycode = key->hardware_keycode;
351   event->timestamp = key->time;
352 #ifdef GAIL_DEBUG
353   g_print ("GailKey:\tsym %u\n\tmods %x\n\tcode %u\n\ttime %lx\n",
354            (unsigned int) event->keyval,
355            (unsigned int) event->state,
356            (unsigned int) event->keycode,
357            (unsigned long int) event->timestamp);
358 #endif
359   return event;
360 }
361
362 typedef struct {
363   AtkKeySnoopFunc func;
364   gpointer        data;
365   guint           key;
366 } KeyEventListener;
367
368 gboolean
369 _gail_util_key_snooper (GtkWidget   *the_widget,
370                         GdkEventKey *event)
371 {
372   GSList *l;
373   AtkKeyEventStruct *atk_event;
374   gboolean result;
375
376   atk_event = atk_key_event_from_gdk_event_key (event);
377
378   result = FALSE;
379
380   for (l = key_listener_list; l; l = l->next)
381     {
382       KeyEventListener *listener = l->data;
383
384       result |= listener->func (atk_event, listener->data);
385     }
386   g_free (atk_event);
387
388   return result;
389 }
390
391 static guint
392 gail_util_add_key_event_listener (AtkKeySnoopFunc  listener_func,
393                                   gpointer         listener_data)
394 {
395   static guint key = 0;
396   KeyEventListener *listener;
397
398   key++;
399
400   listener = g_slice_new0 (KeyEventListener);
401   listener->func = listener_func;
402   listener->data = listener_data;
403   listener->key = key;
404
405   key_listener_list = g_slist_append (key_listener_list, listener);
406
407   return key;
408 }
409
410 static void
411 gail_util_remove_key_event_listener (guint listener_key)
412 {
413   GSList *l;
414
415   for (l = key_listener_list; l; l = l->next)
416     {
417       KeyEventListener *listener = l->data;
418
419       if (listener->key == listener_key)
420         {
421           g_slice_free (KeyEventListener, listener);
422           key_listener_list = g_slist_delete_link (key_listener_list, l);
423
424           break;
425         }
426     }
427 }
428
429 static AtkObject *
430 gail_util_get_root (void)
431 {
432   static AtkObject *root = NULL;
433
434   if (!root)
435     {
436       root = g_object_new (GTK_TYPE_TOPLEVEL_ACCESSIBLE, NULL);
437       atk_object_initialize (root, NULL);
438     }
439
440   return root;
441 }
442
443 static const gchar *
444 gail_util_get_toolkit_name (void)
445 {
446   return "gtk";
447 }
448
449 static const gchar *
450 gail_util_get_toolkit_version (void)
451 {
452   return GTK_VERSION;
453 }
454
455 void
456 _gail_util_install (void)
457 {
458   AtkUtilClass *atk_class = ATK_UTIL_CLASS (g_type_class_ref (ATK_TYPE_UTIL));
459
460   atk_class->add_global_event_listener = gail_util_add_global_event_listener;
461   atk_class->remove_global_event_listener = gail_util_remove_global_event_listener;
462   atk_class->add_key_event_listener = gail_util_add_key_event_listener;
463   atk_class->remove_key_event_listener = gail_util_remove_key_event_listener;
464   atk_class->get_root = gail_util_get_root;
465   atk_class->get_toolkit_name = gail_util_get_toolkit_name;
466   atk_class->get_toolkit_version = gail_util_get_toolkit_version;
467
468   listener_list = g_hash_table_new_full (g_int_hash, g_int_equal, NULL, g_free);
469   do_window_event_initialization ();
470 }