]> Pileus Git - ~andy/gtk/blob - gtk/gtkmenushell.c
in comparisions with GTK_TYPE_STRING, use the fundamental type to catch
[~andy/gtk] / gtk / gtkmenushell.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library 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  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library 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 #include "gdk/gdkkeysyms.h"
20 #include "gtkbindings.h"
21 #include "gtkmain.h"
22 #include "gtkmenuitem.h"
23 #include "gtktearoffmenuitem.h" /* FIXME */
24 #include "gtkmenushell.h"
25 #include "gtksignal.h"
26
27
28 #define MENU_SHELL_TIMEOUT   500
29 #define MENU_SHELL_CLASS(w)  GTK_MENU_SHELL_CLASS (GTK_OBJECT (w)->klass)
30
31
32 enum {
33   DEACTIVATE,
34   SELECTION_DONE,
35   MOVE_CURRENT,
36   ACTIVATE_CURRENT,
37   CANCEL,
38   LAST_SIGNAL
39 };
40
41 typedef void (*GtkMenuShellSignal1) (GtkObject           *object,
42                                      GtkMenuDirectionType arg1,
43                                      gpointer             data);
44 typedef void (*GtkMenuShellSignal2) (GtkObject *object,
45                                      gboolean   arg1,
46                                      gpointer   data);
47
48 /* Terminology:
49  * 
50  * A menu item can be "selected", this means that it is displayed
51  * in the prelight state, and if it has a submenu, that submenu
52  * will be popped up. 
53  * 
54  * A menu is "active" when it is visible onscreen and the user
55  * is selecting from it. A menubar is not active until the user
56  * clicks on one of its menuitems. When a menu is active,
57  * passing the mouse over a submenu will pop it up.
58  *
59  * menu_shell->active_menu_item, is however, not an "active"
60  * menu item (there is no such thing) but rather, the selected
61  * menu item in that MenuShell, if there is one.
62  *
63  * There is also is a concept of the current menu and a current
64  * menu item. The current menu item is the selected menu item
65  * that is furthest down in the heirarchy. (Every active menu_shell
66  * does not necessarily contain a selected menu item, but if
67  * it does, then menu_shell->parent_menu_shell must also contain
68  * a selected menu item. The current menu is the menu that 
69  * contains the current menu_item. It will always have a GTK
70  * grab and receive all key presses.
71  *
72  *
73  * Action signals:
74  *
75  *  ::move_current (GtkMenuDirection *dir)
76  *     Moves the current menu item in direction 'dir':
77  *
78  *       GTK_MENU_DIR_PARENT: To the parent menu shell
79  *       GTK_MENU_DIR_CHILD: To the child menu shell (if this item has
80  *          a submenu.
81  *       GTK_MENU_DIR_NEXT/PREV: To the next or previous item
82  *          in this menu.
83  * 
84  *     As a a bit of a hack to get movement between menus and
85  *     menubars working, if submenu_placement is different for
86  *     the menu and its MenuShell then the following apply:
87  * 
88  *       - For 'parent' the current menu is not just moved to
89  *         the parent, but moved to the previous entry in the parent
90  *       - For 'child', if there is no child, then current is
91  *         moved to the next item in the parent.
92  *
93  * 
94  *  ::activate_current (GBoolean *force_hide)
95  *     Activate the current item. If 'force_hide' is true, hide
96  *     the current menu item always. Otherwise, only hide
97  *     it if menu_item->klass->hide_on_activate is true.
98  *
99  *  ::cancel ()
100  *     Cancels the current selection
101  */
102
103 static void gtk_menu_shell_class_init        (GtkMenuShellClass *klass);
104 static void gtk_menu_shell_init              (GtkMenuShell      *menu_shell);
105 static void gtk_menu_shell_map               (GtkWidget         *widget);
106 static void gtk_menu_shell_realize           (GtkWidget         *widget);
107 static gint gtk_menu_shell_button_press      (GtkWidget         *widget,
108                                               GdkEventButton    *event);
109 static gint gtk_menu_shell_button_release    (GtkWidget         *widget,
110                                               GdkEventButton    *event);
111 static gint gtk_menu_shell_key_press         (GtkWidget         *widget,
112                                               GdkEventKey       *event);
113 static gint gtk_menu_shell_enter_notify      (GtkWidget         *widget,
114                                               GdkEventCrossing  *event);
115 static gint gtk_menu_shell_leave_notify      (GtkWidget         *widget,
116                                               GdkEventCrossing  *event);
117 static void gtk_menu_shell_add               (GtkContainer      *container,
118                                               GtkWidget         *widget);
119 static void gtk_menu_shell_remove            (GtkContainer      *container,
120                                               GtkWidget         *widget);
121 static void gtk_menu_shell_forall            (GtkContainer      *container,
122                                               gboolean           include_internals,
123                                               GtkCallback        callback,
124                                               gpointer           callback_data);
125 static void gtk_real_menu_shell_deactivate   (GtkMenuShell      *menu_shell);
126 static gint gtk_menu_shell_is_item           (GtkMenuShell      *menu_shell,
127                                               GtkWidget         *child);
128 static GtkWidget *gtk_menu_shell_get_item    (GtkMenuShell      *menu_shell,
129                                               GdkEvent          *event);
130 static GtkType    gtk_menu_shell_child_type  (GtkContainer      *container);
131
132 static void       gtk_menu_shell_deselect    (GtkMenuShell      *menu_shell);
133 static void gtk_real_menu_shell_move_current (GtkMenuShell      *menu_shell,
134                                               GtkMenuDirectionType direction);
135 static void gtk_real_menu_shell_activate_current (GtkMenuShell      *menu_shell,
136                                                   gboolean           force_hide);
137 static void gtk_real_menu_shell_cancel           (GtkMenuShell      *menu_shell);
138
139 static GtkContainerClass *parent_class = NULL;
140 static guint menu_shell_signals[LAST_SIGNAL] = { 0 };
141
142
143 GtkType
144 gtk_menu_shell_get_type (void)
145 {
146   static GtkType menu_shell_type = 0;
147
148   if (!menu_shell_type)
149     {
150       static const GtkTypeInfo menu_shell_info =
151       {
152         "GtkMenuShell",
153         sizeof (GtkMenuShell),
154         sizeof (GtkMenuShellClass),
155         (GtkClassInitFunc) gtk_menu_shell_class_init,
156         (GtkObjectInitFunc) gtk_menu_shell_init,
157         /* reserved_1 */ NULL,
158         /* reserved_2 */ NULL,
159         (GtkClassInitFunc) NULL,
160       };
161
162       menu_shell_type = gtk_type_unique (gtk_container_get_type (), &menu_shell_info);
163     }
164
165   return menu_shell_type;
166 }
167
168 static void
169 gtk_menu_shell_class_init (GtkMenuShellClass *klass)
170 {
171   GtkObjectClass *object_class;
172   GtkWidgetClass *widget_class;
173   GtkContainerClass *container_class;
174
175   GtkBindingSet *binding_set;
176
177   object_class = (GtkObjectClass*) klass;
178   widget_class = (GtkWidgetClass*) klass;
179   container_class = (GtkContainerClass*) klass;
180
181   parent_class = gtk_type_class (gtk_container_get_type ());
182
183   menu_shell_signals[DEACTIVATE] =
184     gtk_signal_new ("deactivate",
185                     GTK_RUN_FIRST,
186                     object_class->type,
187                     GTK_SIGNAL_OFFSET (GtkMenuShellClass, deactivate),
188                     gtk_marshal_NONE__NONE,
189                     GTK_TYPE_NONE, 0);
190   menu_shell_signals[SELECTION_DONE] =
191     gtk_signal_new ("selection-done",
192                     GTK_RUN_FIRST,
193                     object_class->type,
194                     GTK_SIGNAL_OFFSET (GtkMenuShellClass, selection_done),
195                     gtk_marshal_NONE__NONE,
196                     GTK_TYPE_NONE, 0);
197   menu_shell_signals[MOVE_CURRENT] =
198     gtk_signal_new ("move_current",
199                     GTK_RUN_LAST | GTK_RUN_ACTION,
200                     object_class->type,
201                     GTK_SIGNAL_OFFSET (GtkMenuShellClass, move_current),
202                     gtk_marshal_NONE__ENUM,
203                     GTK_TYPE_NONE, 1, 
204                     GTK_TYPE_MENU_DIRECTION_TYPE);
205   menu_shell_signals[ACTIVATE_CURRENT] =
206     gtk_signal_new ("activate_current",
207                     GTK_RUN_LAST | GTK_RUN_ACTION,
208                     object_class->type,
209                     GTK_SIGNAL_OFFSET (GtkMenuShellClass, activate_current),
210                     gtk_marshal_NONE__BOOL,
211                     GTK_TYPE_NONE, 1, 
212                     GTK_TYPE_BOOL);
213   menu_shell_signals[CANCEL] =
214     gtk_signal_new ("cancel",
215                     GTK_RUN_LAST | GTK_RUN_ACTION,
216                     object_class->type,
217                     GTK_SIGNAL_OFFSET (GtkMenuShellClass, cancel),
218                     gtk_marshal_NONE__NONE,
219                     GTK_TYPE_NONE, 0);
220   
221   gtk_object_class_add_signals (object_class, menu_shell_signals, LAST_SIGNAL);
222
223   widget_class->map = gtk_menu_shell_map;
224   widget_class->realize = gtk_menu_shell_realize;
225   widget_class->button_press_event = gtk_menu_shell_button_press;
226   widget_class->button_release_event = gtk_menu_shell_button_release;
227   widget_class->key_press_event = gtk_menu_shell_key_press;
228   widget_class->enter_notify_event = gtk_menu_shell_enter_notify;
229   widget_class->leave_notify_event = gtk_menu_shell_leave_notify;
230
231   container_class->add = gtk_menu_shell_add;
232   container_class->remove = gtk_menu_shell_remove;
233   container_class->forall = gtk_menu_shell_forall;
234   container_class->child_type = gtk_menu_shell_child_type;
235
236   klass->submenu_placement = GTK_TOP_BOTTOM;
237   klass->deactivate = gtk_real_menu_shell_deactivate;
238   klass->selection_done = NULL;
239   klass->move_current = gtk_real_menu_shell_move_current;
240   klass->activate_current = gtk_real_menu_shell_activate_current;
241   klass->cancel = gtk_real_menu_shell_cancel;
242
243   binding_set = gtk_binding_set_by_class (klass);
244   gtk_binding_entry_add_signal (binding_set,
245                                 GDK_Escape, 0,
246                                 "cancel", 0);
247   gtk_binding_entry_add_signal (binding_set,
248                                 GDK_Return, 0,
249                                 "activate_current", 1,
250                                 GTK_TYPE_BOOL,
251                                 TRUE);
252   gtk_binding_entry_add_signal (binding_set,
253                                 GDK_space, 0,
254                                 "activate_current", 1,
255                                 GTK_TYPE_BOOL,
256                                 FALSE);
257 }
258
259 static GtkType
260 gtk_menu_shell_child_type (GtkContainer     *container)
261 {
262   return GTK_TYPE_MENU_ITEM;
263 }
264
265 static void
266 gtk_menu_shell_init (GtkMenuShell *menu_shell)
267 {
268   menu_shell->children = NULL;
269   menu_shell->active_menu_item = NULL;
270   menu_shell->parent_menu_shell = NULL;
271   menu_shell->active = FALSE;
272   menu_shell->have_grab = FALSE;
273   menu_shell->have_xgrab = FALSE;
274   menu_shell->ignore_leave = FALSE;
275   menu_shell->button = 0;
276   menu_shell->menu_flag = 0;
277   menu_shell->activate_time = 0;
278 }
279
280 void
281 gtk_menu_shell_append (GtkMenuShell *menu_shell,
282                        GtkWidget    *child)
283 {
284   gtk_menu_shell_insert (menu_shell, child, -1);
285 }
286
287 void
288 gtk_menu_shell_prepend (GtkMenuShell *menu_shell,
289                         GtkWidget    *child)
290 {
291   gtk_menu_shell_insert (menu_shell, child, 0);
292 }
293
294 void
295 gtk_menu_shell_insert (GtkMenuShell *menu_shell,
296                        GtkWidget    *child,
297                        gint          position)
298 {
299   GList *tmp_list;
300   GList *new_list;
301   gint nchildren;
302
303   g_return_if_fail (menu_shell != NULL);
304   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
305   g_return_if_fail (child != NULL);
306   g_return_if_fail (GTK_IS_MENU_ITEM (child));
307
308   gtk_widget_set_parent (child, GTK_WIDGET (menu_shell));
309
310   if (GTK_WIDGET_VISIBLE (child->parent))
311     {
312       if (GTK_WIDGET_REALIZED (child->parent) &&
313           !GTK_WIDGET_REALIZED (child))
314         gtk_widget_realize (child);
315
316       if (GTK_WIDGET_MAPPED (child->parent) &&
317           !GTK_WIDGET_MAPPED (child))
318         gtk_widget_map (child);
319     }
320
321   nchildren = g_list_length (menu_shell->children);
322   if ((position < 0) || (position > nchildren))
323     position = nchildren;
324
325   if (position == nchildren)
326     {
327       menu_shell->children = g_list_append (menu_shell->children, child);
328     }
329   else
330     {
331       tmp_list = g_list_nth (menu_shell->children, position);
332       new_list = g_list_alloc ();
333       new_list->data = child;
334
335       if (tmp_list->prev)
336         tmp_list->prev->next = new_list;
337       new_list->next = tmp_list;
338       new_list->prev = tmp_list->prev;
339       tmp_list->prev = new_list;
340
341       if (tmp_list == menu_shell->children)
342         menu_shell->children = new_list;
343     }
344
345   if (GTK_WIDGET_VISIBLE (menu_shell))
346     gtk_widget_queue_resize (GTK_WIDGET (menu_shell));
347 }
348
349 void
350 gtk_menu_shell_deactivate (GtkMenuShell *menu_shell)
351 {
352   g_return_if_fail (menu_shell != NULL);
353   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
354
355   gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[DEACTIVATE]);
356 }
357
358 static void
359 gtk_menu_shell_map (GtkWidget *widget)
360 {
361   GtkMenuShell *menu_shell;
362   GtkWidget *child;
363   GList *children;
364
365   g_return_if_fail (widget != NULL);
366   g_return_if_fail (GTK_IS_MENU_SHELL (widget));
367
368   menu_shell = GTK_MENU_SHELL (widget);
369   GTK_WIDGET_SET_FLAGS (menu_shell, GTK_MAPPED);
370   gdk_window_show (widget->window);
371
372   children = menu_shell->children;
373   while (children)
374     {
375       child = children->data;
376       children = children->next;
377
378       if (GTK_WIDGET_VISIBLE (child) && !GTK_WIDGET_MAPPED (child))
379         gtk_widget_map (child);
380     }
381 }
382
383 static void
384 gtk_menu_shell_realize (GtkWidget *widget)
385 {
386   GdkWindowAttr attributes;
387   gint attributes_mask;
388
389   g_return_if_fail (widget != NULL);
390   g_return_if_fail (GTK_IS_MENU_SHELL (widget));
391
392   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
393
394   attributes.x = widget->allocation.x;
395   attributes.y = widget->allocation.y;
396   attributes.width = widget->allocation.width;
397   attributes.height = widget->allocation.height;
398   attributes.window_type = GDK_WINDOW_CHILD;
399   attributes.wclass = GDK_INPUT_OUTPUT;
400   attributes.visual = gtk_widget_get_visual (widget);
401   attributes.colormap = gtk_widget_get_colormap (widget);
402   attributes.event_mask = gtk_widget_get_events (widget);
403   attributes.event_mask |= (GDK_EXPOSURE_MASK |
404                             GDK_BUTTON_PRESS_MASK |
405                             GDK_BUTTON_RELEASE_MASK |
406                             GDK_KEY_PRESS_MASK |
407                             GDK_ENTER_NOTIFY_MASK |
408                             GDK_LEAVE_NOTIFY_MASK);
409
410   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
411   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
412   gdk_window_set_user_data (widget->window, widget);
413
414   widget->style = gtk_style_attach (widget->style, widget->window);
415   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
416 }
417
418 static gint
419 gtk_menu_shell_button_press (GtkWidget      *widget,
420                              GdkEventButton *event)
421 {
422   GtkMenuShell *menu_shell;
423   GtkWidget *menu_item;
424
425   g_return_val_if_fail (widget != NULL, FALSE);
426   g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
427   g_return_val_if_fail (event != NULL, FALSE);
428
429   if (event->type != GDK_BUTTON_PRESS)
430     return FALSE;
431
432   menu_shell = GTK_MENU_SHELL (widget);
433
434   if (menu_shell->parent_menu_shell)
435     {
436       gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
437     }
438   else if (!menu_shell->active || !menu_shell->button)
439     {
440       if (!menu_shell->active)
441         {
442           gtk_grab_add (GTK_WIDGET (widget));
443           menu_shell->have_grab = TRUE;
444           menu_shell->active = TRUE;
445         }
446       menu_shell->button = event->button;
447
448       menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent *)event);
449
450       if (menu_item &&
451           GTK_WIDGET_IS_SENSITIVE (menu_item))
452         {
453           if ((menu_item->parent == widget) &&
454               (menu_item != menu_shell->active_menu_item))
455             {
456               if (menu_shell->active_menu_item)
457                 gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
458               
459               menu_shell->active_menu_item = menu_item;
460               gtk_menu_item_set_placement (GTK_MENU_ITEM (menu_shell->active_menu_item),
461                                            MENU_SHELL_CLASS (menu_shell)->submenu_placement);
462               gtk_menu_item_select (GTK_MENU_ITEM (menu_shell->active_menu_item));
463             }
464         }
465     }
466   else
467     {
468       widget = gtk_get_event_widget ((GdkEvent*) event);
469       if (widget == GTK_WIDGET (menu_shell))
470         {
471           gtk_menu_shell_deactivate (menu_shell);
472           gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[SELECTION_DONE]);
473         }
474     }
475
476   return TRUE;
477 }
478
479 static gint
480 gtk_menu_shell_button_release (GtkWidget      *widget,
481                                GdkEventButton *event)
482 {
483   GtkMenuShell *menu_shell;
484   GtkWidget *menu_item;
485   gint deactivate;
486
487   g_return_val_if_fail (widget != NULL, FALSE);
488   g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
489   g_return_val_if_fail (event != NULL, FALSE);
490
491   menu_shell = GTK_MENU_SHELL (widget);
492   if (menu_shell->active)
493     {
494       if (menu_shell->button && (event->button != menu_shell->button))
495         {
496           menu_shell->button = 0;
497           if (menu_shell->parent_menu_shell)
498             gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
499           return TRUE;
500         }
501       
502       menu_shell->button = 0;
503       menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent*) event);
504
505       deactivate = TRUE;
506
507       if ((event->time - menu_shell->activate_time) > MENU_SHELL_TIMEOUT)
508         {
509           if (menu_item && (menu_shell->active_menu_item == menu_item) &&
510               GTK_WIDGET_IS_SENSITIVE (menu_item))
511             {
512               if (GTK_MENU_ITEM (menu_item)->submenu == NULL)
513                 {
514                   gtk_menu_shell_activate_item (menu_shell, menu_item, TRUE);
515                   return TRUE;
516                 }
517             }
518           else if (menu_shell->parent_menu_shell)
519             {
520               menu_shell->active = TRUE;
521               gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
522               return TRUE;
523             }
524         }
525       else
526         deactivate = FALSE;
527       
528       /* If the button click was very fast, or we ended up on a submenu,
529        * leave the menu up
530        */
531       if (!deactivate || 
532           (menu_item && (menu_shell->active_menu_item == menu_item)))
533         {
534           deactivate = FALSE;
535           menu_shell->ignore_leave = TRUE;
536         }
537       else
538         deactivate = TRUE;
539
540       if (deactivate)
541         {
542           gtk_menu_shell_deactivate (menu_shell);
543           gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[SELECTION_DONE]);
544         }
545     }
546
547   return TRUE;
548 }
549
550 static gint
551 gtk_menu_shell_key_press (GtkWidget     *widget,
552                           GdkEventKey *event)
553 {
554   GtkMenuShell *menu_shell;
555   
556   g_return_val_if_fail (widget != NULL, FALSE);
557   g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
558   g_return_val_if_fail (event != NULL, FALSE);
559       
560   menu_shell = GTK_MENU_SHELL (widget);
561
562   if (!menu_shell->active_menu_item && menu_shell->parent_menu_shell)
563     return gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent *)event);
564   
565   if (gtk_bindings_activate (GTK_OBJECT (widget),
566                              event->keyval,
567                              event->state))
568     return TRUE;
569
570   if (gtk_accel_groups_activate (GTK_OBJECT (widget), event->keyval, event->state))
571     return TRUE;
572
573   return FALSE;
574 }
575
576 static gint
577 gtk_menu_shell_enter_notify (GtkWidget        *widget,
578                              GdkEventCrossing *event)
579 {
580   GtkMenuShell *menu_shell;
581   GtkWidget *menu_item;
582
583   g_return_val_if_fail (widget != NULL, FALSE);
584   g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
585   g_return_val_if_fail (event != NULL, FALSE);
586
587   menu_shell = GTK_MENU_SHELL (widget);
588
589   if (menu_shell->active && !menu_shell->ignore_enter)
590     {
591       menu_item = gtk_get_event_widget ((GdkEvent*) event);
592
593       if (!menu_item || !GTK_WIDGET_IS_SENSITIVE (menu_item))
594         return TRUE;
595
596       if ((menu_item->parent == widget) &&
597           (menu_shell->active_menu_item != menu_item) &&
598           GTK_IS_MENU_ITEM (menu_item))
599         {
600           if ((event->detail != GDK_NOTIFY_INFERIOR) &&
601               (GTK_WIDGET_STATE (menu_item) != GTK_STATE_PRELIGHT))
602             {
603               gtk_menu_shell_select_item (menu_shell, menu_item);
604             }
605         }
606       else if (menu_shell->parent_menu_shell)
607         {
608           gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
609         }
610     }
611
612   return TRUE;
613 }
614
615 static gint
616 gtk_menu_shell_leave_notify (GtkWidget        *widget,
617                              GdkEventCrossing *event)
618 {
619   GtkMenuShell *menu_shell;
620   GtkMenuItem *menu_item;
621   GtkWidget *event_widget;
622
623   g_return_val_if_fail (widget != NULL, FALSE);
624   g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
625   g_return_val_if_fail (event != NULL, FALSE);
626
627   if (GTK_WIDGET_VISIBLE (widget))
628     {
629       menu_shell = GTK_MENU_SHELL (widget);
630       event_widget = gtk_get_event_widget ((GdkEvent*) event);
631
632       if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
633         return TRUE;
634
635       menu_item = GTK_MENU_ITEM (event_widget);
636
637       if (menu_shell->ignore_leave)
638         {
639           menu_shell->ignore_leave = FALSE;
640           return TRUE;
641         }
642
643       if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
644         return TRUE;
645
646       if ((menu_shell->active_menu_item == event_widget) &&
647           (menu_item->submenu == NULL))
648         {
649           if ((event->detail != GDK_NOTIFY_INFERIOR) &&
650               (GTK_WIDGET_STATE (menu_item) != GTK_STATE_NORMAL))
651             {
652               gtk_menu_shell_deselect (menu_shell);
653             }
654         }
655       else if (menu_shell->parent_menu_shell)
656         {
657           gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
658         }
659     }
660
661   return TRUE;
662 }
663
664 static void
665 gtk_menu_shell_add (GtkContainer *container,
666                     GtkWidget    *widget)
667 {
668   gtk_menu_shell_append (GTK_MENU_SHELL (container), widget);
669 }
670
671 static void
672 gtk_menu_shell_remove (GtkContainer *container,
673                        GtkWidget    *widget)
674 {
675   GtkMenuShell *menu_shell;
676   gint was_visible;
677   
678   g_return_if_fail (container != NULL);
679   g_return_if_fail (GTK_IS_MENU_SHELL (container));
680   g_return_if_fail (widget != NULL);
681   g_return_if_fail (GTK_IS_MENU_ITEM (widget));
682   
683   was_visible = GTK_WIDGET_VISIBLE (widget);
684   menu_shell = GTK_MENU_SHELL (container);
685   menu_shell->children = g_list_remove (menu_shell->children, widget);
686   
687   gtk_widget_unparent (widget);
688   
689   /* queue resize regardless of GTK_WIDGET_VISIBLE (container),
690    * since that's what is needed by toplevels.
691    */
692   if (was_visible)
693     gtk_widget_queue_resize (GTK_WIDGET (container));
694 }
695
696 static void
697 gtk_menu_shell_forall (GtkContainer *container,
698                        gboolean      include_internals,
699                        GtkCallback   callback,
700                        gpointer      callback_data)
701 {
702   GtkMenuShell *menu_shell;
703   GtkWidget *child;
704   GList *children;
705
706   g_return_if_fail (container != NULL);
707   g_return_if_fail (GTK_IS_MENU_SHELL (container));
708   g_return_if_fail (callback != NULL);
709
710   menu_shell = GTK_MENU_SHELL (container);
711
712   children = menu_shell->children;
713   while (children)
714     {
715       child = children->data;
716       children = children->next;
717
718       (* callback) (child, callback_data);
719     }
720 }
721
722
723 static void
724 gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell)
725 {
726   g_return_if_fail (menu_shell != NULL);
727   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
728
729   if (menu_shell->active)
730     {
731       menu_shell->button = 0;
732       menu_shell->active = FALSE;
733
734       if (menu_shell->active_menu_item)
735         {
736           gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
737           menu_shell->active_menu_item = NULL;
738         }
739
740       if (menu_shell->have_grab)
741         {
742           menu_shell->have_grab = FALSE;
743           gtk_grab_remove (GTK_WIDGET (menu_shell));
744         }
745       if (menu_shell->have_xgrab)
746         {
747           menu_shell->have_xgrab = FALSE;
748           gdk_pointer_ungrab (GDK_CURRENT_TIME);
749           gdk_keyboard_ungrab (GDK_CURRENT_TIME);
750         }
751     }
752 }
753
754 static gint
755 gtk_menu_shell_is_item (GtkMenuShell *menu_shell,
756                         GtkWidget    *child)
757 {
758   GtkWidget *parent;
759
760   g_return_val_if_fail (menu_shell != NULL, FALSE);
761   g_return_val_if_fail (GTK_IS_MENU_SHELL (menu_shell), FALSE);
762   g_return_val_if_fail (child != NULL, FALSE);
763
764   parent = child->parent;
765   while (parent && GTK_IS_MENU_SHELL (parent))
766     {
767       if (parent == (GtkWidget*) menu_shell)
768         return TRUE;
769       parent = GTK_MENU_SHELL (parent)->parent_menu_shell;
770     }
771
772   return FALSE;
773 }
774
775 static GtkWidget *
776 gtk_menu_shell_get_item (GtkMenuShell *menu_shell,
777                          GdkEvent     *event)
778 {
779   GtkWidget *menu_item;
780
781   menu_item = gtk_get_event_widget ((GdkEvent*) event);
782   
783   while (menu_item && !GTK_IS_MENU_ITEM (menu_item))
784     menu_item = menu_item->parent;
785
786   if (menu_item && gtk_menu_shell_is_item (menu_shell, menu_item))
787     return menu_item;
788   else
789     return NULL;
790 }
791
792 /* Handlers for action signals */
793
794 void
795 gtk_menu_shell_select_item (GtkMenuShell      *menu_shell,
796                             GtkWidget         *menu_item)
797 {
798   g_return_if_fail (menu_shell != NULL);
799   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
800   g_return_if_fail (menu_item != NULL);
801   g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
802
803   if (menu_shell->active_menu_item)
804     gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
805   
806   menu_shell->active_menu_item = menu_item;
807   gtk_menu_item_set_placement (GTK_MENU_ITEM (menu_shell->active_menu_item),
808                                MENU_SHELL_CLASS (menu_shell)->submenu_placement);
809   gtk_menu_item_select (GTK_MENU_ITEM (menu_shell->active_menu_item));
810
811   /* This allows the bizarre radio buttons-with-submenus-display-history
812    * behavior
813    */
814   if (GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu)
815     gtk_widget_activate (menu_shell->active_menu_item);
816 }
817
818 static void
819 gtk_menu_shell_deselect (GtkMenuShell      *menu_shell)
820 {
821   gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
822   menu_shell->active_menu_item = NULL;
823 }
824
825 void
826 gtk_menu_shell_activate_item (GtkMenuShell      *menu_shell,
827                               GtkWidget         *menu_item,
828                               gboolean           force_deactivate)
829 {
830   gboolean deactivate = force_deactivate;
831
832   g_return_if_fail (menu_shell != NULL);
833   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
834   g_return_if_fail (menu_item != NULL);
835   g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
836
837   if (!deactivate)
838     {
839       deactivate = GTK_MENU_ITEM_CLASS (GTK_OBJECT (menu_item)->klass)->hide_on_activate;
840     }
841
842   if (deactivate)
843     {
844       gtk_menu_shell_deactivate (menu_shell);
845   
846       /* flush the x-queue, so any grabs are removed and
847        * the menu is actually taken down
848        */
849       gdk_flush ();
850     }
851   gtk_widget_activate (menu_item);
852
853   if (deactivate)
854     gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[SELECTION_DONE]);
855 }
856
857 /* Distance should be +/- 1 */
858 static void
859 gtk_menu_shell_move_selected (GtkMenuShell  *menu_shell, 
860                               gint           distance)
861 {
862   if (menu_shell->active_menu_item)
863     {
864       GList *node = g_list_find (menu_shell->children,
865                                  menu_shell->active_menu_item);
866       GList *start_node = node;
867       
868       if (distance > 0)
869         {
870           node = node->next;
871           while (node != start_node && 
872                  (!node ||
873                   !GTK_WIDGET_IS_SENSITIVE (node->data) ||
874                   !GTK_WIDGET_VISIBLE (node->data) ))
875             {
876               if (!node)
877                 node = menu_shell->children;
878               else
879                 node = node->next;
880             }
881         }
882       else
883         {
884           node = node->prev;
885           while (node != start_node &&
886                  (!node ||
887                   !GTK_WIDGET_IS_SENSITIVE (node->data) ||
888                   !GTK_WIDGET_VISIBLE (node->data) ))
889             {
890               if (!node)
891                 node = g_list_last (menu_shell->children);
892               else
893                 node = node->prev;
894             }
895         }
896       
897       if (node)
898         gtk_menu_shell_select_item (menu_shell, node->data);
899     }
900 }
901
902 static void
903 gtk_real_menu_shell_move_current (GtkMenuShell      *menu_shell,
904                                   GtkMenuDirectionType direction)
905 {
906   GtkMenuShell *parent_menu_shell = NULL;
907   gboolean had_selection;
908
909   had_selection = menu_shell->active_menu_item != NULL;
910
911   if (menu_shell->parent_menu_shell)
912     parent_menu_shell = GTK_MENU_SHELL (menu_shell->parent_menu_shell);
913   
914   switch (direction)
915     {
916     case GTK_MENU_DIR_PARENT:
917       if (parent_menu_shell)
918         {
919           if (GTK_MENU_SHELL_CLASS (GTK_OBJECT (parent_menu_shell)->klass)->submenu_placement == 
920                        GTK_MENU_SHELL_CLASS (GTK_OBJECT (menu_shell)->klass)->submenu_placement)
921             gtk_menu_shell_deselect (menu_shell);
922           else
923             gtk_menu_shell_move_selected (parent_menu_shell, -1);
924         }
925       break;
926       
927     case GTK_MENU_DIR_CHILD:
928       if (menu_shell->active_menu_item &&
929           GTK_BIN (menu_shell->active_menu_item)->child &&
930           GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu)
931         {
932           menu_shell = GTK_MENU_SHELL (GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu);
933           if (menu_shell->children)
934             gtk_menu_shell_select_item (menu_shell, menu_shell->children->data);
935         }
936       else
937         {
938           /* Try to find a menu running the opposite direction */
939           while (parent_menu_shell && 
940                  (GTK_MENU_SHELL_CLASS (GTK_OBJECT (parent_menu_shell)->klass)->submenu_placement ==
941                   GTK_MENU_SHELL_CLASS (GTK_OBJECT (menu_shell)->klass)->submenu_placement))
942             parent_menu_shell = GTK_MENU_SHELL (parent_menu_shell->parent_menu_shell);
943           
944           if (parent_menu_shell)
945             gtk_menu_shell_move_selected (parent_menu_shell, 1);
946         }
947       break;
948       
949     case GTK_MENU_DIR_PREV:
950       gtk_menu_shell_move_selected (menu_shell, -1);
951       if (!had_selection &&
952           !menu_shell->active_menu_item &&
953           menu_shell->children)
954         gtk_menu_shell_select_item (menu_shell, g_list_last (menu_shell->children)->data);
955       break;
956     case GTK_MENU_DIR_NEXT:
957       gtk_menu_shell_move_selected (menu_shell, 1);
958       if (!had_selection &&
959           !menu_shell->active_menu_item &&
960           menu_shell->children)
961         gtk_menu_shell_select_item (menu_shell, menu_shell->children->data);
962       break;
963     }
964   
965 }
966
967 static void
968 gtk_real_menu_shell_activate_current (GtkMenuShell      *menu_shell,
969                                       gboolean           force_hide)
970 {
971   if (menu_shell->active_menu_item &&
972       GTK_WIDGET_IS_SENSITIVE (menu_shell->active_menu_item) &&
973       GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL)
974     {
975       gtk_menu_shell_activate_item (menu_shell,
976                                     menu_shell->active_menu_item,
977                                     force_hide);
978     }
979 }
980
981 static void
982 gtk_real_menu_shell_cancel (GtkMenuShell      *menu_shell)
983 {
984   gtk_menu_shell_deactivate (menu_shell);
985   gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[SELECTION_DONE]);
986 }
987