Search code examples
cgtk3cairo

Drawing to GTK/GDK drawing area with cairo


The mechanism for retrieving the cairo context in GDK/GTK is broken and/or not properly described in documentation. I am trying to draw in a GtkDrawingArea from a button press callback with no success. The documentation suggests doing

GdkWindow *window = gtk_widget_get_window( widget );
cairo_region_t *cairoRegion = cairo_region_create();
GdkDrawingContext *drawingContext = gdk_window_begin_draw_frame( window, cairoRegion );
cairo_t *cr = gdk_drawing_context_get_cairo_context( drawingContext );

and then drawing using the cairo context and then ending properly on

gdk_window_end_draw_frame( window, drawingContext );
cairo_region_destroy( cairoRegion );

All this seems to work on GTK 3.22.30 (and cairo 1.15.12), but not on GTK 3.24.30 (and cairo 1.16.0). Obviously something has been changed. Any idea? A full example follows:

// gcc -Wextra -o hauki hauki.c `pkg-config --cflags --libs gtk+-3.0`
#include <gtk/gtk.h>

gboolean draw_cb(GtkWidget *widget, cairo_t *cr, gpointer data)
{   
   // This works
   cairo_set_source_rgb(cr, 1, 1, 1);
   cairo_paint(cr);
   return FALSE;
}

gboolean button_cb( GtkWidget *widget, GdkEventButton *event, gpointer object )
{
    GdkWindow *window = gtk_widget_get_window( GTK_WIDGET(widget) );

    cairo_region_t *cairoRegion = cairo_region_create();
    GdkDrawingContext *drawingContext = gdk_window_begin_draw_frame( window, cairoRegion );
    cairo_t *cr = gdk_drawing_context_get_cairo_context( drawingContext );

    cairo_set_source_rgb(cr, 1, 0, 0);
    cairo_move_to(cr, 0, 0);
    cairo_line_to(cr, 300, 300);
    cairo_stroke(cr);
    
    gdk_window_end_draw_frame( window, drawingContext );
    cairo_region_destroy( cairoRegion );
}


int main (int argc, char *argv[])
{
   gtk_init( &argc, &argv );

   GtkWidget *window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
   g_signal_connect( window, "destroy", G_CALLBACK (gtk_main_quit), NULL );

   GtkWidget *da = gtk_drawing_area_new();
   gtk_widget_add_events( da, GDK_BUTTON_PRESS_MASK );
   gtk_widget_set_size_request( da, 300, 300 );
   g_signal_connect( da, "draw", G_CALLBACK(draw_cb), NULL );
   g_signal_connect( da, "button_press_event", G_CALLBACK(button_cb), NULL );
   
   gtk_container_add( GTK_CONTAINER (window), da );
   gtk_widget_show( da );
   gtk_widget_show( window );

   gtk_main();

   return 0;
}

Solution

  • gdk_window_begin_draw_frame() is a low-level call which you should normaly only ever need if you're trying to draw something using GDK/Cairo without involving GTK. Since you're obviously using GTK, you shouldn't be using it.

    The way to do drawing with Cairo in GTK is by using a GtkDrawingArea (or if you want to implement very custom behaviour: subclassing GtkWidget and implementing your own draw() vfunc). As you already saw, it has a draw signal, which you can connect a callback to: that callback will be called each time the widget needs to redraw itself.

    In other words, your draw callback should change how it draws itself depending on a certain state:

    #include <gtk/gtk.h>
    
    gboolean button_clicked = FALSE;
    
    gboolean draw_cb(GtkWidget *widget, cairo_t *cr, gpointer data)
    {
        if (button_clicked) {
            cairo_set_source_rgb(cr, 1, 0, 0);
            cairo_move_to(cr, 0, 0);
            cairo_line_to(cr, 300, 300);
            cairo_stroke(cr);
        }
    
        return FALSE;
    }
    
    gboolean button_cb( GtkWidget *widget, GdkEventButton *event, gpointer object )
    {
        button_clicked = TRUE;
        // Explicitly notify the drawing area it should redraw itself
        gtk_widget_queue_draw (widget);
    }
    
    int main (int argc, char *argv[])
    {
       gtk_init( &argc, &argv );
    
       GtkWidget *window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
       g_signal_connect( window, "destroy", G_CALLBACK (gtk_main_quit), NULL );
    
       GtkWidget *da = gtk_drawing_area_new();
       gtk_widget_add_events( da, GDK_BUTTON_PRESS_MASK );
       gtk_widget_set_size_request( da, 300, 300 );
       g_signal_connect( da, "draw", G_CALLBACK(draw_cb), NULL );
       g_signal_connect( da, "button_press_event", G_CALLBACK(button_cb), NULL );
       
       gtk_container_add( GTK_CONTAINER (window), da );
       gtk_widget_show( da );
       gtk_widget_show( window );
    
       gtk_main();
    
       return 0;
    }