]> Pileus Git - ~andy/gtk/blob - gtk/gtkmenu.c
Merge from stable.
[~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 #define GTK_MENU_INTERNALS
28
29 #include <string.h> /* memset */
30 #include "gdk/gdkkeysyms.h"
31 #include "gtkaccelmap.h"
32 #include "gtkbindings.h"
33 #include "gtklabel.h"
34 #include "gtkmain.h"
35 #include "gtkmarshalers.h"
36 #include "gtkmenu.h"
37 #include "gtkmenuitem.h"
38 #include "gtktearoffmenuitem.h"
39 #include "gtkwindow.h"
40 #include "gtkhbox.h"
41 #include "gtkvscrollbar.h"
42 #include "gtksettings.h"
43 #include "gtkintl.h"
44
45
46 #define MENU_ITEM_CLASS(w)   GTK_MENU_ITEM_GET_CLASS (w)
47 #define MENU_NEEDS_RESIZE(m) GTK_MENU_SHELL (m)->menu_flag
48
49 #define DEFAULT_POPUP_DELAY    225
50 #define DEFAULT_POPDOWN_DELAY  1000
51
52 #define NAVIGATION_REGION_OVERSHOOT 50  /* How much the navigation region
53                                          * extends below the submenu
54                                          */
55
56 #define MENU_SCROLL_STEP1 8
57 #define MENU_SCROLL_STEP2 15
58 #define MENU_SCROLL_ARROW_HEIGHT 16
59 #define MENU_SCROLL_FAST_ZONE 8
60 #define MENU_SCROLL_TIMEOUT1 50
61 #define MENU_SCROLL_TIMEOUT2 50
62
63 typedef struct _GtkMenuAttachData       GtkMenuAttachData;
64 typedef struct _GtkMenuPrivate          GtkMenuPrivate;
65
66 struct _GtkMenuAttachData
67 {
68   GtkWidget *attach_widget;
69   GtkMenuDetachFunc detacher;
70 };
71
72 struct _GtkMenuPrivate 
73 {
74   gboolean have_position;
75   gint x;
76   gint y;
77 };
78
79 enum {
80   MOVE_SCROLL,
81   LAST_SIGNAL
82 };
83
84 enum {
85   PROP_0,
86   PROP_TEAROFF_TITLE
87 };
88
89 static void     gtk_menu_class_init        (GtkMenuClass     *klass);
90 static void     gtk_menu_init              (GtkMenu          *menu);
91 static void     gtk_menu_set_property      (GObject      *object,
92                                             guint         prop_id,
93                                             const GValue *value,
94                                             GParamSpec   *pspec);
95 static void     gtk_menu_get_property      (GObject     *object,
96                                             guint        prop_id,
97                                             GValue      *value,
98                                             GParamSpec  *pspec);
99 static void     gtk_menu_destroy           (GtkObject        *object);
100 static void     gtk_menu_finalize          (GObject          *object);
101 static void     gtk_menu_realize           (GtkWidget        *widget);
102 static void     gtk_menu_unrealize         (GtkWidget        *widget);
103 static void     gtk_menu_size_request      (GtkWidget        *widget,
104                                             GtkRequisition   *requisition);
105 static void     gtk_menu_size_allocate     (GtkWidget        *widget,
106                                             GtkAllocation    *allocation);
107 static void     gtk_menu_paint             (GtkWidget        *widget,
108                                             GdkEventExpose   *expose);
109 static void     gtk_menu_show              (GtkWidget        *widget);
110 static gboolean gtk_menu_expose            (GtkWidget        *widget,
111                                             GdkEventExpose   *event);
112 static gboolean gtk_menu_key_press         (GtkWidget        *widget,
113                                             GdkEventKey      *event);
114 static gboolean gtk_menu_button_press      (GtkWidget        *widget,
115                                             GdkEventButton   *event);
116 static gboolean gtk_menu_button_release    (GtkWidget        *widget,
117                                             GdkEventButton   *event);
118 static gboolean gtk_menu_motion_notify     (GtkWidget        *widget,
119                                             GdkEventMotion   *event);
120 static gboolean gtk_menu_enter_notify      (GtkWidget        *widget,
121                                             GdkEventCrossing *event);
122 static gboolean gtk_menu_leave_notify      (GtkWidget        *widget,
123                                             GdkEventCrossing *event);
124 static void     gtk_menu_scroll_to         (GtkMenu          *menu,
125                                             gint              offset);
126
127 static void     gtk_menu_stop_scrolling        (GtkMenu  *menu);
128 static void     gtk_menu_remove_scroll_timeout (GtkMenu  *menu);
129 static gboolean gtk_menu_scroll_timeout        (gpointer  data);
130
131 static void     gtk_menu_scroll_item_visible (GtkMenuShell    *menu_shell,
132                                               GtkWidget       *menu_item);
133 static void     gtk_menu_select_item       (GtkMenuShell     *menu_shell,
134                                             GtkWidget        *menu_item);
135 static void     gtk_menu_real_insert       (GtkMenuShell     *menu_shell,
136                                             GtkWidget        *child,
137                                             gint              position);
138 static void     gtk_menu_scrollbar_changed (GtkAdjustment    *adjustment,
139                                             GtkMenu          *menu);
140 static void     gtk_menu_handle_scrolling  (GtkMenu          *menu,
141                                             gboolean         enter);
142 static void     gtk_menu_set_tearoff_hints (GtkMenu          *menu,
143                                             gint             width);
144 static void     gtk_menu_style_set         (GtkWidget        *widget,
145                                             GtkStyle         *previous_style);
146 static gboolean gtk_menu_focus             (GtkWidget        *widget,
147                                             GtkDirectionType direction);
148 static gint     gtk_menu_get_popup_delay   (GtkMenuShell     *menu_shell);
149 static void     gtk_menu_real_move_scroll  (GtkMenu          *menu,
150                                             GtkScrollType     type);
151
152 static void     gtk_menu_stop_navigating_submenu       (GtkMenu          *menu);
153 static gboolean gtk_menu_stop_navigating_submenu_cb    (gpointer          user_data);
154 static gboolean gtk_menu_navigating_submenu            (GtkMenu          *menu,
155                                                         gint              event_x,
156                                                         gint              event_y);
157 static void     gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
158                                                         GtkMenuItem      *menu_item,
159                                                         GdkEventCrossing *event);
160  
161 static void gtk_menu_deactivate     (GtkMenuShell      *menu_shell);
162 static void gtk_menu_show_all       (GtkWidget         *widget);
163 static void gtk_menu_hide_all       (GtkWidget         *widget);
164 static void gtk_menu_position       (GtkMenu           *menu);
165 static void gtk_menu_reparent       (GtkMenu           *menu, 
166                                      GtkWidget         *new_parent, 
167                                      gboolean           unrealize);
168 static void gtk_menu_remove         (GtkContainer      *menu,
169                                      GtkWidget         *widget);
170
171 static void gtk_menu_update_title   (GtkMenu           *menu);
172
173 static void       menu_grab_transfer_window_destroy (GtkMenu *menu);
174 static GdkWindow *menu_grab_transfer_window_get     (GtkMenu *menu);
175
176 static void _gtk_menu_refresh_accel_paths (GtkMenu *menu,
177                                            gboolean group_changed);
178
179 static GtkMenuShellClass *parent_class = NULL;
180 static const gchar       *attach_data_key = "gtk-menu-attach-data";
181
182 static guint menu_signals[LAST_SIGNAL] = { 0 };
183
184 GtkMenuPrivate *
185 gtk_menu_get_private (GtkMenu *menu)
186 {
187   GtkMenuPrivate *private;
188   static GQuark private_quark = 0;
189
190   if (!private_quark)
191     private_quark = g_quark_from_static_string ("gtk-menu-private");
192
193   private = g_object_get_qdata (G_OBJECT (menu), private_quark);
194
195   if (!private)
196     {
197       private = g_new0 (GtkMenuPrivate, 1);
198       private->have_position = FALSE;
199       
200       g_object_set_qdata_full (G_OBJECT (menu), private_quark,
201                                private, g_free);
202     }
203
204   return private;
205 }
206
207 GType
208 gtk_menu_get_type (void)
209 {
210   static GType menu_type = 0;
211   
212   if (!menu_type)
213     {
214       static const GTypeInfo menu_info =
215       {
216         sizeof (GtkMenuClass),
217         NULL,           /* base_init */
218         NULL,           /* base_finalize */
219         (GClassInitFunc) gtk_menu_class_init,
220         NULL,           /* class_finalize */
221         NULL,           /* class_data */
222         sizeof (GtkMenu),
223         0,              /* n_preallocs */
224         (GInstanceInitFunc) gtk_menu_init,
225       };
226       
227       menu_type = g_type_register_static (GTK_TYPE_MENU_SHELL, "GtkMenu",
228                                           &menu_info, 0);
229     }
230   
231   return menu_type;
232 }
233
234 static void
235 gtk_menu_class_init (GtkMenuClass *class)
236 {
237   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
238   GtkObjectClass *object_class = GTK_OBJECT_CLASS (class);
239   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
240   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
241   GtkMenuShellClass *menu_shell_class = GTK_MENU_SHELL_CLASS (class);
242   GtkBindingSet *binding_set;
243   
244   parent_class = g_type_class_peek_parent (class);
245   
246   gobject_class->finalize = gtk_menu_finalize;
247   gobject_class->set_property = gtk_menu_set_property;
248   gobject_class->get_property = gtk_menu_get_property;
249
250   menu_signals[MOVE_SCROLL] =
251     _gtk_binding_signal_new ("move_scroll",
252                              G_OBJECT_CLASS_TYPE (object_class),
253                              G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
254                              G_CALLBACK (gtk_menu_real_move_scroll),
255                              NULL, NULL,
256                              _gtk_marshal_VOID__ENUM,
257                              G_TYPE_NONE, 1,
258                              GTK_TYPE_SCROLL_TYPE);
259   
260   g_object_class_install_property (gobject_class,
261                                    PROP_TEAROFF_TITLE,
262                                    g_param_spec_string ("tearoff-title",
263                                                         _("Tearoff Title"),
264                                                         _("A title that may be displayed by the window manager when this menu is torn-off"),
265                                                         "",
266                                                         G_PARAM_READABLE | G_PARAM_WRITABLE));
267
268   gtk_widget_class_install_style_property (widget_class,
269                                            g_param_spec_int ("vertical-padding",
270                                                              _("Vertical Padding"),
271                                                              _("Extra space at the top and bottom of the menu"),
272                                                              0,
273                                                              G_MAXINT,
274                                                              1,
275                                                              G_PARAM_READABLE));
276
277   gtk_widget_class_install_style_property (widget_class,
278                                            g_param_spec_int ("vertical-offset",
279                                                              _("Vertical Offset"),
280                                                              _("When the menu is a submenu, position it this number of pixels offset vertically"),
281                                                              G_MININT,
282                                                              G_MAXINT,
283                                                              0,
284                                                              G_PARAM_READABLE));
285
286   gtk_widget_class_install_style_property (widget_class,
287                                            g_param_spec_int ("horizontal-offset",
288                                                              _("Horizontal Offset"),
289                                                              _("When the menu is a submenu, position it this number of pixels offset horizontally"),
290                                                              G_MININT,
291                                                              G_MAXINT,
292                                                              -2,
293                                                              G_PARAM_READABLE));
294
295   object_class->destroy = gtk_menu_destroy;
296   
297   widget_class->realize = gtk_menu_realize;
298   widget_class->unrealize = gtk_menu_unrealize;
299   widget_class->size_request = gtk_menu_size_request;
300   widget_class->size_allocate = gtk_menu_size_allocate;
301   widget_class->show = gtk_menu_show;
302   widget_class->expose_event = gtk_menu_expose;
303   widget_class->key_press_event = gtk_menu_key_press;
304   widget_class->button_press_event = gtk_menu_button_press;
305   widget_class->button_release_event = gtk_menu_button_release;
306   widget_class->motion_notify_event = gtk_menu_motion_notify;
307   widget_class->show_all = gtk_menu_show_all;
308   widget_class->hide_all = gtk_menu_hide_all;
309   widget_class->enter_notify_event = gtk_menu_enter_notify;
310   widget_class->leave_notify_event = gtk_menu_leave_notify;
311   widget_class->motion_notify_event = gtk_menu_motion_notify;
312   widget_class->style_set = gtk_menu_style_set;
313   widget_class->focus = gtk_menu_focus;
314
315   container_class->remove = gtk_menu_remove;
316   
317   menu_shell_class->submenu_placement = GTK_LEFT_RIGHT;
318   menu_shell_class->deactivate = gtk_menu_deactivate;
319   menu_shell_class->select_item = gtk_menu_select_item;
320   menu_shell_class->insert = gtk_menu_real_insert;
321   menu_shell_class->get_popup_delay = gtk_menu_get_popup_delay;
322
323   binding_set = gtk_binding_set_by_class (class);
324   gtk_binding_entry_add_signal (binding_set,
325                                 GDK_Up, 0,
326                                 "move_current", 1,
327                                 GTK_TYPE_MENU_DIRECTION_TYPE,
328                                 GTK_MENU_DIR_PREV);
329   gtk_binding_entry_add_signal (binding_set,
330                                 GDK_KP_Up, 0,
331                                 "move_current", 1,
332                                 GTK_TYPE_MENU_DIRECTION_TYPE,
333                                 GTK_MENU_DIR_PREV);
334   gtk_binding_entry_add_signal (binding_set,
335                                 GDK_Down, 0,
336                                 "move_current", 1,
337                                 GTK_TYPE_MENU_DIRECTION_TYPE,
338                                 GTK_MENU_DIR_NEXT);
339   gtk_binding_entry_add_signal (binding_set,
340                                 GDK_KP_Down, 0,
341                                 "move_current", 1,
342                                 GTK_TYPE_MENU_DIRECTION_TYPE,
343                                 GTK_MENU_DIR_NEXT);
344   gtk_binding_entry_add_signal (binding_set,
345                                 GDK_Left, 0,
346                                 "move_current", 1,
347                                 GTK_TYPE_MENU_DIRECTION_TYPE,
348                                 GTK_MENU_DIR_PARENT);
349   gtk_binding_entry_add_signal (binding_set,
350                                 GDK_KP_Left, 0,
351                                 "move_current", 1,
352                                 GTK_TYPE_MENU_DIRECTION_TYPE,
353                                 GTK_MENU_DIR_PARENT);
354   gtk_binding_entry_add_signal (binding_set,
355                                 GDK_Right, 0,
356                                 "move_current", 1,
357                                 GTK_TYPE_MENU_DIRECTION_TYPE,
358                                 GTK_MENU_DIR_CHILD);
359   gtk_binding_entry_add_signal (binding_set,
360                                 GDK_KP_Right, 0,
361                                 "move_current", 1,
362                                 GTK_TYPE_MENU_DIRECTION_TYPE,
363                                 GTK_MENU_DIR_CHILD);
364   gtk_binding_entry_add_signal (binding_set,
365                                 GDK_Home, 0,
366                                 "move_scroll", 1,
367                                 GTK_TYPE_SCROLL_TYPE,
368                                 GTK_SCROLL_START);
369   gtk_binding_entry_add_signal (binding_set,
370                                 GDK_KP_Home, 0,
371                                 "move_scroll", 1,
372                                 GTK_TYPE_SCROLL_TYPE,
373                                 GTK_SCROLL_START);
374   gtk_binding_entry_add_signal (binding_set,
375                                 GDK_End, 0,
376                                 "move_scroll", 1,
377                                 GTK_TYPE_SCROLL_TYPE,
378                                 GTK_SCROLL_END);
379   gtk_binding_entry_add_signal (binding_set,
380                                 GDK_KP_End, 0,
381                                 "move_scroll", 1,
382                                 GTK_TYPE_SCROLL_TYPE,
383                                 GTK_SCROLL_END);
384   gtk_binding_entry_add_signal (binding_set,
385                                 GDK_Page_Up, 0,
386                                 "move_scroll", 1,
387                                 GTK_TYPE_SCROLL_TYPE,
388                                 GTK_SCROLL_PAGE_UP);
389   gtk_binding_entry_add_signal (binding_set,
390                                 GDK_KP_Page_Up, 0,
391                                 "move_scroll", 1,
392                                 GTK_TYPE_SCROLL_TYPE,
393                                 GTK_SCROLL_PAGE_UP);
394   gtk_binding_entry_add_signal (binding_set,
395                                 GDK_Page_Down, 0,
396                                 "move_scroll", 1,
397                                 GTK_TYPE_SCROLL_TYPE,
398                                 GTK_SCROLL_PAGE_DOWN);
399   gtk_binding_entry_add_signal (binding_set,
400                                 GDK_KP_Page_Down, 0,
401                                 "move_scroll", 1,
402                                 GTK_TYPE_SCROLL_TYPE,
403                                 GTK_SCROLL_PAGE_DOWN);
404
405   gtk_settings_install_property (g_param_spec_boolean ("gtk-can-change-accels",
406                                                        _("Can change accelerators"),
407                                                        _("Whether menu accelerators can be changed by pressing a key over the menu item"),
408                                                        FALSE,
409                                                        G_PARAM_READWRITE));
410
411   gtk_settings_install_property (g_param_spec_int ("gtk-menu-popup-delay",
412                                                    _("Delay before submenus appear"),
413                                                    _("Minimum time the pointer must stay over a menu item before the submenu appear"),
414                                                    0,
415                                                    G_MAXINT,
416                                                    DEFAULT_POPUP_DELAY,
417                                                    G_PARAM_READWRITE));
418
419   gtk_settings_install_property (g_param_spec_int ("gtk-menu-popdown-delay",
420                                                    _("Delay before hiding a submenu"),
421                                                    _("The time before hiding a submenu when the pointer is moving towards the submenu"),
422                                                    0,
423                                                    G_MAXINT,
424                                                    DEFAULT_POPDOWN_DELAY,
425                                                    G_PARAM_READWRITE));
426                                                    
427 }
428
429
430 static void 
431 gtk_menu_set_property (GObject      *object,
432                        guint         prop_id,
433                        const GValue *value,
434                        GParamSpec   *pspec)
435 {
436   GtkMenu *menu;
437   
438   menu = GTK_MENU (object);
439   
440   switch (prop_id)
441     {
442     case PROP_TEAROFF_TITLE:
443       gtk_menu_set_title (menu, g_value_get_string (value));
444       break;      
445     default:
446       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
447       break;
448     }
449 }
450
451 static void 
452 gtk_menu_get_property (GObject     *object,
453                        guint        prop_id,
454                        GValue      *value,
455                        GParamSpec  *pspec)
456 {
457   GtkMenu *menu;
458   
459   menu = GTK_MENU (object);
460   
461   switch (prop_id)
462     {
463     case PROP_TEAROFF_TITLE:
464       g_value_set_string (value, gtk_menu_get_title (menu));
465       break;
466     default:
467       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
468       break;
469     }
470 }
471
472 static gboolean
473 gtk_menu_window_event (GtkWidget *window,
474                        GdkEvent  *event,
475                        GtkWidget *menu)
476 {
477   gboolean handled = FALSE;
478
479   g_object_ref (window);
480   g_object_ref (menu);
481
482   switch (event->type)
483     {
484     case GDK_KEY_PRESS:
485     case GDK_KEY_RELEASE:
486       handled = gtk_widget_event (menu, event);
487       break;
488     default:
489       break;
490     }
491
492   g_object_unref (window);
493   g_object_unref (menu);
494
495   return handled;
496 }
497
498 static void
499 gtk_menu_window_size_request (GtkWidget      *window,
500                               GtkRequisition *requisition,
501                               GtkMenu        *menu)
502 {
503   GtkMenuPrivate *private = gtk_menu_get_private (menu);
504
505   if (private->have_position)
506     {
507       GdkScreen *screen = gtk_widget_get_screen (window);
508       GdkRectangle monitor;
509       gint monitor_num;
510       
511       monitor_num = gdk_screen_get_monitor_at_point (screen,
512                                                      private->x, private->y);
513       gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
514
515       if (private->y + requisition->height > monitor.y + monitor.height)
516         requisition->height = monitor.y + monitor.height - private->y;
517
518       if (private->y < monitor.y)
519         requisition->height -= monitor.y - private->y;
520     }
521 }
522
523 static void
524 gtk_menu_init (GtkMenu *menu)
525 {
526   menu->parent_menu_item = NULL;
527   menu->old_active_menu_item = NULL;
528   menu->accel_group = NULL;
529   menu->position_func = NULL;
530   menu->position_func_data = NULL;
531   menu->toggle_size = 0;
532
533   menu->toplevel = g_object_connect (g_object_new (GTK_TYPE_WINDOW,
534                                                    "type", GTK_WINDOW_POPUP,
535                                                    "child", menu,
536                                                    NULL),
537                                      "signal::event", gtk_menu_window_event, menu,
538                                      "signal::size_request", gtk_menu_window_size_request, menu,
539                                      "signal::destroy", gtk_widget_destroyed, &menu->toplevel,
540                                      NULL);
541   gtk_window_set_resizable (GTK_WINDOW (menu->toplevel), FALSE);
542   gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->toplevel), 0);
543
544   /* Refloat the menu, so that reference counting for the menu isn't
545    * affected by it being a child of the toplevel
546    */
547   GTK_WIDGET_SET_FLAGS (menu, GTK_FLOATING);
548   menu->needs_destruction_ref_count = TRUE;
549
550   menu->view_window = NULL;
551   menu->bin_window = NULL;
552
553   menu->scroll_offset = 0;
554   menu->scroll_step  = 0;
555   menu->timeout_id = 0;
556   menu->scroll_fast = FALSE;
557   
558   menu->tearoff_window = NULL;
559   menu->tearoff_hbox = NULL;
560   menu->torn_off = FALSE;
561   menu->tearoff_active = FALSE;
562   menu->tearoff_adjustment = NULL;
563   menu->tearoff_scrollbar = NULL;
564
565   menu->upper_arrow_visible = FALSE;
566   menu->lower_arrow_visible = FALSE;
567   menu->upper_arrow_prelight = FALSE;
568   menu->lower_arrow_prelight = FALSE;
569   
570   MENU_NEEDS_RESIZE (menu) = TRUE;
571 }
572
573 static void
574 gtk_menu_destroy (GtkObject *object)
575 {
576   GtkMenu *menu;
577   GtkMenuAttachData *data;
578
579   g_return_if_fail (GTK_IS_MENU (object));
580
581   menu = GTK_MENU (object);
582
583   gtk_menu_stop_scrolling (menu);
584   
585   data = g_object_get_data (G_OBJECT (object), attach_data_key);
586   if (data)
587     gtk_menu_detach (menu);
588   
589   gtk_menu_stop_navigating_submenu (menu);
590
591   if (menu->old_active_menu_item)
592     {
593       g_object_unref (menu->old_active_menu_item);
594       menu->old_active_menu_item = NULL;
595     }
596
597   /* Add back the reference count for being a child */
598   if (menu->needs_destruction_ref_count)
599     {
600       menu->needs_destruction_ref_count = FALSE;
601       g_object_ref (object);
602     }
603   
604   if (menu->accel_group)
605     {
606       g_object_unref (menu->accel_group);
607       menu->accel_group = NULL;
608     }
609
610   if (menu->toplevel)
611     gtk_widget_destroy (menu->toplevel);
612   if (menu->tearoff_window)
613     gtk_widget_destroy (menu->tearoff_window);
614
615   GTK_OBJECT_CLASS (parent_class)->destroy (object);
616 }
617
618 static void
619 gtk_menu_finalize (GObject *object)
620 {
621   GtkMenu *menu = GTK_MENU (object);
622
623   g_free (menu->accel_path);
624   
625   G_OBJECT_CLASS (parent_class)->finalize (object);
626 }
627
628 static void
629 menu_change_screen (GtkMenu   *menu,
630                     GdkScreen *new_screen)
631 {
632   if (menu->torn_off)
633     {
634       gtk_window_set_screen (GTK_WINDOW (menu->tearoff_window), new_screen);
635       gtk_menu_position (menu);
636     }
637
638   gtk_window_set_screen (GTK_WINDOW (menu->toplevel), new_screen);
639 }
640
641 static void
642 attach_widget_screen_changed (GtkWidget *attach_widget,
643                               GdkScreen *previous_screen,
644                               GtkMenu   *menu)
645 {
646   if (gtk_widget_has_screen (attach_widget) &&
647       !g_object_get_data (G_OBJECT (menu), "gtk-menu-explicit-screen"))
648     {
649       menu_change_screen (menu, gtk_widget_get_screen (attach_widget));
650     }
651 }
652
653 void
654 gtk_menu_attach_to_widget (GtkMenu             *menu,
655                            GtkWidget           *attach_widget,
656                            GtkMenuDetachFunc    detacher)
657 {
658   GtkMenuAttachData *data;
659   
660   g_return_if_fail (GTK_IS_MENU (menu));
661   g_return_if_fail (GTK_IS_WIDGET (attach_widget));
662   g_return_if_fail (detacher != NULL);
663   
664   /* keep this function in sync with gtk_widget_set_parent()
665    */
666   
667   data = g_object_get_data (G_OBJECT (menu), attach_data_key);
668   if (data)
669     {
670       g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
671                  g_type_name (G_TYPE_FROM_INSTANCE (data->attach_widget)));
672      return;
673     }
674   
675   g_object_ref (menu);
676   gtk_object_sink (GTK_OBJECT (menu));
677   
678   data = g_new (GtkMenuAttachData, 1);
679   data->attach_widget = attach_widget;
680   
681   g_signal_connect (attach_widget, "screen_changed",
682                     G_CALLBACK (attach_widget_screen_changed), menu);
683   attach_widget_screen_changed (attach_widget, NULL, menu);
684   
685   data->detacher = detacher;
686   g_object_set_data (G_OBJECT (menu), attach_data_key, data);
687   
688   if (GTK_WIDGET_STATE (menu) != GTK_STATE_NORMAL)
689     gtk_widget_set_state (GTK_WIDGET (menu), GTK_STATE_NORMAL);
690   
691   /* we don't need to set the style here, since
692    * we are a toplevel widget.
693    */
694
695   /* Fallback title for menu comes from attach widget */
696   gtk_menu_update_title (menu);
697 }
698
699 GtkWidget*
700 gtk_menu_get_attach_widget (GtkMenu *menu)
701 {
702   GtkMenuAttachData *data;
703   
704   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
705   
706   data = g_object_get_data (G_OBJECT (menu), attach_data_key);
707   if (data)
708     return data->attach_widget;
709   return NULL;
710 }
711
712 void
713 gtk_menu_detach (GtkMenu *menu)
714 {
715   GtkMenuAttachData *data;
716   
717   g_return_if_fail (GTK_IS_MENU (menu));
718   
719   /* keep this function in sync with gtk_widget_unparent()
720    */
721   data = g_object_get_data (G_OBJECT (menu), attach_data_key);
722   if (!data)
723     {
724       g_warning ("gtk_menu_detach(): menu is not attached");
725       return;
726     }
727   g_object_set_data (G_OBJECT (menu), attach_data_key, NULL);
728   
729   g_signal_handlers_disconnect_by_func (data->attach_widget,
730                                         (gpointer) attach_widget_screen_changed,
731                                         menu);
732
733   data->detacher (data->attach_widget, menu);
734   
735   if (GTK_WIDGET_REALIZED (menu))
736     gtk_widget_unrealize (GTK_WIDGET (menu));
737   
738   g_free (data);
739   
740   /* Fallback title for menu comes from attach widget */
741   gtk_menu_update_title (menu);
742
743   g_object_unref (menu);
744 }
745
746 static void 
747 gtk_menu_remove (GtkContainer *container,
748                  GtkWidget    *widget)
749 {
750   GtkMenu *menu;
751   g_return_if_fail (GTK_IS_MENU (container));
752   g_return_if_fail (GTK_IS_MENU_ITEM (widget));
753
754   menu = GTK_MENU (container);
755
756   /* Clear out old_active_menu_item if it matches the item we are removing
757    */
758   if (menu->old_active_menu_item == widget)
759     {
760       g_object_unref (menu->old_active_menu_item);
761       menu->old_active_menu_item = NULL;
762     }
763
764   GTK_CONTAINER_CLASS (parent_class)->remove (container, widget);
765 }
766
767
768 GtkWidget*
769 gtk_menu_new (void)
770 {
771   return g_object_new (GTK_TYPE_MENU, NULL);
772 }
773
774 static void
775 gtk_menu_real_insert (GtkMenuShell     *menu_shell,
776                       GtkWidget        *child,
777                       gint              position)
778 {
779   if (GTK_WIDGET_REALIZED (menu_shell))
780     gtk_widget_set_parent_window (child, GTK_MENU (menu_shell)->bin_window);
781
782   GTK_MENU_SHELL_CLASS (parent_class)->insert (menu_shell, child, position);
783 }
784
785 static void
786 gtk_menu_tearoff_bg_copy (GtkMenu *menu)
787 {
788   GtkWidget *widget;
789   gint width, height;
790
791   widget = GTK_WIDGET (menu);
792
793   if (menu->torn_off)
794     {
795       GdkPixmap *pixmap;
796       GdkGC *gc;
797       GdkGCValues gc_values;
798
799       menu->tearoff_active = FALSE;
800       menu->saved_scroll_offset = menu->scroll_offset;
801       
802       gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS;
803       gc = gdk_gc_new_with_values (widget->window,
804                                    &gc_values, GDK_GC_SUBWINDOW);
805       
806       gdk_drawable_get_size (menu->tearoff_window->window, &width, &height);
807       
808       pixmap = gdk_pixmap_new (menu->tearoff_window->window,
809                                width,
810                                height,
811                                -1);
812
813       gdk_draw_drawable (pixmap, gc,
814                          menu->tearoff_window->window,
815                          0, 0, 0, 0, -1, -1);
816       g_object_unref (gc);
817
818       gtk_widget_set_size_request (menu->tearoff_window,
819                                    width,
820                                    height);
821
822       gdk_window_set_back_pixmap (menu->tearoff_window->window, pixmap, FALSE);
823       g_object_unref (pixmap);
824     }
825 }
826
827 static gboolean
828 popup_grab_on_window (GdkWindow *window,
829                       guint32    activate_time)
830 {
831   if ((gdk_pointer_grab (window, TRUE,
832                          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
833                          GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
834                          GDK_POINTER_MOTION_MASK,
835                          NULL, NULL, activate_time) == 0))
836     {
837       if (gdk_keyboard_grab (window, TRUE,
838                              activate_time) == 0)
839         return TRUE;
840       else
841         {
842           gdk_display_pointer_ungrab (gdk_drawable_get_display (window),
843                                       activate_time);
844           return FALSE;
845         }
846     }
847
848   return FALSE;
849 }
850
851 /**
852  * gtk_menu_popup:
853  * @menu: a #GtkMenu.
854  * @parent_menu_shell: the menu shell containing the triggering menu item, or %NULL
855  * @parent_menu_item: the menu item whose activation triggered the popup, or %NULL
856  * @func: a user supplied function used to position the menu, or %NULL
857  * @data: user supplied data to be passed to @func.
858  * @button: the mouse button which was pressed to initiate the event.
859  * @activate_time: the time at which the activation event occurred.
860  *
861  * Displays a menu and makes it available for selection.  Applications can use
862  * this function to display context-sensitive menus, and will typically supply
863  * %NULL for the @parent_menu_shell, @parent_menu_item, @func and @data 
864  * parameters. The default menu positioning function will position the menu
865  * at the current mouse cursor position.
866  *
867  * The @button parameter should be the mouse button pressed to initiate
868  * the menu popup. If the menu popup was initiated by something other than
869  * a mouse button press, such as a mouse button release or a keypress,
870  * @button should be 0.
871  *
872  * The @activate_time parameter should be the time stamp of the event that
873  * initiated the popup. If such an event is not available, use
874  * gtk_get_current_event_time() instead.
875  *
876  */
877 void
878 gtk_menu_popup (GtkMenu             *menu,
879                 GtkWidget           *parent_menu_shell,
880                 GtkWidget           *parent_menu_item,
881                 GtkMenuPositionFunc  func,
882                 gpointer             data,
883                 guint                button,
884                 guint32              activate_time)
885 {
886   GtkWidget *widget;
887   GtkWidget *xgrab_shell;
888   GtkWidget *parent;
889   GdkEvent *current_event;
890   GtkMenuShell *menu_shell;
891
892   g_return_if_fail (GTK_IS_MENU (menu));
893   
894   widget = GTK_WIDGET (menu);
895   menu_shell = GTK_MENU_SHELL (menu);
896   
897   menu_shell->parent_menu_shell = parent_menu_shell;
898   
899   /* Find the last viewable ancestor, and make an X grab on it
900    */
901   parent = GTK_WIDGET (menu);
902   xgrab_shell = NULL;
903   while (parent)
904     {
905       gboolean viewable = TRUE;
906       GtkWidget *tmp = parent;
907       
908       while (tmp)
909         {
910           if (!GTK_WIDGET_MAPPED (tmp))
911             {
912               viewable = FALSE;
913               break;
914             }
915           tmp = tmp->parent;
916         }
917       
918       if (viewable)
919         xgrab_shell = parent;
920       
921       parent = GTK_MENU_SHELL (parent)->parent_menu_shell;
922     }
923
924   /* We want to receive events generated when we map the menu; unfortunately,
925    * since there is probably already an implicit grab in place from the
926    * button that the user used to pop up the menu, we won't receive then --
927    * in particular, the EnterNotify when the menu pops up under the pointer.
928    *
929    * If we are grabbing on a parent menu shell, no problem; just grab on
930    * that menu shell first before popping up the window with owner_events = TRUE.
931    *
932    * When grabbing on the menu itself, things get more convuluted - we
933    * we do an explicit grab on a specially created window with
934    * owner_events = TRUE, which we override further down with a grab
935    * on the menu. (We can't grab on the menu until it is mapped; we
936    * probably could just leave the grab on the other window, with a
937    * little reorganization of the code in gtkmenu*).
938    */
939   if (xgrab_shell && xgrab_shell != widget)
940     {
941       if (popup_grab_on_window (xgrab_shell->window, activate_time))
942         GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE;
943     }
944   else
945     {
946       GdkWindow *transfer_window;
947
948       xgrab_shell = widget;
949       transfer_window = menu_grab_transfer_window_get (menu);
950       if (popup_grab_on_window (transfer_window, activate_time))
951         GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE;
952     }
953
954   if (!GTK_MENU_SHELL (xgrab_shell)->have_xgrab)
955     {
956       /* We failed to make our pointer/keyboard grab. Rather than leaving the user
957        * with a stuck up window, we just abort here. Presumably the user will
958        * try again.
959        */
960       menu_shell->parent_menu_shell = NULL;
961       menu_grab_transfer_window_destroy (menu);
962       return;
963     }
964
965   menu_shell->active = TRUE;
966   menu_shell->button = button;
967
968   /* If we are popping up the menu from something other than, a button
969    * press then, as a heuristic, we ignore enter events for the menu
970    * until we get a MOTION_NOTIFY.  
971    */
972
973   current_event = gtk_get_current_event ();
974   if (current_event)
975     {
976       if ((current_event->type != GDK_BUTTON_PRESS) &&
977           (current_event->type != GDK_ENTER_NOTIFY))
978         menu_shell->ignore_enter = TRUE;
979
980       gdk_event_free (current_event);
981     }
982
983   if (menu->torn_off)
984     {
985       gtk_menu_tearoff_bg_copy (menu);
986
987       gtk_menu_reparent (menu, menu->toplevel, FALSE);
988     }
989   
990   menu->parent_menu_item = parent_menu_item;
991   menu->position_func = func;
992   menu->position_func_data = data;
993   menu_shell->activate_time = activate_time;
994
995   /* We need to show the menu here rather in the init function because
996    * code expects to be able to tell if the menu is onscreen by
997    * looking at the GTK_WIDGET_VISIBLE (menu)
998    */
999   gtk_widget_show (GTK_WIDGET (menu));
1000
1001   /* Position the menu, possibly changing the size request
1002    */
1003   gtk_menu_position (menu);
1004
1005   /* Compute the size of the toplevel and realize it so we
1006    * can scroll correctly.
1007    */
1008   {
1009     GtkRequisition tmp_request;
1010     GtkAllocation tmp_allocation = { 0, };
1011
1012     gtk_widget_size_request (menu->toplevel, &tmp_request);
1013     
1014     tmp_allocation.width = tmp_request.width;
1015     tmp_allocation.height = tmp_request.height;
1016
1017     gtk_widget_size_allocate (menu->toplevel, &tmp_allocation);
1018     
1019     gtk_widget_realize (GTK_WIDGET (menu));
1020   }
1021
1022   gtk_menu_scroll_to (menu, menu->scroll_offset);
1023
1024   /* Once everything is set up correctly, map the toplevel window on
1025      the screen.
1026    */
1027   gtk_widget_show (menu->toplevel);
1028
1029   if (xgrab_shell == widget)
1030     popup_grab_on_window (widget->window, activate_time); /* Should always succeed */
1031
1032   gtk_grab_add (GTK_WIDGET (menu));
1033 }
1034
1035 void
1036 gtk_menu_popdown (GtkMenu *menu)
1037 {
1038   GtkMenuPrivate *private;
1039   GtkMenuShell *menu_shell;
1040
1041   g_return_if_fail (GTK_IS_MENU (menu));
1042   
1043   menu_shell = GTK_MENU_SHELL (menu);
1044   private = gtk_menu_get_private (menu);
1045   
1046   menu_shell->parent_menu_shell = NULL;
1047   menu_shell->active = FALSE;
1048   menu_shell->ignore_enter = FALSE;
1049
1050   private->have_position = FALSE;
1051
1052   gtk_menu_stop_scrolling (menu);
1053   
1054   gtk_menu_stop_navigating_submenu (menu);
1055   
1056   if (menu_shell->active_menu_item)
1057     {
1058       if (menu->old_active_menu_item)
1059         g_object_unref (menu->old_active_menu_item);
1060       menu->old_active_menu_item = menu_shell->active_menu_item;
1061       g_object_ref (menu->old_active_menu_item);
1062     }
1063
1064   gtk_menu_shell_deselect (menu_shell);
1065   
1066   /* The X Grab, if present, will automatically be removed when we hide
1067    * the window */
1068   gtk_widget_hide (menu->toplevel);
1069
1070   if (menu->torn_off)
1071     {
1072       gtk_widget_set_size_request (menu->tearoff_window, -1, -1);
1073       
1074       if (GTK_BIN (menu->toplevel)->child) 
1075         {
1076           gtk_menu_reparent (menu, menu->tearoff_hbox, TRUE);
1077         } 
1078       else
1079         {
1080           /* We popped up the menu from the tearoff, so we need to 
1081            * release the grab - we aren't actually hiding the menu.
1082            */
1083           if (menu_shell->have_xgrab)
1084             {
1085               GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (menu));
1086               
1087               gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
1088               gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
1089             }
1090         }
1091
1092       /* gtk_menu_popdown is called each time a menu item is selected from
1093        * a torn off menu. Only scroll back to the saved position if the
1094        * non-tearoff menu was popped down.
1095        */
1096       if (!menu->tearoff_active)
1097         gtk_menu_scroll_to (menu, menu->saved_scroll_offset);
1098       menu->tearoff_active = TRUE;
1099     }
1100   else
1101     gtk_widget_hide (GTK_WIDGET (menu));
1102
1103   menu_shell->have_xgrab = FALSE;
1104   gtk_grab_remove (GTK_WIDGET (menu));
1105
1106   menu_grab_transfer_window_destroy (menu);
1107 }
1108
1109 GtkWidget*
1110 gtk_menu_get_active (GtkMenu *menu)
1111 {
1112   GtkWidget *child;
1113   GList *children;
1114   
1115   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
1116   
1117   if (!menu->old_active_menu_item)
1118     {
1119       child = NULL;
1120       children = GTK_MENU_SHELL (menu)->children;
1121       
1122       while (children)
1123         {
1124           child = children->data;
1125           children = children->next;
1126           
1127           if (GTK_BIN (child)->child)
1128             break;
1129           child = NULL;
1130         }
1131       
1132       menu->old_active_menu_item = child;
1133       if (menu->old_active_menu_item)
1134         g_object_ref (menu->old_active_menu_item);
1135     }
1136   
1137   return menu->old_active_menu_item;
1138 }
1139
1140 void
1141 gtk_menu_set_active (GtkMenu *menu,
1142                      guint    index)
1143 {
1144   GtkWidget *child;
1145   GList *tmp_list;
1146   
1147   g_return_if_fail (GTK_IS_MENU (menu));
1148   
1149   tmp_list = g_list_nth (GTK_MENU_SHELL (menu)->children, index);
1150   if (tmp_list)
1151     {
1152       child = tmp_list->data;
1153       if (GTK_BIN (child)->child)
1154         {
1155           if (menu->old_active_menu_item)
1156             g_object_unref (menu->old_active_menu_item);
1157           menu->old_active_menu_item = child;
1158           g_object_ref (menu->old_active_menu_item);
1159         }
1160     }
1161 }
1162
1163 void
1164 gtk_menu_set_accel_group (GtkMenu       *menu,
1165                           GtkAccelGroup *accel_group)
1166 {
1167   g_return_if_fail (GTK_IS_MENU (menu));
1168   
1169   if (menu->accel_group != accel_group)
1170     {
1171       if (menu->accel_group)
1172         g_object_unref (menu->accel_group);
1173       menu->accel_group = accel_group;
1174       if (menu->accel_group)
1175         g_object_ref (menu->accel_group);
1176       _gtk_menu_refresh_accel_paths (menu, TRUE);
1177     }
1178 }
1179
1180 GtkAccelGroup*
1181 gtk_menu_get_accel_group (GtkMenu *menu)
1182 {
1183   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
1184
1185   return menu->accel_group;
1186 }
1187
1188 /**
1189  * gtk_menu_set_accel_path
1190  * @menu:       a valid #GtkMenu
1191  * @accel_path: a valid accelerator path
1192  *
1193  * Sets an accelerator path for this menu from which accelerator paths
1194  * for its immediate children, its menu items, can be constructed.
1195  * The main purpose of this function is to spare the programmer the
1196  * inconvenience of having to call gtk_menu_item_set_accel_path() on
1197  * each menu item that should support runtime user changable accelerators.
1198  * Instead, by just calling gtk_menu_set_accel_path() on their parent,
1199  * each menu item of this menu, that contains a label describing its purpose,
1200  * automatically gets an accel path assigned. For example, a menu containing
1201  * menu items "New" and "Exit", will, after 
1202  * <literal>gtk_menu_set_accel_path (menu, "&lt;Gnumeric-Sheet&gt;/File");</literal>
1203  * has been called, assign its items the accel paths:
1204  * <literal>"&lt;Gnumeric-Sheet&gt;/File/New"</literal> and <literal>"&lt;Gnumeric-Sheet&gt;/File/Exit"</literal>.
1205  * Assigning accel paths to menu items then enables the user to change
1206  * their accelerators at runtime. More details about accelerator paths
1207  * and their default setups can be found at gtk_accel_map_add_entry().
1208  */
1209 void
1210 gtk_menu_set_accel_path (GtkMenu     *menu,
1211                          const gchar *accel_path)
1212 {
1213   g_return_if_fail (GTK_IS_MENU (menu));
1214   if (accel_path)
1215     g_return_if_fail (accel_path[0] == '<' && strchr (accel_path, '/')); /* simplistic check */
1216
1217   g_free (menu->accel_path);
1218   menu->accel_path = g_strdup (accel_path);
1219   if (menu->accel_path)
1220     _gtk_menu_refresh_accel_paths (menu, FALSE);
1221 }
1222
1223 typedef struct {
1224   GtkMenu *menu;
1225   gboolean group_changed;
1226 } AccelPropagation;
1227
1228 static void
1229 refresh_accel_paths_foreach (GtkWidget *widget,
1230                              gpointer   data)
1231 {
1232   AccelPropagation *prop = data;
1233
1234   if (GTK_IS_MENU_ITEM (widget))        /* should always be true */
1235     _gtk_menu_item_refresh_accel_path (GTK_MENU_ITEM (widget),
1236                                        prop->menu->accel_path,
1237                                        prop->menu->accel_group,
1238                                        prop->group_changed);
1239 }
1240
1241 static void
1242 _gtk_menu_refresh_accel_paths (GtkMenu *menu,
1243                                gboolean group_changed)
1244 {
1245   g_return_if_fail (GTK_IS_MENU (menu));
1246       
1247   if (menu->accel_path && menu->accel_group)
1248     {
1249       AccelPropagation prop;
1250
1251       prop.menu = menu;
1252       prop.group_changed = group_changed;
1253       gtk_container_foreach (GTK_CONTAINER (menu),
1254                              refresh_accel_paths_foreach,
1255                              &prop);
1256     }
1257 }
1258
1259 void
1260 gtk_menu_reposition (GtkMenu *menu)
1261 {
1262   g_return_if_fail (GTK_IS_MENU (menu));
1263
1264   if (GTK_WIDGET_DRAWABLE (menu) && !menu->torn_off)
1265     gtk_menu_position (menu);
1266 }
1267
1268 static void
1269 gtk_menu_scrollbar_changed (GtkAdjustment *adjustment,
1270                             GtkMenu       *menu)
1271 {
1272   g_return_if_fail (GTK_IS_MENU (menu));
1273
1274   if (adjustment->value != menu->scroll_offset)
1275     gtk_menu_scroll_to (menu, adjustment->value);
1276 }
1277
1278 static void
1279 gtk_menu_set_tearoff_hints (GtkMenu *menu,
1280                             gint     width)
1281 {
1282   GdkGeometry geometry_hints;
1283   
1284   if (!menu->tearoff_window)
1285     return;
1286
1287   if (GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1288     {
1289       gtk_widget_size_request (menu->tearoff_scrollbar, NULL);
1290       width += menu->tearoff_scrollbar->requisition.width;
1291     }
1292
1293   geometry_hints.min_width = width;
1294   geometry_hints.max_width = width;
1295     
1296   geometry_hints.min_height = 0;
1297   geometry_hints.max_height = GTK_WIDGET (menu)->requisition.height;
1298
1299   gtk_window_set_geometry_hints (GTK_WINDOW (menu->tearoff_window),
1300                                  NULL,
1301                                  &geometry_hints,
1302                                  GDK_HINT_MAX_SIZE|GDK_HINT_MIN_SIZE);
1303 }
1304
1305 static void
1306 gtk_menu_update_title (GtkMenu *menu)
1307 {
1308   if (menu->tearoff_window)
1309     {
1310       const gchar *title;
1311       GtkWidget *attach_widget;
1312
1313       title = gtk_menu_get_title (menu);
1314       if (!title)
1315         {
1316           attach_widget = gtk_menu_get_attach_widget (menu);
1317           if (GTK_IS_MENU_ITEM (attach_widget))
1318             {
1319               GtkWidget *child = GTK_BIN (attach_widget)->child;
1320               if (GTK_IS_LABEL (child))
1321                 title = gtk_label_get_text (GTK_LABEL (child));
1322             }
1323         }
1324       
1325       if (title)
1326         gtk_window_set_title (GTK_WINDOW (menu->tearoff_window), title);
1327     }
1328 }
1329
1330 void       
1331 gtk_menu_set_tearoff_state (GtkMenu  *menu,
1332                             gboolean  torn_off)
1333 {
1334   gint width, height;
1335   
1336   g_return_if_fail (GTK_IS_MENU (menu));
1337
1338   if (menu->torn_off != torn_off)
1339     {
1340       menu->torn_off = torn_off;
1341       menu->tearoff_active = torn_off;
1342       
1343       if (menu->torn_off)
1344         {
1345           if (GTK_WIDGET_VISIBLE (menu))
1346             gtk_menu_popdown (menu);
1347
1348           if (!menu->tearoff_window)
1349             {
1350               menu->tearoff_window = gtk_widget_new (GTK_TYPE_WINDOW,
1351                                                      "type", GTK_WINDOW_TOPLEVEL,
1352                                                      "screen", gtk_widget_get_screen (menu->toplevel),
1353                                                      "app_paintable", TRUE,
1354                                                      NULL);
1355
1356               gtk_window_set_type_hint (GTK_WINDOW (menu->tearoff_window),
1357                                         GDK_WINDOW_TYPE_HINT_MENU);
1358               gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->tearoff_window), 0);
1359               g_signal_connect (menu->tearoff_window, "destroy",
1360                                 G_CALLBACK (gtk_widget_destroyed), &menu->tearoff_window);
1361               g_signal_connect (menu->tearoff_window, "event",
1362                                 G_CALLBACK (gtk_menu_window_event), menu);
1363
1364               gtk_menu_update_title (menu);
1365
1366               gtk_widget_realize (menu->tearoff_window);
1367               
1368               menu->tearoff_hbox = gtk_hbox_new (FALSE, FALSE);
1369               gtk_container_add (GTK_CONTAINER (menu->tearoff_window), menu->tearoff_hbox);
1370
1371               gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height);
1372               menu->tearoff_adjustment =
1373                 GTK_ADJUSTMENT (gtk_adjustment_new (0,
1374                                                     0,
1375                                                     GTK_WIDGET (menu)->requisition.height,
1376                                                     MENU_SCROLL_STEP2,
1377                                                     height/2,
1378                                                     height));
1379               g_object_connect (menu->tearoff_adjustment,
1380                                 "signal::value_changed", gtk_menu_scrollbar_changed, menu,
1381                                 NULL);
1382               menu->tearoff_scrollbar = gtk_vscrollbar_new (menu->tearoff_adjustment);
1383
1384               gtk_box_pack_end (GTK_BOX (menu->tearoff_hbox),
1385                                 menu->tearoff_scrollbar,
1386                                 FALSE, FALSE, 0);
1387               
1388               if (menu->tearoff_adjustment->upper > height)
1389                 gtk_widget_show (menu->tearoff_scrollbar);
1390               
1391               gtk_widget_show (menu->tearoff_hbox);
1392             }
1393           
1394           gtk_menu_reparent (menu, menu->tearoff_hbox, FALSE);
1395
1396           gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, NULL);
1397
1398           /* Update menu->requisition
1399            */
1400           gtk_widget_size_request (GTK_WIDGET (menu), NULL);
1401   
1402           gtk_menu_set_tearoff_hints (menu, width);
1403             
1404           gtk_widget_realize (menu->tearoff_window);
1405           gtk_menu_position (menu);
1406           
1407           gtk_widget_show (GTK_WIDGET (menu));
1408           gtk_widget_show (menu->tearoff_window);
1409
1410           gtk_menu_scroll_to (menu, 0);
1411
1412         }
1413       else
1414         {
1415           gtk_widget_hide (menu->tearoff_window);
1416           gtk_menu_reparent (menu, menu->toplevel, FALSE);
1417           gtk_widget_destroy (menu->tearoff_window);
1418           
1419           menu->tearoff_window = NULL;
1420           menu->tearoff_hbox = NULL;
1421           menu->tearoff_scrollbar = NULL;
1422           menu->tearoff_adjustment = NULL;
1423         }
1424     }
1425 }
1426
1427 /**
1428  * gtk_menu_get_tearoff_state:
1429  * @menu: a #GtkMenu
1430  *
1431  * Returns whether the menu is torn off. See
1432  * gtk_menu_set_tearoff_state ().
1433  *
1434  * Return value: %TRUE if the menu is currently torn off.
1435  **/
1436 gboolean
1437 gtk_menu_get_tearoff_state (GtkMenu *menu)
1438 {
1439   g_return_val_if_fail (GTK_IS_MENU (menu), FALSE);
1440
1441   return menu->torn_off;
1442 }
1443
1444 /**
1445  * gtk_menu_set_title:
1446  * @menu: a #GtkMenu
1447  * @title: a string containing the title for the menu.
1448  * 
1449  * Sets the title string for the menu.  The title is displayed when the menu
1450  * is shown as a tearoff menu.
1451  **/
1452 void       
1453 gtk_menu_set_title (GtkMenu     *menu,
1454                     const gchar *title)
1455 {
1456   g_return_if_fail (GTK_IS_MENU (menu));
1457
1458   if (title)
1459     g_object_set_data_full (G_OBJECT (menu), "gtk-menu-title",
1460                             g_strdup (title), (GtkDestroyNotify) g_free);
1461   else
1462     g_object_set_data (G_OBJECT (menu), "gtk-menu-title", NULL);
1463     
1464   gtk_menu_update_title (menu);
1465   g_object_notify (G_OBJECT (menu), "tearoff_title");
1466 }
1467
1468 /**
1469  * gtk_menu_get_title:
1470  * @menu: a #GtkMenu
1471  *
1472  * Returns the title of the menu. See gtk_menu_set_title().
1473  *
1474  * Return value: the title of the menu, or %NULL if the menu has no
1475  * title set on it. This string is owned by the widget and should
1476  * not be modified or freed.
1477  **/
1478 G_CONST_RETURN gchar *
1479 gtk_menu_get_title (GtkMenu *menu)
1480 {
1481   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
1482
1483   return g_object_get_data (G_OBJECT (menu), "gtk-menu-title");
1484 }
1485
1486 void
1487 gtk_menu_reorder_child (GtkMenu   *menu,
1488                         GtkWidget *child,
1489                         gint       position)
1490 {
1491   GtkMenuShell *menu_shell;
1492   g_return_if_fail (GTK_IS_MENU (menu));
1493   g_return_if_fail (GTK_IS_MENU_ITEM (child));
1494   menu_shell = GTK_MENU_SHELL (menu);
1495   if (g_list_find (menu_shell->children, child))
1496     {   
1497       menu_shell->children = g_list_remove (menu_shell->children, child);
1498       menu_shell->children = g_list_insert (menu_shell->children, child, position);   
1499       if (GTK_WIDGET_VISIBLE (menu_shell))
1500         gtk_widget_queue_resize (GTK_WIDGET (menu_shell));
1501     }   
1502 }
1503
1504 static void
1505 gtk_menu_style_set (GtkWidget *widget,
1506                     GtkStyle  *previous_style)
1507 {
1508   if (GTK_WIDGET_REALIZED (widget))
1509     {
1510       GtkMenu *menu = GTK_MENU (widget);
1511       
1512       gtk_style_set_background (widget->style, menu->bin_window, GTK_STATE_NORMAL);
1513       gtk_style_set_background (widget->style, menu->view_window, GTK_STATE_NORMAL);
1514       gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
1515     }
1516 }
1517
1518 static void
1519 gtk_menu_realize (GtkWidget *widget)
1520 {
1521   GdkWindowAttr attributes;
1522   gint attributes_mask;
1523   gint border_width;
1524   GtkMenu *menu;
1525   GtkWidget *child;
1526   GList *children;
1527   guint vertical_padding;
1528   
1529   g_return_if_fail (GTK_IS_MENU (widget));
1530
1531   menu = GTK_MENU (widget);
1532   
1533   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1534   
1535   attributes.window_type = GDK_WINDOW_CHILD;
1536   attributes.x = widget->allocation.x;
1537   attributes.y = widget->allocation.y;
1538   attributes.width = widget->allocation.width;
1539   attributes.height = widget->allocation.height;
1540   attributes.wclass = GDK_INPUT_OUTPUT;
1541   attributes.visual = gtk_widget_get_visual (widget);
1542   attributes.colormap = gtk_widget_get_colormap (widget);
1543   
1544   attributes.event_mask = gtk_widget_get_events (widget);
1545   attributes.event_mask |= (GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK |
1546                             GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK );
1547   
1548   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1549   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
1550   gdk_window_set_user_data (widget->window, widget);
1551   
1552   border_width = GTK_CONTAINER (widget)->border_width;
1553
1554   gtk_widget_style_get (GTK_WIDGET (menu),
1555                         "vertical-padding", &vertical_padding,
1556                         NULL);
1557   
1558   attributes.x = border_width + widget->style->xthickness;
1559   attributes.y = border_width + widget->style->ythickness + vertical_padding;
1560   attributes.width = MAX (1, widget->allocation.width - attributes.x * 2);
1561   attributes.height = MAX (1, widget->allocation.height - attributes.y * 2);
1562
1563   if (menu->upper_arrow_visible)
1564     {
1565       attributes.y += MENU_SCROLL_ARROW_HEIGHT;
1566       attributes.height -= MENU_SCROLL_ARROW_HEIGHT;
1567     }
1568   if (menu->lower_arrow_visible)
1569     attributes.height -= MENU_SCROLL_ARROW_HEIGHT;
1570
1571   menu->view_window = gdk_window_new (widget->window, &attributes, attributes_mask);
1572   gdk_window_set_user_data (menu->view_window, menu);
1573
1574   attributes.x = 0;
1575   attributes.y = 0;
1576   attributes.height = MAX (1, widget->requisition.height - (border_width + widget->style->ythickness + vertical_padding) * 2);
1577   
1578   menu->bin_window = gdk_window_new (menu->view_window, &attributes, attributes_mask);
1579   gdk_window_set_user_data (menu->bin_window, menu);
1580
1581   children = GTK_MENU_SHELL (menu)->children;
1582   while (children)
1583     {
1584       child = children->data;
1585       children = children->next;
1586           
1587       gtk_widget_set_parent_window (child, menu->bin_window);
1588     }
1589   
1590   widget->style = gtk_style_attach (widget->style, widget->window);
1591   gtk_style_set_background (widget->style, menu->bin_window, GTK_STATE_NORMAL);
1592   gtk_style_set_background (widget->style, menu->view_window, GTK_STATE_NORMAL);
1593   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
1594
1595   if (GTK_MENU_SHELL (widget)->active_menu_item)
1596     gtk_menu_scroll_item_visible (GTK_MENU_SHELL (widget),
1597                                   GTK_MENU_SHELL (widget)->active_menu_item);
1598
1599   gdk_window_show (menu->bin_window);
1600   gdk_window_show (menu->view_window);
1601 }
1602
1603 static gboolean 
1604 gtk_menu_focus (GtkWidget       *widget,
1605                 GtkDirectionType direction)
1606 {
1607   /*
1608    * A menu or its menu items cannot have focus
1609    */
1610   return FALSE;
1611 }
1612
1613 /* See notes in gtk_menu_popup() for information about the "grab transfer window"
1614  */
1615 static GdkWindow *
1616 menu_grab_transfer_window_get (GtkMenu *menu)
1617 {
1618   GdkWindow *window = g_object_get_data (G_OBJECT (menu), "gtk-menu-transfer-window");
1619   if (!window)
1620     {
1621       GdkWindowAttr attributes;
1622       gint attributes_mask;
1623       
1624       attributes.x = -100;
1625       attributes.y = -100;
1626       attributes.width = 10;
1627       attributes.height = 10;
1628       attributes.window_type = GDK_WINDOW_TEMP;
1629       attributes.wclass = GDK_INPUT_ONLY;
1630       attributes.override_redirect = TRUE;
1631       attributes.event_mask = 0;
1632
1633       attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR;
1634       
1635       window = gdk_window_new (gtk_widget_get_root_window (GTK_WIDGET (menu)),
1636                                &attributes, attributes_mask);
1637       gdk_window_set_user_data (window, menu);
1638
1639       gdk_window_show (window);
1640
1641       g_object_set_data (G_OBJECT (menu), "gtk-menu-transfer-window", window);
1642     }
1643
1644   return window;
1645 }
1646
1647 static void
1648 menu_grab_transfer_window_destroy (GtkMenu *menu)
1649 {
1650   GdkWindow *window = g_object_get_data (G_OBJECT (menu), "gtk-menu-transfer-window");
1651   if (window)
1652     {
1653       gdk_window_set_user_data (window, NULL);
1654       gdk_window_destroy (window);
1655       g_object_set_data (G_OBJECT (menu), "gtk-menu-transfer-window", NULL);
1656     }
1657 }
1658
1659 static void
1660 gtk_menu_unrealize (GtkWidget *widget)
1661 {
1662   GtkMenu *menu;
1663
1664   g_return_if_fail (GTK_IS_MENU (widget));
1665
1666   menu = GTK_MENU (widget);
1667
1668   menu_grab_transfer_window_destroy (menu);
1669
1670   gdk_window_set_user_data (menu->view_window, NULL);
1671   gdk_window_destroy (menu->view_window);
1672   menu->view_window = NULL;
1673
1674   gdk_window_set_user_data (menu->bin_window, NULL);
1675   gdk_window_destroy (menu->bin_window);
1676   menu->bin_window = NULL;
1677
1678   (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
1679 }
1680
1681 static void
1682 gtk_menu_size_request (GtkWidget      *widget,
1683                        GtkRequisition *requisition)
1684 {
1685   GtkMenu *menu;
1686   GtkMenuShell *menu_shell;
1687   GtkWidget *child;
1688   GList *children;
1689   guint max_toggle_size;
1690   guint max_accel_width;
1691   guint vertical_padding;
1692   GtkRequisition child_requisition;
1693   
1694   g_return_if_fail (GTK_IS_MENU (widget));
1695   g_return_if_fail (requisition != NULL);
1696   
1697   menu = GTK_MENU (widget);
1698   menu_shell = GTK_MENU_SHELL (widget);
1699   
1700   requisition->width = 0;
1701   requisition->height = 0;
1702   
1703   max_toggle_size = 0;
1704   max_accel_width = 0;
1705   
1706   children = menu_shell->children;
1707   while (children)
1708     {
1709       child = children->data;
1710       children = children->next;
1711       
1712       if (GTK_WIDGET_VISIBLE (child))
1713         {
1714           gint toggle_size;
1715
1716           /* It's important to size_request the child
1717            * before doing the toggle size request, in
1718            * case the toggle size request depends on the size
1719            * request of a child of the child (e.g. for ImageMenuItem)
1720            */
1721           
1722           GTK_MENU_ITEM (child)->show_submenu_indicator = TRUE;
1723           gtk_widget_size_request (child, &child_requisition);
1724           
1725           requisition->width = MAX (requisition->width, child_requisition.width);
1726           requisition->height += child_requisition.height;
1727
1728           gtk_menu_item_toggle_size_request (GTK_MENU_ITEM (child), &toggle_size);
1729           max_toggle_size = MAX (max_toggle_size, toggle_size);
1730           max_accel_width = MAX (max_accel_width, GTK_MENU_ITEM (child)->accelerator_width);
1731         }
1732     }
1733
1734   requisition->width += max_toggle_size + max_accel_width;
1735   requisition->width += (GTK_CONTAINER (menu)->border_width +
1736                          widget->style->xthickness) * 2;
1737
1738   gtk_widget_style_get (GTK_WIDGET (menu),
1739                         "vertical-padding", &vertical_padding,
1740                         NULL);
1741   requisition->height += (GTK_CONTAINER (menu)->border_width + vertical_padding +
1742                           widget->style->ythickness) * 2;
1743   
1744   menu->toggle_size = max_toggle_size;
1745
1746   /* Don't resize the tearoff if it is not active, because it won't redraw (it is only a background pixmap).
1747    */
1748   if (menu->tearoff_active)
1749     gtk_menu_set_tearoff_hints (menu, requisition->width);
1750 }
1751
1752 static void
1753 gtk_menu_size_allocate (GtkWidget     *widget,
1754                         GtkAllocation *allocation)
1755 {
1756   GtkMenu *menu;
1757   GtkMenuShell *menu_shell;
1758   GtkWidget *child;
1759   GtkAllocation child_allocation;
1760   GList *children;
1761   gint x, y;
1762   gint width, height;
1763   guint vertical_padding;
1764
1765   g_return_if_fail (GTK_IS_MENU (widget));
1766   g_return_if_fail (allocation != NULL);
1767   
1768   menu = GTK_MENU (widget);
1769   menu_shell = GTK_MENU_SHELL (widget);
1770
1771   widget->allocation = *allocation;
1772
1773   gtk_widget_style_get (GTK_WIDGET (menu),
1774                         "vertical-padding", &vertical_padding,
1775                         NULL);
1776   
1777   x = GTK_CONTAINER (menu)->border_width + widget->style->xthickness;
1778   y = GTK_CONTAINER (menu)->border_width + widget->style->ythickness + vertical_padding;
1779
1780   width = MAX (1, allocation->width - x * 2);
1781   height = MAX (1, allocation->height - y * 2);
1782
1783   if (menu_shell->active)
1784     gtk_menu_scroll_to (menu, menu->scroll_offset);
1785   
1786   if (menu->upper_arrow_visible && !menu->tearoff_active)
1787     {
1788       y += MENU_SCROLL_ARROW_HEIGHT;
1789       height -= MENU_SCROLL_ARROW_HEIGHT;
1790     }
1791   
1792   if (menu->lower_arrow_visible && !menu->tearoff_active)
1793     height -= MENU_SCROLL_ARROW_HEIGHT;
1794   
1795   if (GTK_WIDGET_REALIZED (widget))
1796     {
1797       gdk_window_move_resize (widget->window,
1798                               allocation->x, allocation->y,
1799                               allocation->width, allocation->height);
1800
1801       gdk_window_move_resize (menu->view_window,
1802                               x,
1803                               y,
1804                               width,
1805                               height);
1806     }
1807
1808   if (menu_shell->children)
1809     {
1810       child_allocation.x = 0;
1811       child_allocation.y = 0;
1812       child_allocation.width = width;
1813       
1814       children = menu_shell->children;
1815       while (children)
1816         {
1817           child = children->data;
1818           children = children->next;
1819           
1820           if (GTK_WIDGET_VISIBLE (child))
1821             {
1822               GtkRequisition child_requisition;
1823               gtk_widget_get_child_requisition (child, &child_requisition);
1824               
1825               child_allocation.height = child_requisition.height;
1826
1827               gtk_menu_item_toggle_size_allocate (GTK_MENU_ITEM (child),
1828                                                   menu->toggle_size);
1829
1830               gtk_widget_size_allocate (child, &child_allocation);
1831               gtk_widget_queue_draw (child);
1832               
1833               child_allocation.y += child_allocation.height;
1834             }
1835         }
1836       
1837       /* Resize the item window */
1838       if (GTK_WIDGET_REALIZED (widget))
1839         {
1840           gdk_window_resize (menu->bin_window,
1841                              child_allocation.width,
1842                              child_allocation.y);
1843         }
1844
1845
1846       if (menu->tearoff_active)
1847         {
1848           if (allocation->height >= widget->requisition.height)
1849             {
1850               if (GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1851                 {
1852                   gtk_widget_hide (menu->tearoff_scrollbar);
1853                   gtk_menu_set_tearoff_hints (menu, allocation->width);
1854
1855                   gtk_menu_scroll_to (menu, 0);
1856                 }
1857             }
1858           else
1859             {
1860               menu->tearoff_adjustment->upper = widget->requisition.height;
1861               menu->tearoff_adjustment->page_size = allocation->height;
1862               
1863               if (menu->tearoff_adjustment->value + menu->tearoff_adjustment->page_size >
1864                   menu->tearoff_adjustment->upper)
1865                 {
1866                   gint value;
1867                   value = menu->tearoff_adjustment->upper - menu->tearoff_adjustment->page_size;
1868                   if (value < 0)
1869                     value = 0;
1870                   gtk_menu_scroll_to (menu, value);
1871                 }
1872               
1873               gtk_adjustment_changed (menu->tearoff_adjustment);
1874               
1875               if (!GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1876                 {
1877                   gtk_widget_show (menu->tearoff_scrollbar);
1878                   gtk_menu_set_tearoff_hints (menu, allocation->width);
1879                 }
1880             }
1881         }
1882     }
1883 }
1884
1885 static void
1886 gtk_menu_paint (GtkWidget      *widget,
1887                 GdkEventExpose *event)
1888 {
1889   GtkMenu *menu;
1890   gint width, height;
1891   gint border_x, border_y;
1892   guint vertical_padding;
1893   
1894   g_return_if_fail (GTK_IS_MENU (widget));
1895
1896   menu = GTK_MENU (widget);
1897
1898   gtk_widget_style_get (GTK_WIDGET (menu),
1899                         "vertical-padding", &vertical_padding,
1900                         NULL);
1901   
1902   border_x = GTK_CONTAINER (widget)->border_width + widget->style->xthickness;
1903   border_y = GTK_CONTAINER (widget)->border_width + widget->style->ythickness + vertical_padding;
1904   gdk_drawable_get_size (widget->window, &width, &height);
1905
1906   if (event->window == widget->window)
1907     {
1908       gint arrow_space = MENU_SCROLL_ARROW_HEIGHT - 2 * widget->style->ythickness;
1909       gint arrow_size = 0.7 * arrow_space;
1910         
1911       gtk_paint_box (widget->style,
1912                      widget->window,
1913                      GTK_STATE_NORMAL,
1914                      GTK_SHADOW_OUT,
1915                      NULL, widget, "menu",
1916                      0, 0, -1, -1);
1917       if (menu->upper_arrow_visible && !menu->tearoff_active)
1918         {
1919           gtk_paint_box (widget->style,
1920                          widget->window,
1921                          menu->upper_arrow_prelight ?
1922                          GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1923                          GTK_SHADOW_OUT,
1924                          NULL, widget, "menu",
1925                          border_x,
1926                          border_y,
1927                          width - 2 * border_x,
1928                          MENU_SCROLL_ARROW_HEIGHT);
1929           
1930           gtk_paint_arrow (widget->style,
1931                            widget->window,
1932                            menu->upper_arrow_prelight ?
1933                            GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1934                            GTK_SHADOW_OUT,
1935                            NULL, widget, "menu_scroll_arrow_up",
1936                            GTK_ARROW_UP,
1937                            TRUE,
1938                            (width - arrow_size ) / 2,
1939                            border_y + widget->style->ythickness + (arrow_space - arrow_size)/2,
1940                            arrow_size, arrow_size);
1941         }
1942   
1943       if (menu->lower_arrow_visible && !menu->tearoff_active)
1944         {
1945           gtk_paint_box (widget->style,
1946                          widget->window,
1947                          menu->lower_arrow_prelight ?
1948                          GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1949                          GTK_SHADOW_OUT,
1950                          NULL, widget, "menu",
1951                          border_x,
1952                          height - border_y - MENU_SCROLL_ARROW_HEIGHT,
1953                          width - 2*border_x,
1954                          MENU_SCROLL_ARROW_HEIGHT);
1955           
1956           gtk_paint_arrow (widget->style,
1957                            widget->window,
1958                            menu->lower_arrow_prelight ?
1959                            GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1960                            GTK_SHADOW_OUT,
1961                            NULL, widget, "menu_scroll_arrow_down",
1962                            GTK_ARROW_DOWN,
1963                            TRUE,
1964                            (width - arrow_size) / 2,
1965                            height - border_y - MENU_SCROLL_ARROW_HEIGHT +
1966                               widget->style->ythickness + (arrow_space - arrow_size)/2,
1967                            arrow_size, arrow_size);
1968         }
1969     }
1970 }
1971
1972 static gboolean
1973 gtk_menu_expose (GtkWidget      *widget,
1974                  GdkEventExpose *event)
1975 {
1976   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
1977   g_return_val_if_fail (event != NULL, FALSE);
1978
1979   if (GTK_WIDGET_DRAWABLE (widget))
1980     {
1981       gtk_menu_paint (widget, event);
1982       
1983       (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
1984     }
1985   
1986   return FALSE;
1987 }
1988
1989 static void
1990 gtk_menu_show (GtkWidget *widget)
1991 {
1992   GtkMenu *menu = GTK_MENU (widget);
1993
1994   _gtk_menu_refresh_accel_paths (menu, FALSE);
1995
1996   GTK_WIDGET_CLASS (parent_class)->show (widget);
1997 }
1998
1999 static gboolean
2000 gtk_menu_button_press (GtkWidget      *widget,
2001                          GdkEventButton *event)
2002 {
2003   /* Don't pop down the menu for releases over scroll arrows
2004    */
2005   if (GTK_IS_MENU (widget))
2006     {
2007       GtkMenu *menu = GTK_MENU (widget);
2008
2009       if (menu->upper_arrow_prelight ||  menu->lower_arrow_prelight)
2010         return TRUE;
2011     }
2012
2013   return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
2014 }
2015
2016 static gboolean
2017 gtk_menu_button_release (GtkWidget      *widget,
2018                          GdkEventButton *event)
2019 {
2020   /* Don't pop down the menu for releases over scroll arrows
2021    */
2022   if (GTK_IS_MENU (widget))
2023     {
2024       GtkMenu *menu = GTK_MENU (widget);
2025
2026       if (menu->upper_arrow_prelight ||  menu->lower_arrow_prelight)
2027         return TRUE;
2028     }
2029
2030   return GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event);
2031 }
2032
2033 static gboolean
2034 gtk_menu_key_press (GtkWidget   *widget,
2035                     GdkEventKey *event)
2036 {
2037   GtkMenuShell *menu_shell;
2038   GtkMenu *menu;
2039   gboolean delete = FALSE;
2040   gboolean can_change_accels;
2041   gchar *accel = NULL;
2042   guint accel_key, accel_mods;
2043   GdkModifierType consumed_modifiers;
2044   GdkDisplay *display;
2045   
2046   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
2047   g_return_val_if_fail (event != NULL, FALSE);
2048       
2049   menu_shell = GTK_MENU_SHELL (widget);
2050   menu = GTK_MENU (widget);
2051   
2052   gtk_menu_stop_navigating_submenu (menu);
2053
2054   if (GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event))
2055     return TRUE;
2056
2057   display = gtk_widget_get_display (widget);
2058     
2059   g_object_get (gtk_widget_get_settings (widget),
2060                 "gtk-menu-bar-accel", &accel,
2061                 "gtk-can-change-accels", &can_change_accels,
2062                 NULL);
2063
2064   if (accel)
2065     {
2066       guint keyval = 0;
2067       GdkModifierType mods = 0;
2068       gboolean handled = FALSE;
2069       
2070       gtk_accelerator_parse (accel, &keyval, &mods);
2071
2072       if (keyval == 0)
2073         g_warning ("Failed to parse menu bar accelerator '%s'\n", accel);
2074
2075       /* FIXME this is wrong, needs to be in the global accel resolution
2076        * thing, to properly consider i18n etc., but that probably requires
2077        * AccelGroup changes etc.
2078        */
2079       if (event->keyval == keyval &&
2080           (mods & event->state) == mods)
2081         {
2082           g_signal_emit_by_name (menu, "cancel", 0);
2083         }
2084
2085       g_free (accel);
2086
2087       if (handled)
2088         return TRUE;
2089     }
2090   
2091   switch (event->keyval)
2092     {
2093     case GDK_Delete:
2094     case GDK_KP_Delete:
2095     case GDK_BackSpace:
2096       delete = TRUE;
2097       break;
2098     default:
2099       break;
2100     }
2101
2102   /* Figure out what modifiers went into determining the key symbol */
2103   gdk_keymap_translate_keyboard_state (gdk_keymap_get_for_display (display),
2104                                        event->hardware_keycode, event->state, event->group,
2105                                        NULL, NULL, NULL, &consumed_modifiers);
2106
2107   accel_key = gdk_keyval_to_lower (event->keyval);
2108   accel_mods = event->state & gtk_accelerator_get_default_mod_mask () & ~consumed_modifiers;
2109
2110   /* If lowercasing affects the keysym, then we need to include SHIFT in the modifiers,
2111    * We re-upper case when we match against the keyval, but display and save in caseless form.
2112    */
2113   if (accel_key != event->keyval)
2114     accel_mods |= GDK_SHIFT_MASK;
2115   
2116   /* Modify the accelerators */
2117   if (can_change_accels &&
2118       menu_shell->active_menu_item &&
2119       GTK_BIN (menu_shell->active_menu_item)->child &&                  /* no separators */
2120       GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL &&  /* no submenus */
2121       (delete || gtk_accelerator_valid (accel_key, accel_mods)))
2122     {
2123       GtkWidget *menu_item = menu_shell->active_menu_item;
2124       gboolean locked, replace_accels = TRUE;
2125       const gchar *path;
2126
2127       path = _gtk_widget_get_accel_path (menu_item, &locked);
2128       if (!path || locked)
2129         {
2130           /* can't change accelerators on menu_items without paths
2131            * (basically, those items are accelerator-locked).
2132            */
2133           /* g_print("item has no path or is locked, menu prefix: %s\n", menu->accel_path); */
2134           gdk_display_beep (display);
2135         }
2136       else
2137         {
2138           gboolean changed;
2139
2140           /* For the keys that act to delete the current setting, we delete
2141            * the current setting if there is one, otherwise, we set the
2142            * key as the accelerator.
2143            */
2144           if (delete)
2145             {
2146               GtkAccelKey key;
2147               
2148               if (gtk_accel_map_lookup_entry (path, &key) &&
2149                   (key.accel_key || key.accel_mods))
2150                 {
2151                   accel_key = 0;
2152                   accel_mods = 0;
2153                 }
2154             }
2155           changed = gtk_accel_map_change_entry (path, accel_key, accel_mods, replace_accels);
2156
2157           if (!changed)
2158             {
2159               /* we failed, probably because this key is in use and
2160                * locked already
2161                */
2162               /* g_print("failed to change\n"); */
2163               gdk_display_beep (display);
2164             }
2165         }
2166     }
2167   
2168   return TRUE;
2169 }
2170
2171 static gboolean
2172 gtk_menu_motion_notify  (GtkWidget         *widget,
2173                          GdkEventMotion    *event)
2174 {
2175   GtkWidget *menu_item;
2176   GtkMenu *menu;
2177   GtkMenuShell *menu_shell;
2178
2179   gboolean need_enter;
2180
2181   if (GTK_IS_MENU (widget))
2182     gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE);
2183
2184   /* We received the event for one of two reasons:
2185    *
2186    * a) We are the active menu, and did gtk_grab_add()
2187    * b) The widget is a child of ours, and the event was propagated
2188    *
2189    * Since for computation of navigation regions, we want the menu which
2190    * is the parent of the menu item, for a), we need to find that menu,
2191    * which may be different from 'widget'.
2192    */
2193   menu_item = gtk_get_event_widget ((GdkEvent*) event);
2194   if (!menu_item || !GTK_IS_MENU_ITEM (menu_item) ||
2195       !_gtk_menu_item_is_selectable (menu_item) ||
2196       !GTK_IS_MENU (menu_item->parent))
2197     return FALSE;
2198
2199   menu_shell = GTK_MENU_SHELL (menu_item->parent);
2200   menu = GTK_MENU (menu_shell);
2201   
2202   need_enter = (menu->navigation_region != NULL || menu_shell->ignore_enter);
2203
2204   /* Check to see if we are within an active submenu's navigation region
2205    */
2206   if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
2207     return TRUE; 
2208
2209   if (need_enter)
2210     {
2211       /* The menu is now sensitive to enter events on its items, but
2212        * was previously sensitive.  So we fake an enter event.
2213        */
2214       gint width, height;
2215       
2216       menu_shell->ignore_enter = FALSE; 
2217       
2218       gdk_drawable_get_size (event->window, &width, &height);
2219       if (event->x >= 0 && event->x < width &&
2220           event->y >= 0 && event->y < height)
2221         {
2222           GdkEvent *send_event = gdk_event_new (GDK_ENTER_NOTIFY);
2223           gboolean result;
2224
2225           send_event->crossing.window = g_object_ref (event->window);
2226           send_event->crossing.time = event->time;
2227           send_event->crossing.send_event = TRUE;
2228           send_event->crossing.x_root = event->x_root;
2229           send_event->crossing.y_root = event->y_root;
2230           send_event->crossing.x = event->x;
2231           send_event->crossing.y = event->y;
2232
2233           /* We send the event to 'widget', the currently active menu,
2234            * instead of 'menu', the menu that the pointer is in. This
2235            * will ensure that the event will be ignored unless the
2236            * menuitem is a child of the active menu or some parent
2237            * menu of the active menu.
2238            */
2239           result = gtk_widget_event (widget, send_event);
2240           gdk_event_free (send_event);
2241
2242           return result;
2243         }
2244     }
2245
2246   return FALSE;
2247 }
2248
2249 static gboolean
2250 gtk_menu_scroll_timeout (gpointer  data)
2251 {
2252   GtkMenu *menu;
2253   GtkWidget *widget;
2254   gint offset;
2255   gint view_width, view_height;
2256
2257   GDK_THREADS_ENTER ();
2258
2259   menu = GTK_MENU (data);
2260   widget = GTK_WIDGET (menu);
2261
2262   offset = menu->scroll_offset + menu->scroll_step;
2263
2264   /* If we scroll upward and the non-visible top part
2265    * is smaller than the scroll arrow it would be
2266    * pretty stupid to show the arrow and taking more
2267    * screen space than just scrolling to the top.
2268    */
2269   if ((menu->scroll_step < 0) && (offset < MENU_SCROLL_ARROW_HEIGHT))
2270     offset = 0;
2271
2272   /* Don't scroll over the top if we weren't before: */
2273   if ((menu->scroll_offset >= 0) && (offset < 0))
2274     offset = 0;
2275
2276   gdk_drawable_get_size (widget->window, &view_width, &view_height);
2277
2278   /* Don't scroll past the bottom if we weren't before: */
2279   if (menu->scroll_offset > 0)
2280     view_height -= MENU_SCROLL_ARROW_HEIGHT;
2281   
2282   if ((menu->scroll_offset + view_height <= widget->requisition.height) &&
2283       (offset + view_height > widget->requisition.height))
2284     offset = widget->requisition.height - view_height;
2285
2286   gtk_menu_scroll_to (menu, offset);
2287
2288   GDK_THREADS_LEAVE ();
2289
2290   return TRUE;
2291 }
2292
2293 static void
2294 gtk_menu_handle_scrolling (GtkMenu *menu, gboolean enter)
2295 {
2296   GtkMenuShell *menu_shell;
2297   gint width, height;
2298   gint x, y;
2299   gint border;
2300   GdkRectangle rect;
2301   gboolean in_arrow;
2302   gboolean scroll_fast = FALSE;
2303   guint vertical_padding;
2304
2305   menu_shell = GTK_MENU_SHELL (menu);
2306
2307   gdk_window_get_pointer (GTK_WIDGET (menu)->window, &x, &y, NULL);
2308   gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height);
2309
2310   gtk_widget_style_get (GTK_WIDGET (menu),
2311                         "vertical-padding", &vertical_padding,
2312                         NULL);
2313   
2314   border = GTK_CONTAINER (menu)->border_width +
2315     GTK_WIDGET (menu)->style->ythickness + vertical_padding;
2316
2317   if (menu->upper_arrow_visible && !menu->tearoff_active)
2318     {
2319       rect.x = 0;
2320       rect.y = 0;
2321       rect.width = width;
2322       rect.height = MENU_SCROLL_ARROW_HEIGHT + border;
2323       
2324       in_arrow = FALSE;
2325       if ((x >= rect.x) && (x < rect.x + rect.width) &&
2326           (y >= rect.y) && (y < rect.y + rect.height))
2327         {
2328           in_arrow = TRUE;
2329           scroll_fast = (y < rect.y + MENU_SCROLL_FAST_ZONE);
2330         }
2331         
2332       if (enter && in_arrow &&
2333           (!menu->upper_arrow_prelight || menu->scroll_fast != scroll_fast))
2334         {
2335           menu->upper_arrow_prelight = TRUE;
2336           menu->scroll_fast = scroll_fast;
2337           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
2338           
2339           /* Deselect the active item so that any submenus are poped down */
2340           gtk_menu_shell_deselect (menu_shell);
2341
2342           gtk_menu_remove_scroll_timeout (menu);
2343           menu->scroll_step = (scroll_fast) ? -MENU_SCROLL_STEP2 : -MENU_SCROLL_STEP1;
2344           menu->timeout_id = g_timeout_add ((scroll_fast) ? MENU_SCROLL_TIMEOUT2 : MENU_SCROLL_TIMEOUT1,
2345                                             gtk_menu_scroll_timeout,
2346                                             menu);
2347         }
2348       else if (!enter && !in_arrow && menu->upper_arrow_prelight)
2349         {
2350           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
2351           
2352           gtk_menu_stop_scrolling (menu);
2353         }
2354     }
2355   
2356   if (menu->lower_arrow_visible && !menu->tearoff_active)
2357     {
2358       rect.x = 0;
2359       rect.y = height - border - MENU_SCROLL_ARROW_HEIGHT;
2360       rect.width = width;
2361       rect.height = MENU_SCROLL_ARROW_HEIGHT + border;
2362
2363       in_arrow = FALSE;
2364       if ((x >= rect.x) && (x < rect.x + rect.width) &&
2365           (y >= rect.y) && (y < rect.y + rect.height))
2366         {
2367           in_arrow = TRUE;
2368           scroll_fast = (y > rect.y + rect.height - MENU_SCROLL_FAST_ZONE);
2369         }
2370
2371       if (enter && in_arrow &&
2372           (!menu->lower_arrow_prelight || menu->scroll_fast != scroll_fast))
2373         {
2374           menu->lower_arrow_prelight = TRUE;
2375           menu->scroll_fast = scroll_fast;
2376           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
2377
2378           /* Deselect the active item so that any submenus are poped down */
2379           gtk_menu_shell_deselect (menu_shell);
2380
2381           gtk_menu_remove_scroll_timeout (menu);
2382           menu->scroll_step = (scroll_fast) ? MENU_SCROLL_STEP2 : MENU_SCROLL_STEP1;
2383           menu->timeout_id = g_timeout_add ((scroll_fast) ? MENU_SCROLL_TIMEOUT2 : MENU_SCROLL_TIMEOUT1,
2384                                             gtk_menu_scroll_timeout,
2385                                             menu);
2386         }
2387       else if (!enter && !in_arrow && menu->lower_arrow_prelight)
2388         {
2389           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
2390           
2391           gtk_menu_stop_scrolling (menu);
2392         }
2393     }
2394 }
2395
2396 static gboolean
2397 gtk_menu_enter_notify (GtkWidget        *widget,
2398                        GdkEventCrossing *event)
2399 {
2400   GtkWidget *menu_item;
2401
2402   if (widget && GTK_IS_MENU (widget))
2403     {
2404       GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
2405
2406       if (!menu_shell->ignore_enter)
2407         gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE);
2408     }
2409   
2410   /* If this is a faked enter (see gtk_menu_motion_notify), 'widget'
2411    * will not correspond to the event widget's parent.  Check to see
2412    * if we are in the parent's navigation region.
2413    */
2414   menu_item = gtk_get_event_widget ((GdkEvent*) event);
2415   if (menu_item && GTK_IS_MENU_ITEM (menu_item) && GTK_IS_MENU (menu_item->parent) &&
2416       gtk_menu_navigating_submenu (GTK_MENU (menu_item->parent), event->x_root, event->y_root))
2417     return TRUE;
2418
2419   return GTK_WIDGET_CLASS (parent_class)->enter_notify_event (widget, event); 
2420 }
2421
2422 static gboolean
2423 gtk_menu_leave_notify (GtkWidget        *widget,
2424                        GdkEventCrossing *event)
2425 {
2426   GtkMenuShell *menu_shell;
2427   GtkMenu *menu;
2428   GtkMenuItem *menu_item;
2429   GtkWidget *event_widget;
2430
2431   menu = GTK_MENU (widget);
2432   menu_shell = GTK_MENU_SHELL (widget); 
2433   
2434   if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
2435     return TRUE; 
2436
2437   gtk_menu_handle_scrolling (menu, FALSE);
2438   
2439   event_widget = gtk_get_event_widget ((GdkEvent*) event);
2440   
2441   if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
2442     return TRUE;
2443   
2444   menu_item = GTK_MENU_ITEM (event_widget); 
2445   
2446   /* Here we check to see if we're leaving an active menu item with a submenu, 
2447    * in which case we enter submenu navigation mode. 
2448    */
2449   if (menu_shell->active_menu_item != NULL
2450       && menu_item->submenu != NULL
2451       && menu_item->submenu_placement == GTK_LEFT_RIGHT)
2452     {
2453       if (GTK_MENU_SHELL (menu_item->submenu)->active)
2454         {
2455           gtk_menu_set_submenu_navigation_region (menu, menu_item, event);
2456           return TRUE;
2457         }
2458     }
2459   
2460   return GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event); 
2461 }
2462
2463 static void 
2464 gtk_menu_stop_navigating_submenu (GtkMenu *menu)
2465 {
2466   if (menu->navigation_region) 
2467     {
2468       gdk_region_destroy (menu->navigation_region);
2469       menu->navigation_region = NULL;
2470     }
2471   
2472   if (menu->navigation_timeout)
2473     {
2474       g_source_remove (menu->navigation_timeout);
2475       menu->navigation_timeout = 0;
2476     }
2477 }
2478
2479 /* When the timeout is elapsed, the navigation region is destroyed
2480  * and the menuitem under the pointer (if any) is selected.
2481  */
2482 static gboolean
2483 gtk_menu_stop_navigating_submenu_cb (gpointer user_data)
2484 {
2485   GtkMenu *menu = user_data;
2486   GdkWindow *child_window;
2487
2488   GDK_THREADS_ENTER ();
2489
2490   gtk_menu_stop_navigating_submenu (menu);
2491   
2492   if (GTK_WIDGET_REALIZED (menu))
2493     {
2494       child_window = gdk_window_get_pointer (menu->bin_window, NULL, NULL, NULL);
2495
2496       if (child_window)
2497         {
2498           GdkEvent *send_event = gdk_event_new (GDK_ENTER_NOTIFY);
2499
2500           send_event->crossing.window = g_object_ref (child_window);
2501           send_event->crossing.time = GDK_CURRENT_TIME; /* Bogus */
2502           send_event->crossing.send_event = TRUE;
2503
2504           GTK_WIDGET_CLASS (parent_class)->enter_notify_event (GTK_WIDGET (menu), (GdkEventCrossing *)send_event);
2505
2506           gdk_event_free (send_event);
2507         }
2508     }
2509
2510   GDK_THREADS_LEAVE ();
2511
2512   return FALSE; 
2513 }
2514
2515 static gboolean
2516 gtk_menu_navigating_submenu (GtkMenu *menu,
2517                              gint     event_x,
2518                              gint     event_y)
2519 {
2520   if (menu->navigation_region)
2521     {
2522       if (gdk_region_point_in (menu->navigation_region, event_x, event_y))
2523         return TRUE;
2524       else
2525         {
2526           gtk_menu_stop_navigating_submenu (menu);
2527           return FALSE;
2528         }
2529     }
2530   return FALSE;
2531 }
2532
2533 #undef DRAW_STAY_UP_TRIANGLE
2534
2535 #ifdef DRAW_STAY_UP_TRIANGLE
2536
2537 static void
2538 draw_stay_up_triangle (GdkWindow *window,
2539                        GdkRegion *region)
2540 {
2541   /* Draw ugly color all over the stay-up triangle */
2542   GdkColor ugly_color = { 0, 50000, 10000, 10000 };
2543   GdkGCValues gc_values;
2544   GdkGC *ugly_gc;
2545   GdkRectangle clipbox;
2546
2547   gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS;
2548   ugly_gc = gdk_gc_new_with_values (window, &gc_values, 0 | GDK_GC_SUBWINDOW);
2549   gdk_gc_set_rgb_fg_color (ugly_gc, &ugly_color);
2550   gdk_gc_set_clip_region (ugly_gc, region);
2551
2552   gdk_region_get_clipbox (region, &clipbox);
2553   
2554   gdk_draw_rectangle (window,
2555                      ugly_gc,
2556                      TRUE,
2557                      clipbox.x, clipbox.y,
2558                      clipbox.width, clipbox.height);
2559   
2560   g_object_unref (ugly_gc);
2561 }
2562 #endif
2563
2564 static GdkRegion *
2565 flip_region (GdkRegion *region,
2566              gboolean   flip_x,
2567              gboolean   flip_y)
2568 {
2569   gint n_rectangles;
2570   GdkRectangle *rectangles;
2571   GdkRectangle clipbox;
2572   GdkRegion *new_region;
2573   gint i;
2574
2575   new_region = gdk_region_new ();
2576   
2577   gdk_region_get_rectangles (region, &rectangles, &n_rectangles);
2578   gdk_region_get_clipbox (region, &clipbox);
2579
2580   for (i = 0; i < n_rectangles; ++i)
2581     {
2582       GdkRectangle rect = rectangles[i];
2583
2584       if (flip_y)
2585         rect.y -= 2 * (rect.y - clipbox.y) + rect.height;
2586
2587       if (flip_x)
2588         rect.x -= 2 * (rect.x - clipbox.x) + rect.width;
2589
2590       gdk_region_union_with_rect (new_region, &rect);
2591     }
2592
2593   g_free (rectangles);
2594
2595   return new_region;
2596 }
2597
2598 static void
2599 gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
2600                                         GtkMenuItem      *menu_item,
2601                                         GdkEventCrossing *event)
2602 {
2603   gint submenu_left = 0;
2604   gint submenu_right = 0;
2605   gint submenu_top = 0;
2606   gint submenu_bottom = 0;
2607   gint width = 0;
2608   gint height = 0;
2609   GdkPoint point[3];
2610   GtkWidget *event_widget;
2611
2612   g_return_if_fail (menu_item->submenu != NULL);
2613   g_return_if_fail (event != NULL);
2614   
2615   event_widget = gtk_get_event_widget ((GdkEvent*) event);
2616   
2617   gdk_window_get_origin (menu_item->submenu->window, &submenu_left, &submenu_top);
2618   gdk_drawable_get_size (menu_item->submenu->window, &width, &height);
2619   
2620   submenu_right = submenu_left + width;
2621   submenu_bottom = submenu_top + height;
2622   
2623   gdk_drawable_get_size (event_widget->window, &width, &height);
2624   
2625   if (event->x >= 0 && event->x < width)
2626     {
2627       gint popdown_delay;
2628       gboolean flip_y = FALSE;
2629       gboolean flip_x = FALSE;
2630       
2631       gtk_menu_stop_navigating_submenu (menu);
2632
2633       if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
2634         {
2635           /* right */
2636           point[0].x = event->x_root;
2637           point[1].x = submenu_left;
2638         }
2639       else
2640         {
2641           /* left */
2642           point[0].x = event->x_root + 1;
2643           point[1].x = 2 * (event->x_root + 1) - submenu_right;
2644
2645           flip_x = TRUE;
2646         }
2647
2648       if (event->y < 0)
2649         {
2650           /* top */
2651           point[0].y = event->y_root + 1;
2652           point[1].y = 2 * (event->y_root + 1) - submenu_top + NAVIGATION_REGION_OVERSHOOT;
2653
2654           if (point[0].y >= point[1].y - NAVIGATION_REGION_OVERSHOOT)
2655             return;
2656
2657           flip_y = TRUE;
2658         }
2659       else
2660         {
2661           /* bottom */
2662           point[0].y = event->y_root;
2663           point[1].y = submenu_bottom + NAVIGATION_REGION_OVERSHOOT;
2664
2665           if (point[0].y >= submenu_bottom)
2666             return;
2667         }
2668
2669       point[2].x = point[1].x;
2670       point[2].y = point[0].y;
2671
2672       menu->navigation_region = gdk_region_polygon (point, 3, GDK_WINDING_RULE);
2673
2674       if (flip_x || flip_y)
2675         {
2676           GdkRegion *new_region = flip_region (menu->navigation_region, flip_x, flip_y);
2677           gdk_region_destroy (menu->navigation_region);
2678           menu->navigation_region = new_region;
2679         }
2680
2681       g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
2682                     "gtk-menu-popdown-delay", &popdown_delay,
2683                     NULL);
2684
2685       menu->navigation_timeout = g_timeout_add (popdown_delay,
2686                                                 gtk_menu_stop_navigating_submenu_cb, menu);
2687
2688 #ifdef DRAW_STAY_UP_TRIANGLE
2689       draw_stay_up_triangle (gdk_get_default_root_window(),
2690                              menu->navigation_region);
2691 #endif
2692     }
2693 }
2694
2695 static void
2696 gtk_menu_deactivate (GtkMenuShell *menu_shell)
2697 {
2698   GtkWidget *parent;
2699   
2700   g_return_if_fail (GTK_IS_MENU (menu_shell));
2701   
2702   parent = menu_shell->parent_menu_shell;
2703   
2704   menu_shell->activate_time = 0;
2705   gtk_menu_popdown (GTK_MENU (menu_shell));
2706   
2707   if (parent)
2708     gtk_menu_shell_deactivate (GTK_MENU_SHELL (parent));
2709 }
2710
2711 static void
2712 gtk_menu_position (GtkMenu *menu)
2713 {
2714   GtkWidget *widget;
2715   GtkRequisition requisition;
2716   GtkMenuPrivate *private;
2717   gint x, y;
2718   gint scroll_offset;
2719   gint menu_height;
2720   gboolean push_in;
2721   GdkScreen *screen;
2722   GdkScreen *pointer_screen;
2723   GdkRectangle monitor;
2724   gint monitor_num;
2725
2726   g_return_if_fail (GTK_IS_MENU (menu));
2727
2728   widget = GTK_WIDGET (menu);
2729
2730   screen = gtk_widget_get_screen (widget);
2731   gdk_display_get_pointer (gdk_screen_get_display (screen),
2732                            &pointer_screen, &x, &y, NULL);
2733
2734   /* We need the requisition to figure out the right place to
2735    * popup the menu. In fact, we always need to ask here, since
2736    * if a size_request was queued while we weren't popped up,
2737    * the requisition won't have been recomputed yet.
2738    */
2739   gtk_widget_size_request (widget, &requisition);
2740
2741   if (pointer_screen != screen)
2742     {
2743       /* Pointer is on a different screen; roughly center the
2744        * menu on the screen. If someone was using multiscreen
2745        * + Xinerama together they'd probably want something
2746        * fancier; but that is likely to be vanishingly rare.
2747        */
2748       x = MAX (0, (gdk_screen_get_width (screen) - requisition.width) / 2);
2749       y = MAX (0, (gdk_screen_get_height (screen) - requisition.height) / 2);
2750     }
2751
2752   monitor_num = gdk_screen_get_monitor_at_point (screen, x, y);
2753   gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
2754
2755   push_in = FALSE;
2756   
2757   if (menu->position_func)
2758     (* menu->position_func) (menu, &x, &y, &push_in, menu->position_func_data);
2759   else
2760     {
2761       gint space_left, space_right, space_above, space_below;
2762       gint needed_width;
2763       gint needed_height;
2764       gint xthickness = widget->style->xthickness;
2765       gint ythickness = widget->style->ythickness;
2766       gboolean rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
2767
2768       /* The placement of popup menus horizontally works like this (with
2769        * RTL in parentheses)
2770        *
2771        * - If there is enough room to the right (left) of the mouse cursor,
2772        *   position the menu there.
2773        * 
2774        * - Otherwise, if if there is enough room to the left (right) of the 
2775        *   mouse cursor, position the menu there.
2776        * 
2777        * - Otherwise if the menu is smaller than the monitor, position it
2778        *   on the side of the mouse cursor that has the most space available
2779        *
2780        * - Otherwise (if there is simply not enough room for the menu on the
2781        *   monitor), position it as far left (right) as possible.
2782        *
2783        * Positioning in the vertical direction is similar: first try below
2784        * mouse cursor, then above.
2785        */
2786       space_left = x - monitor.x;
2787       space_right = monitor.x + monitor.width - x - 1;
2788       space_above = y - monitor.y;
2789       space_below = monitor.y + monitor.height - y - 1;
2790
2791       /* position horizontally */
2792
2793       /* the amount of space we need to position the menu. Note the
2794        * menu is offset "xthickness" pixels 
2795        */
2796       needed_width = requisition.width - xthickness;
2797
2798       if (needed_width <= space_left ||
2799           needed_width <= space_right)
2800         {
2801           if ((rtl  && needed_width <= space_left) ||
2802               (!rtl && needed_width >  space_right))
2803             {
2804               /* position left */
2805               x = x + xthickness - requisition.width + 1;
2806             }
2807           else
2808             {
2809               /* position right */
2810               x = x - xthickness;
2811             }
2812
2813           /* x is clamped on-screen further down */
2814         }
2815       else if (requisition.width <= monitor.width)
2816         {
2817           /* the menu is too big to fit on either side of the mouse
2818            * cursor, but smaller than the monitor. Position it on
2819            * the side that has the most space
2820            */
2821           if (space_left > space_right)
2822             {
2823               /* left justify */
2824               x = monitor.x;
2825             }
2826           else
2827             {
2828               /* right justify */
2829               x = monitor.x + monitor.width - requisition.width;
2830             }
2831         }
2832       else /* menu is simply too big for the monitor */
2833         {
2834           if (rtl)
2835             {
2836               /* right justify */
2837               x = monitor.x + monitor.width - requisition.width;
2838             }
2839           else
2840             {
2841               /* left justify */
2842               x = monitor.x;
2843             }
2844         }
2845
2846       /* Position vertically. The algorithm is the same as above, but
2847        * simpler because we don't have to take RTL into account.
2848        */
2849       needed_height = requisition.height - ythickness;
2850
2851       if (needed_height <= space_above ||
2852           needed_height <= space_below)
2853         {
2854           if (needed_height <= space_below)
2855             y = y - ythickness;
2856           else
2857             y = y + ythickness - requisition.height + 1;
2858           
2859           y = CLAMP (y, monitor.y,
2860                      monitor.y + monitor.height - requisition.height);
2861         }
2862       else if (needed_height > space_below && needed_height > space_above)
2863         {
2864           if (space_below >= space_above)
2865             y = monitor.y + monitor.height - requisition.height;
2866           else
2867             y = monitor.y;
2868         }
2869       else
2870         {
2871           y = monitor.y;
2872         }
2873     }
2874
2875   scroll_offset = 0;
2876
2877   if (push_in)
2878     {
2879       menu_height = GTK_WIDGET (menu)->requisition.height;
2880
2881       if (y + menu_height > monitor.y + monitor.height)
2882         {
2883           scroll_offset -= y + menu_height - (monitor.y + monitor.height);
2884           y = (monitor.y + monitor.height) - menu_height;
2885         }
2886   
2887       if (y < monitor.y)
2888         {
2889           scroll_offset += monitor.y - y;
2890           y = monitor.y;
2891         }
2892     }
2893
2894   /* FIXME: should this be done in the various position_funcs ? */
2895   x = CLAMP (x, monitor.x, MAX (monitor.x, monitor.x + monitor.width - requisition.width));
2896  
2897   if (GTK_MENU_SHELL (menu)->active)
2898     {
2899       private = gtk_menu_get_private (menu);
2900       private->have_position = TRUE;
2901       private->x = x;
2902       private->y = y;
2903     }
2904   
2905   if (y + requisition.height > monitor.y + monitor.height)
2906     requisition.height = (monitor.y + monitor.height) - y;
2907   
2908   if (y < monitor.y)
2909     {
2910       scroll_offset += monitor.y - y;
2911       requisition.height -= monitor.y - y;
2912       y = monitor.y;
2913     }
2914
2915   if (scroll_offset > 0)
2916     scroll_offset += MENU_SCROLL_ARROW_HEIGHT;
2917   
2918   gtk_window_move (GTK_WINDOW (GTK_MENU_SHELL (menu)->active ? menu->toplevel : menu->tearoff_window), 
2919                    x, y);
2920
2921   if (!GTK_MENU_SHELL (menu)->active)
2922     {
2923       gtk_window_resize (GTK_WINDOW (menu->tearoff_window),
2924                          requisition.width, requisition.height);
2925     }
2926   
2927   menu->scroll_offset = scroll_offset;
2928 }
2929
2930 static void
2931 gtk_menu_remove_scroll_timeout (GtkMenu *menu)
2932 {
2933   if (menu->timeout_id)
2934     {
2935       g_source_remove (menu->timeout_id);
2936       menu->timeout_id = 0;
2937     }
2938 }
2939
2940 static void
2941 gtk_menu_stop_scrolling (GtkMenu *menu)
2942 {
2943   gtk_menu_remove_scroll_timeout (menu);
2944
2945   menu->upper_arrow_prelight = FALSE;
2946   menu->lower_arrow_prelight = FALSE;
2947 }
2948
2949 static void
2950 gtk_menu_scroll_to (GtkMenu *menu,
2951                     gint    offset)
2952 {
2953   GtkWidget *widget;
2954   gint x, y;
2955   gint view_width, view_height;
2956   gint border_width;
2957   gboolean last_visible;
2958   gint menu_height;
2959   guint vertical_padding;
2960
2961   widget = GTK_WIDGET (menu);
2962
2963   if (menu->tearoff_active &&
2964       menu->tearoff_adjustment &&
2965       (menu->tearoff_adjustment->value != offset))
2966     {
2967       menu->tearoff_adjustment->value =
2968         CLAMP (offset,
2969                0, menu->tearoff_adjustment->upper - menu->tearoff_adjustment->page_size);
2970       gtk_adjustment_value_changed (menu->tearoff_adjustment);
2971     }
2972   
2973   /* Move/resize the viewport according to arrows: */
2974   view_width = widget->allocation.width;
2975   view_height = widget->allocation.height;
2976
2977   gtk_widget_style_get (GTK_WIDGET (menu),
2978                         "vertical-padding", &vertical_padding,
2979                         NULL);
2980   
2981   border_width = GTK_CONTAINER (menu)->border_width;
2982   view_width -= (border_width + widget->style->xthickness) * 2;
2983   view_height -= (border_width + widget->style->ythickness + vertical_padding) * 2;
2984   menu_height = widget->requisition.height -
2985       (border_width + widget->style->ythickness + vertical_padding) * 2;
2986
2987   x = border_width + widget->style->xthickness;
2988   y = border_width + widget->style->ythickness + vertical_padding;
2989
2990   if (!menu->tearoff_active)
2991     {
2992       last_visible = menu->upper_arrow_visible;
2993       menu->upper_arrow_visible = offset > 0;
2994       
2995       if (menu->upper_arrow_visible)
2996         view_height -= MENU_SCROLL_ARROW_HEIGHT;
2997       
2998       if ( (last_visible != menu->upper_arrow_visible) &&
2999            !menu->upper_arrow_visible)
3000         {
3001           menu->upper_arrow_prelight = FALSE;
3002           
3003           /* If we hid the upper arrow, possibly remove timeout */
3004           if (menu->scroll_step < 0)
3005             {
3006               gtk_menu_stop_scrolling (menu);
3007               gtk_widget_queue_draw (GTK_WIDGET (menu));
3008             }
3009         }
3010
3011       last_visible = menu->lower_arrow_visible;
3012       menu->lower_arrow_visible = offset < menu_height - view_height;
3013       
3014       if (menu->lower_arrow_visible)
3015         view_height -= MENU_SCROLL_ARROW_HEIGHT;
3016       
3017       if ( (last_visible != menu->lower_arrow_visible) &&
3018            !menu->lower_arrow_visible)
3019         {
3020           menu->lower_arrow_prelight = FALSE;
3021           
3022           /* If we hid the lower arrow, possibly remove timeout */
3023           if (menu->scroll_step > 0)
3024             {
3025               gtk_menu_stop_scrolling (menu);
3026               gtk_widget_queue_draw (GTK_WIDGET (menu));
3027             }
3028         }
3029       
3030       if (menu->upper_arrow_visible)
3031         y += MENU_SCROLL_ARROW_HEIGHT;
3032     }
3033
3034   /* Scroll the menu: */
3035   if (GTK_WIDGET_REALIZED (menu))
3036     gdk_window_move (menu->bin_window, 0, -offset);
3037
3038   if (GTK_WIDGET_REALIZED (menu))
3039     gdk_window_move_resize (menu->view_window,
3040                             x,
3041                             y,
3042                             view_width,
3043                             view_height);
3044
3045   menu->scroll_offset = offset;
3046 }
3047
3048 static gboolean
3049 compute_child_offset (GtkMenu   *menu,
3050                       GtkWidget *menu_item,
3051                       gint      *offset,
3052                       gint      *height,
3053                       gboolean  *is_last_child)
3054 {
3055   GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
3056   GList *children;
3057   gint child_offset = 0;
3058
3059   for (children = menu_shell->children; children; children = children->next)
3060     {
3061       GtkWidget *child = children->data;
3062       GtkRequisition child_requisition;
3063       gint child_height;
3064       
3065       if (GTK_WIDGET_VISIBLE (child))
3066         {
3067           gtk_widget_size_request (child, &child_requisition);
3068           child_height = child_requisition.height;
3069         }
3070       else
3071         child_height = 0;
3072       
3073       if (child == menu_item)
3074         {
3075           if (is_last_child)
3076             *is_last_child = (children == NULL);
3077           if (offset)
3078             *offset = child_offset;
3079           if (height)
3080             *height = child_height;
3081           
3082           return TRUE;
3083         }
3084       
3085       child_offset += child_height;
3086     }
3087
3088   return FALSE;
3089 }
3090
3091 static void
3092 gtk_menu_scroll_item_visible (GtkMenuShell    *menu_shell,
3093                               GtkWidget       *menu_item)
3094 {
3095   GtkMenu *menu;
3096   gint child_offset, child_height;
3097   gint width, height;
3098   gint y;
3099   gint arrow_height;
3100   gboolean last_child = 0;
3101   
3102   menu = GTK_MENU (menu_shell);
3103
3104   /* We need to check if the selected item fully visible.
3105    * If not we need to scroll the menu so that it becomes fully
3106    * visible.
3107    */
3108
3109   if (compute_child_offset (menu, menu_item,
3110                             &child_offset, &child_height, &last_child))
3111     {
3112       guint vertical_padding;
3113       
3114       y = menu->scroll_offset;
3115       gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height);
3116
3117       gtk_widget_style_get (GTK_WIDGET (menu),
3118                             "vertical-padding", &vertical_padding,
3119                             NULL);
3120                             
3121       height -= 2*GTK_CONTAINER (menu)->border_width + 2*GTK_WIDGET (menu)->style->ythickness + 2*vertical_padding;
3122       
3123       if (child_offset < y)
3124         {
3125           /* Ignore the enter event we might get if the pointer is on the menu
3126            */
3127           menu_shell->ignore_enter = TRUE;
3128           gtk_menu_scroll_to (menu, child_offset);
3129         }
3130       else
3131         {
3132           arrow_height = 0;
3133           if (menu->upper_arrow_visible && !menu->tearoff_active)
3134             arrow_height += MENU_SCROLL_ARROW_HEIGHT;
3135           if (menu->lower_arrow_visible && !menu->tearoff_active)
3136             arrow_height += MENU_SCROLL_ARROW_HEIGHT;
3137           
3138           if (child_offset + child_height > y + height - arrow_height)
3139             {
3140               arrow_height = 0;
3141               if (!last_child && !menu->tearoff_active)
3142                 arrow_height += MENU_SCROLL_ARROW_HEIGHT;
3143               
3144               y = child_offset + child_height - height + arrow_height;
3145               if ((y > 0) && !menu->tearoff_active)
3146                 {
3147                   /* Need upper arrow */
3148                   arrow_height += MENU_SCROLL_ARROW_HEIGHT;
3149                   y = child_offset + child_height - height + arrow_height;
3150                 }
3151               /* Ignore the enter event we might get if the pointer is on the menu
3152                */
3153               menu_shell->ignore_enter = TRUE;
3154               gtk_menu_scroll_to (menu, y);
3155             }
3156         }    
3157       
3158     }
3159 }
3160
3161 static void
3162 gtk_menu_select_item (GtkMenuShell  *menu_shell,
3163                       GtkWidget     *menu_item)
3164 {
3165   GtkMenu *menu = GTK_MENU (menu_shell);
3166
3167   if (GTK_WIDGET_REALIZED (GTK_WIDGET (menu)))
3168     gtk_menu_scroll_item_visible (menu_shell, menu_item);
3169
3170   GTK_MENU_SHELL_CLASS (parent_class)->select_item (menu_shell, menu_item);
3171 }
3172
3173
3174 /* Reparent the menu, taking care of the refcounting
3175  *
3176  * If unrealize is true we force a unrealize while reparenting the parent.
3177  * This can help eliminate flicker in some cases.
3178  *
3179  * What happens is that when the menu is unrealized and then re-realized,
3180  * the allocations are as follows:
3181  *
3182  *  parent - 1x1 at (0,0) 
3183  *  child1 - 100x20 at (0,0)
3184  *  child2 - 100x20 at (0,20)
3185  *  child3 - 100x20 at (0,40)
3186  *
3187  * That is, the parent is small but the children are full sized. Then,
3188  * when the queued_resize gets processed, the parent gets resized to
3189  * full size. 
3190  *
3191  * But in order to eliminate flicker when scrolling, gdkgeometry-x11.c
3192  * contains the following logic:
3193  * 
3194  * - if a move or resize operation on a window would change the clip 
3195  *   region on the children, then before the window is resized
3196  *   the background for children is temporarily set to None, the
3197  *   move/resize done, and the background for the children restored.
3198  *
3199  * So, at the point where the parent is resized to final size, the
3200  * background for the children is temporarily None, and thus they
3201  * are not cleared to the background color and the previous background
3202  * (the image of the menu) is left in place.
3203  */
3204 static void 
3205 gtk_menu_reparent (GtkMenu      *menu, 
3206                    GtkWidget    *new_parent, 
3207                    gboolean      unrealize)
3208 {
3209   GtkObject *object = GTK_OBJECT (menu);
3210   GtkWidget *widget = GTK_WIDGET (menu);
3211   gboolean was_floating = GTK_OBJECT_FLOATING (object);
3212
3213   g_object_ref (object);
3214   gtk_object_sink (object);
3215
3216   if (unrealize)
3217     {
3218       g_object_ref (object);
3219       gtk_container_remove (GTK_CONTAINER (widget->parent), widget);
3220       gtk_container_add (GTK_CONTAINER (new_parent), widget);
3221       g_object_unref (object);
3222     }
3223   else
3224     gtk_widget_reparent (GTK_WIDGET (menu), new_parent);
3225   
3226   if (was_floating)
3227     GTK_OBJECT_SET_FLAGS (object, GTK_FLOATING);
3228   else
3229     g_object_unref (object);
3230 }
3231
3232 static void
3233 gtk_menu_show_all (GtkWidget *widget)
3234 {
3235   g_return_if_fail (GTK_IS_MENU (widget));
3236
3237   /* Show children, but not self. */
3238   gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_show_all, NULL);
3239 }
3240
3241
3242 static void
3243 gtk_menu_hide_all (GtkWidget *widget)
3244 {
3245   g_return_if_fail (GTK_IS_MENU (widget));
3246
3247   /* Hide children, but not self. */
3248   gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_hide_all, NULL);
3249 }
3250
3251 /**
3252  * gtk_menu_set_screen:
3253  * @menu: a #GtkMenu.
3254  * @screen: a #GdkScreen, or %NULL if the screen should be
3255  *          determined by the widget the menu is attached to.
3256  *
3257  * Sets the #GdkScreen on which the menu will be displayed.
3258  * 
3259  * Since: 2.2
3260  **/
3261 void
3262 gtk_menu_set_screen (GtkMenu   *menu, 
3263                      GdkScreen *screen)
3264 {
3265   g_return_if_fail (GTK_IS_MENU (menu));
3266   g_return_if_fail (!screen || GDK_IS_SCREEN (screen));
3267
3268   g_object_set_data (G_OBJECT (menu), "gtk-menu-explicit-screen", screen);
3269
3270   if (screen)
3271     {
3272       menu_change_screen (menu, screen);
3273     }
3274   else
3275     {
3276       GtkWidget *attach_widget = gtk_menu_get_attach_widget (menu);
3277       if (attach_widget)
3278         attach_widget_screen_changed (attach_widget, NULL, menu);
3279     }
3280 }
3281
3282
3283 static gint
3284 gtk_menu_get_popup_delay (GtkMenuShell *menu_shell)
3285 {
3286   gint popup_delay;
3287
3288   g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_shell)),
3289                 "gtk-menu-popup-delay", &popup_delay,
3290                 NULL);
3291
3292   return popup_delay;
3293 }
3294
3295 static gint
3296 get_visible_size (GtkMenu *menu)
3297 {
3298   GtkWidget *widget = GTK_WIDGET (menu);
3299   GtkContainer *container = GTK_CONTAINER (menu);
3300   
3301   gint menu_height = (widget->allocation.height
3302                       - 2 * (container->border_width
3303                              + widget->style->ythickness));
3304   
3305   if (menu->upper_arrow_visible && !menu->tearoff_active)
3306     menu_height -= MENU_SCROLL_ARROW_HEIGHT;
3307   if (menu->lower_arrow_visible && !menu->tearoff_active)
3308     menu_height -= MENU_SCROLL_ARROW_HEIGHT;
3309
3310   return menu_height;
3311 }
3312
3313 /* Find the sensitive on-screen child containing @y, or if none,
3314  * the nearest selectable onscreen child. (%NULL if none)
3315  */
3316 static GtkWidget *
3317 child_at (GtkMenu *menu,
3318           gint     y)
3319 {
3320   GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
3321   GtkWidget *child = NULL;
3322   gint child_offset = 0;
3323   GList *children;
3324   gint menu_height;
3325   gint lower, upper;            /* Onscreen bounds */
3326
3327   menu_height = get_visible_size (menu);
3328   lower = menu->scroll_offset;
3329   upper = menu->scroll_offset + menu_height;
3330   
3331   for (children = menu_shell->children; children; children = children->next)
3332     {
3333       if (GTK_WIDGET_VISIBLE (children->data))
3334         {
3335           GtkRequisition child_requisition;
3336
3337           gtk_widget_size_request (children->data, &child_requisition);
3338
3339           if (_gtk_menu_item_is_selectable (children->data) &&
3340               child_offset >= lower &&
3341               child_offset + child_requisition.height <= upper)
3342             {
3343               child = children->data;
3344               
3345               if (child_offset + child_requisition.height > y &&
3346                   !GTK_IS_TEAROFF_MENU_ITEM (child))
3347                 return child;
3348             }
3349       
3350           child_offset += child_requisition.height;
3351         }
3352     }
3353
3354   return child;
3355 }
3356
3357 static gint
3358 get_menu_height (GtkMenu *menu)
3359 {
3360   gint height;
3361   GtkWidget *widget = GTK_WIDGET (menu);
3362
3363   height = widget->requisition.height;
3364   height -= (GTK_CONTAINER (widget)->border_width + widget->style->ythickness) * 2;
3365
3366   if (menu->upper_arrow_visible && !menu->tearoff_active)
3367     height -= MENU_SCROLL_ARROW_HEIGHT;
3368
3369   if (menu->lower_arrow_visible && !menu->tearoff_active)
3370     height -= MENU_SCROLL_ARROW_HEIGHT;
3371
3372   return height;
3373 }
3374
3375 static void
3376 gtk_menu_real_move_scroll (GtkMenu       *menu,
3377                            GtkScrollType  type)
3378 {
3379   gint page_size = get_visible_size (menu);
3380   gint end_position = get_menu_height (menu);
3381   GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
3382   
3383   switch (type)
3384     {
3385     case GTK_SCROLL_PAGE_UP:
3386     case GTK_SCROLL_PAGE_DOWN:
3387       {
3388         gint old_offset;
3389         gint new_offset;
3390         gint child_offset = 0;
3391         gboolean old_upper_arrow_visible;
3392         gint step;
3393
3394         if (type == GTK_SCROLL_PAGE_UP)
3395           step = - page_size;
3396         else
3397           step = page_size;
3398
3399         if (menu_shell->active_menu_item)
3400           {
3401             gint child_height;
3402             
3403             compute_child_offset (menu, menu_shell->active_menu_item,
3404                                   &child_offset, &child_height, NULL);
3405             child_offset += child_height / 2;
3406           }
3407
3408         menu_shell->ignore_enter = TRUE;
3409         old_upper_arrow_visible = menu->upper_arrow_visible && !menu->tearoff_active;
3410         old_offset = menu->scroll_offset;
3411
3412         new_offset = menu->scroll_offset + step;
3413         new_offset = CLAMP (new_offset, 0, end_position - page_size);
3414
3415         gtk_menu_scroll_to (menu, new_offset);
3416         
3417         if (menu_shell->active_menu_item)
3418           {
3419             GtkWidget *new_child;
3420             gboolean new_upper_arrow_visible = menu->upper_arrow_visible && !menu->tearoff_active;
3421
3422             if (menu->scroll_offset != old_offset)
3423               step = menu->scroll_offset - old_offset;
3424
3425             step -= (new_upper_arrow_visible - old_upper_arrow_visible) * MENU_SCROLL_ARROW_HEIGHT;
3426
3427             new_child = child_at (menu, child_offset + step);
3428             if (new_child)
3429               gtk_menu_shell_select_item (menu_shell, new_child);
3430           }
3431       }
3432       break;
3433     case GTK_SCROLL_START:
3434       /* Ignore the enter event we might get if the pointer is on the menu
3435        */
3436       menu_shell->ignore_enter = TRUE;
3437       gtk_menu_scroll_to (menu, 0);
3438       gtk_menu_shell_select_first (menu_shell, TRUE);
3439       break;
3440     case GTK_SCROLL_END:
3441       /* Ignore the enter event we might get if the pointer is on the menu
3442        */
3443       menu_shell->ignore_enter = TRUE;
3444       gtk_menu_scroll_to (menu, end_position - page_size);
3445       _gtk_menu_shell_select_last (menu_shell, TRUE);
3446       break;
3447     default:
3448       break;
3449     }
3450 }