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