+static GtkWidget *canvas;
+static GdkGC *bg_gc;
+
+static LBX_IMG *image;
+
+static GdkPixbuf *framebuf;
+
+void play_toggled(GtkToggleButton *button, gpointer data)
+{
+ gboolean active = gtk_toggle_button_get_active(button);
+ GtkWidget *spin;
+
+ if (active && !image) {
+ gtk_toggle_button_set_active(button, FALSE);
+ return;
+ }
+
+ spin = GTK_WIDGET(gtk_builder_get_object(builder, "framespin"));
+ gtk_widget_set_sensitive(spin, !active);
+
+ if (active) {
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), 0);
+ }
+}
+
+void set_frame(GtkSpinButton *spin, gpointer data)
+{
+ unsigned frame = gtk_spin_button_get_value_as_int(spin);
+
+ if (image)
+ render_to_pixbuf(image, framebuf, frame);
+ gdk_window_invalidate_rect(canvas->window, NULL, FALSE);
+}
+
+static void redraw_image(void)
+{
+ GtkSpinButton *spin;
+
+ spin = GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "framespin"));
+ set_frame(spin, NULL);
+}
+
+static void tick(void *p, double delta)
+{
+ static double elapsed = 0;
+ double seconds_per_frame = 1.0/15;
+ struct lbx_imginfo info;
+ GtkSpinButton *spin;
+ GtkToggleButton *play;
+ unsigned frame;
+
+ if (!image)
+ return;
+
+ lbximg_getinfo(image, &info);
+ spin = GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "framespin"));
+ play = GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "playbutton"));
+
+ frame = gtk_spin_button_get_value_as_int(spin);
+ if (!gtk_toggle_button_get_active(play))
+ return;
+
+ elapsed += delta;
+ while (elapsed > seconds_per_frame) {
+ elapsed -= seconds_per_frame;
+
+ if (++frame >= info.nframes) {
+ if (!info.looping) {
+ gtk_toggle_button_set_active(play, FALSE);
+ break;
+ }
+
+ frame = info.loopstart;
+ }
+
+ gtk_spin_button_set_value(spin, frame);
+ }
+}
+
+static gboolean timeout(gpointer data)
+{
+ static double lasttime = INFINITY;
+ static GTimer *timer;
+
+ if (isinf(lasttime)) {
+ timer = g_timer_new();
+ if (timer) {
+ lasttime = 0;
+ }
+ } else {
+ double time = g_timer_elapsed(timer, NULL);
+
+ tick(data, time - lasttime);
+ lasttime = time;
+ }
+
+ return TRUE;
+}
+
+gboolean canvas_expose(GtkWidget *canvas, GdkEventExpose *event, gpointer data)
+{
+ if (!framebuf)
+ return FALSE;
+
+ gdk_draw_rectangle(canvas->window, bg_gc, TRUE,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
+
+ gdk_draw_pixbuf(canvas->window, NULL, framebuf,
+ event->area.x, event->area.y, event->area.x, event->area.y,
+ event->area.width, event->area.height,
+ GDK_RGB_DITHER_NORMAL, 0, 0);
+
+ return TRUE;
+}
+
+static int alloc_framebuffer(LBX_IMG *image)
+{
+ struct lbx_imginfo info;
+ GtkSpinButton *spin;
+
+ if (framebuf)
+ g_object_unref(framebuf);
+
+ lbximg_getinfo(image, &info);
+ framebuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
+ info.width, info.height);
+ g_return_val_if_fail(framebuf, -1);
+
+ spin = GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "framespin"));
+ gtk_spin_button_set_range(spin, 0, info.nframes-1);
+ gtk_spin_button_set_value(spin, 0);
+
+ gtk_widget_set_size_request(canvas, info.width, info.height);
+ return 0;
+}
+
+static int close_image(void *handle)
+{
+ lbx_file_close(handle);
+ return 0;
+}
+
+static LBX_IMG *
+load_lbx_image(LBX *archive, unsigned index)
+{
+ LBXfile *file;
+ LBX_IMG *image;
+
+ file = lbx_file_open(archive, index);
+ g_return_val_if_fail(file, NULL);
+
+ image = lbximg_open(file, &lbx_arch_fops, close_image);
+ if (!image)
+ lbx_file_close(file);
+ return image;
+}
+
+void set_override(GtkComboBox *combo)
+{
+ GtkTreeIter iter;
+ gpointer lbx;
+ guint index;
+ LBX_IMG *img;
+
+ memset(palette_override, 0, sizeof palette_override);
+ if (!gtk_combo_box_get_active_iter(combo, &iter))
+ return;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(archives), &iter,
+ 1, &lbx,
+ 2, &index,
+ -1);
+
+ img = load_lbx_image(lbx, index);
+ if (img) {
+ lbximg_getpalette(img, palette_override);
+ lbximg_close(img);
+ redraw_image();
+ }
+}
+
+void set_palette(GtkComboBox *combo)
+{
+ GtkTreeIter iter;
+ gpointer lbx;
+ guint index;
+ LBXfile *f;
+
+ memset(palette_external, 0, sizeof palette_external);
+ if (!gtk_combo_box_get_active_iter(combo, &iter))
+ return;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(archives), &iter,
+ 1, &lbx,
+ 2, &index,
+ -1);
+
+ f = lbx_file_open(lbx, index);
+ if (f) {
+ if (lbximg_loadpalette(f, &lbx_arch_fops, palette_external))
+ memset(palette_external, 0, sizeof palette_external);
+ lbx_file_close(f);
+ }
+
+ redraw_image();
+}
+
+void set_image(GtkComboBox *combo)
+{
+ GtkTreeIter iter;
+ gpointer lbx;
+ guint index;
+ LBX_IMG *img;
+
+ if (image) {
+ lbximg_close(image);
+ image = NULL;
+ }
+
+ gtk_widget_set_size_request(canvas, -1, -1);
+ memset(palette_internal, 0, sizeof palette_internal);
+ if (!gtk_combo_box_get_active_iter(combo, &iter))
+ return;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(archives), &iter,
+ 1, &lbx,
+ 2, &index,
+ -1);
+
+ img = load_lbx_image(lbx, index);
+ if (img) {
+ if (lbximg_getpalette(img, palette_internal) == -1) {
+ puts("crap");
+ lbximg_close(img);
+ }
+
+ if (alloc_framebuffer(img) == -1) {
+ puts("crap");
+ lbximg_close(img);
+ }
+ }
+
+ image = img;
+ redraw_image();
+}
+
+void set_background(GtkCheckMenuItem *item, gpointer data)
+{
+ GdkColor black = { .red = 0, .green = 0, .blue = 0 };
+ GdkColor dark = { .red = 0x6666, .green = 0x6666, .blue = 0x6666 };
+ GdkColor light = { .red = 0x9999, .green = 0x9999, .blue = 0x9999 };
+
+ if (gtk_check_menu_item_get_active(item)) {
+ /* Enable stippled background. */
+ gdk_gc_set_rgb_fg_color(bg_gc, &dark);
+ gdk_gc_set_rgb_bg_color(bg_gc, &light);
+ gdk_gc_set_fill(bg_gc, GDK_OPAQUE_STIPPLED);
+ } else {
+ gdk_gc_set_rgb_fg_color(bg_gc, &black);
+ gdk_gc_set_fill(bg_gc, GDK_SOLID);
+ }
+ redraw_image();
+}
+
+void show_about(GtkWidget *widget)
+{
+ GtkWindow *parent = GTK_WINDOW(gtk_widget_get_toplevel(widget));
+
+ gtk_show_about_dialog(parent,
+ "name", g_get_application_name(),
+ "version", PACKAGE_VERSION,
+ "copyright", "Copyright \xc2\xa9 2010 Nick Bowler",
+ "website", "http://toom.sourceforge.net/",
+ NULL);
+}
+
+static int load_archive(const char *path)