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