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