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