]> Pileus Git - ~andy/gtk/blob - gtk/gtkmenu.c
commiting patch after comments from Alex on IRC
[~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 #include <ctype.h>
28 #include <string.h> /* memset */
29 #include "gdk/gdkkeysyms.h"
30 #include "gtkbindings.h"
31 #include "gtklabel.h"
32 #include "gtkmain.h"
33 #include "gtkmenu.h"
34 #include "gtkmenuitem.h"
35 #include "gtksignal.h"
36 #include "gtkwindow.h"
37 #include "gtkhbox.h"
38 #include "gtkvscrollbar.h"
39 #include "gtksettings.h"
40
41
42 #define MENU_ITEM_CLASS(w)   GTK_MENU_ITEM_GET_CLASS (w)
43 #define MENU_NEEDS_RESIZE(m) GTK_MENU_SHELL (m)->menu_flag
44
45 #define SUBMENU_NAV_REGION_PADDING 2
46 #define SUBMENU_NAV_HYSTERESIS_TIMEOUT 333
47
48 #define MENU_SCROLL_STEP 10
49 #define MENU_SCROLL_ARROW_HEIGHT 16
50 #define MENU_SCROLL_FAST_ZONE 4
51 #define MENU_SCROLL_TIMEOUT1 150
52 #define MENU_SCROLL_TIMEOUT2 50
53
54 typedef struct _GtkMenuAttachData       GtkMenuAttachData;
55
56 struct _GtkMenuAttachData
57 {
58   GtkWidget *attach_widget;
59   GtkMenuDetachFunc detacher;
60 };
61
62
63 static void     gtk_menu_class_init        (GtkMenuClass     *klass);
64 static void     gtk_menu_init              (GtkMenu          *menu);
65 static void     gtk_menu_destroy           (GtkObject        *object);
66 static void     gtk_menu_realize           (GtkWidget        *widget);
67 static void     gtk_menu_unrealize         (GtkWidget        *widget);
68 static void     gtk_menu_size_request      (GtkWidget        *widget,
69                                             GtkRequisition   *requisition);
70 static void     gtk_menu_size_allocate     (GtkWidget        *widget,
71                                             GtkAllocation    *allocation);
72 static void     gtk_menu_paint             (GtkWidget        *widget);
73 static gboolean gtk_menu_expose            (GtkWidget        *widget,
74                                             GdkEventExpose   *event);
75 static gboolean gtk_menu_key_press         (GtkWidget        *widget,
76                                             GdkEventKey      *event);
77 static gboolean gtk_menu_motion_notify     (GtkWidget        *widget,
78                                             GdkEventMotion   *event);
79 static gboolean gtk_menu_enter_notify      (GtkWidget        *widget,
80                                             GdkEventCrossing *event);
81 static gboolean gtk_menu_leave_notify      (GtkWidget        *widget,
82                                             GdkEventCrossing *event);
83 static void     gtk_menu_scroll_to         (GtkMenu          *menu,
84                                             gint              offset);
85 static void     gtk_menu_stop_scrolling    (GtkMenu          *menu);
86 static gboolean gtk_menu_scroll_timeout    (gpointer          data);
87 static void     gtk_menu_select_item       (GtkMenuShell     *menu_shell,
88                                             GtkWidget        *menu_item);
89 static void     gtk_menu_insert            (GtkMenuShell     *menu_shell,
90                                             GtkWidget        *child,
91                                             gint              position);
92 static void     gtk_menu_scrollbar_changed (GtkAdjustment    *adjustment,
93                                             GtkMenu          *menu);
94 static void     gtk_menu_handle_scrolling  (GtkMenu          *menu,
95                                             gboolean         enter);
96 static void     gtk_menu_set_tearoff_hints (GtkMenu          *menu,
97                                             gint             width);
98
99 static void     gtk_menu_stop_navigating_submenu       (GtkMenu          *menu);
100 static gboolean gtk_menu_stop_navigating_submenu_cb    (gpointer          user_data);
101 static gboolean gtk_menu_navigating_submenu            (GtkMenu          *menu,
102                                                         gint              event_x,
103                                                         gint              event_y);
104 static void     gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
105                                                         GtkMenuItem      *menu_item,
106                                                         GdkEventCrossing *event);
107  
108 static void gtk_menu_deactivate     (GtkMenuShell      *menu_shell);
109 static void gtk_menu_show_all       (GtkWidget         *widget);
110 static void gtk_menu_hide_all       (GtkWidget         *widget);
111 static void gtk_menu_position       (GtkMenu           *menu);
112 static void gtk_menu_reparent       (GtkMenu           *menu, 
113                                      GtkWidget         *new_parent, 
114                                      gboolean           unrealize);
115 static void gtk_menu_remove         (GtkContainer      *menu,
116                                      GtkWidget         *widget);
117
118 static GtkMenuShellClass *parent_class = NULL;
119 static const gchar       *attach_data_key = "gtk-menu-attach-data";
120
121 GtkType
122 gtk_menu_get_type (void)
123 {
124   static GtkType menu_type = 0;
125   
126   if (!menu_type)
127     {
128       static const GtkTypeInfo menu_info =
129       {
130         "GtkMenu",
131         sizeof (GtkMenu),
132         sizeof (GtkMenuClass),
133         (GtkClassInitFunc) gtk_menu_class_init,
134         (GtkObjectInitFunc) gtk_menu_init,
135         /* reserved_1 */ NULL,
136         /* reserved_2 */ NULL,
137         (GtkClassInitFunc) NULL,
138       };
139       
140       menu_type = gtk_type_unique (gtk_menu_shell_get_type (), &menu_info);
141     }
142   
143   return menu_type;
144 }
145
146 static void
147 gtk_menu_class_init (GtkMenuClass *class)
148 {
149   GtkObjectClass *object_class;
150   GtkWidgetClass *widget_class;
151   GtkContainerClass *container_class;
152   GtkMenuShellClass *menu_shell_class;
153
154   GtkBindingSet *binding_set;
155   
156   object_class = (GtkObjectClass*) class;
157   widget_class = (GtkWidgetClass*) class;
158   container_class = (GtkContainerClass*) class;
159   menu_shell_class = (GtkMenuShellClass*) class;
160   parent_class = gtk_type_class (gtk_menu_shell_get_type ());
161   
162   object_class->destroy = gtk_menu_destroy;
163   
164   widget_class->realize = gtk_menu_realize;
165   widget_class->unrealize = gtk_menu_unrealize;
166   widget_class->size_request = gtk_menu_size_request;
167   widget_class->size_allocate = gtk_menu_size_allocate;
168   widget_class->expose_event = gtk_menu_expose;
169   widget_class->key_press_event = gtk_menu_key_press;
170   widget_class->motion_notify_event = gtk_menu_motion_notify;
171   widget_class->show_all = gtk_menu_show_all;
172   widget_class->hide_all = gtk_menu_hide_all;
173   widget_class->enter_notify_event = gtk_menu_enter_notify;
174   widget_class->leave_notify_event = gtk_menu_leave_notify;
175
176   container_class->remove = gtk_menu_remove;
177   
178   menu_shell_class->submenu_placement = GTK_LEFT_RIGHT;
179   menu_shell_class->deactivate = gtk_menu_deactivate;
180   menu_shell_class->select_item = gtk_menu_select_item;
181   menu_shell_class->insert = gtk_menu_insert;
182
183   binding_set = gtk_binding_set_by_class (class);
184   gtk_binding_entry_add_signal (binding_set,
185                                 GDK_Up, 0,
186                                 "move_current", 1,
187                                 GTK_TYPE_MENU_DIRECTION_TYPE,
188                                 GTK_MENU_DIR_PREV);
189   gtk_binding_entry_add_signal (binding_set,
190                                 GDK_KP_Up, 0,
191                                 "move_current", 1,
192                                 GTK_TYPE_MENU_DIRECTION_TYPE,
193                                 GTK_MENU_DIR_PREV);
194   gtk_binding_entry_add_signal (binding_set,
195                                 GDK_Down, 0,
196                                 "move_current", 1,
197                                 GTK_TYPE_MENU_DIRECTION_TYPE,
198                                 GTK_MENU_DIR_NEXT);
199   gtk_binding_entry_add_signal (binding_set,
200                                 GDK_KP_Down, 0,
201                                 "move_current", 1,
202                                 GTK_TYPE_MENU_DIRECTION_TYPE,
203                                 GTK_MENU_DIR_NEXT);
204   gtk_binding_entry_add_signal (binding_set,
205                                 GDK_Left, 0,
206                                 "move_current", 1,
207                                 GTK_TYPE_MENU_DIRECTION_TYPE,
208                                 GTK_MENU_DIR_PARENT);
209   gtk_binding_entry_add_signal (binding_set,
210                                 GDK_KP_Left, 0,
211                                 "move_current", 1,
212                                 GTK_TYPE_MENU_DIRECTION_TYPE,
213                                 GTK_MENU_DIR_PARENT);
214   gtk_binding_entry_add_signal (binding_set,
215                                 GDK_Right, 0,
216                                 "move_current", 1,
217                                 GTK_TYPE_MENU_DIRECTION_TYPE,
218                                 GTK_MENU_DIR_CHILD);
219   gtk_binding_entry_add_signal (binding_set,
220                                 GDK_KP_Right, 0,
221                                 "move_current", 1,
222                                 GTK_TYPE_MENU_DIRECTION_TYPE,
223                                 GTK_MENU_DIR_CHILD);
224 }
225
226 static gboolean
227 gtk_menu_window_event (GtkWidget *window,
228                        GdkEvent  *event,
229                        GtkWidget *menu)
230 {
231   gboolean handled = FALSE;
232
233   gtk_widget_ref (window);
234   gtk_widget_ref (menu);
235
236   switch (event->type)
237     {
238     case GDK_KEY_PRESS:
239     case GDK_KEY_RELEASE:
240       handled = gtk_widget_event (menu, event);
241       break;
242     default:
243       break;
244     }
245
246   gtk_widget_unref (window);
247   gtk_widget_unref (menu);
248
249   return handled;
250 }
251
252 static void
253 gtk_menu_init (GtkMenu *menu)
254 {
255   menu->parent_menu_item = NULL;
256   menu->old_active_menu_item = NULL;
257   menu->accel_group = NULL;
258   menu->position_func = NULL;
259   menu->position_func_data = NULL;
260   menu->toggle_size = 0;
261
262   menu->toplevel = g_object_connect (gtk_widget_new (GTK_TYPE_WINDOW,
263                                                      "type", GTK_WINDOW_POPUP,
264                                                      "child", menu,
265                                                      NULL),
266                                      "signal::event", gtk_menu_window_event, menu,
267                                      "signal::destroy", gtk_widget_destroyed, &menu->toplevel,
268                                      NULL);
269   gtk_window_set_policy (GTK_WINDOW (menu->toplevel),
270                          FALSE, FALSE, TRUE);
271   gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->toplevel), 0);
272
273   /* Refloat the menu, so that reference counting for the menu isn't
274    * affected by it being a child of the toplevel
275    */
276   GTK_WIDGET_SET_FLAGS (menu, GTK_FLOATING);
277   menu->needs_destruction_ref_count = TRUE;
278
279   menu->view_window = NULL;
280   menu->bin_window = NULL;
281
282   menu->scroll_offset = 0;
283   menu->scroll_step  = 0;
284   menu->timeout_id = 0;
285   menu->scroll_fast = FALSE;
286   
287   menu->tearoff_window = NULL;
288   menu->tearoff_hbox = NULL;
289   menu->torn_off = FALSE;
290   menu->tearoff_active = FALSE;
291   menu->tearoff_adjustment = NULL;
292   menu->tearoff_scrollbar = NULL;
293
294   menu->upper_arrow_visible = FALSE;
295   menu->lower_arrow_visible = FALSE;
296   menu->upper_arrow_prelight = FALSE;
297   menu->lower_arrow_prelight = FALSE;
298   
299   MENU_NEEDS_RESIZE (menu) = TRUE;
300 }
301
302 static void
303 gtk_menu_destroy (GtkObject *object)
304 {
305   GtkMenu *menu;
306   GtkMenuAttachData *data;
307
308   g_return_if_fail (GTK_IS_MENU (object));
309
310   menu = GTK_MENU (object);
311
312   gtk_menu_stop_scrolling (menu);
313   
314   data = gtk_object_get_data (object, attach_data_key);
315   if (data)
316     gtk_menu_detach (menu);
317   
318   gtk_menu_stop_navigating_submenu (menu);
319
320   gtk_menu_set_accel_group (menu, NULL);
321
322   if (menu->old_active_menu_item)
323     {
324       gtk_widget_unref (menu->old_active_menu_item);
325       menu->old_active_menu_item = NULL;
326     }
327
328   /* Add back the reference count for being a child */
329   if (menu->needs_destruction_ref_count)
330     {
331       menu->needs_destruction_ref_count = FALSE;
332       gtk_object_ref (object);
333     }
334   
335   if (menu->toplevel)
336     gtk_widget_destroy (menu->toplevel);
337   if (menu->tearoff_window)
338     gtk_widget_destroy (menu->tearoff_window);
339
340   GTK_OBJECT_CLASS (parent_class)->destroy (object);
341 }
342
343
344 void
345 gtk_menu_attach_to_widget (GtkMenu             *menu,
346                            GtkWidget           *attach_widget,
347                            GtkMenuDetachFunc    detacher)
348 {
349   GtkMenuAttachData *data;
350   
351   g_return_if_fail (GTK_IS_MENU (menu));
352   g_return_if_fail (GTK_IS_WIDGET (attach_widget));
353   g_return_if_fail (detacher != NULL);
354   
355   /* keep this function in sync with gtk_widget_set_parent()
356    */
357   
358   data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
359   if (data)
360     {
361       g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
362                  gtk_type_name (GTK_OBJECT_TYPE (data->attach_widget)));
363       return;
364     }
365   
366   gtk_object_ref (GTK_OBJECT (menu));
367   gtk_object_sink (GTK_OBJECT (menu));
368   
369   data = g_new (GtkMenuAttachData, 1);
370   data->attach_widget = attach_widget;
371   data->detacher = detacher;
372   gtk_object_set_data (GTK_OBJECT (menu), attach_data_key, data);
373   
374   if (GTK_WIDGET_STATE (menu) != GTK_STATE_NORMAL)
375     gtk_widget_set_state (GTK_WIDGET (menu), GTK_STATE_NORMAL);
376   
377   /* we don't need to set the style here, since
378    * we are a toplevel widget.
379    */
380 }
381
382 GtkWidget*
383 gtk_menu_get_attach_widget (GtkMenu *menu)
384 {
385   GtkMenuAttachData *data;
386   
387   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
388   
389   data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
390   if (data)
391     return data->attach_widget;
392   return NULL;
393 }
394
395 void
396 gtk_menu_detach (GtkMenu *menu)
397 {
398   GtkMenuAttachData *data;
399   
400   g_return_if_fail (GTK_IS_MENU (menu));
401   
402   /* keep this function in sync with gtk_widget_unparent()
403    */
404   data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
405   if (!data)
406     {
407       g_warning ("gtk_menu_detach(): menu is not attached");
408       return;
409     }
410   gtk_object_remove_data (GTK_OBJECT (menu), attach_data_key);
411   
412   data->detacher (data->attach_widget, menu);
413   
414   if (GTK_WIDGET_REALIZED (menu))
415     gtk_widget_unrealize (GTK_WIDGET (menu));
416   
417   g_free (data);
418   
419   gtk_widget_unref (GTK_WIDGET (menu));
420 }
421
422 void 
423 gtk_menu_remove (GtkContainer *container,
424                  GtkWidget    *widget)
425 {
426   GtkMenu *menu;
427   g_return_if_fail (GTK_IS_MENU (container));
428   g_return_if_fail (GTK_IS_MENU_ITEM (widget));
429
430   menu = GTK_MENU (container);
431
432   /* Clear out old_active_menu_item if it matches the item we are removing
433    */
434   if (menu->old_active_menu_item == widget)
435     {
436       gtk_widget_unref (menu->old_active_menu_item);
437       menu->old_active_menu_item = NULL;
438     }
439
440   GTK_CONTAINER_CLASS (parent_class)->remove (container, widget);
441 }
442
443
444 GtkWidget*
445 gtk_menu_new (void)
446 {
447   return GTK_WIDGET (gtk_type_new (gtk_menu_get_type ()));
448 }
449
450 static void
451 gtk_menu_insert (GtkMenuShell     *menu_shell,
452                  GtkWidget        *child,
453                  gint              position)
454 {
455   if (GTK_WIDGET_REALIZED (menu_shell))
456     gtk_widget_set_parent_window (child, GTK_MENU (menu_shell)->bin_window);
457   
458   GTK_MENU_SHELL_CLASS (parent_class)->insert (menu_shell, child, position);
459 }
460
461 static void
462 gtk_menu_tearoff_bg_copy (GtkMenu *menu)
463 {
464   GtkWidget *widget;
465   gint width, height;
466
467   widget = GTK_WIDGET (menu);
468
469   if (menu->torn_off)
470     {
471       GdkPixmap *pixmap;
472       GdkGC *gc;
473       GdkGCValues gc_values;
474
475       menu->tearoff_active = FALSE;
476       menu->saved_scroll_offset = menu->scroll_offset;
477       
478       gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS;
479       gc = gdk_gc_new_with_values (widget->window,
480                                    &gc_values, GDK_GC_SUBWINDOW);
481       
482       gdk_window_get_size (menu->tearoff_window->window, &width, &height);
483       
484       pixmap = gdk_pixmap_new (menu->tearoff_window->window,
485                                width,
486                                height,
487                                -1);
488
489       gdk_draw_pixmap (pixmap, gc,
490                        menu->tearoff_window->window,
491                        0, 0, 0, 0, -1, -1);
492       gdk_gc_unref (gc);
493
494       gtk_widget_set_usize (menu->tearoff_window,
495                             width,
496                             height);
497
498       gdk_window_set_back_pixmap (menu->tearoff_window->window, pixmap, FALSE);
499       gdk_pixmap_unref (pixmap);
500     }
501 }
502
503 void
504 gtk_menu_popup (GtkMenu             *menu,
505                 GtkWidget           *parent_menu_shell,
506                 GtkWidget           *parent_menu_item,
507                 GtkMenuPositionFunc  func,
508                 gpointer             data,
509                 guint                button,
510                 guint32              activate_time)
511 {
512   GtkWidget *widget;
513   GtkWidget *xgrab_shell;
514   GtkWidget *parent;
515   GdkEvent *current_event;
516   GtkMenuShell *menu_shell;
517
518   g_return_if_fail (GTK_IS_MENU (menu));
519   
520   widget = GTK_WIDGET (menu);
521   menu_shell = GTK_MENU_SHELL (menu);
522   
523   menu_shell->parent_menu_shell = parent_menu_shell;
524   menu_shell->active = TRUE;
525   menu_shell->button = button;
526
527   /* If we are popping up the menu from something other than, a button
528    * press then, as a heuristic, we ignore enter events for the menu
529    * until we get a MOTION_NOTIFY.  
530    */
531
532   current_event = gtk_get_current_event ();
533   if (current_event)
534     {
535       if ((current_event->type != GDK_BUTTON_PRESS) &&
536           (current_event->type != GDK_ENTER_NOTIFY))
537         menu_shell->ignore_enter = TRUE;
538     }
539
540   if (menu->torn_off)
541     {
542       gtk_menu_tearoff_bg_copy (menu);
543
544       gtk_menu_reparent (menu, menu->toplevel, FALSE);
545     }
546   
547   menu->parent_menu_item = parent_menu_item;
548   menu->position_func = func;
549   menu->position_func_data = data;
550   menu_shell->activate_time = activate_time;
551
552   gtk_menu_position (menu);
553
554   /* We need to show the menu _here_ because code expects to be
555    * able to tell if the menu is onscreen by looking at the
556    * GTK_WIDGET_VISIBLE (menu)
557    */
558   gtk_widget_show (GTK_WIDGET (menu));
559   gtk_widget_show (menu->toplevel);
560
561   if (current_event)
562     {
563       /* Also, if we're popping up from a key event, select the first
564        * item in the menu. Bad hack, but no better way to do it
565        * in current menu framework.
566        */
567       if (current_event->type == GDK_KEY_PRESS &&
568           GTK_MENU_SHELL (menu)->children)
569         {
570           gtk_menu_shell_select_item (GTK_MENU_SHELL (menu),
571                                       GTK_MENU_SHELL (menu)->children->data);
572         }
573       
574       gdk_event_free (current_event);
575     }
576   
577   gtk_menu_scroll_to (menu, menu->scroll_offset);
578
579   /* Find the last viewable ancestor, and make an X grab on it
580    */
581   parent = GTK_WIDGET (menu);
582   xgrab_shell = NULL;
583   while (parent)
584     {
585       gboolean viewable = TRUE;
586       GtkWidget *tmp = parent;
587       
588       while (tmp)
589         {
590           if (!GTK_WIDGET_MAPPED (tmp))
591             {
592               viewable = FALSE;
593               break;
594             }
595           tmp = tmp->parent;
596         }
597       
598       if (viewable)
599         xgrab_shell = parent;
600       
601       parent = GTK_MENU_SHELL (parent)->parent_menu_shell;
602     }
603   
604   if (xgrab_shell && (!GTK_MENU_SHELL (xgrab_shell)->have_xgrab))
605     {
606       if ((gdk_pointer_grab (xgrab_shell->window, TRUE,
607                              GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
608                              GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
609                              GDK_POINTER_MOTION_MASK,
610                              NULL, NULL, activate_time) == 0))
611         {
612           if (gdk_keyboard_grab (xgrab_shell->window, TRUE,
613                                  activate_time) == 0)
614             GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE;
615           else
616             gdk_pointer_ungrab (activate_time);
617         }
618     }
619
620   gtk_grab_add (GTK_WIDGET (menu));
621 }
622
623 void
624 gtk_menu_popdown (GtkMenu *menu)
625 {
626   GtkMenuShell *menu_shell;
627
628   g_return_if_fail (GTK_IS_MENU (menu));
629   
630   menu_shell = GTK_MENU_SHELL (menu);
631   
632   menu_shell->parent_menu_shell = NULL;
633   menu_shell->active = FALSE;
634   menu_shell->ignore_enter = FALSE;
635
636   gtk_menu_stop_scrolling (menu);
637   
638   gtk_menu_stop_navigating_submenu (menu);
639   
640   if (menu_shell->active_menu_item)
641     {
642       if (menu->old_active_menu_item)
643         gtk_widget_unref (menu->old_active_menu_item);
644       menu->old_active_menu_item = menu_shell->active_menu_item;
645       gtk_widget_ref (menu->old_active_menu_item);
646     }
647
648   gtk_menu_shell_deselect (menu_shell);
649   
650   /* The X Grab, if present, will automatically be removed when we hide
651    * the window */
652   gtk_widget_hide (menu->toplevel);
653
654   if (menu->torn_off)
655     {
656       gint width, height;
657       gdk_window_get_size (menu->tearoff_window->window, &width, &height);
658       gtk_widget_set_usize (menu->tearoff_window,
659                             -1,
660                             height);
661       
662       if (GTK_BIN (menu->toplevel)->child) 
663         {
664           gtk_menu_reparent (menu, menu->tearoff_hbox, TRUE);
665         } 
666       else
667         {
668           /* We popped up the menu from the tearoff, so we need to 
669            * release the grab - we aren't actually hiding the menu.
670            */
671           if (menu_shell->have_xgrab)
672             {
673               gdk_pointer_ungrab (GDK_CURRENT_TIME);
674               gdk_keyboard_ungrab (GDK_CURRENT_TIME);
675             }
676         }
677
678       /* gtk_menu_popdown is called each time a menu item is selected from
679        * a torn off menu. Only scroll back to the saved position if the
680        * non-tearoff menu was popped down.
681        */
682       if (!menu->tearoff_active)
683         gtk_menu_scroll_to (menu, menu->saved_scroll_offset);
684       menu->tearoff_active = TRUE;
685     }
686   else
687     gtk_widget_hide (GTK_WIDGET (menu));
688
689   menu_shell->have_xgrab = FALSE;
690   gtk_grab_remove (GTK_WIDGET (menu));
691 }
692
693 GtkWidget*
694 gtk_menu_get_active (GtkMenu *menu)
695 {
696   GtkWidget *child;
697   GList *children;
698   
699   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
700   
701   if (!menu->old_active_menu_item)
702     {
703       child = NULL;
704       children = GTK_MENU_SHELL (menu)->children;
705       
706       while (children)
707         {
708           child = children->data;
709           children = children->next;
710           
711           if (GTK_BIN (child)->child)
712             break;
713           child = NULL;
714         }
715       
716       menu->old_active_menu_item = child;
717       if (menu->old_active_menu_item)
718         gtk_widget_ref (menu->old_active_menu_item);
719     }
720   
721   return menu->old_active_menu_item;
722 }
723
724 void
725 gtk_menu_set_active (GtkMenu *menu,
726                      guint    index)
727 {
728   GtkWidget *child;
729   GList *tmp_list;
730   
731   g_return_if_fail (GTK_IS_MENU (menu));
732   
733   tmp_list = g_list_nth (GTK_MENU_SHELL (menu)->children, index);
734   if (tmp_list)
735     {
736       child = tmp_list->data;
737       if (GTK_BIN (child)->child)
738         {
739           if (menu->old_active_menu_item)
740             gtk_widget_unref (menu->old_active_menu_item);
741           menu->old_active_menu_item = child;
742           gtk_widget_ref (menu->old_active_menu_item);
743         }
744     }
745 }
746
747 void
748 gtk_menu_set_accel_group (GtkMenu       *menu,
749                           GtkAccelGroup *accel_group)
750 {
751   g_return_if_fail (GTK_IS_MENU (menu));
752   
753   if (menu->accel_group != accel_group)
754     {
755       if (menu->accel_group)
756         gtk_accel_group_unref (menu->accel_group);
757       menu->accel_group = accel_group;
758       if (menu->accel_group)
759         gtk_accel_group_ref (menu->accel_group);
760     }
761 }
762
763 GtkAccelGroup*
764 gtk_menu_get_accel_group (GtkMenu *menu)
765 {
766   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
767
768   return menu->accel_group;
769 }
770
771 void
772 gtk_menu_reposition (GtkMenu *menu)
773 {
774   g_return_if_fail (GTK_IS_MENU (menu));
775
776   if (GTK_WIDGET_DRAWABLE (menu) && !menu->torn_off)
777     gtk_menu_position (menu);
778 }
779
780 static void
781 gtk_menu_scrollbar_changed (GtkAdjustment *adjustment,
782                             GtkMenu       *menu)
783 {
784   g_return_if_fail (GTK_IS_MENU (menu));
785
786   if (adjustment->value != menu->scroll_offset)
787     gtk_menu_scroll_to (menu, adjustment->value);
788 }
789
790 static void
791 gtk_menu_set_tearoff_hints (GtkMenu *menu,
792                             gint     width)
793 {
794   GdkGeometry geometry_hints;
795   
796   if (!menu->tearoff_window)
797     return;
798
799   geometry_hints.min_width = width;
800   geometry_hints.max_width = width;
801     
802   geometry_hints.min_height = 0;
803   geometry_hints.max_height = GTK_WIDGET (menu)->requisition.height;
804   gtk_window_set_geometry_hints (GTK_WINDOW (menu->tearoff_window),
805                                  NULL,
806                                  &geometry_hints,
807                                  GDK_HINT_MAX_SIZE|GDK_HINT_MIN_SIZE);
808 }
809
810
811 void       
812 gtk_menu_set_tearoff_state (GtkMenu  *menu,
813                             gboolean  torn_off)
814 {
815   gint width, height;
816   
817   g_return_if_fail (GTK_IS_MENU (menu));
818
819   if (menu->torn_off != torn_off)
820     {
821       menu->torn_off = torn_off;
822       menu->tearoff_active = torn_off;
823       
824       if (menu->torn_off)
825         {
826           if (GTK_WIDGET_VISIBLE (menu))
827             gtk_menu_popdown (menu);
828
829           if (!menu->tearoff_window)
830             {
831               GtkWidget *attach_widget;
832               gchar *title;
833               
834               menu->tearoff_window = g_object_connect (gtk_widget_new (GTK_TYPE_WINDOW,
835                                                                        "type", GTK_WINDOW_TOPLEVEL,
836                                                                        NULL),
837                                                        "signal::destroy", gtk_widget_destroyed, &menu->tearoff_window,
838                                                        NULL);
839               gtk_window_set_type_hint (GTK_WINDOW (menu->tearoff_window),
840                                         GDK_WINDOW_TYPE_HINT_MENU);
841               gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->tearoff_window), 0);
842               gtk_widget_set_app_paintable (menu->tearoff_window, TRUE);
843               gtk_signal_connect (GTK_OBJECT (menu->tearoff_window),  
844                                   "event",
845                                   GTK_SIGNAL_FUNC (gtk_menu_window_event), 
846                                   GTK_OBJECT (menu));
847               gtk_widget_realize (menu->tearoff_window);
848               
849               title = gtk_object_get_data (GTK_OBJECT (menu), "gtk-menu-title");
850               if (!title)
851                 {
852                   attach_widget = gtk_menu_get_attach_widget (menu);
853                   if (GTK_IS_MENU_ITEM (attach_widget))
854                     {
855                       GtkWidget *child = GTK_BIN (attach_widget)->child;
856                       if (GTK_IS_LABEL (child))
857                         gtk_label_get (GTK_LABEL (child), &title);
858                     }
859                 }
860
861               if (title)
862                 gdk_window_set_title (menu->tearoff_window->window, title);
863
864               gdk_window_set_decorations (menu->tearoff_window->window, 
865                                           GDK_DECOR_ALL |
866                                           GDK_DECOR_RESIZEH |
867                                           GDK_DECOR_MINIMIZE |
868                                           GDK_DECOR_MAXIMIZE);
869               gtk_window_set_policy (GTK_WINDOW (menu->tearoff_window),
870                                      FALSE, FALSE, TRUE);
871
872               menu->tearoff_hbox = gtk_hbox_new (FALSE, FALSE);
873               gtk_container_add (GTK_CONTAINER (menu->tearoff_window), menu->tearoff_hbox);
874
875               gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
876               menu->tearoff_adjustment =
877                 GTK_ADJUSTMENT (gtk_adjustment_new (0,
878                                                     0,
879                                                     GTK_WIDGET (menu)->requisition.height,
880                                                     MENU_SCROLL_STEP,
881                                                     height/2,
882                                                     height));
883               g_object_connect (GTK_OBJECT (menu->tearoff_adjustment),
884                                 "signal::value_changed", gtk_menu_scrollbar_changed, menu,
885                                 NULL);
886               menu->tearoff_scrollbar = gtk_vscrollbar_new (menu->tearoff_adjustment);
887
888               gtk_box_pack_end (GTK_BOX (menu->tearoff_hbox),
889                                 menu->tearoff_scrollbar,
890                                 FALSE, FALSE, 0);
891               
892               if (menu->tearoff_adjustment->upper > height)
893                 gtk_widget_show (menu->tearoff_scrollbar);
894               
895               gtk_widget_show (menu->tearoff_hbox);
896             }
897           
898           gtk_menu_reparent (menu, menu->tearoff_hbox, FALSE);
899           
900           gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
901           if (GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
902             width += menu->tearoff_scrollbar->requisition.width;
903             
904           gtk_menu_set_tearoff_hints (menu, width);
905             
906           gtk_widget_realize (menu->tearoff_window);
907           gtk_menu_position (menu);
908           
909           gtk_widget_show (GTK_WIDGET (menu));
910           gtk_widget_show (menu->tearoff_window);
911
912           gtk_menu_scroll_to (menu, 0);
913
914         }
915       else
916         {
917           gtk_widget_hide (menu->tearoff_window);
918           gtk_menu_reparent (menu, menu->toplevel, FALSE);
919         }
920     }
921 }
922
923 /**
924  * gtk_menu_get_tearoff_state:
925  * @menu: a #GtkMenu
926  *
927  * Returns whether the menu is torn off. See
928  * gtk_menu_set_tearoff_state ().
929  *
930  * Return value: %TRUE if the menu is currently torn off.
931  **/
932 gboolean
933 gtk_menu_get_tearoff_state (GtkMenu *menu)
934 {
935   g_return_val_if_fail (GTK_IS_MENU (menu), FALSE);
936
937   return menu->torn_off;
938 }
939
940 void       
941 gtk_menu_set_title (GtkMenu     *menu,
942                     const gchar *title)
943 {
944   g_return_if_fail (GTK_IS_MENU (menu));
945
946   gtk_object_set_data_full (GTK_OBJECT (menu), "gtk-menu-title",
947                             g_strdup (title), (GtkDestroyNotify) g_free);
948 }
949
950 /**
951  * gtk_menu_get_title:
952  * @menu: a #GtkMenu
953  *
954  * Returns the title of the menu. See gtk_menu_set_title().
955  *
956  * Return value: the title of the menu, or %NULL if the menu has no
957  *               title set on it.
958  **/
959 G_CONST_RETURN gchar *
960 gtk_menu_get_title (GtkMenu *menu)
961 {
962   g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
963
964   return gtk_object_get_data (GTK_OBJECT (menu), "gtk-menu-title");
965 }
966
967 void
968 gtk_menu_reorder_child (GtkMenu   *menu,
969                         GtkWidget *child,
970                         gint       position)
971 {
972   GtkMenuShell *menu_shell;
973   g_return_if_fail (GTK_IS_MENU (menu));
974   g_return_if_fail (GTK_IS_MENU_ITEM (child));
975   menu_shell = GTK_MENU_SHELL (menu);
976   if (g_list_find (menu_shell->children, child))
977     {   
978       menu_shell->children = g_list_remove (menu_shell->children, child);
979       menu_shell->children = g_list_insert (menu_shell->children, child, position);   
980       if (GTK_WIDGET_VISIBLE (menu_shell))
981         gtk_widget_queue_resize (GTK_WIDGET (menu_shell));
982     }   
983 }
984
985 static void
986 gtk_menu_realize (GtkWidget *widget)
987 {
988   GdkWindowAttr attributes;
989   gint attributes_mask;
990   gint border_width;
991   GtkMenu *menu;
992   GtkWidget *child;
993   GList *children;
994
995   g_return_if_fail (GTK_IS_MENU (widget));
996
997   menu = GTK_MENU (widget);
998   
999   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1000   
1001   attributes.window_type = GDK_WINDOW_CHILD;
1002   attributes.x = widget->allocation.x;
1003   attributes.y = widget->allocation.y;
1004   attributes.width = widget->allocation.width;
1005   attributes.height = widget->allocation.height;
1006   attributes.wclass = GDK_INPUT_OUTPUT;
1007   attributes.visual = gtk_widget_get_visual (widget);
1008   attributes.colormap = gtk_widget_get_colormap (widget);
1009   
1010   attributes.event_mask = gtk_widget_get_events (widget);
1011   attributes.event_mask |= (GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK |
1012                             GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK );
1013   
1014   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1015   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
1016   gdk_window_set_user_data (widget->window, widget);
1017   
1018   border_width = GTK_CONTAINER (widget)->border_width;
1019   
1020   attributes.x = border_width + widget->style->xthickness;
1021   attributes.y = border_width + widget->style->ythickness;
1022   attributes.width = MAX (1, widget->allocation.width - attributes.x * 2);
1023   attributes.height = MAX (1, widget->allocation.height - attributes.y * 2);
1024
1025   if (menu->upper_arrow_visible)
1026     {
1027       attributes.y += MENU_SCROLL_ARROW_HEIGHT;
1028       attributes.height -= MENU_SCROLL_ARROW_HEIGHT;
1029     }
1030   if (menu->lower_arrow_visible)
1031     attributes.height -= MENU_SCROLL_ARROW_HEIGHT;
1032
1033   menu->view_window = gdk_window_new (widget->window, &attributes, attributes_mask);
1034   gdk_window_set_user_data (menu->view_window, menu);
1035
1036   attributes.x = 0;
1037   attributes.y = 0;
1038   attributes.height = MAX (1, widget->requisition.height - (border_width + widget->style->ythickness) * 2);
1039   
1040   menu->bin_window = gdk_window_new (menu->view_window, &attributes, attributes_mask);
1041   gdk_window_set_user_data (menu->bin_window, menu);
1042
1043   children = GTK_MENU_SHELL (menu)->children;
1044   while (children)
1045     {
1046       child = children->data;
1047       children = children->next;
1048           
1049       gtk_widget_set_parent_window (child, menu->bin_window);
1050     }
1051   
1052   widget->style = gtk_style_attach (widget->style, widget->window);
1053   gtk_style_set_background (widget->style, menu->bin_window, GTK_STATE_NORMAL);
1054   gtk_style_set_background (widget->style, menu->view_window, GTK_STATE_NORMAL);
1055   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
1056
1057   gtk_menu_paint (widget);
1058   
1059   gdk_window_show (menu->bin_window);
1060   gdk_window_show (menu->view_window);
1061 }
1062
1063 static void
1064 gtk_menu_unrealize (GtkWidget *widget)
1065 {
1066   GtkMenu *menu;
1067
1068   g_return_if_fail (GTK_IS_MENU (widget));
1069
1070   menu = GTK_MENU (widget);
1071
1072   gdk_window_set_user_data (menu->view_window, NULL);
1073   gdk_window_destroy (menu->view_window);
1074   menu->view_window = NULL;
1075
1076   gdk_window_set_user_data (menu->bin_window, NULL);
1077   gdk_window_destroy (menu->bin_window);
1078   menu->bin_window = NULL;
1079
1080   (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
1081 }
1082
1083 static void
1084 gtk_menu_size_request (GtkWidget      *widget,
1085                        GtkRequisition *requisition)
1086 {
1087   GtkMenu *menu;
1088   GtkMenuShell *menu_shell;
1089   GtkWidget *child;
1090   GList *children;
1091   guint max_toggle_size;
1092   guint max_accel_width;
1093   GtkRequisition child_requisition;
1094   gint width;
1095   
1096   g_return_if_fail (GTK_IS_MENU (widget));
1097   g_return_if_fail (requisition != NULL);
1098   
1099   menu = GTK_MENU (widget);
1100   menu_shell = GTK_MENU_SHELL (widget);
1101   
1102   requisition->width = 0;
1103   requisition->height = 0;
1104   
1105   max_toggle_size = 0;
1106   max_accel_width = 0;
1107   
1108   children = menu_shell->children;
1109   while (children)
1110     {
1111       child = children->data;
1112       children = children->next;
1113       
1114       if (GTK_WIDGET_VISIBLE (child))
1115         {
1116           gint toggle_size;
1117
1118           /* It's important to size_request the child
1119            * before doing the toggle size request, in
1120            * case the toggle size request depends on the size
1121            * request of a child of the child (e.g. for ImageMenuItem)
1122            */
1123           
1124           GTK_MENU_ITEM (child)->show_submenu_indicator = TRUE;
1125           gtk_widget_size_request (child, &child_requisition);
1126           
1127           requisition->width = MAX (requisition->width, child_requisition.width);
1128           requisition->height += child_requisition.height;
1129
1130           gtk_menu_item_toggle_size_request (GTK_MENU_ITEM (child), &toggle_size);
1131           max_toggle_size = MAX (max_toggle_size, toggle_size);
1132           max_accel_width = MAX (max_accel_width, GTK_MENU_ITEM (child)->accelerator_width);
1133         }
1134     }
1135   
1136   requisition->width += max_toggle_size + max_accel_width;
1137   requisition->width += (GTK_CONTAINER (menu)->border_width +
1138                          widget->style->xthickness) * 2;
1139   requisition->height += (GTK_CONTAINER (menu)->border_width +
1140                           widget->style->ythickness) * 2;
1141   
1142   menu->toggle_size = max_toggle_size;
1143
1144   /* If the requested width was different than the allocated width, we need to change
1145    * the geometry hints for the tear off window so that the window can actually be resized.
1146    * Don't resize the tearoff if it is not active, beacuse it won't redraw (it is only a background pixmap).
1147    */
1148   if ((requisition->width != GTK_WIDGET (menu)->allocation.width) && menu->tearoff_active)
1149     {
1150       width = requisition->width;
1151       if (menu->tearoff_scrollbar && GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1152         width += menu->tearoff_scrollbar->requisition.width;
1153       gtk_menu_set_tearoff_hints (menu, width);
1154     }
1155 }
1156
1157 static void
1158 gtk_menu_size_allocate (GtkWidget     *widget,
1159                         GtkAllocation *allocation)
1160 {
1161   GtkMenu *menu;
1162   GtkMenuShell *menu_shell;
1163   GtkWidget *child;
1164   GtkAllocation child_allocation;
1165   GList *children;
1166   gint x, y;
1167   gint width, height;
1168
1169   g_return_if_fail (GTK_IS_MENU (widget));
1170   g_return_if_fail (allocation != NULL);
1171   
1172   menu = GTK_MENU (widget);
1173   menu_shell = GTK_MENU_SHELL (widget);
1174
1175   widget->allocation = *allocation;
1176
1177   x = GTK_CONTAINER (menu)->border_width + widget->style->xthickness;
1178   y = GTK_CONTAINER (menu)->border_width + widget->style->ythickness;
1179   
1180   width = MAX (1, allocation->width - x * 2);
1181   height = MAX (1, allocation->height - y * 2);
1182   
1183   if (menu->upper_arrow_visible && !menu->tearoff_active)
1184     {
1185       y += MENU_SCROLL_ARROW_HEIGHT;
1186       height -= MENU_SCROLL_ARROW_HEIGHT;
1187     }
1188   
1189   if (menu->lower_arrow_visible && !menu->tearoff_active)
1190     height -= MENU_SCROLL_ARROW_HEIGHT;
1191   
1192   if (GTK_WIDGET_REALIZED (widget))
1193     {
1194       gdk_window_move_resize (widget->window,
1195                               allocation->x, allocation->y,
1196                               allocation->width, allocation->height);
1197
1198       gdk_window_move_resize (menu->view_window,
1199                               x,
1200                               y,
1201                               width,
1202                               height);
1203     }
1204
1205   if (menu_shell->children)
1206     {
1207       child_allocation.x = 0;
1208       child_allocation.y = 0;
1209       child_allocation.width = width;
1210       
1211       children = menu_shell->children;
1212       while (children)
1213         {
1214           child = children->data;
1215           children = children->next;
1216           
1217           if (GTK_WIDGET_VISIBLE (child))
1218             {
1219               GtkRequisition child_requisition;
1220               gtk_widget_get_child_requisition (child, &child_requisition);
1221               
1222               child_allocation.height = child_requisition.height;
1223
1224               gtk_menu_item_toggle_size_allocate (GTK_MENU_ITEM (child),
1225                                                   menu->toggle_size);
1226               gtk_widget_size_allocate (child, &child_allocation);
1227               gtk_widget_queue_draw (child);
1228               
1229               child_allocation.y += child_allocation.height;
1230             }
1231         }
1232       
1233       /* Resize the item window */
1234       if (GTK_WIDGET_REALIZED (widget))
1235         {
1236           gdk_window_resize (menu->bin_window,
1237                              child_allocation.width,
1238                              child_allocation.y);
1239         }
1240
1241
1242       if (menu->tearoff_active)
1243         {
1244           if (allocation->height >= widget->requisition.height)
1245             {
1246               if (GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1247                 {
1248                   gtk_widget_hide (menu->tearoff_scrollbar);
1249                   gtk_menu_set_tearoff_hints (menu, allocation->width);
1250                   gtk_widget_set_usize (menu->tearoff_window, -1, allocation->height);
1251
1252                   gtk_menu_scroll_to (menu, 0);
1253                 }
1254             }
1255           else
1256             {
1257               menu->tearoff_adjustment->upper = widget->requisition.height;
1258               menu->tearoff_adjustment->page_size = allocation->height;
1259               
1260               if (menu->tearoff_adjustment->value + menu->tearoff_adjustment->page_size >
1261                   menu->tearoff_adjustment->upper)
1262                 {
1263                   gint value;
1264                   value = menu->tearoff_adjustment->upper - menu->tearoff_adjustment->page_size;
1265                   if (value < 0)
1266                     value = 0;
1267                   gtk_menu_scroll_to (menu, value);
1268                 }
1269               
1270               gtk_adjustment_changed (menu->tearoff_adjustment);
1271               
1272               if (!GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1273                 {
1274                   gtk_menu_set_tearoff_hints (menu,
1275                                               allocation->width + menu->tearoff_scrollbar->requisition.width);
1276                   gtk_widget_show (menu->tearoff_scrollbar);
1277                   gtk_widget_set_usize (menu->tearoff_window, -1, allocation->height);
1278                 }
1279             }
1280         }
1281     }
1282 }
1283
1284 static void
1285 gtk_menu_paint (GtkWidget *widget)
1286 {
1287   gint border_x;
1288   gint border_y;
1289   gint width, height;
1290   gint menu_height;
1291   gint top_pos;
1292   GtkMenu *menu;
1293   
1294   g_return_if_fail (GTK_IS_MENU (widget));
1295
1296   menu = GTK_MENU (widget);
1297   
1298   if (GTK_WIDGET_DRAWABLE (widget))
1299     {
1300       gtk_paint_box (widget->style,
1301                      widget->window,
1302                      GTK_STATE_NORMAL,
1303                      GTK_SHADOW_OUT,
1304                      NULL, widget, "menu",
1305                      0, 0, -1, -1);
1306
1307       border_x = GTK_CONTAINER (widget)->border_width + widget->style->xthickness;
1308       border_y = GTK_CONTAINER (widget)->border_width + widget->style->ythickness;
1309       gdk_window_get_size (widget->window, &width, &height);
1310
1311       if (menu->upper_arrow_visible && !menu->tearoff_active)
1312         {
1313           gtk_paint_box (widget->style,
1314                          widget->window,
1315                          menu->upper_arrow_prelight ?
1316                          GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1317                          GTK_SHADOW_OUT,
1318                          NULL, widget, "menu",
1319                          border_x,
1320                          border_y,
1321                          width - 2*border_x,
1322                          MENU_SCROLL_ARROW_HEIGHT);
1323           
1324           gtk_paint_arrow (widget->style,
1325                            widget->window,
1326                            menu->upper_arrow_prelight ?
1327                            GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1328                            GTK_SHADOW_OUT,
1329                            NULL, widget, "menu",
1330                            GTK_ARROW_UP,
1331                            TRUE,
1332                            width / 2 - MENU_SCROLL_ARROW_HEIGHT / 2 + 1,
1333                            2 * border_y + 1,
1334                            MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2,
1335                            MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2);
1336         }
1337   
1338       if (menu->lower_arrow_visible && !menu->tearoff_active)
1339         {
1340           gtk_paint_box (widget->style,
1341                          widget->window,
1342                          menu->lower_arrow_prelight ?
1343                          GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1344                          GTK_SHADOW_OUT,
1345                          NULL, widget, "menu",
1346                          border_x,
1347                          height - border_y - MENU_SCROLL_ARROW_HEIGHT + 1,
1348                          width - 2*border_x,
1349                          MENU_SCROLL_ARROW_HEIGHT);
1350           
1351           gtk_paint_arrow (widget->style,
1352                            widget->window,
1353                            menu->lower_arrow_prelight ?
1354                            GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1355                            GTK_SHADOW_OUT,
1356                            NULL, widget, "menu",
1357                            GTK_ARROW_DOWN,
1358                            TRUE,
1359                            width / 2 - MENU_SCROLL_ARROW_HEIGHT / 2 + 1,
1360                            height - MENU_SCROLL_ARROW_HEIGHT + 1,
1361                            MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2,
1362                            MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2);
1363         }
1364
1365       if (menu->scroll_offset < 0)
1366         gtk_paint_box (widget->style,
1367                        menu->view_window,
1368                        GTK_STATE_ACTIVE,
1369                        GTK_SHADOW_IN,
1370                        NULL, widget, "menu",
1371                        0, 0,
1372                        -1,
1373                        -menu->scroll_offset);
1374
1375       menu_height = widget->requisition.height - 2*border_y;
1376       top_pos = height - 2*border_y - (menu->upper_arrow_visible ? MENU_SCROLL_ARROW_HEIGHT : 0);
1377
1378       if (menu_height - menu->scroll_offset < top_pos)
1379         gtk_paint_box (widget->style,
1380                        menu->view_window,
1381                        GTK_STATE_ACTIVE,
1382                        GTK_SHADOW_IN,
1383                        NULL, widget, "menu",
1384                        0,
1385                        menu_height - menu->scroll_offset,
1386                        -1,
1387                        top_pos - (menu_height - menu->scroll_offset));
1388     }
1389 }
1390
1391 static gboolean
1392 gtk_menu_expose (GtkWidget      *widget,
1393                  GdkEventExpose *event)
1394 {
1395   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
1396   g_return_val_if_fail (event != NULL, FALSE);
1397
1398   if (GTK_WIDGET_DRAWABLE (widget))
1399     {
1400       gtk_menu_paint (widget);
1401       
1402       (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
1403     }
1404   
1405   return FALSE;
1406 }
1407
1408 static gboolean
1409 gtk_menu_key_press (GtkWidget   *widget,
1410                     GdkEventKey *event)
1411 {
1412   GtkMenuShell *menu_shell;
1413   GtkMenu *menu;
1414   gboolean delete = FALSE;
1415   gchar *accel = NULL;
1416   
1417   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
1418   g_return_val_if_fail (event != NULL, FALSE);
1419       
1420   menu_shell = GTK_MENU_SHELL (widget);
1421   menu = GTK_MENU (widget);
1422   
1423   gtk_menu_stop_navigating_submenu (menu);
1424
1425   if (GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event))
1426     return TRUE;
1427     
1428   g_object_get (G_OBJECT (gtk_settings_get_default ()),
1429                 "gtk-menu-bar-accel",
1430                 &accel,
1431                 NULL);
1432
1433   if (accel)
1434     {
1435       guint keyval = 0;
1436       GdkModifierType mods = 0;
1437       gboolean handled = FALSE;
1438       
1439       gtk_accelerator_parse (accel, &keyval, &mods);
1440
1441       if (keyval == 0)
1442         g_warning ("Failed to parse menu bar accelerator '%s'\n", accel);
1443
1444       /* FIXME this is wrong, needs to be in the global accel resolution
1445        * thing, to properly consider i18n etc., but that probably requires
1446        * AccelGroup changes etc.
1447        */
1448       if (event->keyval == keyval &&
1449           (mods & event->state) == mods)
1450         {
1451           gtk_signal_emit_by_name (GTK_OBJECT (menu), "cancel");
1452         }
1453
1454       g_free (accel);
1455
1456       if (handled)
1457         return TRUE;
1458     }
1459   
1460   switch (event->keyval)
1461     {
1462     case GDK_Delete:
1463     case GDK_KP_Delete:
1464     case GDK_BackSpace:
1465       delete = TRUE;
1466       break;
1467     default:
1468       break;
1469     }
1470
1471   /* Modify the accelerators */
1472   if (menu_shell->active_menu_item &&
1473       GTK_BIN (menu_shell->active_menu_item)->child &&
1474       GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL &&
1475       !gtk_widget_accelerators_locked (menu_shell->active_menu_item) &&
1476       (delete ||
1477        (gtk_accelerator_valid (event->keyval, event->state) &&
1478         (event->state ||
1479          (event->keyval >= GDK_F1 && event->keyval <= GDK_F35)))))
1480     {
1481       GtkMenuItem *menu_item;
1482       GtkAccelGroup *accel_group;
1483       
1484       menu_item = GTK_MENU_ITEM (menu_shell->active_menu_item);
1485       
1486       if (!GTK_MENU (widget)->accel_group)
1487         accel_group = gtk_accel_group_get_default ();
1488       else
1489         accel_group = GTK_MENU (widget)->accel_group;
1490       
1491       gtk_widget_remove_accelerators (GTK_WIDGET (menu_item),
1492                                       gtk_signal_name (menu_item->accelerator_signal),
1493                                       TRUE);
1494       
1495       if (!delete &&
1496           0 == gtk_widget_accelerator_signal (GTK_WIDGET (menu_item),
1497                                               accel_group,
1498                                               event->keyval,
1499                                               event->state))
1500         {
1501           GSList *slist;
1502           
1503           slist = gtk_accel_group_entries_from_object (G_OBJECT (menu_item));
1504           while (slist)
1505             {
1506               GtkAccelEntry *ac_entry;
1507               
1508               ac_entry = slist->data;
1509               
1510               if (ac_entry->signal_id == menu_item->accelerator_signal)
1511                 break;
1512               
1513               slist = slist->next;
1514             }
1515           
1516           if (!slist)
1517             gtk_widget_add_accelerator (GTK_WIDGET (menu_item),
1518                                         gtk_signal_name (menu_item->accelerator_signal),
1519                                         accel_group,
1520                                         event->keyval,
1521                                         event->state,
1522                                         GTK_ACCEL_VISIBLE);
1523         }
1524     }
1525   
1526   return TRUE;
1527 }
1528
1529 static gboolean
1530 gtk_menu_motion_notify  (GtkWidget         *widget,
1531                          GdkEventMotion    *event)
1532 {
1533   GtkWidget *menu_item;
1534   GtkMenu *menu;
1535   GtkMenuShell *menu_shell;
1536
1537   gboolean need_enter;
1538
1539
1540   if (GTK_IS_MENU (widget))
1541     gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE);
1542   
1543   /* We received the event for one of two reasons:
1544    *
1545    * a) We are the active menu, and did gtk_grab_add()
1546    * b) The widget is a child of ours, and the event was propagated
1547    *
1548    * Since for computation of navigation regions, we want the menu which
1549    * is the parent of the menu item, for a), we need to find that menu,
1550    * which may be different from 'widget'.
1551    */
1552   menu_item = gtk_get_event_widget ((GdkEvent*) event);
1553   if (!menu_item || !GTK_IS_MENU_ITEM (menu_item) || !GTK_WIDGET_IS_SENSITIVE (menu_item) ||
1554       !GTK_IS_MENU (menu_item->parent))
1555     return FALSE;
1556
1557   menu_shell = GTK_MENU_SHELL (menu_item->parent);
1558   menu = GTK_MENU (menu_shell);
1559   
1560   need_enter = (menu->navigation_region != NULL || menu_shell->ignore_enter);
1561
1562   /* Check to see if we are within an active submenu's navigation region
1563    */
1564   if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
1565     return TRUE; 
1566
1567   if (need_enter)
1568     {
1569       /* The menu is now sensitive to enter events on its items, but
1570        * was previously sensitive.  So we fake an enter event.
1571        */
1572       gint width, height;
1573       
1574       menu_shell->ignore_enter = FALSE; 
1575       
1576       gdk_window_get_size (event->window, &width, &height);
1577       if (event->x >= 0 && event->x < width &&
1578           event->y >= 0 && event->y < height)
1579         {
1580           GdkEvent send_event;
1581
1582           memset (&send_event, 0, sizeof (send_event));
1583           send_event.crossing.type = GDK_ENTER_NOTIFY;
1584           send_event.crossing.window = event->window;
1585           send_event.crossing.time = event->time;
1586           send_event.crossing.send_event = TRUE;
1587           send_event.crossing.x_root = event->x_root;
1588           send_event.crossing.y_root = event->y_root;
1589           send_event.crossing.x = event->x;
1590           send_event.crossing.y = event->y;
1591
1592           /* We send the event to 'widget', the currently active menu,
1593            * instead of 'menu', the menu that the pointer is in. This
1594            * will ensure that the event will be ignored unless the
1595            * menuitem is a child of the active menu or some parent
1596            * menu of the active menu.
1597            */
1598           return gtk_widget_event (widget, &send_event);
1599         }
1600     }
1601
1602   return FALSE;
1603 }
1604
1605 static gboolean
1606 gtk_menu_scroll_timeout (gpointer  data)
1607 {
1608   GtkMenu *menu;
1609   GtkWidget *widget;
1610   gint offset;
1611   gint view_width, view_height;
1612
1613   GDK_THREADS_ENTER ();
1614
1615   menu = GTK_MENU (data);
1616   widget = GTK_WIDGET (menu);
1617
1618   offset = menu->scroll_offset + menu->scroll_step;
1619
1620   /* If we scroll upward and the non-visible top part
1621    * is smaller than the scroll arrow it would be
1622    * pretty stupid to show the arrow and taking more
1623    * screen space than just scrolling to the top.
1624    */
1625   if ((menu->scroll_step < 0) && (offset < MENU_SCROLL_ARROW_HEIGHT))
1626     offset = 0;
1627
1628   /* Don't scroll over the top if we weren't before: */
1629   if ((menu->scroll_offset >= 0) && (offset < 0))
1630     offset = 0;
1631
1632   gdk_window_get_size (widget->window, &view_width, &view_height);
1633
1634   /* Don't scroll past the bottom if we weren't before: */
1635   if (menu->scroll_offset > 0)
1636     view_height -= MENU_SCROLL_ARROW_HEIGHT;
1637   
1638   if ((menu->scroll_offset + view_height <= widget->requisition.height) &&
1639       (offset + view_height > widget->requisition.height))
1640     offset = widget->requisition.height - view_height;
1641
1642   gtk_menu_scroll_to (menu, offset);
1643
1644   GDK_THREADS_LEAVE ();
1645
1646   return TRUE;
1647 }
1648
1649 static void
1650 gtk_menu_handle_scrolling (GtkMenu *menu, gboolean enter)
1651 {
1652   GtkMenuShell *menu_shell;
1653   gint width, height;
1654   gint x, y;
1655   gint border;
1656   GdkRectangle rect;
1657   gboolean in_arrow;
1658   gboolean scroll_fast = FALSE;
1659
1660   menu_shell = GTK_MENU_SHELL (menu);
1661
1662   gdk_window_get_pointer (GTK_WIDGET (menu)->window, &x, &y, NULL);
1663   gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
1664
1665   border = GTK_CONTAINER (menu)->border_width + GTK_WIDGET (menu)->style->ythickness;
1666
1667   if (menu->upper_arrow_visible && !menu->tearoff_active)
1668     {
1669       rect.x = 0;
1670       rect.y = 0;
1671       rect.width = width;
1672       rect.height = MENU_SCROLL_ARROW_HEIGHT + border;
1673       
1674       in_arrow = FALSE;
1675       if ((x >= rect.x) && (x < rect.x + rect.width) &&
1676           (y >= rect.y) && (y < rect.y + rect.height))
1677         {
1678           in_arrow = TRUE;
1679           scroll_fast = (y < rect.y + MENU_SCROLL_FAST_ZONE);
1680         }
1681         
1682       if (enter && in_arrow &&
1683           (!menu->upper_arrow_prelight || menu->scroll_fast != scroll_fast))
1684         {
1685           menu->upper_arrow_prelight = TRUE;
1686           menu->scroll_fast = scroll_fast;
1687           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
1688           
1689           /* Deselect the active item so that any submenus are poped down */
1690           gtk_menu_shell_deselect (menu_shell);
1691
1692           gtk_menu_stop_scrolling (menu);
1693           menu->scroll_step = -MENU_SCROLL_STEP;
1694           menu->timeout_id = g_timeout_add ((scroll_fast) ? MENU_SCROLL_TIMEOUT2 : MENU_SCROLL_TIMEOUT1,
1695                                             gtk_menu_scroll_timeout,
1696                                             menu);
1697         }
1698       else if (!enter && !in_arrow && menu->upper_arrow_prelight)
1699         {
1700           menu->upper_arrow_prelight = FALSE;
1701           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
1702           
1703           gtk_menu_stop_scrolling (menu);
1704         }
1705     }
1706   
1707   if (menu->lower_arrow_visible && !menu->tearoff_active)
1708     {
1709       rect.x = 0;
1710       rect.y = height - border - MENU_SCROLL_ARROW_HEIGHT;
1711       rect.width = width;
1712       rect.height = MENU_SCROLL_ARROW_HEIGHT + border;
1713
1714       in_arrow = FALSE;
1715       if ((x >= rect.x) && (x < rect.x + rect.width) &&
1716           (y >= rect.y) && (y < rect.y + rect.height))
1717         {
1718           in_arrow = TRUE;
1719           scroll_fast = (y > rect.y + rect.height - MENU_SCROLL_FAST_ZONE);
1720         }
1721
1722       if (enter && in_arrow &&
1723           (!menu->lower_arrow_prelight || menu->scroll_fast != scroll_fast))
1724         {
1725           menu->lower_arrow_prelight = TRUE;
1726           menu->scroll_fast = scroll_fast;
1727           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
1728
1729           /* Deselect the active item so that any submenus are poped down */
1730           gtk_menu_shell_deselect (menu_shell);
1731
1732           gtk_menu_stop_scrolling (menu);
1733           menu->scroll_step = MENU_SCROLL_STEP;
1734           menu->timeout_id = g_timeout_add ((scroll_fast) ? MENU_SCROLL_TIMEOUT2 : MENU_SCROLL_TIMEOUT1,
1735                                             gtk_menu_scroll_timeout,
1736                                             menu);
1737         }
1738       else if (!enter && !in_arrow && menu->lower_arrow_prelight)
1739         {
1740           menu->lower_arrow_prelight = FALSE;
1741           gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
1742           
1743           gtk_menu_stop_scrolling (menu);
1744         }
1745     }
1746 }
1747
1748 static gboolean
1749 gtk_menu_enter_notify (GtkWidget        *widget,
1750                        GdkEventCrossing *event)
1751 {
1752   GtkWidget *menu_item;
1753
1754   if (widget && GTK_IS_MENU (widget))
1755     gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE);
1756       
1757   /* If this is a faked enter (see gtk_menu_motion_notify), 'widget'
1758    * will not correspond to the event widget's parent.  Check to see
1759    * if we are in the parent's navigation region.
1760    */
1761   menu_item = gtk_get_event_widget ((GdkEvent*) event);
1762   if (menu_item && GTK_IS_MENU_ITEM (menu_item) && GTK_IS_MENU (menu_item->parent) &&
1763       gtk_menu_navigating_submenu (GTK_MENU (menu_item->parent), event->x_root, event->y_root))
1764     return TRUE;
1765
1766   return GTK_WIDGET_CLASS (parent_class)->enter_notify_event (widget, event); 
1767 }
1768
1769 static gboolean
1770 gtk_menu_leave_notify (GtkWidget        *widget,
1771                        GdkEventCrossing *event)
1772 {
1773   GtkMenuShell *menu_shell;
1774   GtkMenu *menu;
1775   GtkMenuItem *menu_item;
1776   GtkWidget *event_widget;
1777
1778   menu = GTK_MENU (widget);
1779   menu_shell = GTK_MENU_SHELL (widget); 
1780   
1781   if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
1782     return TRUE; 
1783
1784   gtk_menu_handle_scrolling (menu, FALSE);
1785   
1786   event_widget = gtk_get_event_widget ((GdkEvent*) event);
1787   
1788   if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
1789     return TRUE;
1790   
1791   menu_item = GTK_MENU_ITEM (event_widget); 
1792   
1793   /* Here we check to see if we're leaving an active menu item with a submenu, 
1794    * in which case we enter submenu navigation mode. 
1795    */
1796   if (menu_shell->active_menu_item != NULL
1797       && menu_item->submenu != NULL
1798       && menu_item->submenu_placement == GTK_LEFT_RIGHT)
1799     {
1800       if (menu_item->submenu->window != NULL) 
1801         {
1802           gtk_menu_set_submenu_navigation_region (menu, menu_item, event);
1803           return TRUE;
1804         }
1805     }
1806   
1807   return GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event); 
1808 }
1809
1810 static void 
1811 gtk_menu_stop_navigating_submenu (GtkMenu *menu)
1812 {
1813   if (menu->navigation_region) 
1814     {
1815       gdk_region_destroy (menu->navigation_region);
1816       menu->navigation_region = NULL;
1817     }
1818   
1819   if (menu->navigation_timeout)
1820     {
1821       gtk_timeout_remove (menu->navigation_timeout);
1822       menu->navigation_timeout = 0;
1823     }
1824 }
1825
1826 /* When the timeout is elapsed, the navigation region is destroyed
1827  * and the menuitem under the pointer (if any) is selected.
1828  */
1829 static gboolean
1830 gtk_menu_stop_navigating_submenu_cb (gpointer user_data)
1831 {
1832   GtkMenu *menu = user_data;
1833   GdkWindow *child_window;
1834
1835   GDK_THREADS_ENTER ();
1836
1837   gtk_menu_stop_navigating_submenu (menu);
1838   
1839   if (GTK_WIDGET_REALIZED (menu))
1840     {
1841       child_window = gdk_window_get_pointer (GTK_WIDGET (menu)->window, NULL, NULL, NULL);
1842
1843       if (child_window)
1844         {
1845           GdkEventCrossing send_event;
1846
1847           memset (&send_event, 0, sizeof (send_event));
1848           send_event.window = child_window;
1849           send_event.type = GDK_ENTER_NOTIFY;
1850           send_event.time = GDK_CURRENT_TIME; /* Bogus */
1851           send_event.send_event = TRUE;
1852
1853           GTK_WIDGET_CLASS (parent_class)->enter_notify_event (GTK_WIDGET (menu), &send_event); 
1854         }
1855     }
1856
1857   GDK_THREADS_LEAVE ();
1858
1859   return FALSE; 
1860 }
1861
1862 static gboolean
1863 gtk_menu_navigating_submenu (GtkMenu *menu,
1864                              gint     event_x,
1865                              gint     event_y)
1866 {
1867   if (menu->navigation_region)
1868     {
1869       if (gdk_region_point_in (menu->navigation_region, event_x, event_y))
1870         return TRUE;
1871       else
1872         {
1873           gtk_menu_stop_navigating_submenu (menu);
1874           return FALSE;
1875         }
1876     }
1877   return FALSE;
1878 }
1879
1880 static void
1881 gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
1882                                         GtkMenuItem      *menu_item,
1883                                         GdkEventCrossing *event)
1884 {
1885   gint submenu_left = 0;
1886   gint submenu_right = 0;
1887   gint submenu_top = 0;
1888   gint submenu_bottom = 0;
1889   gint width = 0;
1890   gint height = 0;
1891   GdkPoint point[3];
1892   GtkWidget *event_widget;
1893
1894   g_return_if_fail (menu_item->submenu != NULL);
1895   g_return_if_fail (event != NULL);
1896   
1897   event_widget = gtk_get_event_widget ((GdkEvent*) event);
1898   
1899   gdk_window_get_origin (menu_item->submenu->window, &submenu_left, &submenu_top);
1900   gdk_window_get_size (menu_item->submenu->window, &width, &height);
1901   submenu_right = submenu_left + width;
1902   submenu_bottom = submenu_top + height;
1903   
1904   gdk_window_get_size (event_widget->window, &width, &height);
1905   
1906   if (event->x >= 0 && event->x < width)
1907     {
1908       /* Set navigation region */
1909       /* We fudge/give a little padding in case the user
1910        * ``misses the vertex'' of the triangle/is off by a pixel or two.
1911        */ 
1912       if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
1913         point[0].x = event->x_root - SUBMENU_NAV_REGION_PADDING; 
1914       else                             
1915         point[0].x = event->x_root + SUBMENU_NAV_REGION_PADDING;  
1916       
1917       /* Exiting the top or bottom? */ 
1918       if (event->y < 0)
1919         { /* top */
1920           point[0].y = event->y_root + SUBMENU_NAV_REGION_PADDING;
1921           point[1].y = submenu_top;
1922
1923           if (point[0].y <= point[1].y)
1924             return;
1925         }
1926       else
1927         { /* bottom */
1928           point[0].y = event->y_root - SUBMENU_NAV_REGION_PADDING; 
1929           point[1].y = submenu_bottom;
1930
1931           if (point[0].y >= point[1].y)
1932             return;
1933         }
1934       
1935       /* Submenu is to the left or right? */ 
1936       if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
1937         point[1].x = submenu_left;  /* right */
1938       else
1939         point[1].x = submenu_right; /* left */
1940       
1941       point[2].x = point[1].x;
1942       point[2].y = point[0].y;
1943
1944       gtk_menu_stop_navigating_submenu (menu);
1945       
1946       menu->navigation_region = gdk_region_polygon (point, 3, GDK_WINDING_RULE);
1947
1948       menu->navigation_timeout = gtk_timeout_add (SUBMENU_NAV_HYSTERESIS_TIMEOUT, 
1949                                                   gtk_menu_stop_navigating_submenu_cb, menu);
1950     }
1951 }
1952
1953 static void
1954 gtk_menu_deactivate (GtkMenuShell *menu_shell)
1955 {
1956   GtkWidget *parent;
1957   
1958   g_return_if_fail (GTK_IS_MENU (menu_shell));
1959   
1960   parent = menu_shell->parent_menu_shell;
1961   
1962   menu_shell->activate_time = 0;
1963   gtk_menu_popdown (GTK_MENU (menu_shell));
1964   
1965   if (parent)
1966     gtk_menu_shell_deactivate (GTK_MENU_SHELL (parent));
1967 }
1968
1969 static void
1970 gtk_menu_position (GtkMenu *menu)
1971 {
1972   GtkWidget *widget;
1973   GtkRequisition requisition;
1974   gint x, y;
1975   gint screen_width;
1976   gint screen_height;
1977   gint scroll_offset;
1978   gint menu_height;
1979   gboolean push_in;
1980   
1981   g_return_if_fail (GTK_IS_MENU (menu));
1982
1983   widget = GTK_WIDGET (menu);
1984
1985   screen_width = gdk_screen_width ();
1986   screen_height = gdk_screen_height ();
1987
1988   gdk_window_get_pointer (NULL, &x, &y, NULL);
1989
1990   /* We need the requisition to figure out the right place to
1991    * popup the menu. In fact, we always need to ask here, since
1992    * if a size_request was queued while we weren't popped up,
1993    * the requisition won't have been recomputed yet.
1994    */
1995   gtk_widget_size_request (widget, &requisition);
1996
1997   push_in = FALSE;
1998   
1999   if (menu->position_func)
2000     (* menu->position_func) (menu, &x, &y, &push_in, menu->position_func_data);
2001   else
2002     {
2003       x = CLAMP (x - 2, 0, MAX (0, screen_width - requisition.width));
2004       y = CLAMP (y - 2, 0, MAX (0, screen_height - requisition.height));
2005     }
2006
2007   scroll_offset = 0;
2008
2009   if (push_in)
2010     {
2011       menu_height = GTK_WIDGET (menu)->requisition.height;
2012
2013       if (y + menu_height > screen_height)
2014         {
2015           scroll_offset -= y + menu_height - screen_height;
2016           y = screen_height - menu_height;
2017         }
2018   
2019       if (y < 0)
2020         {
2021           scroll_offset -= y;
2022           y = 0;
2023         }
2024     }
2025
2026   if (y + requisition.height > screen_height)
2027     requisition.height = screen_height - y;
2028   
2029   if (y < 0)
2030     {
2031       scroll_offset -= y;
2032       requisition.height -= -y;
2033       y = 0;
2034     }
2035
2036   if (scroll_offset > 0)
2037     scroll_offset += MENU_SCROLL_ARROW_HEIGHT;
2038   
2039   /* FIXME: The MAX() here is because gtk_widget_set_uposition
2040    * is broken. Once we provide an alternate interface that
2041    * allows negative values, then we can remove them.
2042    */
2043   x = MAX (x, 0);
2044   gtk_widget_set_uposition (GTK_MENU_SHELL (menu)->active ?
2045                             menu->toplevel : menu->tearoff_window, 
2046                             x, y);
2047   gtk_widget_set_usize (GTK_MENU_SHELL (menu)->active ?
2048                         menu->toplevel : menu->tearoff_hbox,
2049                         -1, requisition.height);
2050   menu->scroll_offset = scroll_offset;
2051 }
2052
2053 static void
2054 gtk_menu_stop_scrolling (GtkMenu *menu)
2055 {
2056   if (menu->timeout_id)
2057     {
2058       g_source_remove (menu->timeout_id);
2059       menu->timeout_id = 0;
2060     }
2061 }
2062
2063
2064 static void
2065 gtk_menu_scroll_to (GtkMenu *menu,
2066                     gint    offset)
2067 {
2068   GtkWidget *widget;
2069   gint x, y;
2070   gint view_width, view_height;
2071   gint border_width;
2072   gboolean last_visible;
2073   gint menu_height;
2074
2075   widget = GTK_WIDGET (menu);
2076
2077   if (menu->tearoff_active &&
2078       menu->tearoff_adjustment &&
2079       (menu->tearoff_adjustment->value != offset))
2080     {
2081       menu->tearoff_adjustment->value = offset;
2082       gtk_adjustment_value_changed (menu->tearoff_adjustment);
2083     }
2084   
2085   /* Scroll the menu: */
2086   gdk_window_move (menu->bin_window, 0, -offset);
2087
2088   /* Move/resize the viewport according to arrows: */
2089   gdk_window_get_size (widget->window, &view_width, &view_height);
2090
2091   border_width = GTK_CONTAINER (menu)->border_width;
2092   view_width -= (border_width + widget->style->xthickness) * 2;
2093   view_height -= (border_width + widget->style->ythickness) * 2;
2094   menu_height = widget->requisition.height - (border_width + widget->style->ythickness) * 2;
2095
2096   x = border_width + widget->style->xthickness;
2097   y = border_width + widget->style->ythickness;
2098   
2099   if (!menu->tearoff_active)
2100     {
2101       last_visible = menu->upper_arrow_visible;
2102       menu->upper_arrow_visible = (offset > 0);
2103
2104       if (menu->upper_arrow_visible)
2105         view_height -= MENU_SCROLL_ARROW_HEIGHT;
2106       
2107       if ( (last_visible != menu->upper_arrow_visible) &&
2108            !menu->upper_arrow_visible)
2109         {
2110           menu->upper_arrow_prelight = FALSE;
2111           
2112           /* If we hid the upper arrow, possibly remove timeout */
2113           if (menu->scroll_step < 0)
2114             gtk_menu_stop_scrolling (menu);
2115         }
2116       
2117       last_visible = menu->lower_arrow_visible;
2118       menu->lower_arrow_visible = (view_height + offset < menu_height);
2119       
2120       if (menu->lower_arrow_visible)
2121         view_height -= MENU_SCROLL_ARROW_HEIGHT;
2122       
2123       if ( (last_visible != menu->lower_arrow_visible) &&
2124            !menu->lower_arrow_visible)
2125         {
2126           menu->lower_arrow_prelight = FALSE;
2127           
2128           /* If we hid the lower arrow, possibly remove timeout */
2129           if (menu->scroll_step > 0)
2130             gtk_menu_stop_scrolling (menu);
2131         }
2132       
2133       if (menu->upper_arrow_visible)
2134         y += MENU_SCROLL_ARROW_HEIGHT;
2135     }
2136   
2137   
2138   gdk_window_move_resize (menu->view_window,
2139                           x,
2140                           y,
2141                           view_width,
2142                           view_height);
2143
2144   menu->scroll_offset = offset;
2145 }
2146
2147 static void
2148 gtk_menu_select_item (GtkMenuShell  *menu_shell,
2149                       GtkWidget     *menu_item)
2150 {
2151   GtkMenu *menu;
2152   GtkWidget *child;
2153   GList *children;
2154   GtkRequisition child_requisition;
2155   gint child_offset, child_height;
2156   gint width, height;
2157   gint y;
2158   gint arrow_height;
2159   gboolean last_child = 0;
2160   
2161   g_return_if_fail (GTK_IS_MENU (menu_shell));
2162
2163   menu = GTK_MENU (menu_shell);
2164
2165   /* We need to check if the selected item fully visible.
2166    * If not we need to scroll the menu so that it becomes fully
2167    * visible.
2168    */
2169
2170   child = NULL;
2171   child_offset = 0;
2172   child_height = 0;
2173   children = menu_shell->children;
2174   while (children)
2175     {
2176       child = children->data;
2177       children = children->next;
2178       
2179       if (GTK_WIDGET_VISIBLE (child))
2180         {
2181           gtk_widget_size_request (child, &child_requisition);
2182           child_offset += child_height;
2183           child_height = child_requisition.height;
2184         }
2185       
2186       if (child == menu_item)
2187         {
2188           last_child = (children == NULL);
2189           break;
2190         }
2191     }
2192
2193   if (child == menu_item)
2194     {
2195       y = menu->scroll_offset;
2196       gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
2197
2198       height -= 2*GTK_CONTAINER (menu)->border_width + 2*GTK_WIDGET (menu)->style->ythickness;
2199       
2200       if (child_offset + child_height <= y)
2201         {
2202           /* Ignore the enter event we might get if the pointer is on the menu
2203            */
2204           menu_shell->ignore_enter = TRUE;
2205           gtk_menu_scroll_to (menu, child_offset);
2206         }
2207       else
2208         {
2209           arrow_height = 0;
2210           if (menu->upper_arrow_visible && !menu->tearoff_active)
2211             arrow_height += MENU_SCROLL_ARROW_HEIGHT;
2212           if (menu->lower_arrow_visible && !menu->tearoff_active)
2213             arrow_height += MENU_SCROLL_ARROW_HEIGHT;
2214           
2215           if (child_offset >= y + height - arrow_height)
2216             {
2217               arrow_height = 0;
2218               if (!last_child && !menu->tearoff_active)
2219                 arrow_height += MENU_SCROLL_ARROW_HEIGHT;
2220               
2221               y = child_offset + child_height - height + arrow_height;
2222               if ((y > 0) && !menu->tearoff_active)
2223                 {
2224                   /* Need upper arrow */
2225                   arrow_height += MENU_SCROLL_ARROW_HEIGHT;
2226                   y = child_offset + child_height - height + arrow_height;
2227                 }
2228               /* Ignore the enter event we might get if the pointer is on the menu
2229                */
2230               menu_shell->ignore_enter = TRUE;
2231               gtk_menu_scroll_to (menu, y);
2232             }
2233         }    
2234       
2235     }
2236
2237   GTK_MENU_SHELL_CLASS (parent_class)->select_item (menu_shell, menu_item);
2238 }
2239
2240
2241 /* Reparent the menu, taking care of the refcounting
2242  *
2243  * If unrealize is true we force a unrealize while reparenting the parent.
2244  * This can help eliminate flicker in some cases.
2245  *
2246  * What happens is that when the menu is unrealized and then re-realized,
2247  * the allocations are as follows:
2248  *
2249  *  parent - 1x1 at (0,0) 
2250  *  child1 - 100x20 at (0,0)
2251  *  child2 - 100x20 at (0,20)
2252  *  child3 - 100x20 at (0,40)
2253  *
2254  * That is, the parent is small but the children are full sized. Then,
2255  * when the queued_resize gets processed, the parent gets resized to
2256  * full size. 
2257  *
2258  * But in order to eliminate flicker when scrolling, gdkgeometry-x11.c
2259  * contains the following logic:
2260  * 
2261  * - if a move or resize operation on a window would change the clip 
2262  *   region on the children, then before the window is resized
2263  *   the background for children is temporarily set to None, the
2264  *   move/resize done, and the background for the children restored.
2265  *
2266  * So, at the point where the parent is resized to final size, the
2267  * background for the children is temporarily None, and thus they
2268  * are not cleared to the background color and the previous background
2269  * (the image of the menu) is left in place.
2270  */
2271 static void 
2272 gtk_menu_reparent (GtkMenu      *menu, 
2273                    GtkWidget    *new_parent, 
2274                    gboolean      unrealize)
2275 {
2276   GtkObject *object = GTK_OBJECT (menu);
2277   GtkWidget *widget = GTK_WIDGET (menu);
2278   gboolean was_floating = GTK_OBJECT_FLOATING (object);
2279
2280   gtk_object_ref (object);
2281   gtk_object_sink (object);
2282
2283   if (unrealize)
2284     {
2285       gtk_object_ref (object);
2286       gtk_container_remove (GTK_CONTAINER (widget->parent), widget);
2287       gtk_container_add (GTK_CONTAINER (new_parent), widget);
2288       gtk_object_unref (object);
2289     }
2290   else
2291     gtk_widget_reparent (GTK_WIDGET (menu), new_parent);
2292   
2293   if (was_floating)
2294     GTK_OBJECT_SET_FLAGS (object, GTK_FLOATING);
2295   else
2296     gtk_object_unref (object);
2297 }
2298
2299 static void
2300 gtk_menu_show_all (GtkWidget *widget)
2301 {
2302   g_return_if_fail (GTK_IS_MENU (widget));
2303
2304   /* Show children, but not self. */
2305   gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_show_all, NULL);
2306 }
2307
2308
2309 static void
2310 gtk_menu_hide_all (GtkWidget *widget)
2311 {
2312   g_return_if_fail (GTK_IS_MENU (widget));
2313
2314   /* Hide children, but not self. */
2315   gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_hide_all, NULL);
2316 }
2317
2318