Protect against NULL pointer dereference
[platform/upstream/atk.git] / atk / atkutil.c
1 /* ATK -  Accessibility Toolkit
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 "atkutil.h"
23 #include "atkmarshal.h"
24
25 /**
26  * SECTION:atkutil
27  * @Short_description: A set of ATK utility functions for event and toolkit support.
28  * @Title:AtkUtil
29  *
30  * A set of ATK utility functions which are used to support event
31  * registration of various types, and obtaining the 'root' accessible
32  * of a process and information about the current ATK implementation
33  * and toolkit version.
34  */
35
36 static void atk_util_class_init (AtkUtilClass *klass);
37
38 static AtkObject *previous_focus_object = NULL;
39
40 typedef struct _AtkUtilListenerInfo AtkUtilListenerInfo;
41 struct _AtkUtilListenerInfo
42 {
43   gint key;
44   guint signal_id;
45   gulong hook_id;
46 };
47 static GHashTable *listener_list = NULL;
48
49 GType
50 atk_util_get_type (void)
51 {
52   static GType type = 0;
53
54   if (!type)
55     {
56       static const GTypeInfo typeInfo =
57       {
58         sizeof (AtkUtilClass),
59         (GBaseInitFunc) NULL,
60         (GBaseFinalizeFunc) NULL,
61         (GClassInitFunc) atk_util_class_init,
62         (GClassFinalizeFunc) NULL,
63         NULL,
64         sizeof (AtkUtil),
65         0,
66         (GInstanceInitFunc) NULL,
67       } ;
68       type = g_type_register_static (G_TYPE_OBJECT, "AtkUtil", &typeInfo, 0) ;
69     }
70   return type;
71 }
72
73 /*
74  * This file supports the addition and removal of multiple focus handlers
75  * as long as they are all called in the same thread.
76  */
77 static AtkEventListenerInit  focus_tracker_init = (AtkEventListenerInit) NULL;
78
79 static gboolean init_done = FALSE;
80
81 /*
82  * Array of FocusTracker structs
83  */
84 static GArray *trackers = NULL;
85 static guint  global_index = 0;
86
87 typedef struct _FocusTracker FocusTracker;
88
89 struct _FocusTracker {
90   guint index;
91   AtkEventListener func;
92 };
93
94 /**
95  * atk_focus_tracker_init: (skip)
96  * @init: Function to be called for focus tracker initialization
97  *
98  * Specifies the function to be called for focus tracker initialization.
99  * This function should be called by an implementation of the
100  * ATK interface if any specific work needs to be done to enable
101  * focus tracking.
102  *
103  * Deprecated: 2.9.4: Focus tracking has been dropped as a feature
104  * to be implemented by ATK itself.
105  *
106  **/
107 void
108 atk_focus_tracker_init (AtkEventListenerInit    init)
109 {
110   if (!focus_tracker_init)
111     focus_tracker_init = init;
112 }
113
114 /**
115  * atk_add_focus_tracker: (skip)
116  * @focus_tracker: Function to be added to the list of functions to be called
117  * when an object receives focus.
118  *
119  * Adds the specified function to the list of functions to be called
120  * when an object receives focus.
121  *
122  * Deprecated: 2.9.4: Focus tracking has been dropped as a feature
123  * to be implemented by ATK itself. If you need focus tracking on your
124  * implementation, subscribe to the #AtkObject::state-change "focused" signal.
125  *
126  * Returns: added focus tracker id, or 0 on failure.
127  **/
128 guint
129 atk_add_focus_tracker (AtkEventListener   focus_tracker)
130 {
131   g_return_val_if_fail (focus_tracker, 0);
132
133   if (!init_done)
134   {
135     if (focus_tracker_init)
136     {
137       focus_tracker_init ();
138     }
139     trackers = g_array_sized_new (FALSE, TRUE, sizeof (FocusTracker), 0);
140     init_done = TRUE;
141   }
142   if (init_done)
143   {
144     FocusTracker item;
145
146     item.index = ++global_index;
147     item.func = focus_tracker;
148     trackers = g_array_append_val (trackers, item);
149     return global_index;
150   }
151   else
152   {
153     return 0;
154   }
155 }
156
157 /**
158  * atk_remove_focus_tracker:
159  * @tracker_id: the id of the focus tracker to remove
160  *
161  * Removes the specified focus tracker from the list of functions
162  * to be called when any object receives focus.
163  *
164  * Deprecated: 2.9.4: Focus tracking has been dropped as a feature
165  *   to be implemented by ATK itself. If you need focus tracking on your
166  *   implementation, subscribe to the #AtkObject::state-change "focused"
167  *   signal.
168  */
169 void
170 atk_remove_focus_tracker (guint            tracker_id)
171 {
172   FocusTracker *item;
173   guint i;
174
175   if (trackers == NULL)
176     return;
177
178   if (tracker_id == 0)
179     return;
180
181   for (i = 0; i < trackers->len; i++)
182   {
183     item = &g_array_index (trackers, FocusTracker, i);
184     if (item->index == tracker_id)
185     {
186       trackers = g_array_remove_index (trackers, i);
187       break;
188     }
189   }
190 }
191
192 /**
193  * atk_focus_tracker_notify:
194  * @object: an #AtkObject
195  *
196  * Cause the focus tracker functions which have been specified to be
197  * executed for the object.
198  *
199  * Deprecated: 2.9.4: Focus tracking has been dropped as a feature
200  * to be implemented by ATK itself. As #AtkObject::focus-event was
201  * deprecated in favor of a #AtkObject::state-change signal, in order
202  * to notify a focus change on your implementation, you can use
203  * atk_object_notify_state_change() instead.
204  **/
205 void
206 atk_focus_tracker_notify (AtkObject       *object)
207 {
208   FocusTracker *item;
209   guint i;
210
211   if (trackers == NULL)
212     return;
213
214   if (object == previous_focus_object)
215     return;
216   else
217     {
218       if (previous_focus_object)
219         g_object_unref (previous_focus_object);
220
221       previous_focus_object = object;
222       if (object)
223         {
224           g_object_ref (object);
225
226           for (i = 0; i < trackers->len; i++)
227             {
228               item = &g_array_index (trackers, FocusTracker, i);
229               g_return_if_fail (item != NULL);
230               item->func (object);
231             }
232         }
233     
234     }
235 }
236
237 static guint
238 add_listener (GSignalEmissionHook listener,
239               const gchar         *object_type,
240               const gchar         *signal_name,
241               const gchar         *detail_string,
242               const gchar         *hook_data)
243 {
244   GType type;
245   guint signal_id;
246   gint  rc = 0;
247   static gint listener_idx = 1;
248   GQuark detail_quark = 0;
249
250   type = g_type_from_name (object_type);
251   if (type)
252     {
253       signal_id  = g_signal_lookup (signal_name, type);
254       detail_quark = g_quark_from_string (detail_string);
255
256       if (signal_id > 0)
257         {
258           AtkUtilListenerInfo *listener_info;
259
260           rc = listener_idx;
261
262           listener_info = g_new (AtkUtilListenerInfo, 1);
263           listener_info->key = listener_idx;
264           listener_info->hook_id =
265             g_signal_add_emission_hook (signal_id, detail_quark, listener,
266                                         g_strdup (hook_data),
267                                         (GDestroyNotify) g_free);
268           listener_info->signal_id = signal_id;
269
270           g_hash_table_insert(listener_list, &(listener_info->key), listener_info);
271           listener_idx++;
272         }
273       else
274         {
275           g_debug ("Signal type %s not supported\n", signal_name);
276         }
277     }
278   else
279     {
280       g_warning("Invalid object type %s\n", object_type);
281     }
282   return rc;
283 }
284
285 static guint
286 atk_util_real_add_global_event_listener (GSignalEmissionHook listener,
287                                          const gchar *event_type)
288 {
289   guint rc = 0;
290   gchar **split_string;
291   guint length;
292
293   split_string = g_strsplit (event_type, ":", 0);
294   length = g_strv_length (split_string);
295
296   if ((length == 3) || (length == 4))
297     rc = add_listener (listener, split_string[1], split_string[2],
298                        split_string[3], event_type);
299
300   g_strfreev (split_string);
301
302   return rc;
303 }
304
305 static void
306 atk_util_real_remove_global_event_listener (guint remove_listener)
307 {
308   if (remove_listener > 0)
309     {
310       AtkUtilListenerInfo *listener_info;
311       gint tmp_idx = remove_listener;
312
313       listener_info = (AtkUtilListenerInfo *)
314         g_hash_table_lookup(listener_list, &tmp_idx);
315
316       if (listener_info != NULL)
317         {
318           /* Hook id of 0 and signal id of 0 are invalid */
319           if (listener_info->hook_id != 0 && listener_info->signal_id != 0)
320             {
321               /* Remove the emission hook */
322               g_signal_remove_emission_hook(listener_info->signal_id,
323                                             listener_info->hook_id);
324
325               /* Remove the element from the hash */
326               g_hash_table_remove(listener_list, &tmp_idx);
327             }
328           else
329             {
330               g_warning("Invalid listener hook_id %ld or signal_id %d\n",
331                         listener_info->hook_id, listener_info->signal_id);
332             }
333         }
334       else
335         {
336           g_warning("No listener with the specified listener id %d",
337                     remove_listener);
338         }
339     }
340   else
341     {
342       g_warning("Invalid listener_id %d", remove_listener);
343     }
344 }
345
346
347 /**
348  * atk_add_global_event_listener: (skip)
349  * @listener: the listener to notify
350  * @event_type: the type of event for which notification is requested
351  *
352  * Adds the specified function to the list of functions to be called
353  * when an ATK event of type event_type occurs.
354  *
355  * The format of event_type is the following:
356  *  "ATK:&lt;atk_type&gt;:&lt;atk_event&gt;:&lt;atk_event_detail&gt;
357  *
358  * Where "ATK" works as the namespace, &lt;atk_interface&gt; is the name of
359  * the ATK type (interface or object), &lt;atk_event&gt; is the name of the
360  * signal defined on that interface and &lt;atk_event_detail&gt; is the
361  * gsignal detail of that signal. You can find more info about gsignal
362  * details here:
363  * http://developer.gnome.org/gobject/stable/gobject-Signals.html
364  *
365  * The first three parameters are mandatory. The last one is optional.
366  *
367  * For example:
368  *   ATK:AtkObject:state-change
369  *   ATK:AtkText:text-selection-changed
370  *   ATK:AtkText:text-insert:system
371  *
372  * Toolkit implementor note: ATK provides a default implementation for
373  * this virtual method. ATK implementors are discouraged from
374  * reimplementing this method.
375  *
376  * Toolkit implementor note: this method is not intended to be used by
377  * ATK implementors but by ATK consumers.
378  *
379  * ATK consumers note: as this method adds a listener for a given ATK
380  * type, that type should be already registered on the GType system
381  * before calling this method. A simple way to do that is creating an
382  * instance of #AtkNoOpObject. This class implements all ATK
383  * interfaces, so creating the instance will register all ATK types as
384  * a collateral effect.
385  *
386  * Returns: added event listener id, or 0 on failure.
387  **/
388 guint
389 atk_add_global_event_listener (GSignalEmissionHook listener,
390                                const gchar        *event_type)
391 {
392   guint retval;
393   AtkUtilClass *klass = g_type_class_ref (ATK_TYPE_UTIL);
394
395   if (klass->add_global_event_listener)
396     {
397       retval = klass->add_global_event_listener (listener, event_type);
398     }
399   else
400     {
401       retval = 0;
402     }
403   g_type_class_unref (klass);
404
405   return retval;
406 }
407
408 /**
409  * atk_remove_global_event_listener:
410  * @listener_id: the id of the event listener to remove
411  *
412  * @listener_id is the value returned by #atk_add_global_event_listener
413  * when you registered that event listener.
414  *
415  * Toolkit implementor note: ATK provides a default implementation for
416  * this virtual method. ATK implementors are discouraged from
417  * reimplementing this method.
418  *
419  * Toolkit implementor note: this method is not intended to be used by
420  * ATK implementors but by ATK consumers.
421  *
422  * Removes the specified event listener
423  **/
424 void
425 atk_remove_global_event_listener (guint listener_id)
426 {
427   AtkUtilClass *klass = g_type_class_peek (ATK_TYPE_UTIL);
428
429   if (klass && klass->remove_global_event_listener)
430     klass->remove_global_event_listener (listener_id);
431 }
432
433 /**
434  * atk_add_key_event_listener: (skip)
435  * @listener: the listener to notify
436  * @data: a #gpointer that points to a block of data that should be sent to the registered listeners,
437  *        along with the event notification, when it occurs.  
438  *
439  * Adds the specified function to the list of functions to be called
440  *        when a key event occurs.  The @data element will be passed to the
441  *        #AtkKeySnoopFunc (@listener) as the @func_data param, on notification.
442  *
443  * Returns: added event listener id, or 0 on failure.
444  **/
445 guint
446 atk_add_key_event_listener (AtkKeySnoopFunc listener, gpointer data)
447 {
448   guint retval;
449   AtkUtilClass *klass = g_type_class_peek (ATK_TYPE_UTIL);
450   if (klass && klass->add_key_event_listener)
451     {
452       retval = klass->add_key_event_listener (listener, data);
453     }
454   else
455     {
456       retval = 0;
457     }
458
459   return retval;
460 }
461
462 /**
463  * atk_remove_key_event_listener:
464  * @listener_id: the id of the event listener to remove
465  *
466  * @listener_id is the value returned by #atk_add_key_event_listener
467  * when you registered that event listener.
468  *
469  * Removes the specified event listener.
470  **/
471 void
472 atk_remove_key_event_listener (guint listener_id)
473 {
474   AtkUtilClass *klass = g_type_class_peek (ATK_TYPE_UTIL);
475
476   if (klass && klass->remove_key_event_listener)
477     klass->remove_key_event_listener (listener_id);
478 }
479
480 /**
481  * atk_get_root:
482  *
483  * Gets the root accessible container for the current application.
484  *
485  * Returns: (transfer none): the root accessible container for the current
486  * application
487  **/
488 AtkObject*
489 atk_get_root (void)
490 {
491   AtkUtilClass *klass = g_type_class_ref (ATK_TYPE_UTIL);
492   AtkObject    *retval;
493   if (klass->get_root)
494     {
495       retval = klass->get_root ();
496     }
497   else
498     {
499       retval = NULL;
500     }
501   g_type_class_unref (klass);
502
503   return retval;
504 }
505
506 /**
507  * atk_get_focus_object:
508  *
509  * Gets the currently focused object.
510  * 
511  * Since: 1.6
512  *
513  * Returns: (transfer none): the currently focused object for the current
514  * application
515  **/
516 AtkObject*
517 atk_get_focus_object (void)
518 {
519   return previous_focus_object;
520 }
521
522 /**
523  * atk_get_toolkit_name:
524  *
525  * Gets name string for the GUI toolkit implementing ATK for this application.
526  *
527  * Returns: name string for the GUI toolkit implementing ATK for this application
528  **/
529 const gchar*
530 atk_get_toolkit_name (void)
531 {
532   const gchar *retval;
533   AtkUtilClass *klass = g_type_class_ref (ATK_TYPE_UTIL);
534   if (klass->get_toolkit_name)
535     {
536       retval = klass->get_toolkit_name ();
537     }
538   else
539     {
540       retval = NULL;
541     }
542   g_type_class_unref (klass);
543
544   return retval;
545 }
546
547 /**
548  * atk_get_toolkit_version:
549  *
550  * Gets version string for the GUI toolkit implementing ATK for this application.
551  *
552  * Returns: version string for the GUI toolkit implementing ATK for this application
553  **/
554 const gchar*
555 atk_get_toolkit_version (void)
556 {
557   const gchar *retval;
558   AtkUtilClass *klass = g_type_class_ref (ATK_TYPE_UTIL);
559   if (klass->get_toolkit_version)
560     {
561       retval = klass->get_toolkit_version ();
562     }
563   else
564     {
565       retval = NULL;
566     }
567   g_type_class_unref (klass);
568
569   return retval;
570 }
571
572 /**
573  * atk_get_version:
574  *
575  * Gets the current version for ATK.
576  *
577  * Returns: version string for ATK
578  *
579  * Since: 1.20
580  */
581 const gchar *
582 atk_get_version (void)
583 {
584   return VERSION;
585 }
586
587 static void
588 atk_util_class_init (AtkUtilClass *klass)
589 {
590   klass->add_global_event_listener = atk_util_real_add_global_event_listener;
591   klass->remove_global_event_listener = atk_util_real_remove_global_event_listener;
592   klass->get_root = NULL;
593   klass->get_toolkit_name = NULL;
594   klass->get_toolkit_version = NULL;
595
596   listener_list = g_hash_table_new_full (g_int_hash, g_int_equal, NULL,
597                                          g_free);
598 }