]> Pileus Git - ~andy/gtk/blob - gtk/gtkmenu.c
menu: Deprecate GtkTearoffMenuItem
[~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 /**
28  * SECTION:gtkmenu
29  * @Short_description: A menu widget
30  * @Title: GtkMenu
31  *
32  * A #GtkMenu is a #GtkMenuShell that implements a drop down menu
33  * consisting of a list of #GtkMenuItem objects which can be navigated
34  * and activated by the user to perform application functions.
35  *
36  * A #GtkMenu is most commonly dropped down by activating a
37  * #GtkMenuItem in a #GtkMenuBar or popped up by activating a
38  * #GtkMenuItem in another #GtkMenu.
39  *
40  * A #GtkMenu can also be popped up by activating a #GtkOptionMenu.
41  * Other composite widgets such as the #GtkNotebook can pop up a
42  * #GtkMenu as well.
43  *
44  * Applications can display a #GtkMenu as a popup menu by calling the 
45  * gtk_menu_popup() function.  The example below shows how an application
46  * can pop up a menu when the 3rd mouse button is pressed.  
47  *
48  * <example>
49  * <title>Connecting the popup signal handler.</title>
50  * <programlisting>
51  *   /<!---->* connect our handler which will popup the menu *<!---->/
52  *   g_signal_connect_swapped (window, "button_press_event",
53  *      G_CALLBACK (my_popup_handler), menu);
54  * </programlisting>
55  * </example>
56  *
57  * <example>
58  * <title>Signal handler which displays a popup menu.</title>
59  * <programlisting>
60  * static gint
61  * my_popup_handler (GtkWidget *widget, GdkEvent *event)
62  * {
63  *   GtkMenu *menu;
64  *   GdkEventButton *event_button;
65  *
66  *   g_return_val_if_fail (widget != NULL, FALSE);
67  *   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
68  *   g_return_val_if_fail (event != NULL, FALSE);
69  *
70  *   /<!---->* The "widget" is the menu that was supplied when 
71  *    * g_signal_connect_swapped() was called.
72  *    *<!---->/
73  *   menu = GTK_MENU (widget);
74  *
75  *   if (event->type == GDK_BUTTON_PRESS)
76  *     {
77  *       event_button = (GdkEventButton *) event;
78  *       if (event_button->button == 3)
79  *         {
80  *           gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 
81  *                           event_button->button, event_button->time);
82  *           return TRUE;
83  *         }
84  *     }
85  * 
86  *   return FALSE;
87  * }
88  * </programlisting>
89  * </example>
90  */
91
92 #include "config.h"
93
94 #include <string.h>
95
96 #include  <gobject/gvaluecollector.h>
97
98 #include "gtkaccellabel.h"
99 #include "gtkaccelmap.h"
100 #include "gtkbindings.h"
101 #include "gtkcheckmenuitem.h"
102 #include "gtkmain.h"
103 #include "gtkmarshalers.h"
104 #include "gtkmenuprivate.h"
105 #include "gtkmenuitemprivate.h"
106 #include "gtkmenushellprivate.h"
107 #include "gtkwindow.h"
108 #include "gtkbox.h"
109 #include "gtkscrollbar.h"
110 #include "gtksettings.h"
111 #include "gtkprivate.h"
112 #include "gtkwidgetprivate.h"
113 #include "gtkintl.h"
114 #include "gtktypebuiltins.h"
115
116 #undef GTK_DISABLE_DEPRECATED
117 #include "gtktearoffmenuitem.h"
118
119
120 #include "a11y/gtkmenuaccessible.h"
121
122 #define NAVIGATION_REGION_OVERSHOOT 50  /* How much the navigation region
123                                          * extends below the submenu
124                                          */
125
126 #define MENU_SCROLL_STEP1      8
127 #define MENU_SCROLL_STEP2     15
128 #define MENU_SCROLL_FAST_ZONE  8
129 #define MENU_SCROLL_TIMEOUT1  50
130 #define MENU_SCROLL_TIMEOUT2  20
131
132 #define ATTACH_INFO_KEY "gtk-menu-child-attach-info-key"
133 #define ATTACHED_MENUS "gtk-attached-menus"
134
135 typedef struct _GtkMenuAttachData  GtkMenuAttachData;
136 typedef struct _GtkMenuPopdownData GtkMenuPopdownData;
137
138 struct _GtkMenuAttachData
139 {
140   GtkWidget *attach_widget;
141   GtkMenuDetachFunc detacher;
142 };
143
144 struct _OldGtkMenuPrivate
145 {
146   gint x;
147   gint y;
148   gboolean initially_pushed_in;
149
150   GDestroyNotify position_func_data_destroy;
151
152   /* info used for the table */
153   guint *heights;
154   gint heights_length;
155   gint requested_height;
156
157   gint monitor_num;
158
159   /* Cached layout information */
160   gint n_rows;
161   gint n_columns;
162
163   guint accel_size;
164
165   gchar *title;
166
167   /* Arrow states */
168   GtkStateFlags lower_arrow_state;
169   GtkStateFlags upper_arrow_state;
170
171   /* navigation region */
172   int navigation_x;
173   int navigation_y;
174   int navigation_width;
175   int navigation_height;
176
177   guint have_layout           : 1;
178   guint seen_item_enter       : 1;
179   guint have_position         : 1;
180   guint ignore_button_release : 1;
181   guint no_toggle_size        : 1;
182 };
183
184 struct _GtkMenuPopdownData
185 {
186   GtkMenu *menu;
187   GdkDevice *device;
188 };
189
190 typedef struct
191 {
192   gint left_attach;
193   gint right_attach;
194   gint top_attach;
195   gint bottom_attach;
196   gint effective_left_attach;
197   gint effective_right_attach;
198   gint effective_top_attach;
199   gint effective_bottom_attach;
200 } AttachInfo;
201
202 enum {
203   MOVE_SCROLL,
204   LAST_SIGNAL
205 };
206
207 enum {
208   PROP_0,
209   PROP_ACTIVE,
210   PROP_ACCEL_GROUP,
211   PROP_ACCEL_PATH,
212   PROP_ATTACH_WIDGET,
213   PROP_TEAROFF_STATE,
214   PROP_TEAROFF_TITLE,
215   PROP_MONITOR,
216   PROP_RESERVE_TOGGLE_SIZE
217 };
218
219 enum {
220   CHILD_PROP_0,
221   CHILD_PROP_LEFT_ATTACH,
222   CHILD_PROP_RIGHT_ATTACH,
223   CHILD_PROP_TOP_ATTACH,
224   CHILD_PROP_BOTTOM_ATTACH
225 };
226
227 static void     gtk_menu_set_property      (GObject          *object,
228                                             guint             prop_id,
229                                             const GValue     *value,
230                                             GParamSpec       *pspec);
231 static void     gtk_menu_get_property      (GObject          *object,
232                                             guint             prop_id,
233                                             GValue           *value,
234                                             GParamSpec       *pspec);
235 static void     gtk_menu_set_child_property(GtkContainer     *container,
236                                             GtkWidget        *child,
237                                             guint             property_id,
238                                             const GValue     *value,
239                                             GParamSpec       *pspec);
240 static void     gtk_menu_get_child_property(GtkContainer     *container,
241                                             GtkWidget        *child,
242                                             guint             property_id,
243                                             GValue           *value,
244                                             GParamSpec       *pspec);
245 static void     gtk_menu_destroy           (GtkWidget        *widget);
246 static void     gtk_menu_realize           (GtkWidget        *widget);
247 static void     gtk_menu_unrealize         (GtkWidget        *widget);
248 static void     gtk_menu_size_allocate     (GtkWidget        *widget,
249                                             GtkAllocation    *allocation);
250 static void     gtk_menu_show              (GtkWidget        *widget);
251 static gboolean gtk_menu_draw              (GtkWidget        *widget,
252                                             cairo_t          *cr);
253 static gboolean gtk_menu_key_press         (GtkWidget        *widget,
254                                             GdkEventKey      *event);
255 static gboolean gtk_menu_scroll            (GtkWidget        *widget,
256                                             GdkEventScroll   *event);
257 static gboolean gtk_menu_button_press      (GtkWidget        *widget,
258                                             GdkEventButton   *event);
259 static gboolean gtk_menu_button_release    (GtkWidget        *widget,
260                                             GdkEventButton   *event);
261 static gboolean gtk_menu_motion_notify     (GtkWidget        *widget,
262                                             GdkEventMotion   *event);
263 static gboolean gtk_menu_enter_notify      (GtkWidget        *widget,
264                                             GdkEventCrossing *event);
265 static gboolean gtk_menu_leave_notify      (GtkWidget        *widget,
266                                             GdkEventCrossing *event);
267 static void     gtk_menu_scroll_to         (GtkMenu          *menu,
268                                             gint              offset);
269 static void     gtk_menu_grab_notify       (GtkWidget        *widget,
270                                             gboolean          was_grabbed);
271
272 static void     gtk_menu_stop_scrolling         (GtkMenu  *menu);
273 static void     gtk_menu_remove_scroll_timeout  (GtkMenu  *menu);
274 static gboolean gtk_menu_scroll_timeout         (gpointer  data);
275 static gboolean gtk_menu_scroll_timeout_initial (gpointer  data);
276 static void     gtk_menu_start_scrolling        (GtkMenu  *menu);
277
278 static void     gtk_menu_scroll_item_visible (GtkMenuShell    *menu_shell,
279                                               GtkWidget       *menu_item);
280 static void     gtk_menu_select_item       (GtkMenuShell     *menu_shell,
281                                             GtkWidget        *menu_item);
282 static void     gtk_menu_real_insert       (GtkMenuShell     *menu_shell,
283                                             GtkWidget        *child,
284                                             gint              position);
285 static void     gtk_menu_scrollbar_changed (GtkAdjustment    *adjustment,
286                                             GtkMenu          *menu);
287 static void     gtk_menu_handle_scrolling  (GtkMenu          *menu,
288                                             gint              event_x,
289                                             gint              event_y,
290                                             gboolean          enter,
291                                             gboolean          motion);
292 static void     gtk_menu_set_tearoff_hints (GtkMenu          *menu,
293                                             gint             width);
294 static void     gtk_menu_style_updated     (GtkWidget        *widget);
295 static gboolean gtk_menu_focus             (GtkWidget        *widget,
296                                             GtkDirectionType direction);
297 static gint     gtk_menu_get_popup_delay   (GtkMenuShell     *menu_shell);
298 static void     gtk_menu_move_current      (GtkMenuShell     *menu_shell,
299                                             GtkMenuDirectionType direction);
300 static void     gtk_menu_real_move_scroll  (GtkMenu          *menu,
301                                             GtkScrollType     type);
302
303 static void     gtk_menu_stop_navigating_submenu       (GtkMenu          *menu);
304 static gboolean gtk_menu_stop_navigating_submenu_cb    (gpointer          user_data);
305 static gboolean gtk_menu_navigating_submenu            (GtkMenu          *menu,
306                                                         gint              event_x,
307                                                         gint              event_y);
308 static void     gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
309                                                         GtkMenuItem      *menu_item,
310                                                         GdkEventCrossing *event);
311  
312 static void gtk_menu_deactivate     (GtkMenuShell      *menu_shell);
313 static void gtk_menu_show_all       (GtkWidget         *widget);
314 static void gtk_menu_position       (GtkMenu           *menu,
315                                      gboolean           set_scroll_offset);
316 static void gtk_menu_reparent       (GtkMenu           *menu,
317                                      GtkWidget         *new_parent,
318                                      gboolean           unrealize);
319 static void gtk_menu_remove         (GtkContainer      *menu,
320                                      GtkWidget         *widget);
321
322 static void gtk_menu_update_title   (GtkMenu           *menu);
323
324 static void       menu_grab_transfer_window_destroy (GtkMenu *menu);
325 static GdkWindow *menu_grab_transfer_window_get     (GtkMenu *menu);
326
327 static gboolean gtk_menu_real_can_activate_accel (GtkWidget *widget,
328                                                   guint      signal_id);
329 static void _gtk_menu_refresh_accel_paths (GtkMenu *menu,
330                                            gboolean group_changed);
331
332 static void gtk_menu_get_preferred_width            (GtkWidget           *widget,
333                                                      gint                *minimum_size,
334                                                      gint                *natural_size);
335 static void gtk_menu_get_preferred_height           (GtkWidget           *widget,
336                                                      gint                *minimum_size,
337                                                      gint                *natural_size);
338 static void gtk_menu_get_preferred_height_for_width (GtkWidget           *widget,
339                                                      gint                 for_size,
340                                                      gint                *minimum_size,
341                                                      gint                *natural_size);
342
343
344 static const gchar attach_data_key[] = "gtk-menu-attach-data";
345
346 static guint menu_signals[LAST_SIGNAL] = { 0 };
347
348 G_DEFINE_TYPE (GtkMenu, gtk_menu, GTK_TYPE_MENU_SHELL)
349
350 static void
351 menu_queue_resize (GtkMenu *menu)
352 {
353   GtkMenuPrivate *priv = menu->priv;
354
355   priv->have_layout = FALSE;
356   gtk_widget_queue_resize (GTK_WIDGET (menu));
357 }
358
359 static void
360 attach_info_free (AttachInfo *info)
361 {
362   g_slice_free (AttachInfo, info);
363 }
364
365 static AttachInfo *
366 get_attach_info (GtkWidget *child)
367 {
368   GObject *object = G_OBJECT (child);
369   AttachInfo *ai = g_object_get_data (object, ATTACH_INFO_KEY);
370
371   if (!ai)
372     {
373       ai = g_slice_new0 (AttachInfo);
374       g_object_set_data_full (object, I_(ATTACH_INFO_KEY), ai,
375                               (GDestroyNotify) attach_info_free);
376     }
377
378   return ai;
379 }
380
381 static gboolean
382 is_grid_attached (AttachInfo *ai)
383 {
384   return (ai->left_attach >= 0 &&
385           ai->right_attach >= 0 &&
386           ai->top_attach >= 0 &&
387           ai->bottom_attach >= 0);
388 }
389
390 static void
391 menu_ensure_layout (GtkMenu *menu)
392 {
393   GtkMenuPrivate *priv = menu->priv;
394
395   if (!priv->have_layout)
396     {
397       GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
398       GList *l;
399       gchar *row_occupied;
400       gint current_row;
401       gint max_right_attach;
402       gint max_bottom_attach;
403
404       /* Find extents of gridded portion
405        */
406       max_right_attach = 1;
407       max_bottom_attach = 0;
408
409       for (l = menu_shell->priv->children; l; l = l->next)
410         {
411           GtkWidget *child = l->data;
412           AttachInfo *ai = get_attach_info (child);
413
414           if (is_grid_attached (ai))
415             {
416               max_bottom_attach = MAX (max_bottom_attach, ai->bottom_attach);
417               max_right_attach = MAX (max_right_attach, ai->right_attach);
418             }
419         }
420
421       /* Find empty rows */
422       row_occupied = g_malloc0 (max_bottom_attach);
423
424       for (l = menu_shell->priv->children; l; l = l->next)
425         {
426           GtkWidget *child = l->data;
427           AttachInfo *ai = get_attach_info (child);
428
429           if (is_grid_attached (ai))
430             {
431               gint i;
432
433               for (i = ai->top_attach; i < ai->bottom_attach; i++)
434                 row_occupied[i] = TRUE;
435             }
436         }
437
438       /* Lay non-grid-items out in those rows
439        */
440       current_row = 0;
441       for (l = menu_shell->priv->children; l; l = l->next)
442         {
443           GtkWidget *child = l->data;
444           AttachInfo *ai = get_attach_info (child);
445
446           if (!is_grid_attached (ai))
447             {
448               while (current_row < max_bottom_attach && row_occupied[current_row])
449                 current_row++;
450
451               ai->effective_left_attach = 0;
452               ai->effective_right_attach = max_right_attach;
453               ai->effective_top_attach = current_row;
454               ai->effective_bottom_attach = current_row + 1;
455
456               current_row++;
457             }
458           else
459             {
460               ai->effective_left_attach = ai->left_attach;
461               ai->effective_right_attach = ai->right_attach;
462               ai->effective_top_attach = ai->top_attach;
463               ai->effective_bottom_attach = ai->bottom_attach;
464             }
465         }
466
467       g_free (row_occupied);
468
469       priv->n_rows = MAX (current_row, max_bottom_attach);
470       priv->n_columns = max_right_attach;
471       priv->have_layout = TRUE;
472     }
473 }
474
475
476 static gint
477 gtk_menu_get_n_columns (GtkMenu *menu)
478 {
479   GtkMenuPrivate *priv = menu->priv;
480
481   menu_ensure_layout (menu);
482
483   return priv->n_columns;
484 }
485
486 static gint
487 gtk_menu_get_n_rows (GtkMenu *menu)
488 {
489   GtkMenuPrivate *priv = menu->priv;
490
491   menu_ensure_layout (menu);
492
493   return priv->n_rows;
494 }
495
496 static void
497 get_effective_child_attach (GtkWidget *child,
498                             int       *l,
499                             int       *r,
500                             int       *t,
501                             int       *b)
502 {
503   GtkMenu *menu = GTK_MENU (gtk_widget_get_parent (child));
504   AttachInfo *ai;
505   
506   menu_ensure_layout (menu);
507
508   ai = get_attach_info (child);
509
510   if (l)
511     *l = ai->effective_left_attach;
512   if (r)
513     *r = ai->effective_right_attach;
514   if (t)
515     *t = ai->effective_top_attach;
516   if (b)
517     *b = ai->effective_bottom_attach;
518
519 }
520
521 static void
522 gtk_menu_class_init (GtkMenuClass *class)
523 {
524   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
525   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
526   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
527   GtkMenuShellClass *menu_shell_class = GTK_MENU_SHELL_CLASS (class);
528   GtkBindingSet *binding_set;
529   
530   gobject_class->set_property = gtk_menu_set_property;
531   gobject_class->get_property = gtk_menu_get_property;
532
533   widget_class->destroy = gtk_menu_destroy;
534   widget_class->realize = gtk_menu_realize;
535   widget_class->unrealize = gtk_menu_unrealize;
536   widget_class->size_allocate = gtk_menu_size_allocate;
537   widget_class->show = gtk_menu_show;
538   widget_class->draw = gtk_menu_draw;
539   widget_class->scroll_event = gtk_menu_scroll;
540   widget_class->key_press_event = gtk_menu_key_press;
541   widget_class->button_press_event = gtk_menu_button_press;
542   widget_class->button_release_event = gtk_menu_button_release;
543   widget_class->motion_notify_event = gtk_menu_motion_notify;
544   widget_class->show_all = gtk_menu_show_all;
545   widget_class->enter_notify_event = gtk_menu_enter_notify;
546   widget_class->leave_notify_event = gtk_menu_leave_notify;
547   widget_class->style_updated = gtk_menu_style_updated;
548   widget_class->focus = gtk_menu_focus;
549   widget_class->can_activate_accel = gtk_menu_real_can_activate_accel;
550   widget_class->grab_notify = gtk_menu_grab_notify;
551   widget_class->get_preferred_width = gtk_menu_get_preferred_width;
552   widget_class->get_preferred_height = gtk_menu_get_preferred_height;
553   widget_class->get_preferred_height_for_width = gtk_menu_get_preferred_height_for_width;
554
555   container_class->remove = gtk_menu_remove;
556   container_class->get_child_property = gtk_menu_get_child_property;
557   container_class->set_child_property = gtk_menu_set_child_property;
558   
559   menu_shell_class->submenu_placement = GTK_LEFT_RIGHT;
560   menu_shell_class->deactivate = gtk_menu_deactivate;
561   menu_shell_class->select_item = gtk_menu_select_item;
562   menu_shell_class->insert = gtk_menu_real_insert;
563   menu_shell_class->get_popup_delay = gtk_menu_get_popup_delay;
564   menu_shell_class->move_current = gtk_menu_move_current;
565
566   /**
567    * GtkMenu::move-scroll:
568    * @menu: a #GtkMenu
569    * @scroll_type: a #GtkScrollType
570    */
571   menu_signals[MOVE_SCROLL] =
572     g_signal_new_class_handler (I_("move-scroll"),
573                                 G_OBJECT_CLASS_TYPE (gobject_class),
574                                 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
575                                 G_CALLBACK (gtk_menu_real_move_scroll),
576                                 NULL, NULL,
577                                 _gtk_marshal_VOID__ENUM,
578                                 G_TYPE_NONE, 1,
579                                 GTK_TYPE_SCROLL_TYPE);
580
581   /**
582    * GtkMenu:active:
583    *
584    * The index of the currently selected menu item, or -1 if no
585    * menu item is selected.
586    *
587    * Since: 2.14
588    **/
589   g_object_class_install_property (gobject_class,
590                                    PROP_ACTIVE,
591                                    g_param_spec_int ("active",
592                                                      P_("Active"),
593                                                      P_("The currently selected menu item"),
594                                                      -1, G_MAXINT, -1,
595                                                      GTK_PARAM_READWRITE));
596
597   /**
598    * GtkMenu:accel-group:
599    *
600    * The accel group holding accelerators for the menu.
601    *
602    * Since: 2.14
603    **/
604   g_object_class_install_property (gobject_class,
605                                    PROP_ACCEL_GROUP,
606                                    g_param_spec_object ("accel-group",
607                                                         P_("Accel Group"),
608                                                         P_("The accel group holding accelerators for the menu"),
609                                                         GTK_TYPE_ACCEL_GROUP,
610                                                         GTK_PARAM_READWRITE));
611
612   /**
613    * GtkMenu:accel-path:
614    *
615    * An accel path used to conveniently construct accel paths of child items.
616    *
617    * Since: 2.14
618    **/
619   g_object_class_install_property (gobject_class,
620                                    PROP_ACCEL_PATH,
621                                    g_param_spec_string ("accel-path",
622                                                         P_("Accel Path"),
623                                                         P_("An accel path used to conveniently construct accel paths of child items"),
624                                                         NULL,
625                                                         GTK_PARAM_READWRITE));
626
627   /**
628    * GtkMenu:attach-widget:
629    *
630    * The widget the menu is attached to. Setting this property attaches
631    * the menu without a #GtkMenuDetachFunc. If you need to use a detacher,
632    * use gtk_menu_attach_to_widget() directly.
633    *
634    * Since: 2.14
635    **/
636   g_object_class_install_property (gobject_class,
637                                    PROP_ATTACH_WIDGET,
638                                    g_param_spec_object ("attach-widget",
639                                                         P_("Attach Widget"),
640                                                         P_("The widget the menu is attached to"),
641                                                         GTK_TYPE_WIDGET,
642                                                         GTK_PARAM_READWRITE));
643
644   g_object_class_install_property (gobject_class,
645                                    PROP_TEAROFF_TITLE,
646                                    g_param_spec_string ("tearoff-title",
647                                                         P_("Tearoff Title"),
648                                                         P_("A title that may be displayed by the window manager when this menu is torn-off"),
649                                                         NULL,
650                                                         GTK_PARAM_READWRITE));
651
652   /**
653    * GtkMenu:tearoff-state:
654    *
655    * A boolean that indicates whether the menu is torn-off.
656    *
657    * Since: 2.6
658    **/
659   g_object_class_install_property (gobject_class,
660                                    PROP_TEAROFF_STATE,
661                                    g_param_spec_boolean ("tearoff-state",
662                                                          P_("Tearoff State"),
663                                                          P_("A boolean that indicates whether the menu is torn-off"),
664                                                          FALSE,
665                                                          GTK_PARAM_READWRITE));
666
667   /**
668    * GtkMenu:monitor:
669    *
670    * The monitor the menu will be popped up on.
671    *
672    * Since: 2.14
673    **/
674   g_object_class_install_property (gobject_class,
675                                    PROP_MONITOR,
676                                    g_param_spec_int ("monitor",
677                                                      P_("Monitor"),
678                                                      P_("The monitor the menu will be popped up on"),
679                                                      -1, G_MAXINT, -1,
680                                                      GTK_PARAM_READWRITE));
681
682   gtk_widget_class_install_style_property (widget_class,
683                                            g_param_spec_int ("vertical-padding",
684                                                              P_("Vertical Padding"),
685                                                              P_("Extra space at the top and bottom of the menu"),
686                                                              0,
687                                                              G_MAXINT,
688                                                              1,
689                                                              GTK_PARAM_READABLE));
690
691   /**
692    * GtkMenu:reserve-toggle-size:
693    *
694    * A boolean that indicates whether the menu reserves space for
695    * toggles and icons, regardless of their actual presence.
696    *
697    * This property should only be changed from its default value
698    * for special-purposes such as tabular menus. Regular menus that
699    * are connected to a menu bar or context menus should reserve
700    * toggle space for consistency.
701    *
702    * Since: 2.18
703    */
704   g_object_class_install_property (gobject_class,
705                                    PROP_RESERVE_TOGGLE_SIZE,
706                                    g_param_spec_boolean ("reserve-toggle-size",
707                                                          P_("Reserve Toggle Size"),
708                                                          P_("A boolean that indicates whether the menu reserves space for toggles and icons"),
709                                                          TRUE,
710                                                          GTK_PARAM_READWRITE));
711
712   gtk_widget_class_install_style_property (widget_class,
713                                            g_param_spec_int ("horizontal-padding",
714                                                              P_("Horizontal Padding"),
715                                                              P_("Extra space at the left and right edges of the menu"),
716                                                              0,
717                                                              G_MAXINT,
718                                                              0,
719                                                              GTK_PARAM_READABLE));
720
721   gtk_widget_class_install_style_property (widget_class,
722                                            g_param_spec_int ("vertical-offset",
723                                                              P_("Vertical Offset"),
724                                                              P_("When the menu is a submenu, position it this number of pixels offset vertically"),
725                                                              G_MININT,
726                                                              G_MAXINT,
727                                                              0,
728                                                              GTK_PARAM_READABLE));
729
730   gtk_widget_class_install_style_property (widget_class,
731                                            g_param_spec_int ("horizontal-offset",
732                                                              P_("Horizontal Offset"),
733                                                              P_("When the menu is a submenu, position it this number of pixels offset horizontally"),
734                                                              G_MININT,
735                                                              G_MAXINT,
736                                                              -2,
737                                                              GTK_PARAM_READABLE));
738
739   gtk_widget_class_install_style_property (widget_class,
740                                            g_param_spec_boolean ("double-arrows",
741                                                                  P_("Double Arrows"),
742                                                                  P_("When scrolling, always show both arrows."),
743                                                                  TRUE,
744                                                                  GTK_PARAM_READABLE));
745
746   /**
747    * GtkMenu:arrow-placement:
748    *
749    * Indicates where scroll arrows should be placed.
750    *
751    * Since: 2.16
752    **/
753   gtk_widget_class_install_style_property (widget_class,
754                                            g_param_spec_enum ("arrow-placement",
755                                                               P_("Arrow Placement"),
756                                                               P_("Indicates where scroll arrows should be placed"),
757                                                               GTK_TYPE_ARROW_PLACEMENT,
758                                                               GTK_ARROWS_BOTH,
759                                                               GTK_PARAM_READABLE));
760
761  gtk_container_class_install_child_property (container_class,
762                                              CHILD_PROP_LEFT_ATTACH,
763                                               g_param_spec_int ("left-attach",
764                                                                P_("Left Attach"),
765                                                                P_("The column number to attach the left side of the child to"),
766                                                                 -1, INT_MAX, -1,
767                                                                GTK_PARAM_READWRITE));
768
769  gtk_container_class_install_child_property (container_class,
770                                              CHILD_PROP_RIGHT_ATTACH,
771                                               g_param_spec_int ("right-attach",
772                                                                P_("Right Attach"),
773                                                                P_("The column number to attach the right side of the child to"),
774                                                                 -1, INT_MAX, -1,
775                                                                GTK_PARAM_READWRITE));
776
777  gtk_container_class_install_child_property (container_class,
778                                              CHILD_PROP_TOP_ATTACH,
779                                               g_param_spec_int ("top-attach",
780                                                                P_("Top Attach"),
781                                                                P_("The row number to attach the top of the child to"),
782                                                                 -1, INT_MAX, -1,
783                                                                GTK_PARAM_READWRITE));
784
785  gtk_container_class_install_child_property (container_class,
786                                              CHILD_PROP_BOTTOM_ATTACH,
787                                               g_param_spec_int ("bottom-attach",
788                                                                P_("Bottom Attach"),
789                                                                P_("The row number to attach the bottom of the child to"),
790                                                                 -1, INT_MAX, -1,
791                                                                GTK_PARAM_READWRITE));
792
793  /**
794   * GtkMenu:arrow-scaling
795   *
796   * Arbitrary constant to scale down the size of the scroll arrow.
797   *
798   * Since: 2.16
799   */
800   gtk_widget_class_install_style_property (widget_class,
801                                            g_param_spec_float ("arrow-scaling",
802                                                                P_("Arrow Scaling"),
803                                                                P_("Arbitrary constant to scale down the size of the scroll arrow"),
804                                                                0.0, 1.0, 0.7,
805                                                                GTK_PARAM_READABLE));
806
807   binding_set = gtk_binding_set_by_class (class);
808   gtk_binding_entry_add_signal (binding_set,
809                                 GDK_KEY_Up, 0,
810                                 I_("move-current"), 1,
811                                 GTK_TYPE_MENU_DIRECTION_TYPE,
812                                 GTK_MENU_DIR_PREV);
813   gtk_binding_entry_add_signal (binding_set,
814                                 GDK_KEY_KP_Up, 0,
815                                 "move-current", 1,
816                                 GTK_TYPE_MENU_DIRECTION_TYPE,
817                                 GTK_MENU_DIR_PREV);
818   gtk_binding_entry_add_signal (binding_set,
819                                 GDK_KEY_Down, 0,
820                                 "move-current", 1,
821                                 GTK_TYPE_MENU_DIRECTION_TYPE,
822                                 GTK_MENU_DIR_NEXT);
823   gtk_binding_entry_add_signal (binding_set,
824                                 GDK_KEY_KP_Down, 0,
825                                 "move-current", 1,
826                                 GTK_TYPE_MENU_DIRECTION_TYPE,
827                                 GTK_MENU_DIR_NEXT);
828   gtk_binding_entry_add_signal (binding_set,
829                                 GDK_KEY_Left, 0,
830                                 "move-current", 1,
831                                 GTK_TYPE_MENU_DIRECTION_TYPE,
832                                 GTK_MENU_DIR_PARENT);
833   gtk_binding_entry_add_signal (binding_set,
834                                 GDK_KEY_KP_Left, 0,
835                                 "move-current", 1,
836                                 GTK_TYPE_MENU_DIRECTION_TYPE,
837                                 GTK_MENU_DIR_PARENT);
838   gtk_binding_entry_add_signal (binding_set,
839                                 GDK_KEY_Right, 0,
840                                 "move-current", 1,
841                                 GTK_TYPE_MENU_DIRECTION_TYPE,
842                                 GTK_MENU_DIR_CHILD);
843   gtk_binding_entry_add_signal (binding_set,
844                                 GDK_KEY_KP_Right, 0,
845                                 "move-current", 1,
846                                 GTK_TYPE_MENU_DIRECTION_TYPE,
847                                 GTK_MENU_DIR_CHILD);
848   gtk_binding_entry_add_signal (binding_set,
849                                 GDK_KEY_Home, 0,
850                                 "move-scroll", 1,
851                                 GTK_TYPE_SCROLL_TYPE,
852                                 GTK_SCROLL_START);
853   gtk_binding_entry_add_signal (binding_set,
854                                 GDK_KEY_KP_Home, 0,
855                                 "move-scroll", 1,
856                                 GTK_TYPE_SCROLL_TYPE,
857                                 GTK_SCROLL_START);
858   gtk_binding_entry_add_signal (binding_set,
859                                 GDK_KEY_End, 0,
860                                 "move-scroll", 1,
861                                 GTK_TYPE_SCROLL_TYPE,
862                                 GTK_SCROLL_END);
863   gtk_binding_entry_add_signal (binding_set,
864                                 GDK_KEY_KP_End, 0,
865                                 "move-scroll", 1,
866                                 GTK_TYPE_SCROLL_TYPE,
867                                 GTK_SCROLL_END);
868   gtk_binding_entry_add_signal (binding_set,
869                                 GDK_KEY_Page_Up, 0,
870                                 "move-scroll", 1,
871                                 GTK_TYPE_SCROLL_TYPE,
872                                 GTK_SCROLL_PAGE_UP);
873   gtk_binding_entry_add_signal (binding_set,
874                                 GDK_KEY_KP_Page_Up, 0,
875                                 "move-scroll", 1,
876                                 GTK_TYPE_SCROLL_TYPE,
877                                 GTK_SCROLL_PAGE_UP);
878   gtk_binding_entry_add_signal (binding_set,
879                                 GDK_KEY_Page_Down, 0,
880                                 "move-scroll", 1,
881                                 GTK_TYPE_SCROLL_TYPE,
882                                 GTK_SCROLL_PAGE_DOWN);
883   gtk_binding_entry_add_signal (binding_set,
884                                 GDK_KEY_KP_Page_Down, 0,
885                                 "move-scroll", 1,
886                                 GTK_TYPE_SCROLL_TYPE,
887                                 GTK_SCROLL_PAGE_DOWN);
888
889   g_type_class_add_private (gobject_class, sizeof (GtkMenuPrivate));
890
891   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_MENU_ACCESSIBLE);
892 }
893
894
895 static void
896 gtk_menu_set_property (GObject      *object,
897                        guint         prop_id,
898                        const GValue *value,
899                        GParamSpec   *pspec)
900 {
901   GtkMenu *menu = GTK_MENU (object);
902
903   switch (prop_id)
904     {
905     case PROP_ACTIVE:
906       gtk_menu_set_active (menu, g_value_get_int (value));
907       break;
908     case PROP_ACCEL_GROUP:
909       gtk_menu_set_accel_group (menu, g_value_get_object (value));
910       break;
911     case PROP_ACCEL_PATH:
912       gtk_menu_set_accel_path (menu, g_value_get_string (value));
913       break;
914     case PROP_ATTACH_WIDGET:
915       {
916         GtkWidget *widget;
917
918         widget = gtk_menu_get_attach_widget (menu);
919         if (widget)
920           gtk_menu_detach (menu);
921
922         widget = (GtkWidget*) g_value_get_object (value); 
923         if (widget)
924           gtk_menu_attach_to_widget (menu, widget, NULL);
925       }
926       break;
927     case PROP_TEAROFF_STATE:
928       gtk_menu_set_tearoff_state (menu, g_value_get_boolean (value));
929       break;
930     case PROP_TEAROFF_TITLE:
931       gtk_menu_set_title (menu, g_value_get_string (value));
932       break;
933     case PROP_MONITOR:
934       gtk_menu_set_monitor (menu, g_value_get_int (value));
935       break;
936     case PROP_RESERVE_TOGGLE_SIZE:
937       gtk_menu_set_reserve_toggle_size (menu, g_value_get_boolean (value));
938       break;
939     default:
940       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
941       break;
942     }
943 }
944
945 static void
946 gtk_menu_get_property (GObject     *object,
947                        guint        prop_id,
948                        GValue      *value,
949                        GParamSpec  *pspec)
950 {
951   GtkMenu *menu = GTK_MENU (object);
952
953   switch (prop_id)
954     {
955     case PROP_ACTIVE:
956       g_value_set_int (value, g_list_index (GTK_MENU_SHELL (menu)->priv->children, gtk_menu_get_active (menu)));
957       break;
958     case PROP_ACCEL_GROUP:
959       g_value_set_object (value, gtk_menu_get_accel_group (menu));
960       break;
961     case PROP_ACCEL_PATH:
962       g_value_set_string (value, gtk_menu_get_accel_path (menu));
963       break;
964     case PROP_ATTACH_WIDGET:
965       g_value_set_object (value, gtk_menu_get_attach_widget (menu));
966       break;
967     case PROP_TEAROFF_STATE:
968       g_value_set_boolean (value, gtk_menu_get_tearoff_state (menu));
969       break;
970     case PROP_TEAROFF_TITLE:
971       g_value_set_string (value, gtk_menu_get_title (menu));
972       break;
973     case PROP_MONITOR:
974       g_value_set_int (value, gtk_menu_get_monitor (menu));
975       break;
976     case PROP_RESERVE_TOGGLE_SIZE:
977       g_value_set_boolean (value, gtk_menu_get_reserve_toggle_size (menu));
978       break;
979     default:
980       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
981       break;
982     }
983 }
984
985 static void
986 gtk_menu_set_child_property (GtkContainer *container,
987                              GtkWidget    *child,
988                              guint         property_id,
989                              const GValue *value,
990                              GParamSpec   *pspec)
991 {
992   GtkMenu *menu = GTK_MENU (container);
993   AttachInfo *ai = get_attach_info (child);
994
995   switch (property_id)
996     {
997     case CHILD_PROP_LEFT_ATTACH:
998       ai->left_attach = g_value_get_int (value);
999       break;
1000     case CHILD_PROP_RIGHT_ATTACH:
1001       ai->right_attach = g_value_get_int (value);
1002       break;
1003     case CHILD_PROP_TOP_ATTACH:
1004       ai->top_attach = g_value_get_int (value);
1005       break;
1006     case CHILD_PROP_BOTTOM_ATTACH:
1007       ai->bottom_attach = g_value_get_int (value);
1008       break;
1009
1010     default:
1011       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
1012       return;
1013     }
1014
1015   menu_queue_resize (menu);
1016 }
1017
1018 static void
1019 gtk_menu_get_child_property (GtkContainer *container,
1020                              GtkWidget    *child,
1021                              guint         property_id,
1022                              GValue       *value,
1023                              GParamSpec   *pspec)
1024 {
1025   AttachInfo *ai = get_attach_info (child);
1026
1027   switch (property_id)
1028     {
1029     case CHILD_PROP_LEFT_ATTACH:
1030       g_value_set_int (value, ai->left_attach);
1031       break;
1032     case CHILD_PROP_RIGHT_ATTACH:
1033       g_value_set_int (value, ai->right_attach);
1034       break;
1035     case CHILD_PROP_TOP_ATTACH:
1036       g_value_set_int (value, ai->top_attach);
1037       break;
1038     case CHILD_PROP_BOTTOM_ATTACH:
1039       g_value_set_int (value, ai->bottom_attach);
1040       break;
1041       
1042     default:
1043       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
1044       return;
1045     }
1046 }
1047
1048 static gboolean
1049 gtk_menu_window_event (GtkWidget *window,
1050                        GdkEvent  *event,
1051                        GtkWidget *menu)
1052 {
1053   gboolean handled = FALSE;
1054
1055   g_object_ref (window);
1056   g_object_ref (menu);
1057
1058   switch (event->type)
1059     {
1060     case GDK_KEY_PRESS:
1061     case GDK_KEY_RELEASE:
1062       handled = gtk_widget_event (menu, event);
1063       break;
1064     default:
1065       break;
1066     }
1067
1068   g_object_unref (window);
1069   g_object_unref (menu);
1070
1071   return handled;
1072 }
1073
1074 static void
1075 gtk_menu_init (GtkMenu *menu)
1076 {
1077   GtkMenuPrivate *priv;
1078   GtkStyleContext *context;
1079
1080   priv = G_TYPE_INSTANCE_GET_PRIVATE (menu, GTK_TYPE_MENU, GtkMenuPrivate);
1081
1082   menu->priv = priv;
1083
1084   priv->toplevel = g_object_connect (g_object_new (GTK_TYPE_WINDOW,
1085                                                    "type", GTK_WINDOW_POPUP,
1086                                                    "child", menu,
1087                                                    NULL),
1088                                      "signal::event", gtk_menu_window_event, menu,
1089                                      "signal::destroy", gtk_widget_destroyed, &priv->toplevel,
1090                                      NULL);
1091   gtk_window_set_resizable (GTK_WINDOW (priv->toplevel), FALSE);
1092   gtk_window_set_mnemonic_modifier (GTK_WINDOW (priv->toplevel), 0);
1093
1094   /* Refloat the menu, so that reference counting for the menu isn't
1095    * affected by it being a child of the toplevel
1096    */
1097   g_object_force_floating (G_OBJECT (menu));
1098   priv->needs_destruction_ref = TRUE;
1099
1100   priv->monitor_num = -1;
1101
1102   context = gtk_widget_get_style_context (GTK_WIDGET (menu));
1103   gtk_style_context_add_class (context, GTK_STYLE_CLASS_MENU);
1104 }
1105
1106 static void
1107 gtk_menu_destroy (GtkWidget *widget)
1108 {
1109   GtkMenu *menu = GTK_MENU (widget);
1110   GtkMenuPrivate *priv = menu->priv;
1111   GtkMenuAttachData *data;
1112
1113   gtk_menu_remove_scroll_timeout (menu);
1114
1115   data = g_object_get_data (G_OBJECT (widget), attach_data_key);
1116   if (data)
1117     gtk_menu_detach (menu);
1118
1119   gtk_menu_stop_navigating_submenu (menu);
1120
1121   if (priv->old_active_menu_item)
1122     g_clear_object (&priv->old_active_menu_item);
1123
1124   /* Add back the reference count for being a child */
1125   if (priv->needs_destruction_ref)
1126     {
1127       priv->needs_destruction_ref = FALSE;
1128       g_object_ref (widget);
1129     }
1130
1131   if (priv->accel_group)
1132     g_clear_object (&priv->accel_group);
1133
1134   if (priv->toplevel)
1135     gtk_widget_destroy (priv->toplevel);
1136
1137   if (priv->tearoff_window)
1138     gtk_widget_destroy (priv->tearoff_window);
1139
1140   if (priv->heights)
1141     {
1142       g_free (priv->heights);
1143       priv->heights = NULL;
1144     }
1145
1146   if (priv->title)
1147     {
1148       g_free (priv->title);
1149       priv->title = NULL;
1150     }
1151
1152   if (priv->position_func_data_destroy)
1153     {
1154       priv->position_func_data_destroy (priv->position_func_data);
1155       priv->position_func_data = NULL;
1156       priv->position_func_data_destroy = NULL;
1157     }
1158
1159   GTK_WIDGET_CLASS (gtk_menu_parent_class)->destroy (widget);
1160 }
1161
1162 static void
1163 menu_change_screen (GtkMenu   *menu,
1164                     GdkScreen *new_screen)
1165 {
1166   GtkMenuPrivate *priv = menu->priv;
1167
1168   if (gtk_widget_has_screen (GTK_WIDGET (menu)))
1169     {
1170       if (new_screen == gtk_widget_get_screen (GTK_WIDGET (menu)))
1171         return;
1172     }
1173
1174   if (priv->torn_off)
1175     {
1176       gtk_window_set_screen (GTK_WINDOW (priv->tearoff_window), new_screen);
1177       gtk_menu_position (menu, TRUE);
1178     }
1179
1180   gtk_window_set_screen (GTK_WINDOW (priv->toplevel), new_screen);
1181   priv->monitor_num = -1;
1182 }
1183
1184 static void
1185 attach_widget_screen_changed (GtkWidget *attach_widget,
1186                               GdkScreen *previous_screen,
1187                               GtkMenu   *menu)
1188 {
1189   if (gtk_widget_has_screen (attach_widget) &&
1190       !g_object_get_data (G_OBJECT (menu), "gtk-menu-explicit-screen"))
1191     menu_change_screen (menu, gtk_widget_get_screen (attach_widget));
1192 }
1193
1194 /**
1195  * gtk_menu_attach_to_widget:
1196  * @menu: a #GtkMenu
1197  * @attach_widget: the #GtkWidget that the menu will be attached to
1198  * @detacher: (scope async)(allow-none): the user supplied callback function
1199  *             that will be called when the menu calls gtk_menu_detach()
1200  *
1201  * Attaches the menu to the widget and provides a callback function
1202  * that will be invoked when the menu calls gtk_menu_detach() during
1203  * its destruction.
1204  */
1205 void
1206 gtk_menu_attach_to_widget (GtkMenu           *menu,
1207                            GtkWidget         *attach_widget,
1208                            GtkMenuDetachFunc  detacher)
1209 {
1210   GtkMenuAttachData *data;
1211   GList *list;
1212
1213   g_return_if_fail (GTK_IS_MENU (menu));
1214   g_return_if_fail (GTK_IS_WIDGET (attach_widget));
1215
1216   /* keep this function in sync with gtk_widget_set_parent() */
1217   data = g_object_get_data (G_OBJECT (menu), attach_data_key);
1218   if (data)
1219     {
1220       g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
1221                  g_type_name (G_TYPE_FROM_INSTANCE (data->attach_widget)));
1222      return;
1223     }
1224
1225   g_object_ref_sink (menu);
1226
1227   data = g_slice_new (GtkMenuAttachData);
1228   data->attach_widget = attach_widget;
1229
1230   g_signal_connect (attach_widget, "screen-changed",
1231                     G_CALLBACK (attach_widget_screen_changed), menu);
1232   attach_widget_screen_changed (attach_widget, NULL, menu);
1233
1234   data->detacher = detacher;
1235   g_object_set_data (G_OBJECT (menu), I_(attach_data_key), data);
1236   list = g_object_steal_data (G_OBJECT (attach_widget), ATTACHED_MENUS);
1237   if (!g_list_find (list, menu))
1238     list = g_list_prepend (list, menu);
1239
1240   g_object_set_data_full (G_OBJECT (attach_widget), I_(ATTACHED_MENUS), list,
1241                           (GDestroyNotify) g_list_free);
1242
1243   if (gtk_widget_get_state_flags (GTK_WIDGET (menu)) != 0)
1244     gtk_widget_set_state_flags (GTK_WIDGET (menu), 0, TRUE);
1245
1246   /* we don't need to set the style here, since
1247    * we are a toplevel widget.
1248    */
1249
1250   /* Fallback title for menu comes from attach widget */
1251   gtk_menu_update_title (menu);
1252
1253   g_object_notify (G_OBJECT (menu), "attach-widget");
1254 }
1255
1256 /**
1257  * gtk_menu_get_attach_widget:
1258  * @menu: a #GtkMenu
1259  *
1260  * Returns the #GtkWidget that the menu is attached to.
1261  *
1262  * Returns: (transfer none): the #GtkWidget that the menu is attached to
1263  */
1264 GtkWidget*
1265 gtk_menu_get_attach_widget (GtkMenu *menu)
1266 {
1267   GtkMenuAttachData *data;
1268
1269   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
1270
1271   data = g_object_get_data (G_OBJECT (menu), attach_data_key);
1272   if (data)
1273     return data->attach_widget;
1274   return NULL;
1275 }
1276
1277 /**
1278  * gtk_menu_detach:
1279  * @menu: a #GtkMenu
1280  *
1281  * Detaches the menu from the widget to which it had been attached.
1282  * This function will call the callback function, @detacher, provided
1283  * when the gtk_menu_attach_to_widget() function was called.
1284  */
1285 void
1286 gtk_menu_detach (GtkMenu *menu)
1287 {
1288   GtkMenuAttachData *data;
1289   GList *list;
1290
1291   g_return_if_fail (GTK_IS_MENU (menu));
1292
1293   /* keep this function in sync with gtk_widget_unparent() */
1294   data = g_object_get_data (G_OBJECT (menu), attach_data_key);
1295   if (!data)
1296     {
1297       g_warning ("gtk_menu_detach(): menu is not attached");
1298       return;
1299     }
1300   g_object_set_data (G_OBJECT (menu), I_(attach_data_key), NULL);
1301
1302   g_signal_handlers_disconnect_by_func (data->attach_widget,
1303                                         (gpointer) attach_widget_screen_changed,
1304                                         menu);
1305
1306   if (data->detacher)
1307     data->detacher (data->attach_widget, menu);
1308   list = g_object_steal_data (G_OBJECT (data->attach_widget), ATTACHED_MENUS);
1309   list = g_list_remove (list, menu);
1310   if (list)
1311     g_object_set_data_full (G_OBJECT (data->attach_widget), I_(ATTACHED_MENUS), list,
1312                             (GDestroyNotify) g_list_free);
1313   else
1314     g_object_set_data (G_OBJECT (data->attach_widget), I_(ATTACHED_MENUS), NULL);
1315
1316   if (gtk_widget_get_realized (GTK_WIDGET (menu)))
1317     gtk_widget_unrealize (GTK_WIDGET (menu));
1318
1319   g_slice_free (GtkMenuAttachData, data);
1320
1321   /* Fallback title for menu comes from attach widget */
1322   gtk_menu_update_title (menu);
1323
1324   g_object_unref (menu);
1325 }
1326
1327 static void
1328 gtk_menu_remove (GtkContainer *container,
1329                  GtkWidget    *widget)
1330 {
1331   GtkMenu *menu = GTK_MENU (container);
1332   GtkMenuPrivate *priv = menu->priv;
1333
1334   /* Clear out old_active_menu_item if it matches the item we are removing */
1335   if (priv->old_active_menu_item == widget)
1336     g_clear_object (&priv->old_active_menu_item);
1337
1338   GTK_CONTAINER_CLASS (gtk_menu_parent_class)->remove (container, widget);
1339
1340   g_object_set_data (G_OBJECT (widget), I_(ATTACH_INFO_KEY), NULL);
1341
1342   menu_queue_resize (menu);
1343 }
1344
1345 /**
1346  * gtk_menu_new:
1347  *
1348  * Creates a new #GtkMenu
1349  *
1350  * Returns: a new #GtkMenu
1351  */
1352 GtkWidget*
1353 gtk_menu_new (void)
1354 {
1355   return g_object_new (GTK_TYPE_MENU, NULL);
1356 }
1357
1358 static void
1359 gtk_menu_real_insert (GtkMenuShell *menu_shell,
1360                       GtkWidget    *child,
1361                       gint          position)
1362 {
1363   GtkMenu *menu = GTK_MENU (menu_shell);
1364   GtkMenuPrivate *priv = menu->priv;
1365   AttachInfo *ai = get_attach_info (child);
1366
1367   ai->left_attach = -1;
1368   ai->right_attach = -1;
1369   ai->top_attach = -1;
1370   ai->bottom_attach = -1;
1371
1372   if (gtk_widget_get_realized (GTK_WIDGET (menu_shell)))
1373     gtk_widget_set_parent_window (child, priv->bin_window);
1374
1375   GTK_MENU_SHELL_CLASS (gtk_menu_parent_class)->insert (menu_shell, child, position);
1376
1377   menu_queue_resize (menu);
1378 }
1379
1380 static void
1381 gtk_menu_tearoff_bg_copy (GtkMenu *menu)
1382 {
1383   GtkMenuPrivate *priv = menu->priv;
1384   gint width, height;
1385
1386   if (priv->torn_off)
1387     {
1388       GdkWindow *window;
1389       cairo_surface_t *surface;
1390       cairo_pattern_t *pattern;
1391       cairo_t *cr;
1392
1393       priv->tearoff_active = FALSE;
1394       priv->saved_scroll_offset = priv->scroll_offset;
1395
1396       window = gtk_widget_get_window (priv->tearoff_window);
1397       width = gdk_window_get_width (window);
1398       height = gdk_window_get_height (window);
1399
1400       surface = gdk_window_create_similar_surface (window,
1401                                                    CAIRO_CONTENT_COLOR,
1402                                                    width,
1403                                                    height);
1404
1405       cr = cairo_create (surface);
1406       gdk_cairo_set_source_window (cr, window, 0, 0);
1407       cairo_paint (cr);
1408       cairo_destroy (cr);
1409
1410       gtk_widget_set_size_request (priv->tearoff_window, width, height);
1411
1412       pattern = cairo_pattern_create_for_surface (surface);
1413       gdk_window_set_background_pattern (window, pattern);
1414
1415       cairo_pattern_destroy (pattern);
1416       cairo_surface_destroy (surface);
1417     }
1418 }
1419
1420 static gboolean
1421 popup_grab_on_window (GdkWindow *window,
1422                       GdkDevice *keyboard,
1423                       GdkDevice *pointer,
1424                       guint32    activate_time)
1425 {
1426   if (keyboard &&
1427       gdk_device_grab (keyboard, window,
1428                        GDK_OWNERSHIP_WINDOW, TRUE,
1429                        GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
1430                        NULL, activate_time) != GDK_GRAB_SUCCESS)
1431     return FALSE;
1432
1433   if (pointer &&
1434       gdk_device_grab (pointer, window,
1435                        GDK_OWNERSHIP_WINDOW, TRUE,
1436                        GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1437                        GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
1438                        GDK_POINTER_MOTION_MASK,
1439                        NULL, activate_time) != GDK_GRAB_SUCCESS)
1440     {
1441       if (keyboard)
1442         gdk_device_ungrab (keyboard, activate_time);
1443
1444       return FALSE;
1445     }
1446
1447   return TRUE;
1448 }
1449
1450 /**
1451  * gtk_menu_popup_for_device:
1452  * @menu: a #GtkMenu
1453  * @device: (allow-none): a #GdkDevice
1454  * @parent_menu_shell: (allow-none): the menu shell containing the triggering
1455  *     menu item, or %NULL
1456  * @parent_menu_item: (allow-none): the menu item whose activation triggered
1457  *     the popup, or %NULL
1458  * @func: (allow-none): a user supplied function used to position the menu,
1459  *     or %NULL
1460  * @data: (allow-none): user supplied data to be passed to @func
1461  * @destroy: (allow-none): destroy notify for @data
1462  * @button: the mouse button which was pressed to initiate the event
1463  * @activate_time: the time at which the activation event occurred
1464  *
1465  * Displays a menu and makes it available for selection.
1466  *
1467  * Applications can use this function to display context-sensitive menus,
1468  * and will typically supply %NULL for the @parent_menu_shell,
1469  * @parent_menu_item, @func, @data and @destroy parameters. The default
1470  * menu positioning function will position the menu at the current position
1471  * of @device (or its corresponding pointer).
1472  *
1473  * The @button parameter should be the mouse button pressed to initiate
1474  * the menu popup. If the menu popup was initiated by something other than
1475  * a mouse button press, such as a mouse button release or a keypress,
1476  * @button should be 0.
1477  *
1478  * The @activate_time parameter is used to conflict-resolve initiation of
1479  * concurrent requests for mouse/keyboard grab requests. To function
1480  * properly, this needs to be the time stamp of the user event (such as
1481  * a mouse click or key press) that caused the initiation of the popup.
1482  * Only if no such event is available, gtk_get_current_event_time() can
1483  * be used instead.
1484  *
1485  * Since: 3.0
1486  */
1487 void
1488 gtk_menu_popup_for_device (GtkMenu             *menu,
1489                            GdkDevice           *device,
1490                            GtkWidget           *parent_menu_shell,
1491                            GtkWidget           *parent_menu_item,
1492                            GtkMenuPositionFunc  func,
1493                            gpointer             data,
1494                            GDestroyNotify       destroy,
1495                            guint                button,
1496                            guint32              activate_time)
1497 {
1498   GtkMenuPrivate *priv = menu->priv;
1499   GtkWidget *widget;
1500   GtkWidget *xgrab_shell;
1501   GtkWidget *parent;
1502   GdkEvent *current_event;
1503   GtkMenuShell *menu_shell;
1504   gboolean grab_keyboard;
1505   GtkWidget *parent_toplevel;
1506   GdkDevice *keyboard, *pointer;
1507
1508   g_return_if_fail (GTK_IS_MENU (menu));
1509   g_return_if_fail (device == NULL || GDK_IS_DEVICE (device));
1510
1511   if (device == NULL)
1512     device = gtk_get_current_event_device ();
1513
1514   if (device == NULL)
1515     {
1516       GdkDisplay *display;
1517       GdkDeviceManager *device_manager;
1518       GList *devices;
1519
1520       display = gtk_widget_get_display (GTK_WIDGET (menu));
1521       device_manager = gdk_display_get_device_manager (display);
1522       devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_MASTER);
1523
1524       device = devices->data;
1525
1526       g_list_free (devices);
1527     }
1528
1529   widget = GTK_WIDGET (menu);
1530   menu_shell = GTK_MENU_SHELL (menu);
1531
1532   if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
1533     {
1534       keyboard = device;
1535       pointer = gdk_device_get_associated_device (device);
1536     }
1537   else
1538     {
1539       pointer = device;
1540       keyboard = gdk_device_get_associated_device (device);
1541     }
1542
1543   menu_shell->priv->parent_menu_shell = parent_menu_shell;
1544
1545   priv->seen_item_enter = FALSE;
1546
1547   /* Find the last viewable ancestor, and make an X grab on it
1548    */
1549   parent = GTK_WIDGET (menu);
1550   xgrab_shell = NULL;
1551   while (parent)
1552     {
1553       gboolean viewable = TRUE;
1554       GtkWidget *tmp = parent;
1555
1556       while (tmp)
1557         {
1558           if (!gtk_widget_get_mapped (tmp))
1559             {
1560               viewable = FALSE;
1561               break;
1562             }
1563           tmp = gtk_widget_get_parent (tmp);
1564         }
1565
1566       if (viewable)
1567         xgrab_shell = parent;
1568
1569       parent = GTK_MENU_SHELL (parent)->priv->parent_menu_shell;
1570     }
1571
1572   /* We want to receive events generated when we map the menu;
1573    * unfortunately, since there is probably already an implicit
1574    * grab in place from the button that the user used to pop up
1575    * the menu, we won't receive then -- in particular, the EnterNotify
1576    * when the menu pops up under the pointer.
1577    *
1578    * If we are grabbing on a parent menu shell, no problem; just grab
1579    * on that menu shell first before popping up the window with
1580    * owner_events = TRUE.
1581    *
1582    * When grabbing on the menu itself, things get more convoluted --
1583    * we do an explicit grab on a specially created window with
1584    * owner_events = TRUE, which we override further down with a
1585    * grab on the menu. (We can't grab on the menu until it is mapped;
1586    * we probably could just leave the grab on the other window,
1587    * with a little reorganization of the code in gtkmenu*).
1588    */
1589   grab_keyboard = gtk_menu_shell_get_take_focus (menu_shell);
1590   gtk_window_set_accept_focus (GTK_WINDOW (priv->toplevel), grab_keyboard);
1591
1592   if (!grab_keyboard)
1593     keyboard = NULL;
1594
1595   if (xgrab_shell && xgrab_shell != widget)
1596     {
1597       if (popup_grab_on_window (gtk_widget_get_window (xgrab_shell), keyboard, pointer, activate_time))
1598         {
1599           _gtk_menu_shell_set_grab_device (GTK_MENU_SHELL (xgrab_shell), pointer);
1600           GTK_MENU_SHELL (xgrab_shell)->priv->have_xgrab = TRUE;
1601         }
1602     }
1603   else
1604     {
1605       GdkWindow *transfer_window;
1606
1607       xgrab_shell = widget;
1608       transfer_window = menu_grab_transfer_window_get (menu);
1609       if (popup_grab_on_window (transfer_window, keyboard, pointer, activate_time))
1610         {
1611           _gtk_menu_shell_set_grab_device (GTK_MENU_SHELL (xgrab_shell), pointer);
1612           GTK_MENU_SHELL (xgrab_shell)->priv->have_xgrab = TRUE;
1613         }
1614     }
1615
1616   if (!GTK_MENU_SHELL (xgrab_shell)->priv->have_xgrab)
1617     {
1618       /* We failed to make our pointer/keyboard grab.
1619        * Rather than leaving the user with a stuck up window,
1620        * we just abort here. Presumably the user will try again.
1621        */
1622       menu_shell->priv->parent_menu_shell = NULL;
1623       menu_grab_transfer_window_destroy (menu);
1624       return;
1625     }
1626
1627   _gtk_menu_shell_set_grab_device (GTK_MENU_SHELL (menu), pointer);
1628   menu_shell->priv->active = TRUE;
1629   menu_shell->priv->button = button;
1630
1631   /* If we are popping up the menu from something other than, a button
1632    * press then, as a heuristic, we ignore enter events for the menu
1633    * until we get a MOTION_NOTIFY.
1634    */
1635
1636   current_event = gtk_get_current_event ();
1637   if (current_event)
1638     {
1639       if ((current_event->type != GDK_BUTTON_PRESS) &&
1640           (current_event->type != GDK_ENTER_NOTIFY))
1641         menu_shell->priv->ignore_enter = TRUE;
1642
1643       gdk_event_free (current_event);
1644     }
1645   else
1646     menu_shell->priv->ignore_enter = TRUE;
1647
1648   if (priv->torn_off)
1649     {
1650       gtk_menu_tearoff_bg_copy (menu);
1651
1652       gtk_menu_reparent (menu, priv->toplevel, FALSE);
1653     }
1654
1655   parent_toplevel = NULL;
1656   if (parent_menu_shell)
1657     parent_toplevel = gtk_widget_get_toplevel (parent_menu_shell);
1658   else if (!g_object_get_data (G_OBJECT (menu), "gtk-menu-explicit-screen"))
1659     {
1660       GtkWidget *attach_widget = gtk_menu_get_attach_widget (menu);
1661       if (attach_widget)
1662         parent_toplevel = gtk_widget_get_toplevel (attach_widget);
1663     }
1664
1665   /* Set transient for to get the right window group and parent */
1666   if (GTK_IS_WINDOW (parent_toplevel))
1667     gtk_window_set_transient_for (GTK_WINDOW (priv->toplevel),
1668                                   GTK_WINDOW (parent_toplevel));
1669
1670   priv->parent_menu_item = parent_menu_item;
1671   priv->position_func = func;
1672   priv->position_func_data = data;
1673   priv->position_func_data_destroy = destroy;
1674   menu_shell->priv->activate_time = activate_time;
1675
1676   /* We need to show the menu here rather in the init function
1677    * because code expects to be able to tell if the menu is onscreen
1678    * by looking at gtk_widget_get_visible (menu)
1679    */
1680   gtk_widget_show (GTK_WIDGET (menu));
1681
1682   /* Position the menu, possibly changing the size request
1683    */
1684   gtk_menu_position (menu, TRUE);
1685
1686   /* Compute the size of the toplevel and realize it so we
1687    * can scroll correctly.
1688    */
1689   if (!gtk_widget_get_realized (GTK_WIDGET (menu)))
1690   {
1691     GtkRequisition tmp_request;
1692     GtkAllocation tmp_allocation = { 0, };
1693
1694     /* Instead of trusting the menu position function to queue a
1695      * resize when the menu goes out of bounds, invalidate the cached
1696      * size here.
1697      */
1698     gtk_widget_queue_resize (GTK_WIDGET (menu));
1699     gtk_widget_get_preferred_size (priv->toplevel, &tmp_request, NULL);
1700
1701     tmp_allocation.width = tmp_request.width;
1702     tmp_allocation.height = tmp_request.height;
1703
1704     gtk_widget_size_allocate (priv->toplevel, &tmp_allocation);
1705
1706     gtk_widget_realize (priv->toplevel);
1707   }
1708
1709   gtk_menu_scroll_to (menu, priv->scroll_offset);
1710
1711   /* if no item is selected, select the first one */
1712   if (!menu_shell->priv->active_menu_item)
1713     {
1714       gboolean touchscreen_mode;
1715
1716       g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
1717                     "gtk-touchscreen-mode", &touchscreen_mode,
1718                     NULL);
1719
1720       if (touchscreen_mode)
1721         gtk_menu_shell_select_first (menu_shell, TRUE);
1722     }
1723
1724   /* Once everything is set up correctly, map the toplevel */
1725   gtk_widget_show (priv->toplevel);
1726
1727   if (xgrab_shell == widget)
1728     popup_grab_on_window (gtk_widget_get_window (widget), keyboard, pointer, activate_time); /* Should always succeed */
1729
1730   gtk_device_grab_add (GTK_WIDGET (menu), pointer, TRUE);
1731
1732   if (parent_menu_shell)
1733     {
1734       gboolean keyboard_mode;
1735
1736       keyboard_mode = _gtk_menu_shell_get_keyboard_mode (GTK_MENU_SHELL (parent_menu_shell));
1737       _gtk_menu_shell_set_keyboard_mode (menu_shell, keyboard_mode);
1738     }
1739   else if (menu_shell->priv->button == 0) /* a keynav-activated context menu */
1740     _gtk_menu_shell_set_keyboard_mode (menu_shell, TRUE);
1741
1742   _gtk_menu_shell_update_mnemonics (menu_shell);
1743 }
1744
1745 /**
1746  * gtk_menu_popup: (skip)
1747  * @menu: a #GtkMenu
1748  * @parent_menu_shell: (allow-none): the menu shell containing the
1749  *     triggering menu item, or %NULL
1750  * @parent_menu_item: (allow-none): the menu item whose activation
1751  *     triggered the popup, or %NULL
1752  * @func: (allow-none): a user supplied function used to position
1753  *     the menu, or %NULL
1754  * @data: user supplied data to be passed to @func.
1755  * @button: the mouse button which was pressed to initiate the event.
1756  * @activate_time: the time at which the activation event occurred.
1757  *
1758  * Displays a menu and makes it available for selection.
1759  *
1760  * Applications can use this function to display context-sensitive
1761  * menus, and will typically supply %NULL for the @parent_menu_shell,
1762  * @parent_menu_item, @func and @data parameters. The default menu
1763  * positioning function will position the menu at the current mouse
1764  * cursor position.
1765  *
1766  * The @button parameter should be the mouse button pressed to initiate
1767  * the menu popup. If the menu popup was initiated by something other
1768  * than a mouse button press, such as a mouse button release or a keypress,
1769  * @button should be 0.
1770  *
1771  * The @activate_time parameter is used to conflict-resolve initiation
1772  * of concurrent requests for mouse/keyboard grab requests. To function
1773  * properly, this needs to be the timestamp of the user event (such as
1774  * a mouse click or key press) that caused the initiation of the popup.
1775  * Only if no such event is available, gtk_get_current_event_time() can
1776  * be used instead.
1777  */
1778 void
1779 gtk_menu_popup (GtkMenu             *menu,
1780                 GtkWidget           *parent_menu_shell,
1781                 GtkWidget           *parent_menu_item,
1782                 GtkMenuPositionFunc  func,
1783                 gpointer             data,
1784                 guint                button,
1785                 guint32              activate_time)
1786 {
1787   g_return_if_fail (GTK_IS_MENU (menu));
1788
1789   gtk_menu_popup_for_device (menu,
1790                              NULL,
1791                              parent_menu_shell,
1792                              parent_menu_item,
1793                              func, data, NULL,
1794                              button, activate_time);
1795 }
1796
1797 /**
1798  * gtk_menu_popdown:
1799  * @menu: a #GtkMenu
1800  *
1801  * Removes the menu from the screen.
1802  */
1803 void
1804 gtk_menu_popdown (GtkMenu *menu)
1805 {
1806   GtkMenuPrivate *priv;
1807   GtkMenuShell *menu_shell;
1808   GdkDevice *pointer;
1809
1810   g_return_if_fail (GTK_IS_MENU (menu));
1811
1812   menu_shell = GTK_MENU_SHELL (menu);
1813   priv = menu->priv;
1814
1815   menu_shell->priv->parent_menu_shell = NULL;
1816   menu_shell->priv->active = FALSE;
1817   menu_shell->priv->ignore_enter = FALSE;
1818
1819   priv->have_position = FALSE;
1820
1821   gtk_menu_stop_scrolling (menu);
1822   gtk_menu_stop_navigating_submenu (menu);
1823
1824   if (menu_shell->priv->active_menu_item)
1825     {
1826       if (priv->old_active_menu_item)
1827         g_object_unref (priv->old_active_menu_item);
1828       priv->old_active_menu_item = menu_shell->priv->active_menu_item;
1829       g_object_ref (priv->old_active_menu_item);
1830     }
1831
1832   gtk_menu_shell_deselect (menu_shell);
1833
1834   /* The X Grab, if present, will automatically be removed
1835    * when we hide the window
1836    */
1837   if (priv->toplevel)
1838     {
1839       gtk_widget_hide (priv->toplevel);
1840       gtk_window_set_transient_for (GTK_WINDOW (priv->toplevel), NULL);
1841     }
1842
1843   pointer = _gtk_menu_shell_get_grab_device (menu_shell);
1844
1845   if (priv->torn_off)
1846     {
1847       gtk_widget_set_size_request (priv->tearoff_window, -1, -1);
1848
1849       if (gtk_bin_get_child (GTK_BIN (priv->toplevel)))
1850         {
1851           gtk_menu_reparent (menu, priv->tearoff_hbox, TRUE);
1852         }
1853       else
1854         {
1855           /* We popped up the menu from the tearoff, so we need to
1856            * release the grab - we aren't actually hiding the menu.
1857            */
1858           if (menu_shell->priv->have_xgrab && pointer)
1859             {
1860               GdkDevice *keyboard;
1861
1862               gdk_device_ungrab (pointer, GDK_CURRENT_TIME);
1863               keyboard = gdk_device_get_associated_device (pointer);
1864
1865               if (keyboard)
1866                 gdk_device_ungrab (keyboard, GDK_CURRENT_TIME);
1867             }
1868         }
1869
1870       /* gtk_menu_popdown is called each time a menu item is selected from
1871        * a torn off menu. Only scroll back to the saved position if the
1872        * non-tearoff menu was popped down.
1873        */
1874       if (!priv->tearoff_active)
1875         gtk_menu_scroll_to (menu, priv->saved_scroll_offset);
1876       priv->tearoff_active = TRUE;
1877     }
1878   else
1879     gtk_widget_hide (GTK_WIDGET (menu));
1880
1881   menu_shell->priv->have_xgrab = FALSE;
1882
1883   if (pointer)
1884     gtk_device_grab_remove (GTK_WIDGET (menu), pointer);
1885
1886   _gtk_menu_shell_set_grab_device (menu_shell, NULL);
1887
1888   menu_grab_transfer_window_destroy (menu);
1889 }
1890
1891 /**
1892  * gtk_menu_get_active:
1893  * @menu: a #GtkMenu
1894  *
1895  * Returns the selected menu item from the menu.  This is used by the
1896  * #GtkOptionMenu.
1897  *
1898  * Returns: (transfer none): the #GtkMenuItem that was last selected
1899  *          in the menu.  If a selection has not yet been made, the
1900  *          first menu item is selected.
1901  */
1902 GtkWidget*
1903 gtk_menu_get_active (GtkMenu *menu)
1904 {
1905   GtkMenuPrivate *priv = menu->priv;
1906   GtkWidget *child;
1907   GList *children;
1908
1909   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
1910
1911   if (!priv->old_active_menu_item)
1912     {
1913       child = NULL;
1914       children = GTK_MENU_SHELL (menu)->priv->children;
1915
1916       while (children)
1917         {
1918           child = children->data;
1919           children = children->next;
1920
1921           if (gtk_bin_get_child (GTK_BIN (child)))
1922             break;
1923           child = NULL;
1924         }
1925
1926       priv->old_active_menu_item = child;
1927       if (priv->old_active_menu_item)
1928         g_object_ref (priv->old_active_menu_item);
1929     }
1930
1931   return priv->old_active_menu_item;
1932 }
1933
1934 /**
1935  * gtk_menu_set_active:
1936  * @menu: a #GtkMenu
1937  * @index: the index of the menu item to select.  Iindex values are
1938  *         from 0 to n-1
1939  *
1940  * Selects the specified menu item within the menu.  This is used by
1941  * the #GtkOptionMenu and should not be used by anyone else.
1942  */
1943 void
1944 gtk_menu_set_active (GtkMenu *menu,
1945                      guint    index)
1946 {
1947   GtkMenuPrivate *priv = menu->priv;
1948   GtkWidget *child;
1949   GList *tmp_list;
1950
1951   g_return_if_fail (GTK_IS_MENU (menu));
1952
1953   tmp_list = g_list_nth (GTK_MENU_SHELL (menu)->priv->children, index);
1954   if (tmp_list)
1955     {
1956       child = tmp_list->data;
1957       if (gtk_bin_get_child (GTK_BIN (child)))
1958         {
1959           if (priv->old_active_menu_item)
1960             g_object_unref (priv->old_active_menu_item);
1961           priv->old_active_menu_item = child;
1962           g_object_ref (priv->old_active_menu_item);
1963         }
1964     }
1965 }
1966
1967 /**
1968  * gtk_menu_set_accel_group:
1969  * @menu: a #GtkMenu
1970  * @accel_group: (allow-none): the #GtkAccelGroup to be associated
1971  *               with the menu.
1972  *
1973  * Set the #GtkAccelGroup which holds global accelerators for the
1974  * menu.  This accelerator group needs to also be added to all windows
1975  * that this menu is being used in with gtk_window_add_accel_group(),
1976  * in order for those windows to support all the accelerators
1977  * contained in this group.
1978  */
1979 void
1980 gtk_menu_set_accel_group (GtkMenu       *menu,
1981                           GtkAccelGroup *accel_group)
1982 {
1983   GtkMenuPrivate *priv = menu->priv;
1984   g_return_if_fail (GTK_IS_MENU (menu));
1985
1986   if (priv->accel_group != accel_group)
1987     {
1988       if (priv->accel_group)
1989         g_object_unref (priv->accel_group);
1990       priv->accel_group = accel_group;
1991       if (priv->accel_group)
1992         g_object_ref (priv->accel_group);
1993       _gtk_menu_refresh_accel_paths (menu, TRUE);
1994     }
1995 }
1996
1997 /**
1998  * gtk_menu_get_accel_group:
1999  * @menu: a #GtkMenu
2000  *
2001  * Gets the #GtkAccelGroup which holds global accelerators for the
2002  * menu. See gtk_menu_set_accel_group().
2003  *
2004  * Returns: (transfer none): the #GtkAccelGroup associated with the menu
2005  */
2006 GtkAccelGroup*
2007 gtk_menu_get_accel_group (GtkMenu *menu)
2008 {
2009   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
2010
2011   return menu->priv->accel_group;
2012 }
2013
2014 static gboolean
2015 gtk_menu_real_can_activate_accel (GtkWidget *widget,
2016                                   guint      signal_id)
2017 {
2018   /* Menu items chain here to figure whether they can activate their
2019    * accelerators.  Unlike ordinary widgets, menus allow accel
2020    * activation even if invisible since that's the usual case for
2021    * submenus/popup-menus. however, the state of the attach widget
2022    * affects the "activeness" of the menu.
2023    */
2024   GtkWidget *awidget = gtk_menu_get_attach_widget (GTK_MENU (widget));
2025
2026   if (awidget)
2027     return gtk_widget_can_activate_accel (awidget, signal_id);
2028   else
2029     return gtk_widget_is_sensitive (widget);
2030 }
2031
2032 /**
2033  * gtk_menu_set_accel_path
2034  * @menu:       a valid #GtkMenu
2035  * @accel_path: (allow-none): a valid accelerator path
2036  *
2037  * Sets an accelerator path for this menu from which accelerator paths
2038  * for its immediate children, its menu items, can be constructed.
2039  * The main purpose of this function is to spare the programmer the
2040  * inconvenience of having to call gtk_menu_item_set_accel_path() on
2041  * each menu item that should support runtime user changable accelerators.
2042  * Instead, by just calling gtk_menu_set_accel_path() on their parent,
2043  * each menu item of this menu, that contains a label describing its
2044  * purpose, automatically gets an accel path assigned.
2045  *
2046  * For example, a menu containing menu items "New" and "Exit", will, after
2047  * <literal>gtk_menu_set_accel_path (menu, "&lt;Gnumeric-Sheet&gt;/File");</literal>
2048  * has been called, assign its items the accel paths:
2049  * <literal>"&lt;Gnumeric-Sheet&gt;/File/New"</literal> and <literal>"&lt;Gnumeric-Sheet&gt;/File/Exit"</literal>.
2050  *
2051  * Assigning accel paths to menu items then enables the user to change
2052  * their accelerators at runtime. More details about accelerator paths
2053  * and their default setups can be found at gtk_accel_map_add_entry().
2054  *
2055  * Note that @accel_path string will be stored in a #GQuark. Therefore,
2056  * if you pass a static string, you can save some memory by interning
2057  * it first with g_intern_static_string().
2058  */
2059 void
2060 gtk_menu_set_accel_path (GtkMenu     *menu,
2061                          const gchar *accel_path)
2062 {
2063   GtkMenuPrivate *priv = menu->priv;
2064   g_return_if_fail (GTK_IS_MENU (menu));
2065
2066   if (accel_path)
2067     g_return_if_fail (accel_path[0] == '<' && strchr (accel_path, '/')); /* simplistic check */
2068
2069   /* FIXME: accel_path should be defined as const gchar* */
2070   priv->accel_path = (gchar*)g_intern_string (accel_path);
2071   if (priv->accel_path)
2072     _gtk_menu_refresh_accel_paths (menu, FALSE);
2073 }
2074
2075 /**
2076  * gtk_menu_get_accel_path
2077  * @menu: a valid #GtkMenu
2078  *
2079  * Retrieves the accelerator path set on the menu.
2080  *
2081  * Returns: the accelerator path set on the menu.
2082  *
2083  * Since: 2.14
2084  */
2085 const gchar*
2086 gtk_menu_get_accel_path (GtkMenu *menu)
2087 {
2088   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
2089
2090   return menu->priv->accel_path;
2091 }
2092
2093 typedef struct {
2094   GtkMenu *menu;
2095   gboolean group_changed;
2096 } AccelPropagation;
2097
2098 static void
2099 refresh_accel_paths_foreach (GtkWidget *widget,
2100                              gpointer   data)
2101 {
2102   GtkMenuPrivate *priv;
2103   AccelPropagation *prop = data;
2104
2105   if (GTK_IS_MENU_ITEM (widget))  /* should always be true */
2106     {
2107       priv = prop->menu->priv;
2108       _gtk_menu_item_refresh_accel_path (GTK_MENU_ITEM (widget),
2109                                          priv->accel_path,
2110                                          priv->accel_group,
2111                                          prop->group_changed);
2112     }
2113 }
2114
2115 static void
2116 _gtk_menu_refresh_accel_paths (GtkMenu  *menu,
2117                                gboolean  group_changed)
2118 {
2119   GtkMenuPrivate *priv = menu->priv;
2120   g_return_if_fail (GTK_IS_MENU (menu));
2121
2122   if (priv->accel_path && priv->accel_group)
2123     {
2124       AccelPropagation prop;
2125
2126       prop.menu = menu;
2127       prop.group_changed = group_changed;
2128       gtk_container_foreach (GTK_CONTAINER (menu),
2129                              refresh_accel_paths_foreach,
2130                              &prop);
2131     }
2132 }
2133
2134 /**
2135  * gtk_menu_reposition:
2136  * @menu: a #GtkMenu
2137  *
2138  * Repositions the menu according to its position function.
2139  */
2140 void
2141 gtk_menu_reposition (GtkMenu *menu)
2142 {
2143   g_return_if_fail (GTK_IS_MENU (menu));
2144
2145   if (!menu->priv->torn_off && gtk_widget_is_drawable (GTK_WIDGET (menu)))
2146     gtk_menu_position (menu, FALSE);
2147 }
2148
2149 static void
2150 gtk_menu_scrollbar_changed (GtkAdjustment *adjustment,
2151                             GtkMenu       *menu)
2152 {
2153   double value;
2154
2155   value = gtk_adjustment_get_value (adjustment);
2156   if (menu->priv->scroll_offset != value)
2157     gtk_menu_scroll_to (menu, value);
2158 }
2159
2160 static void
2161 gtk_menu_set_tearoff_hints (GtkMenu *menu,
2162                             gint     width)
2163 {
2164   GtkMenuPrivate *priv = menu->priv;
2165   GdkGeometry geometry_hints;
2166
2167   if (!priv->tearoff_window)
2168     return;
2169
2170   if (gtk_widget_get_visible (priv->tearoff_scrollbar))
2171     {
2172       GtkRequisition requisition;
2173
2174       gtk_widget_get_preferred_size (priv->tearoff_scrollbar,
2175                                      &requisition, NULL);
2176       width += requisition.width;
2177     }
2178
2179   geometry_hints.min_width = width;
2180   geometry_hints.max_width = width;
2181
2182   geometry_hints.min_height = 0;
2183   geometry_hints.max_height = priv->requested_height;
2184
2185   gtk_window_set_geometry_hints (GTK_WINDOW (priv->tearoff_window),
2186                                  NULL,
2187                                  &geometry_hints,
2188                                  GDK_HINT_MAX_SIZE|GDK_HINT_MIN_SIZE);
2189 }
2190
2191 static void
2192 gtk_menu_update_title (GtkMenu *menu)
2193 {
2194   GtkMenuPrivate *priv = menu->priv;
2195
2196   if (priv->tearoff_window)
2197     {
2198       const gchar *title;
2199       GtkWidget *attach_widget;
2200
2201       title = gtk_menu_get_title (menu);
2202       if (!title)
2203         {
2204           attach_widget = gtk_menu_get_attach_widget (menu);
2205           if (GTK_IS_MENU_ITEM (attach_widget))
2206             {
2207               GtkWidget *child = gtk_bin_get_child (GTK_BIN (attach_widget));
2208               if (GTK_IS_LABEL (child))
2209                 title = gtk_label_get_text (GTK_LABEL (child));
2210             }
2211         }
2212
2213       if (title)
2214         gtk_window_set_title (GTK_WINDOW (priv->tearoff_window), title);
2215     }
2216 }
2217
2218 static GtkWidget*
2219 gtk_menu_get_toplevel (GtkWidget *menu)
2220 {
2221   GtkWidget *attach, *toplevel;
2222
2223   attach = gtk_menu_get_attach_widget (GTK_MENU (menu));
2224
2225   if (GTK_IS_MENU_ITEM (attach))
2226     attach = gtk_widget_get_parent (attach);
2227
2228   if (GTK_IS_MENU (attach))
2229     return gtk_menu_get_toplevel (attach);
2230   else if (GTK_IS_WIDGET (attach))
2231     {
2232       toplevel = gtk_widget_get_toplevel (attach);
2233       if (gtk_widget_is_toplevel (toplevel))
2234         return toplevel;
2235     }
2236
2237   return NULL;
2238 }
2239
2240 static void
2241 tearoff_window_destroyed (GtkWidget *widget,
2242                           GtkMenu   *menu)
2243 {
2244   gtk_menu_set_tearoff_state (menu, FALSE);
2245 }
2246
2247 /**
2248  * gtk_menu_set_tearoff_state:
2249  * @menu: a #GtkMenu
2250  * @torn_off: If %TRUE, menu is displayed as a tearoff menu.
2251  *
2252  * Changes the tearoff state of the menu.  A menu is normally
2253  * displayed as drop down menu which persists as long as the menu is
2254  * active.  It can also be displayed as a tearoff menu which persists
2255  * until it is closed or reattached.
2256  */
2257 void
2258 gtk_menu_set_tearoff_state (GtkMenu  *menu,
2259                             gboolean  torn_off)
2260 {
2261   GtkMenuPrivate *priv = menu->priv;
2262   gint height;
2263
2264   g_return_if_fail (GTK_IS_MENU (menu));
2265
2266   if (priv->torn_off != torn_off)
2267     {
2268       priv->torn_off = torn_off;
2269       priv->tearoff_active = torn_off;
2270
2271       if (priv->torn_off)
2272         {
2273           if (gtk_widget_get_visible (GTK_WIDGET (menu)))
2274             gtk_menu_popdown (menu);
2275
2276           if (!priv->tearoff_window)
2277             {
2278               GtkWidget *toplevel;
2279
2280               priv->tearoff_window = g_object_new (GTK_TYPE_WINDOW,
2281                                                    "type", GTK_WINDOW_TOPLEVEL,
2282                                                    "screen", gtk_widget_get_screen (priv->toplevel),
2283                                                    "app-paintable", TRUE,
2284                                                    NULL);
2285
2286               gtk_window_set_type_hint (GTK_WINDOW (priv->tearoff_window),
2287                                         GDK_WINDOW_TYPE_HINT_MENU);
2288               gtk_window_set_mnemonic_modifier (GTK_WINDOW (priv->tearoff_window), 0);
2289               g_signal_connect (priv->tearoff_window, "destroy",
2290                                 G_CALLBACK (tearoff_window_destroyed), menu);
2291               g_signal_connect (priv->tearoff_window, "event",
2292                                 G_CALLBACK (gtk_menu_window_event), menu);
2293
2294               gtk_menu_update_title (menu);
2295
2296               gtk_widget_realize (priv->tearoff_window);
2297
2298               toplevel = gtk_menu_get_toplevel (GTK_WIDGET (menu));
2299               if (toplevel != NULL)
2300                 gtk_window_set_transient_for (GTK_WINDOW (priv->tearoff_window),
2301                                               GTK_WINDOW (toplevel));
2302
2303               priv->tearoff_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
2304               gtk_container_add (GTK_CONTAINER (priv->tearoff_window),
2305                                  priv->tearoff_hbox);
2306
2307               height = gdk_window_get_height (gtk_widget_get_window (GTK_WIDGET (menu)));
2308               priv->tearoff_adjustment = gtk_adjustment_new (0,
2309                                                              0, priv->requested_height,
2310                                                              MENU_SCROLL_STEP2,
2311                                                              height/2,
2312                                                              height);
2313               g_object_connect (priv->tearoff_adjustment,
2314                                 "signal::value-changed", gtk_menu_scrollbar_changed, menu,
2315                                 NULL);
2316               priv->tearoff_scrollbar = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, priv->tearoff_adjustment);
2317
2318               gtk_box_pack_end (GTK_BOX (priv->tearoff_hbox),
2319                                 priv->tearoff_scrollbar,
2320                                 FALSE, FALSE, 0);
2321
2322               if (gtk_adjustment_get_upper (priv->tearoff_adjustment) > height)
2323                 gtk_widget_show (priv->tearoff_scrollbar);
2324
2325               gtk_widget_show (priv->tearoff_hbox);
2326             }
2327
2328           gtk_menu_reparent (menu, priv->tearoff_hbox, FALSE);
2329
2330           /* Update menu->requisition */
2331           gtk_widget_get_preferred_size (GTK_WIDGET (menu), NULL, NULL);
2332
2333           gtk_menu_set_tearoff_hints (menu, gdk_window_get_width (gtk_widget_get_window (GTK_WIDGET (menu))));
2334
2335           gtk_widget_realize (priv->tearoff_window);
2336           gtk_menu_position (menu, TRUE);
2337
2338           gtk_widget_show (GTK_WIDGET (menu));
2339           gtk_widget_show (priv->tearoff_window);
2340
2341           gtk_menu_scroll_to (menu, 0);
2342
2343         }
2344       else
2345         {
2346           gtk_widget_hide (GTK_WIDGET (menu));
2347           gtk_widget_hide (priv->tearoff_window);
2348           if (GTK_IS_CONTAINER (priv->toplevel))
2349             gtk_menu_reparent (menu, priv->toplevel, FALSE);
2350           gtk_widget_destroy (priv->tearoff_window);
2351
2352           priv->tearoff_window = NULL;
2353           priv->tearoff_hbox = NULL;
2354           priv->tearoff_scrollbar = NULL;
2355           priv->tearoff_adjustment = NULL;
2356         }
2357
2358       g_object_notify (G_OBJECT (menu), "tearoff-state");
2359     }
2360 }
2361
2362 /**
2363  * gtk_menu_get_tearoff_state:
2364  * @menu: a #GtkMenu
2365  *
2366  * Returns whether the menu is torn off.
2367  * See gtk_menu_set_tearoff_state().
2368  *
2369  * Return value: %TRUE if the menu is currently torn off.
2370  */
2371 gboolean
2372 gtk_menu_get_tearoff_state (GtkMenu *menu)
2373 {
2374   g_return_val_if_fail (GTK_IS_MENU (menu), FALSE);
2375
2376   return menu->priv->torn_off;
2377 }
2378
2379 /**
2380  * gtk_menu_set_title:
2381  * @menu: a #GtkMenu
2382  * @title: a string containing the title for the menu
2383  *
2384  * Sets the title string for the menu.
2385  *
2386  * The title is displayed when the menu is shown as a tearoff
2387  * menu. If @title is %NULL, the menu will see if it is attached
2388  * to a parent menu item, and if so it will try to use the same
2389  * text as that menu item's label.
2390  */
2391 void
2392 gtk_menu_set_title (GtkMenu     *menu,
2393                     const gchar *title)
2394 {
2395   GtkMenuPrivate *priv = menu->priv;
2396   char *old_title;
2397
2398   g_return_if_fail (GTK_IS_MENU (menu));
2399
2400   old_title = priv->title;
2401   priv->title = g_strdup (title);
2402   g_free (old_title);
2403
2404   gtk_menu_update_title (menu);
2405   g_object_notify (G_OBJECT (menu), "tearoff-title");
2406 }
2407
2408 /**
2409  * gtk_menu_get_title:
2410  * @menu: a #GtkMenu
2411  *
2412  * Returns the title of the menu. See gtk_menu_set_title().
2413  *
2414  * Return value: the title of the menu, or %NULL if the menu
2415  *     has no title set on it. This string is owned by GTK+
2416  *     and should not be modified or freed.
2417  **/
2418 const gchar *
2419 gtk_menu_get_title (GtkMenu *menu)
2420 {
2421   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
2422
2423   return menu->priv->title;
2424 }
2425
2426 /**
2427  * gtk_menu_reorder_child:
2428  * @menu: a #GtkMenu
2429  * @child: the #GtkMenuItem to move
2430  * @position: the new position to place @child.
2431  *     Positions are numbered from 0 to n - 1
2432  *
2433  * Moves @child to a new @position in the list of @menu
2434  * children.
2435  */
2436 void
2437 gtk_menu_reorder_child (GtkMenu   *menu,
2438                         GtkWidget *child,
2439                         gint       position)
2440 {
2441   GtkMenuShell *menu_shell;
2442
2443   g_return_if_fail (GTK_IS_MENU (menu));
2444   g_return_if_fail (GTK_IS_MENU_ITEM (child));
2445
2446   menu_shell = GTK_MENU_SHELL (menu);
2447
2448   if (g_list_find (menu_shell->priv->children, child))
2449     {
2450       menu_shell->priv->children = g_list_remove (menu_shell->priv->children, child);
2451       menu_shell->priv->children = g_list_insert (menu_shell->priv->children, child, position);
2452
2453       menu_queue_resize (menu);
2454     }
2455 }
2456
2457 static void
2458 gtk_menu_style_updated (GtkWidget *widget)
2459 {
2460   GTK_WIDGET_CLASS (gtk_menu_parent_class)->style_updated (widget);
2461
2462   if (gtk_widget_get_realized (widget))
2463     {
2464       GtkMenu *menu = GTK_MENU (widget);
2465       GtkMenuPrivate *priv = menu->priv;
2466       GtkStyleContext *context;
2467
2468       context = gtk_widget_get_style_context (widget);
2469
2470       gtk_style_context_set_background (context, priv->bin_window);
2471       gtk_style_context_set_background (context, priv->view_window);
2472       gtk_style_context_set_background (context, gtk_widget_get_window (widget));
2473     }
2474 }
2475
2476 static void
2477 get_arrows_border (GtkMenu   *menu,
2478                    GtkBorder *border)
2479 {
2480   GtkMenuPrivate *priv = menu->priv;
2481   guint scroll_arrow_height;
2482   GtkArrowPlacement arrow_placement;
2483
2484   gtk_widget_style_get (GTK_WIDGET (menu),
2485                         "scroll-arrow-vlength", &scroll_arrow_height,
2486                         "arrow_placement", &arrow_placement,
2487                         NULL);
2488
2489   switch (arrow_placement)
2490     {
2491     case GTK_ARROWS_BOTH:
2492       border->top = priv->upper_arrow_visible ? scroll_arrow_height : 0;
2493       border->bottom = priv->lower_arrow_visible ? scroll_arrow_height : 0;
2494       break;
2495
2496     case GTK_ARROWS_START:
2497       border->top = (priv->upper_arrow_visible ||
2498                      priv->lower_arrow_visible) ? scroll_arrow_height : 0;
2499       border->bottom = 0;
2500       break;
2501
2502     case GTK_ARROWS_END:
2503       border->top = 0;
2504       border->bottom = (priv->upper_arrow_visible ||
2505                         priv->lower_arrow_visible) ? scroll_arrow_height : 0;
2506       break;
2507     }
2508
2509   border->left = border->right = 0;
2510 }
2511
2512 static void
2513 get_menu_padding (GtkWidget *widget,
2514                   GtkBorder *padding)
2515 {
2516   GtkStyleContext *context;
2517   GtkStateFlags state;
2518
2519   context = gtk_widget_get_style_context (widget);
2520   state = gtk_widget_get_state_flags (widget);
2521
2522   gtk_style_context_get_padding (context, state, padding);
2523 }
2524
2525 static void
2526 gtk_menu_realize (GtkWidget *widget)
2527 {
2528   GtkMenu *menu = GTK_MENU (widget);
2529   GtkMenuPrivate *priv = menu->priv;
2530   GtkAllocation allocation;
2531   GtkStyleContext *context;
2532   GdkWindow *window;
2533   GdkWindowAttr attributes;
2534   gint attributes_mask;
2535   gint border_width;
2536   GtkWidget *child;
2537   GList *children;
2538   guint vertical_padding;
2539   guint horizontal_padding;
2540   GtkBorder arrow_border, padding;
2541
2542   g_return_if_fail (GTK_IS_MENU (widget));
2543
2544   gtk_widget_set_realized (widget, TRUE);
2545
2546   gtk_widget_get_allocation (widget, &allocation);
2547
2548   attributes.window_type = GDK_WINDOW_CHILD;
2549   attributes.x = allocation.x;
2550   attributes.y = allocation.y;
2551   attributes.width = allocation.width;
2552   attributes.height = allocation.height;
2553   attributes.wclass = GDK_INPUT_OUTPUT;
2554   attributes.visual = gtk_widget_get_visual (widget);
2555   attributes.event_mask = gtk_widget_get_events (widget);
2556   attributes.event_mask |= (GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK |
2557                             GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK );
2558
2559   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
2560
2561   window = gdk_window_new (gtk_widget_get_parent_window (widget),
2562                            &attributes, attributes_mask);
2563   gtk_widget_set_window (widget, window);
2564   gdk_window_set_user_data (window, widget);
2565
2566   get_menu_padding (widget, &padding);
2567   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
2568   context = gtk_widget_get_style_context (widget);
2569
2570   gtk_widget_style_get (GTK_WIDGET (menu),
2571                         "vertical-padding", &vertical_padding,
2572                         "horizontal-padding", &horizontal_padding,
2573                         NULL);
2574
2575   gtk_widget_get_allocation (widget, &allocation);
2576
2577   attributes.x = border_width + padding.left + horizontal_padding;
2578   attributes.y = border_width + padding.top + vertical_padding;
2579   attributes.width = allocation.width -
2580     (2 * (border_width + horizontal_padding)) - padding.left - padding.right;
2581   attributes.height = allocation.height -
2582     (2 * (border_width + vertical_padding)) - padding.top - padding.bottom;
2583
2584   get_arrows_border (menu, &arrow_border);
2585   attributes.y += arrow_border.top;
2586   attributes.height -= arrow_border.top;
2587   attributes.height -= arrow_border.bottom;
2588
2589   attributes.width = MAX (1, attributes.width);
2590   attributes.height = MAX (1, attributes.height);
2591
2592   priv->view_window = gdk_window_new (window,
2593                                       &attributes, attributes_mask);
2594   gdk_window_set_user_data (priv->view_window, menu);
2595
2596   gtk_widget_get_allocation (widget, &allocation);
2597
2598   attributes.x = 0;
2599   attributes.y = 0;
2600   attributes.width = allocation.width + (2 * (border_width + horizontal_padding)) +
2601     padding.left + padding.right;
2602   attributes.height = priv->requested_height - (2 * (border_width + vertical_padding)) +
2603     padding.top + padding.bottom;
2604
2605   attributes.width = MAX (1, attributes.width);
2606   attributes.height = MAX (1, attributes.height);
2607
2608   priv->bin_window = gdk_window_new (priv->view_window,
2609                                      &attributes, attributes_mask);
2610   gdk_window_set_user_data (priv->bin_window, menu);
2611
2612   children = GTK_MENU_SHELL (menu)->priv->children;
2613   while (children)
2614     {
2615       child = children->data;
2616       children = children->next;
2617
2618       gtk_widget_set_parent_window (child, priv->bin_window);
2619     }
2620
2621   gtk_style_context_set_background (context, priv->bin_window);
2622   gtk_style_context_set_background (context, priv->view_window);
2623   gtk_style_context_set_background (context, window);
2624
2625   if (GTK_MENU_SHELL (widget)->priv->active_menu_item)
2626     gtk_menu_scroll_item_visible (GTK_MENU_SHELL (widget),
2627                                   GTK_MENU_SHELL (widget)->priv->active_menu_item);
2628
2629   gdk_window_show (priv->bin_window);
2630   gdk_window_show (priv->view_window);
2631 }
2632
2633 static gboolean
2634 gtk_menu_focus (GtkWidget       *widget,
2635                 GtkDirectionType direction)
2636 {
2637   /* A menu or its menu items cannot have focus */
2638   return FALSE;
2639 }
2640
2641 /* See notes in gtk_menu_popup() for information
2642  * about the "grab transfer window"
2643  */
2644 static GdkWindow *
2645 menu_grab_transfer_window_get (GtkMenu *menu)
2646 {
2647   GdkWindow *window = g_object_get_data (G_OBJECT (menu), "gtk-menu-transfer-window");
2648   if (!window)
2649     {
2650       GdkWindowAttr attributes;
2651       gint attributes_mask;
2652
2653       attributes.x = -100;
2654       attributes.y = -100;
2655       attributes.width = 10;
2656       attributes.height = 10;
2657       attributes.window_type = GDK_WINDOW_TEMP;
2658       attributes.wclass = GDK_INPUT_ONLY;
2659       attributes.override_redirect = TRUE;
2660       attributes.event_mask = 0;
2661
2662       attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR;
2663
2664       window = gdk_window_new (gtk_widget_get_root_window (GTK_WIDGET (menu)),
2665                                &attributes, attributes_mask);
2666       gdk_window_set_user_data (window, menu);
2667
2668       gdk_window_show (window);
2669
2670       g_object_set_data (G_OBJECT (menu), I_("gtk-menu-transfer-window"), window);
2671     }
2672
2673   return window;
2674 }
2675
2676 static void
2677 menu_grab_transfer_window_destroy (GtkMenu *menu)
2678 {
2679   GdkWindow *window = g_object_get_data (G_OBJECT (menu), "gtk-menu-transfer-window");
2680   if (window)
2681     {
2682       gdk_window_set_user_data (window, NULL);
2683       gdk_window_destroy (window);
2684       g_object_set_data (G_OBJECT (menu), I_("gtk-menu-transfer-window"), NULL);
2685     }
2686 }
2687
2688 static void
2689 gtk_menu_unrealize (GtkWidget *widget)
2690 {
2691   GtkMenu *menu = GTK_MENU (widget);
2692   GtkMenuPrivate *priv = menu->priv;
2693
2694   menu_grab_transfer_window_destroy (menu);
2695
2696   gdk_window_set_user_data (priv->view_window, NULL);
2697   gdk_window_destroy (priv->view_window);
2698   priv->view_window = NULL;
2699
2700   gdk_window_set_user_data (priv->bin_window, NULL);
2701   gdk_window_destroy (priv->bin_window);
2702   priv->bin_window = NULL;
2703
2704   GTK_WIDGET_CLASS (gtk_menu_parent_class)->unrealize (widget);
2705 }
2706
2707 static gint
2708 calculate_line_heights (GtkMenu *menu,
2709                         gint     for_width,
2710                         guint  **ret_min_heights,
2711                         guint  **ret_nat_heights)
2712 {
2713   GtkBorder       padding;
2714   GtkMenuPrivate *priv;
2715   GtkMenuShell   *menu_shell;
2716   GtkWidget      *child, *widget;
2717   GList          *children;
2718   guint           horizontal_padding;
2719   guint           border_width;
2720   guint           n_columns;
2721   gint            n_heights;
2722   guint          *min_heights;
2723   guint          *nat_heights;
2724   gint            avail_width;
2725
2726   priv         = menu->priv;
2727   widget       = GTK_WIDGET (menu);
2728   menu_shell   = GTK_MENU_SHELL (widget);
2729
2730   min_heights  = g_new0 (guint, gtk_menu_get_n_rows (menu));
2731   nat_heights  = g_new0 (guint, gtk_menu_get_n_rows (menu));
2732   n_heights    = gtk_menu_get_n_rows (menu);
2733   n_columns    = gtk_menu_get_n_columns (menu);
2734   avail_width  = for_width - (2 * priv->toggle_size + priv->accel_size) * n_columns;
2735
2736   gtk_widget_style_get (GTK_WIDGET (menu),
2737                         "horizontal-padding", &horizontal_padding,
2738                         NULL);
2739   get_menu_padding (widget, &padding);
2740
2741   border_width = gtk_container_get_border_width (GTK_CONTAINER (menu));
2742   avail_width -= (border_width + horizontal_padding) * 2 + padding.left + padding.right;
2743
2744   for (children = menu_shell->priv->children; children; children = children->next)
2745     {
2746       gint part;
2747       gint toggle_size;
2748       gint l, r, t, b;
2749       gint child_min, child_nat;
2750
2751       child = children->data;
2752
2753       if (!gtk_widget_get_visible (child))
2754         continue;
2755
2756       get_effective_child_attach (child, &l, &r, &t, &b);
2757
2758       part = avail_width / (r - l);
2759
2760       gtk_widget_get_preferred_height_for_width (child, part,
2761                                                  &child_min, &child_nat);
2762
2763       gtk_menu_item_toggle_size_request (GTK_MENU_ITEM (child), &toggle_size);
2764
2765       part = MAX (child_min, toggle_size) / (b - t);
2766       min_heights[t] = MAX (min_heights[t], part);
2767
2768       part = MAX (child_nat, toggle_size) / (b - t);
2769       nat_heights[t] = MAX (nat_heights[t], part);
2770     }
2771
2772   if (ret_min_heights)
2773     *ret_min_heights = min_heights;
2774   else
2775     g_free (min_heights);
2776
2777   if (ret_nat_heights)
2778     *ret_nat_heights = nat_heights;
2779   else
2780     g_free (nat_heights);
2781
2782   return n_heights;
2783 }
2784
2785 static void
2786 gtk_menu_size_allocate (GtkWidget     *widget,
2787                         GtkAllocation *allocation)
2788 {
2789   GtkMenu *menu;
2790   GtkMenuPrivate *priv;
2791   GtkMenuShell *menu_shell;
2792   GtkWidget *child;
2793   GtkAllocation child_allocation;
2794   GList *children;
2795   gint x, y, i;
2796   gint width, height;
2797   guint border_width;
2798   guint vertical_padding;
2799   guint horizontal_padding;
2800   GtkBorder padding;
2801
2802   g_return_if_fail (GTK_IS_MENU (widget));
2803   g_return_if_fail (allocation != NULL);
2804
2805   menu = GTK_MENU (widget);
2806   menu_shell = GTK_MENU_SHELL (widget);
2807   priv = menu->priv;
2808
2809   gtk_widget_set_allocation (widget, allocation);
2810
2811   gtk_widget_style_get (GTK_WIDGET (menu),
2812                         "vertical-padding", &vertical_padding,
2813                         "horizontal-padding", &horizontal_padding,
2814                         NULL);
2815
2816   get_menu_padding (widget, &padding);
2817   border_width = gtk_container_get_border_width (GTK_CONTAINER (menu));
2818
2819   g_free (priv->heights);
2820   priv->heights_length = calculate_line_heights (menu,
2821                                                  allocation->width,
2822                                                  &priv->heights,
2823                                                  NULL);
2824
2825   /* refresh our cached height request */
2826   priv->requested_height = (2 * (border_width + vertical_padding)) +
2827     padding.top + padding.bottom;
2828   for (i = 0; i < priv->heights_length; i++)
2829     priv->requested_height += priv->heights[i];
2830
2831   x = border_width + padding.left + horizontal_padding;
2832   y = border_width + padding.top + vertical_padding;
2833   width = allocation->width - (2 * (border_width + horizontal_padding)) -
2834     padding.left - padding.right;
2835   height = allocation->height - (2 * (border_width + vertical_padding)) -
2836     padding.top - padding.bottom;
2837
2838   if (menu_shell->priv->active)
2839     gtk_menu_scroll_to (menu, priv->scroll_offset);
2840
2841   if (!priv->tearoff_active)
2842     {
2843       GtkBorder arrow_border;
2844
2845       get_arrows_border (menu, &arrow_border);
2846       y += arrow_border.top;
2847       height -= arrow_border.top;
2848       height -= arrow_border.bottom;
2849     }
2850
2851   width = MAX (1, width);
2852   height = MAX (1, height);
2853
2854   if (gtk_widget_get_realized (widget))
2855     {
2856       gdk_window_move_resize (gtk_widget_get_window (widget),
2857                               allocation->x, allocation->y,
2858                               allocation->width, allocation->height);
2859
2860       gdk_window_move_resize (priv->view_window, x, y, width, height);
2861     }
2862
2863   if (menu_shell->priv->children)
2864     {
2865       gint base_width = width / gtk_menu_get_n_columns (menu);
2866
2867       children = menu_shell->priv->children;
2868       while (children)
2869         {
2870           child = children->data;
2871           children = children->next;
2872
2873           if (gtk_widget_get_visible (child))
2874             {
2875               gint i;
2876               gint l, r, t, b;
2877
2878               get_effective_child_attach (child, &l, &r, &t, &b);
2879
2880               if (gtk_widget_get_direction (GTK_WIDGET (menu)) == GTK_TEXT_DIR_RTL)
2881                 {
2882                   guint tmp;
2883                   tmp = gtk_menu_get_n_columns (menu) - l;
2884                   l = gtk_menu_get_n_columns (menu) - r;
2885                   r = tmp;
2886                 }
2887
2888               child_allocation.width = (r - l) * base_width;
2889               child_allocation.height = 0;
2890               child_allocation.x = l * base_width;
2891               child_allocation.y = 0;
2892
2893               for (i = 0; i < b; i++)
2894                 {
2895                   if (i < t)
2896                     child_allocation.y += priv->heights[i];
2897                   else
2898                     child_allocation.height += priv->heights[i];
2899                 }
2900
2901               gtk_menu_item_toggle_size_allocate (GTK_MENU_ITEM (child),
2902                                                   priv->toggle_size);
2903
2904               gtk_widget_size_allocate (child, &child_allocation);
2905               gtk_widget_queue_draw (child);
2906             }
2907         }
2908
2909       /* Resize the item window */
2910       if (gtk_widget_get_realized (widget))
2911         {
2912           gint i;
2913           gint width, height;
2914
2915           height = 0;
2916           for (i = 0; i < gtk_menu_get_n_rows (menu); i++)
2917             height += priv->heights[i];
2918
2919           width = gtk_menu_get_n_columns (menu) * base_width;
2920           gdk_window_resize (priv->bin_window, width, height);
2921         }
2922
2923       if (priv->tearoff_active)
2924         {
2925           if (height >= priv->requested_height)
2926             {
2927               if (gtk_widget_get_visible (priv->tearoff_scrollbar))
2928                 {
2929                   gtk_widget_hide (priv->tearoff_scrollbar);
2930                   gtk_menu_set_tearoff_hints (menu, allocation->width);
2931
2932                   gtk_menu_scroll_to (menu, 0);
2933                 }
2934             }
2935           else
2936             {
2937               gtk_adjustment_configure (priv->tearoff_adjustment,
2938                                         gtk_adjustment_get_value (priv->tearoff_adjustment),
2939                                         0,
2940                                         priv->requested_height,
2941                                         gtk_adjustment_get_step_increment (priv->tearoff_adjustment),
2942                                         gtk_adjustment_get_page_increment (priv->tearoff_adjustment),
2943                                         allocation->height);
2944
2945               if (!gtk_widget_get_visible (priv->tearoff_scrollbar))
2946                 {
2947                   gtk_widget_show (priv->tearoff_scrollbar);
2948                   gtk_menu_set_tearoff_hints (menu, allocation->width);
2949                 }
2950             }
2951         }
2952     }
2953 }
2954
2955 static void
2956 get_arrows_visible_area (GtkMenu      *menu,
2957                          GdkRectangle *border,
2958                          GdkRectangle *upper,
2959                          GdkRectangle *lower,
2960                          gint         *arrow_space)
2961 {
2962   GtkArrowPlacement arrow_placement;
2963   GtkWidget *widget = GTK_WIDGET (menu);
2964   guint border_width;
2965   guint vertical_padding;
2966   guint horizontal_padding;
2967   gint scroll_arrow_height;
2968   GtkBorder menu_padding;
2969
2970   gtk_widget_style_get (widget,
2971                         "vertical-padding", &vertical_padding,
2972                         "horizontal-padding", &horizontal_padding,
2973                         "scroll-arrow-vlength", &scroll_arrow_height,
2974                         "arrow-placement", &arrow_placement,
2975                         NULL);
2976
2977   get_menu_padding (widget, &menu_padding);
2978   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
2979   border->x = border_width + menu_padding.left + horizontal_padding;
2980   border->y = border_width + menu_padding.top + vertical_padding;
2981   border->width = gdk_window_get_width (gtk_widget_get_window (widget));
2982   border->height = gdk_window_get_height (gtk_widget_get_window (widget));
2983
2984   switch (arrow_placement)
2985     {
2986     case GTK_ARROWS_BOTH:
2987       upper->x = border->x;
2988       upper->y = border->y;
2989       upper->width = border->width - 2 * border->x;
2990       upper->height = scroll_arrow_height;
2991
2992       lower->x = border->x;
2993       lower->y = border->height - border->y - scroll_arrow_height;
2994       lower->width = border->width - 2 * border->x;
2995       lower->height = scroll_arrow_height;
2996       break;
2997
2998     case GTK_ARROWS_START:
2999       upper->x = border->x;
3000       upper->y = border->y;
3001       upper->width = (border->width - 2 * border->x) / 2;
3002       upper->height = scroll_arrow_height;
3003
3004       lower->x = border->x + upper->width;
3005       lower->y = border->y;
3006       lower->width = (border->width - 2 * border->x) / 2;
3007       lower->height = scroll_arrow_height;
3008       break;
3009
3010     case GTK_ARROWS_END:
3011       upper->x = border->x;
3012       upper->y = border->height - border->y - scroll_arrow_height;
3013       upper->width = (border->width - 2 * border->x) / 2;
3014       upper->height = scroll_arrow_height;
3015
3016       lower->x = border->x + upper->width;
3017       lower->y = border->height - border->y - scroll_arrow_height;
3018       lower->width = (border->width - 2 * border->x) / 2;
3019       lower->height = scroll_arrow_height;
3020       break;
3021
3022     default:
3023        g_assert_not_reached();
3024        upper->x = upper->y = upper->width = upper->height = 0;
3025        lower->x = lower->y = lower->width = lower->height = 0;
3026     }
3027
3028   *arrow_space = scroll_arrow_height - menu_padding.top - menu_padding.bottom;
3029 }
3030
3031 static gboolean
3032 gtk_menu_draw (GtkWidget *widget,
3033                cairo_t   *cr)
3034 {
3035   GtkMenu *menu;
3036   GtkMenuPrivate *priv;
3037   GtkStyleContext *context;
3038   GdkRectangle border;
3039   GdkRectangle upper;
3040   GdkRectangle lower;
3041   gint arrow_space;
3042   GtkBorder menu_padding;
3043
3044   menu = GTK_MENU (widget);
3045   priv = menu->priv;
3046   context = gtk_widget_get_style_context (widget);
3047
3048   get_arrows_visible_area (menu, &border, &upper, &lower, &arrow_space);
3049   get_menu_padding (widget, &menu_padding);
3050
3051   if (gtk_cairo_should_draw_window (cr, gtk_widget_get_window (widget)))
3052     {
3053       gfloat arrow_scaling;
3054       gint arrow_size;
3055
3056       gtk_widget_style_get (widget, "arrow-scaling", &arrow_scaling, NULL);
3057       arrow_size = arrow_scaling * arrow_space;
3058
3059       gtk_render_background (context, cr, 0, 0,
3060                              gtk_widget_get_allocated_width (widget),
3061                              gtk_widget_get_allocated_height (widget));
3062       gtk_render_frame (context, cr, 0, 0,
3063                         gtk_widget_get_allocated_width (widget),
3064                         gtk_widget_get_allocated_height (widget));
3065
3066       gtk_style_context_save (context);
3067       gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);
3068
3069       if (priv->upper_arrow_visible && !priv->tearoff_active)
3070         {
3071           gtk_style_context_save (context);
3072           gtk_style_context_set_state (context, priv->upper_arrow_state);
3073
3074           gtk_render_background (context, cr,
3075                                  upper.x, upper.y,
3076                                  upper.width, upper.height);
3077           gtk_render_frame (context, cr,
3078                             upper.x, upper.y,
3079                             upper.width, upper.height);
3080
3081           gtk_render_arrow (context, cr, 0,
3082                             upper.x + (upper.width - arrow_size) / 2,
3083                             upper.y + menu_padding.top + (arrow_space - arrow_size) / 2,
3084                             arrow_size);
3085
3086           gtk_style_context_restore (context);
3087         }
3088
3089       if (priv->lower_arrow_visible && !priv->tearoff_active)
3090         {
3091           gtk_style_context_save (context);
3092           gtk_style_context_set_state (context, priv->lower_arrow_state);
3093
3094           gtk_render_background (context, cr,
3095                                  lower.x, lower.y,
3096                                  lower.width, lower.height);
3097           gtk_render_frame (context, cr,
3098                             lower.x, lower.y,
3099                             lower.width, lower.height);
3100
3101           gtk_render_arrow (context, cr, G_PI,
3102                             lower.x + (lower.width - arrow_size) / 2,
3103                             lower.y + menu_padding.bottom + (arrow_space - arrow_size) / 2,
3104                             arrow_size);
3105
3106           gtk_style_context_restore (context);
3107         }
3108
3109       gtk_style_context_restore (context);
3110     }
3111
3112   if (gtk_cairo_should_draw_window (cr, priv->bin_window))
3113     {
3114       gint y = -border.y + priv->scroll_offset;
3115
3116       cairo_save (cr);
3117       gtk_cairo_transform_to_window (cr, widget, priv->bin_window);
3118
3119       if (!priv->tearoff_active)
3120         {
3121           GtkBorder arrow_border;
3122
3123           get_arrows_border (menu, &arrow_border);
3124           y -= arrow_border.top;
3125         }
3126
3127       gtk_render_background (context, cr,
3128                              - border.x, y,
3129                              border.width, border.height);
3130       gtk_render_frame (context, cr,
3131                         - border.x, y,
3132                         border.width, border.height);
3133
3134       cairo_restore (cr);
3135     }
3136
3137   GTK_WIDGET_CLASS (gtk_menu_parent_class)->draw (widget, cr);
3138
3139   return FALSE;
3140 }
3141
3142 static void
3143 gtk_menu_show (GtkWidget *widget)
3144 {
3145   GtkMenu *menu = GTK_MENU (widget);
3146
3147   _gtk_menu_refresh_accel_paths (menu, FALSE);
3148
3149   GTK_WIDGET_CLASS (gtk_menu_parent_class)->show (widget);
3150 }
3151
3152
3153 static void 
3154 gtk_menu_get_preferred_width (GtkWidget *widget,
3155                               gint      *minimum_size,
3156                               gint      *natural_size)
3157 {
3158   GtkMenu        *menu;
3159   GtkMenuShell   *menu_shell;
3160   GtkMenuPrivate *priv;
3161   GtkWidget      *child;
3162   GList          *children;
3163   guint           max_toggle_size;
3164   guint           max_accel_width;
3165   guint           horizontal_padding;
3166   guint           border_width;
3167   gint            child_min, child_nat;
3168   gint            min_width, nat_width;
3169   GtkBorder       padding;
3170
3171   menu       = GTK_MENU (widget);
3172   menu_shell = GTK_MENU_SHELL (widget);
3173   priv       = menu->priv;
3174
3175   min_width = nat_width = 0;
3176
3177   max_toggle_size = 0;
3178   max_accel_width = 0;
3179
3180   children = menu_shell->priv->children;
3181   while (children)
3182     {
3183       gint part;
3184       gint toggle_size;
3185       gint l, r, t, b;
3186
3187       child = children->data;
3188       children = children->next;
3189
3190       if (! gtk_widget_get_visible (child))
3191         continue;
3192
3193       get_effective_child_attach (child, &l, &r, &t, &b);
3194
3195       /* It's important to size_request the child
3196        * before doing the toggle size request, in
3197        * case the toggle size request depends on the size
3198        * request of a child of the child (e.g. for ImageMenuItem)
3199        */
3200        gtk_widget_get_preferred_width (child, &child_min, &child_nat);
3201
3202        gtk_menu_item_toggle_size_request (GTK_MENU_ITEM (child), &toggle_size);
3203        max_toggle_size = MAX (max_toggle_size, toggle_size);
3204        max_accel_width = MAX (max_accel_width,
3205                               GTK_MENU_ITEM (child)->priv->accelerator_width);
3206
3207        part = child_min / (r - l);
3208        min_width = MAX (min_width, part);
3209
3210        part = child_nat / (r - l);
3211        nat_width = MAX (nat_width, part);
3212     }
3213
3214   /* If the menu doesn't include any images or check items
3215    * reserve the space so that all menus are consistent.
3216    * We only do this for 'ordinary' menus, not for combobox
3217    * menus or multi-column menus
3218    */
3219   if (max_toggle_size == 0 &&
3220       gtk_menu_get_n_columns (menu) == 1 &&
3221       !priv->no_toggle_size)
3222     {
3223       GtkStyleContext *context;
3224       GtkWidgetPath *menu_path, *check_path;
3225       guint toggle_spacing;
3226       guint indicator_size;
3227
3228       context = gtk_widget_get_style_context (widget);
3229       menu_path = gtk_widget_path_copy (gtk_style_context_get_path (context));
3230
3231       /* Create a GtkCheckMenuItem path, only to query indicator spacing */
3232       check_path = gtk_widget_path_copy (menu_path);
3233       gtk_widget_path_append_type (check_path, GTK_TYPE_CHECK_MENU_ITEM);
3234
3235       gtk_style_context_set_path (context, check_path);
3236       gtk_widget_path_free (check_path);
3237
3238       gtk_style_context_get_style (context,
3239                                    "toggle-spacing", &toggle_spacing,
3240                                    "indicator-size", &indicator_size,
3241                                    NULL);
3242
3243       max_toggle_size = indicator_size + toggle_spacing;
3244
3245       /* Restore real widget path */
3246       gtk_style_context_set_path (context, menu_path);
3247       gtk_widget_path_free (menu_path);
3248     }
3249
3250   min_width += 2 * max_toggle_size + max_accel_width;
3251   min_width *= gtk_menu_get_n_columns (menu);
3252
3253   nat_width += 2 * max_toggle_size + max_accel_width;
3254   nat_width *= gtk_menu_get_n_columns (menu);
3255
3256   gtk_widget_style_get (GTK_WIDGET (menu),
3257                         "horizontal-padding", &horizontal_padding,
3258                         NULL);
3259
3260   get_menu_padding (widget, &padding);
3261   border_width = gtk_container_get_border_width (GTK_CONTAINER (menu));
3262   min_width   += (2 * (border_width + horizontal_padding)) +
3263     padding.left + padding.right;
3264   nat_width   += (2 * (border_width + horizontal_padding)) +
3265     padding.left + padding.right;
3266
3267   priv->toggle_size = max_toggle_size;
3268   priv->accel_size  = max_accel_width;
3269
3270   if (minimum_size)
3271     *minimum_size = min_width;
3272
3273   if (natural_size)
3274     *natural_size = nat_width;
3275
3276   /* Don't resize the tearoff if it is not active,
3277    * because it won't redraw (it is only a background pixmap).
3278    */
3279   if (priv->tearoff_active)
3280     gtk_menu_set_tearoff_hints (menu, min_width);
3281 }
3282
3283 static void
3284 gtk_menu_get_preferred_height (GtkWidget *widget,
3285                                gint      *minimum_size,
3286                                gint      *natural_size)
3287 {
3288   gint min_width;
3289
3290   /* Menus are height-for-width only, just return the height
3291    * for the minimum width
3292    */
3293   GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, NULL);
3294   GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, min_width, minimum_size, natural_size);
3295 }
3296
3297 static void
3298 gtk_menu_get_preferred_height_for_width (GtkWidget *widget,
3299                                          gint       for_size,
3300                                          gint      *minimum_size,
3301                                          gint      *natural_size)
3302 {
3303   GtkBorder       padding;
3304   GtkMenu        *menu = GTK_MENU (widget);
3305   GtkMenuPrivate *priv = menu->priv;
3306   guint          *min_heights, *nat_heights;
3307   guint           vertical_padding, border_width;
3308   gint            n_heights, i;
3309   gint            min_height, nat_height;
3310
3311   gtk_widget_style_get (widget, "vertical-padding", &vertical_padding, NULL);
3312   border_width = gtk_container_get_border_width (GTK_CONTAINER (menu));
3313   get_menu_padding (widget, &padding);
3314
3315   min_height = nat_height = (border_width + vertical_padding) * 2 +
3316           padding.top + padding.bottom;
3317
3318   n_heights =
3319     calculate_line_heights (menu, for_size, &min_heights, &nat_heights);
3320
3321   for (i = 0; i < n_heights; i++)
3322     {
3323       min_height += min_heights[i];
3324       nat_height += nat_heights[i];
3325     }
3326
3327   if (priv->have_position)
3328     {
3329       GdkScreen *screen = gtk_widget_get_screen (priv->toplevel);
3330       GdkRectangle monitor;
3331
3332       gdk_screen_get_monitor_geometry (screen, priv->monitor_num, &monitor);
3333
3334       if (priv->position_y + min_height > monitor.y + monitor.height)
3335         min_height = monitor.y + monitor.height - priv->position_y;
3336
3337       if (priv->position_y + nat_height > monitor.y + monitor.height)
3338         nat_height = monitor.y + monitor.height - priv->position_y;
3339
3340       if (priv->position_y < monitor.y)
3341         {
3342           min_height -= monitor.y - priv->position_y;
3343           nat_height -= monitor.y - priv->position_y;
3344         }
3345     }
3346
3347   if (minimum_size)
3348     *minimum_size = min_height;
3349
3350   if (natural_size)
3351     *natural_size = nat_height;
3352
3353   g_free (min_heights);
3354   g_free (nat_heights);
3355 }
3356
3357
3358
3359 static gboolean
3360 gtk_menu_button_scroll (GtkMenu        *menu,
3361                         GdkEventButton *event)
3362 {
3363   GtkMenuPrivate *priv = menu->priv;
3364
3365   if (priv->upper_arrow_prelight || priv->lower_arrow_prelight)
3366     {
3367       gboolean touchscreen_mode;
3368
3369       g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
3370                     "gtk-touchscreen-mode", &touchscreen_mode,
3371                     NULL);
3372
3373       if (touchscreen_mode)
3374         gtk_menu_handle_scrolling (menu,
3375                                    event->x_root, event->y_root,
3376                                    event->type == GDK_BUTTON_PRESS,
3377                                    FALSE);
3378
3379       return TRUE;
3380     }
3381
3382   return FALSE;
3383 }
3384
3385 static gboolean
3386 pointer_in_menu_window (GtkWidget *widget,
3387                         gdouble    x_root,
3388                         gdouble    y_root)
3389 {
3390   GtkMenu *menu = GTK_MENU (widget);
3391   GtkMenuPrivate *priv = menu->priv;
3392   GtkAllocation allocation;
3393
3394   if (gtk_widget_get_mapped (priv->toplevel))
3395     {
3396       GtkMenuShell *menu_shell;
3397       gint          window_x, window_y;
3398
3399       gdk_window_get_position (gtk_widget_get_window (priv->toplevel),
3400                                &window_x, &window_y);
3401
3402       gtk_widget_get_allocation (widget, &allocation);
3403       if (x_root >= window_x && x_root < window_x + allocation.width &&
3404           y_root >= window_y && y_root < window_y + allocation.height)
3405         return TRUE;
3406
3407       menu_shell = GTK_MENU_SHELL (widget);
3408
3409       if (GTK_IS_MENU (menu_shell->priv->parent_menu_shell))
3410         return pointer_in_menu_window (menu_shell->priv->parent_menu_shell,
3411                                        x_root, y_root);
3412     }
3413
3414   return FALSE;
3415 }
3416
3417 static gboolean
3418 gtk_menu_button_press (GtkWidget      *widget,
3419                        GdkEventButton *event)
3420 {
3421   if (event->type != GDK_BUTTON_PRESS)
3422     return FALSE;
3423
3424   /* Don't pass down to menu shell for presses over scroll arrows
3425    */
3426   if (gtk_menu_button_scroll (GTK_MENU (widget), event))
3427     return TRUE;
3428
3429   /*  Don't pass down to menu shell if a non-menuitem part of the menu
3430    *  was clicked. The check for the event_widget being a GtkMenuShell
3431    *  works because we have the pointer grabbed on menu_shell->window
3432    *  with owner_events=TRUE, so all events that are either outside
3433    *  the menu or on its border are delivered relative to
3434    *  menu_shell->window.
3435    */
3436   if (GTK_IS_MENU_SHELL (gtk_get_event_widget ((GdkEvent *) event)) &&
3437       pointer_in_menu_window (widget, event->x_root, event->y_root))
3438     return TRUE;
3439
3440   return GTK_WIDGET_CLASS (gtk_menu_parent_class)->button_press_event (widget, event);
3441 }
3442
3443 static gboolean
3444 gtk_menu_button_release (GtkWidget      *widget,
3445                          GdkEventButton *event)
3446 {
3447   GtkMenuPrivate *priv = GTK_MENU (widget)->priv;
3448
3449   if (priv->ignore_button_release)
3450     {
3451       priv->ignore_button_release = FALSE;
3452       return FALSE;
3453     }
3454
3455   if (event->type != GDK_BUTTON_RELEASE)
3456     return FALSE;
3457
3458   /* Don't pass down to menu shell for releases over scroll arrows
3459    */
3460   if (gtk_menu_button_scroll (GTK_MENU (widget), event))
3461     return TRUE;
3462
3463   /*  Don't pass down to menu shell if a non-menuitem part of the menu
3464    *  was clicked (see comment in button_press()).
3465    */
3466   if (GTK_IS_MENU_SHELL (gtk_get_event_widget ((GdkEvent *) event)) &&
3467       pointer_in_menu_window (widget, event->x_root, event->y_root))
3468     {
3469       /*  Ugly: make sure menu_shell->button gets reset to 0 when we
3470        *  bail out early here so it is in a consistent state for the
3471        *  next button_press/button_release in GtkMenuShell.
3472        *  See bug #449371.
3473        */
3474       if (GTK_MENU_SHELL (widget)->priv->active)
3475         GTK_MENU_SHELL (widget)->priv->button = 0;
3476
3477       return TRUE;
3478     }
3479
3480   return GTK_WIDGET_CLASS (gtk_menu_parent_class)->button_release_event (widget, event);
3481 }
3482
3483 static const gchar *
3484 get_accel_path (GtkWidget *menu_item,
3485                 gboolean  *locked)
3486 {
3487   const gchar *path;
3488   GtkWidget *label;
3489   GClosure *accel_closure;
3490   GtkAccelGroup *accel_group;
3491
3492   path = _gtk_widget_get_accel_path (menu_item, locked);
3493   if (!path)
3494     {
3495       path = GTK_MENU_ITEM (menu_item)->priv->accel_path;
3496
3497       if (locked)
3498         {
3499           *locked = TRUE;
3500
3501           label = gtk_bin_get_child (GTK_BIN (menu_item));
3502
3503           if (GTK_IS_ACCEL_LABEL (label))
3504             {
3505               g_object_get (label,
3506                             "accel-closure", &accel_closure,
3507                             NULL);
3508               if (accel_closure)
3509                 {
3510                   accel_group = gtk_accel_group_from_accel_closure (accel_closure);
3511
3512                   *locked = gtk_accel_group_get_is_locked (accel_group);
3513                 }
3514             }
3515         }
3516     }
3517
3518   return path;
3519 }
3520
3521 static gboolean
3522 gtk_menu_key_press (GtkWidget   *widget,
3523                     GdkEventKey *event)
3524 {
3525   GtkMenuShell *menu_shell;
3526   GtkMenu *menu;
3527   gboolean delete = FALSE;
3528   gboolean can_change_accels;
3529   gchar *accel = NULL;
3530   guint accel_key, accel_mods;
3531   GdkModifierType consumed_modifiers;
3532   GdkDisplay *display;
3533
3534   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
3535   g_return_val_if_fail (event != NULL, FALSE);
3536
3537   menu_shell = GTK_MENU_SHELL (widget);
3538   menu = GTK_MENU (widget);
3539
3540   gtk_menu_stop_navigating_submenu (menu);
3541
3542   if (GTK_WIDGET_CLASS (gtk_menu_parent_class)->key_press_event (widget, event))
3543     return TRUE;
3544
3545   display = gtk_widget_get_display (widget);
3546
3547   g_object_get (gtk_widget_get_settings (widget),
3548                 "gtk-menu-bar-accel", &accel,
3549                 "gtk-can-change-accels", &can_change_accels,
3550                 NULL);
3551
3552   if (accel && *accel)
3553     {
3554       guint keyval = 0;
3555       GdkModifierType mods = 0;
3556
3557       gtk_accelerator_parse (accel, &keyval, &mods);
3558
3559       if (keyval == 0)
3560         g_warning ("Failed to parse menu bar accelerator '%s'\n", accel);
3561
3562       /* FIXME this is wrong, needs to be in the global accel resolution
3563        * thing, to properly consider i18n etc., but that probably requires
3564        * AccelGroup changes etc.
3565        */
3566       if (event->keyval == keyval && (mods & event->state) == mods)
3567         {
3568           gtk_menu_shell_cancel (menu_shell);
3569           g_free (accel);
3570           return TRUE;
3571         }
3572     }
3573
3574   g_free (accel);
3575
3576   switch (event->keyval)
3577     {
3578     case GDK_KEY_Delete:
3579     case GDK_KEY_KP_Delete:
3580     case GDK_KEY_BackSpace:
3581       delete = TRUE;
3582       break;
3583     default:
3584       break;
3585     }
3586
3587   /* Figure out what modifiers went into determining the key symbol */
3588   gdk_keymap_translate_keyboard_state (gdk_keymap_get_for_display (display),
3589                                        event->hardware_keycode,
3590                                        event->state, event->group,
3591                                        NULL, NULL, NULL, &consumed_modifiers);
3592
3593   accel_key = gdk_keyval_to_lower (event->keyval);
3594   accel_mods = event->state & gtk_accelerator_get_default_mod_mask () & ~consumed_modifiers;
3595
3596   /* If lowercasing affects the keysym, then we need to include SHIFT
3597    * in the modifiers, we re-uppercase when we match against the keyval,
3598    * but display and save in caseless form.
3599    */
3600   if (accel_key != event->keyval)
3601     accel_mods |= GDK_SHIFT_MASK;
3602
3603   /* Modify the accelerators */
3604   if (can_change_accels &&
3605       menu_shell->priv->active_menu_item &&
3606       gtk_bin_get_child (GTK_BIN (menu_shell->priv->active_menu_item)) && /* no separators */
3607       GTK_MENU_ITEM (menu_shell->priv->active_menu_item)->priv->submenu == NULL &&  /* no submenus */
3608       (delete || gtk_accelerator_valid (accel_key, accel_mods)))
3609     {
3610       GtkWidget *menu_item = menu_shell->priv->active_menu_item;
3611       gboolean locked, replace_accels = TRUE;
3612       const gchar *path;
3613
3614       path = get_accel_path (menu_item, &locked);
3615       if (!path || locked)
3616         {
3617           /* Can't change accelerators on menu_items without paths
3618            * (basically, those items are accelerator-locked).
3619            */
3620           gtk_widget_error_bell (widget);
3621         }
3622       else
3623         {
3624           gboolean changed;
3625
3626           /* For the keys that act to delete the current setting,
3627            * we delete the current setting if there is one, otherwise,
3628            * we set the key as the accelerator.
3629            */
3630           if (delete)
3631             {
3632               GtkAccelKey key;
3633
3634               if (gtk_accel_map_lookup_entry (path, &key) &&
3635                   (key.accel_key || key.accel_mods))
3636                 {
3637                   accel_key = 0;
3638                   accel_mods = 0;
3639                 }
3640             }
3641           changed = gtk_accel_map_change_entry (path, accel_key, accel_mods, replace_accels);
3642
3643           if (!changed)
3644             {
3645               /* We failed, probably because this key is in use
3646                * and locked already.
3647                */
3648               gtk_widget_error_bell (widget);
3649             }
3650         }
3651     }
3652
3653   return TRUE;
3654 }
3655
3656 static gboolean
3657 check_threshold (GtkWidget *widget,
3658                  gint       start_x,
3659                  gint       start_y,
3660                  gint       x,
3661                  gint       y)
3662 {
3663 #define THRESHOLD 8
3664
3665   return
3666     ABS (start_x - x) > THRESHOLD ||
3667     ABS (start_y - y) > THRESHOLD;
3668 }
3669
3670 static gboolean
3671 definitely_within_item (GtkWidget *widget,
3672                         gint       x,
3673                         gint       y)
3674 {
3675   GdkWindow *window = GTK_MENU_ITEM (widget)->priv->event_window;
3676   int w, h;
3677
3678   w = gdk_window_get_width (window);
3679   h = gdk_window_get_height (window);
3680
3681   return
3682     check_threshold (widget, 0, 0, x, y) &&
3683     check_threshold (widget, w - 1, 0, x, y) &&
3684     check_threshold (widget, w - 1, h - 1, x, y) &&
3685     check_threshold (widget, 0, h - 1, x, y);
3686 }
3687
3688 static gboolean
3689 gtk_menu_has_navigation_triangle (GtkMenu *menu)
3690 {
3691   GtkMenuPrivate *priv = menu->priv;
3692
3693   return priv->navigation_height && priv->navigation_width;
3694 }
3695
3696 static gboolean
3697 gtk_menu_motion_notify (GtkWidget      *widget,
3698                         GdkEventMotion *event)
3699 {
3700   GtkWidget *menu_item;
3701   GtkMenu *menu;
3702   GtkMenuShell *menu_shell;
3703   GtkWidget *parent;
3704
3705   gboolean need_enter;
3706
3707   if (GTK_IS_MENU (widget))
3708     {
3709       GtkMenuPrivate *priv = GTK_MENU(widget)->priv;
3710
3711       if (priv->ignore_button_release)
3712         priv->ignore_button_release = FALSE;
3713
3714       gtk_menu_handle_scrolling (GTK_MENU (widget), event->x_root, event->y_root,
3715                                  TRUE, TRUE);
3716     }
3717
3718   /* We received the event for one of two reasons:
3719    *
3720    * a) We are the active menu, and did gtk_grab_add()
3721    * b) The widget is a child of ours, and the event was propagated
3722    *
3723    * Since for computation of navigation regions, we want the menu which
3724    * is the parent of the menu item, for a), we need to find that menu,
3725    * which may be different from 'widget'.
3726    */
3727   menu_item = gtk_get_event_widget ((GdkEvent*) event);
3728   parent = gtk_widget_get_parent (menu_item);
3729   if (!GTK_IS_MENU_ITEM (menu_item) ||
3730       !GTK_IS_MENU (parent))
3731     return FALSE;
3732
3733   menu_shell = GTK_MENU_SHELL (parent);
3734   menu = GTK_MENU (menu_shell);
3735
3736   if (definitely_within_item (menu_item, event->x, event->y))
3737     menu_shell->priv->activate_time = 0;
3738
3739   need_enter = (gtk_menu_has_navigation_triangle (menu) || menu_shell->priv->ignore_enter);
3740
3741   /* Check to see if we are within an active submenu's navigation region
3742    */
3743   if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
3744     return TRUE;
3745
3746   /* Make sure we pop down if we enter a non-selectable menu item, so we
3747    * don't show a submenu when the cursor is outside the stay-up triangle.
3748    */
3749   if (!_gtk_menu_item_is_selectable (menu_item))
3750     {
3751       /* We really want to deselect, but this gives the menushell code
3752        * a chance to do some bookkeeping about the menuitem.
3753        */
3754       gtk_menu_shell_select_item (menu_shell, menu_item);
3755       return FALSE;
3756     }
3757
3758   if (need_enter)
3759     {
3760       /* The menu is now sensitive to enter events on its items, but
3761        * was previously sensitive.  So we fake an enter event.
3762        */
3763       menu_shell->priv->ignore_enter = FALSE;
3764
3765       if (event->x >= 0 && event->x < gdk_window_get_width (event->window) &&
3766           event->y >= 0 && event->y < gdk_window_get_height (event->window))
3767         {
3768           GdkEvent *send_event = gdk_event_new (GDK_ENTER_NOTIFY);
3769           gboolean result;
3770
3771           send_event->crossing.window = g_object_ref (event->window);
3772           send_event->crossing.time = event->time;
3773           send_event->crossing.send_event = TRUE;
3774           send_event->crossing.x_root = event->x_root;
3775           send_event->crossing.y_root = event->y_root;
3776           send_event->crossing.x = event->x;
3777           send_event->crossing.y = event->y;
3778           send_event->crossing.state = event->state;
3779           gdk_event_set_device (send_event, gdk_event_get_device ((GdkEvent *) event));
3780
3781           /* We send the event to 'widget', the currently active menu,
3782            * instead of 'menu', the menu that the pointer is in. This
3783            * will ensure that the event will be ignored unless the
3784            * menuitem is a child of the active menu or some parent
3785            * menu of the active menu.
3786            */
3787           result = gtk_widget_event (widget, send_event);
3788           gdk_event_free (send_event);
3789
3790           return result;
3791         }
3792     }
3793
3794   return FALSE;
3795 }
3796
3797 static gboolean
3798 get_double_arrows (GtkMenu *menu)
3799 {
3800   GtkMenuPrivate   *priv = menu->priv;
3801   gboolean          double_arrows;
3802   GtkArrowPlacement arrow_placement;
3803
3804   gtk_widget_style_get (GTK_WIDGET (menu),
3805                         "double-arrows", &double_arrows,
3806                         "arrow-placement", &arrow_placement,
3807                         NULL);
3808
3809   if (arrow_placement != GTK_ARROWS_BOTH)
3810     return TRUE;
3811
3812   return double_arrows || (priv->initially_pushed_in &&
3813                            priv->scroll_offset != 0);
3814 }
3815
3816 static void
3817 gtk_menu_scroll_by (GtkMenu *menu,
3818                     gint     step)
3819 {
3820   GtkMenuPrivate *priv = menu->priv;
3821   GtkBorder arrow_border;
3822   GtkWidget *widget;
3823   gint offset;
3824   gint view_height;
3825   gboolean double_arrows;
3826
3827   widget = GTK_WIDGET (menu);
3828   offset = priv->scroll_offset + step;
3829
3830   get_arrows_border (menu, &arrow_border);
3831
3832   double_arrows = get_double_arrows (menu);
3833
3834   /* If we scroll upward and the non-visible top part
3835    * is smaller than the scroll arrow it would be
3836    * pretty stupid to show the arrow and taking more
3837    * screen space than just scrolling to the top.
3838    */
3839   if (!double_arrows)
3840     if ((step < 0) && (offset < arrow_border.top))
3841       offset = 0;
3842
3843   /* Don't scroll over the top if we weren't before: */
3844   if ((priv->scroll_offset >= 0) && (offset < 0))
3845     offset = 0;
3846
3847   view_height = gdk_window_get_height (gtk_widget_get_window (widget));
3848
3849   if (priv->scroll_offset == 0 &&
3850       view_height >= priv->requested_height)
3851     return;
3852
3853   /* Don't scroll past the bottom if we weren't before: */
3854   if (priv->scroll_offset > 0)
3855     view_height -= arrow_border.top;
3856
3857   /* When both arrows are always shown,
3858    * reduce view height even more.
3859    */
3860   if (double_arrows)
3861     view_height -= arrow_border.bottom;
3862
3863   if ((priv->scroll_offset + view_height <= priv->requested_height) &&
3864       (offset + view_height > priv->requested_height))
3865     offset = priv->requested_height - view_height;
3866
3867   if (offset != priv->scroll_offset)
3868     gtk_menu_scroll_to (menu, offset);
3869 }
3870
3871 static void
3872 gtk_menu_do_timeout_scroll (GtkMenu  *menu,
3873                             gboolean  touchscreen_mode)
3874 {
3875   GtkMenuPrivate *priv = menu->priv;
3876   gboolean upper_visible;
3877   gboolean lower_visible;
3878
3879   upper_visible = priv->upper_arrow_visible;
3880   lower_visible = priv->lower_arrow_visible;
3881
3882   gtk_menu_scroll_by (menu, priv->scroll_step);
3883
3884   if (touchscreen_mode &&
3885       (upper_visible != priv->upper_arrow_visible ||
3886        lower_visible != priv->lower_arrow_visible))
3887     {
3888       /* We are about to hide a scroll arrow while the mouse is pressed,
3889        * this would cause the uncovered menu item to be activated on button
3890        * release. Therefore we need to ignore button release here
3891        */
3892       GTK_MENU_SHELL (menu)->priv->ignore_enter = TRUE;
3893       priv->ignore_button_release = TRUE;
3894     }
3895 }
3896
3897 static gboolean
3898 gtk_menu_scroll_timeout (gpointer data)
3899 {
3900   GtkMenu  *menu;
3901   gboolean  touchscreen_mode;
3902
3903   menu = GTK_MENU (data);
3904
3905   g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
3906                 "gtk-touchscreen-mode", &touchscreen_mode,
3907                 NULL);
3908
3909   gtk_menu_do_timeout_scroll (menu, touchscreen_mode);
3910
3911   return TRUE;
3912 }
3913
3914 static gboolean
3915 gtk_menu_scroll_timeout_initial (gpointer data)
3916 {
3917   GtkMenu  *menu;
3918   guint     timeout;
3919   gboolean  touchscreen_mode;
3920
3921   menu = GTK_MENU (data);
3922
3923   g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
3924                 "gtk-timeout-repeat", &timeout,
3925                 "gtk-touchscreen-mode", &touchscreen_mode,
3926                 NULL);
3927
3928   gtk_menu_do_timeout_scroll (menu, touchscreen_mode);
3929
3930   gtk_menu_remove_scroll_timeout (menu);
3931
3932   menu->priv->scroll_timeout =
3933     gdk_threads_add_timeout (timeout, gtk_menu_scroll_timeout, menu);
3934
3935   return FALSE;
3936 }
3937
3938 static void
3939 gtk_menu_start_scrolling (GtkMenu *menu)
3940 {
3941   guint    timeout;
3942   gboolean touchscreen_mode;
3943
3944   g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
3945                 "gtk-timeout-repeat", &timeout,
3946                 "gtk-touchscreen-mode", &touchscreen_mode,
3947                 NULL);
3948
3949   gtk_menu_do_timeout_scroll (menu, touchscreen_mode);
3950
3951   menu->priv->scroll_timeout =
3952     gdk_threads_add_timeout (timeout, gtk_menu_scroll_timeout_initial, menu);
3953 }
3954
3955 static gboolean
3956 gtk_menu_scroll (GtkWidget      *widget,
3957                  GdkEventScroll *event)
3958 {
3959   GtkMenu *menu = GTK_MENU (widget);
3960
3961   switch (event->direction)
3962     {
3963     case GDK_SCROLL_RIGHT:
3964     case GDK_SCROLL_DOWN:
3965       gtk_menu_scroll_by (menu, MENU_SCROLL_STEP2);
3966       break;
3967     case GDK_SCROLL_LEFT:
3968     case GDK_SCROLL_UP:
3969       gtk_menu_scroll_by (menu, - MENU_SCROLL_STEP2);
3970       break;
3971     }
3972
3973   return TRUE;
3974 }
3975
3976 static void
3977 get_arrows_sensitive_area (GtkMenu      *menu,
3978                            GdkRectangle *upper,
3979                            GdkRectangle *lower)
3980 {
3981   GtkArrowPlacement arrow_placement;
3982   GtkWidget *widget = GTK_WIDGET (menu);
3983   GdkWindow *window;
3984   gint width, height;
3985   guint border;
3986   guint vertical_padding;
3987   gint win_x, win_y;
3988   gint scroll_arrow_height;
3989   GtkBorder padding;
3990
3991   window = gtk_widget_get_window (widget);
3992   width = gdk_window_get_width (window);
3993   height = gdk_window_get_height (window);
3994
3995   gtk_widget_style_get (widget,
3996                         "vertical-padding", &vertical_padding,
3997                         "scroll-arrow-vlength", &scroll_arrow_height,
3998                         "arrow-placement", &arrow_placement,
3999                         NULL);
4000
4001   border = gtk_container_get_border_width (GTK_CONTAINER (menu)) + vertical_padding;
4002   get_menu_padding (widget, &padding);
4003
4004   gdk_window_get_position (window, &win_x, &win_y);
4005
4006   switch (arrow_placement)
4007     {
4008     case GTK_ARROWS_BOTH:
4009       if (upper)
4010         {
4011           upper->x = win_x;
4012           upper->y = win_y;
4013           upper->width = width;
4014           upper->height = scroll_arrow_height + border + padding.top;
4015         }
4016
4017       if (lower)
4018         {
4019           lower->x = win_x;
4020           lower->y = win_y + height - border - padding.bottom - scroll_arrow_height;
4021           lower->width = width;
4022           lower->height = scroll_arrow_height + border + padding.bottom;
4023         }
4024       break;
4025
4026     case GTK_ARROWS_START:
4027       if (upper)
4028         {
4029           upper->x = win_x;
4030           upper->y = win_y;
4031           upper->width = width / 2;
4032           upper->height = scroll_arrow_height + border + padding.top;
4033         }
4034
4035       if (lower)
4036         {
4037           lower->x = win_x + width / 2;
4038           lower->y = win_y;
4039           lower->width = width / 2;
4040           lower->height = scroll_arrow_height + border + padding.bottom;
4041         }
4042       break;
4043
4044     case GTK_ARROWS_END:
4045       if (upper)
4046         {
4047           upper->x = win_x;
4048           upper->y = win_y + height - border - scroll_arrow_height;
4049           upper->width = width / 2;
4050           upper->height = scroll_arrow_height + border + padding.top;
4051         }
4052
4053       if (lower)
4054         {
4055           lower->x = win_x + width / 2;
4056           lower->y = win_y + height - border - scroll_arrow_height;
4057           lower->width = width / 2;
4058           lower->height = scroll_arrow_height + border + padding.bottom;
4059         }
4060       break;
4061     }
4062 }
4063
4064
4065 static void
4066 gtk_menu_handle_scrolling (GtkMenu *menu,
4067                            gint     x,
4068                            gint     y,
4069                            gboolean enter,
4070                            gboolean motion)
4071 {
4072   GtkMenuPrivate *priv = menu->priv;
4073   GtkMenuShell *menu_shell;
4074   GdkRectangle rect;
4075   gboolean in_arrow;
4076   gboolean scroll_fast = FALSE;
4077   gint top_x, top_y;
4078   gboolean touchscreen_mode;
4079
4080   menu_shell = GTK_MENU_SHELL (menu);
4081
4082   g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
4083                 "gtk-touchscreen-mode", &touchscreen_mode,
4084                 NULL);
4085
4086   gdk_window_get_position (gtk_widget_get_window (priv->toplevel),
4087                            &top_x, &top_y);
4088   x -= top_x;
4089   y -= top_y;
4090
4091   /*  upper arrow handling  */
4092
4093   get_arrows_sensitive_area (menu, &rect, NULL);
4094
4095   in_arrow = FALSE;
4096   if (priv->upper_arrow_visible && !priv->tearoff_active &&
4097       (x >= rect.x) && (x < rect.x + rect.width) &&
4098       (y >= rect.y) && (y < rect.y + rect.height))
4099     {
4100       in_arrow = TRUE;
4101     }
4102
4103   if (touchscreen_mode)
4104     priv->upper_arrow_prelight = in_arrow;
4105
4106   if ((priv->upper_arrow_state & GTK_STATE_FLAG_INSENSITIVE) == 0)
4107     {
4108       gboolean arrow_pressed = FALSE;
4109
4110       if (priv->upper_arrow_visible && !priv->tearoff_active)
4111         {
4112           if (touchscreen_mode)
4113             {
4114               if (enter && priv->upper_arrow_prelight)
4115                 {
4116                   if (priv->scroll_timeout == 0)
4117                     {
4118                       /* Deselect the active item so that
4119                        * any submenus are popped down
4120                        */
4121                       gtk_menu_shell_deselect (menu_shell);
4122
4123                       gtk_menu_remove_scroll_timeout (menu);
4124                       priv->scroll_step = -MENU_SCROLL_STEP2; /* always fast */
4125
4126                       if (!motion)
4127                         {
4128                           /* Only do stuff on click. */
4129                           gtk_menu_start_scrolling (menu);
4130                           arrow_pressed = TRUE;
4131                         }
4132                     }
4133                   else
4134                     {
4135                       arrow_pressed = TRUE;
4136                     }
4137                 }
4138               else if (!enter)
4139                 {
4140                   gtk_menu_stop_scrolling (menu);
4141                 }
4142             }
4143           else /* !touchscreen_mode */
4144             {
4145               scroll_fast = (y < rect.y + MENU_SCROLL_FAST_ZONE);
4146
4147               if (enter && in_arrow &&
4148                   (!priv->upper_arrow_prelight ||
4149                    priv->scroll_fast != scroll_fast))
4150                 {
4151                   priv->upper_arrow_prelight = TRUE;
4152                   priv->scroll_fast = scroll_fast;
4153
4154                   /* Deselect the active item so that
4155                    * any submenus are popped down
4156                    */
4157                   gtk_menu_shell_deselect (menu_shell);
4158
4159                   gtk_menu_remove_scroll_timeout (menu);
4160                   priv->scroll_step = scroll_fast
4161                                         ? -MENU_SCROLL_STEP2
4162                                         : -MENU_SCROLL_STEP1;
4163
4164                   priv->scroll_timeout =
4165                     gdk_threads_add_timeout (scroll_fast
4166                                                ? MENU_SCROLL_TIMEOUT2
4167                                                : MENU_SCROLL_TIMEOUT1,
4168                                              gtk_menu_scroll_timeout, menu);
4169                 }
4170               else if (!enter && !in_arrow && priv->upper_arrow_prelight)
4171                 {
4172                   gtk_menu_stop_scrolling (menu);
4173                 }
4174             }
4175         }
4176
4177       /*  gtk_menu_start_scrolling() might have hit the top of the
4178        *  menu, so check if the button isn't insensitive before
4179        *  changing it to something else.
4180        */
4181       if ((priv->upper_arrow_state & GTK_STATE_FLAG_INSENSITIVE) == 0)
4182         {
4183           GtkStateFlags arrow_state = 0;
4184
4185           if (arrow_pressed)
4186             arrow_state |= GTK_STATE_FLAG_ACTIVE;
4187
4188           if (priv->upper_arrow_prelight)
4189             arrow_state |= GTK_STATE_FLAG_PRELIGHT;
4190
4191           if (arrow_state != priv->upper_arrow_state)
4192             {
4193               priv->upper_arrow_state = arrow_state;
4194
4195               gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (menu)),
4196                                           &rect, FALSE);
4197             }
4198         }
4199     }
4200
4201   /*  lower arrow handling  */
4202
4203   get_arrows_sensitive_area (menu, NULL, &rect);
4204
4205   in_arrow = FALSE;
4206   if (priv->lower_arrow_visible && !priv->tearoff_active &&
4207       (x >= rect.x) && (x < rect.x + rect.width) &&
4208       (y >= rect.y) && (y < rect.y + rect.height))
4209     {
4210       in_arrow = TRUE;
4211     }
4212
4213   if (touchscreen_mode)
4214     priv->lower_arrow_prelight = in_arrow;
4215
4216   if ((priv->lower_arrow_state & GTK_STATE_FLAG_INSENSITIVE) == 0)
4217     {
4218       gboolean arrow_pressed = FALSE;
4219
4220       if (priv->lower_arrow_visible && !priv->tearoff_active)
4221         {
4222           if (touchscreen_mode)
4223             {
4224               if (enter && priv->lower_arrow_prelight)
4225                 {
4226                   if (priv->scroll_timeout == 0)
4227                     {
4228                       /* Deselect the active item so that
4229                        * any submenus are popped down
4230                        */
4231                       gtk_menu_shell_deselect (menu_shell);
4232
4233                       gtk_menu_remove_scroll_timeout (menu);
4234                       priv->scroll_step = MENU_SCROLL_STEP2; /* always fast */
4235
4236                       if (!motion)
4237                         {
4238                           /* Only do stuff on click. */
4239                           gtk_menu_start_scrolling (menu);
4240                           arrow_pressed = TRUE;
4241                         }
4242                     }
4243                   else
4244                     {
4245                       arrow_pressed = TRUE;
4246                     }
4247                 }
4248               else if (!enter)
4249                 {
4250                   gtk_menu_stop_scrolling (menu);
4251                 }
4252             }
4253           else /* !touchscreen_mode */
4254             {
4255               scroll_fast = (y > rect.y + rect.height - MENU_SCROLL_FAST_ZONE);
4256
4257               if (enter && in_arrow &&
4258                   (!priv->lower_arrow_prelight ||
4259                    priv->scroll_fast != scroll_fast))
4260                 {
4261                   priv->lower_arrow_prelight = TRUE;
4262                   priv->scroll_fast = scroll_fast;
4263
4264                   /* Deselect the active item so that
4265                    * any submenus are popped down
4266                    */
4267                   gtk_menu_shell_deselect (menu_shell);
4268
4269                   gtk_menu_remove_scroll_timeout (menu);
4270                   priv->scroll_step = scroll_fast
4271                                         ? MENU_SCROLL_STEP2
4272                                         : MENU_SCROLL_STEP1;
4273
4274                   priv->scroll_timeout =
4275                     gdk_threads_add_timeout (scroll_fast
4276                                                ? MENU_SCROLL_TIMEOUT2
4277                                                : MENU_SCROLL_TIMEOUT1,
4278                                              gtk_menu_scroll_timeout, menu);
4279                 }
4280               else if (!enter && !in_arrow && priv->lower_arrow_prelight)
4281                 {
4282                   gtk_menu_stop_scrolling (menu);
4283                 }
4284             }
4285         }
4286
4287       /*  gtk_menu_start_scrolling() might have hit the bottom of the
4288        *  menu, so check if the button isn't insensitive before
4289        *  changing it to something else.
4290        */
4291       if ((priv->lower_arrow_state & GTK_STATE_FLAG_INSENSITIVE) == 0)
4292         {
4293           GtkStateFlags arrow_state = 0;
4294
4295           if (arrow_pressed)
4296             arrow_state |= GTK_STATE_FLAG_ACTIVE;
4297
4298           if (priv->lower_arrow_prelight)
4299             arrow_state |= GTK_STATE_FLAG_PRELIGHT;
4300
4301           if (arrow_state != priv->lower_arrow_state)
4302             {
4303               priv->lower_arrow_state = arrow_state;
4304
4305               gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (menu)),
4306                                           &rect, FALSE);
4307             }
4308         }
4309     }
4310 }
4311
4312 static gboolean
4313 gtk_menu_enter_notify (GtkWidget        *widget,
4314                        GdkEventCrossing *event)
4315 {
4316   GtkWidget *menu_item;
4317   GtkWidget *parent;
4318   gboolean   touchscreen_mode;
4319
4320   if (event->mode == GDK_CROSSING_GTK_GRAB ||
4321       event->mode == GDK_CROSSING_GTK_UNGRAB ||
4322       event->mode == GDK_CROSSING_STATE_CHANGED)
4323     return TRUE;
4324
4325   g_object_get (gtk_widget_get_settings (widget),
4326                 "gtk-touchscreen-mode", &touchscreen_mode,
4327                 NULL);
4328
4329   menu_item = gtk_get_event_widget ((GdkEvent*) event);
4330   if (GTK_IS_MENU (widget))
4331     {
4332       GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
4333
4334       if (!menu_shell->priv->ignore_enter)
4335         gtk_menu_handle_scrolling (GTK_MENU (widget),
4336                                    event->x_root, event->y_root, TRUE, TRUE);
4337     }
4338
4339   if (!touchscreen_mode && GTK_IS_MENU_ITEM (menu_item))
4340     {
4341       GtkWidget *menu = gtk_widget_get_parent (menu_item);
4342
4343       if (GTK_IS_MENU (menu))
4344         {
4345           GtkMenuPrivate *priv = (GTK_MENU (menu))->priv;
4346           GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
4347
4348           if (priv->seen_item_enter)
4349             {
4350               /* This is the second enter we see for an item
4351                * on this menu. This means a release should always
4352                * mean activate.
4353                */
4354               menu_shell->priv->activate_time = 0;
4355             }
4356           else if ((event->detail != GDK_NOTIFY_NONLINEAR &&
4357                     event->detail != GDK_NOTIFY_NONLINEAR_VIRTUAL))
4358             {
4359               if (definitely_within_item (menu_item, event->x, event->y))
4360                 {
4361                   /* This is an actual user-enter (ie. not a pop-under)
4362                    * In this case, the user must either have entered
4363                    * sufficiently far enough into the item, or he must move
4364                    * far enough away from the enter point. (see
4365                    * gtk_menu_motion_notify())
4366                    */
4367                   menu_shell->priv->activate_time = 0;
4368                 }
4369             }
4370
4371           priv->seen_item_enter = TRUE;
4372         }
4373     }
4374
4375   /* If this is a faked enter (see gtk_menu_motion_notify), 'widget'
4376    * will not correspond to the event widget's parent.  Check to see
4377    * if we are in the parent's navigation region.
4378    */
4379   parent = gtk_widget_get_parent (menu_item);
4380   if (GTK_IS_MENU_ITEM (menu_item) && GTK_IS_MENU (parent) &&
4381       gtk_menu_navigating_submenu (GTK_MENU (parent),
4382                                    event->x_root, event->y_root))
4383     return TRUE;
4384
4385   return GTK_WIDGET_CLASS (gtk_menu_parent_class)->enter_notify_event (widget, event);
4386 }
4387
4388 static gboolean
4389 gtk_menu_leave_notify (GtkWidget        *widget,
4390                        GdkEventCrossing *event)
4391 {
4392   GtkMenuShell *menu_shell;
4393   GtkMenu *menu;
4394   GtkMenuItem *menu_item;
4395   GtkWidget *event_widget;
4396
4397   if (event->mode == GDK_CROSSING_GTK_GRAB ||
4398       event->mode == GDK_CROSSING_GTK_UNGRAB ||
4399       event->mode == GDK_CROSSING_STATE_CHANGED)
4400     return TRUE;
4401
4402   menu = GTK_MENU (widget);
4403   menu_shell = GTK_MENU_SHELL (widget);
4404
4405   if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
4406     return TRUE;
4407
4408   gtk_menu_handle_scrolling (menu, event->x_root, event->y_root, FALSE, TRUE);
4409
4410   event_widget = gtk_get_event_widget ((GdkEvent*) event);
4411
4412   if (!GTK_IS_MENU_ITEM (event_widget))
4413     return TRUE;
4414
4415   menu_item = GTK_MENU_ITEM (event_widget);
4416
4417   /* Here we check to see if we're leaving an active menu item
4418    * with a submenu, in which case we enter submenu navigation mode.
4419    */
4420   if (menu_shell->priv->active_menu_item != NULL
4421       && menu_item->priv->submenu != NULL
4422       && menu_item->priv->submenu_placement == GTK_LEFT_RIGHT)
4423     {
4424       if (GTK_MENU_SHELL (menu_item->priv->submenu)->priv->active)
4425         {
4426           gtk_menu_set_submenu_navigation_region (menu, menu_item, event);
4427           return TRUE;
4428         }
4429       else if (menu_item == GTK_MENU_ITEM (menu_shell->priv->active_menu_item))
4430         {
4431           /* We are leaving an active menu item with nonactive submenu.
4432            * Deselect it so we don't surprise the user with by popping
4433            * up a submenu _after_ he left the item.
4434            */
4435           gtk_menu_shell_deselect (menu_shell);
4436           return TRUE;
4437         }
4438     }
4439
4440   return GTK_WIDGET_CLASS (gtk_menu_parent_class)->leave_notify_event (widget, event);
4441 }
4442
4443 static void
4444 gtk_menu_stop_navigating_submenu (GtkMenu *menu)
4445 {
4446   GtkMenuPrivate *priv = menu->priv;
4447
4448   priv->navigation_x = 0;
4449   priv->navigation_y = 0;
4450   priv->navigation_width = 0;
4451   priv->navigation_height = 0;
4452
4453   if (priv->navigation_timeout)
4454     {
4455       g_source_remove (priv->navigation_timeout);
4456       priv->navigation_timeout = 0;
4457     }
4458 }
4459
4460 /* When the timeout is elapsed, the navigation region is destroyed
4461  * and the menuitem under the pointer (if any) is selected.
4462  */
4463 static gboolean
4464 gtk_menu_stop_navigating_submenu_cb (gpointer user_data)
4465 {
4466   GtkMenuPopdownData *popdown_data = user_data;
4467   GtkMenu *menu = popdown_data->menu;
4468   GtkMenuPrivate *priv = menu->priv;
4469   GdkWindow *child_window;
4470
4471   gtk_menu_stop_navigating_submenu (menu);
4472
4473   if (gtk_widget_get_realized (GTK_WIDGET (menu)))
4474     {
4475       child_window = gdk_window_get_device_position (priv->bin_window,
4476                                                      popdown_data->device,
4477                                                      NULL, NULL, NULL);
4478
4479       if (child_window)
4480         {
4481           GdkEvent *send_event = gdk_event_new (GDK_ENTER_NOTIFY);
4482
4483           send_event->crossing.window = g_object_ref (child_window);
4484           send_event->crossing.time = GDK_CURRENT_TIME; /* Bogus */
4485           send_event->crossing.send_event = TRUE;
4486           gdk_event_set_device (send_event, popdown_data->device);
4487
4488           GTK_WIDGET_CLASS (gtk_menu_parent_class)->enter_notify_event (GTK_WIDGET (menu), (GdkEventCrossing *)send_event);
4489
4490           gdk_event_free (send_event);
4491         }
4492     }
4493
4494   return FALSE;
4495 }
4496
4497 static gboolean
4498 gtk_menu_navigating_submenu (GtkMenu *menu,
4499                              gint     event_x,
4500                              gint     event_y)
4501 {
4502   GtkMenuPrivate *priv = menu->priv;
4503   gint width, height;
4504
4505   if (!gtk_menu_has_navigation_triangle (menu))
4506     return FALSE;
4507
4508   width = priv->navigation_width;
4509   height = priv->navigation_height;
4510
4511   /* Check if x/y are in the triangle spanned by the navigation parameters */
4512
4513   /* 1) Move the coordinates so the triangle starts at 0,0 */
4514   event_x -= priv->navigation_x;
4515   event_y -= priv->navigation_y;
4516
4517   /* 2) Ensure both legs move along the positive axis */
4518   if (width < 0)
4519     {
4520       event_x = -event_x;
4521       width = -width;
4522     }
4523   if (height < 0)
4524     {
4525       event_y = -event_y;
4526       height = -height;
4527     }
4528
4529   /* 3) Check that the given coordinate is inside the triangle. The formula
4530    * is a transformed form of this formula: x/w + y/h <= 1
4531    */
4532   if (event_x >= 0 && event_y >= 0 &&
4533       event_x * height + event_y * width <= width * height)
4534     {
4535       return TRUE;
4536     }
4537   else
4538     {
4539       gtk_menu_stop_navigating_submenu (menu);
4540       return FALSE;
4541     }
4542 }
4543
4544 static void
4545 gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
4546                                         GtkMenuItem      *menu_item,
4547                                         GdkEventCrossing *event)
4548 {
4549   GtkMenuPrivate *priv = menu->priv;
4550   gint submenu_left = 0;
4551   gint submenu_right = 0;
4552   gint submenu_top = 0;
4553   gint submenu_bottom = 0;
4554   gint width = 0;
4555   GtkWidget *event_widget;
4556   GtkMenuPopdownData *popdown_data;
4557   GdkWindow *window;
4558
4559   g_return_if_fail (menu_item->priv->submenu != NULL);
4560   g_return_if_fail (event != NULL);
4561
4562   event_widget = gtk_get_event_widget ((GdkEvent*) event);
4563
4564   window = gtk_widget_get_window (menu_item->priv->submenu);
4565   gdk_window_get_origin (window, &submenu_left, &submenu_top);
4566
4567   submenu_right = submenu_left + gdk_window_get_width (window);
4568   submenu_bottom = submenu_top + gdk_window_get_height (window);
4569
4570   width = gdk_window_get_width (gtk_widget_get_window (event_widget));
4571
4572   if (event->x >= 0 && event->x < width)
4573     {
4574       gint popdown_delay;
4575
4576       gtk_menu_stop_navigating_submenu (menu);
4577
4578       /* The navigation region is the triangle closest to the x/y
4579        * location of the rectangle. This is why the width or height
4580        * can be negative.
4581        */
4582       if (menu_item->priv->submenu_direction == GTK_DIRECTION_RIGHT)
4583         {
4584           /* right */
4585           priv->navigation_x = submenu_left;
4586           priv->navigation_width = event->x_root - submenu_left;
4587         }
4588       else
4589         {
4590           /* left */
4591           priv->navigation_x = submenu_right;
4592           priv->navigation_width = event->x_root - submenu_right;
4593         }
4594
4595       if (event->y < 0)
4596         {
4597           /* top */
4598           priv->navigation_y = event->y_root;
4599           priv->navigation_height = submenu_top - event->y_root - NAVIGATION_REGION_OVERSHOOT;
4600
4601           if (priv->navigation_height >= 0)
4602             return;
4603         }
4604       else
4605         {
4606           /* bottom */
4607           priv->navigation_y = event->y_root;
4608           priv->navigation_height = submenu_bottom - event->y_root + NAVIGATION_REGION_OVERSHOOT;
4609
4610           if (priv->navigation_height <= 0)
4611             return;
4612         }
4613
4614       g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
4615                     "gtk-menu-popdown-delay", &popdown_delay,
4616                     NULL);
4617
4618       popdown_data = g_new (GtkMenuPopdownData, 1);
4619       popdown_data->menu = menu;
4620       popdown_data->device = gdk_event_get_device ((GdkEvent *) event);
4621
4622       priv->navigation_timeout = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT,
4623                                                                popdown_delay,
4624                                                                gtk_menu_stop_navigating_submenu_cb,
4625                                                                popdown_data,
4626                                                                (GDestroyNotify) g_free);
4627     }
4628 }
4629
4630 static void
4631 gtk_menu_deactivate (GtkMenuShell *menu_shell)
4632 {
4633   GtkWidget *parent;
4634
4635   g_return_if_fail (GTK_IS_MENU (menu_shell));
4636
4637   parent = menu_shell->priv->parent_menu_shell;
4638
4639   menu_shell->priv->activate_time = 0;
4640   gtk_menu_popdown (GTK_MENU (menu_shell));
4641
4642   if (parent)
4643     gtk_menu_shell_deactivate (GTK_MENU_SHELL (parent));
4644 }
4645
4646 static void
4647 gtk_menu_position (GtkMenu  *menu,
4648                    gboolean  set_scroll_offset)
4649 {
4650   GtkMenuPrivate *priv = menu->priv;
4651   GtkWidget *widget;
4652   GtkRequisition requisition;
4653   gint x, y;
4654   gint scroll_offset;
4655   gint menu_height;
4656   GdkScreen *screen;
4657   GdkScreen *pointer_screen;
4658   GdkRectangle monitor;
4659   GdkDevice *pointer;
4660
4661   widget = GTK_WIDGET (menu);
4662
4663   screen = gtk_widget_get_screen (widget);
4664   pointer = _gtk_menu_shell_get_grab_device (GTK_MENU_SHELL (menu));
4665   gdk_device_get_position (pointer, &pointer_screen, &x, &y);
4666
4667   /* Realize so we have the proper width and heigh to figure out
4668    * the right place to popup the menu.
4669    */
4670   gtk_widget_realize (priv->toplevel);
4671   requisition.width = gtk_widget_get_allocated_width (widget);
4672   requisition.height = gtk_widget_get_allocated_height (widget);
4673
4674   if (pointer_screen != screen)
4675     {
4676       /* Pointer is on a different screen; roughly center the
4677        * menu on the screen. If someone was using multiscreen
4678        * + Xinerama together they'd probably want something
4679        * fancier; but that is likely to be vanishingly rare.
4680        */
4681       x = MAX (0, (gdk_screen_get_width (screen) - requisition.width) / 2);
4682       y = MAX (0, (gdk_screen_get_height (screen) - requisition.height) / 2);
4683     }
4684
4685   priv->monitor_num = gdk_screen_get_monitor_at_point (screen, x, y);
4686   priv->initially_pushed_in = FALSE;
4687
4688   /* Set the type hint here to allow custom position functions
4689    * to set a different hint
4690    */
4691   if (!gtk_widget_get_visible (priv->toplevel))
4692     gtk_window_set_type_hint (GTK_WINDOW (priv->toplevel), GDK_WINDOW_TYPE_HINT_POPUP_MENU);
4693
4694   if (priv->position_func)
4695     {
4696       (* priv->position_func) (menu, &x, &y, &priv->initially_pushed_in,
4697                                priv->position_func_data);
4698
4699       if (priv->monitor_num < 0)
4700         priv->monitor_num = gdk_screen_get_monitor_at_point (screen, x, y);
4701
4702       gdk_screen_get_monitor_geometry (screen, priv->monitor_num, &monitor);
4703     }
4704   else
4705     {
4706       gint space_left, space_right, space_above, space_below;
4707       gint needed_width;
4708       gint needed_height;
4709       GtkBorder padding;
4710       gboolean rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
4711
4712       get_menu_padding (widget, &padding);
4713
4714       /* The placement of popup menus horizontally works like this (with
4715        * RTL in parentheses)
4716        *
4717        * - If there is enough room to the right (left) of the mouse cursor,
4718        *   position the menu there.
4719        *
4720        * - Otherwise, if if there is enough room to the left (right) of the
4721        *   mouse cursor, position the menu there.
4722        *
4723        * - Otherwise if the menu is smaller than the monitor, position it
4724        *   on the side of the mouse cursor that has the most space available
4725        *
4726        * - Otherwise (if there is simply not enough room for the menu on the
4727        *   monitor), position it as far left (right) as possible.
4728        *
4729        * Positioning in the vertical direction is similar: first try below
4730        * mouse cursor, then above.
4731        */
4732       gdk_screen_get_monitor_geometry (screen, priv->monitor_num, &monitor);
4733
4734       space_left = x - monitor.x;
4735       space_right = monitor.x + monitor.width - x - 1;
4736       space_above = y - monitor.y;
4737       space_below = monitor.y + monitor.height - y - 1;
4738
4739       /* Position horizontally. */
4740
4741       /* the amount of space we need to position the menu.
4742        * Note the menu is offset "thickness" pixels
4743        */
4744       needed_width = requisition.width - padding.left;
4745
4746       if (needed_width <= space_left ||
4747           needed_width <= space_right)
4748         {
4749           if ((rtl  && needed_width <= space_left) ||
4750               (!rtl && needed_width >  space_right))
4751             {
4752               /* position left */
4753               x = x + padding.left - requisition.width + 1;
4754             }
4755           else
4756             {
4757               /* position right */
4758               x = x - padding.right;
4759             }
4760
4761           /* x is clamped on-screen further down */
4762         }
4763       else if (requisition.width <= monitor.width)
4764         {
4765           /* the menu is too big to fit on either side of the mouse
4766            * cursor, but smaller than the monitor. Position it on
4767            * the side that has the most space
4768            */
4769           if (space_left > space_right)
4770             {
4771               /* left justify */
4772               x = monitor.x;
4773             }
4774           else
4775             {
4776               /* right justify */
4777               x = monitor.x + monitor.width - requisition.width;
4778             }
4779         }
4780       else /* menu is simply too big for the monitor */
4781         {
4782           if (rtl)
4783             {
4784               /* right justify */
4785               x = monitor.x + monitor.width - requisition.width;
4786             }
4787           else
4788             {
4789               /* left justify */
4790               x = monitor.x;
4791             }
4792         }
4793
4794       /* Position vertically.
4795        * The algorithm is the same as above, but simpler
4796        * because we don't have to take RTL into account.
4797        */
4798       needed_height = requisition.height - padding.top;
4799
4800       if (needed_height <= space_above ||
4801           needed_height <= space_below)
4802         {
4803           if (needed_height <= space_below)
4804             y = y - padding.top;
4805           else
4806             y = y + padding.bottom - requisition.height + 1;
4807
4808           y = CLAMP (y, monitor.y,
4809                      monitor.y + monitor.height - requisition.height);
4810         }
4811       else if (needed_height > space_below && needed_height > space_above)
4812         {
4813           if (space_below >= space_above)
4814             y = monitor.y + monitor.height - requisition.height;
4815           else
4816             y = monitor.y;
4817         }
4818       else
4819         {
4820           y = monitor.y;
4821         }
4822     }
4823
4824   scroll_offset = 0;
4825
4826   if (priv->initially_pushed_in)
4827     {
4828       menu_height = requisition.height;
4829
4830       if (y + menu_height > monitor.y + monitor.height)
4831         {
4832           scroll_offset -= y + menu_height - (monitor.y + monitor.height);
4833           y = (monitor.y + monitor.height) - menu_height;
4834         }
4835
4836       if (y < monitor.y)
4837         {
4838           scroll_offset += monitor.y - y;
4839           y = monitor.y;
4840         }
4841     }
4842
4843   /* FIXME: should this be done in the various position_funcs ? */
4844   x = CLAMP (x, monitor.x, MAX (monitor.x, monitor.x + monitor.width - requisition.width));
4845
4846   if (GTK_MENU_SHELL (menu)->priv->active)
4847     {
4848       priv->have_position = TRUE;
4849       priv->position_x = x;
4850       priv->position_y = y;
4851     }
4852
4853   if (y + requisition.height > monitor.y + monitor.height)
4854     requisition.height = (monitor.y + monitor.height) - y;
4855
4856   if (y < monitor.y)
4857     {
4858       scroll_offset += monitor.y - y;
4859       requisition.height -= monitor.y - y;
4860       y = monitor.y;
4861     }
4862
4863   if (scroll_offset > 0)
4864     {
4865       GtkBorder arrow_border;
4866
4867       get_arrows_border (menu, &arrow_border);
4868       scroll_offset += arrow_border.top;
4869     }
4870
4871   gtk_window_move (GTK_WINDOW (GTK_MENU_SHELL (menu)->priv->active ? priv->toplevel : priv->tearoff_window),
4872                    x, y);
4873
4874   if (!GTK_MENU_SHELL (menu)->priv->active)
4875     {
4876       gtk_window_resize (GTK_WINDOW (priv->tearoff_window),
4877                          requisition.width, requisition.height);
4878     }
4879
4880   if (set_scroll_offset)
4881     priv->scroll_offset = scroll_offset;
4882 }
4883
4884 static void
4885 gtk_menu_remove_scroll_timeout (GtkMenu *menu)
4886 {
4887   GtkMenuPrivate *priv = menu->priv;
4888
4889   if (priv->scroll_timeout)
4890     {
4891       g_source_remove (priv->scroll_timeout);
4892       priv->scroll_timeout = 0;
4893     }
4894 }
4895
4896 static void
4897 gtk_menu_stop_scrolling (GtkMenu *menu)
4898 {
4899   GtkMenuPrivate *priv = menu->priv;
4900   gboolean touchscreen_mode;
4901
4902   gtk_menu_remove_scroll_timeout (menu);
4903
4904   g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
4905                 "gtk-touchscreen-mode", &touchscreen_mode,
4906                 NULL);
4907
4908   if (!touchscreen_mode)
4909     {
4910       priv->upper_arrow_prelight = FALSE;
4911       priv->lower_arrow_prelight = FALSE;
4912     }
4913 }
4914
4915 static void
4916 gtk_menu_scroll_to (GtkMenu *menu,
4917                     gint    offset)
4918 {
4919   GtkMenuPrivate *priv = menu->priv;
4920   GtkAllocation allocation;
4921   GtkBorder arrow_border, padding;
4922   GtkWidget *widget;
4923   gint x, y;
4924   gint view_width, view_height;
4925   gint border_width;
4926   gint menu_height;
4927   guint vertical_padding;
4928   guint horizontal_padding;
4929   gboolean double_arrows;
4930
4931   widget = GTK_WIDGET (menu);
4932
4933   if (priv->tearoff_active && priv->tearoff_adjustment)
4934     gtk_adjustment_set_value (priv->tearoff_adjustment, offset);
4935
4936   /* Move/resize the viewport according to arrows: */
4937   gtk_widget_get_allocation (widget, &allocation);
4938   view_width = allocation.width;
4939   view_height = allocation.height;
4940
4941   gtk_widget_style_get (GTK_WIDGET (menu),
4942                         "vertical-padding", &vertical_padding,
4943                         "horizontal-padding", &horizontal_padding,
4944                         NULL);
4945
4946   get_menu_padding (widget, &padding);
4947   double_arrows = get_double_arrows (menu);
4948
4949   border_width = gtk_container_get_border_width (GTK_CONTAINER (menu));
4950
4951   view_width -= (2 * (border_width + horizontal_padding)) + padding.left + padding.right;
4952   view_height -= (2 * (border_width + vertical_padding)) + padding.top + padding.bottom;
4953   menu_height = priv->requested_height - (2 * (border_width + vertical_padding)) -
4954     padding.top - padding.bottom;
4955
4956   x = border_width + padding.left + horizontal_padding;
4957   y = border_width + padding.top + vertical_padding;
4958
4959   if (double_arrows && !priv->tearoff_active)
4960     {
4961       if (view_height < menu_height               ||
4962           (offset > 0 && priv->scroll_offset > 0) ||
4963           (offset < 0 && priv->scroll_offset < 0))
4964         {
4965           GtkStateFlags upper_arrow_previous_state = priv->upper_arrow_state;
4966           GtkStateFlags lower_arrow_previous_state = priv->lower_arrow_state;
4967
4968           if (!priv->upper_arrow_visible || !priv->lower_arrow_visible)
4969             gtk_widget_queue_draw (GTK_WIDGET (menu));
4970
4971           priv->upper_arrow_visible = priv->lower_arrow_visible = TRUE;
4972
4973           get_arrows_border (menu, &arrow_border);
4974           y += arrow_border.top;
4975           view_height -= arrow_border.top;
4976           view_height -= arrow_border.bottom;
4977
4978           if (offset <= 0)
4979             priv->upper_arrow_state |= GTK_STATE_FLAG_INSENSITIVE;
4980           else
4981             {
4982               priv->upper_arrow_state &= ~(GTK_STATE_FLAG_INSENSITIVE);
4983
4984               if (priv->upper_arrow_prelight)
4985                 priv->upper_arrow_state |= GTK_STATE_FLAG_PRELIGHT;
4986               else
4987                 priv->upper_arrow_state &= ~(GTK_STATE_FLAG_PRELIGHT);
4988             }
4989
4990           if (offset >= menu_height - view_height)
4991             priv->lower_arrow_state |= GTK_STATE_FLAG_INSENSITIVE;
4992           else
4993             {
4994               priv->lower_arrow_state &= ~(GTK_STATE_FLAG_INSENSITIVE);
4995
4996               if (priv->lower_arrow_prelight)
4997                 priv->lower_arrow_state |= GTK_STATE_FLAG_PRELIGHT;
4998               else
4999                 priv->lower_arrow_state &= ~(GTK_STATE_FLAG_PRELIGHT);
5000             }
5001
5002           if ((priv->upper_arrow_state != upper_arrow_previous_state) ||
5003               (priv->lower_arrow_state != lower_arrow_previous_state))
5004             gtk_widget_queue_draw (GTK_WIDGET (menu));
5005
5006           if ((upper_arrow_previous_state & GTK_STATE_FLAG_INSENSITIVE) == 0 &&
5007               (priv->upper_arrow_state & GTK_STATE_FLAG_INSENSITIVE) != 0)
5008             {
5009               /* At the upper border, possibly remove timeout */
5010               if (priv->scroll_step < 0)
5011                 {
5012                   gtk_menu_stop_scrolling (menu);
5013                   gtk_widget_queue_draw (GTK_WIDGET (menu));
5014                 }
5015             }
5016
5017           if ((lower_arrow_previous_state & GTK_STATE_FLAG_INSENSITIVE) == 0 &&
5018               (priv->lower_arrow_state & GTK_STATE_FLAG_INSENSITIVE) != 0)
5019             {
5020               /* At the lower border, possibly remove timeout */
5021               if (priv->scroll_step > 0)
5022                 {
5023                   gtk_menu_stop_scrolling (menu);
5024                   gtk_widget_queue_draw (GTK_WIDGET (menu));
5025                 }
5026             }
5027         }
5028       else if (priv->upper_arrow_visible || priv->lower_arrow_visible)
5029         {
5030           offset = 0;
5031
5032           priv->upper_arrow_visible = priv->lower_arrow_visible = FALSE;
5033           priv->upper_arrow_prelight = priv->lower_arrow_prelight = FALSE;
5034
5035           gtk_menu_stop_scrolling (menu);
5036           gtk_widget_queue_draw (GTK_WIDGET (menu));
5037         }
5038     }
5039   else if (!priv->tearoff_active)
5040     {
5041       gboolean last_visible;
5042
5043       last_visible = priv->upper_arrow_visible;
5044       priv->upper_arrow_visible = offset > 0;
5045
5046       /* upper_arrow_visible may have changed, so requery the border */
5047       get_arrows_border (menu, &arrow_border);
5048       view_height -= arrow_border.top;
5049
5050       if ((last_visible != priv->upper_arrow_visible) &&
5051           !priv->upper_arrow_visible)
5052         {
5053           priv->upper_arrow_prelight = FALSE;
5054
5055           /* If we hide the upper arrow, possibly remove timeout */
5056           if (priv->scroll_step < 0)
5057             {
5058               gtk_menu_stop_scrolling (menu);
5059               gtk_widget_queue_draw (GTK_WIDGET (menu));
5060             }
5061         }
5062
5063       last_visible = priv->lower_arrow_visible;
5064       priv->lower_arrow_visible = offset < menu_height - view_height;
5065
5066       /* lower_arrow_visible may have changed, so requery the border */
5067       get_arrows_border (menu, &arrow_border);
5068       view_height -= arrow_border.bottom;
5069
5070       if ((last_visible != priv->lower_arrow_visible) &&
5071            !priv->lower_arrow_visible)
5072         {
5073           priv->lower_arrow_prelight = FALSE;
5074
5075           /* If we hide the lower arrow, possibly remove timeout */
5076           if (priv->scroll_step > 0)
5077             {
5078               gtk_menu_stop_scrolling (menu);
5079               gtk_widget_queue_draw (GTK_WIDGET (menu));
5080             }
5081         }
5082
5083       y += arrow_border.top;
5084     }
5085
5086   /* Scroll the menu: */
5087   if (gtk_widget_get_realized (widget))
5088     gdk_window_move (priv->bin_window, 0, -offset);
5089
5090   if (gtk_widget_get_realized (widget))
5091     gdk_window_move_resize (priv->view_window, x, y, view_width, view_height);
5092
5093   priv->scroll_offset = offset;
5094 }
5095
5096 static gboolean
5097 compute_child_offset (GtkMenu   *menu,
5098                       GtkWidget *menu_item,
5099                       gint      *offset,
5100                       gint      *height,
5101                       gboolean  *is_last_child)
5102 {
5103   GtkMenuPrivate *priv = menu->priv;
5104   gint item_top_attach;
5105   gint item_bottom_attach;
5106   gint child_offset = 0;
5107   gint i;
5108
5109   get_effective_child_attach (menu_item, NULL, NULL,
5110                               &item_top_attach, &item_bottom_attach);
5111
5112   /* there is a possibility that we get called before _size_request,
5113    * so check the height table for safety.
5114    */
5115   if (!priv->heights || priv->heights_length < gtk_menu_get_n_rows (menu))
5116     return FALSE;
5117
5118   /* when we have a row with only invisible children, its height will
5119    * be zero, so there's no need to check WIDGET_VISIBLE here
5120    */
5121   for (i = 0; i < item_top_attach; i++)
5122     child_offset += priv->heights[i];
5123
5124   if (is_last_child)
5125     *is_last_child = (item_bottom_attach == gtk_menu_get_n_rows (menu));
5126   if (offset)
5127     *offset = child_offset;
5128   if (height)
5129     *height = priv->heights[item_top_attach];
5130
5131   return TRUE;
5132 }
5133
5134 static void
5135 gtk_menu_scroll_item_visible (GtkMenuShell *menu_shell,
5136                               GtkWidget    *menu_item)
5137 {
5138   GtkMenu *menu = GTK_MENU (menu_shell);
5139   GtkMenuPrivate *priv = menu->priv;
5140   GtkWidget *widget = GTK_WIDGET (menu_shell);
5141   gint child_offset, child_height;
5142   gint height;
5143   gint y;
5144   gint arrow_height;
5145   gboolean last_child = 0;
5146
5147   /* We need to check if the selected item fully visible.
5148    * If not we need to scroll the menu so that it becomes fully
5149    * visible.
5150    */
5151   if (compute_child_offset (menu, menu_item,
5152                             &child_offset, &child_height, &last_child))
5153     {
5154       guint vertical_padding;
5155       gboolean double_arrows;
5156       GtkBorder padding;
5157
5158       y = priv->scroll_offset;
5159       height = gdk_window_get_height (gtk_widget_get_window (widget));
5160
5161       gtk_widget_style_get (widget,
5162                             "vertical-padding", &vertical_padding,
5163                             NULL);
5164
5165       double_arrows = get_double_arrows (menu);
5166       get_menu_padding (widget, &padding);
5167
5168       height -= 2 * gtk_container_get_border_width (GTK_CONTAINER (menu)) +
5169                 padding.top + padding.bottom +
5170                 2 * vertical_padding;
5171       if (child_offset < y)
5172         {
5173           /* Ignore the enter event we might get if the pointer
5174            * is on the menu
5175            */
5176           menu_shell->priv->ignore_enter = TRUE;
5177           gtk_menu_scroll_to (menu, child_offset);
5178         }
5179       else
5180         {
5181           GtkBorder arrow_border;
5182
5183           arrow_height = 0;
5184
5185           get_arrows_border (menu, &arrow_border);
5186           if (!priv->tearoff_active)
5187             arrow_height = arrow_border.top + arrow_border.bottom;
5188
5189           if (child_offset + child_height > y + height - arrow_height)
5190             {
5191               arrow_height = 0;
5192               if ((!last_child && !priv->tearoff_active) || double_arrows)
5193                 arrow_height += arrow_border.bottom;
5194
5195               y = child_offset + child_height - height + arrow_height;
5196               if (((y > 0) && !priv->tearoff_active) || double_arrows)
5197                 {
5198                   /* Need upper arrow */
5199                   arrow_height += arrow_border.top;
5200                   y = child_offset + child_height - height + arrow_height;
5201                 }
5202               /* Ignore the enter event we might get if the pointer
5203                * is on the menu
5204                */
5205               menu_shell->priv->ignore_enter = TRUE;
5206               gtk_menu_scroll_to (menu, y);
5207             }
5208         }
5209     }
5210 }
5211
5212 static void
5213 gtk_menu_select_item (GtkMenuShell *menu_shell,
5214                       GtkWidget    *menu_item)
5215 {
5216   GtkMenu *menu = GTK_MENU (menu_shell);
5217
5218   if (gtk_widget_get_realized (GTK_WIDGET (menu)))
5219     gtk_menu_scroll_item_visible (menu_shell, menu_item);
5220
5221   GTK_MENU_SHELL_CLASS (gtk_menu_parent_class)->select_item (menu_shell, menu_item);
5222 }
5223
5224
5225 /* Reparent the menu, taking care of the refcounting
5226  *
5227  * If unrealize is true we force a unrealize while reparenting the parent.
5228  * This can help eliminate flicker in some cases.
5229  *
5230  * What happens is that when the menu is unrealized and then re-realized,
5231  * the allocations are as follows:
5232  *
5233  *  parent - 1x1 at (0,0)
5234  *  child1 - 100x20 at (0,0)
5235  *  child2 - 100x20 at (0,20)
5236  *  child3 - 100x20 at (0,40)
5237  *
5238  * That is, the parent is small but the children are full sized. Then,
5239  * when the queued_resize gets processed, the parent gets resized to
5240  * full size.
5241  *
5242  * But in order to eliminate flicker when scrolling, gdkgeometry-x11.c
5243  * contains the following logic:
5244  *
5245  * - if a move or resize operation on a window would change the clip
5246  *   region on the children, then before the window is resized
5247  *   the background for children is temporarily set to None, the
5248  *   move/resize done, and the background for the children restored.
5249  *
5250  * So, at the point where the parent is resized to final size, the
5251  * background for the children is temporarily None, and thus they
5252  * are not cleared to the background color and the previous background
5253  * (the image of the menu) is left in place.
5254  */
5255 static void
5256 gtk_menu_reparent (GtkMenu   *menu,
5257                    GtkWidget *new_parent,
5258                    gboolean   unrealize)
5259 {
5260   GObject *object = G_OBJECT (menu);
5261   GtkWidget *widget = GTK_WIDGET (menu);
5262   gboolean was_floating = g_object_is_floating (object);
5263
5264   g_object_ref_sink (object);
5265
5266   if (unrealize)
5267     {
5268       g_object_ref (object);
5269       gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (widget)), widget);
5270       gtk_container_add (GTK_CONTAINER (new_parent), widget);
5271       g_object_unref (object);
5272     }
5273   else
5274     gtk_widget_reparent (widget, new_parent);
5275
5276   if (was_floating)
5277     g_object_force_floating (object);
5278   else
5279     g_object_unref (object);
5280 }
5281
5282 static void
5283 gtk_menu_show_all (GtkWidget *widget)
5284 {
5285   /* Show children, but not self. */
5286   gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_show_all, NULL);
5287 }
5288
5289 /**
5290  * gtk_menu_set_screen:
5291  * @menu: a #GtkMenu
5292  * @screen: (allow-none): a #GdkScreen, or %NULL if the screen should be
5293  *          determined by the widget the menu is attached to
5294  *
5295  * Sets the #GdkScreen on which the menu will be displayed.
5296  *
5297  * Since: 2.2
5298  */
5299 void
5300 gtk_menu_set_screen (GtkMenu   *menu,
5301                      GdkScreen *screen)
5302 {
5303   g_return_if_fail (GTK_IS_MENU (menu));
5304   g_return_if_fail (screen == NULL || GDK_IS_SCREEN (screen));
5305
5306   g_object_set_data (G_OBJECT (menu), I_("gtk-menu-explicit-screen"), screen);
5307
5308   if (screen)
5309     {
5310       menu_change_screen (menu, screen);
5311     }
5312   else
5313     {
5314       GtkWidget *attach_widget = gtk_menu_get_attach_widget (menu);
5315       if (attach_widget)
5316         attach_widget_screen_changed (attach_widget, NULL, menu);
5317     }
5318 }
5319
5320 /**
5321  * gtk_menu_attach:
5322  * @menu: a #GtkMenu
5323  * @child: a #GtkMenuItem
5324  * @left_attach: The column number to attach the left side of the item to
5325  * @right_attach: The column number to attach the right side of the item to
5326  * @top_attach: The row number to attach the top of the item to
5327  * @bottom_attach: The row number to attach the bottom of the item to
5328  *
5329  * Adds a new #GtkMenuItem to a (table) menu. The number of 'cells' that
5330  * an item will occupy is specified by @left_attach, @right_attach,
5331  * @top_attach and @bottom_attach. These each represent the leftmost,
5332  * rightmost, uppermost and lower column and row numbers of the table.
5333  * (Columns and rows are indexed from zero).
5334  *
5335  * Note that this function is not related to gtk_menu_detach().
5336  *
5337  * Since: 2.4
5338  */
5339 void
5340 gtk_menu_attach (GtkMenu   *menu,
5341                  GtkWidget *child,
5342                  guint      left_attach,
5343                  guint      right_attach,
5344                  guint      top_attach,
5345                  guint      bottom_attach)
5346 {
5347   GtkMenuShell *menu_shell;
5348   GtkWidget *parent;
5349
5350   g_return_if_fail (GTK_IS_MENU (menu));
5351   g_return_if_fail (GTK_IS_MENU_ITEM (child));
5352   parent = gtk_widget_get_parent (child);
5353   g_return_if_fail (parent == NULL || parent == GTK_WIDGET (menu));
5354   g_return_if_fail (left_attach < right_attach);
5355   g_return_if_fail (top_attach < bottom_attach);
5356
5357   menu_shell = GTK_MENU_SHELL (menu);
5358
5359   if (!parent)
5360     {
5361       AttachInfo *ai = get_attach_info (child);
5362
5363       ai->left_attach = left_attach;
5364       ai->right_attach = right_attach;
5365       ai->top_attach = top_attach;
5366       ai->bottom_attach = bottom_attach;
5367
5368       menu_shell->priv->children = g_list_append (menu_shell->priv->children, child);
5369
5370       gtk_widget_set_parent (child, GTK_WIDGET (menu));
5371
5372       menu_queue_resize (menu);
5373     }
5374   else
5375     {
5376       gtk_container_child_set (GTK_CONTAINER (parent), child,
5377                                "left-attach",   left_attach,
5378                                "right-attach",  right_attach,
5379                                "top-attach",    top_attach,
5380                                "bottom-attach", bottom_attach,
5381                                NULL);
5382     }
5383 }
5384
5385 static gint
5386 gtk_menu_get_popup_delay (GtkMenuShell *menu_shell)
5387 {
5388   gint popup_delay;
5389
5390   g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_shell)),
5391                 "gtk-menu-popup-delay", &popup_delay,
5392                 NULL);
5393
5394   return popup_delay;
5395 }
5396
5397 static GtkWidget *
5398 find_child_containing (GtkMenuShell *menu_shell,
5399                        int           left,
5400                        int           right,
5401                        int           top,
5402                        int           bottom)
5403 {
5404   GList *list;
5405
5406   /* find a child which includes the area given by
5407    * left, right, top, bottom.
5408    */
5409   for (list = menu_shell->priv->children; list; list = list->next)
5410     {
5411       gint l, r, t, b;
5412
5413       if (!_gtk_menu_item_is_selectable (list->data))
5414         continue;
5415
5416       get_effective_child_attach (list->data, &l, &r, &t, &b);
5417
5418       if (l <= left && right <= r && t <= top && bottom <= b)
5419         return GTK_WIDGET (list->data);
5420     }
5421
5422   return NULL;
5423 }
5424
5425 static void
5426 gtk_menu_move_current (GtkMenuShell         *menu_shell,
5427                        GtkMenuDirectionType  direction)
5428 {
5429   GtkMenu *menu = GTK_MENU (menu_shell);
5430   gint i;
5431   gint l, r, t, b;
5432   GtkWidget *match = NULL;
5433
5434   if (gtk_widget_get_direction (GTK_WIDGET (menu_shell)) == GTK_TEXT_DIR_RTL)
5435     {
5436       switch (direction)
5437         {
5438         case GTK_MENU_DIR_CHILD:
5439           direction = GTK_MENU_DIR_PARENT;
5440           break;
5441         case GTK_MENU_DIR_PARENT:
5442           direction = GTK_MENU_DIR_CHILD;
5443           break;
5444         default: ;
5445         }
5446     }
5447
5448   /* use special table menu key bindings */
5449   if (menu_shell->priv->active_menu_item && gtk_menu_get_n_columns (menu) > 1)
5450     {
5451       get_effective_child_attach (menu_shell->priv->active_menu_item, &l, &r, &t, &b);
5452
5453       if (direction == GTK_MENU_DIR_NEXT)
5454         {
5455           for (i = b; i < gtk_menu_get_n_rows (menu); i++)
5456             {
5457               match = find_child_containing (menu_shell, l, l + 1, i, i + 1);
5458               if (match)
5459                 break;
5460             }
5461
5462           if (!match)
5463             {
5464               /* wrap around */
5465               for (i = 0; i < t; i++)
5466                 {
5467                   match = find_child_containing (menu_shell,
5468                                                  l, l + 1, i, i + 1);
5469                   if (match)
5470                     break;
5471                 }
5472             }
5473         }
5474       else if (direction == GTK_MENU_DIR_PREV)
5475         {
5476           for (i = t; i > 0; i--)
5477             {
5478               match = find_child_containing (menu_shell,
5479                                              l, l + 1, i - 1, i);
5480               if (match)
5481                 break;
5482             }
5483
5484           if (!match)
5485             {
5486               /* wrap around */
5487               for (i = gtk_menu_get_n_rows (menu); i > b; i--)
5488                 {
5489                   match = find_child_containing (menu_shell,
5490                                                  l, l + 1, i - 1, i);
5491                   if (match)
5492                     break;
5493                 }
5494             }
5495         }
5496       else if (direction == GTK_MENU_DIR_PARENT)
5497         {
5498           /* we go one left if possible */
5499           if (l > 0)
5500             match = find_child_containing (menu_shell,
5501                                            l - 1, l, t, t + 1);
5502
5503           if (!match)
5504             {
5505               GtkWidget *parent = menu_shell->priv->parent_menu_shell;
5506
5507               if (!parent
5508                   || g_list_length (GTK_MENU_SHELL (parent)->priv->children) <= 1)
5509                 match = menu_shell->priv->active_menu_item;
5510             }
5511         }
5512       else if (direction == GTK_MENU_DIR_CHILD)
5513         {
5514           /* we go one right if possible */
5515           if (r < gtk_menu_get_n_columns (menu))
5516             match = find_child_containing (menu_shell, r, r + 1, t, t + 1);
5517
5518           if (!match)
5519             {
5520               GtkWidget *parent = menu_shell->priv->parent_menu_shell;
5521
5522               if (! GTK_MENU_ITEM (menu_shell->priv->active_menu_item)->priv->submenu &&
5523                   (!parent ||
5524                    g_list_length (GTK_MENU_SHELL (parent)->priv->children) <= 1))
5525                 match = menu_shell->priv->active_menu_item;
5526             }
5527         }
5528
5529       if (match)
5530         {
5531           gtk_menu_shell_select_item (menu_shell, match);
5532           return;
5533         }
5534     }
5535
5536   GTK_MENU_SHELL_CLASS (gtk_menu_parent_class)->move_current (menu_shell, direction);
5537 }
5538
5539 static gint
5540 get_visible_size (GtkMenu *menu)
5541 {
5542   GtkMenuPrivate *priv = menu->priv;
5543   GtkAllocation allocation;
5544   GtkWidget *widget = GTK_WIDGET (menu);
5545   GtkContainer *container = GTK_CONTAINER (menu);
5546   GtkBorder padding;
5547   gint menu_height;
5548
5549   gtk_widget_get_allocation (widget, &allocation);
5550   get_menu_padding (widget, &padding);
5551
5552   menu_height = (allocation.height -
5553                  (2 * gtk_container_get_border_width (container)) -
5554                  padding.top - padding.bottom);
5555
5556   if (!priv->tearoff_active)
5557     {
5558       GtkBorder arrow_border;
5559
5560       get_arrows_border (menu, &arrow_border);
5561       menu_height -= arrow_border.top;
5562       menu_height -= arrow_border.bottom;
5563     }
5564
5565   return menu_height;
5566 }
5567
5568 /* Find the sensitive on-screen child containing @y, or if none,
5569  * the nearest selectable onscreen child. (%NULL if none)
5570  */
5571 static GtkWidget *
5572 child_at (GtkMenu *menu,
5573           gint     y)
5574 {
5575   GtkMenuPrivate *priv = menu->priv;
5576   GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
5577   GtkWidget *child = NULL;
5578   gint child_offset = 0;
5579   GList *children;
5580   gint menu_height;
5581   gint lower, upper; /* Onscreen bounds */
5582
5583   menu_height = get_visible_size (menu);
5584   lower = priv->scroll_offset;
5585   upper = priv->scroll_offset + menu_height;
5586
5587   for (children = menu_shell->priv->children; children; children = children->next)
5588     {
5589       if (gtk_widget_get_visible (children->data))
5590         {
5591           GtkRequisition child_requisition;
5592
5593           gtk_widget_get_preferred_size (children->data,
5594                                          &child_requisition, NULL);
5595
5596           if (_gtk_menu_item_is_selectable (children->data) &&
5597               child_offset >= lower &&
5598               child_offset + child_requisition.height <= upper)
5599             {
5600               child = children->data;
5601
5602               if (child_offset + child_requisition.height > y &&
5603                   !GTK_IS_TEAROFF_MENU_ITEM (child))
5604                 return child;
5605             }
5606
5607           child_offset += child_requisition.height;
5608         }
5609     }
5610
5611   return child;
5612 }
5613
5614 static gint
5615 get_menu_height (GtkMenu *menu)
5616 {
5617   GtkMenuPrivate *priv = menu->priv;
5618   GtkAllocation allocation;
5619   GtkWidget *widget = GTK_WIDGET (menu);
5620   GtkBorder padding;
5621   gint height;
5622
5623   gtk_widget_get_allocation (widget, &allocation);
5624   get_menu_padding (widget, &padding);
5625
5626   height = allocation.height;
5627   height -= (gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2) +
5628     padding.top + padding.bottom;
5629
5630   if (!priv->tearoff_active)
5631     {
5632       GtkBorder arrow_border;
5633
5634       get_arrows_border (menu, &arrow_border);
5635       height -= arrow_border.top;
5636       height -= arrow_border.bottom;
5637     }
5638
5639   return height;
5640 }
5641
5642 static void
5643 gtk_menu_real_move_scroll (GtkMenu       *menu,
5644                            GtkScrollType  type)
5645 {
5646   GtkMenuPrivate *priv = menu->priv;
5647   gint page_size = get_visible_size (menu);
5648   gint end_position = get_menu_height (menu);
5649   GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
5650
5651   switch (type)
5652     {
5653     case GTK_SCROLL_PAGE_UP:
5654     case GTK_SCROLL_PAGE_DOWN:
5655       {
5656         gint old_offset;
5657         gint new_offset;
5658         gint child_offset = 0;
5659         gboolean old_upper_arrow_visible;
5660         gint step;
5661
5662         if (type == GTK_SCROLL_PAGE_UP)
5663           step = - page_size;
5664         else
5665           step = page_size;
5666
5667         if (menu_shell->priv->active_menu_item)
5668           {
5669             gint child_height;
5670
5671             compute_child_offset (menu, menu_shell->priv->active_menu_item,
5672                                   &child_offset, &child_height, NULL);
5673             child_offset += child_height / 2;
5674           }
5675
5676         menu_shell->priv->ignore_enter = TRUE;
5677         old_upper_arrow_visible = priv->upper_arrow_visible && !priv->tearoff_active;
5678         old_offset = priv->scroll_offset;
5679
5680         new_offset = priv->scroll_offset + step;
5681         new_offset = CLAMP (new_offset, 0, end_position - page_size);
5682
5683         gtk_menu_scroll_to (menu, new_offset);
5684
5685         if (menu_shell->priv->active_menu_item)
5686           {
5687             GtkWidget *new_child;
5688             gboolean new_upper_arrow_visible = priv->upper_arrow_visible && !priv->tearoff_active;
5689             GtkBorder arrow_border;
5690             get_arrows_border (menu, &arrow_border);
5691
5692             if (priv->scroll_offset != old_offset)
5693               step = priv->scroll_offset - old_offset;
5694
5695             step -= (new_upper_arrow_visible - old_upper_arrow_visible) * arrow_border.top;
5696
5697             new_child = child_at (menu, child_offset + step);
5698             if (new_child)
5699               gtk_menu_shell_select_item (menu_shell, new_child);
5700           }
5701       }
5702       break;
5703     case GTK_SCROLL_START:
5704       /* Ignore the enter event we might get if the pointer is on the menu */
5705       menu_shell->priv->ignore_enter = TRUE;
5706       gtk_menu_scroll_to (menu, 0);
5707       gtk_menu_shell_select_first (menu_shell, TRUE);
5708       break;
5709     case GTK_SCROLL_END:
5710       /* Ignore the enter event we might get if the pointer is on the menu */
5711       menu_shell->priv->ignore_enter = TRUE;
5712       gtk_menu_scroll_to (menu, end_position - page_size);
5713       _gtk_menu_shell_select_last (menu_shell, TRUE);
5714       break;
5715     default:
5716       break;
5717     }
5718 }
5719
5720
5721 /**
5722  * gtk_menu_set_monitor:
5723  * @menu: a #GtkMenu
5724  * @monitor_num: the number of the monitor on which the menu should
5725  *    be popped up
5726  *
5727  * Informs GTK+ on which monitor a menu should be popped up.
5728  * See gdk_screen_get_monitor_geometry().
5729  *
5730  * This function should be called from a #GtkMenuPositionFunc
5731  * if the menu should not appear on the same monitor as the pointer.
5732  * This information can't be reliably inferred from the coordinates
5733  * returned by a #GtkMenuPositionFunc, since, for very long menus,
5734  * these coordinates may extend beyond the monitor boundaries or even
5735  * the screen boundaries.
5736  *
5737  * Since: 2.4
5738  */
5739 void
5740 gtk_menu_set_monitor (GtkMenu *menu,
5741                       gint     monitor_num)
5742 {
5743   GtkMenuPrivate *priv = menu->priv;
5744
5745   g_return_if_fail (GTK_IS_MENU (menu));
5746
5747   priv->monitor_num = monitor_num;
5748 }
5749
5750 /**
5751  * gtk_menu_get_monitor:
5752  * @menu: a #GtkMenu
5753  *
5754  * Retrieves the number of the monitor on which to show the menu.
5755  *
5756  * Returns: the number of the monitor on which the menu should
5757  *    be popped up or -1, if no monitor has been set
5758  *
5759  * Since: 2.14
5760  */
5761 gint
5762 gtk_menu_get_monitor (GtkMenu *menu)
5763 {
5764   g_return_val_if_fail (GTK_IS_MENU (menu), -1);
5765
5766   return menu->priv->monitor_num;
5767 }
5768
5769 /**
5770  * gtk_menu_get_for_attach_widget:
5771  * @widget: a #GtkWidget
5772  *
5773  * Returns a list of the menus which are attached to this widget.
5774  * This list is owned by GTK+ and must not be modified.
5775  *
5776  * Return value: (element-type GtkWidget) (transfer none): the list
5777  *     of menus attached to his widget.
5778  *
5779  * Since: 2.6
5780  */
5781 GList*
5782 gtk_menu_get_for_attach_widget (GtkWidget *widget)
5783 {
5784   GList *list;
5785
5786   g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
5787
5788   list = g_object_get_data (G_OBJECT (widget), ATTACHED_MENUS);
5789
5790   return list;
5791 }
5792
5793 static void
5794 gtk_menu_grab_notify (GtkWidget *widget,
5795                       gboolean   was_grabbed)
5796 {
5797   GtkWidget *toplevel;
5798   GtkWindowGroup *group;
5799   GtkWidget *grab;
5800   GdkDevice *pointer;
5801
5802   pointer = _gtk_menu_shell_get_grab_device (GTK_MENU_SHELL (widget));
5803
5804   if (!pointer ||
5805       !gtk_widget_device_is_shadowed (widget, pointer))
5806     return;
5807
5808   toplevel = gtk_widget_get_toplevel (widget);
5809
5810   if (!GTK_IS_WINDOW (toplevel))
5811     return;
5812
5813   group = gtk_window_get_group (GTK_WINDOW (toplevel));
5814   grab = gtk_window_group_get_current_device_grab (group, pointer);
5815
5816   if (GTK_MENU_SHELL (widget)->priv->active && !GTK_IS_MENU_SHELL (grab))
5817     gtk_menu_shell_cancel (GTK_MENU_SHELL (widget));
5818 }
5819
5820 /**
5821  * gtk_menu_set_reserve_toggle_size:
5822  * @menu: a #GtkMenu
5823  * @reserve_toggle_size: whether to reserve size for toggles
5824  *
5825  * Sets whether the menu should reserve space for drawing toggles
5826  * or icons, regardless of their actual presence.
5827  *
5828  * Since: 2.18
5829  */
5830 void
5831 gtk_menu_set_reserve_toggle_size (GtkMenu  *menu,
5832                                   gboolean  reserve_toggle_size)
5833 {
5834   GtkMenuPrivate *priv = menu->priv;
5835   gboolean no_toggle_size;
5836
5837   g_return_if_fail (GTK_IS_MENU (menu));
5838
5839   no_toggle_size = !reserve_toggle_size;
5840
5841   if (priv->no_toggle_size != no_toggle_size)
5842     {
5843       priv->no_toggle_size = no_toggle_size;
5844
5845       g_object_notify (G_OBJECT (menu), "reserve-toggle-size");
5846     }
5847 }
5848
5849 /**
5850  * gtk_menu_get_reserve_toggle_size:
5851  * @menu: a #GtkMenu
5852  *
5853  * Returns whether the menu reserves space for toggles and
5854  * icons, regardless of their actual presence.
5855  *
5856  * Returns: Whether the menu reserves toggle space
5857  *
5858  * Since: 2.18
5859  */
5860 gboolean
5861 gtk_menu_get_reserve_toggle_size (GtkMenu *menu)
5862 {
5863   g_return_val_if_fail (GTK_IS_MENU (menu), FALSE);
5864
5865   return !menu->priv->no_toggle_size;
5866 }