Subject: bug#22000: Patch addressing the menu-bar frame-resize interaction

From: Vivek Dasmohapatra <[email-masked]>
Date: Thu, 18 Oct 2018 02:02:27 +0100 (BST)

New patch series - I think this has all the features and functionality
discussed so far.
From c9e24d6bf4d3d5baae9bb4dade97cfcb8a5ecafc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vivek=20Das=C2=A0Mohapatra?= <[email-masked]>
Date: Sun, 15 Jul 2018 18:59:59 +0100
Subject: [PATCH 1/3] GTK3 menu bars force frame resizing (Bug#22000)

Menu bars force the frame they are in to resize when the menu bar
width exceeds the frame width, both at the point the menu bar grows
past the frame width and whenever the gtk idle resize callback is
triggered.

The effect is that the user's frame width is effectively ignored, and
emacs will semi-predictably resize itself to accommodate the menu bar.

This effect can be suppressed by wrapping the menu bar in a scrollable
window.

This behaviour is controlled by the `menu-bar-scrollbar' parameter
which may have a value of 'automatic, 'always, 'forced-resize or
anything else (equivalent to nil).

Limitations in earlier versions of GTK mean that there are
some version-dependent differences in behaviour.

GTK 3.16 and later:

The menu bar is always in a scrolled window . In the default mode
the menu bar is truncated when it tries to grow wider than the frame.
CSS is used to strip away the excess space this introduces.

In 'always or 'automatic mode, the CSS is relaxed slightly to work
around a GTK focus glitch, but otherwise the widget setup is identical.
The menubar will have a scrollbar either always, or when it tries to
grow too wide.

In 'forced-resize mode the frame-size-jitter behaviour described in
bug #22000 is preserved.

Before GTK 3.16:

When in 'always or 'automatic mode, the menu bar will be in a scrolled
window. The extra space cannot be properly ameliorated with CSS
styling as this does not seem to work well.

In the default mode, the scrolled window is not present - the menu bar
is dynamically re-parented between the scrolled window (which is
created on demand) and the emacs pane (vbox widget) when the menu bar
scrolling mode is changed.

In these versions of GTK the menu-bar truncation behaviour is not
easily achievable, so the default mode is identical to 'forced-resize
mode.
---
 src/frame.c   |   7 ++
 src/gtkutil.c | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 src/gtkutil.h |   3 +
 src/xfns.c    |  12 +++-
 src/xterm.h   |   4 ++
 5 files changed, 220 insertions(+), 9 deletions(-)

diff --git a/src/frame.c b/src/frame.c
index 0a6ca26f5d..e4e430de8f 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -3550,6 +3550,7 @@ static const struct frame_parm_table frame_parms[] =
   {"right-divider-width",	SYMBOL_INDEX (Qright_divider_width)},
   {"bottom-divider-width",	SYMBOL_INDEX (Qbottom_divider_width)},
   {"menu-bar-lines",		SYMBOL_INDEX (Qmenu_bar_lines)},
+  {"menu-bar-scrollbar",	SYMBOL_INDEX (Qmenu_bar_scrollbar)},
   {"mouse-color",		SYMBOL_INDEX (Qmouse_color)},
   {"name",			SYMBOL_INDEX (Qname)},
   {"scroll-bar-width",		SYMBOL_INDEX (Qscroll_bar_width)},
@@ -5660,6 +5661,11 @@ syms_of_frame (void)
   DEFSYM (Qx_resource_name, "x-resource-name");
   DEFSYM (Qx_frame_parameter, "x-frame-parameter");
 
+  /* Values for menu-bar-scrollbar frame parameter (GTK only) */
+  DEFSYM (Qautomatic, "automatic");
+  DEFSYM (Qalways, "always");
+  DEFSYM (Qforced_resize, "forced-resize");
+
   DEFSYM (Qworkarea, "workarea");
   DEFSYM (Qmm_size, "mm-size");
   DEFSYM (Qframes, "frames");
@@ -5675,6 +5681,7 @@ syms_of_frame (void)
   DEFSYM (Qtitle_bar_size, "title-bar-size");
   DEFSYM (Qmenu_bar_external, "menu-bar-external");
   DEFSYM (Qmenu_bar_size, "menu-bar-size");
+  DEFSYM (Qmenu_bar_scrollbar, "menu-bar-scrollbar");
   DEFSYM (Qtool_bar_external, "tool-bar-external");
   DEFSYM (Qtool_bar_size, "tool-bar-size");
   /* The following are used for frame_size_history.  */
diff --git a/src/gtkutil.c b/src/gtkutil.c
index 83b306a730..c4637e0cba 100644
--- a/src/gtkutil.c
+++ b/src/gtkutil.c
@@ -1136,6 +1136,10 @@ delete_cb (GtkWidget *widget,
   return TRUE;
 }
 
+#define MENUBAR_STYLESHEET \
+  ".mbtrunc  * { border-width: 1px; margin-top: -2px; margin-bottom: -2px; }\n" \
+  ".mbscroll * { border-width: 1px; margin-top: -1px; margin-bottom: 0px; }\n"
+
 /* Create and set up the GTK widgets for frame F.
    Return true if creation succeeded.  */
 
@@ -1147,6 +1151,9 @@ xg_create_frame_widgets (struct frame *f)
   GtkWidget *wfixed;
 #ifndef HAVE_GTK3
   GtkRcStyle *style;
+#else
+  GtkCssProvider *css;
+  GdkScreen *screen;
 #endif
   char *title = 0;
 
@@ -1213,6 +1220,17 @@ xg_create_frame_widgets (struct frame *f)
       store_frame_param (f, Qundecorated, Qt);
     }
 
+  /* Add a CSS provider for the frame which will be used for dynamic styling
+     when we change widget behaviour (eg menubar scrollbars).  */
+  css = gtk_css_provider_new ();
+  screen = gtk_widget_get_screen (wtop);
+  /* This should probably be moved ino the filesystem.  */
+  gtk_css_provider_load_from_data (css, MENUBAR_STYLESHEET, -1, NULL);
+  gtk_style_context_add_provider_for_screen (screen,
+                                             GTK_STYLE_PROVIDER (css),
+                                             GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+  g_object_unref (css);
+
   FRAME_GTK_OUTER_WIDGET (f) = wtop;
   FRAME_GTK_WIDGET (f) = wfixed;
   f->output_data.x->vbox_widget = wvbox;
@@ -3434,7 +3452,11 @@ menubar_map_cb (GtkWidget *w, gpointer user_data)
 {
   GtkRequisition req;
   struct frame *f = user_data;
-  gtk_widget_get_preferred_size (w, NULL, &req);
+  struct x_output *x = f->output_data.x;
+
+  /* Use the menubar viewport for size if there is one: */
+  gtk_widget_get_preferred_size (x->menubar_visible_widget ?: w, NULL, &req);
+
   if (FRAME_MENUBAR_HEIGHT (f) != req.height)
     {
       FRAME_MENUBAR_HEIGHT (f) = req.height;
@@ -3442,6 +3464,150 @@ menubar_map_cb (GtkWidget *w, gpointer user_data)
     }
 }
 
+/* Remove a gtk widget from any parent it may belong to.
+   Ensures that this does not change target's ref count.  */
+static void
+orphan_widget (GtkWidget *w)
+{
+  GtkWidget *parent = gtk_widget_get_parent (w);
+
+  if (parent && GTK_IS_CONTAINER (parent))
+    {
+      g_object_ref (w);
+      gtk_container_remove (GTK_CONTAINER (parent), w);
+
+#if !GTK_CHECK_VERSION(3, 8, 0)
+      /* gtk 3.8 and earlier: viewport must be managed manually
+         but we don't need to save it, unlike the scrolled window
+         or the menubar.  */
+      if (GTK_IS_VIEWPORT (parent))
+        {
+          GtkWidget *grandparent = gtk_widget_get_parent (parent);
+
+          if (parent && GTK_IS_CONTAINER (grandparent))
+            gtk_container_remove (GTK_CONTAINER (grandparent), parent);
+        }
+#endif
+      return;
+    }
+}
+
+/* As per orphan_widget but we also want to get rid of it and clean up.  */
+static void
+discard_widget (GtkWidget *w)
+{
+  orphan_widget  (w);
+  if (g_object_is_floating (w))
+    g_object_ref_sink (w);
+  g_object_unref (w);
+}
+
+/* Sets up the menubar style for scrolling/non-scrolling modes.
+   Reparents the menubar directly into the vbox for non-scrolling
+   mode and adds a scrolledwindow+viewport for scrolling modes.  */
+static void
+menubar_scrollbox (struct frame *f, int scroll)
+{
+  struct x_output *x = f->output_data.x;
+
+  if (scroll)
+    {
+      if (x->menubar_visible_widget == x->menubar_viewport)
+        return;
+
+      x->menubar_visible_widget = x->menubar_viewport;
+      orphan_widget (x->menubar_widget);
+
+#if GTK_CHECK_VERSION (3, 8, 0)
+      gtk_container_add (GTK_CONTAINER (x->menubar_viewport), x->menubar_widget);
+#else
+      gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (x->menubar_viewport), x->menubar_widget);
+#endif
+    }
+  else
+    {
+      if (x->menubar_visible_widget == x->menubar_widget)
+        return;
+
+      x->menubar_visible_widget = x->menubar_widget;
+      orphan_widget (x->menubar_widget);
+      orphan_widget (x->menubar_viewport);
+    }
+
+  gtk_box_pack_start (GTK_BOX (x->vbox_widget),
+                      GTK_WIDGET (x->menubar_visible_widget), FALSE, FALSE, 0);
+  gtk_box_reorder_child (GTK_BOX (x->vbox_widget),
+                         GTK_WIDGET (x->menubar_visible_widget), 0);
+  gtk_widget_show_all (x->menubar_visible_widget);
+}
+
+#if GTK_CHECK_VERSION (3, 16, 0)
+#define MENUBAR_SCROLLBAR_DEFAULT_POLICY GTK_POLICY_EXTERNAL
+#else
+#define MENUBAR_SCROLLBAR_DEFAULT_POLICY GTK_POLICY_NEVER
+#endif
+
+/* Apply the scrollbar mode and related style settings.
+   This also inserts or removes the intervening viewport as necessary
+   and maps any widgets that need mapping. Note that after gtk 3.16
+   we don't need (or want) to remove the scrolledwindow or viewport,
+   it suffices to change their style.  */
+void
+xg_update_frame_menubar_scrollbar_mode (struct frame *f, Lisp_Object mode)
+{
+  GtkStyleContext *style;
+  struct x_output *x = f->output_data.x;
+  GtkScrolledWindow *sw;
+  GtkPolicyType scroll_policy = MENUBAR_SCROLLBAR_DEFAULT_POLICY;
+
+  if (!x->menubar_viewport)
+    return;
+
+  if (EQ (mode, Qautomatic))
+    scroll_policy = GTK_POLICY_AUTOMATIC;
+  else if (EQ (mode, Qalways))
+    scroll_policy = GTK_POLICY_ALWAYS;
+  else if (EQ (mode, Qforced_resize))
+    scroll_policy = GTK_POLICY_NEVER;
+
+  sw = GTK_SCROLLED_WINDOW (x->menubar_viewport);
+  style = gtk_widget_get_style_context (x->menubar_viewport);
+
+#if GTK_CHECK_VERSION(3, 16, 0)
+  /* Always want the scrollable container for capable-enough GTK versions */
+  menubar_scrollbox (f, 1);
+
+  switch (scroll_policy)
+    {
+    case GTK_POLICY_AUTOMATIC:
+    case GTK_POLICY_ALWAYS:
+      gtk_style_context_add_class (style, "mbscroll");
+      gtk_style_context_remove_class (style, "mbtrunc");
+      break;
+    default:
+      gtk_style_context_remove_class (style, "mbscroll");
+      gtk_style_context_add_class (style, "mbtrunc");
+    }
+#else
+  /* In older GTK versions we need to swap out the scrollable container
+     instead since we can't get truncating behaviour and CSS styling is
+     not well supported. */
+  switch (scroll_policy)
+    {
+    case GTK_POLICY_AUTOMATIC:
+    case GTK_POLICY_ALWAYS:
+      menubar_scrollbox (f, 1);
+      gtk_style_context_remove_class (style, "mbtrunc");
+      break;
+    default:
+      menubar_scrollbox (f, 0);
+      gtk_style_context_add_class (style, "mbtrunc");
+    }
+#endif
+
+  gtk_scrolled_window_set_policy (sw, scroll_policy, GTK_POLICY_AUTOMATIC);
+}
+
 /* Recompute all the widgets of frame F, when the menu bar has been
    changed.  */
 
@@ -3450,6 +3616,7 @@ xg_update_frame_menubar (struct frame *f)
 {
   struct x_output *x = f->output_data.x;
   GtkRequisition req;
+  Lisp_Object menuscroll;
 
   if (!x->menubar_widget || gtk_widget_get_mapped (x->menubar_widget))
     return;
@@ -3459,13 +3626,29 @@ xg_update_frame_menubar (struct frame *f)
 
   block_input ();
 
-  gtk_box_pack_start (GTK_BOX (x->vbox_widget), x->menubar_widget,
-                      FALSE, FALSE, 0);
-  gtk_box_reorder_child (GTK_BOX (x->vbox_widget), x->menubar_widget, 0);
+  menuscroll = get_frame_param (f, Qmenu_bar_scrollbar);
+
+  /* Put the menu bar inside a scrolled window so that adding items
+     to the menu bar (such as when entering dired mode or activating
+     a minor more) does not trigger a frame resize:*/
+  x->menubar_viewport = gtk_scrolled_window_new(NULL, NULL);
+
+  /* Leave the keyboard focus where it is when clicking the scrollwindow: */
+#if GTK_CHECK_VERSION (3, 20, 0)
+  gtk_widget_set_focus_on_click (x->menubar_viewport, FALSE);
+#endif
+
+#if GTK_CHECK_VERSION (3, 16, 0)
+  /* If we don't set this then the scrollable keeps focus when the user
+     interacts with the scrollbar, at least until the menubar is clicked.
+     Overlay scrolling is more compact but until the focus problem is fixed
+     it's not livable with. */
+  gtk_scrolled_window_set_overlay_scrolling (GTK_SCROLLED_WINDOW (x->menubar_viewport), FALSE);
+#endif
 
   g_signal_connect (x->menubar_widget, "map", G_CALLBACK (menubar_map_cb), f);
-  gtk_widget_show_all (x->menubar_widget);
-  gtk_widget_get_preferred_size (x->menubar_widget, NULL, &req);
+  xg_update_frame_menubar_scrollbar_mode (f, menuscroll);
+  gtk_widget_get_preferred_size (x->menubar_visible_widget, NULL, &req);
 
   if (FRAME_MENUBAR_HEIGHT (f) != req.height)
     {
@@ -3487,10 +3670,14 @@ free_frame_menubar (struct frame *f)
     {
       block_input ();
 
-      gtk_container_remove (GTK_CONTAINER (x->vbox_widget), x->menubar_widget);
+      discard_widget (x->menubar_widget);
+      discard_widget (x->menubar_viewport);
+
        /* The menubar and its children shall be deleted when removed from
           the container.  */
-      x->menubar_widget = 0;
+      x->menubar_visible_widget = NULL;
+      x->menubar_viewport = NULL;
+      x->menubar_widget = NULL;
       FRAME_MENUBAR_HEIGHT (f) = 0;
       adjust_frame_size (f, -1, -1, 2, 0, Qmenu_bar_lines);
       unblock_input ();
diff --git a/src/gtkutil.h b/src/gtkutil.h
index 7dcd549f5c..96220f27c8 100644
--- a/src/gtkutil.h
+++ b/src/gtkutil.h
@@ -103,6 +103,9 @@ extern void xg_modify_menubar_widgets (GtkWidget *menubar,
                                        GCallback deactivate_cb,
                                        GCallback highlight_cb);
 
+extern void xg_update_frame_menubar_scrollbar_mode (struct frame *f,
+                                                    Lisp_Object mode);
+
 extern void xg_update_frame_menubar (struct frame *f);
 
 extern bool xg_event_is_for_menubar (struct frame *, const XEvent *);
diff --git a/src/xfns.c b/src/xfns.c
index 3da853ede8..9503ae1e1d 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -1601,6 +1601,13 @@ x_set_menu_bar_lines (struct frame *f, Lisp_Object value, Lisp_Object oldval)
   adjust_frame_glyphs (f);
 }
 
+static void
+x_set_menu_bar_scrollbar (struct frame *f, Lisp_Object value, Lisp_Object oldval)
+{
+#ifdef USE_GTK /* menubar resize/scrolling only happens under GTK.  */
+  xg_update_frame_menubar_scrollbar_mode (f, value);
+#endif
+}
 
 /* Set the number of lines used for the tool bar of frame F to VALUE.
    VALUE not an integer, or < 0 means set the lines to zero.  OLDVAL
@@ -3888,7 +3895,9 @@ This function is an internal primitive--use `make-frame' instead.  */)
 		       NILP (Vtool_bar_mode)
 		       ? make_number (0) : make_number (1),
 		       NULL, NULL, RES_TYPE_NUMBER);
-
+  /* How scrolling is handled for oversized (too wide) menu bars.  */
+  x_default_parameter (f, parms, Qmenu_bar_scrollbar, Qnil,
+                       NULL, NULL, RES_TYPE_SYMBOL);
   x_default_parameter (f, parms, Qbuffer_predicate, Qnil,
 		       "bufferPredicate", "BufferPredicate",
 		       RES_TYPE_SYMBOL);
@@ -7536,6 +7545,7 @@ frame_parm_handler x_frame_parm_handlers[] =
   x_set_right_divider_width,
   x_set_bottom_divider_width,
   x_set_menu_bar_lines,
+  x_set_menu_bar_scrollbar,
   x_set_mouse_color,
   x_explicitly_set_name,
   x_set_scroll_bar_width,
diff --git a/src/xterm.h b/src/xterm.h
index f73dd0e25a..ac5f7f08da 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -581,7 +581,11 @@ struct x_output
   /* The widget used for laying out widgets horizontally.  */
   GtkWidget *hbox_widget;
   /* The menubar in this frame.  */
+  GtkWidget *menubar_viewport;
   GtkWidget *menubar_widget;
+  /* If the viewport is in usem this will be the viewport, otherwise it
+     will be the menubar_widget. Used to get height calculations right. */
+  GtkWidget *menubar_visible_widget;
   /* The tool bar in this frame  */
   GtkWidget *toolbar_widget;
   /* True if tool bar is packed into the hbox widget (i.e. vertical).  */
-- 
2.11.0

From 489a38cceda02e62dc50367347930713f4454f95 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vivek=20Das=C2=A0Mohapatra?= <[email-masked]>
Date: Thu, 11 Oct 2018 13:48:47 +0100
Subject: [PATCH 2/3] Document the new menu-bar-scrollbar frame parameter

---
 doc/lispref/frames.texi | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi
index 2f9bb39886..601749d97e 100644
--- a/doc/lispref/frames.texi
+++ b/doc/lispref/frames.texi
@@ -601,6 +601,10 @@ Frame Layout
 frame unchanged, so the native height of the frame (see below) will
 change instead.
 
+If the menu bar is drawn by GTK then its behavior when it would grow
+wider than the root frame is controlled by the @code{menu-bar-scrollbar}
+parameter (@pxref{Layout Parameters}).
+
 @item Tool Bar
 @cindex internal tool bar
 @cindex external tool bar
@@ -1814,6 +1818,23 @@ Layout Parameters
 (@pxref{Frame Geometry}) allows to derive whether the menu bar actually
 occupies one or more lines.
 
+@vindex menu-bar-scrollbar@r{, a frame parameter}
+@item menu-bar-scrollbar
+The behavior of GTK menu bars when they would otherwise grow wider than
+the frame.  Valid values are:
+@itemize
+@item @code{always} - Scrollbar is present, menu bar scrolls when too wide.
+@item @code{automatic} - Scrollbar appears when menubar grows too wide.
+@item @code{forced-resize} - No scrollbar.  Growing menubar forces a frame resize.
+@item @code{nil} (or any other value)
+@itemize
+@item GTK >= 3.16 - No scrollbar.  Menu bar is truncated if it grows too wide.
+@item GTK < 3,16 - Same behavior as @code{forced-resize}.
+@end itemize
+@end itemize
+It is worth noting that for GTK before 3.16 the scrollbar adds a significant
+amount of vertical padding to the menubar: This appears to be unavoidable.
+
 @vindex tool-bar-lines@r{, a frame parameter}
 @item tool-bar-lines
 The number of lines to use for the tool bar (@pxref{Tool Bar}).  The
-- 
2.11.0

From d281f45206321ef7e41e73baa0edf67f0e7216be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vivek=20Das=C2=A0Mohapatra?= <[email-masked]>
Date: Thu, 18 Oct 2018 01:38:05 +0100
Subject: [PATCH 3/3] Hook up menu bar scrollbar functionality to customize &
 Options menu
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

menu-bar-scrollbar-mode custom variable updates default and
initial frame parameters.

menu-bar-scrollbar-mode command cycles the custom variable through
the available/supported values

Options ► Show Hide ► Menu Bar Scroll/Truncate menu connected to
the custom variable.
---
 lisp/menu-bar.el | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 137 insertions(+)

diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index e2ebd98119..e8b36851e2 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -673,6 +673,7 @@ menu-bar-options-save
 		   line-number-mode column-number-mode size-indication-mode
 		   cua-mode show-paren-mode transient-mark-mode
 		   blink-cursor-mode display-time-mode display-battery-mode
+                   menu-bar-scrollbar-mode
 		   ;; These are set by other functions that don't set
 		   ;; the customized state.  Having them here has the
 		   ;; side-effect that turning them off via X
@@ -1042,6 +1043,137 @@ menu-bar-showhide-tool-bar-menu-customize-enable-bottom
   (interactive)
   (menu-bar-set-tool-bar-position 'bottom))
 
+(defcustom menu-bar-scrollbar-mode nil
+      "Specify how GTK menu bars deal with the frame being too narrow to hold them.\n
+Valid values are:
+  `always'        - the menu bar always has a scrollbar
+  `automatic'     - a scrollbar is added when required
+  `forced-resize' - no scrollbar, the frame is forced to resize to accommodate
+                    the menu bar.
+   nil (or any other value) - the menu bar is truncated\n
+Note that prior to GTK 3.16 truncation is not possible and the default
+is equivalent to 'forced-resize.\n
+Do not set this variable directly - use the customize interface to make sure
+that `default-frame-alist', `initial-frame-alist' and all existing frames
+remain in sync."
+      :version "26.1"
+      :type '(choice (const always)
+		     (const automaic)
+		     (const forced-resize)
+		     (const nil))
+      :group 'frames
+      :initialize 'custom-initialize-default
+      :set (lambda (sym val)
+             (setq val (if (memq val '(automatic always forced-resize)) val nil))
+             (set-default sym val)
+             (modify-all-frames-parameters
+	      (list (cons 'menu-bar-scrollbar val)))))
+
+(defun menu-bar-can-be-truncated ()
+  (let (version)
+    (when (boundp 'gtk-version-string)
+      (setq version (mapcar 'string-to-number (split-string gtk-version-string "\\.")))
+      (or (and (eq (car version) 3) (>= (cadr version) 16))
+          (>= (car version) 4)))))
+
+(defun menu-bar-scrollbar-next-mode (mode)
+  "Return the next menu-bar-scrollbar frame parameter value after MODE.
+Takes into account the abilities of the available GTK version."
+  (if (menu-bar-can-be-truncated)
+      (progn
+        (if (not (memq mode '(always automatic forced-resize nil))) (setq mode nil))
+        (cadr (memq mode '(nil automatic always forced-resize nil))))
+    (if (not (memq mode '(always automatic nil))) (setq mode nil))
+    (cadr (memq mode '(nil automatic always nil)))))
+
+(defun menu-bar-scrollbar-mode (&optional mode)
+  "Cycle through scroll/truncate/resize modes for GTK menu bars.\n
+If the optional parameter MODE is specified then apply that instead.
+The new mode is stored in the variable `menu-bar-scrollbar-mode' via
+the custom interface (but not automatically saved).\n
+Returns the new MODE.\n
+NOTE: pass 'default if you want to set the mode explicitly to nil.\n
+See `menu-bar-scrollbar' in Info node `(elisp)Layout Parameters' for details."
+  (interactive)
+  (if mode
+      ;; explicit mode passed, map any non-standard value back to nil
+      (setq mode (car (memq mode '(automatic always forced-resize))))
+    ;; no explicit mode: pick the new value based on our fixed progression:
+    (setq mode (menu-bar-scrollbar-next-mode
+                (or menu-bar-scrollbar-mode 'default))))
+  ;; set, apply but do not save the new value:
+  (customize-set-variable 'menu-bar-scrollbar-mode mode)
+  mode)
+
+(defun menu-bar-showhide-menu-bar-scrollbar-mode-customize-forced-resize ()
+  "Resize the frame to accommodate the menu bar."
+  (interactive)
+  (customize-set-variable 'menu-bar-scrollbar-mode 'forced-resize))
+(defun menu-bar-showhide-menu-bar-scrollbar-mode-customize-always ()
+  "Add a permanent scrollbar to the menu bar."
+  (interactive)
+  (customize-set-variable 'menu-bar-scrollbar-mode 'always))
+(defun menu-bar-showhide-menu-bar-scrollbar-mode-customize-automatic ()
+  "Add a scrollbar to the menu bar when it tries to grow past the frame edge.."
+  (interactive)
+  (customize-set-variable 'menu-bar-scrollbar-mode 'automatic))
+(defun menu-bar-showhide-menu-bar-scrollbar-mode-customize-nil ()
+  "Truncate the menu bar to fit the frame."
+  (interactive)
+  (customize-set-variable 'menu-bar-scrollbar-mode 'default))
+
+(defvar menu-bar-showhide-menu-bar-scrollbar-mode-menu
+  (let ((menu (make-sparse-keymap "Menu Bar Scroll/Truncate")))
+
+    (bindings--define-key menu [showhide-menu-bar-scrollbar-mode-forced-resize]
+      '(menu-item "Resize Frame"
+                  menu-bar-showhide-menu-bar-scrollbar-mode-customize-forced-resize
+                  :help "Resize the frame to accommodate the menu bar"
+                  :visible (display-graphic-p)
+                  :button
+                  (:radio . (if (menu-bar-can-be-truncated)
+                                (eq (frame-parameter
+                                     (menu-bar-frame-for-menubar)
+                                     'menu-bar-scrollbar)
+                                    'forced-resize)
+                              (not (memq (frame-parameter
+                                          (menu-bar-frame-for-menubar)
+                                          'menu-bar-scrollbar)
+                                         '(automatic always)))))))
+
+    (bindings--define-key menu [showhide-menu-bar-scrollbar-mode-always]
+      '(menu-item "Always Scroll"
+                  menu-bar-showhide-menu-bar-scrollbar-mode-customize-always
+                  :help "Always add a scroll bar to the menu bar"
+                  :visible (display-graphic-p)
+                  :button
+                  (:radio . (eq (frame-parameter (menu-bar-frame-for-menubar)
+                                                 'menu-bar-scrollbar)
+                                'always))))
+
+    (bindings--define-key menu [showhide-menu-bar-scrollbar-mode-automatic]
+      '(menu-item "Automatic"
+                  menu-bar-showhide-menu-bar-scrollbar-mode-customize-automatic
+                  :help "Display a scroll bar only when the menu is too wide"
+                  :visible (display-graphic-p)
+                  :button
+                  (:radio . (eq (frame-parameter (menu-bar-frame-for-menubar)
+                                                 'menu-bar-scrollbar)
+                                'automatic))))
+
+    (bindings--define-key menu [showhide-menu-bar-scrollbar-mode-truncate]
+      '(menu-item "Truncate"
+                  menu-bar-showhide-menu-bar-scrollbar-mode-customize-nil
+                  :help "Truncate the menubar to fit inside the frame"
+                  :visible (and (display-graphic-p)
+                                (menu-bar-can-be-truncated))
+                  :button
+                  (:radio . (not (memq
+                                  (frame-parameter (menu-bar-frame-for-menubar)
+                                                   'menu-bar-scrollbar)
+                                       '(automatic always forced-resize))))))
+    menu))
+
 (when (featurep 'move-toolbar)
   (defvar menu-bar-showhide-tool-bar-menu
     (let ((menu (make-sparse-keymap "Tool Bar")))
@@ -1225,6 +1357,11 @@ menu-bar-showhide-menu
                   :visible (and (display-graphic-p) (fboundp 'x-show-tip))
                   :button (:toggle . tooltip-mode)))
 
+    (bindings--define-key menu [showhide-menu-bar-scrollbar-menu]
+      `(menu-item "Menu Bar Scroll/Truncate"
+                  ,menu-bar-showhide-menu-bar-scrollbar-mode-menu
+                  :visible (display-graphic-p)))
+
     (bindings--define-key menu [menu-bar-mode]
       '(menu-item "Menu Bar" toggle-menu-bar-mode-from-frame
                   :help "Turn menu bar on/off"
-- 
2.11.0