c++gtkmmgtkmm3

Catching a click signal on a Gtk::DrawingArea


I'm working on a speed-optimized fractal generator using Glade and C++ and I'm having some trouble getting my head around some gtkmm subtleties.

I'm trying to catch a click event from a Gtk::DrawingArea, i.e. call a function when clicking on it.

I'm unable to create a Gtk::EventBox object with the standard constructor. After much trial and error, I've managed to catch a click event from the main window:

#include <glibmm.h>
#include <gtkmm.h>
#include <cmath>
#include <iostream>

Gtk::Window* W_Main;
Gtk::DrawingArea* D_Fractal;

//Gtk::EventBox EB_CatchClick;

int AWidth;
int AHeight;
bool test;

gboolean on_D_Fractal_draw(const Cairo::RefPtr<Cairo::Context> cr) {
    AWidth = D_Fractal->get_width();
    AHeight = D_Fractal->get_height();
    
    //Draw background
    cr->set_source_rgb(1.0, 1.0, 1.0);
    if(test) {
        cr->set_source_rgb(0.0, 0.0, 0.0);
    }
    
    cr->rectangle(10, 10, AWidth-20, AHeight-20);
    cr->fill();
    
    return 0;
}

gboolean on_W_Main_button_press_event(GdkEventButton *event) {
    switch ( event->type )
    {
        case GDK_BUTTON_PRESS :
            test = true;
            std::cout << "test" << std::endl;
            D_Fractal->queue_draw();
            return TRUE;

        case GDK_BUTTON_RELEASE :
            return TRUE;

        default:
            return TRUE;
    }
    return 0;
}

int main(int argc, char** argv) {
    test = false;
    auto app = Gtk::Application::create(argc, argv, "org.myname.mandelbronatoor");
    auto builder = Gtk::Builder::create_from_file("Mandelbronatoor.glade");
    
    builder->get_widget("W_Main", W_Main);
    builder->get_widget("D_Fractal", D_Fractal);
    
    D_Fractal->signal_draw().connect(sigc::ptr_fun(on_D_Fractal_draw));
    W_Main->signal_button_press_event().connect(sigc::ptr_fun(on_W_Main_button_press_event));
    

    app->run(*W_Main);
}

But the mere inclusion of the line EB_CatchClick = Gtk::EventBox() into main(int argc, char** argv) along with the uncommenting of the respective declaration results in the following error.

Can't create a GtkStyleContext without a display connection

I have found this page, but the structure of their program differs significantly from that of mine so as to render any direct transfer unlikely to work. I'd be grateful for any suggestions.

EDIT: I've worked a bit on the problem, and now the code looks like this:

#include <glibmm.h>
#include <gtkmm.h>
#include <cmath>
#include <iostream>

Gtk::Window* W_Main;
Gtk::DrawingArea* D_Fractal;

Gtk::EventBox* EB_CatchClick;

int AWidth;
int AHeight;
bool test;

gboolean on_D_Fractal_draw(const Cairo::RefPtr<Cairo::Context> cr) {
    AWidth = D_Fractal->get_width();
    AHeight = D_Fractal->get_height();
    
    //Draw background
    cr->set_source_rgb(1.0, 1.0, 1.0);
    if(test) {
        cr->set_source_rgb(0.0, 0.0, 0.0);
    }
    
    cr->rectangle(10, 10, AWidth-20, AHeight-20);
    cr->fill();
    
    return 0;
}

gboolean on_D_Fractal_button_press_event(GdkEventButton *event) {
    switch ( event->type )
    {
        case GDK_BUTTON_PRESS :
            test = true;
            std::cout << "test" << std::endl;
            D_Fractal->queue_draw();
            return TRUE;

        case GDK_BUTTON_RELEASE :
            return TRUE;

        default:
            return TRUE;
    }
    return 0;
}

int main(int argc, char** argv) {
    test = false;
    auto app = Gtk::Application::create(argc, argv, "org.myname.mandelbronatoor");
    auto builder = Gtk::Builder::create_from_file("Mandelbronatoor.glade");
    
    builder->get_widget("W_Main", W_Main);
    builder->get_widget("D_Fractal", D_Fractal);
    
    EB_CatchClick = new Gtk::EventBox;
    EB_CatchClick->set_size_request(600,400);
    EB_CatchClick->add(*D_Fractal);
    
    D_Fractal->signal_draw().connect(sigc::ptr_fun(on_D_Fractal_draw));
    EB_CatchClick->signal_button_press_event().connect(sigc::ptr_fun(on_D_Fractal_button_press_event));

    app->run(*W_Main);
    
    delete EB_CatchClick;
}

Somehow, putting the Gtk::EventBox on the heap seemed to make it compile. Now, however, I get the following runtime error message:

Attempting to add a widget with type 
gtkmm__GtkDrawingArea to a container of type 
gtkmm__GtkEventBox, but the widget is already 
inside a container of type gtkmm__GtkWindow, 
please remove the widget from its existing 
container first.

Does this mean that I have to remove my Gtk::DrawingArea from my Gtk::Window, add it to the Gtk::EventBox and then add the Gtk::EventBox to the Gtk::Window? This seems like an awfully inelegant solution (if it works at all), and I'd like to hereby inquire about alternatives.

EDIT 2: A Glade file had been requested in the comments, so here it is:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface>
  <requires lib="gtk+" version="3.24"/>
  <object class="GtkWindow" id="W_Main">
    <property name="can-focus">False</property>
    <property name="title" translatable="yes">Mandelbronatoor</property>
    <property name="default-width">600</property>
    <property name="default-height">400</property>
    <signal name="button-press-event" handler="on_W_Main_button_press_event" swapped="no"/>
    <child>
      <!-- n-columns=1 n-rows=2 -->
      <object class="GtkGrid" id="G_Main">
        <property name="visible">True</property>
        <property name="can-focus">False</property>
        <property name="row-homogeneous">True</property>
        <child>
          <object class="GtkDrawingArea" id="D_Fractal">
            <property name="visible">True</property>
            <property name="can-focus">False</property>
            <property name="hexpand">True</property>
            <property name="vexpand">True</property>
          </object>
          <packing>
            <property name="left-attach">0</property>
            <property name="top-attach">0</property>
          </packing>
        </child>
        <child>
          <placeholder/>
        </child>
      </object>
    </child>
  </object>
</interface>

Solution

  • You can use the following main function:

    int main(int argc, char** argv) {
        test = false;
        auto app = Gtk::Application::create(argc, argv, "org.myname.mandelbronatoor");
        auto builder = Gtk::Builder::create_from_file("Mandelbronatoor.glade");
        
        builder->get_widget("W_Main", W_Main);
        builder->get_widget("EB_CatchClick", EB_CatchClick);
        builder->get_widget("D_Fractal", D_Fractal);
        
        D_Fractal->signal_draw().connect(sigc::ptr_fun(on_D_Fractal_draw));
        EB_CatchClick->signal_button_press_event().connect(sigc::ptr_fun(on_D_Fractal_button_press_event));
    
        return app->run(*W_Main);
    }
    

    With the following Glade file:

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- Generated with glade 3.38.2 -->
    <interface>
      <requires lib="gtk+" version="3.24"/>
      <object class="GtkWindow" id="W_Main">
        <property name="can-focus">False</property>
        <property name="title" translatable="yes">Mandelbronatoor</property>
        <property name="default-width">600</property>
        <property name="default-height">400</property>
        <signal name="button-press-event" handler="on_W_Main_button_press_event" swapped="no"/>
        <child>
          <!-- n-columns=1 n-rows=2 -->
          <object class="GtkGrid" id="G_Main">
            <property name="visible">True</property>
            <property name="can-focus">False</property>
            <property name="row-homogeneous">True</property>
            <child>
              <object class="GtkEventBox" id="EB_CatchClick">
                <property name="name">EB_CatchClick</property>
                <property name="visible">True</property>
                <property name="can-focus">False</property>
                <property name="events">GDK_BUTTON_PRESS_MASK</property>
                <child>
                  <object class="GtkDrawingArea" id="D_Fractal">
                    <property name="visible">True</property>
                    <property name="can-focus">False</property>
                    <property name="hexpand">True</property>
                    <property name="vexpand">True</property>
                  </object>
                </child>
              </object>
              <packing>
                <property name="left-attach">0</property>
                <property name="top-attach">0</property>
              </packing>
            </child>
            <child>
              <placeholder/>
            </child>
          </object>
        </child>
      </object>
    </interface>
    

    The event box wraps the drawing area and is a child of the grid, which is the layout of (therefore the child of) the window.

    In my opinion, the event box is not needed here since all your widgets are held within a window.