3

I have been able to do the following:

  for (int i = 0; i < NUM_LEDS; ++i) {
    ledoff = gtk_image_new_from_file("./ledoff.png");
      leds[i].pos=ledpos[i];
      gtk_layout_put(GTK_LAYOUT(layout), ledoff, leds[i].pos.x, leds[i].pos.y);
      leds[i].status=OFF;
  }

Basically, this loads a bunch of "ledoff" images onto some window.

What I need is to change the image ledoff to ledon every time I click on leds[i].pos.x, leds[i].pos.y. At the beginning I thought it was just a matter of loading a new image and replacing the previous one, but then since this will be done thousands of times, I thought I was "malloc'ing" one new file everytime I do gtk_image_new_from_file! Is this true? Or am I just replacing the file and not adding a new one?

halfer
  • 19,824
  • 17
  • 99
  • 186
Dervin Thunk
  • 19,515
  • 28
  • 127
  • 217
  • 1
    If you truly "don't know anything about GTK," it might make more sense to build your UI using a toolkit you're more familiar with. – Sean Bright Jan 16 '13 at 17:54
  • 1
    I'm learning quickly. It would be nice if someone could answer the question, instead of telling me what to in their opinion with my time. But there, now I know about GTK, just not enough to do what I need. – Dervin Thunk Jan 16 '13 at 17:59
  • I'm glad to hear that you are learning quickly. Best of luck to you. – Sean Bright Jan 16 '13 at 18:02
  • If you really have thousands of these, you might consider drawing the images directly on the GtkLayout instead of adding them as widgets. That way you can load the on and off images once and draw them as necessary. – Sean Bright Jan 16 '13 at 18:42
  • @SeanBright: I just want an example of how I can click, say, a png of a red circle (which I have drawn and positioned in the layout), and turn into say a png blue circle. Thousands of times. It would be like a toggle button, without the button. I need an example of how to do that: from you or from the web, that's all. – Dervin Thunk Jan 16 '13 at 19:06
  • I added some runnable example code below. – Sean Bright Jan 16 '13 at 20:39

3 Answers3

4

Here is a working example that creates a 50x50 "LED" array in a window and allows you to toggle their status by clicking on them. It's not really that efficient, and as I pointed out in comments you're better of drawing the images yourself right onto the GtkLayout, but this will at least serve as a proof of concept.

Edit: I've updated the sample code to take into account liberforce's suggestions, which make things cleaner and more memory efficient.

#include <gtk/gtk.h>

#define ICON_WIDTH 16
#define ICON_HEIGHT 16
#define NUM_LEDS 2500

typedef enum {
    ON,
    OFF
} led_status;

typedef struct {
    GtkWidget *img;
    struct {
        gint x;
        gint y;
    } pos;
    led_status status;
} led;

static led leds[NUM_LEDS];
static GdkPixbuf *led_on;
static GdkPixbuf *led_off;

static gboolean click_handler(GtkWidget *widget,
                              GdkEvent *event,
                              gpointer user_data)
{
    led *info = user_data;

    if (info->status == ON) {
        gtk_image_set_from_pixbuf(GTK_IMAGE(info->img), led_off);
        info->status = OFF;
    } else {
        gtk_image_set_from_pixbuf(GTK_IMAGE(info->img), led_on);
        info->status = ON;
    }

    return TRUE;
}

int main(int argc, char** argv)
{
    GtkWidget *window, *layout;
    int i = 0, x, y;

    gtk_init(&argc, &argv);

    /* Load our images (ignoring errors - as any good sample code would) */
    led_on  = gdk_pixbuf_new_from_file("led-on.png", NULL);
    led_off = gdk_pixbuf_new_from_file("led-off.png", NULL);

    /* Initialize our array */
    for (x = 0; x < 50; x++) {
        for (y = 0; y < 50; y++) {
            leds[i].img = gtk_image_new();
            leds[i].pos.x = x * ICON_WIDTH;
            leds[i].pos.y = y * ICON_HEIGHT;
            leds[i].status = OFF;

            /* Initialize our image from the pixbuf we've already loaded */
            gtk_image_set_from_pixbuf(GTK_IMAGE(leds[i].img), led_off);
            i++;
        }
    }

    /* Create a window */
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "LEDs");
    gtk_signal_connect(GTK_OBJECT(window),
                       "destroy",
                       G_CALLBACK(gtk_main_quit),
                       NULL);

    /* Create the widget */
    layout = gtk_layout_new(NULL, NULL);

    for (i = 0; i < NUM_LEDS; i++) {
        /*
         * A GtkImage doesn't have a window, so we need to put it inside
         * a GtkEventBox so we can capture events.
         */
        GtkWidget *eb = gtk_event_box_new();
        g_signal_connect(G_OBJECT(eb),
                         "button_press_event",
                         G_CALLBACK(click_handler),
                         &leds[i]);
        gtk_container_add(GTK_CONTAINER(eb), leds[i].img);
        gtk_layout_put(GTK_LAYOUT(layout), eb, leds[i].pos.x, leds[i].pos.y);
    }

    gtk_container_add(GTK_CONTAINER(window), layout);
    gtk_widget_show_all(window);

    gtk_main();

    return 0;
}
Community
  • 1
  • 1
Sean Bright
  • 118,630
  • 17
  • 138
  • 146
  • 1
    Several comments: use gtk_image_new + gdk_pixbuf_new_from_file + gtk_image_set_from_pixbuf to initialize the image widgets. Calling g_object_ref on them is useless I think they have a floating reference, and once added in the GtkLayout, they will be ref'ed. Don't call g_object_set_data to create an "info" property, just pass the &leds[i] as the last parameter (the user_data) of g_signal_connect for the "button_press-event", so you get it in the callback. – liberforce Jan 17 '13 at 09:51
  • Good notes, thanks. The only place I disagree is with the call to `g_object_ref()`. In my test environment, without the `g_object_ref()`, when the image is removed from the `GtkEventBox` it is getting disposed, so the second click on an "LED" results in an assertion. – Sean Bright Jan 17 '13 at 11:39
  • That being said, if instead of removing the widget we just update it with `gtk_image_set_from_pixbuf()`, the `g_object_ref()` becomes unnecessary. I'll update my answer with your suggestions. Thanks. – Sean Bright Jan 17 '13 at 11:54
2

One solution is to create one GtkImage for each position where there will be a led. Don't use gtk_image_new_from_file, as it will load each time the image file. Instead:

  • call gdk_pixbuf_new_from_file for each of your 2 image files
  • call gtk_image_new to create each image widget, and immediately initialize them with the right pixel buffer using gtk_image_set_from_pixbuf
  • when you need to change the image displayed, just get the corresponding previously created GtkImage, and change the image displayed with gtk_image_set_from_pixbuf

This ensures low memory consumption: you only have 2 pixel buffers that are allocated (and reference counted from the GtkImage instances), and you only create one GtkImage per led (instead of destroying/creating one each time you change the image displayed).

EDIT: here's an improved of Sean Bright's submission, in which I fixed a few mistakes.

#include <gtk/gtk.h>

#define MAX_LEDS_PER_LINE 50
#define NUM_LEDS 2500

static GdkPixbuf * led_on;
static GdkPixbuf * led_off;

static gboolean click_handler(GtkWidget *widget,
        GdkEvent *event,
        gpointer user_data)
{
    gboolean *is_led_on = user_data;
    GList * children = gtk_container_get_children (GTK_CONTAINER (widget));

    *is_led_on = ! *is_led_on; /* invert led state */
    gtk_image_set_from_pixbuf (GTK_IMAGE(children->data), (*is_led_on) ? led_on : led_off);
    g_list_free (children);
    return TRUE; /* stop event propagation */
}

int main(int argc, char** argv)
{
    GtkWidget *window, *table;
    gboolean leds[NUM_LEDS];
    int i = 0;

    gtk_init(&argc, &argv);

    /* Create a window */
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "LEDs");
    gtk_signal_connect(GTK_OBJECT(window),
            "destroy",
            G_CALLBACK(gtk_main_quit),
            NULL);

    /* Load leds on/off images */
    led_on = gdk_pixbuf_new_from_file ("on.png", NULL);
    led_off = gdk_pixbuf_new_from_file ("off.png", NULL);

    /* Create the container */
    int n_rows = (NUM_LEDS / MAX_LEDS_PER_LINE) + 1;
    int n_cols = (NUM_LEDS / MAX_LEDS_PER_LINE) + 1;
    table = gtk_table_new (n_rows, n_cols, FALSE);

    /* Create the leds */
    for (i = 0; i < NUM_LEDS; i++)
    {
        leds[i] = FALSE; /* FALSE means OFF, TRUE means ON */

        /*
         * A GtkImage doesn't have a window, so we need to put it inside
         * a GtkEventBox so we can capture events.
         */
        GtkWidget *image = gtk_image_new ();
        gtk_image_set_from_pixbuf (GTK_IMAGE(image), led_off);
        GtkWidget *eb = gtk_event_box_new();
        g_signal_connect(G_OBJECT(eb),
                "button-press-event",
                G_CALLBACK(click_handler),
                &leds[i]);
        gtk_container_add(GTK_CONTAINER(eb), image);
        int row = i / MAX_LEDS_PER_LINE;
        int col = i % MAX_LEDS_PER_LINE;
        gtk_table_attach (GTK_TABLE(table),
                eb,
                row, row + 1,
                col, col + 1,
                0,
                0,
                0,
                0);
    }

    gtk_container_add(GTK_CONTAINER(window), table);
    gtk_widget_show_all(window);

    gtk_main();

    return 0;
}

Here, I implemented my first remarks, and improved a few things:

  • I fixed the signature of the click_handler callback
  • I fixed the return value of the click_handler callback
  • I removed the structs, as we just need a boolean to know the led states. It could even have been a property stored in the GtkImage, but it's often a good idea to separate the backend logic from the user interface.
  • I used GtkTable instead of GtkLayout, because I saw no need for pixel-exact placement of the leds. In GTK 3, though, GtkTable is replaced by GtkGrid. GtkTable however had difficulties with gtk_table_add_defaults, because resizing the window caused recalculating the size of 2500 widgets, so I just used the most simple size calculation options.
liberforce
  • 11,189
  • 37
  • 48
0

I am not sure of this but you should place your 2 images in the same position on the screen and then show the one you need and hide the one you don't need. I don't know if it is a good way to do that but I am pretty sure that you are not allocing stuff using this way.

gtk_widget_hide(your_image2);
gtk_widget_show(your_image1);

Hope it helps.

Regards,

Joze
  • 1,285
  • 4
  • 19
  • 33