]> Pileus Git - ~andy/gtk/blob - gtk/gtkmenu.c
Documentation additions.
[~andy/gtk] / gtk / gtkmenu.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 <ctype.h>
28 #include <string.h> /* memset */
29 #include "gdk/gdkkeysyms.h"
30 #include "gtkaccelmap.h"
31 #include "gtkbindings.h"
32 #include "gtklabel.h"
33 #include "gtkmain.h"
34 #include "gtkmenu.h"
35 #include "gtkmenuitem.h"
36 #include "gtksignal.h"
37 #include "gtkwindow.h"
38 #include "gtkhbox.h"
39 #include "gtkvscrollbar.h"
40 #include "gtksettings.h"
41 #include "gtkintl.h"
42
43
44 #define MENU_ITEM_CLASS(w)   GTK_MENU_ITEM_GET_CLASS (w)
45 #define MENU_NEEDS_RESIZE(m) GTK_MENU_SHELL (m)->menu_flag
46
47 #define SUBMENU_NAV_REGION_PADDING 2
48 #define SUBMENU_NAV_HYSTERESIS_TIMEOUT 333
49
50 #define MENU_SCROLL_STEP 10
51 #define MENU_SCROLL_ARROW_HEIGHT 16
52 #define MENU_SCROLL_FAST_ZONE 4
53 #define MENU_SCROLL_TIMEOUT1 150
54 #define MENU_SCROLL_TIMEOUT2 50
55
56 typedef struct _GtkMenuAttachData       GtkMenuAttachData;
57
58 struct _GtkMenuAttachData
59 {
60   GtkWidget *attach_widget;
61   GtkMenuDetachFunc detacher;
62 };
63
64 enum {
65   PROP_0,
66   PROP_TEAROFF_TITLE
67 };
68
69 static void     gtk_menu_class_init        (GtkMenuClass     *klass);
70 static void     gtk_menu_init              (GtkMenu          *menu);
71 static void     gtk_menu_set_property      (GObject      *object,
72                                             guint         prop_id,
73                                             const GValue *value,
74                                             GParamSpec   *pspec);
75 static void     gtk_menu_get_property      (GObject     *object,
76                                             guint        prop_id,
77                                             GValue      *value,
78                                             GParamSpec  *pspec);
79 static void     gtk_menu_destroy           (GtkObject        *object);
80 static void     gtk_menu_finalize          (GObject          *object);
81 static void     gtk_menu_realize           (GtkWidget        *widget);
82 static void     gtk_menu_unrealize         (GtkWidget        *widget);
83 static void     gtk_menu_size_request      (GtkWidget        *widget,
84                                             GtkRequisition   *requisition);
85 static void     gtk_menu_size_allocate     (GtkWidget        *widget,
86                                             GtkAllocation    *allocation);
87 static void     gtk_menu_paint             (GtkWidget        *widget,
88                                             GdkEventExpose   *expose);
89 static void     gtk_menu_show              (GtkWidget        *widget);
90 static gboolean gtk_menu_expose            (GtkWidget        *widget,
91                                             GdkEventExpose   *event);
92 static gboolean gtk_menu_key_press         (GtkWidget        *widget,
93                                             GdkEventKey      *event);
94 static gboolean gtk_menu_motion_notify     (GtkWidget        *widget,
95                                             GdkEventMotion   *event);
96 static gboolean gtk_menu_enter_notify      (GtkWidget        *widget,
97                                             GdkEventCrossing *event);
98 static gboolean gtk_menu_leave_notify      (GtkWidget        *widget,
99                                             GdkEventCrossing *event);
100 static void     gtk_menu_scroll_to         (GtkMenu          *menu,
101                                             gint              offset);
102 static void     gtk_menu_stop_scrolling    (GtkMenu          *menu);
103 static gboolean gtk_menu_scroll_timeout    (gpointer          data);
104 static void     gtk_menu_scroll_item_visible (GtkMenuShell    *menu_shell,
105                                               GtkWidget       *menu_item);
106 static void     gtk_menu_select_item       (GtkMenuShell     *menu_shell,
107                                             GtkWidget        *menu_item);
108 static void     gtk_menu_real_insert       (GtkMenuShell     *menu_shell,
109                                             GtkWidget        *child,
110                                             gint              position);
111 static void     gtk_menu_scrollbar_changed (GtkAdjustment    *adjustment,
112                                             GtkMenu          *menu);
113 static void     gtk_menu_handle_scrolling  (GtkMenu          *menu,
114                                             gboolean         enter);
115 static void     gtk_menu_set_tearoff_hints (GtkMenu          *menu,
116                                             gint             width);
117
118 static void     gtk_menu_stop_navigating_submenu       (GtkMenu          *menu);
119 static gboolean gtk_menu_stop_navigating_submenu_cb    (gpointer          user_data);
120 static gboolean gtk_menu_navigating_submenu            (GtkMenu          *menu,
121                                                         gint              event_x,
122                                                         gint              event_y);
123 static void     gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
124                                                         GtkMenuItem      *menu_item,
125                                                         GdkEventCrossing *event);
126  
127 static void gtk_menu_deactivate     (GtkMenuShell      *menu_shell);
128 static void gtk_menu_show_all       (GtkWidget         *widget);
129 static void gtk_menu_hide_all       (GtkWidget         *widget);
130 static void gtk_menu_position       (GtkMenu           *menu);
131 static void gtk_menu_reparent       (GtkMenu           *menu, 
132                                      GtkWidget         *new_parent, 
133                                      gboolean           unrealize);
134 static void gtk_menu_remove         (GtkContainer      *menu,
135                                      GtkWidget         *widget);
136
137 static void gtk_menu_update_title   (GtkMenu           *menu);
138
139 static void _gtk_menu_refresh_accel_paths (GtkMenu *menu,
140                                            gboolean group_changed);
141
142 static GtkMenuShellClass *parent_class = NULL;
143 static const gchar       *attach_data_key = "gtk-menu-attach-data";
144
145 GtkType
146 gtk_menu_get_type (void)
147 {
148   static GtkType menu_type = 0;
149   
150   if (!menu_type)
151     {
152       static const GtkTypeInfo menu_info =
153       {
154         "GtkMenu",
155         sizeof (GtkMenu),
156         sizeof (GtkMenuClass),
157         (GtkClassInitFunc) gtk_menu_class_init,
158         (GtkObjectInitFunc) gtk_menu_init,
159         /* reserved_1 */ NULL,
160         /* reserved_2 */ NULL,
161         (GtkClassInitFunc) NULL,
162       };
163       
164       menu_type = gtk_type_unique (gtk_menu_shell_get_type (), &menu_info);
165     }
166   
167   return menu_type;
168 }
169
170 static void
171 gtk_menu_class_init (GtkMenuClass *class)
172 {
173   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
174   GtkObjectClass *object_class = GTK_OBJECT_CLASS (class);
175   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
176   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
177   GtkMenuShellClass *menu_shell_class = GTK_MENU_SHELL_CLASS (class);
178   GtkBindingSet *binding_set;
179   
180   parent_class = g_type_class_peek_parent (class);
181   
182   gobject_class->finalize = gtk_menu_finalize;
183   gobject_class->set_property = gtk_menu_set_property;
184   gobject_class->get_property = gtk_menu_get_property;
185
186   g_object_class_install_property (gobject_class,
187                                    PROP_TEAROFF_TITLE,
188                                    g_param_spec_string ("tearoff-title",
189                                                         _("Tearoff Title"),
190                                                         _("A title that may be displayed by the window manager when this menu is torn-off."),
191                                                         "",
192                                                         G_PARAM_READABLE | G_PARAM_WRITABLE));
193   object_class->destroy = gtk_menu_destroy;
194   
195   widget_class->realize = gtk_menu_realize;
196   widget_class->unrealize = gtk_menu_unrealize;
197   widget_class->size_request = gtk_menu_size_request;
198   widget_class->size_allocate = gtk_menu_size_allocate;
199   widget_class->show = gtk_menu_show;
200   widget_class->expose_event = gtk_menu_expose;
201   widget_class->key_press_event = gtk_menu_key_press;
202   widget_class->motion_notify_event = gtk_menu_motion_notify;
203   widget_class->show_all = gtk_menu_show_all;
204   widget_class->hide_all = gtk_menu_hide_all;
205   widget_class->enter_notify_event = gtk_menu_enter_notify;
206   widget_class->leave_notify_event = gtk_menu_leave_notify;
207
208   container_class->remove = gtk_menu_remove;
209   
210   menu_shell_class->submenu_placement = GTK_LEFT_RIGHT;
211   menu_shell_class->deactivate = gtk_menu_deactivate;
212   menu_shell_class->select_item = gtk_menu_select_item;
213   menu_shell_class->insert = gtk_menu_real_insert;
214
215   binding_set = gtk_binding_set_by_class (class);
216   gtk_binding_entry_add_signal (binding_set,
217                                 GDK_Up, 0,
218                                 "move_current", 1,
219                                 GTK_TYPE_MENU_DIRECTION_TYPE,
220                                 GTK_MENU_DIR_PREV);
221   gtk_binding_entry_add_signal (binding_set,
222                                 GDK_KP_Up, 0,
223                                 "move_current", 1,
224                                 GTK_TYPE_MENU_DIRECTION_TYPE,
225                                 GTK_MENU_DIR_PREV);
226   gtk_binding_entry_add_signal (binding_set,
227                                 GDK_Down, 0,
228                                 "move_current", 1,
229                                 GTK_TYPE_MENU_DIRECTION_TYPE,
230                                 GTK_MENU_DIR_NEXT);
231   gtk_binding_entry_add_signal (binding_set,
232                                 GDK_KP_Down, 0,
233                                 "move_current", 1,
234                                 GTK_TYPE_MENU_DIRECTION_TYPE,
235                                 GTK_MENU_DIR_NEXT);
236   gtk_binding_entry_add_signal (binding_set,
237                                 GDK_Left, 0,
238                                 "move_current", 1,
239                                 GTK_TYPE_MENU_DIRECTION_TYPE,
240                                 GTK_MENU_DIR_PARENT);
241   gtk_binding_entry_add_signal (binding_set,
242                                 GDK_KP_Left, 0,
243                                 "move_current", 1,
244                                 GTK_TYPE_MENU_DIRECTION_TYPE,
245                                 GTK_MENU_DIR_PARENT);
246   gtk_binding_entry_add_signal (binding_set,
247                                 GDK_Right, 0,
248                                 "move_current", 1,
249                                 GTK_TYPE_MENU_DIRECTION_TYPE,
250                                 GTK_MENU_DIR_CHILD);
251   gtk_binding_entry_add_signal (binding_set,
252                                 GDK_KP_Right, 0,
253                                 "move_current", 1,
254                                 GTK_TYPE_MENU_DIRECTION_TYPE,
255                                 GTK_MENU_DIR_CHILD);
256 }
257
258
259 static void 
260 gtk_menu_set_property (GObject      *object,
261                        guint         prop_id,
262                        const GValue *value,
263                        GParamSpec   *pspec)
264 {
265   GtkMenu *menu;
266   
267   menu = GTK_MENU (object);
268   
269   switch (prop_id)
270     {
271     case PROP_TEAROFF_TITLE:
272       gtk_menu_set_title (menu, g_value_get_string (value));
273       break;      
274     default:
275       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
276       break;
277     }
278 }
279
280 static void 
281 gtk_menu_get_property (GObject     *object,
282                        guint        prop_id,
283                        GValue      *value,
284                        GParamSpec  *pspec)
285 {
286   GtkMenu *menu;
287   
288   menu = GTK_MENU (object);
289   
290   switch (prop_id)
291     {
292     case PROP_TEAROFF_TITLE:
293       g_value_set_string (value, gtk_menu_get_title (menu));
294       break;
295     default:
296       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
297       break;
298     }
299 }
300
301 static gboolean
302 gtk_menu_window_event (GtkWidget *window,
303                        GdkEvent  *event,
304                        GtkWidget *menu)
305 {
306   gboolean handled = FALSE;
307
308   gtk_widget_ref (window);
309   gtk_widget_ref (menu);
310
311   switch (event->type)
312     {
313     case GDK_KEY_PRESS:
314     case GDK_KEY_RELEASE:
315       handled = gtk_widget_event (menu, event);
316       break;
317     default:
318       break;
319     }
320
321   gtk_widget_unref (window);
322   gtk_widget_unref (menu);
323
324   return handled;
325 }
326
327 static void
328 gtk_menu_init (GtkMenu *menu)
329 {
330   menu->parent_menu_item = NULL;
331   menu->old_active_menu_item = NULL;
332   menu->accel_group = NULL;
333   menu->position_func = NULL;
334   menu->position_func_data = NULL;
335   menu->toggle_size = 0;
336
337   menu->toplevel = g_object_connect (gtk_widget_new (GTK_TYPE_WINDOW,
338                                                      "type", GTK_WINDOW_POPUP,
339                                                      "child", menu,
340                                                      NULL),
341                                      "signal::event", gtk_menu_window_event, menu,
342                                      "signal::destroy", gtk_widget_destroyed, &menu->toplevel,
343                                      NULL);
344   gtk_window_set_policy (GTK_WINDOW (menu->toplevel),
345                          FALSE, FALSE, TRUE);
346   gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->toplevel), 0);
347
348   /* Refloat the menu, so that reference counting for the menu isn't
349    * affected by it being a child of the toplevel
350    */
351   GTK_WIDGET_SET_FLAGS (menu, GTK_FLOATING);
352   menu->needs_destruction_ref_count = TRUE;
353
354   menu->view_window = NULL;
355   menu->bin_window = NULL;
356
357   menu->scroll_offset = 0;
358   menu->scroll_step  = 0;
359   menu->timeout_id = 0;
360   menu->scroll_fast = FALSE;
361   
362   menu->tearoff_window = NULL;
363   menu->tearoff_hbox = NULL;
364   menu->torn_off = FALSE;
365   menu->tearoff_active = FALSE;
366   menu->tearoff_adjustment = NULL;
367   menu->tearoff_scrollbar = NULL;
368
369   menu->upper_arrow_visible = FALSE;
370   menu->lower_arrow_visible = FALSE;
371   menu->upper_arrow_prelight = FALSE;
372   menu->lower_arrow_prelight = FALSE;
373   
374   MENU_NEEDS_RESIZE (menu) = TRUE;
375 }
376
377 static void
378 gtk_menu_destroy (GtkObject *object)
379 {
380   GtkMenu *menu;
381   GtkMenuAttachData *data;
382
383   g_return_if_fail (GTK_IS_MENU (object));
384
385   menu = GTK_MENU (object);
386
387   gtk_menu_stop_scrolling (menu);
388   
389   data = gtk_object_get_data (object, attach_data_key);
390   if (data)
391     gtk_menu_detach (menu);
392   
393   gtk_menu_stop_navigating_submenu (menu);
394
395   if (menu->old_active_menu_item)
396     {
397       gtk_widget_unref (menu->old_active_menu_item);
398       menu->old_active_menu_item = NULL;
399     }
400
401   /* Add back the reference count for being a child */
402   if (menu->needs_destruction_ref_count)
403     {
404       menu->needs_destruction_ref_count = FALSE;
405       gtk_object_ref (object);
406     }
407   
408   if (menu->accel_group)
409     {
410       g_object_unref (menu->accel_group);
411       menu->accel_group = NULL;
412     }
413
414   if (menu->toplevel)
415     gtk_widget_destroy (menu->toplevel);
416   if (menu->tearoff_window)
417     gtk_widget_destroy (menu->tearoff_window);
418
419   GTK_OBJECT_CLASS (parent_class)->destroy (object);
420 }
421
422 static void
423 gtk_menu_finalize (GObject *object)
424 {
425   GtkMenu *menu = GTK_MENU (object);
426
427   g_free (menu->accel_path);
428   
429   G_OBJECT_CLASS (parent_class)->finalize (object);
430 }
431
432 void
433 gtk_menu_attach_to_widget (GtkMenu             *menu,
434                            GtkWidget           *attach_widget,
435                            GtkMenuDetachFunc    detacher)
436 {
437   GtkMenuAttachData *data;
438   
439   g_return_if_fail (GTK_IS_MENU (menu));
440   g_return_if_fail (GTK_IS_WIDGET (attach_widget));
441   g_return_if_fail (detacher != NULL);
442   
443   /* keep this function in sync with gtk_widget_set_parent()
444    */
445   
446   data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
447   if (data)
448     {
449       g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
450                  gtk_type_name (GTK_OBJECT_TYPE (data->attach_widget)));
451       return;
452     }
453   
454   gtk_object_ref (GTK_OBJECT (menu));
455   gtk_object_sink (GTK_OBJECT (menu));
456   
457   data = g_new (GtkMenuAttachData, 1);
458   data->attach_widget = attach_widget;
459   data->detacher = detacher;
460   gtk_object_set_data (GTK_OBJECT (menu), attach_data_key, data);
461   
462   if (GTK_WIDGET_STATE (menu) != GTK_STATE_NORMAL)
463     gtk_widget_set_state (GTK_WIDGET (menu), GTK_STATE_NORMAL);
464   
465   /* we don't need to set the style here, since
466    * we are a toplevel widget.
467    */
468
469   /* Fallback title for menu comes from attach widget */
470   gtk_menu_update_title (menu);
471 }
472
473 GtkWidget*
474 gtk_menu_get_attach_widget (GtkMenu *menu)
475 {
476   GtkMenuAttachData *data;
477   
478   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
479   
480   data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
481   if (data)
482     return data->attach_widget;
483   return NULL;
484 }
485
486 void
487 gtk_menu_detach (GtkMenu *menu)
488 {
489   GtkMenuAttachData *data;
490   
491   g_return_if_fail (GTK_IS_MENU (menu));
492   
493   /* keep this function in sync with gtk_widget_unparent()
494    */
495   data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
496   if (!data)
497     {
498       g_warning ("gtk_menu_detach(): menu is not attached");
499       return;
500     }
501   gtk_object_remove_data (GTK_OBJECT (menu), attach_data_key);
502   
503   data->detacher (data->attach_widget, menu);
504   
505   if (GTK_WIDGET_REALIZED (menu))
506     gtk_widget_unrealize (GTK_WIDGET (menu));
507   
508   g_free (data);
509   
510   /* Fallback title for menu comes from attach widget */
511   gtk_menu_update_title (menu);
512
513   gtk_widget_unref (GTK_WIDGET (menu));
514 }
515
516 static void 
517 gtk_menu_remove (GtkContainer *container,
518                  GtkWidget    *widget)
519 {
520   GtkMenu *menu;
521   g_return_if_fail (GTK_IS_MENU (container));
522   g_return_if_fail (GTK_IS_MENU_ITEM (widget));
523
524   menu = GTK_MENU (container);
525
526   /* Clear out old_active_menu_item if it matches the item we are removing
527    */
528   if (menu->old_active_menu_item == widget)
529     {
530       gtk_widget_unref (menu->old_active_menu_item);
531       menu->old_active_menu_item = NULL;
532     }
533
534   GTK_CONTAINER_CLASS (parent_class)->remove (container, widget);
535 }
536
537
538 GtkWidget*
539 gtk_menu_new (void)
540 {
541   return GTK_WIDGET (gtk_type_new (gtk_menu_get_type ()));
542 }
543
544 static void
545 gtk_menu_real_insert (GtkMenuShell     *menu_shell,
546                       GtkWidget        *child,
547                       gint              position)
548 {
549   if (GTK_WIDGET_REALIZED (menu_shell))
550     gtk_widget_set_parent_window (child, GTK_MENU (menu_shell)->bin_window);
551   
552   GTK_MENU_SHELL_CLASS (parent_class)->insert (menu_shell, child, position);
553 }
554
555 static void
556 gtk_menu_tearoff_bg_copy (GtkMenu *menu)
557 {
558   GtkWidget *widget;
559   gint width, height;
560
561   widget = GTK_WIDGET (menu);
562
563   if (menu->torn_off)
564     {
565       GdkPixmap *pixmap;
566       GdkGC *gc;
567       GdkGCValues gc_values;
568
569       menu->tearoff_active = FALSE;
570       menu->saved_scroll_offset = menu->scroll_offset;
571       
572       gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS;
573       gc = gdk_gc_new_with_values (widget->window,
574                                    &gc_values, GDK_GC_SUBWINDOW);
575       
576       gdk_window_get_size (menu->tearoff_window->window, &width, &height);
577       
578       pixmap = gdk_pixmap_new (menu->tearoff_window->window,
579                                width,
580                                height,
581                                -1);
582
583       gdk_draw_pixmap (pixmap, gc,
584                        menu->tearoff_window->window,
585                        0, 0, 0, 0, -1, -1);
586       gdk_gc_unref (gc);
587
588       gtk_widget_set_usize (menu->tearoff_window,
589                             width,
590                             height);
591
592       gdk_window_set_back_pixmap (menu->tearoff_window->window, pixmap, FALSE);
593       gdk_pixmap_unref (pixmap);
594     }
595 }
596
597 void
598 gtk_menu_popup (GtkMenu             *menu,
599                 GtkWidget           *parent_menu_shell,
600                 GtkWidget           *parent_menu_item,
601                 GtkMenuPositionFunc  func,
602                 gpointer             data,
603                 guint                button,
604                 guint32              activate_time)
605 {
606   GtkWidget *widget;
607   GtkWidget *xgrab_shell;
608   GtkWidget *parent;
609   GdkEvent *current_event;
610   GtkMenuShell *menu_shell;
611
612   g_return_if_fail (GTK_IS_MENU (menu));
613   
614   widget = GTK_WIDGET (menu);
615   menu_shell = GTK_MENU_SHELL (menu);
616   
617   menu_shell->parent_menu_shell = parent_menu_shell;
618   menu_shell->active = TRUE;
619   menu_shell->button = button;
620
621   /* If we are popping up the menu from something other than, a button
622    * press then, as a heuristic, we ignore enter events for the menu
623    * until we get a MOTION_NOTIFY.  
624    */
625
626   current_event = gtk_get_current_event ();
627   if (current_event)
628     {
629       if ((current_event->type != GDK_BUTTON_PRESS) &&
630           (current_event->type != GDK_ENTER_NOTIFY))
631         menu_shell->ignore_enter = TRUE;
632     }
633
634   if (menu->torn_off)
635     {
636       gtk_menu_tearoff_bg_copy (menu);
637
638       gtk_menu_reparent (menu, menu->toplevel, FALSE);
639     }
640   
641   menu->parent_menu_item = parent_menu_item;
642   menu->position_func = func;
643   menu->position_func_data = data;
644   menu_shell->activate_time = activate_time;
645
646   gtk_menu_position (menu);
647
648   /* We need to show the menu _here_ because code expects to be
649    * able to tell if the menu is onscreen by looking at the
650    * GTK_WIDGET_VISIBLE (menu)
651    */
652   gtk_widget_show (GTK_WIDGET (menu));
653   gtk_widget_show (menu->toplevel);
654
655   if (current_event)
656     {
657       /* Also, if we're popping up from a key event, select the first
658        * item in the menu. Bad hack, but no better way to do it
659        * in current menu framework.
660        */
661       if (current_event->type == GDK_KEY_PRESS &&
662           GTK_MENU_SHELL (menu)->children)
663         {
664           gtk_menu_shell_select_item (GTK_MENU_SHELL (menu),
665                                       GTK_MENU_SHELL (menu)->children->data);
666         }
667       
668       gdk_event_free (current_event);
669     }
670   
671   gtk_menu_scroll_to (menu, menu->scroll_offset);
672
673   /* Find the last viewable ancestor, and make an X grab on it
674    */
675   parent = GTK_WIDGET (menu);
676   xgrab_shell = NULL;
677   while (parent)
678     {
679       gboolean viewable = TRUE;
680       GtkWidget *tmp = parent;
681       
682       while (tmp)
683         {
684           if (!GTK_WIDGET_MAPPED (tmp))
685             {
686               viewable = FALSE;
687               break;
688             }
689           tmp = tmp->parent;
690         }
691       
692       if (viewable)
693         xgrab_shell = parent;
694       
695       parent = GTK_MENU_SHELL (parent)->parent_menu_shell;
696     }
697   
698   if (xgrab_shell && (!GTK_MENU_SHELL (xgrab_shell)->have_xgrab))
699     {
700       if ((gdk_pointer_grab (xgrab_shell->window, TRUE,
701                              GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
702                              GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
703                              GDK_POINTER_MOTION_MASK,
704                              NULL, NULL, activate_time) == 0))
705         {
706           if (gdk_keyboard_grab (xgrab_shell->window, TRUE,
707                                  activate_time) == 0)
708             GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE;
709           else
710             gdk_pointer_ungrab (activate_time);
711         }
712     }
713
714   gtk_grab_add (GTK_WIDGET (menu));
715 }
716
717 void
718 gtk_menu_popdown (GtkMenu *menu)
719 {
720   GtkMenuShell *menu_shell;
721
722   g_return_if_fail (GTK_IS_MENU (menu));
723   
724   menu_shell = GTK_MENU_SHELL (menu);
725   
726   menu_shell->parent_menu_shell = NULL;
727   menu_shell->active = FALSE;
728   menu_shell->ignore_enter = FALSE;
729
730   gtk_menu_stop_scrolling (menu);
731   
732   gtk_menu_stop_navigating_submenu (menu);
733   
734   if (menu_shell->active_menu_item)
735     {
736       if (menu->old_active_menu_item)
737         gtk_widget_unref (menu->old_active_menu_item);
738       menu->old_active_menu_item = menu_shell->active_menu_item;
739       gtk_widget_ref (menu->old_active_menu_item);
740     }
741
742   gtk_menu_shell_deselect (menu_shell);
743   
744   /* The X Grab, if present, will automatically be removed when we hide
745    * the window */
746   gtk_widget_hide (menu->toplevel);
747
748   if (menu->torn_off)
749     {
750       gint width, height;
751       gdk_window_get_size (menu->tearoff_window->window, &width, &height);
752       gtk_widget_set_usize (menu->tearoff_window,
753                             -1,
754                             height);
755       
756       if (GTK_BIN (menu->toplevel)->child) 
757         {
758           gtk_menu_reparent (menu, menu->tearoff_hbox, TRUE);
759         } 
760       else
761         {
762           /* We popped up the menu from the tearoff, so we need to 
763            * release the grab - we aren't actually hiding the menu.
764            */
765           if (menu_shell->have_xgrab)
766             {
767               gdk_pointer_ungrab (GDK_CURRENT_TIME);
768               gdk_keyboard_ungrab (GDK_CURRENT_TIME);
769             }
770         }
771
772       /* gtk_menu_popdown is called each time a menu item is selected from
773        * a torn off menu. Only scroll back to the saved position if the
774        * non-tearoff menu was popped down.
775        */
776       if (!menu->tearoff_active)
777         gtk_menu_scroll_to (menu, menu->saved_scroll_offset);
778       menu->tearoff_active = TRUE;
779     }
780   else
781     gtk_widget_hide (GTK_WIDGET (menu));
782
783   menu_shell->have_xgrab = FALSE;
784   gtk_grab_remove (GTK_WIDGET (menu));
785 }
786
787 GtkWidget*
788 gtk_menu_get_active (GtkMenu *menu)
789 {
790   GtkWidget *child;
791   GList *children;
792   
793   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
794   
795   if (!menu->old_active_menu_item)
796     {
797       child = NULL;
798       children = GTK_MENU_SHELL (menu)->children;
799       
800       while (children)
801         {
802           child = children->data;
803           children = children->next;
804           
805           if (GTK_BIN (child)->child)
806             break;
807           child = NULL;
808         }
809       
810       menu->old_active_menu_item = child;
811       if (menu->old_active_menu_item)
812         gtk_widget_ref (menu->old_active_menu_item);
813     }
814   
815   return menu->old_active_menu_item;
816 }
817
818 void
819 gtk_menu_set_active (GtkMenu *menu,
820                      guint    index)
821 {
822   GtkWidget *child;
823   GList *tmp_list;
824   
825   g_return_if_fail (GTK_IS_MENU (menu));
826   
827   tmp_list = g_list_nth (GTK_MENU_SHELL (menu)->children, index);
828   if (tmp_list)
829     {
830       child = tmp_list->data;
831       if (GTK_BIN (child)->child)
832         {
833           if (menu->old_active_menu_item)
834             gtk_widget_unref (menu->old_active_menu_item);
835           menu->old_active_menu_item = child;
836           gtk_widget_ref (menu->old_active_menu_item);
837         }
838     }
839 }
840
841 void
842 gtk_menu_set_accel_group (GtkMenu       *menu,
843                           GtkAccelGroup *accel_group)
844 {
845   g_return_if_fail (GTK_IS_MENU (menu));
846   
847   if (menu->accel_group != accel_group)
848     {
849       if (menu->accel_group)
850         gtk_accel_group_unref (menu->accel_group);
851       menu->accel_group = accel_group;
852       if (menu->accel_group)
853         gtk_accel_group_ref (menu->accel_group);
854       _gtk_menu_refresh_accel_paths (menu, TRUE);
855     }
856 }
857
858 GtkAccelGroup*
859 gtk_menu_get_accel_group (GtkMenu *menu)
860 {
861   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
862
863   return menu->accel_group;
864 }
865
866 /**
867  * gtk_menu_set_accel_path
868  * @menu:       a valid #GtkMenu
869  * @accel_path: a valid accelerator path
870  *
871  * Sets an accelerator path for this menu from which accelerator paths
872  * for its immediate children, its menu items, can be constructed.
873  * The main purpose of this function is to spare the programmer the
874  * inconvenience of having to call gtk_menu_item_set_accel_path() on
875  * each menu item that should support runtime user changable accelerators.
876  * Instead, by just calling gtk_menu_set_accel_path() on their parent,
877  * each menu item of this menu, that contains a label describing its purpose,
878  * automatically gets an accel path assigned. For example, a menu containing
879  * menu items "New" and "Exit", will, after 
880  * <literal>gtk_menu_set_accel_path (menu, "&lt;Gnumeric-Sheet&gt;/File");</literal>
881  * has been called, assign its items the accel paths:
882  * <literal>"&lt;Gnumeric-Sheet&gt;/File/New"</literal> and <literal>"&lt;Gnumeric-Sheet&gt;/File/Exit"</literal>.
883  * Assigning accel paths to menu items then enables the user to change
884  * their accelerators at runtime. More details about accelerator paths
885  * and their default setups can be found at gtk_accel_map_add_entry().
886  */
887 void
888 gtk_menu_set_accel_path (GtkMenu     *menu,
889                          const gchar *accel_path)
890 {
891   g_return_if_fail (GTK_IS_MENU (menu));
892   if (accel_path)
893     g_return_if_fail (accel_path[0] == '<' && strchr (accel_path, '/')); /* simplistic check */
894
895   g_free (menu->accel_path);
896   menu->accel_path = g_strdup (accel_path);
897   if (menu->accel_path)
898     _gtk_menu_refresh_accel_paths (menu, FALSE);
899 }
900
901 typedef struct {
902   GtkMenu *menu;
903   gboolean group_changed;
904 } AccelPropagation;
905
906 static void
907 refresh_accel_paths_foreach (GtkWidget *widget,
908                              gpointer   data)
909 {
910   AccelPropagation *prop = data;
911
912   if (GTK_IS_MENU_ITEM (widget))        /* should always be true */
913     _gtk_menu_item_refresh_accel_path (GTK_MENU_ITEM (widget),
914                                        prop->menu->accel_path,
915                                        prop->menu->accel_group,
916                                        prop->group_changed);
917 }
918
919 static void
920 _gtk_menu_refresh_accel_paths (GtkMenu *menu,
921                                gboolean group_changed)
922 {
923   g_return_if_fail (GTK_IS_MENU (menu));
924       
925   if (menu->accel_path && menu->accel_group)
926     {
927       AccelPropagation prop;
928
929       prop.menu = menu;
930       prop.group_changed = group_changed;
931       gtk_container_foreach (GTK_CONTAINER (menu),
932                              refresh_accel_paths_foreach,
933                              &prop);
934     }
935 }
936
937 void
938 gtk_menu_reposition (GtkMenu *menu)
939 {
940   g_return_if_fail (GTK_IS_MENU (menu));
941
942   if (GTK_WIDGET_DRAWABLE (menu) && !menu->torn_off)
943     gtk_menu_position (menu);
944 }
945
946 static void
947 gtk_menu_scrollbar_changed (GtkAdjustment *adjustment,
948                             GtkMenu       *menu)
949 {
950   g_return_if_fail (GTK_IS_MENU (menu));
951
952   if (adjustment->value != menu->scroll_offset)
953     gtk_menu_scroll_to (menu, adjustment->value);
954 }
955
956 static void
957 gtk_menu_set_tearoff_hints (GtkMenu *menu,
958                             gint     width)
959 {
960   GdkGeometry geometry_hints;
961   
962   if (!menu->tearoff_window)
963     return;
964
965   if (GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
966     {
967       gtk_widget_size_request (menu->tearoff_scrollbar, NULL);
968       width += menu->tearoff_scrollbar->requisition.width;
969     }
970
971   geometry_hints.min_width = width;
972   geometry_hints.max_width = width;
973     
974   geometry_hints.min_height = 0;
975   geometry_hints.max_height = GTK_WIDGET (menu)->requisition.height;
976   gtk_window_set_geometry_hints (GTK_WINDOW (menu->tearoff_window),
977                                  NULL,
978                                  &geometry_hints,
979                                  GDK_HINT_MAX_SIZE|GDK_HINT_MIN_SIZE);
980 }
981
982 static void
983 gtk_menu_update_title (GtkMenu *menu)
984 {
985   if (menu->tearoff_window)
986     {
987       const gchar *title;
988       GtkWidget *attach_widget;
989
990       title = gtk_menu_get_title (menu);
991       if (!title)
992         {
993           attach_widget = gtk_menu_get_attach_widget (menu);
994           if (GTK_IS_MENU_ITEM (attach_widget))
995             {
996               GtkWidget *child = GTK_BIN (attach_widget)->child;
997               if (GTK_IS_LABEL (child))
998                 title = gtk_label_get_text (GTK_LABEL (child));
999             }
1000         }
1001       
1002       if (title)
1003         gtk_window_set_title (GTK_WINDOW (menu->tearoff_window), title);
1004     }
1005 }
1006
1007 void       
1008 gtk_menu_set_tearoff_state (GtkMenu  *menu,
1009                             gboolean  torn_off)
1010 {
1011   gint width, height;
1012   
1013   g_return_if_fail (GTK_IS_MENU (menu));
1014
1015   if (menu->torn_off != torn_off)
1016     {
1017       menu->torn_off = torn_off;
1018       menu->tearoff_active = torn_off;
1019       
1020       if (menu->torn_off)
1021         {
1022           if (GTK_WIDGET_VISIBLE (menu))
1023             gtk_menu_popdown (menu);
1024
1025           if (!menu->tearoff_window)
1026             {
1027               menu->tearoff_window = g_object_connect (gtk_widget_new (GTK_TYPE_WINDOW,
1028                                                                        "type", GTK_WINDOW_TOPLEVEL,
1029                                                                        NULL),
1030                                                        "signal::destroy", gtk_widget_destroyed, &menu->tearoff_window,
1031                                                        NULL);
1032               gtk_window_set_type_hint (GTK_WINDOW (menu->tearoff_window),
1033                                         GDK_WINDOW_TYPE_HINT_MENU);
1034               gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->tearoff_window), 0);
1035               gtk_widget_set_app_paintable (menu->tearoff_window, TRUE);
1036               gtk_signal_connect (GTK_OBJECT (menu->tearoff_window),  
1037                                   "event",
1038                                   GTK_SIGNAL_FUNC (gtk_menu_window_event), 
1039                                   GTK_OBJECT (menu));
1040
1041               gtk_menu_update_title (menu);
1042
1043               gtk_widget_realize (menu->tearoff_window);
1044               
1045               gdk_window_set_decorations (menu->tearoff_window->window, 
1046                                           GDK_DECOR_ALL |
1047                                           GDK_DECOR_RESIZEH |
1048                                           GDK_DECOR_MINIMIZE |
1049                                           GDK_DECOR_MAXIMIZE);
1050               gtk_window_set_resizable (GTK_WINDOW (menu->tearoff_window), FALSE);
1051
1052               menu->tearoff_hbox = gtk_hbox_new (FALSE, FALSE);
1053               gtk_container_add (GTK_CONTAINER (menu->tearoff_window), menu->tearoff_hbox);
1054
1055               gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
1056               menu->tearoff_adjustment =
1057                 GTK_ADJUSTMENT (gtk_adjustment_new (0,
1058                                                     0,
1059                                                     GTK_WIDGET (menu)->requisition.height,
1060                                                     MENU_SCROLL_STEP,
1061                                                     height/2,
1062                                                     height));
1063               g_object_connect (GTK_OBJECT (menu->tearoff_adjustment),
1064                                 "signal::value_changed", gtk_menu_scrollbar_changed, menu,
1065                                 NULL);
1066               menu->tearoff_scrollbar = gtk_vscrollbar_new (menu->tearoff_adjustment);
1067
1068               gtk_box_pack_end (GTK_BOX (menu->tearoff_hbox),
1069                                 menu->tearoff_scrollbar,
1070                                 FALSE, FALSE, 0);
1071               
1072               if (menu->tearoff_adjustment->upper > height)
1073                 gtk_widget_show (menu->tearoff_scrollbar);
1074               
1075               gtk_widget_show (menu->tearoff_hbox);
1076             }
1077           
1078           gtk_menu_reparent (menu, menu->tearoff_hbox, FALSE);
1079
1080           gdk_window_get_size (GTK_WIDGET (menu)->window, &width, NULL);
1081
1082           /* Update menu->requisition
1083            */
1084           gtk_widget_size_request (GTK_WIDGET (menu), NULL);
1085   
1086           gtk_menu_set_tearoff_hints (menu, width);
1087             
1088           gtk_widget_realize (menu->tearoff_window);
1089           gtk_menu_position (menu);
1090           
1091           gtk_widget_show (GTK_WIDGET (menu));
1092           gtk_widget_show (menu->tearoff_window);
1093
1094           gtk_menu_scroll_to (menu, 0);
1095
1096         }
1097       else
1098         {
1099           gtk_widget_hide (menu->tearoff_window);
1100           gtk_menu_reparent (menu, menu->toplevel, FALSE);
1101         }
1102     }
1103 }
1104
1105 /**
1106  * gtk_menu_get_tearoff_state:
1107  * @menu: a #GtkMenu
1108  *
1109  * Returns whether the menu is torn off. See
1110  * gtk_menu_set_tearoff_state ().
1111  *
1112  * Return value: %TRUE if the menu is currently torn off.
1113  **/
1114 gboolean
1115 gtk_menu_get_tearoff_state (GtkMenu *menu)
1116 {
1117   g_return_val_if_fail (GTK_IS_MENU (menu), FALSE);
1118
1119   return menu->torn_off;
1120 }
1121
1122 /**
1123  * gtk_menu_set_title:
1124  * @menu: a #GtkMenu
1125  * @title: a string containing the title for the menu.
1126  * 
1127  * Sets the title string for the menu.  The title is displayed when the menu
1128  * is shown as a tearoff menu.
1129  **/
1130 void       
1131 gtk_menu_set_title (GtkMenu     *menu,
1132                     const gchar *title)
1133 {
1134   g_return_if_fail (GTK_IS_MENU (menu));
1135
1136   if (title)
1137     g_object_set_data_full (G_OBJECT (menu), "gtk-menu-title",
1138                             g_strdup (title), (GtkDestroyNotify) g_free);
1139   else
1140     g_object_set_data (G_OBJECT (menu), "gtk-menu-title", NULL);
1141     
1142   gtk_menu_update_title (menu);
1143   g_object_notify (G_OBJECT (menu), "tearoff_title");
1144 }
1145
1146 /**
1147  * gtk_menu_get_title:
1148  * @menu: a #GtkMenu
1149  *
1150  * Returns the title of the menu. See gtk_menu_set_title().
1151  *
1152  * Return value: the title of the menu, or %NULL if the menu has no
1153  *               title set on it.
1154  **/
1155 G_CONST_RETURN gchar *
1156 gtk_menu_get_title (GtkMenu *menu)
1157 {
1158   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
1159
1160   return gtk_object_get_data (GTK_OBJECT (menu), "gtk-menu-title");
1161 }
1162
1163 void
1164 gtk_menu_reorder_child (GtkMenu   *menu,
1165                         GtkWidget *child,
1166                         gint       position)
1167 {
1168   GtkMenuShell *menu_shell;
1169   g_return_if_fail (GTK_IS_MENU (menu));
1170   g_return_if_fail (GTK_IS_MENU_ITEM (child));
1171   menu_shell = GTK_MENU_SHELL (menu);
1172   if (g_list_find (menu_shell->children, child))
1173     {   
1174       menu_shell->children = g_list_remove (menu_shell->children, child);
1175       menu_shell->children = g_list_insert (menu_shell->children, child, position);   
1176       if (GTK_WIDGET_VISIBLE (menu_shell))
1177         gtk_widget_queue_resize (GTK_WIDGET (menu_shell));
1178     }   
1179 }
1180
1181 static void
1182 gtk_menu_realize (GtkWidget *widget)
1183 {
1184   GdkWindowAttr attributes;
1185   gint attributes_mask;
1186   gint border_width;
1187   GtkMenu *menu;
1188   GtkWidget *child;
1189   GList *children;
1190
1191   g_return_if_fail (GTK_IS_MENU (widget));
1192
1193   menu = GTK_MENU (widget);
1194   
1195   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1196   
1197   attributes.window_type = GDK_WINDOW_CHILD;
1198   attributes.x = widget->allocation.x;
1199   attributes.y = widget->allocation.y;
1200   attributes.width = widget->allocation.width;
1201   attributes.height = widget->allocation.height;
1202   attributes.wclass = GDK_INPUT_OUTPUT;
1203   attributes.visual = gtk_widget_get_visual (widget);
1204   attributes.colormap = gtk_widget_get_colormap (widget);
1205   
1206   attributes.event_mask = gtk_widget_get_events (widget);
1207   attributes.event_mask |= (GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK |
1208                             GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK );
1209   
1210   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1211   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
1212   gdk_window_set_user_data (widget->window, widget);
1213   
1214   border_width = GTK_CONTAINER (widget)->border_width;
1215   
1216   attributes.x = border_width + widget->style->xthickness;
1217   attributes.y = border_width + widget->style->ythickness;
1218   attributes.width = MAX (1, widget->allocation.width - attributes.x * 2);
1219   attributes.height = MAX (1, widget->allocation.height - attributes.y * 2);
1220
1221   if (menu->upper_arrow_visible)
1222     {
1223       attributes.y += MENU_SCROLL_ARROW_HEIGHT;
1224       attributes.height -= MENU_SCROLL_ARROW_HEIGHT;
1225     }
1226   if (menu->lower_arrow_visible)
1227     attributes.height -= MENU_SCROLL_ARROW_HEIGHT;
1228
1229   menu->view_window = gdk_window_new (widget->window, &attributes, attributes_mask);
1230   gdk_window_set_user_data (menu->view_window, menu);
1231
1232   attributes.x = 0;
1233   attributes.y = 0;
1234   attributes.height = MAX (1, widget->requisition.height - (border_width + widget->style->ythickness) * 2);
1235   
1236   menu->bin_window = gdk_window_new (menu->view_window, &attributes, attributes_mask);
1237   gdk_window_set_user_data (menu->bin_window, menu);
1238
1239   children = GTK_MENU_SHELL (menu)->children;
1240   while (children)
1241     {
1242       child = children->data;
1243       children = children->next;
1244           
1245       gtk_widget_set_parent_window (child, menu->bin_window);
1246     }
1247   
1248   widget->style = gtk_style_attach (widget->style, widget->window);
1249   gtk_style_set_background (widget->style, menu->bin_window, GTK_STATE_NORMAL);
1250   gtk_style_set_background (widget->style, menu->view_window, GTK_STATE_NORMAL);
1251   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
1252
1253   gtk_menu_scroll_item_visible (GTK_MENU_SHELL (widget),
1254                                 GTK_MENU_SHELL (widget)->active_menu_item);
1255
1256   gdk_window_show (menu->bin_window);
1257   gdk_window_show (menu->view_window);
1258 }
1259
1260 static void
1261 gtk_menu_unrealize (GtkWidget *widget)
1262 {
1263   GtkMenu *menu;
1264
1265   g_return_if_fail (GTK_IS_MENU (widget));
1266
1267   menu = GTK_MENU (widget);
1268
1269   gdk_window_set_user_data (menu->view_window, NULL);
1270   gdk_window_destroy (menu->view_window);
1271   menu->view_window = NULL;
1272
1273   gdk_window_set_user_data (menu->bin_window, NULL);
1274   gdk_window_destroy (menu->bin_window);
1275   menu->bin_window = NULL;
1276
1277   (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
1278 }
1279
1280 static void
1281 gtk_menu_size_request (GtkWidget      *widget,
1282                        GtkRequisition *requisition)
1283 {
1284   GtkMenu *menu;
1285   GtkMenuShell *menu_shell;
1286   GtkWidget *child;
1287   GList *children;
1288   guint max_toggle_size;
1289   guint max_accel_width;
1290   GtkRequisition child_requisition;
1291   
1292   g_return_if_fail (GTK_IS_MENU (widget));
1293   g_return_if_fail (requisition != NULL);
1294   
1295   menu = GTK_MENU (widget);
1296   menu_shell = GTK_MENU_SHELL (widget);
1297   
1298   requisition->width = 0;
1299   requisition->height = 0;
1300   
1301   max_toggle_size = 0;
1302   max_accel_width = 0;
1303   
1304   children = menu_shell->children;
1305   while (children)
1306     {
1307       child = children->data;
1308       children = children->next;
1309       
1310       if (GTK_WIDGET_VISIBLE (child))
1311         {
1312           gint toggle_size;
1313
1314           /* It's important to size_request the child
1315            * before doing the toggle size request, in
1316            * case the toggle size request depends on the size
1317            * request of a child of the child (e.g. for ImageMenuItem)
1318            */
1319           
1320           GTK_MENU_ITEM (child)->show_submenu_indicator = TRUE;
1321           gtk_widget_size_request (child, &child_requisition);
1322           
1323           requisition->width = MAX (requisition->width, child_requisition.width);
1324           requisition->height += child_requisition.height;
1325
1326           gtk_menu_item_toggle_size_request (GTK_MENU_ITEM (child), &toggle_size);
1327           max_toggle_size = MAX (max_toggle_size, toggle_size);
1328           max_accel_width = MAX (max_accel_width, GTK_MENU_ITEM (child)->accelerator_width);
1329         }
1330     }
1331   
1332   requisition->width += max_toggle_size + max_accel_width;
1333   requisition->width += (GTK_CONTAINER (menu)->border_width +
1334                          widget->style->xthickness) * 2;
1335   requisition->height += (GTK_CONTAINER (menu)->border_width +
1336                           widget->style->ythickness) * 2;
1337   
1338   menu->toggle_size = max_toggle_size;
1339
1340   /* If the requested width was different than the allocated width, we need to change
1341    * the geometry hints for the tear off window so that the window can actually be resized.
1342    * Don't resize the tearoff if it is not active, because it won't redraw (it is only a background pixmap).
1343    */
1344   if ((requisition->width != GTK_WIDGET (menu)->allocation.width) && menu->tearoff_active)
1345     gtk_menu_set_tearoff_hints (menu, requisition->width);
1346 }
1347
1348 static void
1349 gtk_menu_size_allocate (GtkWidget     *widget,
1350                         GtkAllocation *allocation)
1351 {
1352   GtkMenu *menu;
1353   GtkMenuShell *menu_shell;
1354   GtkWidget *child;
1355   GtkAllocation child_allocation;
1356   GList *children;
1357   gint x, y;
1358   gint width, height;
1359
1360   g_return_if_fail (GTK_IS_MENU (widget));
1361   g_return_if_fail (allocation != NULL);
1362   
1363   menu = GTK_MENU (widget);
1364   menu_shell = GTK_MENU_SHELL (widget);
1365
1366   widget->allocation = *allocation;
1367
1368   x = GTK_CONTAINER (menu)->border_width + widget->style->xthickness;
1369   y = GTK_CONTAINER (menu)->border_width + widget->style->ythickness;
1370   
1371   width = MAX (1, allocation->width - x * 2);
1372   height = MAX (1, allocation->height - y * 2);
1373   
1374   if (menu->upper_arrow_visible && !menu->tearoff_active)
1375     {
1376       y += MENU_SCROLL_ARROW_HEIGHT;
1377       height -= MENU_SCROLL_ARROW_HEIGHT;
1378     }
1379   
1380   if (menu->lower_arrow_visible && !menu->tearoff_active)
1381     height -= MENU_SCROLL_ARROW_HEIGHT;
1382   
1383   if (GTK_WIDGET_REALIZED (widget))
1384     {
1385       gdk_window_move_resize (widget->window,
1386                               allocation->x, allocation->y,
1387                               allocation->width, allocation->height);
1388
1389       gdk_window_move_resize (menu->view_window,
1390                               x,
1391                               y,
1392                               width,
1393                               height);
1394     }
1395
1396   if (menu_shell->children)
1397     {
1398       child_allocation.x = 0;
1399       child_allocation.y = 0;
1400       child_allocation.width = width;
1401       
1402       children = menu_shell->children;
1403       while (children)
1404         {
1405           child = children->data;
1406           children = children->next;
1407           
1408           if (GTK_WIDGET_VISIBLE (child))
1409             {
1410               GtkRequisition child_requisition;
1411               gtk_widget_get_child_requisition (child, &child_requisition);
1412               
1413               child_allocation.height = child_requisition.height;
1414
1415               gtk_menu_item_toggle_size_allocate (GTK_MENU_ITEM (child),
1416                                                   menu->toggle_size);
1417               gtk_widget_size_allocate (child, &child_allocation);
1418               gtk_widget_queue_draw (child);
1419               
1420               child_allocation.y += child_allocation.height;
1421             }
1422         }
1423       
1424       /* Resize the item window */
1425       if (GTK_WIDGET_REALIZED (widget))
1426         {
1427           gdk_window_resize (menu->bin_window,
1428                              child_allocation.width,
1429                              child_allocation.y);
1430         }
1431
1432
1433       if (menu->tearoff_active)
1434         {
1435           if (allocation->height >= widget->requisition.height)
1436             {
1437               if (GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1438                 {
1439                   gtk_widget_hide (menu->tearoff_scrollbar);
1440                   gtk_menu_set_tearoff_hints (menu, allocation->width);
1441                   gtk_widget_set_usize (menu->tearoff_window, -1, allocation->height);
1442
1443                   gtk_menu_scroll_to (menu, 0);
1444                 }
1445             }
1446           else
1447             {
1448               menu->tearoff_adjustment->upper = widget->requisition.height;
1449               menu->tearoff_adjustment->page_size = allocation->height;
1450               
1451               if (menu->tearoff_adjustment->value + menu->tearoff_adjustment->page_size >
1452                   menu->tearoff_adjustment->upper)
1453                 {
1454                   gint value;
1455                   value = menu->tearoff_adjustment->upper - menu->tearoff_adjustment->page_size;
1456                   if (value < 0)
1457                     value = 0;
1458                   gtk_menu_scroll_to (menu, value);
1459                 }
1460               
1461               gtk_adjustment_changed (menu->tearoff_adjustment);
1462               
1463               if (!GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1464                 {
1465                   gtk_widget_show (menu->tearoff_scrollbar);
1466                   gtk_menu_set_tearoff_hints (menu, allocation->width);
1467                   gtk_widget_set_usize (menu->tearoff_window, -1, allocation->height);
1468                 }
1469             }
1470         }
1471     }
1472 }
1473
1474 static void
1475 gtk_menu_paint (GtkWidget      *widget,
1476                 GdkEventExpose *event)
1477 {
1478   GtkMenu *menu;
1479   gint width, height;
1480   gint border_x, border_y;
1481   
1482   g_return_if_fail (GTK_IS_MENU (widget));
1483
1484   menu = GTK_MENU (widget);
1485   
1486   border_x = GTK_CONTAINER (widget)->border_width + widget->style->xthickness;
1487   border_y = GTK_CONTAINER (widget)->border_width + widget->style->ythickness;
1488   gdk_window_get_size (widget->window, &width, &height);
1489
1490   if (event->window == widget->window)
1491     {
1492       gtk_paint_box (widget->style,
1493                      widget->window,
1494                      GTK_STATE_NORMAL,
1495                      GTK_SHADOW_OUT,
1496                      NULL, widget, "menu",
1497                      0, 0, -1, -1);
1498       if (menu->upper_arrow_visible && !menu->tearoff_active)
1499         {
1500           gtk_paint_box (widget->style,
1501                          widget->window,
1502                          menu->upper_arrow_prelight ?
1503                          GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1504                          GTK_SHADOW_OUT,
1505                          NULL, widget, "menu",
1506                          border_x,
1507                          border_y,
1508                          width - 2*border_x,
1509                          MENU_SCROLL_ARROW_HEIGHT);
1510           
1511           gtk_paint_arrow (widget->style,
1512                            widget->window,
1513                            menu->upper_arrow_prelight ?
1514                            GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1515                            GTK_SHADOW_OUT,
1516                            NULL, widget, "menu",
1517                            GTK_ARROW_UP,
1518                            TRUE,
1519                            width / 2 - MENU_SCROLL_ARROW_HEIGHT / 2 + 1,
1520                            2 * border_y + 1,
1521                            MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2,
1522                            MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2);
1523         }
1524   
1525       if (menu->lower_arrow_visible && !menu->tearoff_active)
1526         {
1527           gtk_paint_box (widget->style,
1528                          widget->window,
1529                          menu->lower_arrow_prelight ?
1530                          GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1531                          GTK_SHADOW_OUT,
1532                          NULL, widget, "menu",
1533                          border_x,
1534                          height - border_y - MENU_SCROLL_ARROW_HEIGHT + 1,
1535                          width - 2*border_x,
1536                          MENU_SCROLL_ARROW_HEIGHT);
1537           
1538           gtk_paint_arrow (widget->style,
1539                            widget->window,
1540                            menu->lower_arrow_prelight ?
1541                            GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1542                            GTK_SHADOW_OUT,
1543                            NULL, widget, "menu",
1544                            GTK_ARROW_DOWN,
1545                            TRUE,
1546                            width / 2 - MENU_SCROLL_ARROW_HEIGHT / 2 + 1,
1547                            height - MENU_SCROLL_ARROW_HEIGHT + 1,
1548                            MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2,
1549                            MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2);
1550         }
1551     }
1552   else if (event->window == menu->view_window)
1553     {
1554       gint menu_height;
1555       gint top_pos;
1556       
1557       if (menu->scroll_offset < 0)
1558         gtk_paint_box (widget->style,
1559                        menu->view_window,
1560                        GTK_STATE_ACTIVE,
1561                        GTK_SHADOW_IN,
1562                        NULL, widget, "menu",
1563                        0, 0,
1564                        -1,
1565                        -menu->scroll_offset);
1566
1567       menu_height = widget->requisition.height - 2*border_y;
1568       top_pos = height - 2*border_y - (menu->upper_arrow_visible ? MENU_SCROLL_ARROW_HEIGHT : 0);
1569
1570       if (menu_height - menu->scroll_offset < top_pos)
1571         gtk_paint_box (widget->style,
1572                        menu->view_window,
1573                        GTK_STATE_ACTIVE,
1574                        GTK_SHADOW_IN,
1575                        NULL, widget, "menu",
1576                        0,
1577                        menu_height - menu->scroll_offset,
1578                        -1,
1579                        top_pos - (menu_height - menu->scroll_offset));
1580     }
1581 }
1582
1583 static gboolean
1584 gtk_menu_expose (GtkWidget      *widget,
1585                  GdkEventExpose *event)
1586 {
1587   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
1588   g_return_val_if_fail (event != NULL, FALSE);
1589
1590   if (GTK_WIDGET_DRAWABLE (widget))
1591     {
1592       gtk_menu_paint (widget, event);
1593       
1594       (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
1595     }
1596   
1597   return FALSE;
1598 }
1599
1600 static void
1601 gtk_menu_show (GtkWidget *widget)
1602 {
1603   GtkMenu *menu = GTK_MENU (widget);
1604
1605   _gtk_menu_refresh_accel_paths (menu, FALSE);
1606
1607   GTK_WIDGET_CLASS (parent_class)->show (widget);
1608 }
1609
1610 static gboolean
1611 gtk_menu_key_press (GtkWidget   *widget,
1612                     GdkEventKey *event)
1613 {
1614   GtkMenuShell *menu_shell;
1615   GtkMenu *menu;
1616   GtkAccelGroup *accel_group;
1617   gboolean delete = FALSE;
1618   gchar *accel = NULL;
1619   guint accel_key, accel_mods;
1620   
1621   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
1622   g_return_val_if_fail (event != NULL, FALSE);
1623       
1624   menu_shell = GTK_MENU_SHELL (widget);
1625   menu = GTK_MENU (widget);
1626   accel_group = gtk_menu_get_accel_group (menu);
1627   
1628   gtk_menu_stop_navigating_submenu (menu);
1629
1630   if (GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event))
1631     return TRUE;
1632     
1633   g_object_get (G_OBJECT (gtk_settings_get_default ()),
1634                 "gtk-menu-bar-accel",
1635                 &accel,
1636                 NULL);
1637
1638   if (accel)
1639     {
1640       guint keyval = 0;
1641       GdkModifierType mods = 0;
1642       gboolean handled = FALSE;
1643       
1644       gtk_accelerator_parse (accel, &keyval, &mods);
1645
1646       if (keyval == 0)
1647         g_warning ("Failed to parse menu bar accelerator '%s'\n", accel);
1648
1649       /* FIXME this is wrong, needs to be in the global accel resolution
1650        * thing, to properly consider i18n etc., but that probably requires
1651        * AccelGroup changes etc.
1652        */
1653       if (event->keyval == keyval &&
1654           (mods & event->state) == mods)
1655         {
1656           gtk_signal_emit_by_name (GTK_OBJECT (menu), "cancel");
1657         }
1658
1659       g_free (accel);
1660
1661       if (handled)
1662         return TRUE;
1663     }
1664   
1665   switch (event->keyval)
1666     {
1667     case GDK_Delete:
1668     case GDK_KP_Delete:
1669     case GDK_BackSpace:
1670       delete = TRUE;
1671       break;
1672     default:
1673       break;
1674     }
1675
1676   accel_key = event->keyval;
1677   accel_mods = event->state & gtk_accelerator_get_default_mod_mask ();
1678
1679   /* Modify the accelerators */
1680   if (menu_shell->active_menu_item &&
1681       GTK_BIN (menu_shell->active_menu_item)->child &&                  /* no seperators */
1682       GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL &&  /* no submenus */
1683       (delete ||
1684        (gtk_accelerator_valid (event->keyval, event->state) &&
1685         (accel_mods ||
1686          (accel_key >= GDK_F1 && accel_key <= GDK_F35)))))
1687     {
1688       GtkWidget *menu_item = menu_shell->active_menu_item;
1689       gboolean replace_accels = TRUE;
1690       const gchar *path;
1691
1692       path = GTK_MENU_ITEM (menu_item)->accel_path;
1693       if (!path)
1694         path = _gtk_widget_get_accel_path (menu_item);
1695       if (!path)
1696         {
1697           /* can't change accelerators on menu_items without paths
1698            * (basically, those items are accelerator-locked).
1699            */
1700           /* g_print("item has no path, menu prefix: %s\n", menu->accel_path); */
1701           gdk_beep ();
1702         }
1703       else
1704         {
1705           gboolean changed;
1706
1707           if (delete)
1708             {
1709               accel_key = 0;
1710               accel_mods = 0;
1711             }
1712           changed = gtk_accel_map_change_entry (path, accel_key, accel_mods, replace_accels);
1713
1714           if (!changed)
1715             {
1716               /* we failed, probably because this key is in use and
1717                * locked already
1718                */
1719               /* g_print("failed to change\n"); */
1720               gdk_beep ();
1721             }
1722         }
1723     }
1724   
1725   return TRUE;
1726 }
1727
1728 static gboolean
1729 gtk_menu_motion_notify  (GtkWidget         *widget,
1730                          GdkEventMotion    *event)
1731 {
1732   GtkWidget *menu_item;
1733   GtkMenu *menu;
1734   GtkMenuShell *menu_shell;
1735
1736   gboolean need_enter;
1737
1738
1739   if (GTK_IS_MENU (widget))
1740     gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE);
1741   
1742   /* We received the event for one of two reasons:
1743    *
1744    * a) We are the active menu, and did gtk_grab_add()
1745    * b) The widget is a child of ours, and the event was propagated
1746    *
1747    * Since for computation of navigation regions, we want the menu which
1748    * is the parent of the menu item, for a), we need to find that menu,
1749    * which may be different from 'widget'.
1750    */
1751   menu_item = gtk_get_event_widget ((GdkEvent*) event);
1752   if (!menu_item || !GTK_IS_MENU_ITEM (menu_item) ||
1753       !_gtk_menu_item_is_selectable (menu_item) ||
1754       !GTK_IS_MENU (menu_item->parent))
1755     return FALSE;
1756
1757   menu_shell = GTK_MENU_SHELL (menu_item->parent);
1758   menu = GTK_MENU (menu_shell);
1759   
1760   need_enter = (menu->navigation_region != NULL || menu_shell->ignore_enter);
1761
1762   /* Check to see if we are within an active submenu's navigation region
1763    */
1764   if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
1765     return TRUE; 
1766
1767   if (need_enter)
1768     {
1769       /* The menu is now sensitive to enter events on its items, but
1770        * was previously sensitive.  So we fake an enter event.
1771        */
1772       gint width, height;
1773       
1774       menu_shell->ignore_enter = FALSE; 
1775       
1776       gdk_window_get_size (event->window, &width, &height);
1777       if (event->x >= 0 && event->x < width &&
1778           event->y >= 0 && event->y < height)
1779         {
1780           GdkEvent send_event;
1781
1782           memset (&send_event, 0, sizeof (send_event));
1783           send_event.crossing.type = GDK_ENTER_NOTIFY;
1784           send_event.crossing.window = event->window;
1785           send_event.crossing.time = event->time;
1786           send_event.crossing.send_event = TRUE;
1787           send_event.crossing.x_root = event->x_root;
1788           send_event.crossing.y_root = event->y_root;
1789           send_event.crossing.x = event->x;
1790           send_event.crossing.y = event->y;
1791
1792           /* We send the event to 'widget', the currently active menu,
1793            * instead of 'menu', the menu that the pointer is in. This
1794            * will ensure that the event will be ignored unless the
1795            * menuitem is a child of the active menu or some parent
1796            * menu of the active menu.
1797            */
1798           return gtk_widget_event (widget, &send_event);
1799         }
1800     }
1801
1802   return FALSE;
1803 }
1804
1805 static gboolean
1806 gtk_menu_scroll_timeout (gpointer  data)
1807 {
1808   GtkMenu *menu;
1809   GtkWidget *widget;
1810   gint offset;
1811   gint view_width, view_height;
1812
1813   GDK_THREADS_ENTER ();
1814
1815   menu = GTK_MENU (data);
1816   widget = GTK_WIDGET (menu);
1817
1818   offset = menu->scroll_offset + menu->scroll_step;
1819
1820   /* If we scroll upward and the non-visible top part
1821    * is smaller than the scroll arrow it would be
1822    * pretty stupid to show the arrow and taking more
1823    * screen space than just scrolling to the top.
1824    */
1825   if ((menu->scroll_step < 0) && (offset < MENU_SCROLL_ARROW_HEIGHT))
1826     offset = 0;
1827
1828   /* Don't scroll over the top if we weren't before: */
1829   if ((menu->scroll_offset >= 0) && (offset < 0))
1830     offset = 0;
1831
1832   gdk_window_get_size (widget->window, &view_width, &view_height);
1833
1834   /* Don't scroll past the bottom if we weren't before: */
1835   if (menu->scroll_offset > 0)
1836     view_height -= MENU_SCROLL_ARROW_HEIGHT;
1837   
1838   if ((menu->scroll_offset + view_height <= widget->requisition.height) &&
1839       (offset + view_height > widget->requisition.height))
1840     offset = widget->requisition.height - view_height;
1841
1842   gtk_menu_scroll_to (menu, offset);
1843
1844   GDK_THREADS_LEAVE ();
1845
1846   return TRUE;
1847 }
1848
1849 static void
1850 gtk_menu_handle_scrolling (GtkMenu *menu, gboolean enter)
1851 {
1852   GtkMenuShell *menu_shell;
1853   gint width, height;
1854   gint x, y;
1855   gint border;
1856   GdkRectangle rect;
1857   gboolean in_arrow;
1858   gboolean scroll_fast = FALSE;
1859
1860   menu_shell = GTK_MENU_SHELL (menu);
1861
1862   gdk_window_get_pointer (GTK_WIDGET (menu)->window, &x, &y, NULL);
1863   gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
1864
1865   border = GTK_CONTAINER (menu)->border_width + GTK_WIDGET (menu)->style->ythickness;
1866
1867   if (menu->upper_arrow_visible && !menu->tearoff_active)
1868     {
1869       rect.x = 0;
1870       rect.y = 0;
1871       rect.width = width;
1872       rect.height = MENU_SCROLL_ARROW_HEIGHT + border;
1873       
1874       in_arrow = FALSE;
1875       if ((x >= rect.x) && (x < rect.x + rect.width) &&
1876           (y >= rect.y) && (y < rect.y + rect.height))
1877         {
1878           in_arrow = TRUE;
1879           scroll_fast = (y < rect.y + MENU_SCROLL_FAST_ZONE);
1880         }
1881         
1882       if (enter && in_arrow &&
1883           (!menu->upper_arrow_prelight || menu->scroll_fast != scroll_fast))
1884         {
1885           menu->upper_arrow_prelight = TRUE;
1886           menu->scroll_fast = scroll_fast;
1887           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
1888           
1889           /* Deselect the active item so that any submenus are poped down */
1890           gtk_menu_shell_deselect (menu_shell);
1891
1892           gtk_menu_stop_scrolling (menu);
1893           menu->scroll_step = -MENU_SCROLL_STEP;
1894           menu->timeout_id = g_timeout_add ((scroll_fast) ? MENU_SCROLL_TIMEOUT2 : MENU_SCROLL_TIMEOUT1,
1895                                             gtk_menu_scroll_timeout,
1896                                             menu);
1897         }
1898       else if (!enter && !in_arrow && menu->upper_arrow_prelight)
1899         {
1900           menu->upper_arrow_prelight = FALSE;
1901           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
1902           
1903           gtk_menu_stop_scrolling (menu);
1904         }
1905     }
1906   
1907   if (menu->lower_arrow_visible && !menu->tearoff_active)
1908     {
1909       rect.x = 0;
1910       rect.y = height - border - MENU_SCROLL_ARROW_HEIGHT;
1911       rect.width = width;
1912       rect.height = MENU_SCROLL_ARROW_HEIGHT + border;
1913
1914       in_arrow = FALSE;
1915       if ((x >= rect.x) && (x < rect.x + rect.width) &&
1916           (y >= rect.y) && (y < rect.y + rect.height))
1917         {
1918           in_arrow = TRUE;
1919           scroll_fast = (y > rect.y + rect.height - MENU_SCROLL_FAST_ZONE);
1920         }
1921
1922       if (enter && in_arrow &&
1923           (!menu->lower_arrow_prelight || menu->scroll_fast != scroll_fast))
1924         {
1925           menu->lower_arrow_prelight = TRUE;
1926           menu->scroll_fast = scroll_fast;
1927           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
1928
1929           /* Deselect the active item so that any submenus are poped down */
1930           gtk_menu_shell_deselect (menu_shell);
1931
1932           gtk_menu_stop_scrolling (menu);
1933           menu->scroll_step = MENU_SCROLL_STEP;
1934           menu->timeout_id = g_timeout_add ((scroll_fast) ? MENU_SCROLL_TIMEOUT2 : MENU_SCROLL_TIMEOUT1,
1935                                             gtk_menu_scroll_timeout,
1936                                             menu);
1937         }
1938       else if (!enter && !in_arrow && menu->lower_arrow_prelight)
1939         {
1940           menu->lower_arrow_prelight = FALSE;
1941           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
1942           
1943           gtk_menu_stop_scrolling (menu);
1944         }
1945     }
1946 }
1947
1948 static gboolean
1949 gtk_menu_enter_notify (GtkWidget        *widget,
1950                        GdkEventCrossing *event)
1951 {
1952   GtkWidget *menu_item;
1953
1954   if (widget && GTK_IS_MENU (widget))
1955     gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE);
1956       
1957   /* If this is a faked enter (see gtk_menu_motion_notify), 'widget'
1958    * will not correspond to the event widget's parent.  Check to see
1959    * if we are in the parent's navigation region.
1960    */
1961   menu_item = gtk_get_event_widget ((GdkEvent*) event);
1962   if (menu_item && GTK_IS_MENU_ITEM (menu_item) && GTK_IS_MENU (menu_item->parent) &&
1963       gtk_menu_navigating_submenu (GTK_MENU (menu_item->parent), event->x_root, event->y_root))
1964     return TRUE;
1965
1966   return GTK_WIDGET_CLASS (parent_class)->enter_notify_event (widget, event); 
1967 }
1968
1969 static gboolean
1970 gtk_menu_leave_notify (GtkWidget        *widget,
1971                        GdkEventCrossing *event)
1972 {
1973   GtkMenuShell *menu_shell;
1974   GtkMenu *menu;
1975   GtkMenuItem *menu_item;
1976   GtkWidget *event_widget;
1977
1978   menu = GTK_MENU (widget);
1979   menu_shell = GTK_MENU_SHELL (widget); 
1980   
1981   if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
1982     return TRUE; 
1983
1984   gtk_menu_handle_scrolling (menu, FALSE);
1985   
1986   event_widget = gtk_get_event_widget ((GdkEvent*) event);
1987   
1988   if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
1989     return TRUE;
1990   
1991   menu_item = GTK_MENU_ITEM (event_widget); 
1992   
1993   /* Here we check to see if we're leaving an active menu item with a submenu, 
1994    * in which case we enter submenu navigation mode. 
1995    */
1996   if (menu_shell->active_menu_item != NULL
1997       && menu_item->submenu != NULL
1998       && menu_item->submenu_placement == GTK_LEFT_RIGHT)
1999     {
2000       if (menu_item->submenu->window != NULL) 
2001         {
2002           gtk_menu_set_submenu_navigation_region (menu, menu_item, event);
2003           return TRUE;
2004         }
2005     }
2006   
2007   return GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event); 
2008 }
2009
2010 static void 
2011 gtk_menu_stop_navigating_submenu (GtkMenu *menu)
2012 {
2013   if (menu->navigation_region) 
2014     {
2015       gdk_region_destroy (menu->navigation_region);
2016       menu->navigation_region = NULL;
2017     }
2018   
2019   if (menu->navigation_timeout)
2020     {
2021       gtk_timeout_remove (menu->navigation_timeout);
2022       menu->navigation_timeout = 0;
2023     }
2024 }
2025
2026 /* When the timeout is elapsed, the navigation region is destroyed
2027  * and the menuitem under the pointer (if any) is selected.
2028  */
2029 static gboolean
2030 gtk_menu_stop_navigating_submenu_cb (gpointer user_data)
2031 {
2032   GtkMenu *menu = user_data;
2033   GdkWindow *child_window;
2034
2035   GDK_THREADS_ENTER ();
2036
2037   gtk_menu_stop_navigating_submenu (menu);
2038   
2039   if (GTK_WIDGET_REALIZED (menu))
2040     {
2041       child_window = gdk_window_get_pointer (menu->bin_window, NULL, NULL, NULL);
2042
2043       if (child_window)
2044         {
2045           GdkEventCrossing send_event;
2046
2047           memset (&send_event, 0, sizeof (send_event));
2048           send_event.window = child_window;
2049           send_event.type = GDK_ENTER_NOTIFY;
2050           send_event.time = GDK_CURRENT_TIME; /* Bogus */
2051           send_event.send_event = TRUE;
2052
2053           GTK_WIDGET_CLASS (parent_class)->enter_notify_event (GTK_WIDGET (menu), &send_event); 
2054         }
2055     }
2056
2057   GDK_THREADS_LEAVE ();
2058
2059   return FALSE; 
2060 }
2061
2062 static gboolean
2063 gtk_menu_navigating_submenu (GtkMenu *menu,
2064                              gint     event_x,
2065                              gint     event_y)
2066 {
2067   if (menu->navigation_region)
2068     {
2069       if (gdk_region_point_in (menu->navigation_region, event_x, event_y))
2070         return TRUE;
2071       else
2072         {
2073           gtk_menu_stop_navigating_submenu (menu);
2074           return FALSE;
2075         }
2076     }
2077   return FALSE;
2078 }
2079
2080 static void
2081 gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
2082                                         GtkMenuItem      *menu_item,
2083                                         GdkEventCrossing *event)
2084 {
2085   gint submenu_left = 0;
2086   gint submenu_right = 0;
2087   gint submenu_top = 0;
2088   gint submenu_bottom = 0;
2089   gint width = 0;
2090   gint height = 0;
2091   GdkPoint point[3];
2092   GtkWidget *event_widget;
2093
2094   g_return_if_fail (menu_item->submenu != NULL);
2095   g_return_if_fail (event != NULL);
2096   
2097   event_widget = gtk_get_event_widget ((GdkEvent*) event);
2098   
2099   gdk_window_get_origin (menu_item->submenu->window, &submenu_left, &submenu_top);
2100   gdk_window_get_size (menu_item->submenu->window, &width, &height);
2101   submenu_right = submenu_left + width;
2102   submenu_bottom = submenu_top + height;
2103   
2104   gdk_window_get_size (event_widget->window, &width, &height);
2105   
2106   if (event->x >= 0 && event->x < width)
2107     {
2108       /* Set navigation region */
2109       /* We fudge/give a little padding in case the user
2110        * ``misses the vertex'' of the triangle/is off by a pixel or two.
2111        */ 
2112       if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
2113         point[0].x = event->x_root - SUBMENU_NAV_REGION_PADDING; 
2114       else                             
2115         point[0].x = event->x_root + SUBMENU_NAV_REGION_PADDING;  
2116       
2117       /* Exiting the top or bottom? */ 
2118       if (event->y < 0)
2119         { /* top */
2120           point[0].y = event->y_root + SUBMENU_NAV_REGION_PADDING;
2121           point[1].y = submenu_top;
2122
2123           if (point[0].y <= point[1].y)
2124             return;
2125         }
2126       else
2127         { /* bottom */
2128           point[0].y = event->y_root - SUBMENU_NAV_REGION_PADDING; 
2129           point[1].y = submenu_bottom;
2130
2131           if (point[0].y >= point[1].y)
2132             return;
2133         }
2134       
2135       /* Submenu is to the left or right? */ 
2136       if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
2137         point[1].x = submenu_left;  /* right */
2138       else
2139         point[1].x = submenu_right; /* left */
2140       
2141       point[2].x = point[1].x;
2142       point[2].y = point[0].y;
2143
2144       gtk_menu_stop_navigating_submenu (menu);
2145       
2146       menu->navigation_region = gdk_region_polygon (point, 3, GDK_WINDING_RULE);
2147
2148       menu->navigation_timeout = gtk_timeout_add (SUBMENU_NAV_HYSTERESIS_TIMEOUT, 
2149                                                   gtk_menu_stop_navigating_submenu_cb, menu);
2150     }
2151 }
2152
2153 static void
2154 gtk_menu_deactivate (GtkMenuShell *menu_shell)
2155 {
2156   GtkWidget *parent;
2157   
2158   g_return_if_fail (GTK_IS_MENU (menu_shell));
2159   
2160   parent = menu_shell->parent_menu_shell;
2161   
2162   menu_shell->activate_time = 0;
2163   gtk_menu_popdown (GTK_MENU (menu_shell));
2164   
2165   if (parent)
2166     gtk_menu_shell_deactivate (GTK_MENU_SHELL (parent));
2167 }
2168
2169 static void
2170 gtk_menu_position (GtkMenu *menu)
2171 {
2172   GtkWidget *widget;
2173   GtkRequisition requisition;
2174   gint x, y;
2175   gint screen_width;
2176   gint screen_height;
2177   gint scroll_offset;
2178   gint menu_height;
2179   gboolean push_in;
2180   
2181   g_return_if_fail (GTK_IS_MENU (menu));
2182
2183   widget = GTK_WIDGET (menu);
2184
2185   screen_width = gdk_screen_width ();
2186   screen_height = gdk_screen_height ();
2187
2188   gdk_window_get_pointer (NULL, &x, &y, NULL);
2189
2190   /* We need the requisition to figure out the right place to
2191    * popup the menu. In fact, we always need to ask here, since
2192    * if a size_request was queued while we weren't popped up,
2193    * the requisition won't have been recomputed yet.
2194    */
2195   gtk_widget_size_request (widget, &requisition);
2196
2197   push_in = FALSE;
2198   
2199   if (menu->position_func)
2200     (* menu->position_func) (menu, &x, &y, &push_in, menu->position_func_data);
2201   else
2202     {
2203       x = CLAMP (x - 2, 0, MAX (0, screen_width - requisition.width));
2204       y = CLAMP (y - 2, 0, MAX (0, screen_height - requisition.height));
2205     }
2206
2207   scroll_offset = 0;
2208
2209   if (push_in)
2210     {
2211       menu_height = GTK_WIDGET (menu)->requisition.height;
2212
2213       if (y + menu_height > screen_height)
2214         {
2215           scroll_offset -= y + menu_height - screen_height;
2216           y = screen_height - menu_height;
2217         }
2218   
2219       if (y < 0)
2220         {
2221           scroll_offset -= y;
2222           y = 0;
2223         }
2224     }
2225
2226   if (y + requisition.height > screen_height)
2227     requisition.height = screen_height - y;
2228   
2229   if (y < 0)
2230     {
2231       scroll_offset -= y;
2232       requisition.height -= -y;
2233       y = 0;
2234     }
2235
2236   if (scroll_offset > 0)
2237     scroll_offset += MENU_SCROLL_ARROW_HEIGHT;
2238   
2239   gtk_window_move (GTK_WINDOW (GTK_MENU_SHELL (menu)->active ? menu->toplevel : menu->tearoff_window), 
2240                    x, y);
2241   gtk_widget_set_usize (GTK_MENU_SHELL (menu)->active ?
2242                         menu->toplevel : menu->tearoff_hbox,
2243                         -1, requisition.height);
2244   menu->scroll_offset = scroll_offset;
2245 }
2246
2247 static void
2248 gtk_menu_stop_scrolling (GtkMenu *menu)
2249 {
2250   if (menu->timeout_id)
2251     {
2252       g_source_remove (menu->timeout_id);
2253       menu->timeout_id = 0;
2254     }
2255 }
2256
2257
2258 static void
2259 gtk_menu_scroll_to (GtkMenu *menu,
2260                     gint    offset)
2261 {
2262   GtkWidget *widget;
2263   gint x, y;
2264   gint view_width, view_height;
2265   gint border_width;
2266   gboolean last_visible;
2267   gint menu_height;
2268
2269   widget = GTK_WIDGET (menu);
2270
2271   if (menu->tearoff_active &&
2272       menu->tearoff_adjustment &&
2273       (menu->tearoff_adjustment->value != offset))
2274     {
2275       menu->tearoff_adjustment->value = offset;
2276       gtk_adjustment_value_changed (menu->tearoff_adjustment);
2277     }
2278   
2279   /* Scroll the menu: */
2280   gdk_window_move (menu->bin_window, 0, -offset);
2281
2282   /* Move/resize the viewport according to arrows: */
2283   gdk_window_get_size (widget->window, &view_width, &view_height);
2284
2285   border_width = GTK_CONTAINER (menu)->border_width;
2286   view_width -= (border_width + widget->style->xthickness) * 2;
2287   view_height -= (border_width + widget->style->ythickness) * 2;
2288   menu_height = widget->requisition.height - (border_width + widget->style->ythickness) * 2;
2289
2290   x = border_width + widget->style->xthickness;
2291   y = border_width + widget->style->ythickness;
2292   
2293   if (!menu->tearoff_active)
2294     {
2295       last_visible = menu->upper_arrow_visible;
2296       menu->upper_arrow_visible = (offset > 0);
2297
2298       if (menu->upper_arrow_visible)
2299         view_height -= MENU_SCROLL_ARROW_HEIGHT;
2300       
2301       if ( (last_visible != menu->upper_arrow_visible) &&
2302            !menu->upper_arrow_visible)
2303         {
2304           menu->upper_arrow_prelight = FALSE;
2305           
2306           /* If we hid the upper arrow, possibly remove timeout */
2307           if (menu->scroll_step < 0)
2308             gtk_menu_stop_scrolling (menu);
2309         }
2310       
2311       last_visible = menu->lower_arrow_visible;
2312       menu->lower_arrow_visible = (view_height + offset < menu_height);
2313       
2314       if (menu->lower_arrow_visible)
2315         view_height -= MENU_SCROLL_ARROW_HEIGHT;
2316       
2317       if ( (last_visible != menu->lower_arrow_visible) &&
2318            !menu->lower_arrow_visible)
2319         {
2320           menu->lower_arrow_prelight = FALSE;
2321           
2322           /* If we hid the lower arrow, possibly remove timeout */
2323           if (menu->scroll_step > 0)
2324             gtk_menu_stop_scrolling (menu);
2325         }
2326       
2327       if (menu->upper_arrow_visible)
2328         y += MENU_SCROLL_ARROW_HEIGHT;
2329     }
2330   
2331   gdk_window_move_resize (menu->view_window,
2332                           x,
2333                           y,
2334                           view_width,
2335                           view_height);
2336
2337   menu->scroll_offset = offset;
2338 }
2339
2340 static void
2341 gtk_menu_scroll_item_visible (GtkMenuShell    *menu_shell,
2342                               GtkWidget       *menu_item)
2343 {
2344   GtkMenu *menu;
2345   GtkWidget *child;
2346   GList *children;
2347   GtkRequisition child_requisition;
2348   gint child_offset, child_height;
2349   gint width, height;
2350   gint y;
2351   gint arrow_height;
2352   gboolean last_child = 0;
2353   
2354   menu = GTK_MENU (menu_shell);
2355
2356   /* We need to check if the selected item fully visible.
2357    * If not we need to scroll the menu so that it becomes fully
2358    * visible.
2359    */
2360
2361   child = NULL;
2362   child_offset = 0;
2363   child_height = 0;
2364   children = menu_shell->children;
2365   while (children)
2366     {
2367       child = children->data;
2368       children = children->next;
2369       
2370       if (GTK_WIDGET_VISIBLE (child))
2371         {
2372           gtk_widget_size_request (child, &child_requisition);
2373           child_offset += child_height;
2374           child_height = child_requisition.height;
2375         }
2376       
2377       if (child == menu_item)
2378         {
2379           last_child = (children == NULL);
2380           break;
2381         }
2382     }
2383
2384   if (child == menu_item)
2385     {
2386       y = menu->scroll_offset;
2387       gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
2388
2389       height -= 2*GTK_CONTAINER (menu)->border_width + 2*GTK_WIDGET (menu)->style->ythickness;
2390       
2391       if (child_offset + child_height <= y)
2392         {
2393           /* Ignore the enter event we might get if the pointer is on the menu
2394            */
2395           menu_shell->ignore_enter = TRUE;
2396           gtk_menu_scroll_to (menu, child_offset);
2397         }
2398       else
2399         {
2400           arrow_height = 0;
2401           if (menu->upper_arrow_visible && !menu->tearoff_active)
2402             arrow_height += MENU_SCROLL_ARROW_HEIGHT;
2403           if (menu->lower_arrow_visible && !menu->tearoff_active)
2404             arrow_height += MENU_SCROLL_ARROW_HEIGHT;
2405           
2406           if (child_offset >= y + height - arrow_height)
2407             {
2408               arrow_height = 0;
2409               if (!last_child && !menu->tearoff_active)
2410                 arrow_height += MENU_SCROLL_ARROW_HEIGHT;
2411               
2412               y = child_offset + child_height - height + arrow_height;
2413               if ((y > 0) && !menu->tearoff_active)
2414                 {
2415                   /* Need upper arrow */
2416                   arrow_height += MENU_SCROLL_ARROW_HEIGHT;
2417                   y = child_offset + child_height - height + arrow_height;
2418                 }
2419               /* Ignore the enter event we might get if the pointer is on the menu
2420                */
2421               menu_shell->ignore_enter = TRUE;
2422               gtk_menu_scroll_to (menu, y);
2423             }
2424         }    
2425       
2426     }
2427 }
2428
2429 static void
2430 gtk_menu_select_item (GtkMenuShell  *menu_shell,
2431                       GtkWidget     *menu_item)
2432 {
2433   GtkMenu *menu = GTK_MENU (menu_shell);
2434
2435   if (GTK_WIDGET_REALIZED (GTK_WIDGET (menu)))
2436     gtk_menu_scroll_item_visible (menu_shell, menu_item);
2437
2438   GTK_MENU_SHELL_CLASS (parent_class)->select_item (menu_shell, menu_item);
2439 }
2440
2441
2442 /* Reparent the menu, taking care of the refcounting
2443  *
2444  * If unrealize is true we force a unrealize while reparenting the parent.
2445  * This can help eliminate flicker in some cases.
2446  *
2447  * What happens is that when the menu is unrealized and then re-realized,
2448  * the allocations are as follows:
2449  *
2450  *  parent - 1x1 at (0,0) 
2451  *  child1 - 100x20 at (0,0)
2452  *  child2 - 100x20 at (0,20)
2453  *  child3 - 100x20 at (0,40)
2454  *
2455  * That is, the parent is small but the children are full sized. Then,
2456  * when the queued_resize gets processed, the parent gets resized to
2457  * full size. 
2458  *
2459  * But in order to eliminate flicker when scrolling, gdkgeometry-x11.c
2460  * contains the following logic:
2461  * 
2462  * - if a move or resize operation on a window would change the clip 
2463  *   region on the children, then before the window is resized
2464  *   the background for children is temporarily set to None, the
2465  *   move/resize done, and the background for the children restored.
2466  *
2467  * So, at the point where the parent is resized to final size, the
2468  * background for the children is temporarily None, and thus they
2469  * are not cleared to the background color and the previous background
2470  * (the image of the menu) is left in place.
2471  */
2472 static void 
2473 gtk_menu_reparent (GtkMenu      *menu, 
2474                    GtkWidget    *new_parent, 
2475                    gboolean      unrealize)
2476 {
2477   GtkObject *object = GTK_OBJECT (menu);
2478   GtkWidget *widget = GTK_WIDGET (menu);
2479   gboolean was_floating = GTK_OBJECT_FLOATING (object);
2480
2481   gtk_object_ref (object);
2482   gtk_object_sink (object);
2483
2484   if (unrealize)
2485     {
2486       gtk_object_ref (object);
2487       gtk_container_remove (GTK_CONTAINER (widget->parent), widget);
2488       gtk_container_add (GTK_CONTAINER (new_parent), widget);
2489       gtk_object_unref (object);
2490     }
2491   else
2492     gtk_widget_reparent (GTK_WIDGET (menu), new_parent);
2493   
2494   if (was_floating)
2495     GTK_OBJECT_SET_FLAGS (object, GTK_FLOATING);
2496   else
2497     gtk_object_unref (object);
2498 }
2499
2500 static void
2501 gtk_menu_show_all (GtkWidget *widget)
2502 {
2503   g_return_if_fail (GTK_IS_MENU (widget));
2504
2505   /* Show children, but not self. */
2506   gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_show_all, NULL);
2507 }
2508
2509
2510 static void
2511 gtk_menu_hide_all (GtkWidget *widget)
2512 {
2513   g_return_if_fail (GTK_IS_MENU (widget));
2514
2515   /* Hide children, but not self. */
2516   gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_hide_all, NULL);
2517 }
2518
2519