Search code examples
c++gtkcairogtkmm

How do I control when code executes in the on_draw event of gktmm3-Gtk::DrawingArea


I am using C++11 with GNU tool chain with gtkmm3, on Ubuntu 12.04 LTS 32 bit. I have been experimenting with some of the examples for gtkmm3 in Programming with gtkmm 3.

Based on 17.2.1.Example there, I inherited from Gtk::DrawingArea (MyDrawingArea here) and overrode the on_draw() event handler as follows:

MyDrawingArea.hpp

...

protected:

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

MyDrawingArea.cpp (quick and dirty, just for concept demo)

double y{ 10 };
double x{ 10 };

bool MyDrawingArea::on_draw( const Cairo::RefPtr<Cairo::Context>& cr )
{
        this->get_window( )->freeze_updates( );
        cr->set_line_width( 3.0 );
        cr->set_source_rgb( 1, 0, 0 );
        cr->move_to( x, y );
        cr->line_to( x * 2, y * 2 );
        cr->stroke( );
        this->get_window( )->thaw_updates( );
        x += 50;
        y += 50;
        return true;
 }

This code draws a single diagonal line. I want its position to change when I callMyDrawingArea.queue_draw() through an event handler in my application. This works:

Event 1: enter image description here Event 2: enter image description here

My problem is that, obviously, the on_draw() event handler fires every time the window is repainted. Simply moving the application's main window (which contains MyDrawingArea) causes on_draw() to fire and the line is rendered in a new position.

How do I control when the code in my on_draw() event will run, so that the line is rendered anew only when I call MyDrawingArea.queue_draw() in my application code, but is preserved in its previous state at other times? (I don't think I'm asking about how to prevent on_draw() from firing, but perhaps that's what has to happen?)

Simply setting a flag notifying that my event has made the call and only then running my code to render the line raises other problems, because when the flag is not set, I lose everything that was previously rendered.

This seems to be an impossible situation: Either the line is redrawn in a new position for every on_draw() event, or erased completely if I use a flag to invoke my drawing logic, and on_draw() fires when that flag isn't set.

How should I manage this? Do I need logic to manage two different on_draw() options: If the app makes the call, render the new version of the line, if not, repaint the old version? This gets complicated - I'm thinking I'm missing something. Is there a different event I should be using? Is the some way I can obtain from Cairo::Context information about who called On_draw(), etc that would help me negotiate this issue?


Solution

  • I solved this problem by writing a little state machine that controls how the on_draw() event behaves:

    Enum representing different signal states when on on_draw() fires:

    enum class Draw_Signals
    {
        DS_RedrawSignal, DS_HoldSignal, DS_ClearSignal, DS_End
    };
    
    DS_RedrawSignal 
    

    Means redraw with new additions/changes as per the most recent application activity.

    DS_HoldSignal 
    

    Means maintain the current graphics state but do nothing new.

    DS_ClearSignal 
    

    Means clear the drawing area entirely and start over.

    The MyDrawingArea class has a member:

    Draw_Signals mDSignal { Draw_Signals::DS_HoldSignal };

    This value is set by methods that triggeron_draw() from my application with a "setter" method.

    Without going into all the details, my MyDrawingArea::on_draw() now looks something like this:

    bool MyDrawingArea::on_draw( const Cairo::RefPtr<Cairo::Context>& cr )
        {
    
            get_window( )->freeze_updates( );
    
            switch ( mDSignal )
              {
                case Draw_Signals::DS_RedrawSignal:
                  {
                    DrawNewLine( cr );//draws new line and caches it for use when RedrawOldLine() is called.
                    break;
                  }
    
                case Draw_Signals::DS_HoldSignal:
                  {
                    RedrawOldLine( cr );
                    break;
                  }
    
                case Draw_Signals::DS_ClearSignal:
                  {
                    clearDrawArea( );
                  }
    
              }
    
            get_window( )->thaw_updates( );
    
            mDSignal = Draw_Signals::DS_HoldSignal;
    
            return true;
    
        }
    

    They key here is that the default state is always Draw_Signals::DS_HoldSignal, which simply redraws the previous content, and new drawing only occurs when explicitly signaled otherwise from the application.