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