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