Search code examples
c++gtkgtk3gtkmmgtkmm3

Signal_delete_event doesn't change page in notebook with set_current_page


I'd like to change to a certain page in a notebook when deleting the window and do some work before effectively deleting the window. The code below gives 1 for get_current_page, but the page isn't effectively changed to 1.

What should be the solution to this problem?

Form::Form()
{
    add(box);

    notebook.set_size_request(800, 600);
    notebook.set_show_tabs(false);
    box.pack_start(notebook, Gtk::PACK_SHRINK);

    label_intro.set_text("Intro");
    box_intro.pack_start(label_intro);
    box_intro.show();

    label_exit.set_text("Preparing clean exit ... Please wait!");
    box_exit.pack_start(label_exit);
    box_exit.show();

    notebook.insert_page(box_intro, "Intro", 0);
    notebook.insert_page(box_exit, "Exit", 1);

    signal_delete_event().connect(sigc::mem_fun(*this, &Form::is_deleted));

    set_title("title");
    resize(800, 600);
    show_all();
}

bool Form::is_deleted(GdkEventAny *any_event)
{
    notebook.set_current_page(1);
    std::cout << "current_page " << notebook.get_current_page() << std::endl; // gives 1

    std::this_thread::sleep_for(2000ms);
    return Window::on_delete_event(any_event);
}

class Form : public Window
{
public:
    Form();
private:
    bool is_deleted(GdkEventAny *any_event);
private:
    // Form
    Box box;
    Notebook notebook;
    Box box_intro, box_exit;
    Label label_intro, label_exit;
};

Solution

  • Sorry for the late answer, your question made my work more than I thought!

    Working with the Glib::TimeoutSource class (which is ridiculously under documented...), I was able to hack my way around this limitation.

    Basically, my strategy was, on a single click, to run the delete-event signal handler two times:

    1. once to update the notebook. In this case we return true to indicate the handler has "fully handled" the signal, and propagation should not happen, hence not closing the window. At this point, the user sees the change in the UI, but no work has been done. In this pass, we also set a Glib::Timeout to later call the close method on the window, calling once again the delete-event signal handler.
    2. twice to do the work. This time, we do the real work (UI has already been updated). Once the work is done, we propagate the handler and close the window.

    Here is this code*:

    #include <chrono>
    #include <iostream>
    #include <thread>
    
    #include <gtkmm.h>
    
    using namespace std::chrono_literals;
    
    class Form : public Gtk::Window
    {
    
    public:
    
        Form();
    
    private:
    
        Gtk::Box      box;
        Gtk::Notebook notebook;
        Gtk::Box      box_intro;
        Gtk::Box      box_exit;
        Gtk::Label    label_intro;
        Gtk::Label    label_exit;
    
        // This flag indicates if a first call to the "delete-event" signal
        // has been done. On a second call to this event, this should be
        // set to "true" to alter the handler's behaviour.
        bool flag = false;
    };
    
    Form::Form()
    {
        add(box); 
        notebook.set_size_request(800, 600);
        notebook.set_show_tabs(false);
        box.pack_start(notebook, Gtk::PACK_SHRINK);
    
        label_intro.set_text("Intro");
        box_intro.pack_start(label_intro);
        box_intro.show();
    
        label_exit.set_text("Preparing clean exit ... Please wait!");
        box_exit.pack_start(label_exit);
        box_exit.show();
    
        notebook.insert_page(box_intro, "Intro", 0);
        notebook.insert_page(box_exit, "Exit", 1);
    
        signal_delete_event().connect(
            [this](GdkEventAny *any_event)
            {
                if(!flag)
                {
                    // First time in. We change the notebook page:
                    notebook.set_current_page(1);
    
                    // If we block right away and don't return from
                    // this handler, the GUI will freeze. Hence, we set
                    // a timer to "close" the window in 10ms. Not that
                    // closing the window will call once more this handler...
                    Glib::signal_timeout().connect(
                        [this]()
                        {
                            close();
                            return false; // Disconnect after on call...
                        },
                        10
                    );
    
                    // So we change the flag value, to alter its behavior on the
                    // next pass.
                    flag = true;
    
                    return true; // Fully handled for now... leaving the handler.
                                 // This will allow the main loop to be run and the
                                 // window to uptate.
                }
    
                // On the second run, we do the work:
                std::this_thread::sleep_for(1900ms);
    
                // And we close the window (for real this time):
                return Window::on_delete_event(any_event);
            }
        );
    
        set_title("title");
        resize(800, 600);
        show_all();
    }
    
    int main(int argc, char *argv[])
    {
        auto app = Gtk::Application::create(argc, argv, "org.gtkmm.examples.base");
    
        Form window;
        window.show_all();
    
        return app->run(window);
    }
    

    * I took the liberty to use lambda expressions, as I think they are more readable and encapsulated, and I wasn't sure if you knew about them. In any case, take what feels best.

    I understand this is some sort of a hack, but after having tried a lot of things, I have come to believe this is the best we can achieve without dealing with more involved multi-threading strategies (which I am no expert of, sadly).

    Hopes this, at least temporarily, solves your issue.