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