Search code examples
c++gtkpass-by-referencegtk3gtkmm

Passing by reference to signal handler


Compile with g++ main.cc -Wall $(pkg-config gtkmm-3.0 --cflags --libs)

What it currently does:

Displays a window with a simple SpinButton

What I want to do:

Pass in a reference of spinbutton to the signal handler on_spinbutton_change so that I can getAdjustment and set the formatting (like here)

Question:

How do I pass in a reference of spinbutton and optionally additional data (like a simple integer)?

main.cc (compiles fine, does not pass reference):

#include <iostream>
#include <gtkmm/application.h>
#include <gtkmm/spinbutton.h>
#include <gtkmm/window.h>

class HelloWorld : public Gtk::Window {
public:
    HelloWorld();
    virtual ~HelloWorld();

protected:
    static gboolean on_spinbutton_change();
    Gtk::SpinButton spinbutton;
};

HelloWorld::HelloWorld() {
    spinbutton.signal_output().connect(sigc::ptr_fun(&HelloWorld::on_spinbutton_change));
    add(spinbutton);
    spinbutton.show();
}

HelloWorld::~HelloWorld() {}

gboolean HelloWorld::on_spinbutton_change() {
    std::cout << "Hello World" << std::endl;
    return true;
}

int main (int argc, char *argv[]) {
    auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example");
    HelloWorld helloworld;
    return app->run(helloworld);
}

main.cc (does not compile, attempt to pass reference):

#include <iostream>
#include <gtkmm/application.h>
#include <gtkmm/spinbutton.h>
#include <gtkmm/window.h>

class HelloWorld : public Gtk::Window {
public:
    HelloWorld();
    virtual ~HelloWorld();

protected:
    static gboolean on_spinbutton_change(Gtk::SpinButton *spin);
    Gtk::SpinButton spinbutton;
};

HelloWorld::HelloWorld() {
    spinbutton.signal_output().connect(sigc::bind<Gtk::SpinButton*>(sigc::ptr_fun(&HelloWorld::on_spinbutton_change), spinbutton));
    add(spinbutton);
    spinbutton.show();
}

HelloWorld::~HelloWorld() {}

gboolean HelloWorld::on_spinbutton_change(Gtk::SpinButton *spin) {
    std::cout << "Hello World" << std::endl;
    return true;
}

int main (int argc, char *argv[]) {
    auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example");
    HelloWorld helloworld;
    return app->run(helloworld);
}

Solution

  • I must admit that I switched from gtkmm to Qt some years ago. Out of curiosity, I installed gtkmm 3 in my cygwin (I'm on Windows 10) to prepare a sample (and find out how rusty I've become concerning gtkmm).

    Considering the attempts of OP, I used multiple different signatures which can be used to achieve the same result.

    1. static gboolean Window::on_spinbtn_output_p(Gtk::SpinButton *pGtkSpinBtn);
      the corresponding connect():

      _gtkSpinBtn1.signal_output().connect(
        sigc::bind(
          sigc::ptr_fun(&Window::on_spinbtn_output_p),
          &_gtkSpinBtn1));
    2. static gboolean Window::on_spinbtn_output_r(Gtk::SpinButton &gtkSpinBtn);
      the corresponding connect():

      _gtkSpinBtn2.signal_output().connect(
        sigc::bind(
          sigc::ptr_fun(&Window::on_spinbtn_output_r),
          sigc::ref(_gtkSpinBtn2)));
    3. gboolean Window::on_spinbtn3_output() (non-static)
      the corresponding connect():

      _gtkSpinBtn3.signal_output().connect(
        sigc::mem_fun(this, &Window::on_spinbtn3_output));

    The complete sample testGtkSpinBtnSig.cc:

    #include <iostream>
    #include <gtkmm/application.h>
    #include <gtkmm/box.h>
    #include <gtkmm/spinbutton.h>
    #include <gtkmm/window.h>
    
    class Window: public Gtk::Window {
    
      private:
        Gtk::VBox _gtkVBox;
        Gtk::SpinButton _gtkSpinBtn1;
        Gtk::SpinButton _gtkSpinBtn2;
        Gtk::SpinButton _gtkSpinBtn3;
    
      public:
        Window();
        virtual ~Window() = default;
        Window(const Window&) = delete;
        Window& operator=(const Window&) = delete;
    
      protected:
        static gboolean on_spinbtn_output_p(Gtk::SpinButton *pGtkSpinBtn);
        static gboolean on_spinbtn_output_r(Gtk::SpinButton &gtkSpinBtn);
        gboolean on_spinbtn3_output();
    };
    
    Window::Window(): Gtk::Window()
    {
      _gtkSpinBtn1.set_range(0.0, 10.0); _gtkSpinBtn1.set_value(1.0);
      _gtkVBox.pack_start(_gtkSpinBtn1);
      _gtkSpinBtn2.set_range(0.0, 10.0); _gtkSpinBtn2.set_value(2.0);
      _gtkVBox.pack_start(_gtkSpinBtn2);
      _gtkSpinBtn3.set_range(0.0, 10.0); _gtkSpinBtn3.set_value(3.0);
      _gtkVBox.pack_start(_gtkSpinBtn3);
      add(_gtkVBox);
      _gtkVBox.show_all();
      // install signal handlers
      _gtkSpinBtn1.signal_output().connect(
        sigc::bind(
          sigc::ptr_fun(&Window::on_spinbtn_output_p),
          &_gtkSpinBtn1));
      _gtkSpinBtn2.signal_output().connect(
        sigc::bind(
          sigc::ptr_fun(&Window::on_spinbtn_output_r),
          sigc::ref(_gtkSpinBtn2)));
      _gtkSpinBtn3.signal_output().connect(
        sigc::mem_fun(this, &Window::on_spinbtn3_output));
    }
    
    gboolean Window::on_spinbtn_output_p(Gtk::SpinButton *pGtkSpinBtn)
    {
      std::cout << "Window::on_spinbtn_output_p(): pGtkSpinBtn->get_value(): "
        << pGtkSpinBtn->get_value() << '\n';
      return true;
    }
    
    gboolean Window::on_spinbtn_output_r(Gtk::SpinButton &gtkSpinBtn)
    {
      std::cout << "Window::on_spinbtn_output_r(): gtkSpinBtn.get_value(): "
        << gtkSpinBtn.get_value() << '\n';
      return true;
    }
    
    gboolean Window::on_spinbtn3_output()
    {
      std::cout << "Window::on_spinbtn_output(): _gtkSpinBtn3.get_value(): "
        << _gtkSpinBtn3.get_value() << '\n';
      return true;
    }
    
    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create(argc, argv, "Test SpinButton Signals");
      Window gtkWin;
      return app->run(gtkWin);
    }
    

    Compiled and tested:

    $ g++ testGtkSpinBtnSig.cc -Wall $(pkg-config gtkmm-3.0 --cflags --libs) -o testGtkSpinBtnSig
    
    $ ./testGtkSpinBtnSig
    Window::on_spinbtn_output_p(): pGtkSpinBtn->get_value(): 1
    Window::on_spinbtn_output_r(): gtkSpinBtn.get_value(): 2
    Window::on_spinbtn_output(): _gtkSpinBtn3.get_value(): 3
    

    Snapshot of testGtkSpinBtnSig


    Remembering, that OP actually wants to modify the formatting of the GtkSpinButton text another idea came into mind.

    A notable extension of the gtkmm binding (in comparison to GTK+) is the fact that all the GTK+ widget class signals are provided as virtual methods. (I miss this feature very much in Qt where you have either virtual methods or signals but (IMHO) never both of them.) Practically, this means, in gtkmm you always have the option

    • to make a derived widget class with enhanced capabilities (overloading the virtual methods for certain signals) or
    • to modify the behavior of an individual instance (by connecting signal handlers).

    Beside of this, it is, of course, also possible to derive a widget class which connects a method of its own to a signal inherited from the base class. (In gtkmm 2.4, I had to do this in the rare cases where the virtual methods were missing for GTK+ signals.)

    So, I modified the above sample adding a derived SpinButton and changing the signal callbacks to format the spin button text.

    testGtkSpinBtnSig.cc:

    #include <sstream>
    #include <iomanip>
    #include <gtkmm/application.h>
    #include <gtkmm/box.h>
    #include <gtkmm/spinbutton.h>
    #include <gtkmm/window.h>
    
    std::string format(double value)
    {
      std::ostringstream out;
      out << std::fixed << std::setw(4) << std::setprecision(1) << std::setfill('0')
        << value;
      return out.str();
    }
    
    class SpinButton: public Gtk::SpinButton {
      public:
        SpinButton (double climb_rate = 0.0, guint digits = 0):
          Gtk::SpinButton(climb_rate, digits)
        { }
        virtual ~SpinButton() = default;
        SpinButton(const SpinButton&) = delete;
        SpinButton& operator=(const SpinButton&) = delete;
    
      protected:
        virtual bool on_output() override;
    };
    
    bool SpinButton::on_output()
    {
      const double value = get_value();
      set_text(format(value));
      return true;
    }
    
    class Window: public Gtk::Window {
    
      private:
        Gtk::VBox _gtkVBox;
        Gtk::SpinButton _gtkSpinBtn1;
        Gtk::SpinButton _gtkSpinBtn2;
        Gtk::SpinButton _gtkSpinBtn3;
        SpinButton _gtkSpinBtn4; // derived SpinButton
    
      public:
        Window();
        virtual ~Window() = default;
        Window(const Window&) = delete;
        Window& operator=(const Window&) = delete;
    
      protected:
        static gboolean on_spinbtn_output_p(Gtk::SpinButton *pGtkSpinBtn);
        static gboolean on_spinbtn_output_r(Gtk::SpinButton &gtkSpinBtn);
        gboolean on_spinbtn3_output();
    };
    
    Window::Window(): Gtk::Window()
    {
      _gtkSpinBtn1.set_range(0.0, 10.0); _gtkSpinBtn1.set_value(1.0);
      _gtkVBox.pack_start(_gtkSpinBtn1);
      _gtkSpinBtn2.set_range(0.0, 10.0); _gtkSpinBtn2.set_value(2.0);
      _gtkVBox.pack_start(_gtkSpinBtn2);
      _gtkSpinBtn3.set_range(0.0, 10.0); _gtkSpinBtn3.set_value(3.0);
      _gtkVBox.pack_start(_gtkSpinBtn3);
      _gtkSpinBtn4.set_range(0.0, 10.0); _gtkSpinBtn4.set_value(4.0);
      _gtkVBox.pack_start(_gtkSpinBtn4);
      add(_gtkVBox);
      _gtkVBox.show_all();
      // install signal handlers
      _gtkSpinBtn1.signal_output().connect(
        sigc::bind(
          sigc::ptr_fun(&Window::on_spinbtn_output_p),
          &_gtkSpinBtn1));
      _gtkSpinBtn2.signal_output().connect(
        sigc::bind(
          sigc::ptr_fun(&Window::on_spinbtn_output_r),
          sigc::ref(_gtkSpinBtn2)));
      _gtkSpinBtn3.signal_output().connect(
        sigc::mem_fun(this, &Window::on_spinbtn3_output));
    }
    
    gboolean Window::on_spinbtn_output_p(Gtk::SpinButton *pGtkSpinBtn)
    {
      pGtkSpinBtn->set_text(format(pGtkSpinBtn->get_value()));
      return true;
    }
    
    gboolean Window::on_spinbtn_output_r(Gtk::SpinButton &gtkSpinBtn)
    {
      gtkSpinBtn.set_text(format(gtkSpinBtn.get_value()));
      return true;
    }
    
    gboolean Window::on_spinbtn3_output()
    {
      _gtkSpinBtn3.set_text(format(_gtkSpinBtn3.get_value()));
      return true;
    }
    
    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create(argc, argv, "Test SpinButton Signals");
      Window gtkWin;
      return app->run(gtkWin);
    }
    

    Compiled and tested:

    $ g++ testGtkSpinBtnSig.cc -Wall $(pkg-config gtkmm-3.0 --cflags --libs) -o testGtkSpinBtnSig
    

    Snapshot of testGtkSpinBtnSig (2nd version)