patch 9.2.0584: GTK4: missing UI features
Commit:
https://github.com/vim/vim/commit/8f508cdcfb2c354b93cbd20789301d03bfb0bcfc
Author: Foxe Chen <
chen...@gmail.com>
Date: Mon Jun 1 20:26:18 2026 +0000
patch 9.2.0584: GTK4: missing UI features
Problem: GTK4: :popup, menu item show/hide and menu bar state
updates are not implemented.
Solution: Implement gui_make_popup(), gui_mch_menu_hidden() and
gui_mch_draw_menubar() (Foxe Chen).
closes: #20393
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 8d650397c..d6d32b7cc 100644
--- a/src/gui_gtk4.c
+++ b/src/gui_gtk4.c
@@ -814,8 +814,6 @@ gui_mch_settitle(char_u *title, char_u *icon UNUSED)
gtk_window_set_title(GTK_WINDOW(gui.mainwin), (const char *)title);
}
-static int in_set_shellsize = FALSE;
-
/*
* Get height of window decorations, that we cannot determine directly. For
* example, the GtkHeaderBar widget. This is called in gui_resize_shell(), we
@@ -2480,17 +2478,41 @@ gui_mch_menu_grey(vimmenu_T *menu, int grey)
}
}
+#if defined(FEAT_MENU)
+/*
+ * Make menu item hidden or not hidden.
+ */
void
-gui_mch_menu_hidden(vimmenu_T *menu UNUSED, int hidden UNUSED)
+gui_mch_menu_hidden(vimmenu_T *menu, int hidden)
{
- // No-op: menu system not yet implemented for GTK4.
+ if (menu->id == 0)
+ return;
+
+ if (hidden)
+ {
+ if (gtk_widget_get_visible(menu->id))
+ {
+ gtk_widget_set_visible(menu->id, FALSE);
+ gui_mch_update();
+ }
+ }
+ else
+ {
+ if (!gtk_widget_get_visible(menu->id))
+ {
+ gtk_widget_set_visible(menu->id, TRUE);
+ gui_mch_update();
+ }
+ }
}
void
gui_mch_draw_menubar(void)
{
- // No-op: menu system not yet implemented for GTK4.
+ // Just make sure that the visual changes get effect immediately
+ gui_mch_update();
}
+#endif
/*
* ============================================================
@@ -3244,19 +3266,181 @@ gui_get_x11_windis(Window *win UNUSED, Display **dis UNUSED)
return FAIL;
}
+#if defined(FEAT_MENU)
void
gui_gtk_set_mnemonics(int enable UNUSED)
{
- // No-op: menu mnemonics depend on menu system, not yet implemented
- // for GTK4.
+ // TODO: implement?
+}
+
+ static void
+popupmenu_closed_cb(GtkPopover *popover, gpointer data UNUSED)
+{
+ gtk_widget_unparent(GTK_WIDGET(popover));
+ if (gui.drawarea != NULL)
+ gtk_widget_queue_draw(gui.drawarea);
+}
+
+typedef struct {
+ GtkPopover *popover;
+ vimmenu_T *menu;
+} popup_item_data_T;
+
+ static void
+popup_item_clicked_cb(GtkButton *button UNUSED, gpointer data)
+{
+ popup_item_data_T *d = data;
+
+ if (d->popover != NULL)
+ gtk_popover_popdown(d->popover);
+ if (d->menu != NULL)
+ {
+ gui_menu_cb(d->menu);
+ gui_mch_flush();
+ }
+}
+
+ static void
+popup_item_data_free(gpointer data, GClosure *closure UNUSED)
+{
+ g_free(data);
+}
+
+/*
+ * Open a popup for the given menu at point (x, y).
+ */
+ static void
+gui_gtk_popup_at(vimmenu_T *menu, int x, int y)
+{
+ GtkWidget *popover;
+ GtkWidget *box;
+ GtkWidget *parent;
+ GdkRectangle rect;
+ vimmenu_T *child;
+ int mode;
+ int natural_width = 0;
+
+ if (menu == NULL || menu->children == NULL)
+ return;
+
+ // Attach the popover to drawarea's parent rather than to drawarea itself.
+ // GtkDrawingArea is a leaf widget whose snapshot does not iterate children,
+ // and parenting a popover to it has been observed to leave the drawing area
+ // blank while the popover is open.
+ parent = gtk_widget_get_parent(gui.drawarea);
+ if (parent == NULL)
+ parent = gui.drawarea;
+
+ // Build the popover by hand instead of using gtk_popover_menu_new_from_model.
+ // GtkPopoverMenu relies on the "menu.<name>" action-group lookup walking up
+ // the parent chain, which has been observed to silently fail on some
+ // compositors when the popover is parented via gtk_widget_set_parent. Wiring
+ // each menu item to a plain "clicked" signal sidesteps that entirely.
+ popover = gtk_popover_new();
+ gtk_widget_set_parent(popover, parent);
+ gtk_popover_set_has_arrow(GTK_POPOVER(popover), FALSE);
+ gtk_popover_set_position(GTK_POPOVER(popover), GTK_POS_BOTTOM);
+ gtk_widget_add_css_class(popover, "menu");
+
+ box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_popover_set_child(GTK_POPOVER(popover), box);
+
+ mode = get_menu_mode_flag();
+
+ for (child = menu->children; child != NULL; child = child->next)
+ {
+ GtkWidget *item;
+ char_u *label;
+ popup_item_data_T *cb_data;
+
+ if (menu_is_separator(child->name))
+ {
+ item = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+ gtk_box_append(GTK_BOX(box), item);
+ continue;
+ }
+
+ label = CONVERT_TO_UTF8(child->dname);
+ item = gtk_button_new_with_mnemonic(
+ label != NULL ? (const char *)label : "");
+ CONVERT_TO_UTF8_FREE(label);
+
+ gtk_widget_add_css_class(item, "flat");
+ gtk_widget_add_css_class(item, "model");
+ gtk_button_set_has_frame(GTK_BUTTON(item), FALSE);
+ gtk_widget_set_halign(item, GTK_ALIGN_FILL);
+ {
+ GtkWidget *btn_label = gtk_button_get_child(GTK_BUTTON(item));
+ if (GTK_IS_LABEL(btn_label))
+ gtk_label_set_xalign(GTK_LABEL(btn_label), 0.0);
+ }
+
+ if (!(child->modes & child->enabled & mode))
+ gtk_widget_set_sensitive(item, FALSE);
+
+ cb_data = g_new0(popup_item_data_T, 1);
+ cb_data->popover = GTK_POPOVER(popover);
+ cb_data->menu = child;
+ g_signal_connect_data(item, "clicked",
+ G_CALLBACK(popup_item_clicked_cb),
+ cb_data, popup_item_data_free, 0);
+
+ gtk_box_append(GTK_BOX(box), item);
+ }
+
+ rect.x = x;
+ rect.y = y;
+ // GtkPopover with GTK_POS_BOTTOM centres horizontally on the pointing-to
+ // rectangle. Use the box's natural width so the popover's left edge ends
+ // up at the cursor (down-and-to-the-right of the pointer).
+ gtk_widget_measure(box, GTK_ORIENTATION_HORIZONTAL, -1,
+ NULL, &natural_width, NULL, NULL);
+ rect.width = natural_width > 0 ? natural_width : 1;
+ rect.height = 1;
+ gtk_popover_set_pointing_to(GTK_POPOVER(popover), &rect);
+
+ g_signal_connect(popover, "closed",
+ G_CALLBACK(popupmenu_closed_cb), NULL);
+ gtk_popover_popup(GTK_POPOVER(popover));
}
void
-gui_make_popup(char_u *path_name UNUSED, int mouse_pos UNUSED)
+gui_make_popup(char_u *path_name, int mouse_pos)
{
- // No-op: popup menus depend on menu system, not yet implemented
- // for GTK4.
+ vimmenu_T *menu;
+ int x, y;
+
+ menu = gui_find_menu(path_name);
+ if (menu == NULL || menu->submenu_id == NULL)
+ return;
+
+ if (mouse_pos)
+ gui_mch_getmouse(&x, &y);
+ else
+ {
+ // Find the cursor position relative to parent of drawarea
+ GtkWidget *parent = gtk_widget_get_parent(gui.drawarea);
+ graphene_point_t point;
+ if (parent == NULL)
+ parent = gui.drawarea;
+
+ if (!gtk_widget_compute_point(gui.drawarea, parent,
+ &GRAPHENE_POINT_INIT(0, 0), &point))
+ x = y = 0;
+ else
+ {
+ x = point.x;
+ y = point.y;
+ }
+
+ x += FILL_X(curwin->w_wincol + curwin->w_wcol + 1) + 1;
+ y += FILL_Y(W_WINROW(curwin) + curwin->w_wrow + 1) + 1;
+ }
+
+ gui_gtk_popup_at(menu, x, y);
+
}
+#endif // FEAT_MENU
int
get_menu_tool_width(void)
@@ -4020,135 +4204,13 @@ gui_mch_destroy_menu(vimmenu_T *menu)
}
}
- static void
-popupmenu_closed_cb(GtkPopover *popover, gpointer data UNUSED)
-{
- gtk_widget_unparent(GTK_WIDGET(popover));
- if (gui.drawarea != NULL)
- gtk_widget_queue_draw(gui.drawarea);
-}
-
-typedef struct {
- GtkPopover *popover;
- vimmenu_T *menu;
-} popup_item_data_T;
-
- static void
-popup_item_clicked_cb(GtkButton *button UNUSED, gpointer data)
-{
- popup_item_data_T *d = data;
-
- if (d->popover != NULL)
- gtk_popover_popdown(d->popover);
- if (d->menu != NULL)
- {
- gui_menu_cb(d->menu);
- gui_mch_flush();
- }
-}
-
- static void
-popup_item_data_free(gpointer data, GClosure *closure UNUSED)
-{
- g_free(data);
-}
-
void
gui_mch_show_popupmenu(vimmenu_T *menu)
{
- GtkWidget *popover;
- GtkWidget *box;
- GtkWidget *parent;
- GdkRectangle rect;
- vimmenu_T *child;
- int mode;
- int natural_width = 0;
-
- if (menu == NULL || menu->children == NULL)
- return;
-
- // Attach the popover to drawarea's parent (the GtkOverlay) rather than
- // to drawarea itself. GtkDrawingArea is a leaf widget whose snapshot
- // does not iterate children, and parenting a popover to it has been
- // observed to leave the drawing area blank while the popover is open.
- parent = gtk_widget_get_parent(gui.drawarea);
- if (parent == NULL)
- parent = gui.drawarea;
-
- // Build the popover by hand instead of using gtk_popover_menu_new_from_model.
- // GtkPopoverMenu relies on the "menu.<name>" action-group lookup walking up
- // the parent chain, which has been observed to silently fail on some
- // compositors when the popover is parented via gtk_widget_set_parent. Wiring
- // each menu item to a plain "clicked" signal sidesteps that entirely.
- popover = gtk_popover_new();
- gtk_widget_set_parent(popover, parent);
- gtk_popover_set_has_arrow(GTK_POPOVER(popover), FALSE);
- gtk_popover_set_position(GTK_POPOVER(popover), GTK_POS_BOTTOM);
- gtk_widget_add_css_class(popover, "menu");
-
- box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
- gtk_popover_set_child(GTK_POPOVER(popover), box);
-
- mode = get_menu_mode_flag();
-
- for (child = menu->children; child != NULL; child = child->next)
- {
- GtkWidget *item;
- char_u *label;
- popup_item_data_T *cb_data;
-
- if (menu_is_separator(child->name))
- {
- item = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
- gtk_box_append(GTK_BOX(box), item);
- continue;
- }
-
- label = CONVERT_TO_UTF8(child->dname);
- item = gtk_button_new_with_mnemonic(
- label != NULL ? (const char *)label : "");
- CONVERT_TO_UTF8_FREE(label);
-
- gtk_widget_add_css_class(item, "flat");
- gtk_widget_add_css_class(item, "model");
- gtk_button_set_has_frame(GTK_BUTTON(item), FALSE);
- gtk_widget_set_halign(item, GTK_ALIGN_FILL);
- {
- GtkWidget *btn_label = gtk_button_get_child(GTK_BUTTON(item));
- if (GTK_IS_LABEL(btn_label))
- gtk_label_set_xalign(GTK_LABEL(btn_label), 0.0);
- }
-
- if (!(child->modes & child->enabled & mode))
- gtk_widget_set_sensitive(item, FALSE);
-
- cb_data = g_new0(popup_item_data_T, 1);
- cb_data->popover = GTK_POPOVER(popover);
- cb_data->menu = child;
- g_signal_connect_data(item, "clicked",
- G_CALLBACK(popup_item_clicked_cb),
- cb_data, popup_item_data_free, 0);
-
- gtk_box_append(GTK_BOX(box), item);
- }
-
- if (!query_pointer_pos(&rect.x, &rect.y))
- {
- rect.x = 0;
- rect.y = 0;
- }
- // GtkPopover with GTK_POS_BOTTOM centres horizontally on the pointing-to
- // rectangle. Use the box's natural width so the popover's left edge ends
- // up at the cursor (down-and-to-the-right of the pointer).
- gtk_widget_measure(box, GTK_ORIENTATION_HORIZONTAL, -1,
- NULL, &natural_width, NULL, NULL);
- rect.width = natural_width > 0 ? natural_width : 1;
- rect.height = 1;
- gtk_popover_set_pointing_to(GTK_POPOVER(popover), &rect);
+ int x, y;
- g_signal_connect(popover, "closed",
- G_CALLBACK(popupmenu_closed_cb), NULL);
- gtk_popover_popup(GTK_POPOVER(popover));
+ gui_mch_getmouse(&x, &y);
+ gui_gtk_popup_at(menu, x, y);
}
static void
diff --git a/src/version.c b/src/version.c
index 6d7077fe9..208e7164d 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 584,
/**/
583,
/**/