]> Pileus Git - ~andy/gtk/blob - gtk/a11y/gailmenuitem.c
gail: No need to include modules/other in CFLAGS anymore
[~andy/gtk] / gtk / a11y / 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 <string.h>
21 #include <gtk/gtk.h>
22 #include <gdk/gdkkeysyms.h>
23 #include "gailmenuitem.h"
24 #include "gailsubmenuitem.h"
25 #include <libgail-util/gailmisc.h>
26
27 #define KEYBINDING_SEPARATOR ";"
28
29 static void gail_menu_item_class_init  (GailMenuItemClass *klass);
30 static void gail_menu_item_init        (GailMenuItem      *menu_item);
31
32 static void                  gail_menu_item_real_initialize
33                                                           (AtkObject       *obj,
34                                                            gpointer        data);
35 static gint                  gail_menu_item_get_n_children (AtkObject      *obj);
36 static AtkObject*            gail_menu_item_ref_child      (AtkObject      *obj,
37                                                             gint           i);
38 static AtkStateSet*          gail_menu_item_ref_state_set  (AtkObject      *obj);
39 static void                  gail_menu_item_finalize       (GObject        *object);
40 static void                  gail_menu_item_label_map_gtk  (GtkWidget     *widget,
41                                                             gpointer       data);
42 static void                  gail_menu_item_init_textutil  (GailMenuItem  *item,
43                                                             GtkWidget     *label);
44 static void                  gail_menu_item_notify_label_gtk (GObject       *obj,
45                                                               GParamSpec    *pspec,
46                                                               gpointer      data);
47 static const gchar *         gail_menu_item_get_name         (AtkObject    *object);
48
49
50 static void                  atk_action_interface_init     (AtkActionIface *iface);
51 static gboolean              gail_menu_item_do_action      (AtkAction      *action,
52                                                             gint           i);
53 static gboolean              idle_do_action                (gpointer       data);
54 static gint                  gail_menu_item_get_n_actions  (AtkAction      *action);
55 static const gchar* gail_menu_item_get_description(AtkAction      *action,
56                                                             gint           i);
57 static const gchar* gail_menu_item_action_get_name (AtkAction      *action,
58                                                              gint           i);
59 static const gchar* gail_menu_item_get_keybinding (AtkAction      *action,
60                                                             gint           i);
61 static gboolean              gail_menu_item_set_description(AtkAction      *action,
62                                                             gint           i,
63                                                             const gchar    *desc);
64 static void                  menu_item_select              (GtkMenuItem        *item);
65 static void                  menu_item_deselect            (GtkMenuItem        *item);
66 static void                  menu_item_selection           (GtkMenuItem        *item,
67                                                             gboolean       selected);
68 static gboolean              find_accel                    (GtkAccelKey    *key,
69                                                             GClosure       *closure,
70                                                             gpointer       data);
71 static gboolean              find_accel_new                (GtkAccelKey    *key,
72                                                             GClosure       *closure,
73                                                             gpointer       data);
74 /* atktext.h */
75 static void       atk_text_interface_init          (AtkTextIface      *iface);
76
77 static gchar*     gail_menu_item_get_text          (AtkText           *text,
78                                                     gint               start_pos,
79                                                     gint               end_pos);
80 static gunichar   gail_menu_item_get_character_at_offset (AtkText     *text,
81                                                           gint         offset);
82 static gchar*     gail_menu_item_get_text_before_offset  (AtkText     *text,
83                                                           gint         offset,
84                                                           AtkTextBoundary  boundary_type,
85                                                           gint        *start_offset,
86                                                           gint        *end_offset);
87 static gchar*     gail_menu_item_get_text_at_offset      (AtkText     *text,
88                                                           gint         offset,
89                                                           AtkTextBoundary   boundary_type,
90                                                           gint        *start_offset,
91                                                           gint        *end_offset);
92 static gchar*     gail_menu_item_get_text_after_offset   (AtkText     *text,
93                                                           gint         offset,
94                                                           AtkTextBoundary   boundary_type,
95                                                           gint        *start_offset,
96                                                           gint        *end_offset);
97 static gint       gail_menu_item_get_character_count     (AtkText     *text);
98 static void       gail_menu_item_get_character_extents   (AtkText     *text,
99                                                           gint         offset,
100                                                           gint        *x,
101                                                           gint        *y,
102                                                           gint        *width,
103                                                           gint        *height,
104                                                           AtkCoordType  coords);
105 static gint      gail_menu_item_get_offset_at_point      (AtkText     *text,
106                                                           gint         x,
107                                                           gint         y,
108                                                           AtkCoordType  coords);
109 static AtkAttributeSet* gail_menu_item_get_run_attributes (AtkText    *text,
110                                                            gint        offset,
111                                                            gint       *start_offset,
112                                                            gint       *end_offset);
113 static AtkAttributeSet* gail_menu_item_get_default_attributes (AtkText *text);
114 static GtkWidget*       get_label_from_container   (GtkWidget    *container);
115 static gchar *          get_text_from_label_widget (GtkWidget    *widget);
116
117
118 G_DEFINE_TYPE_WITH_CODE (GailMenuItem, gail_menu_item, GAIL_TYPE_CONTAINER,
119                          G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, atk_action_interface_init)
120                          G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init))
121
122 static void
123 gail_menu_item_class_init (GailMenuItemClass *klass)
124 {
125   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
126   AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
127
128   gobject_class->finalize = gail_menu_item_finalize;
129
130   class->get_n_children = gail_menu_item_get_n_children;
131   class->ref_child = gail_menu_item_ref_child;
132   class->ref_state_set = gail_menu_item_ref_state_set;
133   class->initialize = gail_menu_item_real_initialize;
134   class->get_name = gail_menu_item_get_name;
135 }
136
137 static void
138 gail_menu_item_real_initialize (AtkObject *obj,
139                                 gpointer  data)
140 {
141   GtkWidget *widget;
142   GtkWidget *parent;
143   GailMenuItem *item = GAIL_MENU_ITEM (obj);
144   GtkWidget *label;
145
146   ATK_OBJECT_CLASS (gail_menu_item_parent_class)->initialize (obj, data);
147
148   item->textutil = NULL;
149   item->text = NULL;
150
151   label = get_label_from_container (GTK_WIDGET (data));
152   if (gtk_widget_get_mapped (label))
153     gail_menu_item_init_textutil (item, label);
154   else
155     g_signal_connect (label,
156                       "map",
157                       G_CALLBACK (gail_menu_item_label_map_gtk),
158                       item);
159
160   g_signal_connect (data,
161                     "select",
162                     G_CALLBACK (menu_item_select),
163                     NULL);
164   g_signal_connect (data,
165                     "deselect",
166                     G_CALLBACK (menu_item_deselect),
167                     NULL);
168   widget = GTK_WIDGET (data);
169   parent = gtk_widget_get_parent (widget);
170   if (GTK_IS_MENU (parent))
171     {
172       GtkWidget *parent_widget;
173
174       parent_widget =  gtk_menu_get_attach_widget (GTK_MENU (parent));
175
176       if (!GTK_IS_MENU_ITEM (parent_widget))
177         parent_widget = gtk_widget_get_parent (widget);
178        if (parent_widget)
179         {
180           atk_object_set_parent (obj, gtk_widget_get_accessible (parent_widget));
181         }
182     }
183   g_object_set_data (G_OBJECT (obj), "atk-component-layer",
184                      GINT_TO_POINTER (ATK_LAYER_POPUP));
185
186   if (GTK_IS_TEAROFF_MENU_ITEM (data))
187     obj->role = ATK_ROLE_TEAR_OFF_MENU_ITEM;
188   else if (GTK_IS_SEPARATOR_MENU_ITEM (data))
189     obj->role = ATK_ROLE_SEPARATOR;
190   else
191     obj->role = ATK_ROLE_MENU_ITEM;
192 }
193
194 static void
195 gail_menu_item_init (GailMenuItem *menu_item)
196 {
197   menu_item->click_keybinding = NULL;
198   menu_item->click_description = NULL;
199 }
200
201 static void
202 gail_menu_item_label_map_gtk (GtkWidget *widget,
203                               gpointer data)
204 {
205   GailMenuItem *item;
206
207   item = GAIL_MENU_ITEM (data);
208   gail_menu_item_init_textutil (item, widget);
209 }
210
211 static void
212 gail_menu_item_notify_label_gtk (GObject           *obj,
213                                  GParamSpec        *pspec,
214                                  gpointer           data)
215 {
216   AtkObject* atk_obj = ATK_OBJECT (data);
217   GtkLabel *label;
218   GailMenuItem *menu_item;
219
220   if (strcmp (pspec->name, "label") == 0)
221     {
222       const gchar* label_text;
223
224       label = GTK_LABEL (obj);
225
226       label_text = gtk_label_get_text (label);
227
228       menu_item = GAIL_MENU_ITEM (atk_obj);
229       gail_text_util_text_setup (menu_item->textutil, label_text);
230
231       if (atk_obj->name == NULL)
232       {
233         /*
234          * The label has changed so notify a change in accessible-name
235          */
236         g_object_notify (G_OBJECT (atk_obj), "accessible-name");
237       }
238       /*
239        * The label is the only property which can be changed
240        */
241       g_signal_emit_by_name (atk_obj, "visible_data_changed");
242     }
243 }
244
245 static void
246 gail_menu_item_init_textutil (GailMenuItem  *item,
247                               GtkWidget *label)
248 {
249   const gchar *label_text;
250
251   if (item->textutil == NULL)
252     {
253       item->textutil = gail_text_util_new ();
254       g_signal_connect (label,
255                         "notify",
256                         (GCallback) gail_menu_item_notify_label_gtk,
257                         item);     
258     }
259   label_text = get_text_from_label_widget (label);
260   gail_text_util_text_setup (item->textutil, label_text);
261 }
262
263 /* atktext.h */
264
265 static void
266 atk_text_interface_init (AtkTextIface *iface)
267 {
268   iface->get_text = gail_menu_item_get_text;
269   iface->get_character_at_offset = gail_menu_item_get_character_at_offset;
270   iface->get_text_before_offset = gail_menu_item_get_text_before_offset;
271   iface->get_text_at_offset = gail_menu_item_get_text_at_offset;
272   iface->get_text_after_offset = gail_menu_item_get_text_after_offset;
273   iface->get_character_count = gail_menu_item_get_character_count;
274   iface->get_character_extents = gail_menu_item_get_character_extents;
275   iface->get_offset_at_point = gail_menu_item_get_offset_at_point;
276   iface->get_run_attributes = gail_menu_item_get_run_attributes;
277   iface->get_default_attributes = gail_menu_item_get_default_attributes;
278 }
279
280 static gchar*
281 gail_menu_item_get_text (AtkText *text,
282                          gint    start_pos,
283                          gint    end_pos)
284 {
285   GtkWidget *widget;
286   GtkWidget *label;
287   GailMenuItem *item;
288   const gchar *label_text;
289
290   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
291   if (widget == NULL)
292     /* State is defunct */
293     return NULL;
294
295   label = get_label_from_container (widget);
296
297   item = GAIL_MENU_ITEM (text);
298   if (!item->textutil)
299     gail_menu_item_init_textutil (item, label);
300
301   label_text = get_text_from_label_widget (label);
302
303   if (label_text == NULL)
304     return NULL;
305   else
306     return gail_text_util_get_substring (item->textutil,
307                                          start_pos, end_pos);
308 }
309
310 static gchar*
311 gail_menu_item_get_text_before_offset (AtkText         *text,
312                                         gint            offset,
313                                         AtkTextBoundary boundary_type,
314                                         gint            *start_offset,
315                                         gint            *end_offset)
316 {
317   GtkWidget *widget;
318   GtkWidget *label;
319   GailMenuItem *item;
320   
321   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
322   
323   if (widget == NULL)
324     /* State is defunct */
325     return NULL;
326   
327   /* Get label */
328   label = get_label_from_container (widget);
329
330   item = GAIL_MENU_ITEM (text);
331   if (!item->textutil)
332     gail_menu_item_init_textutil (item, label);
333
334   return gail_text_util_get_text (item->textutil,
335                            NULL, GAIL_BEFORE_OFFSET, 
336                            boundary_type, offset, start_offset, end_offset); 
337 }
338
339 static gchar*
340 gail_menu_item_get_text_at_offset (AtkText         *text,
341                                    gint            offset,
342                                    AtkTextBoundary boundary_type,
343                                    gint            *start_offset,
344                                    gint            *end_offset)
345 {
346   GtkWidget *widget;
347   GtkWidget *label;
348   GailMenuItem *item;
349  
350   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
351   
352   if (widget == NULL)
353     /* State is defunct */
354     return NULL;
355   
356   /* Get label */
357   label = get_label_from_container (widget);
358
359   item = GAIL_MENU_ITEM (text);
360   if (!item->textutil)
361     gail_menu_item_init_textutil (item, label);
362
363   return gail_text_util_get_text (item->textutil,
364                               NULL, GAIL_AT_OFFSET, 
365                               boundary_type, offset, start_offset, end_offset);
366 }
367
368 static gchar*
369 gail_menu_item_get_text_after_offset (AtkText         *text,
370                                       gint            offset,
371                                       AtkTextBoundary boundary_type,
372                                       gint            *start_offset,
373                                       gint            *end_offset)
374 {
375   GtkWidget *widget;
376   GtkWidget *label;
377   GailMenuItem *item;
378
379   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
380   
381   if (widget == NULL)
382   {
383     /* State is defunct */
384     return NULL;
385   }
386   
387   /* Get label */
388   label = get_label_from_container (widget);
389
390   item = GAIL_MENU_ITEM (text);
391   if (!item->textutil)
392     gail_menu_item_init_textutil (item, label);
393
394   return gail_text_util_get_text (item->textutil,
395                            NULL, GAIL_AFTER_OFFSET, 
396                            boundary_type, offset, start_offset, end_offset);
397 }
398
399 static gint
400 gail_menu_item_get_character_count (AtkText *atk_text)
401 {
402   gchar *text;
403   gint len;
404
405   text = gail_menu_item_get_text (atk_text, 0, -1);
406   if (text)
407     {
408       len = g_utf8_strlen (text, -1);
409       g_free (text);
410     }
411   else
412     len = 0;
413
414   return len;
415 }
416
417 static void
418 gail_menu_item_get_character_extents (AtkText      *text,
419                                       gint         offset,
420                                       gint         *x,
421                                       gint         *y,
422                                       gint         *width,
423                                       gint         *height,
424                                       AtkCoordType coords)
425 {
426   GtkWidget *widget;
427   GtkWidget *label;
428   PangoRectangle char_rect;
429   gint index, x_layout, y_layout;
430   const gchar *label_text;
431  
432   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
433
434   if (widget == NULL)
435     /* State is defunct */
436     return;
437
438   label = get_label_from_container (widget);
439
440   if (!GTK_IS_LABEL(label))
441     return;
442
443   gtk_label_get_layout_offsets (GTK_LABEL (label), &x_layout, &y_layout);
444   label_text = gtk_label_get_text (GTK_LABEL (label));
445   index = g_utf8_offset_to_pointer (label_text, offset) - label_text;
446   pango_layout_index_to_pos (gtk_label_get_layout (GTK_LABEL (label)), index, &char_rect);
447
448   gail_misc_get_extents_from_pango_rectangle (label, &char_rect,
449                     x_layout, y_layout, x, y, width, height, coords);
450 }
451
452 static gint 
453 gail_menu_item_get_offset_at_point (AtkText      *text,
454                                gint         x,
455                                gint         y,
456                                AtkCoordType coords)
457
458   GtkWidget *widget;
459   GtkWidget *label;
460   gint index, x_layout, y_layout;
461   const gchar *label_text;
462
463   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
464   if (widget == NULL)
465     /* State is defunct */
466     return -1;
467
468   label = get_label_from_container (widget);
469
470   if (!GTK_IS_LABEL(label))
471     return -1;
472   
473   gtk_label_get_layout_offsets (GTK_LABEL (label), &x_layout, &y_layout);
474   
475   index = gail_misc_get_index_at_point_in_layout (label, 
476                                               gtk_label_get_layout (GTK_LABEL (label)), 
477                                               x_layout, y_layout, x, y, coords);
478   label_text = gtk_label_get_text (GTK_LABEL (label));
479   if (index == -1)
480     {
481       if (coords == ATK_XY_WINDOW || coords == ATK_XY_SCREEN)
482         return g_utf8_strlen (label_text, -1);
483
484       return index;  
485     }
486   else
487     return g_utf8_pointer_to_offset (label_text, label_text + index);  
488 }
489
490 static AtkAttributeSet*
491 gail_menu_item_get_run_attributes (AtkText *text,
492                               gint    offset,
493                               gint    *start_offset,
494                               gint    *end_offset)
495 {
496   GtkWidget *widget;
497   GtkWidget *label;
498   AtkAttributeSet *at_set = NULL;
499   GtkJustification justify;
500   GtkTextDirection dir;
501
502   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
503   if (widget == NULL)
504     /* State is defunct */
505     return NULL;
506
507   label = get_label_from_container (widget);
508
509   if (!GTK_IS_LABEL(label))
510     return NULL;
511   
512   /* Get values set for entire label, if any */
513   justify = gtk_label_get_justify (GTK_LABEL (label));
514   if (justify != GTK_JUSTIFY_CENTER)
515     {
516       at_set = gail_misc_add_attribute (at_set, 
517                                         ATK_TEXT_ATTR_JUSTIFICATION,
518      g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_JUSTIFICATION, justify)));
519     }
520   dir = gtk_widget_get_direction (label);
521   if (dir == GTK_TEXT_DIR_RTL)
522     {
523       at_set = gail_misc_add_attribute (at_set, 
524                                         ATK_TEXT_ATTR_DIRECTION,
525      g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION, dir)));
526     }
527
528   at_set = gail_misc_layout_get_run_attributes (at_set,
529                                                 gtk_label_get_layout (GTK_LABEL (label)),
530                                                 (gchar *) gtk_label_get_text (GTK_LABEL (label)),
531                                                 offset,
532                                                 start_offset,
533                                                 end_offset);
534   return at_set;
535 }
536
537 static AtkAttributeSet*
538 gail_menu_item_get_default_attributes (AtkText *text)
539 {
540   GtkWidget *widget;
541   GtkWidget *label;
542   AtkAttributeSet *at_set = NULL;
543
544   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
545   if (widget == NULL)
546     /* State is defunct */
547     return NULL;
548
549   label = get_label_from_container (widget);
550
551   if (!GTK_IS_LABEL(label))
552     return NULL;
553
554   at_set = gail_misc_get_default_attributes (at_set,
555                                              gtk_label_get_layout (GTK_LABEL (label)),
556                                              widget);
557   return at_set;
558 }
559
560 static gunichar 
561 gail_menu_item_get_character_at_offset (AtkText *text,
562                                    gint   offset)
563 {
564   gchar *string;
565   gchar *index;
566   gunichar ch;
567
568   string = gail_menu_item_get_text (text, 0, -1);
569
570   if (string == NULL || offset >= g_utf8_strlen (string, -1))
571     ch = '\0';
572   else
573     {
574       index = g_utf8_offset_to_pointer (string, offset);
575       ch = g_utf8_get_char (index);
576     }
577
578   g_free (string);
579
580   return ch;
581 }
582
583 static GtkWidget*
584 get_label_from_container (GtkWidget *container)
585 {
586   GtkWidget *label;
587   GList *children, *tmp_list;
588
589   if (!GTK_IS_CONTAINER (container))
590     return NULL;
591  
592   children = gtk_container_get_children (GTK_CONTAINER (container));
593   label = NULL;
594
595   for (tmp_list = children; tmp_list != NULL; tmp_list = tmp_list->next) 
596     {
597       if (GTK_IS_LABEL (tmp_list->data))
598         {
599           label = tmp_list->data;
600           break;
601         }
602       else if (GTK_IS_CELL_VIEW (tmp_list->data))
603         {
604           label = tmp_list->data;
605           break;
606         }
607       /*
608  *        * Get label from menu item in desktop background preferences
609  *               * option menu. See bug #144084.
610  *                      */
611       else if (GTK_IS_BOX (tmp_list->data))
612         {
613           label = get_label_from_container (GTK_WIDGET (tmp_list->data));
614           if (label)
615             break;
616         }
617     }
618   g_list_free (children);
619
620   return label;
621 }
622
623 static gchar *
624 get_text_from_label_widget (GtkWidget *label)
625 {
626   if (GTK_IS_LABEL (label))
627     return g_strdup (gtk_label_get_text (GTK_LABEL (label)));
628   else if (GTK_IS_CELL_VIEW (label))
629     {
630       GList *cells, *l;
631       GtkTreeModel *model;
632       GtkTreeIter iter;
633       GtkTreePath *path;
634       GtkCellArea *area;
635       gchar *text;
636
637       model = gtk_cell_view_get_model (GTK_CELL_VIEW (label));
638       path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (label));
639       gtk_tree_model_get_iter (model, &iter, path);
640       gtk_tree_path_free (path);
641
642       area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (label));
643
644       gtk_cell_area_apply_attributes (area, model, &iter, FALSE, FALSE);
645
646       cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (label));
647       for (l = cells; l; l = l->next)
648         {
649           GtkCellRenderer *cell = l->data;
650
651           if (GTK_IS_CELL_RENDERER_TEXT (cell))
652             {
653               g_object_get (cell, "text", &text, NULL);
654               break;
655             }
656         }
657
658       g_list_free (cells);
659
660       return text;
661     }
662
663   return NULL;
664 }
665
666 AtkObject*
667 gail_menu_item_new (GtkWidget *widget)
668 {
669   GObject *object;
670   AtkObject *accessible;
671   
672   g_return_val_if_fail (GTK_IS_MENU_ITEM (widget), NULL);
673
674   if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)))
675     return gail_sub_menu_item_new (widget);
676
677   object = g_object_new (GAIL_TYPE_MENU_ITEM, NULL);
678
679   accessible = ATK_OBJECT (object);
680   atk_object_initialize (accessible, widget);
681
682   return accessible;
683 }
684
685 GList *
686 get_children (GtkWidget *submenu)
687 {
688   GList *children;
689
690   children = gtk_container_get_children (GTK_CONTAINER (submenu));
691   if (g_list_length (children) == 0)
692     {
693       /*
694        * If menu is empty it may be because the menu items are created only 
695        * on demand. For example, in gnome-panel the menu items are created
696        * only when "show" signal is emitted on the menu.
697        *
698        * The following hack forces the menu items to be created.
699        */
700       if (!gtk_widget_get_visible (submenu))
701         {
702           /* FIXME GTK_WIDGET_SET_FLAGS (submenu, GTK_VISIBLE); */
703           g_signal_emit_by_name (submenu, "show");
704           /* FIXME GTK_WIDGET_UNSET_FLAGS (submenu, GTK_VISIBLE); */
705         }
706       g_list_free (children);
707       children = gtk_container_get_children (GTK_CONTAINER (submenu));
708     }
709   return children;
710 }
711
712 /*
713  * If a menu item has a submenu return the items of the submenu as the 
714  * accessible children; otherwise expose no accessible children.
715  */
716 static gint
717 gail_menu_item_get_n_children (AtkObject* obj)
718 {
719   GtkWidget *widget;
720   GtkWidget *submenu;
721   gint count = 0;
722
723   g_return_val_if_fail (GAIL_IS_MENU_ITEM (obj), count);
724
725   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
726   if (widget == NULL)
727     return count;
728
729   submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
730   if (submenu)
731     {
732       GList *children;
733
734       children = get_children (submenu);
735       count = g_list_length (children);
736       g_list_free (children);
737     }
738   return count;
739 }
740
741 static AtkObject*
742 gail_menu_item_ref_child (AtkObject *obj,
743                           gint       i)
744 {
745   AtkObject  *accessible;
746   GtkWidget *widget;
747   GtkWidget *submenu;
748
749   g_return_val_if_fail (GAIL_IS_MENU_ITEM (obj), NULL);
750   g_return_val_if_fail ((i >= 0), NULL);
751
752   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
753   if (widget == NULL)
754     return NULL;
755
756   submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
757   if (submenu)
758     {
759       GList *children;
760       GList *tmp_list;
761
762       children = get_children (submenu);
763       tmp_list = g_list_nth (children, i);
764       if (!tmp_list)
765         {
766           g_list_free (children);
767           return NULL;
768         }
769       accessible = gtk_widget_get_accessible (GTK_WIDGET (tmp_list->data));
770       g_list_free (children);
771       g_object_ref (accessible);
772     }
773   else
774     accessible = NULL;
775
776   return accessible;
777 }
778
779 static AtkStateSet*
780 gail_menu_item_ref_state_set (AtkObject *obj)
781 {
782   AtkObject *menu_item;
783   AtkStateSet *state_set, *parent_state_set;
784
785   state_set = ATK_OBJECT_CLASS (gail_menu_item_parent_class)->ref_state_set (obj);
786
787   menu_item = atk_object_get_parent (obj);
788
789   if (menu_item)
790     {
791       if (!GTK_IS_MENU_ITEM (gtk_accessible_get_widget (GTK_ACCESSIBLE (menu_item))))
792         return state_set;
793
794       parent_state_set = atk_object_ref_state_set (menu_item);
795       if (!atk_state_set_contains_state (parent_state_set, ATK_STATE_SELECTED))
796         {
797           atk_state_set_remove_state (state_set, ATK_STATE_FOCUSED);
798           atk_state_set_remove_state (state_set, ATK_STATE_SHOWING);
799         }
800     }
801   return state_set;
802 }
803
804 static void
805 atk_action_interface_init (AtkActionIface *iface)
806 {
807   iface->do_action = gail_menu_item_do_action;
808   iface->get_n_actions = gail_menu_item_get_n_actions;
809   iface->get_description = gail_menu_item_get_description;
810   iface->get_name = gail_menu_item_action_get_name;
811   iface->get_keybinding = gail_menu_item_get_keybinding;
812   iface->set_description = gail_menu_item_set_description;
813 }
814
815 static gboolean
816 gail_menu_item_do_action (AtkAction *action,
817                           gint      i)
818 {
819   if (i == 0)
820     {
821       GtkWidget *item;
822       GailMenuItem *gail_menu_item;
823
824       item = gtk_accessible_get_widget (GTK_ACCESSIBLE (action));
825       if (item == NULL)
826         /* State is defunct */
827         return FALSE;
828
829       if (!gtk_widget_get_sensitive (item) || !gtk_widget_get_visible (item))
830         return FALSE;
831
832       gail_menu_item = GAIL_MENU_ITEM (action);
833       if (gail_menu_item->action_idle_handler)
834         return FALSE;
835       else
836         {
837           gail_menu_item->action_idle_handler =
838             gdk_threads_add_idle_full (G_PRIORITY_DEFAULT_IDLE,
839                                        idle_do_action,
840                                        g_object_ref (gail_menu_item),
841                                        (GDestroyNotify) g_object_unref);
842         }
843       return TRUE;
844     }
845   else
846     return FALSE;
847 }
848
849 static void
850 ensure_menus_unposted (GailMenuItem *menu_item)
851 {
852   AtkObject *parent;
853   GtkWidget *widget;
854
855   parent = atk_object_get_parent (ATK_OBJECT (menu_item));
856   while (parent)
857     {
858       widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (parent));
859       if (GTK_IS_MENU (widget))
860         {
861           if (gtk_widget_get_mapped (widget))
862             gtk_menu_shell_cancel (GTK_MENU_SHELL (widget));
863
864           return;
865         }
866       parent = atk_object_get_parent (parent);
867     }
868 }
869
870 static gboolean
871 idle_do_action (gpointer data)
872 {
873   GtkWidget *item;
874   GtkWidget *item_parent;
875   GailMenuItem *menu_item;
876   gboolean item_mapped;
877
878   menu_item = GAIL_MENU_ITEM (data);
879   menu_item->action_idle_handler = 0;
880   item = gtk_accessible_get_widget (GTK_ACCESSIBLE (menu_item));
881   if (item == NULL /* State is defunct */ ||
882       !gtk_widget_get_sensitive (item) || !gtk_widget_get_visible (item))
883     return FALSE;
884
885   item_parent = gtk_widget_get_parent (item);
886   gtk_menu_shell_select_item (GTK_MENU_SHELL (item_parent), item);
887   item_mapped = gtk_widget_get_mapped (item);
888   /*
889    * This is what is called when <Return> is pressed for a menu item
890    */
891   g_signal_emit_by_name (item_parent, "activate_current",  
892                          /*force_hide*/ 1); 
893   if (!item_mapped)
894     ensure_menus_unposted (menu_item);
895
896   return FALSE;
897 }
898
899 static gint
900 gail_menu_item_get_n_actions (AtkAction *action)
901 {
902   /*
903    * Menu item has 1 action
904    */
905   return 1;
906 }
907
908 static const gchar*
909 gail_menu_item_get_description (AtkAction *action,
910                                 gint      i)
911 {
912   if (i == 0)
913     {
914       GailMenuItem *item;
915
916       item = GAIL_MENU_ITEM (action);
917       return item->click_description;
918     }
919   else
920     return NULL;
921 }
922
923 static const gchar*
924 gail_menu_item_action_get_name (AtkAction *action,
925                                 gint      i)
926 {
927   if (i == 0)
928     return "click";
929   else
930     return NULL;
931 }
932
933 static const gchar*
934 gail_menu_item_get_keybinding (AtkAction *action,
935                                gint      i)
936 {
937   /*
938    * This function returns a string of the form A;B;C where
939    * A is the keybinding for the widget; B is the keybinding to traverse
940    * from the menubar and C is the accelerator.
941    * The items in the keybinding to traverse from the menubar are separated
942    * by ":".
943    */
944   GailMenuItem  *gail_menu_item;
945   gchar *keybinding = NULL;
946   gchar *item_keybinding = NULL;
947   gchar *full_keybinding = NULL;
948   gchar *accelerator = NULL;
949
950   gail_menu_item = GAIL_MENU_ITEM (action);
951   if (i == 0)
952     {
953       GtkWidget *item;
954       GtkWidget *temp_item;
955       GtkWidget *child;
956       GtkWidget *parent;
957
958       item = gtk_accessible_get_widget (GTK_ACCESSIBLE (action));
959       if (item == NULL)
960         /* State is defunct */
961         return NULL;
962
963       temp_item = item;
964       while (TRUE)
965         {
966           GdkModifierType mnemonic_modifier = 0;
967           guint key_val;
968           gchar *key, *temp_keybinding;
969
970           child = gtk_bin_get_child (GTK_BIN (temp_item));
971           if (child == NULL)
972             {
973               /* Possibly a tear off menu item; it could also be a menu 
974                * separator generated by gtk_item_factory_create_items()
975                */
976               return NULL;
977             }
978           parent = gtk_widget_get_parent (temp_item);
979           if (!parent)
980             {
981               /*
982                * parent can be NULL when activating a window from the panel
983                */
984               return NULL;
985             }
986           g_return_val_if_fail (GTK_IS_MENU_SHELL (parent), NULL);
987           if (GTK_IS_MENU_BAR (parent))
988             {
989               GtkWidget *toplevel;
990
991               toplevel = gtk_widget_get_toplevel (parent);
992               if (toplevel && GTK_IS_WINDOW (toplevel))
993                 mnemonic_modifier = gtk_window_get_mnemonic_modifier (
994                                        GTK_WINDOW (toplevel));
995             }
996           if (GTK_IS_LABEL (child))
997             {
998               key_val = gtk_label_get_mnemonic_keyval (GTK_LABEL (child));
999               if (key_val != GDK_KEY_VoidSymbol)
1000                 {
1001                   key = gtk_accelerator_name (key_val, mnemonic_modifier);
1002                   if (full_keybinding)
1003                     temp_keybinding = g_strconcat (key, ":", full_keybinding, NULL);
1004                   else 
1005                     temp_keybinding = g_strconcat (key, NULL);
1006                   if (temp_item == item)
1007                     {
1008                       item_keybinding = g_strdup (key); 
1009                     }
1010                   g_free (key);
1011                   g_free (full_keybinding);
1012                   full_keybinding = temp_keybinding;
1013                 }
1014               else
1015                 {
1016                   /* No keybinding */
1017                   g_free (full_keybinding);
1018                   full_keybinding = NULL;
1019                   break;
1020                 }        
1021             }        
1022           if (GTK_IS_MENU_BAR (parent))
1023             /* We have reached the menu bar so we are finished */
1024             break;
1025           g_return_val_if_fail (GTK_IS_MENU (parent), NULL);
1026           temp_item = gtk_menu_get_attach_widget (GTK_MENU (parent));
1027           if (!GTK_IS_MENU_ITEM (temp_item))
1028             {
1029               /* 
1030                * Menu is attached to something other than a menu item;
1031                * probably an option menu
1032                */
1033               g_free (full_keybinding);
1034               full_keybinding = NULL;
1035               break;
1036             }
1037         }
1038
1039       parent = gtk_widget_get_parent (item);
1040       if (GTK_IS_MENU (parent))
1041         {
1042           GtkAccelGroup *group; 
1043           GtkAccelKey *key;
1044
1045           group = gtk_menu_get_accel_group (GTK_MENU (parent));
1046
1047           if (group)
1048             {
1049               key = gtk_accel_group_find (group, find_accel, item);
1050             }
1051           else
1052             {
1053               /*
1054                * If the menu item is created using GtkAction and GtkUIManager
1055                * we get here.
1056                */
1057               key = NULL;
1058               child = gtk_bin_get_child (GTK_BIN (item));
1059               if (GTK_IS_ACCEL_LABEL (child))
1060                 {
1061                   GtkAccelLabel *accel_label;
1062                   GClosure      *accel_closure;
1063
1064                   accel_label = GTK_ACCEL_LABEL (child);
1065                   g_object_get (accel_label,
1066                                 "accel-closure", &accel_closure,
1067                                 NULL);
1068                   if (accel_closure)
1069                     {
1070                       key = gtk_accel_group_find (gtk_accel_group_from_accel_closure (accel_closure),
1071                                                   find_accel_new,
1072                                                   accel_closure);
1073                     }
1074                
1075                 }
1076             }
1077
1078           if (key)
1079             {           
1080               accelerator = gtk_accelerator_name (key->accel_key,
1081                                                   key->accel_mods);
1082             }
1083         }
1084     }
1085   /*
1086    * Concatenate the bindings
1087    */
1088   if (item_keybinding || full_keybinding || accelerator)
1089     {
1090       gchar *temp;
1091       if (item_keybinding)
1092         {
1093           keybinding = g_strconcat (item_keybinding, KEYBINDING_SEPARATOR, NULL);
1094           g_free (item_keybinding);
1095         }
1096       else
1097         keybinding = g_strconcat (KEYBINDING_SEPARATOR, NULL);
1098
1099       if (full_keybinding)
1100         {
1101           temp = g_strconcat (keybinding, full_keybinding, 
1102                               KEYBINDING_SEPARATOR, NULL);
1103           g_free (full_keybinding);
1104         }
1105       else
1106         temp = g_strconcat (keybinding, KEYBINDING_SEPARATOR, NULL);
1107
1108       g_free (keybinding);
1109       keybinding = temp;
1110       if (accelerator)
1111         {
1112           temp = g_strconcat (keybinding, accelerator, NULL);
1113           g_free (accelerator);
1114           g_free (keybinding);
1115           keybinding = temp;
1116       }
1117     }
1118   g_free (gail_menu_item->click_keybinding);
1119   gail_menu_item->click_keybinding = keybinding;
1120   return keybinding;
1121 }
1122
1123 static gboolean
1124 gail_menu_item_set_description (AtkAction      *action,
1125                                 gint           i,
1126                                 const gchar    *desc)
1127 {
1128   if (i == 0)
1129     {
1130       GailMenuItem *item;
1131
1132       item = GAIL_MENU_ITEM (action);
1133       g_free (item->click_description);
1134       item->click_description = g_strdup (desc);
1135       return TRUE;
1136     }
1137   else
1138     return FALSE;
1139 }
1140
1141 static void
1142 gail_menu_item_finalize (GObject *object)
1143 {
1144   GailMenuItem *menu_item = GAIL_MENU_ITEM (object);
1145
1146   g_free (menu_item->click_keybinding);
1147   g_free (menu_item->click_description);
1148   if (menu_item->action_idle_handler)
1149     {
1150       g_source_remove (menu_item->action_idle_handler);
1151       menu_item->action_idle_handler = 0;
1152     }
1153
1154   if (menu_item->textutil)
1155     {
1156       g_object_unref (menu_item->textutil);
1157     }
1158   if (menu_item->text)
1159     {
1160       g_free (menu_item->text);
1161       menu_item->text = NULL;
1162     }
1163
1164   G_OBJECT_CLASS (gail_menu_item_parent_class)->finalize (object);
1165 }
1166
1167 static const gchar *
1168 gail_menu_item_get_name (AtkObject *obj)
1169 {
1170   const gchar* name;
1171   GtkWidget *widget;
1172   GtkWidget *label;
1173
1174   name = ATK_OBJECT_CLASS (gail_menu_item_parent_class)->get_name (obj);
1175
1176   if (name)
1177     return name;
1178
1179   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
1180   if (widget == NULL)
1181     return NULL;
1182
1183   label = get_label_from_container (widget);
1184   if (GTK_IS_LABEL (label))
1185     return gtk_label_get_text (GTK_LABEL(label));
1186
1187   return NULL;
1188 }
1189
1190 static void
1191 menu_item_select (GtkMenuItem *item)
1192 {
1193   menu_item_selection (item, TRUE);
1194 }
1195
1196 static void
1197 menu_item_deselect (GtkMenuItem *item)
1198 {
1199   menu_item_selection (item, FALSE);
1200 }
1201
1202 static void
1203 menu_item_selection (GtkMenuItem  *item,
1204                      gboolean selected)
1205 {
1206   AtkObject *obj, *parent;
1207   gint i;
1208
1209   obj = gtk_widget_get_accessible (GTK_WIDGET (item));
1210   atk_object_notify_state_change (obj, ATK_STATE_SELECTED, selected);
1211
1212   for (i = 0; i < atk_object_get_n_accessible_children (obj); i++)
1213     {
1214       AtkObject *child;
1215       child = atk_object_ref_accessible_child (obj, i);
1216       atk_object_notify_state_change (child, ATK_STATE_SHOWING, selected);
1217       g_object_unref (child);
1218     }
1219   parent = atk_object_get_parent (obj);
1220   g_signal_emit_by_name (parent, "selection_changed"); 
1221 }
1222
1223 static gboolean
1224 find_accel (GtkAccelKey *key,
1225             GClosure    *closure,
1226             gpointer     data)
1227 {
1228   /*
1229    * We assume that closure->data points to the widget
1230    * pending gtk_widget_get_accel_closures being made public
1231    */
1232   return data == (gpointer) closure->data;
1233 }
1234
1235 static gboolean
1236 find_accel_new (GtkAccelKey *key,
1237                 GClosure    *closure,
1238                 gpointer     data)
1239 {
1240   return data == (gpointer) closure;
1241 }