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