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

From: Vivek Dasmohapatra <[email-masked]>
Date: Mon, 16 Jul 2018 20:58:37 +0100 (BST)

I have #ifdef'd the calls you mentioned with GTK_CHECK_VERSION guards.

I also checked with one of the gnome/gtk devs and wrapping a
widget with strict ideas about its size is how you are supposed
to prevent its size from propagating: I suppose an alternative
would be some widget that doesn't resize or scroll at all,
but the basic approach would be the same.

I could not find a way to make overlay scrolbars behave, I think it's 
probably a bug in overlay scrollbars since classic scrollbars behave
just fine: I don't think that will be a problem as 3.16 is when overlay 
scrolling was introduced and that was also when the disabling function
was provided.

From cd3e84356de102eab24e57042285c28ac2b2c970 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] 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.
---
 src/gtkutil.c | 34 +++++++++++++++++++++++++++++-----
 src/xterm.h   |  1 +
 2 files changed, 30 insertions(+), 5 deletions(-)

diff --git a/src/gtkutil.c b/src/gtkutil.c
index 69325ff00a..d16274f6ab 100644
--- a/src/gtkutil.c
+++ b/src/gtkutil.c
@@ -3461,6 +3461,7 @@ xg_update_frame_menubar (struct frame *f)
 {
   struct x_output *x = f->output_data.x;
   GtkRequisition req;
+  GtkScrolledWindow *sw;
 
   if (!x->menubar_widget || gtk_widget_get_mapped (x->menubar_widget))
     return;
@@ -3470,12 +3471,33 @@ 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);
+  /* 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);
+  sw = GTK_SCROLLED_WINDOW (x->menubar_viewport);
+  gtk_scrolled_window_set_policy (sw, GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
+
+  /* Leave the keyboard focus where it is when clicking the scrollwindow: */
+#if GTK_CHECK_VERSION (3, 20, 0)
+  gtk_widget_set_focus_on_click (GTK_WIDGET(sw), 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 (sw, FALSE);
+#endif
+
+  gtk_container_add (GTK_CONTAINER (sw), x->menubar_widget);
+
+  gtk_box_pack_start (GTK_BOX (x->vbox_widget), GTK_WIDGET(sw), FALSE, FALSE, 0);
+  gtk_box_reorder_child (GTK_BOX (x->vbox_widget), GTK_WIDGET(sw), 0);
 
   g_signal_connect (x->menubar_widget, "map", G_CALLBACK (menubar_map_cb), f);
-  gtk_widget_show_all (x->menubar_widget);
+  gtk_widget_show_all (x->menubar_viewport);
   gtk_widget_get_preferred_size (x->menubar_widget, NULL, &req);
 
   if (FRAME_MENUBAR_HEIGHT (f) != req.height)
@@ -3498,9 +3520,11 @@ free_frame_menubar (struct frame *f)
     {
       block_input ();
 
-      gtk_container_remove (GTK_CONTAINER (x->vbox_widget), x->menubar_widget);
+      gtk_container_remove (GTK_CONTAINER (x->menubar_viewport), x->menubar_widget);
+      gtk_container_remove (GTK_CONTAINER (x->vbox_widget), x->menubar_viewport);
        /* The menubar and its children shall be deleted when removed from
           the container.  */
+      x->menubar_viewport = 0;
       x->menubar_widget = 0;
       FRAME_MENUBAR_HEIGHT (f) = 0;
       adjust_frame_size (f, -1, -1, 2, 0, Qmenu_bar_lines);
diff --git a/src/xterm.h b/src/xterm.h
index 1849a5c953..9bf3d9778b 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -583,6 +583,7 @@ 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;
   /* The tool bar in this frame  */
   GtkWidget *toolbar_widget;
-- 
2.11.0