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