]> Pileus Git - ~andy/gtk/blob - modules/other/gail/gailmenuitem.c
Integrate gail into gtk+. Bug #169488.
[~andy/gtk] / modules / other / gail / gailmenuitem.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 <gtk/gtk.h>
21 #include <gdk/gdkkeysyms.h>
22 #include "gailmenuitem.h"
23 #include "gailsubmenuitem.h"
24
25 #define KEYBINDING_SEPARATOR ";"
26
27 static void gail_menu_item_class_init  (GailMenuItemClass *klass);
28 static void gail_menu_item_object_init (GailMenuItem      *menu_item);
29
30 static void                  gail_menu_item_real_initialize
31                                                           (AtkObject       *obj,
32                                                            gpointer        data);
33 static gint                  gail_menu_item_get_n_children (AtkObject      *obj);
34 static AtkObject*            gail_menu_item_ref_child      (AtkObject      *obj,
35                                                             gint           i);
36 static void                  gail_menu_item_finalize       (GObject        *object);
37
38 static void                  atk_action_interface_init     (AtkActionIface *iface);
39 static gboolean              gail_menu_item_do_action      (AtkAction      *action,
40                                                             gint           i);
41 static gboolean              idle_do_action                (gpointer       data);
42 static gint                  gail_menu_item_get_n_actions  (AtkAction      *action);
43 static G_CONST_RETURN gchar* gail_menu_item_get_description(AtkAction      *action,
44                                                             gint           i);
45 static G_CONST_RETURN gchar* gail_menu_item_get_name       (AtkAction      *action,
46                                                             gint           i);
47 static G_CONST_RETURN gchar* gail_menu_item_get_keybinding (AtkAction      *action,
48                                                             gint           i);
49 static gboolean              gail_menu_item_set_description(AtkAction      *action,
50                                                             gint           i,
51                                                             const gchar    *desc);
52 static void                  menu_item_select              (GtkItem        *item);
53 static void                  menu_item_deselect            (GtkItem        *item);
54 static void                  menu_item_selection           (GtkItem        *item,
55                                                             gboolean       selected);
56 static gboolean              find_accel                    (GtkAccelKey    *key,
57                                                             GClosure       *closure,
58                                                             gpointer       data);
59 static gboolean              find_accel_new                (GtkAccelKey    *key,
60                                                             GClosure       *closure,
61                                                             gpointer       data);
62
63 static gpointer parent_class = NULL;
64
65 GType
66 gail_menu_item_get_type (void)
67 {
68   static GType type = 0;
69
70   if (!type)
71     {
72       static const GTypeInfo tinfo =
73       {
74         sizeof (GailMenuItemClass),
75         (GBaseInitFunc) NULL, /* base init */
76         (GBaseFinalizeFunc) NULL, /* base finalize */
77         (GClassInitFunc) gail_menu_item_class_init, /* class init */
78         (GClassFinalizeFunc) NULL, /* class finalize */
79         NULL, /* class data */
80         sizeof (GailMenuItem), /* instance size */
81         0, /* nb preallocs */
82         (GInstanceInitFunc) gail_menu_item_object_init, /* instance init */
83         NULL /* value table */
84       };
85
86       static const GInterfaceInfo atk_action_info =
87       {
88         (GInterfaceInitFunc) atk_action_interface_init,
89         (GInterfaceFinalizeFunc) NULL,
90         NULL
91       };
92
93       type = g_type_register_static (GAIL_TYPE_ITEM,
94                                      "GailMenuItem", &tinfo, 0);
95       g_type_add_interface_static (type, ATK_TYPE_ACTION,
96                                    &atk_action_info);
97     }
98   return type;
99 }
100
101 static void
102 gail_menu_item_class_init (GailMenuItemClass *klass)
103 {
104   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
105   AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
106
107   gobject_class->finalize = gail_menu_item_finalize;
108
109   class->get_n_children = gail_menu_item_get_n_children;
110   class->ref_child = gail_menu_item_ref_child;
111   class->initialize = gail_menu_item_real_initialize;
112
113   parent_class = g_type_class_peek_parent (klass);
114 }
115
116 static void
117 gail_menu_item_real_initialize (AtkObject *obj,
118                                 gpointer  data)
119 {
120   GtkWidget *widget;
121   GtkWidget *parent;
122
123   ATK_OBJECT_CLASS (parent_class)->initialize (obj, data);
124
125   g_signal_connect (data,
126                     "select",
127                     G_CALLBACK (menu_item_select),
128                     NULL);
129   g_signal_connect (data,
130                     "deselect",
131                     G_CALLBACK (menu_item_deselect),
132                     NULL);
133   widget = GTK_WIDGET (data);
134   parent = gtk_widget_get_parent (widget);
135   if (GTK_IS_MENU (parent))
136     {
137       GtkWidget *parent_widget;
138
139       parent_widget =  gtk_menu_get_attach_widget (GTK_MENU (parent));
140
141       if (!GTK_IS_MENU_ITEM (parent_widget))
142         parent_widget = gtk_widget_get_parent (widget);
143        if (parent_widget)
144         {
145           atk_object_set_parent (obj, gtk_widget_get_accessible (parent_widget));
146         }
147     }
148   g_object_set_data (G_OBJECT (obj), "atk-component-layer",
149                      GINT_TO_POINTER (ATK_LAYER_POPUP));
150
151   if (GTK_IS_TEAROFF_MENU_ITEM (data))
152     obj->role = ATK_ROLE_TEAR_OFF_MENU_ITEM;
153   else if (GTK_IS_SEPARATOR_MENU_ITEM (data))
154     obj->role = ATK_ROLE_SEPARATOR;
155   else
156     obj->role = ATK_ROLE_MENU_ITEM;
157 }
158
159 static void
160 gail_menu_item_object_init (GailMenuItem *menu_item)
161 {
162   menu_item->click_keybinding = NULL;
163   menu_item->click_description = NULL;
164 }
165
166 AtkObject*
167 gail_menu_item_new (GtkWidget *widget)
168 {
169   GObject *object;
170   AtkObject *accessible;
171   
172   g_return_val_if_fail (GTK_IS_MENU_ITEM (widget), NULL);
173
174   if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)))
175     return gail_sub_menu_item_new (widget);
176
177   object = g_object_new (GAIL_TYPE_MENU_ITEM, NULL);
178
179   accessible = ATK_OBJECT (object);
180   atk_object_initialize (accessible, widget);
181
182   return accessible;
183 }
184
185 GList *
186 get_children (GtkWidget *submenu)
187 {
188   GList *children;
189
190   children = gtk_container_get_children (GTK_CONTAINER (submenu));
191   if (g_list_length (children) == 0)
192     {
193       /*
194        * If menu is empty it may be because the menu items are created only 
195        * on demand. For example, in gnome-panel the menu items are created
196        * only when "show" signal is emitted on the menu.
197        *
198        * The following hack forces the menu items to be created.
199        */
200       if (!GTK_WIDGET_VISIBLE (submenu))
201         {
202           GTK_WIDGET_SET_FLAGS (submenu, GTK_VISIBLE);
203           g_signal_emit_by_name (submenu, "show");
204           GTK_WIDGET_UNSET_FLAGS (submenu, GTK_VISIBLE);
205         }
206       g_list_free (children);
207       children = gtk_container_get_children (GTK_CONTAINER (submenu));
208     }
209   return children;
210 }
211
212 /*
213  * If a menu item has a submenu return the items of the submenu as the 
214  * accessible children; otherwise expose no accessible children.
215  */
216 static gint
217 gail_menu_item_get_n_children (AtkObject* obj)
218 {
219   GtkWidget *widget;
220   GtkWidget *submenu;
221   gint count = 0;
222
223   g_return_val_if_fail (GAIL_IS_MENU_ITEM (obj), count);
224
225   widget = GTK_ACCESSIBLE (obj)->widget;
226   if (widget == NULL)
227     return count;
228
229   submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
230   if (submenu)
231     {
232       GList *children;
233
234       children = get_children (submenu);
235       count = g_list_length (children);
236       g_list_free (children);
237     }
238   return count;
239 }
240
241 static AtkObject*
242 gail_menu_item_ref_child (AtkObject *obj,
243                           gint       i)
244 {
245   AtkObject  *accessible;
246   GtkWidget *widget;
247   GtkWidget *submenu;
248
249   g_return_val_if_fail (GAIL_IS_MENU_ITEM (obj), NULL);
250   g_return_val_if_fail ((i >= 0), NULL);
251
252   widget = GTK_ACCESSIBLE (obj)->widget;
253   if (widget == NULL)
254     return NULL;
255
256   submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
257   if (submenu)
258     {
259       GList *children;
260       GList *tmp_list;
261
262       children = get_children (submenu);
263       tmp_list = g_list_nth (children, i);
264       if (!tmp_list)
265         {
266           g_list_free (children);
267           return NULL;
268         }
269       accessible = gtk_widget_get_accessible (GTK_WIDGET (tmp_list->data));
270       g_list_free (children);
271       g_object_ref (accessible);
272     }
273   else
274     accessible = NULL;
275
276   return accessible;
277 }
278
279 static void
280 atk_action_interface_init (AtkActionIface *iface)
281 {
282   g_return_if_fail (iface != NULL);
283
284   iface->do_action = gail_menu_item_do_action;
285   iface->get_n_actions = gail_menu_item_get_n_actions;
286   iface->get_description = gail_menu_item_get_description;
287   iface->get_name = gail_menu_item_get_name;
288   iface->get_keybinding = gail_menu_item_get_keybinding;
289   iface->set_description = gail_menu_item_set_description;
290 }
291
292 static gboolean
293 gail_menu_item_do_action (AtkAction *action,
294                           gint      i)
295 {
296   if (i == 0)
297     {
298       GtkWidget *item;
299       GailMenuItem *gail_menu_item;
300
301       item = GTK_ACCESSIBLE (action)->widget;
302       if (item == NULL)
303         /* State is defunct */
304         return FALSE;
305
306       if (!GTK_WIDGET_SENSITIVE (item) || !GTK_WIDGET_VISIBLE (item))
307         return FALSE;
308
309       gail_menu_item = GAIL_MENU_ITEM (action);
310       if (gail_menu_item->action_idle_handler)
311         return FALSE;
312       else
313         {
314           g_object_ref (gail_menu_item);
315           gail_menu_item->action_idle_handler = g_idle_add (idle_do_action, gail_menu_item);
316         }
317       return TRUE;
318     }
319   else
320     return FALSE;
321 }
322
323 static void
324 ensure_menus_unposted (GailMenuItem *menu_item)
325 {
326   AtkObject *parent;
327   GtkWidget *widget;
328
329   parent = atk_object_get_parent (ATK_OBJECT (menu_item));
330   while (parent)
331     {
332       if (GTK_IS_ACCESSIBLE (parent))
333         {
334           widget = GTK_ACCESSIBLE (parent)->widget;
335           if (GTK_IS_MENU (widget))
336             {
337               if (GTK_WIDGET_MAPPED (widget))
338                 gtk_menu_shell_cancel (GTK_MENU_SHELL (widget));
339
340               return;
341             }
342         }
343       parent = atk_object_get_parent (parent);
344     }
345 }
346
347 static gboolean
348 idle_do_action (gpointer data)
349 {
350   GtkWidget *item;
351   GtkWidget *item_parent;
352   GailMenuItem *menu_item;
353   gboolean item_mapped;
354
355   GDK_THREADS_ENTER ();
356
357   menu_item = GAIL_MENU_ITEM (data);
358   menu_item->action_idle_handler = 0;
359   item = GTK_ACCESSIBLE (menu_item)->widget;
360   if (item == NULL /* State is defunct */ ||
361       !GTK_WIDGET_SENSITIVE (item) || !GTK_WIDGET_VISIBLE (item))
362   {
363     g_object_unref (menu_item);
364     GDK_THREADS_LEAVE ();
365     return FALSE;
366   }
367
368   item_parent = gtk_widget_get_parent (item);
369   gtk_menu_shell_select_item (GTK_MENU_SHELL (item_parent), item);
370   item_mapped = GTK_WIDGET_MAPPED (item);
371   /*
372    * This is what is called when <Return> is pressed for a menu item
373    */
374   g_signal_emit_by_name (item_parent, "activate_current",  
375                          /*force_hide*/ 1); 
376   if (!item_mapped)
377     ensure_menus_unposted (menu_item);
378
379   g_object_unref (menu_item);
380   GDK_THREADS_LEAVE ();
381
382   return FALSE;
383 }
384
385 static gint
386 gail_menu_item_get_n_actions (AtkAction *action)
387 {
388   /*
389    * Menu item has 1 action
390    */
391   return 1;
392 }
393
394 static G_CONST_RETURN gchar*
395 gail_menu_item_get_description (AtkAction *action,
396                                 gint      i)
397 {
398   if (i == 0)
399     {
400       GailMenuItem *item;
401
402       item = GAIL_MENU_ITEM (action);
403       return item->click_description;
404     }
405   else
406     return NULL;
407 }
408
409 static G_CONST_RETURN gchar*
410 gail_menu_item_get_name (AtkAction *action,
411                          gint      i)
412 {
413   if (i == 0)
414     return "click";
415   else
416     return NULL;
417 }
418
419 static G_CONST_RETURN gchar*
420 gail_menu_item_get_keybinding (AtkAction *action,
421                                gint      i)
422 {
423   /*
424    * This function returns a string of the form A;B;C where
425    * A is the keybinding for the widget; B is the keybinding to traverse
426    * from the menubar and C is the accelerator.
427    * The items in the keybinding to traverse from the menubar are separated
428    * by ":".
429    */
430   GailMenuItem  *gail_menu_item;
431   gchar *keybinding = NULL;
432   gchar *item_keybinding = NULL;
433   gchar *full_keybinding = NULL;
434   gchar *accelerator = NULL;
435
436   gail_menu_item = GAIL_MENU_ITEM (action);
437   if (i == 0)
438     {
439       GtkWidget *item;
440       GtkWidget *temp_item;
441       GtkWidget *child;
442       GtkWidget *parent;
443
444       item = GTK_ACCESSIBLE (action)->widget;
445       if (item == NULL)
446         /* State is defunct */
447         return NULL;
448
449       temp_item = item;
450       while (TRUE)
451         {
452           GdkModifierType mnemonic_modifier = 0;
453           guint key_val;
454           gchar *key, *temp_keybinding;
455
456           child = gtk_bin_get_child (GTK_BIN (temp_item));
457           if (child == NULL)
458             {
459               /* Possibly a tear off menu item; it could also be a menu 
460                * separator generated by gtk_item_factory_create_items()
461                */
462               return NULL;
463             }
464           parent = gtk_widget_get_parent (temp_item);
465           if (!parent)
466             {
467               /*
468                * parent can be NULL when activating a window from the panel
469                */
470               return NULL;
471             }
472           g_return_val_if_fail (GTK_IS_MENU_SHELL (parent), NULL);
473           if (GTK_IS_MENU_BAR (parent))
474             {
475               GtkWidget *toplevel;
476
477               toplevel = gtk_widget_get_toplevel (parent);
478               if (toplevel && GTK_IS_WINDOW (toplevel))
479                 mnemonic_modifier = gtk_window_get_mnemonic_modifier (
480                                        GTK_WINDOW (toplevel));
481             }
482           if (GTK_IS_LABEL (child))
483             {
484               key_val = gtk_label_get_mnemonic_keyval (GTK_LABEL (child));
485               if (key_val != GDK_VoidSymbol)
486                 {
487                   key = gtk_accelerator_name (key_val, mnemonic_modifier);
488                   if (full_keybinding)
489                     temp_keybinding = g_strconcat (key, ":", full_keybinding, NULL);
490                   else 
491                     temp_keybinding = g_strconcat (key, NULL);
492                   if (temp_item == item)
493                     {
494                       item_keybinding = g_strdup (key); 
495                     }
496                   g_free (key);
497                   g_free (full_keybinding);
498                   full_keybinding = temp_keybinding;
499                 }
500               else
501                 {
502                   /* No keybinding */
503                   g_free (full_keybinding);
504                   full_keybinding = NULL;
505                   break;
506                 }        
507             }        
508           if (GTK_IS_MENU_BAR (parent))
509             /* We have reached the menu bar so we are finished */
510             break;
511           g_return_val_if_fail (GTK_IS_MENU (parent), NULL);
512           temp_item = gtk_menu_get_attach_widget (GTK_MENU (parent));
513           if (!GTK_IS_MENU_ITEM (temp_item))
514             {
515               /* 
516                * Menu is attached to something other than a menu item;
517                * probably an option menu
518                */
519               g_free (full_keybinding);
520               full_keybinding = NULL;
521               break;
522             }
523         }
524
525       parent = gtk_widget_get_parent (item);
526       if (GTK_IS_MENU (parent))
527         {
528           GtkAccelGroup *group; 
529           GtkAccelKey *key;
530
531           group = gtk_menu_get_accel_group (GTK_MENU (parent));
532
533           if (group)
534             {
535               key = gtk_accel_group_find (group, find_accel, item);
536             }
537           else
538             {
539               /*
540                * If the menu item is created using GtkAction and GtkUIManager
541                * we get here.
542                */
543               key = NULL;
544               child = GTK_BIN (item)->child;
545               if (GTK_IS_ACCEL_LABEL (child))
546                 {
547                   GtkAccelLabel *accel_label;
548
549                   accel_label = GTK_ACCEL_LABEL (child);
550                   if (accel_label->accel_closure)
551                     {
552                       key = gtk_accel_group_find (accel_label->accel_group,
553                                                   find_accel_new,
554                                                   accel_label->accel_closure);
555                     }
556                
557                 }
558             }
559
560           if (key)
561             {           
562               accelerator = gtk_accelerator_name (key->accel_key,
563                                                   key->accel_mods);
564             }
565         }
566     }
567   /*
568    * Concatenate the bindings
569    */
570   if (item_keybinding || full_keybinding || accelerator)
571     {
572       gchar *temp;
573       if (item_keybinding)
574         {
575           keybinding = g_strconcat (item_keybinding, KEYBINDING_SEPARATOR, NULL);
576           g_free (item_keybinding);
577         }
578       else
579         keybinding = g_strconcat (KEYBINDING_SEPARATOR, NULL);
580
581       if (full_keybinding)
582         {
583           temp = g_strconcat (keybinding, full_keybinding, 
584                               KEYBINDING_SEPARATOR, NULL);
585           g_free (full_keybinding);
586         }
587       else
588         temp = g_strconcat (keybinding, KEYBINDING_SEPARATOR, NULL);
589
590       g_free (keybinding);
591       keybinding = temp;
592       if (accelerator)
593         {
594           temp = g_strconcat (keybinding, accelerator, NULL);
595           g_free (accelerator);
596           g_free (keybinding);
597           keybinding = temp;
598       }
599     }
600   g_free (gail_menu_item->click_keybinding);
601   gail_menu_item->click_keybinding = keybinding;
602   return keybinding;
603 }
604
605 static gboolean
606 gail_menu_item_set_description (AtkAction      *action,
607                                 gint           i,
608                                 const gchar    *desc)
609 {
610   if (i == 0)
611     {
612       GailMenuItem *item;
613
614       item = GAIL_MENU_ITEM (action);
615       g_free (item->click_description);
616       item->click_description = g_strdup (desc);
617       return TRUE;
618     }
619   else
620     return FALSE;
621 }
622
623 static void
624 gail_menu_item_finalize (GObject *object)
625 {
626   GailMenuItem *menu_item = GAIL_MENU_ITEM (object);
627
628   g_free (menu_item->click_keybinding);
629   g_free (menu_item->click_description);
630   if (menu_item->action_idle_handler)
631     {
632       g_source_remove (menu_item->action_idle_handler);
633       menu_item->action_idle_handler = 0;
634     }
635
636   G_OBJECT_CLASS (parent_class)->finalize (object);
637 }
638
639 static void
640 menu_item_select (GtkItem *item)
641 {
642   menu_item_selection (item, TRUE);
643 }
644
645 static void
646 menu_item_deselect (GtkItem *item)
647 {
648   menu_item_selection (item, FALSE);
649 }
650
651 static void
652 menu_item_selection (GtkItem  *item,
653                      gboolean selected)
654 {
655   AtkObject *obj, *parent;
656
657   obj = gtk_widget_get_accessible (GTK_WIDGET (item));
658   atk_object_notify_state_change (obj, ATK_STATE_SELECTED, selected);
659  
660   parent = atk_object_get_parent (obj);
661   g_signal_emit_by_name (parent, "selection_changed"); 
662 }
663
664 static gboolean
665 find_accel (GtkAccelKey *key,
666             GClosure    *closure,
667             gpointer     data)
668 {
669   /*
670    * We assume that closure->data points to the widget
671    * pending gtk_widget_get_accel_closures being made public
672    */
673   return data == (gpointer) closure->data;
674 }
675
676 static gboolean
677 find_accel_new (GtkAccelKey *key,
678                 GClosure    *closure,
679                 gpointer     data)
680 {
681   return data == (gpointer) closure;
682 }