]> Pileus Git - ~andy/gtk/blob - gtk/a11y/gtkmenuitemaccessible.c
c4402224ff26cfb0d8f73940f399831bc5223565
[~andy/gtk] / gtk / a11y / gtkmenuitemaccessible.c
1 /* GAIL - The GNOME Accessibility Implementation Library
2  * Copyright 2001, 2002, 2003 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 <string.h>
21 #include <gtk/gtk.h>
22 #include "gtkmenuitemaccessible.h"
23 #include "gtksubmenuitemaccessible.h"
24
25 #define KEYBINDING_SEPARATOR ";"
26
27 static void menu_item_select   (GtkMenuItem *item);
28 static void menu_item_deselect (GtkMenuItem *item);
29
30 static GtkWidget *get_label_from_container   (GtkWidget *container);
31 static gchar     *get_text_from_label_widget (GtkWidget *widget);
32
33
34 static void atk_action_interface_init (AtkActionIface *iface);
35
36 G_DEFINE_TYPE_WITH_CODE (GtkMenuItemAccessible, gtk_menu_item_accessible, GTK_TYPE_CONTAINER_ACCESSIBLE,
37                          G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, atk_action_interface_init))
38
39 static void
40 gtk_menu_item_accessible_initialize (AtkObject *obj,
41                                      gpointer   data)
42 {
43   GtkWidget *widget;
44   GtkWidget *parent;
45
46   ATK_OBJECT_CLASS (gtk_menu_item_accessible_parent_class)->initialize (obj, data);
47
48   g_signal_connect (data, "select", G_CALLBACK (menu_item_select), NULL);
49   g_signal_connect (data, "deselect", G_CALLBACK (menu_item_deselect), NULL);
50
51   widget = GTK_WIDGET (data);
52   parent = gtk_widget_get_parent (widget);
53   if (GTK_IS_MENU (parent))
54     {
55       GtkWidget *parent_widget;
56
57       parent_widget =  gtk_menu_get_attach_widget (GTK_MENU (parent));
58
59       if (!GTK_IS_MENU_ITEM (parent_widget))
60         parent_widget = gtk_widget_get_parent (widget);
61       if (parent_widget)
62         atk_object_set_parent (obj, gtk_widget_get_accessible (parent_widget));
63     }
64
65   gtk_widget_accessible_set_layer (GTK_WIDGET_ACCESSIBLE (obj), ATK_LAYER_POPUP);
66
67   if (GTK_IS_TEAROFF_MENU_ITEM (data))
68     obj->role = ATK_ROLE_TEAR_OFF_MENU_ITEM;
69   else if (GTK_IS_SEPARATOR_MENU_ITEM (data))
70     obj->role = ATK_ROLE_SEPARATOR;
71   else
72     obj->role = ATK_ROLE_MENU_ITEM;
73 }
74
75 static gint
76 gtk_menu_item_accessible_get_n_children (AtkObject *obj)
77 {
78   GtkWidget *widget;
79   GtkWidget *submenu;
80   gint count = 0;
81
82   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
83   if (widget == NULL)
84     return count;
85
86   submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
87   if (submenu)
88     {
89       GList *children;
90
91       children = gtk_container_get_children (GTK_CONTAINER (submenu));
92       count = g_list_length (children);
93       g_list_free (children);
94     }
95   return count;
96 }
97
98 static AtkObject *
99 gtk_menu_item_accessible_ref_child (AtkObject *obj,
100                                     gint       i)
101 {
102   AtkObject  *accessible;
103   GtkWidget *widget;
104   GtkWidget *submenu;
105
106   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
107   if (widget == NULL)
108     return NULL;
109
110   accessible = NULL;
111   submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
112   if (submenu)
113     {
114       GList *children;
115       GList *tmp_list;
116
117       children = gtk_container_get_children (GTK_CONTAINER (submenu));
118       tmp_list = g_list_nth (children, i);
119       if (tmp_list)
120         {
121           accessible = gtk_widget_get_accessible (GTK_WIDGET (tmp_list->data));
122           g_object_ref (accessible);
123         }
124       g_list_free (children);
125     }
126
127   return accessible;
128 }
129
130 static AtkStateSet *
131 gtk_menu_item_accessible_ref_state_set (AtkObject *obj)
132 {
133   AtkObject *menu_item;
134   AtkStateSet *state_set, *parent_state_set;
135
136   state_set = ATK_OBJECT_CLASS (gtk_menu_item_accessible_parent_class)->ref_state_set (obj);
137
138   menu_item = atk_object_get_parent (obj);
139
140   if (menu_item)
141     {
142       if (!GTK_IS_MENU_ITEM (gtk_accessible_get_widget (GTK_ACCESSIBLE (menu_item))))
143         return state_set;
144
145       parent_state_set = atk_object_ref_state_set (menu_item);
146       if (!atk_state_set_contains_state (parent_state_set, ATK_STATE_SELECTED))
147         {
148           atk_state_set_remove_state (state_set, ATK_STATE_FOCUSED);
149           atk_state_set_remove_state (state_set, ATK_STATE_SHOWING);
150         }
151       g_object_unref (parent_state_set);
152     }
153
154   return state_set;
155 }
156
157 static const gchar *
158 gtk_menu_item_accessible_get_name (AtkObject *obj)
159 {
160   const gchar *name;
161   GtkWidget *widget;
162   GtkWidget *label;
163   GtkMenuItemAccessible *accessible;
164
165   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
166   if (widget == NULL)
167     return NULL;
168
169   name = ATK_OBJECT_CLASS (gtk_menu_item_accessible_parent_class)->get_name (obj);
170   if (name)
171     return name;
172
173   accessible = GTK_MENU_ITEM_ACCESSIBLE (obj);
174   label = get_label_from_container (widget);
175
176   g_free (accessible->text);
177   accessible->text = get_text_from_label_widget (label);
178
179   return accessible->text;
180 }
181
182 static void
183 gtk_menu_item_accessible_finalize (GObject *object)
184 {
185   GtkMenuItemAccessible *accessible = GTK_MENU_ITEM_ACCESSIBLE (object);
186
187   g_free (accessible->text);
188
189   G_OBJECT_CLASS (gtk_menu_item_accessible_parent_class)->finalize (object);
190 }
191
192 static void
193 gtk_menu_item_accessible_notify_gtk (GObject    *obj,
194                                      GParamSpec *pspec)
195 {
196   AtkObject* atk_obj;
197
198   atk_obj = gtk_widget_get_accessible (GTK_WIDGET (obj));
199
200   if (strcmp (pspec->name, "label") == 0)
201     {
202       if (atk_obj->name == NULL)
203         g_object_notify (G_OBJECT (atk_obj), "accessible-name");
204       g_signal_emit_by_name (atk_obj, "visible_data_changed");
205     }
206   else
207     GTK_WIDGET_ACCESSIBLE_CLASS (gtk_menu_item_accessible_parent_class)->notify_gtk (obj, pspec);
208 }
209
210 static void
211 gtk_menu_item_accessible_class_init (GtkMenuItemAccessibleClass *klass)
212 {
213   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
214   AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
215   GtkWidgetAccessibleClass *widget_class = (GtkWidgetAccessibleClass*)klass;
216
217   widget_class->notify_gtk = gtk_menu_item_accessible_notify_gtk;
218
219   gobject_class->finalize = gtk_menu_item_accessible_finalize;
220
221   class->get_n_children = gtk_menu_item_accessible_get_n_children;
222   class->ref_child = gtk_menu_item_accessible_ref_child;
223   class->ref_state_set = gtk_menu_item_accessible_ref_state_set;
224   class->initialize = gtk_menu_item_accessible_initialize;
225   class->get_name = gtk_menu_item_accessible_get_name;
226 }
227
228 static void
229 gtk_menu_item_accessible_init (GtkMenuItemAccessible *menu_item)
230 {
231 }
232
233 static GtkWidget *
234 get_label_from_container (GtkWidget *container)
235 {
236   GtkWidget *label;
237   GList *children, *tmp_list;
238
239   if (!GTK_IS_CONTAINER (container))
240     return NULL;
241
242   children = gtk_container_get_children (GTK_CONTAINER (container));
243   label = NULL;
244
245   for (tmp_list = children; tmp_list != NULL; tmp_list = tmp_list->next)
246     {
247       if (GTK_IS_LABEL (tmp_list->data))
248         {
249           label = tmp_list->data;
250           break;
251         }
252       else if (GTK_IS_CELL_VIEW (tmp_list->data))
253         {
254           label = tmp_list->data;
255           break;
256         }
257       else if (GTK_IS_BOX (tmp_list->data))
258         {
259           label = get_label_from_container (GTK_WIDGET (tmp_list->data));
260           if (label)
261             break;
262         }
263     }
264   g_list_free (children);
265
266   return label;
267 }
268
269 static gchar *
270 get_text_from_label_widget (GtkWidget *label)
271 {
272   if (GTK_IS_LABEL (label))
273     return g_strdup (gtk_label_get_text (GTK_LABEL (label)));
274   else if (GTK_IS_CELL_VIEW (label))
275     {
276       GList *cells, *l;
277       GtkTreeModel *model;
278       GtkTreeIter iter;
279       GtkTreePath *path;
280       GtkCellArea *area;
281       gchar *text;
282
283       model = gtk_cell_view_get_model (GTK_CELL_VIEW (label));
284       path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (label));
285       gtk_tree_model_get_iter (model, &iter, path);
286       gtk_tree_path_free (path);
287
288       area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (label));
289       gtk_cell_area_apply_attributes (area, model, &iter, FALSE, FALSE);
290       cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (label));
291
292       text = NULL;
293       for (l = cells; l; l = l->next)
294         {
295           GtkCellRenderer *cell = l->data;
296
297           if (GTK_IS_CELL_RENDERER_TEXT (cell))
298             {
299               g_object_get (cell, "text", &text, NULL);
300               break;
301             }
302         }
303
304       g_list_free (cells);
305
306       return text;
307     }
308
309   return NULL;
310 }
311
312 static void
313 ensure_menus_unposted (GtkMenuItemAccessible *menu_item)
314 {
315   AtkObject *parent;
316   GtkWidget *widget;
317
318   parent = atk_object_get_parent (ATK_OBJECT (menu_item));
319   while (parent)
320     {
321       widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (parent));
322       if (GTK_IS_MENU (widget))
323         {
324           if (gtk_widget_get_mapped (widget))
325             gtk_menu_shell_cancel (GTK_MENU_SHELL (widget));
326
327           return;
328         }
329       parent = atk_object_get_parent (parent);
330     }
331 }
332
333 static gboolean
334 gtk_menu_item_accessible_do_action (AtkAction *action,
335                                     gint       i)
336 {
337   GtkWidget *item, *item_parent;
338   gboolean item_mapped;
339
340   item = gtk_accessible_get_widget (GTK_ACCESSIBLE (action));
341   if (item == NULL)
342     return FALSE;
343
344   if (i != 0)
345     return FALSE;
346
347   if (!gtk_widget_get_sensitive (item) || !gtk_widget_get_visible (item))
348     return FALSE;
349
350   item_parent = gtk_widget_get_parent (item);
351   if (!GTK_IS_MENU_SHELL (item_parent))
352     return FALSE;
353
354   gtk_menu_shell_select_item (GTK_MENU_SHELL (item_parent), item);
355   item_mapped = gtk_widget_get_mapped (item);
356
357   /* This is what is called when <Return> is pressed for a menu item.
358    * The last argument means 'force hide'.
359    */
360   g_signal_emit_by_name (item_parent, "activate_current", 1);
361   if (!item_mapped)
362     ensure_menus_unposted (GTK_MENU_ITEM_ACCESSIBLE (action));
363
364   return TRUE;
365 }
366
367 static gint
368 gtk_menu_item_accessible_get_n_actions (AtkAction *action)
369 {
370   return 1;
371 }
372
373 static const gchar *
374 gtk_menu_item_accessible_action_get_name (AtkAction *action,
375                                           gint       i)
376 {
377   if (i != 0)
378     return NULL;
379
380   return "click";
381 }
382
383 static gboolean
384 find_accel_by_widget (GtkAccelKey *key,
385                       GClosure    *closure,
386                       gpointer     data)
387 {
388   /* We assume that closure->data points to the widget
389    * pending gtk_widget_get_accel_closures being made public
390    */
391   return data == (gpointer) closure->data;
392 }
393
394 static gboolean
395 find_accel_by_closure (GtkAccelKey *key,
396                        GClosure    *closure,
397                        gpointer     data)
398 {
399   return data == (gpointer) closure;
400 }
401
402 /* This function returns a string of the form A;B;C where A is
403  * the keybinding for the widget; B is the keybinding to traverse
404  * from the menubar and C is the accelerator. The items in the
405  * keybinding to traverse from the menubar are separated by ":".
406  */
407 static const gchar *
408 gtk_menu_item_accessible_get_keybinding (AtkAction *action,
409                                          gint       i)
410 {
411   gchar *keybinding = NULL;
412   gchar *item_keybinding = NULL;
413   gchar *full_keybinding = NULL;
414   gchar *accelerator = NULL;
415   GtkWidget *item;
416   GtkWidget *temp_item;
417   GtkWidget *child;
418   GtkWidget *parent;
419
420   item = gtk_accessible_get_widget (GTK_ACCESSIBLE (action));
421   if (item == NULL)
422     return NULL;
423
424   if (i != 0)
425     return NULL;
426
427   temp_item = item;
428   while (TRUE)
429     {
430       GdkModifierType mnemonic_modifier = 0;
431       guint key_val;
432       gchar *key, *temp_keybinding;
433
434       child = gtk_bin_get_child (GTK_BIN (temp_item));
435       if (child == NULL)
436         return NULL;
437
438       parent = gtk_widget_get_parent (temp_item);
439       if (!parent)
440         /* parent can be NULL when activating a window from the panel */
441         return NULL;
442
443       if (GTK_IS_MENU_BAR (parent))
444         {
445           GtkWidget *toplevel;
446
447           toplevel = gtk_widget_get_toplevel (parent);
448           if (toplevel && GTK_IS_WINDOW (toplevel))
449             mnemonic_modifier =
450               gtk_window_get_mnemonic_modifier (GTK_WINDOW (toplevel));
451         }
452
453       if (GTK_IS_LABEL (child))
454         {
455           key_val = gtk_label_get_mnemonic_keyval (GTK_LABEL (child));
456           if (key_val != GDK_KEY_VoidSymbol)
457             {
458               key = gtk_accelerator_name (key_val, mnemonic_modifier);
459               if (full_keybinding)
460                 temp_keybinding = g_strconcat (key, ":", full_keybinding, NULL);
461               else
462                 temp_keybinding = g_strdup (key);
463
464               if (temp_item == item)
465                 item_keybinding = g_strdup (key);
466
467               g_free (key);
468               g_free (full_keybinding);
469               full_keybinding = temp_keybinding;
470             }
471           else
472             {
473               /* No keybinding */
474               g_free (full_keybinding);
475               full_keybinding = NULL;
476               break;
477             }
478         }
479
480       /* We have reached the menu bar so we are finished */
481       if (GTK_IS_MENU_BAR (parent))
482         break;
483
484       g_return_val_if_fail (GTK_IS_MENU (parent), NULL);
485       temp_item = gtk_menu_get_attach_widget (GTK_MENU (parent));
486       if (!GTK_IS_MENU_ITEM (temp_item))
487         {
488           /* Menu is attached to something other than a menu item;
489            * probably an option menu
490            */
491           g_free (full_keybinding);
492           full_keybinding = NULL;
493           break;
494         }
495     }
496
497   parent = gtk_widget_get_parent (item);
498   if (GTK_IS_MENU (parent))
499     {
500       GtkAccelGroup *group;
501       GtkAccelKey *key;
502
503       group = gtk_menu_get_accel_group (GTK_MENU (parent));
504       if (group)
505         key = gtk_accel_group_find (group, find_accel_by_widget, item);
506       else
507         {
508           key = NULL;
509           child = gtk_bin_get_child (GTK_BIN (item));
510           if (GTK_IS_ACCEL_LABEL (child))
511             {
512               GtkAccelLabel *accel_label;
513               GClosure      *accel_closure;
514
515               accel_label = GTK_ACCEL_LABEL (child);
516               g_object_get (accel_label, "accel-closure", &accel_closure, NULL);
517               if (accel_closure)
518                 {
519                   key = gtk_accel_group_find (gtk_accel_group_from_accel_closure (accel_closure),
520                                               find_accel_by_closure,
521                                               accel_closure);
522                   g_closure_unref (accel_closure);
523                 }
524             }
525         }
526
527      if (key)
528        accelerator = gtk_accelerator_name (key->accel_key, key->accel_mods);
529    }
530
531   /* Concatenate the bindings */
532   if (item_keybinding || full_keybinding || accelerator)
533     {
534       gchar *temp;
535       if (item_keybinding)
536         {
537           keybinding = g_strconcat (item_keybinding, KEYBINDING_SEPARATOR, NULL);
538           g_free (item_keybinding);
539         }
540       else
541         keybinding = g_strdup (KEYBINDING_SEPARATOR);
542
543       if (full_keybinding)
544         {
545           temp = g_strconcat (keybinding, full_keybinding,
546                               KEYBINDING_SEPARATOR, NULL);
547           g_free (full_keybinding);
548         }
549       else
550         temp = g_strconcat (keybinding, KEYBINDING_SEPARATOR, NULL);
551
552       g_free (keybinding);
553       keybinding = temp;
554       if (accelerator)
555         {
556           temp = g_strconcat (keybinding, accelerator, NULL);
557           g_free (accelerator);
558           g_free (keybinding);
559           keybinding = temp;
560       }
561     }
562
563   return keybinding;
564 }
565
566 static void
567 atk_action_interface_init (AtkActionIface *iface)
568 {
569   iface->do_action = gtk_menu_item_accessible_do_action;
570   iface->get_n_actions = gtk_menu_item_accessible_get_n_actions;
571   iface->get_name = gtk_menu_item_accessible_action_get_name;
572   iface->get_keybinding = gtk_menu_item_accessible_get_keybinding;
573 }
574
575 static void
576 menu_item_selection (GtkMenuItem  *item,
577                      gboolean      selected)
578 {
579   AtkObject *obj, *parent;
580   gint i;
581
582   obj = gtk_widget_get_accessible (GTK_WIDGET (item));
583   atk_object_notify_state_change (obj, ATK_STATE_SELECTED, selected);
584
585   for (i = 0; i < atk_object_get_n_accessible_children (obj); i++)
586     {
587       AtkObject *child;
588       child = atk_object_ref_accessible_child (obj, i);
589       atk_object_notify_state_change (child, ATK_STATE_SHOWING, selected);
590       g_object_unref (child);
591     }
592   parent = atk_object_get_parent (obj);
593   g_signal_emit_by_name (parent, "selection_changed");
594 }
595
596 static void
597 menu_item_select (GtkMenuItem *item)
598 {
599   menu_item_selection (item, TRUE);
600 }
601
602 static void
603 menu_item_deselect (GtkMenuItem *item)
604 {
605   menu_item_selection (item, FALSE);
606 }