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