]> Pileus Git - ~andy/gtk/blob - modules/other/gail/gailmenuitem.c
d5dc8f9417dc316f9e8f66ab143e9a49247be6d7
[~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           gail_menu_item->action_idle_handler =
315             gdk_threads_add_idle_full (G_PRIORITY_DEFAULT_IDLE,
316                                        idle_do_action,
317                                        g_object_ref (gail_menu_item),
318                                        (GDestroyNotify) g_object_unref);
319         }
320       return TRUE;
321     }
322   else
323     return FALSE;
324 }
325
326 static void
327 ensure_menus_unposted (GailMenuItem *menu_item)
328 {
329   AtkObject *parent;
330   GtkWidget *widget;
331
332   parent = atk_object_get_parent (ATK_OBJECT (menu_item));
333   while (parent)
334     {
335       if (GTK_IS_ACCESSIBLE (parent))
336         {
337           widget = GTK_ACCESSIBLE (parent)->widget;
338           if (GTK_IS_MENU (widget))
339             {
340               if (GTK_WIDGET_MAPPED (widget))
341                 gtk_menu_shell_cancel (GTK_MENU_SHELL (widget));
342
343               return;
344             }
345         }
346       parent = atk_object_get_parent (parent);
347     }
348 }
349
350 static gboolean
351 idle_do_action (gpointer data)
352 {
353   GtkWidget *item;
354   GtkWidget *item_parent;
355   GailMenuItem *menu_item;
356   gboolean item_mapped;
357
358   menu_item = GAIL_MENU_ITEM (data);
359   menu_item->action_idle_handler = 0;
360   item = GTK_ACCESSIBLE (menu_item)->widget;
361   if (item == NULL /* State is defunct */ ||
362       !GTK_WIDGET_SENSITIVE (item) || !GTK_WIDGET_VISIBLE (item))
363     return FALSE;
364
365   item_parent = gtk_widget_get_parent (item);
366   gtk_menu_shell_select_item (GTK_MENU_SHELL (item_parent), item);
367   item_mapped = GTK_WIDGET_MAPPED (item);
368   /*
369    * This is what is called when <Return> is pressed for a menu item
370    */
371   g_signal_emit_by_name (item_parent, "activate_current",  
372                          /*force_hide*/ 1); 
373   if (!item_mapped)
374     ensure_menus_unposted (menu_item);
375
376   return FALSE;
377 }
378
379 static gint
380 gail_menu_item_get_n_actions (AtkAction *action)
381 {
382   /*
383    * Menu item has 1 action
384    */
385   return 1;
386 }
387
388 static G_CONST_RETURN gchar*
389 gail_menu_item_get_description (AtkAction *action,
390                                 gint      i)
391 {
392   if (i == 0)
393     {
394       GailMenuItem *item;
395
396       item = GAIL_MENU_ITEM (action);
397       return item->click_description;
398     }
399   else
400     return NULL;
401 }
402
403 static G_CONST_RETURN gchar*
404 gail_menu_item_get_name (AtkAction *action,
405                          gint      i)
406 {
407   if (i == 0)
408     return "click";
409   else
410     return NULL;
411 }
412
413 static G_CONST_RETURN gchar*
414 gail_menu_item_get_keybinding (AtkAction *action,
415                                gint      i)
416 {
417   /*
418    * This function returns a string of the form A;B;C where
419    * A is the keybinding for the widget; B is the keybinding to traverse
420    * from the menubar and C is the accelerator.
421    * The items in the keybinding to traverse from the menubar are separated
422    * by ":".
423    */
424   GailMenuItem  *gail_menu_item;
425   gchar *keybinding = NULL;
426   gchar *item_keybinding = NULL;
427   gchar *full_keybinding = NULL;
428   gchar *accelerator = NULL;
429
430   gail_menu_item = GAIL_MENU_ITEM (action);
431   if (i == 0)
432     {
433       GtkWidget *item;
434       GtkWidget *temp_item;
435       GtkWidget *child;
436       GtkWidget *parent;
437
438       item = GTK_ACCESSIBLE (action)->widget;
439       if (item == NULL)
440         /* State is defunct */
441         return NULL;
442
443       temp_item = item;
444       while (TRUE)
445         {
446           GdkModifierType mnemonic_modifier = 0;
447           guint key_val;
448           gchar *key, *temp_keybinding;
449
450           child = gtk_bin_get_child (GTK_BIN (temp_item));
451           if (child == NULL)
452             {
453               /* Possibly a tear off menu item; it could also be a menu 
454                * separator generated by gtk_item_factory_create_items()
455                */
456               return NULL;
457             }
458           parent = gtk_widget_get_parent (temp_item);
459           if (!parent)
460             {
461               /*
462                * parent can be NULL when activating a window from the panel
463                */
464               return NULL;
465             }
466           g_return_val_if_fail (GTK_IS_MENU_SHELL (parent), NULL);
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 = gtk_window_get_mnemonic_modifier (
474                                        GTK_WINDOW (toplevel));
475             }
476           if (GTK_IS_LABEL (child))
477             {
478               key_val = gtk_label_get_mnemonic_keyval (GTK_LABEL (child));
479               if (key_val != GDK_VoidSymbol)
480                 {
481                   key = gtk_accelerator_name (key_val, mnemonic_modifier);
482                   if (full_keybinding)
483                     temp_keybinding = g_strconcat (key, ":", full_keybinding, NULL);
484                   else 
485                     temp_keybinding = g_strconcat (key, NULL);
486                   if (temp_item == item)
487                     {
488                       item_keybinding = g_strdup (key); 
489                     }
490                   g_free (key);
491                   g_free (full_keybinding);
492                   full_keybinding = temp_keybinding;
493                 }
494               else
495                 {
496                   /* No keybinding */
497                   g_free (full_keybinding);
498                   full_keybinding = NULL;
499                   break;
500                 }        
501             }        
502           if (GTK_IS_MENU_BAR (parent))
503             /* We have reached the menu bar so we are finished */
504             break;
505           g_return_val_if_fail (GTK_IS_MENU (parent), NULL);
506           temp_item = gtk_menu_get_attach_widget (GTK_MENU (parent));
507           if (!GTK_IS_MENU_ITEM (temp_item))
508             {
509               /* 
510                * Menu is attached to something other than a menu item;
511                * probably an option menu
512                */
513               g_free (full_keybinding);
514               full_keybinding = NULL;
515               break;
516             }
517         }
518
519       parent = gtk_widget_get_parent (item);
520       if (GTK_IS_MENU (parent))
521         {
522           GtkAccelGroup *group; 
523           GtkAccelKey *key;
524
525           group = gtk_menu_get_accel_group (GTK_MENU (parent));
526
527           if (group)
528             {
529               key = gtk_accel_group_find (group, find_accel, item);
530             }
531           else
532             {
533               /*
534                * If the menu item is created using GtkAction and GtkUIManager
535                * we get here.
536                */
537               key = NULL;
538               child = GTK_BIN (item)->child;
539               if (GTK_IS_ACCEL_LABEL (child))
540                 {
541                   GtkAccelLabel *accel_label;
542
543                   accel_label = GTK_ACCEL_LABEL (child);
544                   if (accel_label->accel_closure)
545                     {
546                       key = gtk_accel_group_find (accel_label->accel_group,
547                                                   find_accel_new,
548                                                   accel_label->accel_closure);
549                     }
550                
551                 }
552             }
553
554           if (key)
555             {           
556               accelerator = gtk_accelerator_name (key->accel_key,
557                                                   key->accel_mods);
558             }
559         }
560     }
561   /*
562    * Concatenate the bindings
563    */
564   if (item_keybinding || full_keybinding || accelerator)
565     {
566       gchar *temp;
567       if (item_keybinding)
568         {
569           keybinding = g_strconcat (item_keybinding, KEYBINDING_SEPARATOR, NULL);
570           g_free (item_keybinding);
571         }
572       else
573         keybinding = g_strconcat (KEYBINDING_SEPARATOR, NULL);
574
575       if (full_keybinding)
576         {
577           temp = g_strconcat (keybinding, full_keybinding, 
578                               KEYBINDING_SEPARATOR, NULL);
579           g_free (full_keybinding);
580         }
581       else
582         temp = g_strconcat (keybinding, KEYBINDING_SEPARATOR, NULL);
583
584       g_free (keybinding);
585       keybinding = temp;
586       if (accelerator)
587         {
588           temp = g_strconcat (keybinding, accelerator, NULL);
589           g_free (accelerator);
590           g_free (keybinding);
591           keybinding = temp;
592       }
593     }
594   g_free (gail_menu_item->click_keybinding);
595   gail_menu_item->click_keybinding = keybinding;
596   return keybinding;
597 }
598
599 static gboolean
600 gail_menu_item_set_description (AtkAction      *action,
601                                 gint           i,
602                                 const gchar    *desc)
603 {
604   if (i == 0)
605     {
606       GailMenuItem *item;
607
608       item = GAIL_MENU_ITEM (action);
609       g_free (item->click_description);
610       item->click_description = g_strdup (desc);
611       return TRUE;
612     }
613   else
614     return FALSE;
615 }
616
617 static void
618 gail_menu_item_finalize (GObject *object)
619 {
620   GailMenuItem *menu_item = GAIL_MENU_ITEM (object);
621
622   g_free (menu_item->click_keybinding);
623   g_free (menu_item->click_description);
624   if (menu_item->action_idle_handler)
625     {
626       g_source_remove (menu_item->action_idle_handler);
627       menu_item->action_idle_handler = 0;
628     }
629
630   G_OBJECT_CLASS (parent_class)->finalize (object);
631 }
632
633 static void
634 menu_item_select (GtkItem *item)
635 {
636   menu_item_selection (item, TRUE);
637 }
638
639 static void
640 menu_item_deselect (GtkItem *item)
641 {
642   menu_item_selection (item, FALSE);
643 }
644
645 static void
646 menu_item_selection (GtkItem  *item,
647                      gboolean selected)
648 {
649   AtkObject *obj, *parent;
650
651   obj = gtk_widget_get_accessible (GTK_WIDGET (item));
652   atk_object_notify_state_change (obj, ATK_STATE_SELECTED, selected);
653  
654   parent = atk_object_get_parent (obj);
655   g_signal_emit_by_name (parent, "selection_changed"); 
656 }
657
658 static gboolean
659 find_accel (GtkAccelKey *key,
660             GClosure    *closure,
661             gpointer     data)
662 {
663   /*
664    * We assume that closure->data points to the widget
665    * pending gtk_widget_get_accel_closures being made public
666    */
667   return data == (gpointer) closure->data;
668 }
669
670 static gboolean
671 find_accel_new (GtkAccelKey *key,
672                 GClosure    *closure,
673                 gpointer     data)
674 {
675   return data == (gpointer) closure;
676 }