patch 9.2.0657: GTK4: missing menu when right-clicking in tabline
Commit:
https://github.com/vim/vim/commit/2a149bf34fd6713983f1d2cc589d00c6757a6f30
Author: Foxe Chen <
chen...@gmail.com>
Date: Tue Jun 16 19:19:33 2026 +0000
patch 9.2.0657: GTK4: missing menu when right-clicking in tabline
Problem: GTK4: missing menu when right-clicking in tabline
Solution: Add tabline menu for GTK4 GUI (similar to GTK4 GUI)
(Foxe Chen).
closes: #20518
Signed-off-by: Foxe Chen <
chen...@gmail.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/src/gui_gtk4.c b/src/gui_gtk4.c
index 82c9248a7..90cd7aa3c 100644
--- a/src/gui_gtk4.c
+++ b/src/gui_gtk4.c
@@ -284,8 +284,11 @@ static void focus_out_event(GtkEventControllerFocus *controller, gpointer data);
static gboolean drop_cb(GtkDropTarget *target, const GValue *value, double x, double y, gpointer data);
#endif
#ifdef FEAT_GUI_TABLINE
+static void tabline_enter_cb(GtkEventController *controller, double x, double y, void *udata);
static void on_select_tab(GtkNotebook *notebook, gpointer *page, gint idx, gpointer data);
static void on_tab_reordered(GtkNotebook *notebook, gpointer *page, gint idx, gpointer data);
+static GMenu *create_tabline_popup_menu(GActionGroup **agroup_store);
+static void tabline_menu_press_event(GtkGestureClick *gesture, int n_press, double x, double y, GtkWidget *popover);
#endif
static void mainwin_destroy_cb(GObject *object, gpointer data);
static gboolean delete_event_cb(GtkWindow *window, gpointer data);
@@ -506,6 +509,47 @@ gui_mch_init(void)
G_CALLBACK(on_select_tab), NULL);
g_signal_connect(G_OBJECT(gui.tabline), "page-reordered",
G_CALLBACK(on_tab_reordered), NULL);
+
+ {
+ GtkEventController *mcontroller = gtk_event_controller_motion_new();
+
+ g_signal_connect(mcontroller, "enter",
+ G_CALLBACK(tabline_enter_cb), NULL);
+
+ // Make sure that tabline_enter_cb() is always called before
+ // tabpage_enter_cb().
+ gtk_event_controller_set_propagation_phase(
+ mcontroller, GTK_PHASE_CAPTURE);
+ gtk_widget_add_controller(gui.tabline, mcontroller);
+ }
+
+ // Create right click popup menu for tabline
+ {
+ GtkGesture *click;
+ GActionGroup *agroup;
+ GMenu *menu;
+ GtkWidget *popover;
+
+ click = gtk_gesture_click_new();
+ menu = create_tabline_popup_menu(&agroup);
+ popover = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu));
+ g_object_unref(menu);
+
+ gtk_widget_set_parent(popover, gui.tabline);
+ g_object_set_data(G_OBJECT(gui.tabline), "menu", popover);
+ gtk_widget_insert_action_group(gui.tabline, "tabline", agroup);
+ g_object_unref(agroup);
+
+ gtk_popover_set_has_arrow(GTK_POPOVER(popover), FALSE);
+ gtk_popover_set_position(GTK_POPOVER(popover), GTK_POS_BOTTOM);
+
+ // Listen for anny mouse button
+ gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click), 0);
+
+ g_signal_connect_object(click, "pressed",
+ G_CALLBACK(tabline_menu_press_event), popover, G_CONNECT_DEFAULT);
+ gtk_widget_add_controller(gui.tabline, GTK_EVENT_CONTROLLER(click));
+ }
#endif
// The form widget manages absolute positioning of scrollbars and the draw
@@ -743,7 +787,13 @@ gui_mch_open(void)
gui_mch_exit(int rc UNUSED)
{
if (gui.mainwin != NULL)
+ {
+#ifdef FEAT_GUI_TABLINE
+ // Must unparent popover menu for tabline or we will get warning
+ gtk_widget_unparent(g_object_get_data(G_OBJECT(gui.tabline), "menu"));
+#endif
gtk_window_destroy(GTK_WINDOW(gui.mainwin));
+ }
}
int
@@ -2737,6 +2787,28 @@ gui_mch_showing_tabline(void)
}
static int ignore_tabline_evt = FALSE;
+// Page hovered over in tab line, zero indicates none
+static int tabpage_hover = 0;
+
+ static void
+tabpage_enter_cb(
+ GtkEventController *controller UNUSED,
+ double x UNUSED,
+ double y UNUSED,
+ void *udata)
+{
+ tabpage_hover = GPOINTER_TO_INT(udata);
+}
+
+ static void
+tabline_enter_cb(
+ GtkEventController *controller UNUSED,
+ double x UNUSED,
+ double y UNUSED,
+ void *udata UNUSED)
+{
+ tabpage_hover = 0;
+}
void
gui_mch_update_tabline(void)
@@ -2757,6 +2829,10 @@ gui_mch_update_tabline(void)
for (tp = first_tabpage; tp != NULL; tp = tp->tp_next, ++nr)
{
+ GtkNotebookPage *page_widget;
+ GtkWidget *tab_widget;
+ GtkEventController *mcontroller;
+
if (tp == curtab)
curtabidx = nr;
@@ -2778,9 +2854,18 @@ gui_mch_update_tabline(void)
page, TRUE);
}
- event_box = gtk_notebook_get_tab_label(GTK_NOTEBOOK(gui.tabline), page);
- g_object_set_data(G_OBJECT(event_box), "tab_num",
+ // Attach motion event controller to detect which tab page is currently
+ // hovered over.
+ page_widget = gtk_notebook_get_page(GTK_NOTEBOOK(gui.tabline), page);
+ mcontroller = gtk_event_controller_motion_new();
+ g_signal_connect(mcontroller, "enter", G_CALLBACK(tabpage_enter_cb),
GINT_TO_POINTER(tab_num));
+ gtk_event_controller_set_propagation_phase(
+ mcontroller, GTK_PHASE_CAPTURE);
+ g_object_get(page_widget, "tab", &tab_widget, NULL);
+ gtk_widget_add_controller(tab_widget, mcontroller);
+
+ event_box = gtk_notebook_get_tab_label(GTK_NOTEBOOK(gui.tabline), page);
label = gtk_widget_get_first_child(event_box);
get_tabline_label(tp, FALSE);
labeltext = CONVERT_TO_UTF8(NameBuff);
@@ -2842,6 +2927,96 @@ on_tab_reordered(
else
tabpage_move(idx);
}
+
+/*
+ * Handle selecting an item in the tab line popup menu.
+ */
+ static void
+tabline_menu_action_cb(
+ GSimpleAction *action UNUSED,
+ GVariant *parameter UNUSED,
+ void *udata)
+{
+ send_tabline_menu_event(tabpage_hover, GPOINTER_TO_INT(udata));
+}
+
+ static void
+add_tabline_menu_item(
+ GMenu *gmenu,
+ GActionMap *amap,
+ const char *name,
+ const char *action,
+ int resp)
+{
+ GSimpleAction *act = g_simple_action_new(action, NULL);
+ char detailed[32];
+
+ g_signal_connect(act, "activate", G_CALLBACK(tabline_menu_action_cb),
+ GINT_TO_POINTER(resp));
+ g_action_map_add_action(amap, G_ACTION(act));
+ g_object_unref(act);
+
+ vim_snprintf(detailed, sizeof(detailed), "tabline.%s", action);
+ g_menu_append(gmenu, name, detailed);
+}
+
+/*
+ * Create a menu for the tab line.
+ */
+ static GMenu *
+create_tabline_popup_menu(GActionGroup **agroup_store)
+{
+ GMenu *gmenu = g_menu_new();
+ GSimpleActionGroup *agroup = g_simple_action_group_new();
+
+ add_tabline_menu_item(gmenu, G_ACTION_MAP(agroup),
+ _("Close Tab"), "close-tab", TABLINE_MENU_CLOSE);
+ add_tabline_menu_item(gmenu, G_ACTION_MAP(agroup),
+ _("New Tab"), "new-tab", TABLINE_MENU_NEW);
+ add_tabline_menu_item(gmenu, G_ACTION_MAP(agroup),
+ _("Open Tab..."), "open-tab", TABLINE_MENU_OPEN);
+
+ *agroup_store = G_ACTION_GROUP(agroup);
+ return gmenu;
+}
+
+ static void
+tabline_menu_press_event(
+ GtkGestureClick *gesture,
+ int n_press UNUSED,
+ double x,
+ double y,
+ GtkWidget *popover)
+{
+ guint but;
+
+ but = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture));
+ if (but == GDK_BUTTON_SECONDARY)
+ {
+ // Right mouse button pressed, popup menu
+ GdkRectangle rect;
+ rect.x = x;
+ rect.y = y;
+ rect.width = 1;
+ rect.height = 1;
+
+ gtk_popover_set_pointing_to(GTK_POPOVER(popover), &rect);
+ gtk_popover_popup(GTK_POPOVER(popover));
+ }
+ else if (but == GDK_BUTTON_PRIMARY)
+ {
+ if (tabpage_hover == 0)
+ // Click after all tabs moves to next tab page. When "x" is
+ // small guess it's the left button.
+ send_tabline_event(x < 50 ? -1 : 0);
+ }
+ else if (but == GDK_BUTTON_MIDDLE)
+ {
+ if (tabpage_hover != 0)
+ // Middle mouse click on tabpage label closes that tab.
+ send_tabline_menu_event(tabpage_hover, TABLINE_MENU_CLOSE);
+ }
+}
#endif
/*
diff --git a/src/po/vim.pot b/src/po/vim.pot
index fa6d37d69..b185a5144 100644
--- a/src/po/vim.pot
+++ b/src/po/vim.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Vim
"
"Report-Msgid-Bugs-To:
vim...@vim.org
"
-"POT-Creation-Date: 2026-06-13 19:24+0000
"
+"POT-Creation-Date: 2026-06-16 19:21+0000
"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>
"
"Language-Team: LANGUAGE <
L...@li.org>
"
@@ -956,6 +956,15 @@ msgstr ""
msgid "_Close"
msgstr ""
+msgid "Close Tab"
+msgstr ""
+
+msgid "New Tab"
+msgstr ""
+
+msgid "Open Tab..."
+msgstr ""
+
msgid "Direction:"
msgstr ""
@@ -971,9 +980,6 @@ msgstr ""
msgid "New tab"
msgstr ""
-msgid "Open Tab..."
-msgstr ""
-
msgid "Vim: Main window unexpectedly destroyed
"
msgstr ""
diff --git a/src/version.c b/src/version.c
index 864688f60..fef84f283 100644
--- a/src/version.c
+++ b/src/version.c
@@ -759,6 +759,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 657,
/**/
656,
/**/