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