Draw event function not getting executed

Please refer to the snippet below.

//gcc `pkg-config --cflags --libs gtk4` multiple_graphs.c -lcairo -lgtk-4 -lglib-2.0 -lgio-2.0 -lgobject-2.0 && ./a.out

#include<gtk/gtk.h>

const int graph_height = 200;
const int graph_width = 300;
const int num_of_graphs = 3;

struct node
{
	int position;
	int execute_count;
	GtkWidget *button;
	GtkWidget *box;
	GtkWidget *drawing_area;
	cairo_surface_t *surface;
	cairo_t *cr;
};

void on_draw_event( GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer param )
{
	struct node * const param_node = param;
	const GdkRGBA rgba =
	{
		.red   = ((float)(random()%101))/100,
		.green = ((float)(random()%101))/100,
		.blue  = ((float)(random()%101))/100,
		.alpha = 1,
	};

	const int x1 = random()%width;
	const int x2 = random()%width;
	const int y1 = random()%height;
	const int y2 = random()%height;
	gdk_cairo_set_source_rgba( param_node->cr, &rgba );
	cairo_move_to( param_node->cr, x1, y1 );
	cairo_line_to( param_node->cr, x2, y2 );
	cairo_stroke( param_node->cr );
	cairo_set_source_surface( cr, param_node->surface, 0, 0 );
	cairo_paint( cr );
	param_node->execute_count++;
}

gboolean draw_graphs( gpointer param )
{
	struct node *all_nodes = param;
	for( int i=0; i<num_of_graphs; i++ )
	{
		gtk_widget_queue_draw( all_nodes[i].drawing_area );
	}
	return G_SOURCE_CONTINUE;
}

struct node * prv_node;

static gboolean button_clicked( GtkButton *button, gpointer param )
{
	struct node * const param_node = param;
	gtk_widget_set_visible( prv_node->box, false );
	prv_node = param_node;
	gtk_widget_set_visible( param_node->box, true );
	return G_SOURCE_CONTINUE;
}

static gboolean close_request_received( GtkWindow *main_window, gpointer param )
{
	struct node *all_nodes = param;
	for( int i=0; i<num_of_graphs; i++ )
	{
		printf( "Graph %d count: %d\n", i, all_nodes[i].execute_count );
	}
	gtk_window_destroy( main_window );
}

static void activate( GtkApplication *app, gpointer argv )
{
	GtkWidget *main_window = gtk_application_window_new( app );
	GtkWidget *main_vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 5 );
	gtk_window_set_child( (GtkWindow*)main_window, main_vbox );
	GtkWidget *button_hbox = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 5 );
	gtk_box_append( (GtkBox*)main_vbox, button_hbox );
	struct node *all_nodes = calloc( sizeof(struct node), num_of_graphs );
	if( g_signal_connect( main_window, "close-request", G_CALLBACK(close_request_received), all_nodes ) <= 0 )
	{
		fprintf( stderr, "%s %d\n", __func__, __LINE__ );
		exit( EXIT_FAILURE );
	}
	char button_name[20];
	for( int i=0; i<num_of_graphs; i++ )
	{
		all_nodes[i].position = i;
		sprintf( button_name, "Graph %d", i );
		all_nodes[i].button = gtk_button_new_with_label( button_name );
		gtk_box_append( (GtkBox*)button_hbox, all_nodes[i].button );
		if( g_signal_connect( G_OBJECT(all_nodes[i].button), "clicked", G_CALLBACK( button_clicked ), &all_nodes[i] ) <= 0 )
		{
			fprintf( stderr, "%s %d\n", __func__, __LINE__ );
			exit( EXIT_FAILURE );
		}
		all_nodes[i].box = gtk_box_new( GTK_ORIENTATION_VERTICAL, 5 );
		all_nodes[i].drawing_area = gtk_drawing_area_new();
		gtk_box_append( (GtkBox*)all_nodes[i].box, all_nodes[i].drawing_area );
		gtk_box_append( (GtkBox*)main_vbox, all_nodes[i].box );
		gtk_widget_set_size_request( all_nodes[i].drawing_area, graph_width, graph_height );
		all_nodes[i].surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, graph_width, graph_height );
		all_nodes[i].cr = cairo_create( all_nodes[i].surface );
		gtk_drawing_area_set_draw_func( (GtkDrawingArea*)all_nodes[i].drawing_area, on_draw_event, &all_nodes[i], NULL );
	}
	prv_node = &all_nodes[0];
	gtk_widget_set_visible( main_window, true );
	for( int i=0; i<num_of_graphs; i++ )
	{
		gtk_widget_set_visible( all_nodes[i].box, false );
	}
	gtk_widget_set_visible( prv_node->box, true );
	g_timeout_add_seconds( 5, draw_graphs, all_nodes );
}

int main( const int argc, char * argv[] )
{
	GtkApplication * const app = gtk_application_new( "TrueTraffik.Gui", 0 );
	if( g_signal_connect( app, "activate", G_CALLBACK( activate ), argv ) <= 0 )
	{
		fprintf( stderr, "%s %d\n", __func__, __LINE__ );
		exit( EXIT_FAILURE );
	}
	const int status = g_application_run( G_APPLICATION( app ), 0, NULL );
	g_object_unref( app );

	return status;
}

result I am seeing after closing the window is

Graph 0 count: 3
Graph 1 count: 2
Graph 2 count: 1

Because of certain constraints, I can display only one graph at any given time.
May be because of this reason, on_draw_event function is being executed only if visibility is true.

Would like to understand if there is a way to ensure the execution of on_draw_event irrespective of visibility status.

Hi,

as far as I know, the draw callback won’t be called if the widget is hidden, or the window minimized, etc…

If you need to perform some actions other than drawing at specific times, then don’t rely on the draw callback, and use a g_timeout_add instead (or a tick callback for e.g. fast animations).

//gcc `pkg-config --cflags gtk4` multiple_graphs_experiment.c `pkg-config --libs gtk4` && ./a.out

#include<gtk/gtk.h>

const int graph_height = 200;
const int graph_width = 300;
const int num_of_graphs = 3;

struct node
{
	int position;
	int execute_count;
	GtkWidget *button;
	GtkWidget *box;
	GtkWidget *drawing_area;
	cairo_surface_t *surface;
	cairo_t *cr;
};

gboolean draw_graphs( gpointer param )
{
	struct node *all_nodes = param;
	for( int i=0; i<num_of_graphs; i++ )
	{
		const GdkRGBA rgba =
		{
			.red   = ((float)(random()%101))/100,
			.green = ((float)(random()%101))/100,
			.blue  = ((float)(random()%101))/100,
			.alpha = 1,
		};

		const int x1 = random()%graph_width;
		const int x2 = random()%graph_width;
		const int y1 = random()%graph_height;
		const int y2 = random()%graph_height;
		cairo_move_to( all_nodes[i].cr, x1, y1 );
		cairo_line_to( all_nodes[i].cr, x2, y2 );
		cairo_stroke( all_nodes[i].cr );
		cairo_paint( all_nodes[i].cr );
		all_nodes[i].execute_count++;
	}
	return G_SOURCE_CONTINUE;
}

struct node * prv_node;

static gboolean button_clicked( GtkButton *button, gpointer param )
{
	struct node * const param_node = param;
	gtk_widget_set_visible( prv_node->box, false );
	prv_node = param_node;
	gtk_widget_set_visible( param_node->box, true );
	return G_SOURCE_CONTINUE;
}

static gboolean close_request_received( GtkWindow *main_window, gpointer param )
{
	struct node *all_nodes = param;
	for( int i=0; i<num_of_graphs; i++ )
	{
		printf( "Graph %d count: %d\n", i, all_nodes[i].execute_count );
		char file_name[100];
		sprintf( file_name, "image_%d.png", i );
		cairo_surface_write_to_png( all_nodes[i].surface, file_name );
	}
	gtk_window_destroy( main_window );
}

static void activate( GtkApplication *app, gpointer argv )
{
	GtkWidget *main_window = gtk_application_window_new( app );
	GtkWidget *main_vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 5 );
	gtk_window_set_child( (GtkWindow*)main_window, main_vbox );
	GtkWidget *button_hbox = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 5 );
	gtk_box_append( (GtkBox*)main_vbox, button_hbox );
	struct node *all_nodes = calloc( sizeof(struct node), num_of_graphs );
	if( g_signal_connect( main_window, "close-request", G_CALLBACK(close_request_received), all_nodes ) <= 0 )
	{
		fprintf( stderr, "%s %d\n", __func__, __LINE__ );
		exit( EXIT_FAILURE );
	}
	char button_name[20];
	for( int i=0; i<num_of_graphs; i++ )
	{
		all_nodes[i].position = i;
		sprintf( button_name, "Graph %d", i );
		all_nodes[i].button = gtk_button_new_with_label( button_name );
		gtk_box_append( (GtkBox*)button_hbox, all_nodes[i].button );
		if( g_signal_connect( G_OBJECT(all_nodes[i].button), "clicked", G_CALLBACK( button_clicked ), &all_nodes[i] ) <= 0 )
		{
			fprintf( stderr, "%s %d\n", __func__, __LINE__ );
			exit( EXIT_FAILURE );
		}
		all_nodes[i].box = gtk_box_new( GTK_ORIENTATION_VERTICAL, 5 );
		all_nodes[i].drawing_area = gtk_drawing_area_new();
		gtk_box_append( (GtkBox*)all_nodes[i].box, all_nodes[i].drawing_area );
		gtk_box_append( (GtkBox*)main_vbox, all_nodes[i].box );
		gtk_widget_set_size_request( all_nodes[i].drawing_area, graph_width, graph_height );
		all_nodes[i].surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, graph_width, graph_height );
		all_nodes[i].cr = cairo_create( all_nodes[i].surface );
	}
	prv_node = &all_nodes[0];
	gtk_widget_set_visible( main_window, true );
	for( int i=0; i<num_of_graphs; i++ )
	{
		gtk_widget_set_visible( all_nodes[i].box, false );
	}
	gtk_widget_set_visible( prv_node->box, true );
	g_timeout_add_seconds( 1, draw_graphs, all_nodes );
}

int main( const int argc, char * argv[] )
{
	GtkApplication * const app = gtk_application_new( "TrueTraffik.Gui", 0 );
	if( g_signal_connect( app, "activate", G_CALLBACK( activate ), argv ) <= 0 )
	{
		fprintf( stderr, "%s %d\n", __func__, __LINE__ );
		exit( EXIT_FAILURE );
	}
	const int status = g_application_run( G_APPLICATION( app ), 0, NULL );
	g_object_unref( app );

	return status;
}

I’ve moved the code to g_timeout_add_seconds. But I’m seeing a blank instead of any image.
Could you please let me know how to link the drawing_area and the surface ?

You must setup a drawing function with gtk_drawing_area_set_draw_func (), otherwise the drawing area will stay blank. That’s how you get the surface for drawing.

I don’t understand why you need to call on_draw_event also when the widget is not visible…
Can you please explain the usecase?

In the scenario I’m working, number of drawing areas will be decided by end user dynamically.

He can click for new graphs as many as he want and then he will start using the solution by pressing start button. During this time, we will start plotting various values as graphs on these drawing areas.

After sometime, user will click stop button which will stop the plotting as well.

once stop button is clicked, no data will be available.

Because of the screen size constraints, we will be able to display only one graph at a time. However, all the graphs has to be plotted and once stop button is clicked, all the surfaces has to be saved as png files.

In this case, you can use the same on_draw_event for both displaying and saving to png.

First, remove the line:
cairo_set_source_surface( cr, param_node->surface, 0, 0 );
There is no need to update the image surface from there.

Use the gtk_drawing_area_set_draw_func () to let the drawingarea display its content.
No need to take care of the invisible ones.

Then, at the end, when you want to save to png, use again on_draw_event, but instead of using the context of the drawingarea, pass the one of the image surface:

surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, graph_width, graph_height );
context = cairo_create (surface);
on_draw_event (NULL, context, graph_width, graph_height);
cairo_surface_write_to_png (*surface, "output.png");

This topic was automatically closed 45 days after the last reply. New replies are no longer allowed.