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