+static GtkWidget *canvas;
+
+static cairo_surface_t *framebuf;
+static cairo_pattern_t *bg_pattern;
+static bool bg_stipple;
+
+static struct lbx_image *image;
+static struct lbx_colour palette[256];
+
+/* We support stacking up to three palettes, with each superseding the last. */
+static struct lbx_colour palette_external[256];
+static struct lbx_colour palette_internal[256];
+static struct lbx_colour palette_override[256];
+
+static void refresh_palette(void)
+{
+ memcpy(palette, palette_external, sizeof palette);
+ lbxgui_stack_palette(palette, palette_internal);
+ lbxgui_stack_palette(palette, palette_override);
+
+ if (framebuf)
+ lbxgui_render_restart(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)
+ lbxgui_render_argb(framebuf, image, frame, palette);
+ gdk_window_invalidate_rect(canvas->window, NULL, FALSE);
+}
+
+static void redraw_image(void)
+{
+ GtkSpinButton *spin;
+
+ refresh_palette();
+
+ 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;
+ GtkSpinButton *spin;
+ GtkToggleButton *play;
+ unsigned frame, newframe;
+
+ if (!image)
+ return;
+
+ 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;
+ newframe = frame;
+ while (elapsed > seconds_per_frame) {
+ elapsed -= seconds_per_frame;
+
+ if (++newframe >= image->frames) {
+ if (image->leadin == image->frames - 1) {
+ gtk_toggle_button_set_active(play, FALSE);
+ break;
+ }
+
+ newframe = image->leadin;
+ }
+ }
+
+ if (frame != newframe)
+ gtk_spin_button_set_value(spin, newframe);
+}
+
+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)
+{
+ cairo_t *cr;
+
+ cr = gdk_cairo_create(canvas->window);
+
+ if (bg_stipple) {
+ cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
+ gdk_cairo_rectangle(cr, &event->area);
+ cairo_fill(cr);
+
+ cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
+ gdk_cairo_rectangle(cr, &event->area);
+ cairo_mask(cr, bg_pattern);
+ } else {
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ gdk_cairo_rectangle(cr, &event->area);
+ cairo_fill(cr);
+ }
+
+ cairo_set_source_surface(cr, framebuf, 0, 0);
+ gdk_cairo_rectangle(cr, &event->area);
+ cairo_fill(cr);
+
+ cairo_destroy(cr);
+
+ return TRUE;
+}
+
+static int alloc_framebuffer(struct lbx_image *img)
+{
+ GtkSpinButton *spin;
+
+ cairo_surface_destroy(framebuf);
+ framebuf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ img->width, img->height);
+
+ spin = GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "framespin"));
+ gtk_spin_button_set_range(spin, 0, img->frames-1);
+ gtk_spin_button_set_value(spin, 0);
+
+ gtk_widget_set_size_request(canvas, img->width, img->height);
+ return 0;
+}
+
+static int close_image(void *handle)
+{
+ lbx_file_close(handle);
+ return 0;
+}
+
+static struct lbx_image *load_lbx_image(struct lbx *archive, unsigned index)
+{
+ LBXfile *file;
+ struct lbx_image *image;
+
+ file = lbx_file_open(archive, index);
+ g_return_val_if_fail(file, NULL);
+
+ image = lbx_img_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;
+ struct lbx_image *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) {
+ lbx_img_getpalette(img, palette_override);
+ lbx_img_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 (lbx_img_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;
+ struct lbx_image *img;
+
+ if (image) {
+ lbx_img_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 (lbx_img_getpalette(img, palette_internal) == -1) {
+ puts("crap");
+ lbx_img_close(img);
+ }
+
+ alloc_framebuffer(img);
+ }
+
+ image = img;
+ redraw_image();
+}
+
+void set_background(GtkCheckMenuItem *item, gpointer data)
+{
+ bg_stipple = gtk_check_menu_item_get_active(item);
+ 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)