patch 9.2.0578: GTK4: :unmenu does not remove entries from the menubar

Commit: 
https://github.com/vim/vim/commit/63a9471e225ec8388f3d7f03ba6bc0a137533529
Author: Yasuhiro Matsumoto <[email protected]>
Date:   Sun May 31 20:32:40 2026 +0000

    patch 9.2.0578: GTK4: :unmenu does not remove entries from the menubar
    
    Problem:  GTK4: gui_mch_destroy_menu() never removed the entry from its
              parent GMenu, so :unmenu was effectively a no-op against the
              visible menubar model.  After "Refresh menu" the Buffers menu
              items doubled because runtime/menu.vim BMShow() re-appended
              Refresh / Delete / Alternate / Next / Previous / -SEP- into a
              still-populated GMenu.  The GAction registered for the item
              and the reference on its submenu GMenu were also leaked.
    Solution: Compute the entry's position in the parent GMenu by walking
              the vimmenu_T sibling list, call g_menu_remove(), remove the
              GAction we registered, and release our reference on the
              submenu GMenu (Yasuhiro Matsumoto)
    
    fixes:  #20262
    closes: #20314
    
    Co-Authored-by: Claude <[email protected]>
    Signed-off-by: Yasuhiro Matsumoto <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/gui_gtk4.c b/src/gui_gtk4.c
index e7bbd9ad5..8d650397c 100644
--- a/src/gui_gtk4.c
+++ b/src/gui_gtk4.c
@@ -3919,29 +3919,103 @@ gui_mch_menu_set_tip(vimmenu_T *menu UNUSED)
 {
 }
 
+/*
+ * Return TRUE if "menu" has a corresponding entry in its parent's GMenu.
+ * Popup menus, toolbar children and orphaned submenus do not.
+ */
+    static int
+menu_has_gmenu_slot(vimmenu_T *menu)
+{
+    if (menu == NULL || menu->name == NULL)
+       return FALSE;
+    if (menu->name[0] == ']' || menu_is_popup(menu->name))
+       return FALSE;
+    if (menu->parent != NULL)
+    {
+       if (menu_is_toolbar(menu->parent->name))
+           return FALSE;
+       if (menu->parent->submenu_id == NULL)
+           return FALSE;
+       return TRUE;
+    }
+    return menu_is_menubar(menu->name);
+}
+
+/*
+ * Find the parent GMenu containing the entry for "menu" and the position of
+ * that entry.  Returns TRUE on success.
+ */
+    static int
+get_gmenu_pos_in_parent(vimmenu_T *menu, GMenu **parent_out, int *pos_out)
+{
+    GMenu      *parent_gmenu;
+    vimmenu_T  *first_sibling;
+    vimmenu_T  *sib;
+    int                pos = 0;
+
+    if (!menu_has_gmenu_slot(menu))
+       return FALSE;
+
+    if (menu->parent != NULL)
+    {
+       parent_gmenu = (GMenu *)(gpointer)menu->parent->submenu_id;
+       first_sibling = menu->parent->children;
+    }
+    else
+    {
+       if (gui.menubar == NULL)
+           return FALSE;
+       parent_gmenu = (GMenu *)(gpointer)g_object_get_data(
+               G_OBJECT(gui.menubar), "vim-gmenu");
+       first_sibling = root_menu;
+    }
+    if (parent_gmenu == NULL)
+       return FALSE;
+
+    for (sib = first_sibling; sib != NULL && sib != menu; sib = sib->next)
+       if (menu_has_gmenu_slot(sib))
+           pos++;
+    if (sib != menu)
+       return FALSE;
+
+    *parent_out = parent_gmenu;
+    *pos_out = pos;
+    return TRUE;
+}
+
     void
 gui_mch_destroy_menu(vimmenu_T *menu)
 {
-    // For toolbar buttons, remove from toolbar
+    GMenu      *parent_gmenu = NULL;
+    int                pos = 0;
+
+    // For toolbar buttons and separators, remove from the toolbar box.
     if (menu->id != NULL && menu->id != (GtkWidget *)1)
     {
        GtkWidget *parent_widget = gtk_widget_get_parent(menu->id);
+
        if (parent_widget != NULL)
            gtk_box_remove(GTK_BOX(parent_widget), menu->id);
-       menu->id = NULL;
     }
-    else
-       menu->id = NULL;
+    menu->id = NULL;
+
+    // Remove the entry from the parent GMenu so the visible menu updates.
+    if (get_gmenu_pos_in_parent(menu, &parent_gmenu, &pos))
+       g_menu_remove(parent_gmenu, pos);
 
-    // Free stored action name
-    vim_free(menu->label);
-    menu->label = NULL;
+    // Remove the GAction created for this item and free its name.
+    if (menu->label != NULL)
+    {
+       if (menu_action_group != NULL)
+           g_action_map_remove_action(G_ACTION_MAP(menu_action_group),
+                   (const char *)menu->label);
+       VIM_CLEAR(menu->label);
+    }
 
-    // GMenu items cannot be individually removed easily.
-    // The submenu GMenu is unreffed if present.
+    // Release our reference on the submenu GMenu (if any).
     if (menu->submenu_id != NULL)
     {
-       // Don't unref - GMenu may be referenced by the model
+       g_object_unref(menu->submenu_id);
        menu->submenu_id = NULL;
     }
 }
diff --git a/src/version.c b/src/version.c
index b97066f50..80020f59f 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 */
+/**/
+    578,
 /**/
     577,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wTn1s-00GYHY-0M%40256bit.org.

Raspunde prin e-mail lui