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.