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