2
votes

I have the following code that uses GTK+ widget toolkit to display a window with a button. Clicking this button will show a modal dialog. Note that the call to gtk_dialog_run will recursively start another instance of the main loop, i.e. the on_click function will not return until the dialog box is dismissed.

I would like to have two of these top-level windows, each with a button and the ability to spawn their own modal dialog. Showing the dialog would only disable the window that spawned it and there could be up to two active modal dialogs at the same time, one for each top-level window.

In win32, I could accomplish this simply by running each top-level window in a separate thread. However, it seems that gtk_main can only be run from one thread. So how can I manage multiple window stacks in GTK+ (without sacrificing the simplicity of gtk_dialog_run if possible)?

Update: The code now displays both windows and adds them to their respective window groups.

#include <gtk/gtk.h>

struct modal_stack
{
    GtkWindowGroup * group;
    GtkWidget * window;
};

static void on_click(GtkWidget *widget, gpointer sptr)
{
    modal_stack * s = (modal_stack *)sptr;
    GtkWidget * dialog = gtk_file_chooser_dialog_new(
        "Open File", 0, GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL,
        GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
    gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(s->window));
    gtk_window_group_add_window(s->group, GTK_WINDOW(dialog));
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_window_group_remove_window(s->group, GTK_WINDOW(dialog));
    gtk_widget_destroy(dialog);
}

void create_window(modal_stack & s)
{
    s.group = gtk_window_group_new();
    s.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    gtk_widget_set_usize(s.window, 200, 200);
    g_signal_connect(G_OBJECT (s.window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);
    g_signal_connect(G_OBJECT (s.window), "delete_event",
        G_CALLBACK(gtk_main_quit), NULL);

    GtkWidget * button = gtk_button_new ();
    gtk_button_set_label(GTK_BUTTON(button), "show dialog");
    g_signal_connect(G_OBJECT (button), "clicked",
        G_CALLBACK(on_click), (gpointer) &s);
    gtk_widget_show(button);

    gtk_container_add(GTK_CONTAINER (s.window), button);
    gtk_widget_show(s.window);

    gtk_window_group_add_window(s.group, GTK_WINDOW(s.window));
}

int main(int argc, char *argv[])
{
    gtk_init (&argc, &argv);
    modal_stack wnd1, wnd2;
    create_window(wnd1);
    create_window(wnd2);
    gtk_main();
}
3

3 Answers

5
votes

Put your call to gtk_dialog_run inside a function that is called by g_idle_add().

3
votes

There's a discrete mention in the gtk_dialog_run docs that gtk_dialog_run will only block interaction with windows in the same window group as your modal dialog.

2
votes

For me your test code is working fine, with two bugs I am seeing:

  • as you note the gtk_dialog_run() are nested so the outer one is waiting on the inner. this isn't really fixable I don't think, other than avoiding gtk_dialog_run() in this situation.
  • there's a warning about GTK_IS_WINDOW_GROUP, I think manually adding/removing the dialog to/from window group is mangling the refcounting

However, I can click on buttons fine and navigate around the filesystem in the file dialog, and I can use the open/close buttons.

I might guess there's a bug specific to the GTK version you're using that prevents clicking the dialog. Without knowing what the bug is, I completely believe that Kurt's idea to run the dialog in an idle instead of in the clicked handler could work around it. It may be that GTK gets confused by adding the modal grab inside the clicked handler. Just speculation. However, fwiw I'm not seeing the bug here on GTK 2.20.1.

A couple things I noticed in the code:

  • gtk_widget_set_usize should be gtk_window_set_default_size probably
  • you don't need to manually add/remove the dialog to the window group. set_transient_for automatically adds the dialog to the parent's window group, and destroying a window automatically removes it from its window group. removing those calls fixes the warning I'm seeing on dialog cancel.