patch 9.2.0607: GTK4: inputdialog() does not work as expected

Commit: 
https://github.com/vim/vim/commit/beef02a975815cb03d9a7159ede72629d498ddf2
Author: Foxe Chen <[email protected]>
Date:   Tue Jun 9 18:58:08 2026 +0000

    patch 9.2.0607: GTK4: inputdialog() does not work as expected
    
    Problem:  GTK4: inputdialog() does not work as expected
    Solution: Refactor the dialog code to create a custom window instead of
              using GtkAlertDialog, while at it, also makes mnemonics
              work as expected (Foxe Chen).
    
    closes: #20448
    
    Signed-off-by: Foxe Chen <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/gui_gtk4.c b/src/gui_gtk4.c
index 033b3f7ba..5328a4b5b 100644
--- a/src/gui_gtk4.c
+++ b/src/gui_gtk4.c
@@ -4555,97 +4555,269 @@ gui_mch_browsedir(char_u *title, char_u *initdir)
  * ============================================================
  */
 
-typedef struct {
-    int                response;
-    gboolean   done;
-} AlertDialogData;
+/*
+ * Split up button_string into individual button labels by inserting NUL bytes.
+ * Also replace the Vim-style mnemonic accelerator prefix '&' with '_'.
+ * "button_string" is duplicated; caller must free the duplicated string via
+ * *tofree.
+ */
+    static char **
+split_button_string(char_u *button_string, int *n_buttons, char **tofree)
+{
+    char           **array;
+    char_u         *p;
+    unsigned int    count = 1;
+
+    button_string = (char_u *)g_strdup((const char *)button_string);
+
+    for (p = button_string; *p != NUL; ++p)
+       if (*p == DLG_BUTTON_SEP)
+           ++count;
+
+    array = g_malloc_n(count, sizeof(char *));
+    count = 0;
+
+    if (array != NULL)
+    {
+       array[count++] = (char *)button_string;
+       for (p = button_string; *p != NUL; )
+       {
+           if (*p == DLG_BUTTON_SEP)
+           {
+               *p++ = NUL;
+               array[count++] = (char *)p;
+           }
+           else if (*p == DLG_HOTKEY_CHAR)
+               *p++ = '_';
+           else
+               MB_PTR_ADV(p);
+       }
+    }
+
+    *tofree = (char *)button_string;
+    *n_buttons = count;
+    return array;
+}
+
+/*
+ * Convert VIM_GENERIC, VIM_ERROR, etc into an icon name. Returns NULL for
+ * VIM_GENERIC.
+ */
+    static const char *
+dialog_type_to_icon(int type)
+{
+    switch (type)
+    {
+       case VIM_ERROR:
+           return "dialog-error-symbolic";
+       case VIM_WARNING:
+           return "dialog-warning-symbolic";
+       case VIM_INFO:
+           return "dialog-information-symbolic";
+       case VIM_QUESTION:
+           return "dialog-question-symbolic";
+       default:
+           break;
+    }
+    return NULL;
+}
+
+// Data associated with each button in the dialog
+typedef struct
+{
+    int                but_idx;
+    int                *response;
+    gboolean   *done;
+} DialogButtonState;
 
     static void
-alert_dialog_cb(GObject *source, GAsyncResult *res, gpointer data)
+dialog_button_clicked_cb(GtkButton *button, DialogButtonState *state)
 {
-    AlertDialogData *add = (AlertDialogData *)data;
-    add->response = gtk_alert_dialog_choose_finish(
-                   GTK_ALERT_DIALOG(source), res, NULL);
-    add->done = TRUE;
+    *state->response = state->but_idx;
+    *state->done = TRUE;
+}
+
+    static gboolean
+dialog_key_pressed_cb(
+       GtkEventControllerKey   *controller,
+       guint                   keyval,
+       guint                   keycode,
+       GdkModifierType         state,
+       gboolean                *done)
+{
+    if (keyval == GDK_KEY_Escape)
+    {
+       *done = TRUE;
+       return TRUE;
+    }
+    return FALSE;
+}
+
+    static gboolean
+dialog_close_request_cb(GtkWindow *win, gboolean *win_closed)
+{
+    *win_closed = TRUE;
+    return FALSE;
 }
 
     int
 gui_mch_dialog(
-       int     type UNUSED,
+       int     type,
        char_u  *title,
        char_u  *message,
        char_u  *buttons,
-       int     dfltbutton,
-       char_u  *textfield UNUSED,
+       int     def_but,
+       char_u  *textfield,
        int     ex_cmd UNUSED)
 {
-    GtkAlertDialog     *dlg;
-    AlertDialogData    add;
-    char_u             *p;
-    char_u             *buf = NULL;
-    int                        butcount = 0;
-    int                        i;
-    const char         *btn_labels[64];
-    char_u             *btn_conv[64];
+    GtkWindow          *win = GTK_WINDOW(gtk_window_new());
+    GtkWidget          *vertbox;
+    GtkWidget          *message_box;
+    const char         *icon_name;
+    GtkWidget          *icon;
+    GtkWidget          *label;
+    GtkWidget          *entry = NULL;
+    char_u             *utf8_title;
+    char_u             *utf8_message;
+    GtkEventController *key_controller;
+    DialogButtonState  *but_states = NULL;
+    char               *tofree = NULL;
+    int                        response = -1;
+    gboolean           done = FALSE;
+    gboolean           win_closed = FALSE;
+
+    utf8_title = CONVERT_TO_UTF8(title);
+    if (utf8_title != NULL)
+       gtk_window_set_title(win, (const char *)utf8_title);
+    CONVERT_TO_UTF8_FREE(utf8_title);
+
+    gtk_window_set_transient_for(win, GTK_WINDOW(gui.mainwin));
+    gtk_window_set_modal(win, TRUE);
+    gtk_window_set_default_size(win, 300, -1);
+    gtk_window_set_destroy_with_parent(win, TRUE);
+    g_signal_connect(win, "close-request",
+           G_CALLBACK(dialog_close_request_cb), &win_closed);
+
+    // Create main vertical layout container
+    vertbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 16);
+    gtk_window_set_child(win, vertbox);
+    gtk_widget_set_margin_top(vertbox, 24);
+    gtk_widget_set_margin_bottom(vertbox, 24);
+    gtk_widget_set_margin_start(vertbox, 12);
+    gtk_widget_set_margin_end(vertbox, 12);
 
-    title = CONVERT_TO_UTF8(title);
-    message = CONVERT_TO_UTF8(message);
+    // Add the message label
+    message_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
+    gtk_widget_set_halign(message_box, GTK_ALIGN_CENTER);
+    gtk_box_append(GTK_BOX(vertbox), message_box);
 
-    // Parse button labels from the "&Yes
&No
&Cancel" format
-    if (buttons != NULL)
+    // If type is not VIM_GENERIC, add an icon to make the dialog look nicer :)
+    icon_name = dialog_type_to_icon(type);
+    if (icon_name != NULL)
     {
-       buf = vim_strsave(buttons);
-       if (buf != NULL)
-       {
-           p = buf;
-           while (*p != NUL && butcount < 63)
-           {
-               char_u *start = p;
-               while (*p != NUL && *p != '
')
-                   ++p;
-               if (*p == '
')
-                   *p++ = NUL;
-               // Skip '&' mnemonic marker
-               if (*start == '&')
-                   ++start;
-               btn_conv[butcount] = CONVERT_TO_UTF8(start);
-               btn_labels[butcount] = (const char *)btn_conv[butcount];
-               butcount++;
-           }
-       }
+       icon = gtk_image_new_from_icon_name(icon_name);
+       gtk_image_set_icon_size(GTK_IMAGE(icon), GTK_ICON_SIZE_LARGE);
+       gtk_box_append(GTK_BOX(message_box), icon);
     }
-    btn_labels[butcount] = NULL;
 
-    dlg = gtk_alert_dialog_new("%s", message ? (char *)message : "");
-    if (title != NULL)
-       gtk_alert_dialog_set_detail(dlg, (const char *)title);
-    gtk_alert_dialog_set_buttons(dlg, btn_labels);
-    gtk_alert_dialog_set_modal(dlg, TRUE);
+    utf8_message = CONVERT_TO_UTF8(message);
+    label = gtk_label_new((const char *)utf8_message);
+    CONVERT_TO_UTF8_FREE(utf8_message);
+    gtk_label_set_wrap(GTK_LABEL(label), TRUE);
+    gtk_label_set_max_width_chars(GTK_LABEL(label), 40);
+    gtk_box_append(GTK_BOX(message_box), label);
+
+    // Close the dialog when the <Esc> key is pressed. the GTK3 GUI also allows
+    // mnemonics without <Alt> key, but that behaviour comes from GTK+ 1.2 
(from
+    // 1999!), so most users probably don't care...
+    key_controller = gtk_event_controller_key_new();
+    g_signal_connect(key_controller, "key-pressed", 
G_CALLBACK(dialog_key_pressed_cb), &done);
+    gtk_widget_add_controller(GTK_WIDGET(win), key_controller);
+
+    if (textfield != NULL)
+    {
+       // Add text entry so user can enter text
+       char_u *utf8_text = CONVERT_TO_UTF8(textfield);
+
+       entry = gtk_entry_new();
+
+       if (utf8_text != NULL)
+           gtk_editable_set_text(GTK_EDITABLE(entry), (const char *)utf8_text);
+       else
+           gtk_editable_set_text(GTK_EDITABLE(entry), "");
+       CONVERT_TO_UTF8_FREE(utf8_text);
+
+       // Make it so that pressing enter key will activate "def_but" button
+       // (which is set as the default widget).
+       gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
+       gtk_box_append(GTK_BOX(vertbox), entry);
+    }
 
-    if (dfltbutton > 0 && dfltbutton <= butcount)
-       gtk_alert_dialog_set_default_button(dlg, dfltbutton - 1);
-    if (butcount > 0)
-       gtk_alert_dialog_set_cancel_button(dlg, butcount - 1);
+    if (buttons != NULL)
+    {
+       GtkWidget   *but_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+       char        **buttons_arr; // Note that array is allocated, not strings
+       int         n_buttons;
 
-    add.response = -1;
-    add.done = FALSE;
+       gtk_widget_set_halign(but_box, GTK_ALIGN_CENTER);
+       gtk_box_set_homogeneous(GTK_BOX(but_box), TRUE);
+       gtk_box_append(GTK_BOX(vertbox), but_box);
+
+       buttons_arr = split_button_string(buttons, &n_buttons, &tofree);
+
+       but_states = g_malloc_n(n_buttons, sizeof(DialogButtonState));
+
+       for (int i = 0; i < n_buttons; i++)
+       {
+           char_u              *but_label;
+           GtkWidget           *but;
+           DialogButtonState   *but_state = but_states + i;
+
+           but_label = CONVERT_TO_UTF8((char_u *)buttons_arr[i]);
+           if (but_label == NULL)
+               continue;
+
+           but = gtk_button_new_with_mnemonic((char *)but_label);
+           if (i == def_but - 1)
+               gtk_window_set_default_widget(win, but);
+           gtk_box_append(GTK_BOX(but_box), but);
+           CONVERT_TO_UTF8_FREE(but_label);
+
+           but_state->but_idx = i;
+           but_state->response = &response;
+           but_state->done = &done;
+
+           g_signal_connect(but, "clicked",
+                   G_CALLBACK(dialog_button_clicked_cb), but_state);
+       }
+       g_free(buttons_arr);
+    }
 
-    gtk_alert_dialog_choose(dlg, GTK_WINDOW(gui.mainwin), NULL,
-           alert_dialog_cb, &add);
+    gtk_window_present(win);
 
-    while (!add.done)
+    while (!done && !win_closed)
        g_main_context_iteration(NULL, TRUE);
 
-    g_object_unref(dlg);
+    if (done)
+    {
+       if (textfield != NULL)
+       {
+           // Get the text the user entered
+           char_u *text;
 
-    for (i = 0; i < butcount; i++)
-       CONVERT_TO_UTF8_FREE(btn_conv[i]);
-    vim_free(buf);
-    CONVERT_TO_UTF8_FREE(title);
-    CONVERT_TO_UTF8_FREE(message);
+           text = (char_u *)gtk_editable_get_text(GTK_EDITABLE(entry));
+           text = CONVERT_FROM_UTF8(text);
+           vim_strncpy(textfield, text, IOSIZE - 1);
+           CONVERT_FROM_UTF8_FREE(text);
+       }
+
+       gtk_window_destroy(win);
+    }
+    g_free(but_states);
+    g_free(tofree);
 
-    // GTK returns 0-based index, Vim wants 1-based
-    return add.response >= 0 ? add.response + 1 : 0;
+    // Vim buttons are indexed starting from one.
+    return response == -1 ? 0 : response + 1;
 }
 
 /*
diff --git a/src/proto/gui_gtk4.pro b/src/proto/gui_gtk4.pro
index 0a1ba1296..653b5132b 100644
--- a/src/proto/gui_gtk4.pro
+++ b/src/proto/gui_gtk4.pro
@@ -103,7 +103,7 @@ void gui_mch_update_scrollbar_size(void);
 void gui_mch_set_text_area_pos(int x, int y, int w, int h);
 char_u *gui_mch_browse(int saving, char_u *title, char_u *dflt, char_u *ext, 
char_u *initdir, char_u *filter);
 char_u *gui_mch_browsedir(char_u *title, char_u *initdir);
-int gui_mch_dialog(int type, char_u *title, char_u *message, char_u *buttons, 
int dfltbutton, char_u *textfield, int ex_cmd);
+int gui_mch_dialog(int type, char_u *title, char_u *message, char_u *buttons, 
int def_but, char_u *textfield, int ex_cmd);
 void gui_mch_find_dialog(exarg_T *eap);
 void gui_mch_replace_dialog(exarg_T *eap);
 void ex_helpfind(exarg_T *eap);
diff --git a/src/testdir/lsan-suppress.txt b/src/testdir/lsan-suppress.txt
index 7083c1bbb..e6c2af18d 100644
--- a/src/testdir/lsan-suppress.txt
+++ b/src/testdir/lsan-suppress.txt
@@ -16,3 +16,7 @@ leak:libxcb*.so.*
 leak:gdk_x11_screen_init_gl
 # leak reported on CI for test_clipmethod
 leak:libgtk-3*.so.*
+# Leak from GTK when creating gtk4 dialog windows?
+leak:wl_proxy_marshal_array_flags
+leak:gtk_widget_realize
+leak:libgtk-4*.so.*
diff --git a/src/version.c b/src/version.c
index 7633b7e54..e35cb0962 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 */
+/**/
+    607,
 /**/
     606,
 /**/

-- 
-- 
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/E1wX1ui-006gRa-5F%40256bit.org.

Raspunde prin e-mail lui