Scrolling menus take 3



Ok, here is my latest try at scrolling menus. According to previous
discussions this one implements a more Mac-like handling of menu size and
positioning for srolling menus. It also includes a patch by Jonathan that
gives a new system for allocating size of toggles in menu items.

About the behaviour: When a menu is popped up from a menubar and the space
above the menubar is larger than below the menu is (still) placed above
the menu bar. This is a bit contrary to the stuff i've read about
menus. You're supposed to always use the same motion to active some menu
item so that you leverage the muscle memory. On the other hand this makes
the menus scroll when they might not need to, so I left the old behaviour.

There is still a bug, and I really need some help fixing it. When you pop
up a menu from a GtkOptionMenu the first time after having changed the
option everything works, but each time after that (until selecting
another menu item) the GtkMenuItem placed over the GtkOptionMenu doesn't
get any enter_notify. This means the current menu item isn't selected
until you mouse out of the item and in again.

I've tried pretty hard to understand why this happens, but I just can't
figure out it, and I don't know how to continue debugging it.

Comments on the behaviour and/or the code?

/ Alex

Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/gtk+/ChangeLog,v
retrieving revision 1.1439
diff -u -p -r1.1439 ChangeLog
--- ChangeLog	2000/10/22 17:19:51	1.1439
+++ ChangeLog	2000/10/23 16:21:29
@@ -1,3 +1,39 @@
+2000-10-23  Alexander Larsson  <alexl redhat com>
+
+	* gtk/gtkmenu.c: Add support for scrolling menus.
+
+	* gtk/gtkmenu.h: Add data needed for scrolling menus.
+
+	* gtk/gtkmenuitem.c (gtk_menu_item_popup_submenu): Pass the
+	current event time (if any) to gtk_menu_popup() so that single
+	click / button hold down mode can be detected by GtkMenu if
+	the menu appears over the menuitem.
+	
+	* gtk/gtkmenuitem.c (gtk_menu_item_position_menu): Change menu
+	positioning behaviour to fit to scrolling menus.
+
+	* gtk/gtkmenushell.[ch]: Virtualize gtk_menu_shell_insert() and
+	gtk_menu_shell_select_item() since these need to be overridden in
+	GtkMenu.
+
+	* gtk/gtkoptionmenu.c (gtk_opttion_menu_position): Change menu
+	positioning behaviour to fit to scrolling menus.
+	
+	Patch from Jonathan Blandford  <jrb redhat com>
+
+	* gtk/gtkmenuitem.[ch] (gtk_menu_item_toggle_size_request): new
+	system to handle size requests.  First, we ask what the size of
+	the toggle is.  Then, when allocating the size, we allocate the
+	toggle_size first.  This way we can have multiple menu-item
+	classes w/o needing a seperate class for each.
+
+	* gtk/gtkmenu.c (gtk_menu_size_request): Actually use the new system.
+	* gtk/gtkmenu.c (gtk_menu_size_allocate): Use the new system.
+
+	* gtk/gtkcheckmenuitem.c
+	(gtk_check_menu_item_toggle_size_request): New function to handle
+	the toggle size-request.
+	
 2000-10-22  Tor Lillqvist  <tml iki fi>
 
 	* gdk/win32/gdkgc-win32.c
Index: gtk/gtkcheckmenuitem.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkcheckmenuitem.c,v
retrieving revision 1.22
diff -u -p -r1.22 gtkcheckmenuitem.c
--- gtk/gtkcheckmenuitem.c	2000/07/26 11:32:42	1.22
+++ gtk/gtkcheckmenuitem.c	2000/10/23 16:21:30
@@ -28,25 +28,27 @@
 #include "gtkaccellabel.h"
 #include "gtksignal.h"
 
+#define CHECK_TOGGLE_SIZE 12
 
-
 enum {
   TOGGLED,
   LAST_SIGNAL
 };
 
 
-static void gtk_check_menu_item_class_init          (GtkCheckMenuItemClass *klass);
-static void gtk_check_menu_item_init                (GtkCheckMenuItem      *check_menu_item);
-static void gtk_check_menu_item_draw                (GtkWidget             *widget,
-						     GdkRectangle          *area);
-static gint gtk_check_menu_item_expose              (GtkWidget             *widget,
-						     GdkEventExpose        *event);
-static void gtk_check_menu_item_activate            (GtkMenuItem           *menu_item);
-static void gtk_check_menu_item_draw_indicator      (GtkCheckMenuItem      *check_menu_item,
-						     GdkRectangle          *area);
-static void gtk_real_check_menu_item_draw_indicator (GtkCheckMenuItem      *check_menu_item,
-						     GdkRectangle          *area);
+static void gtk_check_menu_item_class_init           (GtkCheckMenuItemClass *klass);
+static void gtk_check_menu_item_init                 (GtkCheckMenuItem      *check_menu_item);
+static void gtk_check_menu_item_draw                 (GtkWidget             *widget,
+						      GdkRectangle          *area);
+static gint gtk_check_menu_item_expose               (GtkWidget             *widget,
+						      GdkEventExpose        *event);
+static void gtk_check_menu_item_activate             (GtkMenuItem           *menu_item);
+static void gtk_check_menu_item_toggle_size_request  (GtkMenuItem           *menu_item,
+						      guint16               *requisition);
+static void gtk_check_menu_item_draw_indicator       (GtkCheckMenuItem      *check_menu_item,
+						      GdkRectangle          *area);
+static void gtk_real_check_menu_item_draw_indicator  (GtkCheckMenuItem      *check_menu_item,
+						      GdkRectangle          *area);
 
 
 static GtkMenuItemClass *parent_class = NULL;
@@ -95,8 +97,8 @@ gtk_check_menu_item_class_init (GtkCheck
   widget_class->expose_event = gtk_check_menu_item_expose;
   
   menu_item_class->activate = gtk_check_menu_item_activate;
-  menu_item_class->toggle_size = 12;
   menu_item_class->hide_on_activate = FALSE;
+  menu_item_class->toggle_size_request = gtk_check_menu_item_toggle_size_request;
   
   klass->toggled = NULL;
   klass->draw_indicator = gtk_real_check_menu_item_draw_indicator;
@@ -145,6 +147,16 @@ gtk_check_menu_item_set_active (GtkCheck
 
   if (check_menu_item->active != is_active)
     gtk_menu_item_activate (GTK_MENU_ITEM (check_menu_item));
+}
+
+static void
+gtk_check_menu_item_toggle_size_request (GtkMenuItem *menu_item,
+					 guint16     *requisition)
+{
+  g_return_if_fail (menu_item != NULL);
+  g_return_if_fail (GTK_IS_CHECK_MENU_ITEM (menu_item));
+
+  *requisition = CHECK_TOGGLE_SIZE;
 }
 
 void
Index: gtk/gtkmenu.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmenu.c,v
retrieving revision 1.49
diff -u -p -r1.49 gtkmenu.c
--- gtk/gtkmenu.c	2000/09/08 01:53:46	1.49
+++ gtk/gtkmenu.c	2000/10/23 16:21:30
@@ -33,6 +33,8 @@
 #include "gtkmenuitem.h"
 #include "gtksignal.h"
 #include "gtkwindow.h"
+#include "gtkhbox.h"
+#include "gtkvscrollbar.h"
 
 
 #define MENU_ITEM_CLASS(w)   GTK_MENU_ITEM_GET_CLASS (w)
@@ -41,6 +43,12 @@
 #define SUBMENU_NAV_REGION_PADDING 2
 #define SUBMENU_NAV_HYSTERESIS_TIMEOUT 333
 
+#define MENU_SCROLL_STEP 10
+#define MENU_SCROLL_ARROW_HEIGHT 16
+#define MENU_SCROLL_FAST_ZONE 4
+#define MENU_SCROLL_TIMEOUT1 150
+#define MENU_SCROLL_TIMEOUT2 50
+
 typedef struct _GtkMenuAttachData	GtkMenuAttachData;
 
 struct _GtkMenuAttachData
@@ -50,27 +58,40 @@ struct _GtkMenuAttachData
 };
 
 
-static void	gtk_menu_class_init    (GtkMenuClass	  *klass);
-static void	gtk_menu_init	       (GtkMenu		  *menu);
-static void	gtk_menu_destroy       (GtkObject	  *object);
-static void	gtk_menu_realize       (GtkWidget	  *widget);
-static void	gtk_menu_size_request  (GtkWidget	  *widget,
-					GtkRequisition    *requisition);
-static void	gtk_menu_size_allocate (GtkWidget	  *widget,
-					GtkAllocation     *allocation);
-static void	gtk_menu_paint	       (GtkWidget	  *widget);
-static void	gtk_menu_draw	       (GtkWidget	  *widget,
-					GdkRectangle      *area);
-static gboolean gtk_menu_expose	       (GtkWidget	  *widget,
-					GdkEventExpose    *event);
-static gboolean gtk_menu_key_press     (GtkWidget	  *widget,
-					GdkEventKey       *event);
-static gboolean gtk_menu_motion_notify (GtkWidget	  *widget,
-					GdkEventMotion    *event);
-static gboolean gtk_menu_enter_notify  (GtkWidget         *widget,
-					GdkEventCrossing  *event); 
-static gboolean gtk_menu_leave_notify  (GtkWidget         *widget,
-					GdkEventCrossing  *event);
+static void     gtk_menu_class_init        (GtkMenuClass     *klass);
+static void     gtk_menu_init              (GtkMenu          *menu);
+static void     gtk_menu_destroy           (GtkObject        *object);
+static void     gtk_menu_realize           (GtkWidget        *widget);
+static void     gtk_menu_unrealize         (GtkWidget        *widget);
+static void     gtk_menu_size_request      (GtkWidget        *widget,
+					    GtkRequisition   *requisition);
+static void     gtk_menu_size_allocate     (GtkWidget        *widget,
+					    GtkAllocation    *allocation);
+static void     gtk_menu_paint             (GtkWidget        *widget);
+static void     gtk_menu_draw              (GtkWidget        *widget,
+					    GdkRectangle     *area);
+static gboolean gtk_menu_expose            (GtkWidget        *widget,
+					    GdkEventExpose   *event);
+static gboolean gtk_menu_key_press         (GtkWidget        *widget,
+					    GdkEventKey      *event);
+static gboolean gtk_menu_motion_notify     (GtkWidget        *widget,
+					    GdkEventMotion   *event);
+static gboolean gtk_menu_enter_notify      (GtkWidget        *widget,
+					    GdkEventCrossing *event);
+static gboolean gtk_menu_leave_notify      (GtkWidget        *widget,
+					    GdkEventCrossing *event);
+static void     gtk_menu_scroll_to         (GtkMenu          *menu,
+					    gint              offset);
+static gboolean gtk_menu_scroll_timeout    (gpointer          data);
+static void     gtk_menu_select_item       (GtkMenuShell     *menu_shell,
+					    GtkWidget        *menu_item);
+static void     gtk_menu_real_insert       (GtkMenuShell     *menu_shell,
+					    GtkWidget        *child,
+					    gint              position);
+static void     gtk_menu_scrollbar_changed (GtkAdjustment    *adjustment,
+					    GtkMenu          *menu);
+static void     gtk_menu_handle_scrolling  (GtkMenu          *menu,
+					    gboolean         enter);
 
 static void     gtk_menu_stop_navigating_submenu       (GtkMenu          *menu);
 static gboolean gtk_menu_stop_navigating_submenu_cb    (gpointer          user_data);
@@ -137,6 +158,7 @@ gtk_menu_class_init (GtkMenuClass *class
   object_class->destroy = gtk_menu_destroy;
   
   widget_class->realize = gtk_menu_realize;
+  widget_class->unrealize = gtk_menu_unrealize;
   widget_class->draw = gtk_menu_draw;
   widget_class->size_request = gtk_menu_size_request;
   widget_class->size_allocate = gtk_menu_size_allocate;
@@ -150,6 +172,8 @@ gtk_menu_class_init (GtkMenuClass *class
   
   menu_shell_class->submenu_placement = GTK_LEFT_RIGHT;
   menu_shell_class->deactivate = gtk_menu_deactivate;
+  menu_shell_class->select_item = gtk_menu_select_item;
+  menu_shell_class->insert = gtk_menu_real_insert;
 
   binding_set = gtk_binding_set_by_class (class);
   gtk_binding_entry_add_signal (binding_set,
@@ -188,8 +212,7 @@ gtk_menu_window_event (GtkWidget *window
     {
     case GDK_KEY_PRESS:
     case GDK_KEY_RELEASE:
-      gtk_widget_event (menu, event);
-      handled = TRUE;
+      handled = gtk_widget_event (menu, event);
       break;
     default:
       break;
@@ -209,6 +232,7 @@ gtk_menu_init (GtkMenu *menu)
   menu->accel_group = NULL;
   menu->position_func = NULL;
   menu->position_func_data = NULL;
+  menu->toggle_size = 0;
 
   menu->toplevel = gtk_widget_new (GTK_TYPE_WINDOW,
 				   "type", GTK_WINDOW_POPUP,
@@ -225,9 +249,24 @@ gtk_menu_init (GtkMenu *menu)
   GTK_WIDGET_SET_FLAGS (menu, GTK_FLOATING);
   menu->needs_destruction_ref_count = TRUE;
 
+  menu->view_window = NULL;
+  menu->bin_window = NULL;
+
+  menu->scroll_offset = 0;
+  menu->scroll_step  = 0;
+  menu->timeout_id = 0;
+  menu->scroll_fast = FALSE;
+  
   menu->tearoff_window = NULL;
+  menu->tearoff_hbox = NULL;
   menu->torn_off = FALSE;
+  menu->tearoff_active = FALSE;
 
+  menu->upper_arrow_visible = FALSE;
+  menu->lower_arrow_visible = FALSE;
+  menu->upper_arrow_prelight = FALSE;
+  menu->lower_arrow_prelight = FALSE;
+  
   MENU_NEEDS_RESIZE (menu) = TRUE;
 }
 
@@ -241,6 +280,12 @@ gtk_menu_destroy (GtkObject *object)
 
   menu = GTK_MENU (object);
   
+  if (menu->timeout_id)
+    {
+      g_source_remove (menu->timeout_id);
+      menu->timeout_id = 0;
+    }
+  
   data = gtk_object_get_data (object, attach_data_key);
   if (data)
     gtk_menu_detach (menu);
@@ -382,9 +427,25 @@ gtk_menu_insert (GtkMenu   *menu,
 }
 
 static void
+gtk_menu_real_insert (GtkMenuShell     *menu_shell,
+		      GtkWidget        *child,
+		      gint              position)
+{
+  g_return_if_fail (menu_shell != NULL);
+  g_return_if_fail (GTK_IS_MENU (menu_shell));
+  g_return_if_fail (child != NULL);
+  g_return_if_fail (GTK_IS_MENU_ITEM (child));
+  
+  GTK_MENU_SHELL_CLASS (parent_class)->insert (menu_shell, child, position);
+  
+  gtk_widget_set_parent_window (child, GTK_MENU (menu_shell)->bin_window);
+}
+
+static void
 gtk_menu_tearoff_bg_copy (GtkMenu *menu)
 {
   GtkWidget *widget;
+  gint width, height;
 
   widget = GTK_WIDGET (menu);
 
@@ -393,25 +454,30 @@ gtk_menu_tearoff_bg_copy (GtkMenu *menu)
       GdkPixmap *pixmap;
       GdkGC *gc;
       GdkGCValues gc_values;
+
+      menu->tearoff_active = FALSE;
+      menu->saved_scroll_offset = menu->scroll_offset;
       
       gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS;
       gc = gdk_gc_new_with_values (widget->window,
 				   &gc_values, GDK_GC_SUBWINDOW);
       
-      pixmap = gdk_pixmap_new (widget->window,
-			       widget->requisition.width,
-			       widget->requisition.height,
+      gdk_window_get_size (menu->tearoff_window->window, &width, &height);
+      
+      pixmap = gdk_pixmap_new (menu->tearoff_window->window,
+			       width,
+			       height,
 			       -1);
 
       gdk_draw_pixmap (pixmap, gc,
-		       widget->window,
+		       menu->tearoff_window->window,
 		       0, 0, 0, 0, -1, -1);
       gdk_gc_unref (gc);
       
       gtk_widget_set_usize (menu->tearoff_window,
-			    widget->requisition.width,
-			    widget->requisition.height);
-      
+			    width,
+			    height);
+
       gdk_window_set_back_pixmap (menu->tearoff_window->window, pixmap, FALSE);
       gdk_pixmap_unref (pixmap);
     }
@@ -431,7 +497,7 @@ gtk_menu_popup (GtkMenu		    *menu,
   GtkWidget *parent;
   GdkEvent *current_event;
   GtkMenuShell *menu_shell;
-  
+
   g_return_if_fail (menu != NULL);
   g_return_if_fail (GTK_IS_MENU (menu));
   
@@ -479,7 +545,7 @@ gtk_menu_popup (GtkMenu		    *menu,
    */
   gtk_widget_show (GTK_WIDGET (menu));
   gtk_widget_show (menu->toplevel);
-  
+
   /* Find the last viewable ancestor, and make an X grab on it
    */
   parent = GTK_WIDGET (menu);
@@ -526,7 +592,9 @@ gtk_menu_popup (GtkMenu		    *menu,
 
       gdk_cursor_destroy (cursor);
     }
-  
+
+  gtk_menu_scroll_to (menu, menu->scroll_offset);
+
   gtk_grab_add (GTK_WIDGET (menu));
 }
 
@@ -534,7 +602,7 @@ void
 gtk_menu_popdown (GtkMenu *menu)
 {
   GtkMenuShell *menu_shell;
-  
+
   g_return_if_fail (menu != NULL);
   g_return_if_fail (GTK_IS_MENU (menu));
   
@@ -543,6 +611,12 @@ gtk_menu_popdown (GtkMenu *menu)
   menu_shell->parent_menu_shell = NULL;
   menu_shell->active = FALSE;
   menu_shell->ignore_enter = FALSE;
+
+  if (menu->timeout_id)
+    {
+      g_source_remove (menu->timeout_id);
+      menu->timeout_id = 0;
+    }
   
   gtk_menu_stop_navigating_submenu (menu);
   
@@ -562,9 +636,15 @@ gtk_menu_popdown (GtkMenu *menu)
 
   if (menu->torn_off)
     {
+      gint width, height;
+      gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
+      gtk_widget_set_usize (menu->tearoff_window,
+			    -1,
+			    height);
+      
       if (GTK_BIN (menu->toplevel)->child) 
 	{
-	  gtk_menu_reparent (menu, menu->tearoff_window, FALSE);
+	  gtk_menu_reparent (menu, menu->tearoff_hbox, FALSE);
 	} 
       else
 	{
@@ -577,6 +657,12 @@ gtk_menu_popdown (GtkMenu *menu)
 	      gdk_keyboard_ungrab (GDK_CURRENT_TIME);
 	    }
 	}
+      menu->tearoff_active = TRUE;
+      menu->tearoff_adjustment->upper = GTK_WIDGET (menu)->requisition.height;
+      gtk_adjustment_changed (menu->tearoff_adjustment);
+      
+      gtk_menu_scroll_to (menu, menu->saved_scroll_offset);
+      
     }
   else
     gtk_widget_hide (GTK_WIDGET (menu));
@@ -707,17 +793,29 @@ gtk_menu_reposition (GtkMenu *menu)
     gtk_menu_position (menu);
 }
 
+static void
+gtk_menu_scrollbar_changed (GtkAdjustment *adjustment,
+			    GtkMenu       *menu)
+{
+  g_return_if_fail (menu != NULL);
+  g_return_if_fail (GTK_IS_MENU (menu));
+
+  gtk_menu_scroll_to (menu, adjustment->value);
+}
 
 void       
 gtk_menu_set_tearoff_state (GtkMenu  *menu,
 			    gboolean  torn_off)
 {
+  gint width, height;
+
   g_return_if_fail (menu != NULL);
   g_return_if_fail (GTK_IS_MENU (menu));
 
   if (menu->torn_off != torn_off)
     {
       menu->torn_off = torn_off;
+      menu->tearoff_active = torn_off;
       
       if (menu->torn_off)
 	{
@@ -762,13 +860,44 @@ gtk_menu_set_tearoff_state (GtkMenu  *me
 					  GDK_DECOR_MAXIMIZE);
 	      gtk_window_set_policy (GTK_WINDOW (menu->tearoff_window),
 				     FALSE, FALSE, TRUE);
+
+	      menu->tearoff_hbox = gtk_hbox_new (FALSE, FALSE);
+	      gtk_container_add (GTK_CONTAINER (menu->tearoff_window), menu->tearoff_hbox);
+
+	      gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
+	      menu->tearoff_adjustment =
+		GTK_ADJUSTMENT (gtk_adjustment_new (0,
+						    0,
+						    GTK_WIDGET (menu)->requisition.height,
+						    MENU_SCROLL_STEP,
+						    height/2,
+						    height));
+	      gtk_signal_connect (GTK_OBJECT (menu->tearoff_adjustment), "value_changed",
+				  gtk_menu_scrollbar_changed,
+				  menu);
+	      menu->tearoff_scrollbar = gtk_vscrollbar_new (menu->tearoff_adjustment);
+
+	      gtk_box_pack_end (GTK_BOX (menu->tearoff_hbox),
+				menu->tearoff_scrollbar,
+				FALSE, FALSE, 0);
+	      
+	      if (menu->tearoff_adjustment->upper > height)
+		gtk_widget_show (menu->tearoff_scrollbar);
+	      gtk_widget_show (menu->tearoff_hbox);
 	    }
-	  gtk_menu_reparent (menu, menu->tearoff_window, FALSE);
+	  gtk_menu_reparent (menu, menu->tearoff_hbox, FALSE);
 
 	  gtk_menu_position (menu);
 	  
 	  gtk_widget_show (GTK_WIDGET (menu));
 	  gtk_widget_show (menu->tearoff_window);
+	  
+	  menu->tearoff_adjustment->value = 0;
+	  menu->tearoff_adjustment->upper = GTK_WIDGET (menu)->requisition.height;
+	  gtk_adjustment_changed (menu->tearoff_adjustment);
+	  
+	  gtk_menu_scroll_to (menu, 0);
+
 	}
       else
 	{
@@ -812,9 +941,15 @@ gtk_menu_realize (GtkWidget *widget)
 {
   GdkWindowAttr attributes;
   gint attributes_mask;
-  
+  gint border_width;
+  GtkMenu *menu;
+  GtkWidget *child;
+  GList *children;
+
   g_return_if_fail (widget != NULL);
   g_return_if_fail (GTK_IS_MENU (widget));
+
+  menu = GTK_MENU (widget);
   
   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
   
@@ -826,19 +961,82 @@ gtk_menu_realize (GtkWidget *widget)
   attributes.wclass = GDK_INPUT_OUTPUT;
   attributes.visual = gtk_widget_get_visual (widget);
   attributes.colormap = gtk_widget_get_colormap (widget);
+  
   attributes.event_mask = gtk_widget_get_events (widget);
-  attributes.event_mask |= (GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK);
+  attributes.event_mask |= (GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK |
+			    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK );
   
   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
   gdk_window_set_user_data (widget->window, widget);
   
+  border_width = GTK_CONTAINER (widget)->border_width;
+  
+  attributes.x = border_width + widget->style->xthickness;
+  attributes.y = border_width + widget->style->ythickness;
+  attributes.width = MAX (1, (gint)widget->allocation.width - attributes.x * 2);
+  attributes.height = MAX (1, (gint)widget->allocation.height - attributes.y * 2);
+
+  if (menu->upper_arrow_visible)
+    {
+      attributes.y += MENU_SCROLL_ARROW_HEIGHT;
+      attributes.height -= MENU_SCROLL_ARROW_HEIGHT;
+    }
+  if (menu->lower_arrow_visible)
+    attributes.height -= MENU_SCROLL_ARROW_HEIGHT;
+
+  menu->view_window = gdk_window_new (widget->window, &attributes, attributes_mask);
+  gdk_window_set_user_data (menu->view_window, menu);
+
+  attributes.x = 0;
+  attributes.y = 0;
+  attributes.height = widget->requisition.height;
+  
+  menu->bin_window = gdk_window_new (menu->view_window, &attributes, attributes_mask);
+  gdk_window_set_user_data (menu->bin_window, menu);
+
+  children = GTK_MENU_SHELL (menu)->children;
+  while (children)
+    {
+      child = children->data;
+      children = children->next;
+	  
+      gtk_widget_set_parent_window (child, menu->bin_window);
+    }
+  
   widget->style = gtk_style_attach (widget->style, widget->window);
+  gtk_style_set_background (widget->style, menu->bin_window, GTK_STATE_NORMAL);
+  gtk_style_set_background (widget->style, menu->view_window, GTK_STATE_NORMAL);
   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
-  gtk_menu_paint(widget);
+
+  gtk_menu_paint (widget);
+  
+  gdk_window_show (menu->bin_window);
+  gdk_window_show (menu->view_window);
 }
 
 static void
+gtk_menu_unrealize (GtkWidget *widget)
+{
+  GtkMenu *menu;
+
+  g_return_if_fail (widget != NULL);
+  g_return_if_fail (GTK_IS_MENU (widget));
+
+  menu = GTK_MENU (widget);
+
+  gdk_window_set_user_data (menu->view_window, NULL);
+  gdk_window_destroy (menu->view_window);
+  menu->view_window = NULL;
+
+  gdk_window_set_user_data (menu->bin_window, NULL);
+  gdk_window_destroy (menu->bin_window);
+  menu->bin_window = NULL;
+
+  (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
+}
+
+static void
 gtk_menu_size_request (GtkWidget      *widget,
 		       GtkRequisition *requisition)
 {
@@ -871,13 +1069,16 @@ gtk_menu_size_request (GtkWidget      *w
       
       if (GTK_WIDGET_VISIBLE (child))
 	{
+	  guint16 toggle_size;
+
 	  GTK_MENU_ITEM (child)->show_submenu_indicator = TRUE;
 	  gtk_widget_size_request (child, &child_requisition);
 	  
 	  requisition->width = MAX (requisition->width, child_requisition.width);
 	  requisition->height += child_requisition.height;
-	  
-	  max_toggle_size = MAX (max_toggle_size, MENU_ITEM_CLASS (child)->toggle_size);
+
+	  gtk_menu_item_toggle_size_request (GTK_MENU_ITEM (child), &toggle_size);
+	  max_toggle_size = MAX (max_toggle_size, toggle_size);
 	  max_accel_width = MAX (max_accel_width, GTK_MENU_ITEM (child)->accelerator_width);
 	}
     }
@@ -888,14 +1089,7 @@ gtk_menu_size_request (GtkWidget      *w
   requisition->height += (GTK_CONTAINER (menu)->border_width +
 			  widget->style->ythickness) * 2;
   
-  children = menu_shell->children;
-  while (children)
-    {
-      child = children->data;
-      children = children->next;
-      
-      GTK_MENU_ITEM (child)->toggle_size = max_toggle_size;
-    }
+  menu->toggle_size = max_toggle_size;
 }
 
 static void
@@ -907,28 +1101,51 @@ gtk_menu_size_allocate (GtkWidget     *w
   GtkWidget *child;
   GtkAllocation child_allocation;
   GList *children;
-  
+  gint x, y;
+  gint width, height;
+
   g_return_if_fail (widget != NULL);
   g_return_if_fail (GTK_IS_MENU (widget));
   g_return_if_fail (allocation != NULL);
   
   menu = GTK_MENU (widget);
   menu_shell = GTK_MENU_SHELL (widget);
-  
+
   widget->allocation = *allocation;
-  if (GTK_WIDGET_REALIZED (widget))
-    gdk_window_move_resize (widget->window,
-			    allocation->x, allocation->y,
-			    allocation->width, allocation->height);
 
+  x = (GTK_CONTAINER (menu)->border_width + widget->style->xthickness);
+  y = (GTK_CONTAINER (menu)->border_width + widget->style->ythickness);
+  
+  width = MAX (1, (gint)allocation->width - x * 2);
+  height = MAX (1, (gint)allocation->height - y * 2);
+  
+  if (menu->upper_arrow_visible && !menu->tearoff_active)
+    {
+      y += MENU_SCROLL_ARROW_HEIGHT;
+      height -= MENU_SCROLL_ARROW_HEIGHT;
+    }
+  
+  if (menu->lower_arrow_visible && !menu->tearoff_active)
+    height -= MENU_SCROLL_ARROW_HEIGHT;
+  
+  if (GTK_WIDGET_REALIZED (widget))
+    {
+      gdk_window_move_resize (widget->window,
+			      allocation->x, allocation->y,
+			      allocation->width, allocation->height);
+
+      gdk_window_move_resize (menu->view_window,
+			      x,
+			      y,
+			      width,
+			      height);
+    }
 
   if (menu_shell->children)
     {
-      child_allocation.x = (GTK_CONTAINER (menu)->border_width +
-			    widget->style->xthickness);
-      child_allocation.y = (GTK_CONTAINER (menu)->border_width +
-			    widget->style->ythickness);
-      child_allocation.width = MAX (1, (gint)allocation->width - child_allocation.x * 2);
+      child_allocation.x = 0;
+      child_allocation.y = 0;
+      child_allocation.width = width;
       
       children = menu_shell->children;
       while (children)
@@ -942,21 +1159,39 @@ gtk_menu_size_allocate (GtkWidget     *w
 	      gtk_widget_get_child_requisition (child, &child_requisition);
 	      
 	      child_allocation.height = child_requisition.height;
-	      
+
+	      gtk_menu_item_toggle_size_allocate (GTK_MENU_ITEM (child),
+						  menu->toggle_size);
 	      gtk_widget_size_allocate (child, &child_allocation);
 	      gtk_widget_queue_draw (child);
 	      
 	      child_allocation.y += child_allocation.height;
 	    }
 	}
+      
+      /* Resize the item window */
+      if (GTK_WIDGET_REALIZED (widget))
+	{
+	  gdk_window_resize (menu->bin_window,
+			     child_allocation.width,
+			     child_allocation.y + allocation->height);
+	}
+
     }
 }
 
 static void
 gtk_menu_paint (GtkWidget *widget)
 {
+  guint border_x;
+  guint border_y;
+  guint width, height;
+  GtkMenu *menu;
+  
   g_return_if_fail (widget != NULL);
   g_return_if_fail (GTK_IS_MENU (widget));
+
+  menu = GTK_MENU(widget);
   
   if (GTK_WIDGET_DRAWABLE (widget))
     {
@@ -966,7 +1201,66 @@ gtk_menu_paint (GtkWidget *widget)
 		     GTK_SHADOW_OUT,
 		     NULL, widget, "menu",
 		     0, 0, -1, -1);
+
+      border_x = GTK_CONTAINER (widget)->border_width + widget->style->xthickness;
+      border_y = GTK_CONTAINER (widget)->border_width + widget->style->ythickness;
+      gdk_window_get_size (widget->window, &width, &height);
+
+      if (menu->upper_arrow_visible && !menu->tearoff_active)
+	{
+	  gtk_paint_box (widget->style,
+			 widget->window,
+			 menu->upper_arrow_prelight ?
+			 GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
+			 GTK_SHADOW_OUT,
+			 NULL, widget, "menu",
+			 border_x,
+			 border_y,
+			 width - 2*border_x,
+			 MENU_SCROLL_ARROW_HEIGHT);
+	  
+	  gtk_paint_arrow (widget->style,
+			   widget->window,
+			   menu->upper_arrow_prelight ?
+			   GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
+			   GTK_SHADOW_OUT,
+			   NULL, widget, "menu",
+			   GTK_ARROW_UP,
+			   TRUE,
+			   width / 2 - MENU_SCROLL_ARROW_HEIGHT / 2 + 1,
+			   2 * border_y + 1,
+			   MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2,
+			   MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2);
+	}
+  
+      if (menu->lower_arrow_visible && !menu->tearoff_active)
+	{
+	  gtk_paint_box (widget->style,
+			 widget->window,
+			 menu->lower_arrow_prelight ?
+			 GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
+			 GTK_SHADOW_OUT,
+			 NULL, widget, "menu",
+			 border_x,
+			 height - border_y - MENU_SCROLL_ARROW_HEIGHT + 1,
+			 width - 2*border_x,
+			 MENU_SCROLL_ARROW_HEIGHT);
+	  
+	  gtk_paint_arrow (widget->style,
+			   widget->window,
+			   menu->lower_arrow_prelight ?
+			   GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
+			   GTK_SHADOW_OUT,
+			   NULL, widget, "menu",
+			   GTK_ARROW_DOWN,
+			   TRUE,
+			   width / 2 - MENU_SCROLL_ARROW_HEIGHT / 2 + 1,
+			   height - MENU_SCROLL_ARROW_HEIGHT + 1,
+			   MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2,
+			   MENU_SCROLL_ARROW_HEIGHT - 2 * border_y - 2);
+	}
     }
+
 }
 
 static void
@@ -1044,7 +1338,7 @@ gtk_menu_key_press (GtkWidget	*widget,
 {
   GtkMenuShell *menu_shell;
   gboolean delete = FALSE;
-  
+
   g_return_val_if_fail (widget != NULL, FALSE);
   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
   g_return_val_if_fail (event != NULL, FALSE);
@@ -1136,6 +1430,10 @@ gtk_menu_motion_notify  (GtkWidget	   *w
 
   gboolean need_enter;
 
+
+  if (GTK_IS_MENU (widget))
+    gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE);
+  
   /* We received the event for one of two reasons:
    *
    * a) We are the active menu, and did gtk_grab_add()
@@ -1145,7 +1443,6 @@ gtk_menu_motion_notify  (GtkWidget	   *w
    * is the parent of the menu item, for a), we need to find that menu,
    * which may be different from 'widget'.
    */
-  
   menu_item = gtk_get_event_widget ((GdkEvent*) event);
   if (!menu_item || !GTK_IS_MENU_ITEM (menu_item) || !GTK_WIDGET_IS_SENSITIVE (menu_item) ||
       !GTK_IS_MENU (menu_item->parent))
@@ -1199,11 +1496,164 @@ gtk_menu_motion_notify  (GtkWidget	   *w
 }
 
 static gboolean
+gtk_menu_scroll_timeout (gpointer  data)
+{
+  GtkMenu *menu;
+  GtkWidget *widget;
+  gint offset;
+  guint view_width, view_height;
+
+  menu = GTK_MENU (data);
+  widget = GTK_WIDGET (menu);
+
+  offset = menu->scroll_offset + menu->scroll_step;
+
+  if ((menu->scroll_offset >= 0) && (offset < 0))
+    offset = 0;
+
+  /* If we scroll upward and the non-visible top part
+   * is smaller than the scroll arrow it would be
+   * pretty stupid to show the arrow and taking more
+   * screen space than just scrolling to the top.
+   */
+  if ((menu->scroll_step < 0) && (offset < MENU_SCROLL_ARROW_HEIGHT))
+    offset = 0;
+
+  /* Move/resize the viewport according to arrows: */
+  gdk_window_get_size (widget->window, &view_width, &view_height);
+
+  if (offset > 0)
+    view_height -= MENU_SCROLL_ARROW_HEIGHT;
+
+  if ((menu->scroll_offset + view_height <= widget->requisition.height) &&
+      (offset + view_height > widget->requisition.height))
+    offset = widget->requisition.height - view_height;
+  
+  gtk_menu_scroll_to (menu, offset);
+
+  return TRUE;
+}
+
+static void
+gtk_menu_handle_scrolling (GtkMenu *menu, gboolean enter)
+{
+  GtkMenuShell *menu_shell;
+  gint width, height;
+  gint x, y;
+  guint border;
+  GdkRectangle rect;
+  gboolean in_arrow;
+  gboolean scroll_fast = FALSE;
+
+  menu_shell = GTK_MENU_SHELL (menu);
+
+  gdk_window_get_pointer (GTK_WIDGET (menu)->window, &x, &y, NULL);
+  gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
+
+  border = GTK_CONTAINER (menu)->border_width + GTK_WIDGET (menu)->style->ythickness;
+
+  if (menu->upper_arrow_visible && !menu->tearoff_active)
+    {
+      rect.x = 0;
+      rect.y = 0;
+      rect.width = width;
+      rect.height = MENU_SCROLL_ARROW_HEIGHT + border;
+      
+      in_arrow = FALSE;
+      if ((x >= rect.x) && (x < rect.x + rect.width) &&
+	  (y >= rect.y) && (y < rect.y + rect.height))
+	{
+	  in_arrow = TRUE;
+	  scroll_fast = (y < rect.y + MENU_SCROLL_FAST_ZONE);
+	}
+	
+      if (enter && in_arrow &&
+	  (!menu->upper_arrow_prelight || menu->scroll_fast != scroll_fast))
+	{
+	  menu->upper_arrow_prelight = TRUE;
+	  menu->scroll_fast = scroll_fast;
+	  gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
+	  
+	  /* Deselect the active item so that any submenus are poped down */
+	  gtk_menu_shell_deselect (menu_shell);
+	  
+	  if (menu->timeout_id)
+	    g_source_remove (menu->timeout_id);
+	  menu->scroll_step = -MENU_SCROLL_STEP;
+	  menu->timeout_id = g_timeout_add ((scroll_fast)?MENU_SCROLL_TIMEOUT2:MENU_SCROLL_TIMEOUT1,
+					    gtk_menu_scroll_timeout,
+					    menu);
+	}
+      else if (!enter && !in_arrow && menu->upper_arrow_prelight)
+	{
+	  menu->upper_arrow_prelight = FALSE;
+	  gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
+	  
+	  if (menu->timeout_id)
+	    {
+	      g_source_remove (menu->timeout_id);
+	      menu->timeout_id = 0;
+	      menu->scroll_step = 0;
+	    }
+	}
+    }
+  
+  if (menu->lower_arrow_visible && !menu->tearoff_active)
+    {
+      rect.x = 0;
+      rect.y = height - border - MENU_SCROLL_ARROW_HEIGHT;
+      rect.width = width;
+      rect.height = MENU_SCROLL_ARROW_HEIGHT + border;
+
+      in_arrow = FALSE;
+      if ((x >= rect.x) && (x < rect.x + rect.width) &&
+	  (y >= rect.y) && (y < rect.y + rect.height))
+	{
+	  in_arrow = TRUE;
+	  scroll_fast = (y > rect.y + rect.height - MENU_SCROLL_FAST_ZONE);
+	}
+
+      if (enter && in_arrow &&
+	  (!menu->lower_arrow_prelight || menu->scroll_fast != scroll_fast))
+	{
+	  menu->lower_arrow_prelight = TRUE;
+	  menu->scroll_fast = scroll_fast;
+	  gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
+
+	  /* Deselect the active item so that any submenus are poped down */
+	  gtk_menu_shell_deselect (menu_shell);
+	  
+	  if (menu->timeout_id)
+	    g_source_remove (menu->timeout_id);
+	  menu->scroll_step = MENU_SCROLL_STEP;
+	  menu->timeout_id = g_timeout_add ((scroll_fast)?MENU_SCROLL_TIMEOUT2:MENU_SCROLL_TIMEOUT1,
+					    gtk_menu_scroll_timeout,
+					    menu);
+	}
+      else if (!enter && !in_arrow && menu->lower_arrow_prelight)
+	{
+	  menu->lower_arrow_prelight = FALSE;
+	  gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
+	  
+	  if (menu->timeout_id)
+	    {
+	      g_source_remove (menu->timeout_id);
+	      menu->timeout_id = 0;
+	      menu->scroll_step = 0;
+	    }
+	}
+    }
+}
+
+static gboolean
 gtk_menu_enter_notify (GtkWidget        *widget,
 		       GdkEventCrossing *event)
 {
   GtkWidget *menu_item;
 
+  if (widget && GTK_IS_MENU(widget))
+    gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE);
+      
   /* If this is a faked enter (see gtk_menu_motion_notify), 'widget'
    * will not correspond to the event widget's parent.  Check to see
    * if we are in the parent's navigation region.
@@ -1211,7 +1661,7 @@ gtk_menu_enter_notify (GtkWidget        
   menu_item = gtk_get_event_widget ((GdkEvent*) event);
   if (menu_item && GTK_IS_MENU_ITEM (menu_item) && GTK_IS_MENU (menu_item->parent) &&
       gtk_menu_navigating_submenu (GTK_MENU (menu_item->parent), event->x_root, event->y_root))
-    return TRUE; 
+    return TRUE;
 
   return GTK_WIDGET_CLASS (parent_class)->enter_notify_event (widget, event); 
 }
@@ -1223,13 +1673,15 @@ gtk_menu_leave_notify (GtkWidget        
   GtkMenuShell *menu_shell;
   GtkMenu *menu;
   GtkMenuItem *menu_item;
-  GtkWidget *event_widget; 
+  GtkWidget *event_widget;
 
   menu = GTK_MENU (widget);
   menu_shell = GTK_MENU_SHELL (widget); 
   
   if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
     return TRUE; 
+
+  gtk_menu_handle_scrolling (menu, FALSE);
   
   event_widget = gtk_get_event_widget ((GdkEvent*) event);
   
@@ -1416,12 +1868,18 @@ gtk_menu_position (GtkMenu *menu)
   GtkWidget *widget;
   GtkRequisition requisition;
   gint x, y;
- 
+  gint screen_width;
+  gint screen_height;
+  gint scroll_offset;
+
   g_return_if_fail (menu != NULL);
   g_return_if_fail (GTK_IS_MENU (menu));
 
   widget = GTK_WIDGET (menu);
 
+  screen_width = gdk_screen_width ();
+  screen_height = gdk_screen_height ();
+
   gdk_window_get_pointer (NULL, &x, &y, NULL);
 
   /* We need the requisition to figure out the right place to
@@ -1430,30 +1888,232 @@ gtk_menu_position (GtkMenu *menu)
    * the requisition won't have been recomputed yet.
    */
   gtk_widget_size_request (widget, &requisition);
-      
+
   if (menu->position_func)
     (* menu->position_func) (menu, &x, &y, menu->position_func_data);
   else
     {
-      gint screen_width;
-      gint screen_height;
-      
-      screen_width = gdk_screen_width ();
-      screen_height = gdk_screen_height ();
-	  
       x = CLAMP (x - 2, 0, MAX (0, screen_width - requisition.width));
       y = CLAMP (y - 2, 0, MAX (0, screen_height - requisition.height));
     }
 
+  scroll_offset = 0;
+  
+  if (y + requisition.height > screen_height)
+    {
+      scroll_offset -= y + requisition.height - screen_height;
+      y = screen_height - requisition.height;
+    }
+  
+  if (y < 0)
+    {
+      scroll_offset -= y;
+      y = 0;
+    }
+
+  if (scroll_offset > 0)
+    scroll_offset += MENU_SCROLL_ARROW_HEIGHT;
+  
+  if (requisition.height > screen_height)
+    requisition.height = screen_height;
+
   /* FIXME: The MAX() here is because gtk_widget_set_uposition
    * is broken. Once we provide an alternate interface that
    * allows negative values, then we can remove them.
    */
   gtk_widget_set_uposition (GTK_MENU_SHELL (menu)->active ?
-			        menu->toplevel : menu->tearoff_window, 
-			    MAX (x, 0), MAX (y, 0));
+			    menu->toplevel : menu->tearoff_window, 
+			    MAX (x, 0), y);
+  gtk_widget_set_usize (GTK_MENU_SHELL (menu)->active ?
+			menu->toplevel : menu->tearoff_hbox,
+			-1, requisition.height);
+
+  menu->scroll_offset = scroll_offset;
 }
 
+static void
+gtk_menu_scroll_to (GtkMenu *menu,
+		    gint    offset)
+{
+  GtkWidget *widget;
+  gint x, y;
+  guint view_width, view_height;
+  gint border_width;
+  gboolean last_visible;
+  guint menu_height;
+
+  widget = GTK_WIDGET (menu);
+
+  /* Scroll the menu: */
+  gdk_window_move (menu->bin_window, 0, -offset);
+
+  /* Move/resize the viewport according to arrows: */
+  gdk_window_get_size (widget->window, &view_width, &view_height);
+
+  border_width = GTK_CONTAINER (menu)->border_width;
+  view_width -= (border_width + widget->style->xthickness) * 2;
+  view_height -= (border_width + widget->style->ythickness) * 2;
+  menu_height = widget->requisition.height - (border_width + widget->style->ythickness) * 2;
+
+  x = border_width + widget->style->xthickness;
+  y = border_width + widget->style->ythickness;
+  
+  if (!menu->tearoff_active)
+    {
+      last_visible = menu->upper_arrow_visible;
+      menu->upper_arrow_visible = (offset > 0);
+
+      if (menu->upper_arrow_visible)
+	view_height -= MENU_SCROLL_ARROW_HEIGHT;
+      
+      if ( (last_visible != menu->upper_arrow_visible) &&
+	   !menu->upper_arrow_visible)
+	{
+	  menu->upper_arrow_prelight = FALSE;
+	  
+	  /* If we hid the upper arrow, possibly remove timeout */
+	  if ((menu->scroll_step < 0) && menu->timeout_id)
+	    {
+	      g_source_remove (menu->timeout_id);
+	      menu->timeout_id = 0;
+	      menu->scroll_step = 0;
+	    }
+	}
+      
+      last_visible = menu->lower_arrow_visible;
+      menu->lower_arrow_visible = (view_height + offset < menu_height);
+      
+      if (menu->lower_arrow_visible)
+	view_height -= MENU_SCROLL_ARROW_HEIGHT;
+      
+      if ( (last_visible != menu->lower_arrow_visible) &&
+	   !menu->lower_arrow_visible)
+	{
+	  menu->lower_arrow_prelight = FALSE;
+	  
+	  /* If we hid the lower arrow, possibly remove timeout */
+	  if ((menu->scroll_step > 0) && menu->timeout_id)
+	    {
+	      g_source_remove (menu->timeout_id);
+	      menu->timeout_id = 0;
+	      menu->scroll_step = 0;
+	    }
+	}
+      
+      if (menu->upper_arrow_visible)
+	y += MENU_SCROLL_ARROW_HEIGHT;
+    }
+  
+  
+  gdk_window_move_resize (menu->view_window,
+			  x,
+			  y,
+			  view_width,
+			  view_height);
+
+  menu->scroll_offset = offset;
+}
+
+static void
+gtk_menu_select_item (GtkMenuShell  *menu_shell,
+		      GtkWidget     *menu_item)
+{
+  GtkMenu *menu;
+  GtkWidget *child;
+  GList *children;
+  GtkRequisition child_requisition;
+  gint child_offset, child_height;
+  gint width, height;
+  gint y;
+  gint arrow_height;
+  
+  g_return_if_fail (menu_shell != NULL);
+  g_return_if_fail (GTK_IS_MENU (menu_shell));
+
+  menu = GTK_MENU (menu_shell);
+
+  /* We need to check if the selected item fully visible.
+   * If not we need to scroll the menu so that it becomes fully
+   * visible.
+   */
+
+  child = NULL;
+  child_offset = 0;
+  child_height = 0;
+  children = menu_shell->children;
+  while (children)
+    {
+      child = children->data;
+      children = children->next;
+      
+      if (GTK_WIDGET_VISIBLE (child))
+	{
+	  gtk_widget_size_request (child, &child_requisition);
+	  child_offset += child_height;
+	  child_height = child_requisition.height;
+	}
+      
+      if (child == menu_item)
+	break;
+    }
+
+  if (child == menu_item)
+    {
+      y = menu->scroll_offset;
+      gdk_window_get_size (GTK_WIDGET (menu)->window, &width, &height);
+
+      height -= 2*GTK_CONTAINER (menu)->border_width + 2*GTK_WIDGET (menu)->style->ythickness;
+      
+      if (child_offset + child_height <= y)
+	{
+	  /* Ignore the enter event we might get if the pointer is on the menu
+	   */
+	  menu_shell->ignore_enter = TRUE;
+	  gtk_menu_scroll_to (menu, child_offset);
+	}
+      else
+	{
+	  arrow_height = 0;
+	  if (menu->upper_arrow_visible && !menu->tearoff_active)
+	    arrow_height += MENU_SCROLL_ARROW_HEIGHT;
+	  if (menu->lower_arrow_visible && !menu->tearoff_active)
+	    arrow_height += MENU_SCROLL_ARROW_HEIGHT;
+	  if ( child_offset >= y + height - arrow_height)
+	    {
+	      y = 0;
+	      children = menu_shell->children;
+	      while (children)
+		{
+		  child = children->data;
+		  children = children->next;
+		  
+		  if (GTK_WIDGET_VISIBLE (child))
+		    {
+		      arrow_height = 0;
+		      if (y > 0 && !menu->tearoff_active)
+			arrow_height += MENU_SCROLL_ARROW_HEIGHT;
+		      if (children && !menu->tearoff_active)
+			arrow_height += MENU_SCROLL_ARROW_HEIGHT;
+		      if (child_offset < y + height - arrow_height) 
+			break;
+		      gtk_widget_size_request (child, &child_requisition);
+		      y += child_requisition.height;
+		    }
+		}
+
+	      /* Ignore the enter event we might get if the pointer is on the menu
+	       */
+	      menu_shell->ignore_enter = TRUE;
+	      gtk_menu_scroll_to (menu, y);
+	    }
+	}    
+      
+    }
+
+  GTK_MENU_SHELL_CLASS (parent_class)->select_item (menu_shell, menu_item);
+}
+
+
 /* Reparent the menu, taking care of the refcounting
  */
 static void 
@@ -1477,7 +2137,6 @@ gtk_menu_reparent (GtkMenu      *menu, 
     }
   else
     gtk_widget_reparent (GTK_WIDGET (menu), new_parent);
-  gtk_widget_set_usize (new_parent, -1, -1);
   
   if (was_floating)
     GTK_OBJECT_SET_FLAGS (object, GTK_FLOATING);
@@ -1505,4 +2164,5 @@ gtk_menu_hide_all (GtkWidget *widget)
   /* Hide children, but not self. */
   gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_hide_all, NULL);
 }
+
 
Index: gtk/gtkmenu.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmenu.h,v
retrieving revision 1.22
diff -u -p -r1.22 gtkmenu.h
--- gtk/gtkmenu.h	2000/09/02 02:43:50	1.22
+++ gtk/gtkmenu.h	2000/10/23 16:21:30
@@ -37,7 +37,6 @@
 extern "C" {
 #endif /* __cplusplus */
 
-
 #define GTK_TYPE_MENU			(gtk_menu_get_type ())
 #define GTK_MENU(obj)			(GTK_CHECK_CAST ((obj), GTK_TYPE_MENU, GtkMenu))
 #define GTK_MENU_CLASS(klass)		(GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_MENU, GtkMenuClass))
@@ -62,18 +61,31 @@ struct _GtkMenu
   
   GtkWidget *parent_menu_item;
   GtkWidget *old_active_menu_item;
-  
+
   GtkAccelGroup *accel_group;
   GtkMenuPositionFunc position_func;
   gpointer position_func_data;
 
+  guint toggle_size;
   /* Do _not_ touch these widgets directly. We hide the reference
    * count from the toplevel to the menu, so it must be restored
    * before operating on these widgets
    */
   GtkWidget *toplevel;
+  
   GtkWidget *tearoff_window;
-
+  GtkWidget *tearoff_hbox;
+  GtkWidget *tearoff_scrollbar;
+  GtkAdjustment *tearoff_adjustment;
+
+  GdkWindow *view_window;
+  GdkWindow *bin_window;
+
+  gint scroll_offset;
+  gint saved_scroll_offset;
+  gint scroll_step;
+  guint timeout_id;
+  
   /* When a submenu of this menu is popped up, motion in this
    * region is ignored
    */
@@ -82,6 +94,17 @@ struct _GtkMenu
 
   guint needs_destruction_ref_count : 1;
   guint torn_off : 1;
+  /* The tearoff is active when it is torn off and the not-torn-off
+   * menu is not popped up.
+   */
+  guint tearoff_active : 1; 
+
+  guint scroll_fast : 1;
+
+  guint upper_arrow_visible : 1;
+  guint lower_arrow_visible : 1;
+  guint upper_arrow_prelight : 1;
+  guint lower_arrow_prelight : 1;
 };
 
 struct _GtkMenuClass
Index: gtk/gtkmenuitem.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmenuitem.c,v
retrieving revision 1.41
diff -u -p -r1.41 gtkmenuitem.c
--- gtk/gtkmenuitem.c	2000/07/26 11:32:45	1.41
+++ gtk/gtkmenuitem.c	2000/10/23 16:21:30
@@ -42,6 +42,8 @@
 enum {
   ACTIVATE,
   ACTIVATE_ITEM,
+  TOGGLE_SIZE_REQUEST,
+  TOGGLE_SIZE_ALLOCATE,
   LAST_SIGNAL
 };
 
@@ -59,9 +61,15 @@ static void gtk_menu_item_draw          
 					  GdkRectangle     *area);
 static gint gtk_menu_item_expose         (GtkWidget        *widget,
 					  GdkEventExpose   *event);
-static void gtk_real_menu_item_select    (GtkItem          *item);
-static void gtk_real_menu_item_deselect  (GtkItem          *item);
-static void gtk_real_menu_item_activate_item  (GtkMenuItem      *item);
+
+static void gtk_real_menu_item_select               (GtkItem     *item);
+static void gtk_real_menu_item_deselect             (GtkItem     *item);
+static void gtk_real_menu_item_activate_item        (GtkMenuItem *item);
+static void gtk_real_menu_item_toggle_size_request  (GtkMenuItem *menu_item,
+						     guint16     *requisition);
+static void gtk_real_menu_item_toggle_size_allocate (GtkMenuItem *menu_item,
+						     guint16      allocation);
+
 static gint gtk_menu_item_select_timeout (gpointer          data);
 static void gtk_menu_item_popup_submenu  (gpointer     data);
 static void gtk_menu_item_position_menu  (GtkMenu          *menu,
@@ -138,6 +146,24 @@ gtk_menu_item_class_init (GtkMenuItemCla
                     gtk_signal_default_marshaller,
 		    GTK_TYPE_NONE, 0);
 
+  menu_item_signals[TOGGLE_SIZE_REQUEST] =
+    gtk_signal_new ("toggle_size_request",
+                    GTK_RUN_FIRST,
+                    GTK_CLASS_TYPE (object_class),
+                    GTK_SIGNAL_OFFSET (GtkMenuItemClass, toggle_size_request),
+                    gtk_marshal_NONE__POINTER,
+		    GTK_TYPE_NONE, 1,
+		    GTK_TYPE_POINTER);
+
+  menu_item_signals[TOGGLE_SIZE_ALLOCATE] =
+    gtk_signal_new ("toggle_size_allocate",
+                    GTK_RUN_FIRST,
+                    GTK_CLASS_TYPE (object_class),
+                    GTK_SIGNAL_OFFSET (GtkMenuItemClass, toggle_size_allocate),
+                    gtk_marshal_NONE__UINT,
+		    GTK_TYPE_NONE, 1,
+		    GTK_TYPE_INT);
+
   gtk_object_class_add_signals (object_class, menu_item_signals, LAST_SIGNAL);
 
   object_class->destroy = gtk_menu_item_destroy;
@@ -157,8 +183,9 @@ gtk_menu_item_class_init (GtkMenuItemCla
 
   klass->activate = NULL;
   klass->activate_item = gtk_real_menu_item_activate_item;
+  klass->toggle_size_request = gtk_real_menu_item_toggle_size_request;
+  klass->toggle_size_allocate = gtk_real_menu_item_toggle_size_allocate;
 
-  klass->toggle_size = 0;
   klass->hide_on_activate = TRUE;
 }
 
@@ -312,7 +339,26 @@ gtk_menu_item_activate (GtkMenuItem *men
   
   gtk_signal_emit (GTK_OBJECT (menu_item), menu_item_signals[ACTIVATE]);
 }
+void
+gtk_menu_item_toggle_size_request (GtkMenuItem *menu_item,
+				   guint16     *requisition)
+{
+  g_return_if_fail (menu_item != NULL);
+  g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
+
+  gtk_signal_emit (GTK_OBJECT (menu_item), menu_item_signals[TOGGLE_SIZE_REQUEST], requisition);
+}
 
+void
+gtk_menu_item_toggle_size_allocate (GtkMenuItem *menu_item,
+				    guint16      allocation)
+{
+  g_return_if_fail (menu_item != NULL);
+  g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
+
+  gtk_signal_emit (GTK_OBJECT (menu_item), menu_item_signals[TOGGLE_SIZE_ALLOCATE], allocation);
+}
+
 static void
 gtk_menu_item_accel_width_foreach (GtkWidget *widget,
 				   gpointer data)
@@ -631,7 +677,26 @@ gtk_real_menu_item_activate_item (GtkMen
 	}
     }
 }
+static void
+gtk_real_menu_item_toggle_size_request (GtkMenuItem *menu_item,
+					guint16     *requisition)
+{
+  g_return_if_fail (menu_item != NULL);
+  g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
 
+  *requisition = 0;
+}
+
+static void
+gtk_real_menu_item_toggle_size_allocate (GtkMenuItem *menu_item,
+					 guint16      allocation)
+{
+  g_return_if_fail (menu_item != NULL);
+  g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
+
+  menu_item->toggle_size = allocation;
+}
+
 static gint
 gtk_menu_item_select_timeout (gpointer data)
 {
@@ -654,13 +719,18 @@ gtk_menu_item_popup_submenu (gpointer da
 
   if (GTK_WIDGET_IS_SENSITIVE (menu_item->submenu))
     {
+      guint32 etime;
+      GdkEvent *event = gtk_get_current_event ();
+
+      etime = event ? gdk_event_get_time (event) : GDK_CURRENT_TIME;
+      
       gtk_menu_popup (GTK_MENU (menu_item->submenu),
 		      GTK_WIDGET (menu_item)->parent,
 		      GTK_WIDGET (menu_item),
 		      gtk_menu_item_position_menu,
 		      menu_item,
 		      GTK_MENU_SHELL (GTK_WIDGET (menu_item)->parent)->button,
-		      0);
+		      etime);
     }
 }
 
@@ -702,9 +772,10 @@ gtk_menu_item_position_menu (GtkMenu  *m
 	ty += GTK_WIDGET (menu_item)->allocation.height;
       else if ((ty - theight) >= 0)
 	ty -= theight;
-      else
+      else if (screen_height - (ty + GTK_WIDGET (menu_item)->allocation.height) > ty)
 	ty += GTK_WIDGET (menu_item)->allocation.height;
-
+      else
+	ty -= theight;
       break;
 
     case GTK_LEFT_RIGHT:
@@ -738,14 +809,16 @@ gtk_menu_item_position_menu (GtkMenu  *m
 
       ty += GTK_WIDGET (menu_item)->allocation.height / 4;
 
+      /* If the height of the menu doesn't fit we move it upward. */
+      ty = CLAMP (ty, 0, MAX (0, screen_height - theight));
       break;
     }
 
-  /* If we have negative, tx, ty here it is because we can't get
-   * the menu all the way on screen. Favor the upper-left portion.
+  /* If we have negative, tx, here it is because we can't get
+   * the menu all the way on screen. Favor the left portion.
    */
   *x = CLAMP (tx, 0, MAX (0, screen_width - twidth));
-  *y = CLAMP (ty, 0, MAX (0, screen_height - theight));
+  *y = ty;
 }
 
 void
Index: gtk/gtkmenuitem.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmenuitem.h,v
retrieving revision 1.12
diff -u -p -r1.12 gtkmenuitem.h
--- gtk/gtkmenuitem.h	2000/08/30 00:33:37	1.12
+++ gtk/gtkmenuitem.h	2000/10/23 16:21:30
@@ -70,7 +70,6 @@ struct _GtkMenuItemClass
 {
   GtkItemClass parent_class;
   
-  guint toggle_size;
   /* If the following flag is true, then we should always hide
    * the menu when the MenuItem is activated. Otherwise, the 
    * it is up to the caller. For instance, when navigating
@@ -79,26 +78,35 @@ struct _GtkMenuItemClass
    */
   guint hide_on_activate : 1;
   
-  void (* activate)      (GtkMenuItem *menu_item);
-  void (* activate_item) (GtkMenuItem *menu_item);
+  void (* activate)             (GtkMenuItem *menu_item);
+  void (* activate_item)        (GtkMenuItem *menu_item);
+  void (* toggle_size_request)  (GtkMenuItem *menu_item,
+				 guint16     *requisition);
+  void (* toggle_size_allocate) (GtkMenuItem *menu_item,
+				 guint16      allocation);
 };
 
 
-GtkType	   gtk_menu_item_get_type	  (void) G_GNUC_CONST;
-GtkWidget* gtk_menu_item_new		  (void);
-GtkWidget* gtk_menu_item_new_with_label	  (const gchar	       *label);
-void	   gtk_menu_item_set_submenu	  (GtkMenuItem	       *menu_item,
-					   GtkWidget	       *submenu);
-void	   gtk_menu_item_remove_submenu	  (GtkMenuItem	       *menu_item);
-void	   gtk_menu_item_set_placement	  (GtkMenuItem	       *menu_item,
-					   GtkSubmenuPlacement	placement);
-void	   gtk_menu_item_configure	  (GtkMenuItem	       *menu_item,
-					   gint			show_toggle_indicator,
-					   gint			show_submenu_indicator);
-void	   gtk_menu_item_select		  (GtkMenuItem	       *menu_item);
-void	   gtk_menu_item_deselect	  (GtkMenuItem	       *menu_item);
-void	   gtk_menu_item_activate	  (GtkMenuItem	       *menu_item);
-void	   gtk_menu_item_right_justify	  (GtkMenuItem	       *menu_item);
+GtkType	   gtk_menu_item_get_type	      (void) G_GNUC_CONST;
+GtkWidget* gtk_menu_item_new                  (void);
+GtkWidget* gtk_menu_item_new_with_label       (const gchar         *label);
+void       gtk_menu_item_set_submenu          (GtkMenuItem         *menu_item,
+					       GtkWidget           *submenu);
+void       gtk_menu_item_remove_submenu       (GtkMenuItem         *menu_item);
+void       gtk_menu_item_set_placement        (GtkMenuItem         *menu_item,
+					       GtkSubmenuPlacement  placement);
+void       gtk_menu_item_configure            (GtkMenuItem         *menu_item,
+					       gint                 show_toggle_indicator,
+					       gint                 show_submenu_indicator);
+void       gtk_menu_item_select               (GtkMenuItem         *menu_item);
+void       gtk_menu_item_deselect             (GtkMenuItem         *menu_item);
+void       gtk_menu_item_activate             (GtkMenuItem         *menu_item);
+void       gtk_menu_item_toggle_size_request  (GtkMenuItem         *menu_item,
+					       guint16             *requisition);
+void       gtk_menu_item_toggle_size_allocate (GtkMenuItem         *menu_item,
+					       guint16              allocation);
+void       gtk_menu_item_right_justify        (GtkMenuItem         *menu_item);
+
 
 
 #ifdef __cplusplus
Index: gtk/gtkmenushell.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmenushell.c,v
retrieving revision 1.33
diff -u -p -r1.33 gtkmenushell.c
--- gtk/gtkmenushell.c	2000/07/26 11:32:45	1.33
+++ gtk/gtkmenushell.c	2000/10/23 16:21:30
@@ -129,12 +129,17 @@ static void gtk_menu_shell_forall       
 					      gboolean		 include_internals,
 					      GtkCallback        callback,
 					      gpointer           callback_data);
+static void gtk_menu_shell_real_insert       (GtkMenuShell *menu_shell,
+					      GtkWidget    *child,
+					      gint          position);
 static void gtk_real_menu_shell_deactivate   (GtkMenuShell      *menu_shell);
 static gint gtk_menu_shell_is_item           (GtkMenuShell      *menu_shell,
 					      GtkWidget         *child);
 static GtkWidget *gtk_menu_shell_get_item    (GtkMenuShell      *menu_shell,
 					      GdkEvent          *event);
 static GtkType    gtk_menu_shell_child_type  (GtkContainer      *container);
+static void gtk_menu_shell_real_select_item  (GtkMenuShell      *menu_shell,
+					      GtkWidget         *menu_item);
 static void gtk_menu_shell_select_submenu_first (GtkMenuShell   *menu_shell); 
 
 static void gtk_real_menu_shell_move_current (GtkMenuShell      *menu_shell,
@@ -246,6 +251,8 @@ gtk_menu_shell_class_init (GtkMenuShellC
   klass->move_current = gtk_real_menu_shell_move_current;
   klass->activate_current = gtk_real_menu_shell_activate_current;
   klass->cancel = gtk_real_menu_shell_cancel;
+  klass->select_item = gtk_menu_shell_real_select_item;
+  klass->insert = gtk_menu_shell_real_insert;
 
   binding_set = gtk_binding_set_by_class (klass);
   gtk_binding_entry_add_signal (binding_set,
@@ -303,6 +310,24 @@ gtk_menu_shell_insert (GtkMenuShell *men
 		       GtkWidget    *child,
 		       gint          position)
 {
+  GtkMenuShellClass *class;
+
+  g_return_if_fail (menu_shell != NULL);
+  g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
+  g_return_if_fail (child != NULL);
+  g_return_if_fail (GTK_IS_MENU_ITEM (child));
+
+  class = GTK_MENU_SHELL_GET_CLASS (menu_shell);
+
+  if (class->insert)
+    class->insert (menu_shell, child, position);
+}
+
+static void
+gtk_menu_shell_real_insert (GtkMenuShell *menu_shell,
+			    GtkWidget    *child,
+			    gint          position)
+{
   g_return_if_fail (menu_shell != NULL);
   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
   g_return_if_fail (child != NULL);
@@ -573,7 +598,7 @@ gtk_menu_shell_enter_notify (GtkWidget  
 
       if (!menu_item || !GTK_WIDGET_IS_SENSITIVE (menu_item))
 	return TRUE;
-
+      
       if ((menu_item->parent == widget) &&
 	  (menu_shell->active_menu_item != menu_item) &&
 	  GTK_IS_MENU_ITEM (menu_item))
@@ -775,6 +800,24 @@ gtk_menu_shell_get_item (GtkMenuShell *m
 void
 gtk_menu_shell_select_item (GtkMenuShell *menu_shell,
 			    GtkWidget    *menu_item)
+{
+  GtkMenuShellClass *class;
+
+  g_return_if_fail (menu_shell != NULL);
+  g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
+  g_return_if_fail (menu_item != NULL);
+  g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
+
+  class = GTK_MENU_SHELL_GET_CLASS (menu_shell);
+
+  if (class->select_item)
+    class->select_item (menu_shell, menu_item);
+}
+
+
+static void
+gtk_menu_shell_real_select_item (GtkMenuShell *menu_shell,
+				 GtkWidget    *menu_item)
 {
   g_return_if_fail (menu_shell != NULL);
   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
Index: gtk/gtkmenushell.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmenushell.h,v
retrieving revision 1.10
diff -u -p -r1.10 gtkmenushell.h
--- gtk/gtkmenushell.h	2000/08/30 00:33:37	1.10
+++ gtk/gtkmenushell.h	2000/10/23 16:21:30
@@ -81,6 +81,11 @@ struct _GtkMenuShellClass
   void (*activate_current) (GtkMenuShell *menu_shell,
 			    gboolean      force_hide);
   void (*cancel)           (GtkMenuShell *menu_shell);
+  void (*select_item)      (GtkMenuShell *menu_shell,
+			    GtkWidget    *menu_item);
+  void (*insert)           (GtkMenuShell *menu_shell,
+			    GtkWidget    *child,
+			    gint          position);
 };
 
 
Index: gtk/gtkoptionmenu.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkoptionmenu.c,v
retrieving revision 1.32
diff -u -p -r1.32 gtkoptionmenu.c
--- gtk/gtkoptionmenu.c	2000/07/26 11:32:45	1.32
+++ gtk/gtkoptionmenu.c	2000/10/23 16:21:30
@@ -657,13 +657,10 @@ gtk_option_menu_position (GtkMenu  *menu
   GtkWidget *child;
   GtkRequisition requisition;
   GList *children;
-  gint shift_menu;
   gint screen_width;
-  gint screen_height;
   gint menu_xpos;
   gint menu_ypos;
-  gint width;
-  gint height;
+  gint menu_width;
 
   g_return_if_fail (user_data != NULL);
   g_return_if_fail (GTK_IS_OPTION_MENU (user_data));
@@ -671,11 +668,9 @@ gtk_option_menu_position (GtkMenu  *menu
   option_menu = GTK_OPTION_MENU (user_data);
 
   gtk_widget_get_child_requisition (GTK_WIDGET (menu), &requisition);
-  width = requisition.width;
-  height = requisition.height;
+  menu_width = requisition.width;
 
   active = gtk_menu_get_active (GTK_MENU (option_menu->menu));
-  children = GTK_MENU_SHELL (option_menu->menu)->children;
   gdk_window_get_origin (GTK_WIDGET (option_menu)->window, &menu_xpos, &menu_ypos);
 
   menu_ypos += GTK_WIDGET (option_menu)->allocation.height / 2 - 2;
@@ -686,6 +681,7 @@ gtk_option_menu_position (GtkMenu  *menu
       menu_ypos -= requisition.height / 2;
     }
 
+  children = GTK_MENU_SHELL (option_menu->menu)->children;
   while (children)
     {
       child = children->data;
@@ -703,32 +699,11 @@ gtk_option_menu_position (GtkMenu  *menu
     }
 
   screen_width = gdk_screen_width ();
-  screen_height = gdk_screen_height ();
-
-  shift_menu = FALSE;
-  if (menu_ypos < 0)
-    {
-      menu_ypos = 0;
-      shift_menu = TRUE;
-    }
-  else if ((menu_ypos + height) > screen_height)
-    {
-      menu_ypos -= ((menu_ypos + height) - screen_height);
-      shift_menu = TRUE;
-    }
-
-  if (shift_menu)
-    {
-      if ((menu_xpos + GTK_WIDGET (option_menu)->allocation.width + width) <= screen_width)
-	menu_xpos += GTK_WIDGET (option_menu)->allocation.width;
-      else
-	menu_xpos -= width;
-    }
-
+  
   if (menu_xpos < 0)
     menu_xpos = 0;
-  else if ((menu_xpos + width) > screen_width)
-    menu_xpos -= ((menu_xpos + width) - screen_width);
+  else if ((menu_xpos + menu_width) > screen_width)
+    menu_xpos -= ((menu_xpos + menu_width) - screen_width);
 
   *x = menu_xpos;
   *y = menu_ypos;





[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]