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