2
votes

I am currently trying to learn GTK+3/cairo using C. I have written a small application which draws a gauge face and needle on a gtk drawing area using cairo.

This works well so far, i have tried creating multiple drawing areas (multiple gauges) which all have the same callback function for the draw event, this also works well with static values.

Now my question, i would like to be able to draw multiple gauges each with their own values. But how do i know in my callback function which gauge (drawing area) emitted the draw signal? I guess this also includes how do i "create and store" data for the gauges so their properties can be fetched from within the draw callback.

I should probably create a struct that holds the data for a gauge, and create multiple of those. That's what i have gathered by looking at various projects that use GTK, but it's too complicated for me to fully understand how it works.

This is how i create the drawing area(s):

gaugearea1 = gtk_drawing_area_new();
gtk_box_pack_start(GTK_BOX(hbox), gaugearea1, FALSE, FALSE, 5);
gtk_widget_set_size_request(gaugearea1, 300, 300);
gtk_widget_realize(gaugearea1);
g_signal_connect(gaugearea1, "draw", G_CALLBACK(draw_event), NULL);

And the callback function is written like this, pretty standard.

static gboolean draw_event(GtkWidget *widget, cairo_t *cr)
{
    GdkWindow *win;
    win = gtk_widget_get_window(widget);

    // Draw all arcs/lines using cr
}

Any tips on how to approach something like this would be greatly appreciated.

1

1 Answers

5
votes

The GtkDrawingArea that emits the signal is simply the widget you receive in your callback. Just cast it to the appropriate type, if needed.

But look carefully at the docs from the draw signal:

The "draw" signal

gboolean     user_function                  (GtkWidget    *widget,
                                            CairoContext *cr,
                                            gpointer      user_data) : Run Last

Your draw_event function is missing the last parameter, the gpointer user_data.

And that's the use of the last NULL parameter of the call to g_signal_connect(). So you can put here a pointer to a struct with all the data you need.

Or you can use the g_object_set_data() function to attach a pointer to the widget, but I wouldn't recommend that for such a simple application.

If you have a fixed number of gauges, all is well: just create the same number of structs, but if your number of gauges is dynamic, you have to create the structs in the head, so a new problem arises: when do you free the data? The answer is in g_signal_connect_data(): this function receives an additional callback that is called when the struct is no longer needed.

Something like the follwoing:

struct GaugeData
{
    /* your data here */
};
static void gauge_data_free(gpointer ptr, GClosure *clo)
{
    struct GaugeData *data = ptr;
    /* free extra resources, if needed */
    g_free(data);
}
static gboolean draw_event(GtkWidget *widget, cairo_t *cr, gpointer ptr)
{
    struct GaugeData *data = ptr;

    // Draw all arcs/lines using cr and data
}

void CreateOneGauge()
{
    gaugearea1 = gtk_drawing_area_new();
    struct GaugeData *data = g_new(GaugeData, 1);
    /* init the data */

    /* ... */
    g_signal_connect_data(gaugearea1, "draw", G_CALLBACK(draw_event), 
                data, gauge_data_free, 0);
}