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