c++gtkmm4

gtkmm 4.10 - New filedialog class - handler not called until calling routine exits


I have been converting from gtkmm 4.8 to 4.10 and trying to replace the filechooserdialog with the new filedialog. My test code (below) finds that the routine that invokes, for example, dialog->save() will not wait until the handler is complete and I am used to coding as per the filechooserdialog where I could set it to wait until the user has dismissed the dialog. The header file is below:

#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H

#include <gtkmm.h>

class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow();
  ~ExampleWindow() override;

protected:
  // Signal handlers:
  void on_button_file();
  void on_button_quit();
  void on_file_dialog_save(const Glib::RefPtr<Gio::AsyncResult> &result,
    const Glib::RefPtr<Gtk::FileDialog> &dialog);

  // Child widgets:
  Gtk::Box m_VBox;
  Gtk::Box m_ButtonBox;
  Gtk::Button m_Button_Quit;
  Gtk::Button m_Button_File;
};

#include "filedialog.h"

ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
  m_Button_File("File"),
  m_Button_Quit("Quit")
{
  set_title("FileDialog example");
  set_default_size(100, 100);

  m_VBox.set_margin(5);
  set_child(m_VBox);

  m_VBox.append(m_ButtonBox);

  m_ButtonBox.append(m_Button_File);
  m_ButtonBox.append(m_Button_Quit);

  m_ButtonBox.set_margin(5);

  m_Button_File.signal_clicked().connect(
    sigc::mem_fun(*this, &ExampleWindow::on_button_file));

  m_Button_Quit.signal_clicked().connect(
    sigc::mem_fun(*this, &ExampleWindow::on_button_quit));
}
#endif //GTKMM_EXAMPLEWINDOW_H

The cpp file is below:

#include "filedialog.h"

ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
  m_Button_File("File"),
  m_Button_Quit("Quit")
{
  set_title("FileDialog example");
  set_default_size(100, 100);

  m_VBox.set_margin(5);
  set_child(m_VBox);

  m_VBox.append(m_ButtonBox);

  m_ButtonBox.append(m_Button_File);
  m_ButtonBox.append(m_Button_Quit);

  m_ButtonBox.set_margin(5);

  m_Button_File.signal_clicked().connect(
    sigc::mem_fun(*this, &ExampleWindow::on_button_file));

  m_Button_Quit.signal_clicked().connect(
    sigc::mem_fun(*this, &ExampleWindow::on_button_quit));
}

ExampleWindow::~ExampleWindow()
{
}

void ExampleWindow::on_button_file()
{
   auto dialog = Gtk::FileDialog::create();
   dialog->set_title("Testing file dialog save");
   printf("Created file/save dialog\n");
   dialog->save(*this,
     sigc::bind(
           sigc::mem_fun(
          *this,
          &ExampleWindow::on_file_dialog_save
          ),
           dialog
        )
      );
   printf("Exited file button handler\n");
}

void ExampleWindow::on_file_dialog_save(const Glib::RefPtr<Gio::AsyncResult> &result,
    const Glib::RefPtr<Gtk::FileDialog> &dialog)
{
   printf("Before save/finish call\n");
   auto file = dialog->save_finish(result);
   printf("After save/finish call\n");
}

void ExampleWindow::on_button_quit()
{
  set_visible(false);
}

When I run the program, the following output occurs:

Created file/save dialog Exited file button handler Before save/finish call After save/finish call

and thus I can't seem to have the file button wait until the handler is complete.

  1. Created simple example.
  2. Plently of internet searches

Further investigation

Added a semaphore, initialised to one, then modified the File button handler as per the following:

void ExampleWindow::on_button_file()
{
   auto dialog = Gtk::FileDialog::create();
   dialog->set_title("Testing file dialog save");
   printf("Created file/save dialog\n");
   sem_wait(&m_dialog_semaphore);
   printf("Before dialog->save() call\n");
   dialog->save(*this,
     sigc::bind(
           sigc::mem_fun(
          *this,
          &ExampleWindow::on_file_dialog_save
          ),
           dialog
        )
      );
   sem_wait(&m_dialog_semaphore);
   printf("Exited file button handler\n");
}

and then added the following code to the dialog handler:

void ExampleWindow::on_file_dialog_save(const Glib::RefPtr<Gio::AsyncResult> &result,
    const Glib::RefPtr<Gtk::FileDialog> &dialog)
{
   printf("Before save/finish call\n");
   auto file = dialog->save_finish(result);
   printf("After save/finish call\n");
   sem_post(&m_dialog_semaphore);
}

with the idea being to ensure that the file button handler has to wait until the dialog handler exits and thus the code behaves as I would expect and want to. The program now does not display the dialog and freezes as that point. The only output now is as follows:

Created file/save dialog Before dialog->save() call

It seems that until such time that the file button handler exits that the dialog will not display. This seems odd to me and when I reviewed (again) the sample code in the gtkmm examples book I found that it was way too simple an example and did not answer any of my questions.


Solution

  • Solution When I finally realised that there was not any way to make an asynchronous member function behave in a sequential fashion the solution came to be quite quickly. I was too focused on the old filechooserdialog to see that I needed to work with the new filedialog class and not against it as I had been doing.

    What you need to do is to sigc::bind() a pointer to a function that will perform the additional work after the file dialog call. You then connect up the handler function for the dialog type that you require as per normal. The function pointer will be one of the parameters and you can simply invoke the function at the end of the handler.

    This meant that my software libraries required little change and I can now move on with converting my applications to gtkmm 4.10.

    Example Code For Solution

    The first thing that is required is the creation of a function pointer to the additional work that you require to be performed when the open (or save) is complete. Something as per the below will suffice:

        //
        //Now we need to define the browse for a file dialog extra work function
        //and as we wish to use a member function of the class we need to bind
        //the 'this' pointer so that the member function will be called correctly.
        //
        auto browse_file_function =
            std::bind(
                &DA_MainWindow::Browse_File_Work,
                this,
                dialog
            );
    
    

    Then you need to link the open() handler with the above function so that within the handler you can then invoke your additional work code.

    
        //
        //Now we need to set up the handler for the requested event.
        //
        dialog->open(
            *parent_window,
            sigc::bind(
                sigc::ptr_fun(
                    &MyGtk::File_Dialog_Run_Open_File_Handler
                ),
                dialog,
                browse_file_function
            )
        );
    

    Then within the handler function you simply perform:

    browse_file_function();
    

    to perform the post processing once the user has selected a file.