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