Search code examples
cgtkcairo

How to get GTK Cairo to plot multiple times without a triggering event


I'm fairly new to GTK and Cairo, and I need to write code that will allow it to draw my data in a while(1) loop each time gtk_widget_queue_draw is called. Here's my attempt at it:

#include <cairo.h>
#include <gtk/gtk.h>
#include <unistd.h>

int scrH = 892,  // Window dimensions.
    scrW = 1427,
    type = 0;    // What kind of lines to draw.

int on_draw_event(GtkWidget *widget,
                  cairo_t *cr, 
                  gpointer user_data) {
  cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
  cairo_paint(cr);
  cairo_set_line_width(cr, 0.5);
  if (type == 0) {
    // Plot a few horizontal lines.
    for (int i = 0; i < scrH; i += 10) {
      cairo_set_source_rgb(cr, 0.2, 0.99, 0.2);
      cairo_move_to(cr, (double) 10, i);
      cairo_line_to(cr, (double) (scrW - 10.0), i);
      cairo_stroke(cr);
    }
  } else {
    // Plot a few vertical lines.
    for (int i = 0; i < scrW; i += 10) {
      cairo_set_source_rgb(cr, 0.99, 0.2, 0.99);
      cairo_move_to(cr, (double) i, 10.0);
      cairo_line_to(cr, (double) i, (scrW - 10.0));
      cairo_stroke(cr);
    }
  }

  return 0;
}

void clicked(GtkWidget *widget,
             GdkEventButton *event,
             gpointer user_data) {
  /* This one works, but is useless for my application.
  if (event->button == 1) {
    type = 0;
    gtk_widget_queue_draw(widget);
  } else if (event->button == 3) {
    gtk_widget_queue_draw(widget);
    type = 1;
  } //TEST */

  /* This one only plots once, not many times. How can I fix it?
  while (1) {
    if (++type == 2) { type = 0; }

    // This was a failed attempt to fix it. It too only plotted a single time.
    // gdk_threads_add_idle((GSourceFunc)gtk_widget_queue_draw, (void*) widget);
    gtk_widget_queue_draw(widget);
    sleep(1);
  } //TEST*/
}


int main(int argc, char *argv[]) {
  GtkWidget *window;
  GtkWidget *darea;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  darea = gtk_drawing_area_new();
  gtk_container_add(GTK_CONTAINER(window), darea);

  gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);

  g_signal_connect(G_OBJECT(darea), "draw", 
      G_CALLBACK(on_draw_event), NULL); 
  g_signal_connect(window, "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(window, "button-press-event", 
      G_CALLBACK(clicked), NULL);

  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), scrW, scrH); 
  gtk_window_set_title(GTK_WINDOW(window), "Lines");

  gtk_widget_show_all(window);

  gtk_main();
}

Sorry about the code length. Graphical environments tend to be sesquipedalian!

The code was compiled with:

gcc -g -o fj testGTK.c `pkg-config --cflags gtk+-3.0 --libs gtk+-3.0`

on a Debian Linux machine. Details? OK: uname -a:

Linux Sirius 4.19.0-14-amd64 #1 SMP Debian 4.19.171-2 (2021-01-30) x86_64 GNU/Linux


Solution

  • GTK uses an event loop. When you call gtk_main(), it starts this loop, which handles click events, drawing your window, etc. The idea is that the loop calls your functions whenever an event happens. Your function handles it, then returns control back to the main loop so it can handle more events.

    Your while(1) loop never returns control to the main loop, so no more events will ever be handled--including the draw event you queue inside the loop. (gtk_widget_queue_draw() doesn't redraw the widget immediately; it schedules a redraw for the next loop).

    To fix this, instead of a while(1) loop with a sleep() call, try using g_timeout_add(). This will call a function every interval milliseconds as part of that main loop.