jmgonet
jmgonet

Reputation: 1261

In GTKMM, on_draw method stops being called after invalidate occurs in separated thread

Using GTKMM, I'm extending the DrawingArea widget with the idea that an external process provides it with images. My CameraDrawingArea will then display the images at the right size using Cairo.

Each time an image arrives, I store it and I call the invalidate method, which eventually ends up in a call to on_draw, where I can resize and display the image.

My problem is the following:

To show it here, I've simplified the code so that there is nothing external to the class, and no link with other libraries. I've replaced the process providing the images by a method with for-loops, and the display of the image by printing a simple text in the middle of the widget area:

    #include "camera-drawing-area.hpp"

    #include <iostream>

    CameraDrawingArea::CameraDrawingArea():
    captureThread(nullptr) {
        fontDescription.set_family("Monospace");
        fontDescription.set_weight(Pango::WEIGHT_BOLD);
        fontDescription.set_size(30 * Pango::SCALE);

        keepCapturing = true;
        captureThread = new std::thread([this] { 
                doCapture(); 
                });
    }

    void CameraDrawingArea::doCapture() {
        while (keepCapturing) {
            float f = 0.0;
            for (int n = 0; n < 1000; n++) {
                for (int m = 0; m < 1000; m++) {
                    for (int o = 0; o < 500; o++) {
                        f += 1.2;
                    }
                }
            }
            std::cout << "doCapture - " << f << std::endl; 
            refreshDrawing();
        }
    }

    void CameraDrawingArea::refreshDrawing() {
        auto win = get_window();
        if (win) {
            win->invalidate(false);
            std::cout << "refreshDrawing" << std::endl; 
        }
    }

    bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
        std::cout << "on_draw" << std::endl; 

        static char buffer[50];
        static int n = 0;
        sprintf(buffer, "-%d-", n++);

        Gtk::Allocation allocation = get_allocation();
        const int width = allocation.get_width();
        const int height = allocation.get_height();


        auto layout = create_pango_layout(buffer);
        layout->set_font_description(fontDescription);

        int textWidth, textHeight;
        layout->get_pixel_size(textWidth, textHeight);
        cr->set_source_rgb(0.5, 0.2, 0.1);
        cr->move_to((width - textWidth) / 2, (height - textHeight) / 2);
        layout->show_in_cairo_context(cr);
        cr->stroke();

        return true;
    }

    CameraDrawingArea::~CameraDrawingArea() {
        keepCapturing = false;
        captureThread->join();
        free(captureThread);
    }

And this is my header file:

    #ifndef CAMERA_DRAWING_AREA_HPP
    #define CAMERA_DRAWING_AREA_HPP

    #include <gtkmm.h>
    #include <thread>

    class CameraDrawingArea : public Gtk::DrawingArea {
    public:
        CameraDrawingArea();
        virtual ~CameraDrawingArea();

    protected:
        bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;

    private:
        bool keepCapturing;
        void doCapture();
        void refreshDrawing();
        std::thread* captureThread;
        Pango::FontDescription fontDescription;
    };
    #endif

The problem manifests itself as follows:

Also, if I stop the application before it stops refreshing, then it ends up nicely. But, if I stop the application after it stops refreshing, then I see this message below (the ID value varies):

GLib-CRITICAL **: 10:05:04.716: Source ID 25 was not found when attempting to remove it

I'm quite sure that I do something wrong, but clueless about what. Any help would be appreciated.

I also checked the following questions, but they don't seem to be related with my case:

Upvotes: 0

Views: 755

Answers (2)

jmgonet
jmgonet

Reputation: 1261

Based on the answer form @ptomato, I've rewritten my example code. The golden rule is do not call GUI functions from another thread, but if you do, then acquire some specific GDK locks first. That's the purpose of Glib::Dispatcher :

If a Glib::Dispatcher object is constructed in the main GUI thread (which will therefore be the receiver thread), any worker thread can emit on it and have the connected slots safely execute gtkmm functions.

Based on that, I've added a new private member Glib::Dispatcher refreshDrawingDispatcher that will allow threads to safely the invalidate the windows area:

    #ifndef CAMERA_DRAWING_AREA_HPP
    #define CAMERA_DRAWING_AREA_HPP

    #include <gtkmm.h>
    #include <thread>

    class CameraDrawingArea :
    public Gtk::DrawingArea {
    public:
        CameraDrawingArea();
        virtual ~CameraDrawingArea();

    protected:
        bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;

    private:
        bool keepCapturing;
        void doCapture();
        void refreshDrawing();
        Glib::Dispatcher refreshDrawingDispatcher;
        std::thread* captureThread;
        Pango::FontDescription fontDescription;
    };
    #endif

Then, I've connected the dispatcher to the refreshDrawing method. I do this in the class constructor, which is called during GUI start up and therefore in the main GUI thread:

    CameraDrawingArea::CameraDrawingArea():
    refreshDrawingDispatcher(),
    captureThread(nullptr) {
        fontDescription.set_family("Monospace");
        fontDescription.set_weight(Pango::WEIGHT_BOLD);
        fontDescription.set_size(30 * Pango::SCALE);

        keepCapturing = true;
        captureThread = new std::thread([this] {
                doCapture(); 
                });

        refreshDrawingDispatcher.connect(sigc::mem_fun(*this, &CameraDrawingArea::refreshDrawing));
    }   

Finally, the thread has to call the dispatcher:

    void CameraDrawingArea::doCapture() {
        while (keepCapturing) {
            float f = 0.0;
            for (int n = 0; n < 1000; n++) {
                for (int m = 0; m < 1000; m++) {
                    for (int o = 0; o < 500; o++) {
                        f += 1.2;
                    }
                }
            }
            std::cout << "doCapture - " << f << std::endl; 
            refreshDrawingDispatcher.emit();
        }
    }

And now, this works without further problems.

Upvotes: 0

ptomato
ptomato

Reputation: 57920

You can't use GTK methods from any other thread than the one in which you started the GTK main loop. Probably the win->invalidate() call is causing things to go wrong here.

Instead, use Glib::Dispatcher to communicate with the main thread, or use gdk_threads_add_idle() for a more C-style solution.

Upvotes: 1

Related Questions