#include <time.h>
#include <stdarg.h>
#include <string.h>
+#include <errno.h>
#include <sys/time.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
*/
struct frontend {
GtkWidget *window;
+ GtkAccelGroup *accelgroup;
GtkWidget *area;
GtkWidget *statusbar;
guint statusctx;
int ncolours;
GdkColormap *colmap;
int w, h;
- midend_data *me;
+ midend *me;
GdkGC *gc;
int bbox_l, bbox_r, bbox_u, bbox_d;
int timer_active, timer_id;
GtkWidget *cfgbox;
void *paste_data;
int paste_data_len;
- char *laststatus;
int pw, ph; /* pixmap size (w, h are area size) */
int ox, oy; /* offset of pixmap in drawing area */
+ char *filesel_name;
};
void get_random_seed(void **randseed, int *randseedsize)
output[2] = col.blue / 65535.0;
}
-void status_bar(frontend *fe, char *text)
+void gtk_status_bar(void *handle, char *text)
{
- char *rewritten;
+ frontend *fe = (frontend *)handle;
assert(fe->statusbar);
- rewritten = midend_rewrite_statusbar(fe->me, text);
- if (!fe->laststatus || strcmp(rewritten, fe->laststatus)) {
- gtk_statusbar_pop(GTK_STATUSBAR(fe->statusbar), fe->statusctx);
- gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx, rewritten);
- sfree(fe->laststatus);
- fe->laststatus = rewritten;
- } else {
- sfree(rewritten);
- }
+ gtk_statusbar_pop(GTK_STATUSBAR(fe->statusbar), fe->statusctx);
+ gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx, text);
}
-void start_draw(frontend *fe)
+void gtk_start_draw(void *handle)
{
+ frontend *fe = (frontend *)handle;
fe->gc = gdk_gc_new(fe->area->window);
fe->bbox_l = fe->w;
fe->bbox_r = 0;
fe->bbox_d = 0;
}
-void clip(frontend *fe, int x, int y, int w, int h)
+void gtk_clip(void *handle, int x, int y, int w, int h)
{
+ frontend *fe = (frontend *)handle;
GdkRectangle rect;
rect.x = x;
gdk_gc_set_clip_rectangle(fe->gc, &rect);
}
-void unclip(frontend *fe)
+void gtk_unclip(void *handle)
{
+ frontend *fe = (frontend *)handle;
GdkRectangle rect;
rect.x = 0;
gdk_gc_set_clip_rectangle(fe->gc, &rect);
}
-void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
- int align, int colour, char *text)
+void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize,
+ int align, int colour, char *text)
{
+ frontend *fe = (frontend *)handle;
int i;
/*
if (align & ALIGN_VCENTRE)
rect.y -= rect.height / 2;
+ else
+ rect.y -= rect.height;
if (align & ALIGN_HCENTRE)
rect.x -= rect.width / 2;
&lb, &rb, &wid, &asc, &desc);
if (align & ALIGN_VCENTRE)
y += asc - (asc+desc)/2;
+ else
+ y += asc;
/*
* ... but horizontal extents with respect to the provided
}
-void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
+void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour)
{
+ frontend *fe = (frontend *)handle;
gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
gdk_draw_rectangle(fe->pixmap, fe->gc, 1, x, y, w, h);
}
-void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
+void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
{
+ frontend *fe = (frontend *)handle;
gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2);
}
-void draw_polygon(frontend *fe, int *coords, int npoints,
- int fill, int colour)
+void gtk_draw_poly(void *handle, int *coords, int npoints,
+ int fillcolour, int outlinecolour)
{
+ frontend *fe = (frontend *)handle;
GdkPoint *points = snewn(npoints, GdkPoint);
int i;
points[i].y = coords[i*2+1];
}
- gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
- gdk_draw_polygon(fe->pixmap, fe->gc, fill, points, npoints);
+ if (fillcolour >= 0) {
+ gdk_gc_set_foreground(fe->gc, &fe->colours[fillcolour]);
+ gdk_draw_polygon(fe->pixmap, fe->gc, TRUE, points, npoints);
+ }
+ assert(outlinecolour >= 0);
+ gdk_gc_set_foreground(fe->gc, &fe->colours[outlinecolour]);
+
+ /*
+ * In principle we ought to be able to use gdk_draw_polygon for
+ * the outline as well. In fact, it turns out to interact badly
+ * with a clipping region, for no terribly obvious reason, so I
+ * draw the outline as a sequence of lines instead.
+ */
+ for (i = 0; i < npoints; i++)
+ gdk_draw_line(fe->pixmap, fe->gc,
+ points[i].x, points[i].y,
+ points[(i+1)%npoints].x, points[(i+1)%npoints].y);
sfree(points);
}
-void draw_circle(frontend *fe, int cx, int cy, int radius,
- int fill, int colour)
+void gtk_draw_circle(void *handle, int cx, int cy, int radius,
+ int fillcolour, int outlinecolour)
{
- gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
- gdk_draw_arc(fe->pixmap, fe->gc, fill,
- cx - radius, cy - radius,
- 2 * radius, 2 * radius, 0, 360 * 64);
+ frontend *fe = (frontend *)handle;
+ if (fillcolour >= 0) {
+ gdk_gc_set_foreground(fe->gc, &fe->colours[fillcolour]);
+ gdk_draw_arc(fe->pixmap, fe->gc, TRUE,
+ cx - radius, cy - radius,
+ 2 * radius, 2 * radius, 0, 360 * 64);
+ }
+
+ assert(outlinecolour >= 0);
+ gdk_gc_set_foreground(fe->gc, &fe->colours[outlinecolour]);
+ gdk_draw_arc(fe->pixmap, fe->gc, FALSE,
+ cx - radius, cy - radius,
+ 2 * radius, 2 * radius, 0, 360 * 64);
}
struct blitter {
int w, h, x, y;
};
-blitter *blitter_new(int w, int h)
+blitter *gtk_blitter_new(void *handle, int w, int h)
{
/*
* We can't create the pixmap right now, because fe->window
return bl;
}
-void blitter_free(blitter *bl)
+void gtk_blitter_free(void *handle, blitter *bl)
{
if (bl->pixmap)
gdk_pixmap_unref(bl->pixmap);
sfree(bl);
}
-void blitter_save(frontend *fe, blitter *bl, int x, int y)
+void gtk_blitter_save(void *handle, blitter *bl, int x, int y)
{
+ frontend *fe = (frontend *)handle;
if (!bl->pixmap)
bl->pixmap = gdk_pixmap_new(fe->area->window, bl->w, bl->h, -1);
bl->x = x;
x, y, 0, 0, bl->w, bl->h);
}
-void blitter_load(frontend *fe, blitter *bl, int x, int y)
+void gtk_blitter_load(void *handle, blitter *bl, int x, int y)
{
+ frontend *fe = (frontend *)handle;
assert(bl->pixmap);
if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) {
x = bl->x;
0, 0, x, y, bl->w, bl->h);
}
-void draw_update(frontend *fe, int x, int y, int w, int h)
+void gtk_draw_update(void *handle, int x, int y, int w, int h)
{
+ frontend *fe = (frontend *)handle;
if (fe->bbox_l > x ) fe->bbox_l = x ;
if (fe->bbox_r < x+w) fe->bbox_r = x+w;
if (fe->bbox_u > y ) fe->bbox_u = y ;
if (fe->bbox_d < y+h) fe->bbox_d = y+h;
}
-void end_draw(frontend *fe)
+void gtk_end_draw(void *handle)
{
+ frontend *fe = (frontend *)handle;
gdk_gc_unref(fe->gc);
fe->gc = NULL;
}
}
+const struct drawing_api gtk_drawing = {
+ gtk_draw_text,
+ gtk_draw_rect,
+ gtk_draw_line,
+ gtk_draw_poly,
+ gtk_draw_circle,
+ gtk_draw_update,
+ gtk_clip,
+ gtk_unclip,
+ gtk_start_draw,
+ gtk_end_draw,
+ gtk_status_bar,
+ gtk_blitter_new,
+ gtk_blitter_free,
+ gtk_blitter_save,
+ gtk_blitter_load,
+ NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
+ NULL, /* line_width */
+};
+
static void destroy(GtkWidget *widget, gpointer data)
{
frontend *fe = (frontend *)data;
deactivate_timer(fe);
+ midend_free(fe->me);
gtk_main_quit();
}
if (!fe->pixmap)
return TRUE;
+#if !GTK_CHECK_VERSION(2,0,0)
+ /* Gtk 1.2 passes a key event to this function even if it's also
+ * defined as an accelerator.
+ * Gtk 2 doesn't do this, and this function appears not to exist there. */
+ if (fe->accelgroup &&
+ gtk_accel_group_get_entry(fe->accelgroup,
+ event->keyval, event->state))
+ return TRUE;
+#endif
+
if (event->keyval == GDK_Up)
keyval = shift | ctrl | CURSOR_UP;
else if (event->keyval == GDK_KP_Up || event->keyval == GDK_KP_8)
keyval = MOD_NUM_KEYPAD | '0';
else if (event->keyval == GDK_KP_Begin || event->keyval == GDK_KP_5)
keyval = MOD_NUM_KEYPAD | '5';
+ else if (event->keyval == GDK_BackSpace ||
+ event->keyval == GDK_Delete ||
+ event->keyval == GDK_KP_Delete)
+ keyval = '\177';
else if (event->string[0] && !event->string[1])
keyval = (unsigned char)event->string[0];
else
gc = gdk_gc_new(fe->area->window);
gdk_gc_set_foreground(gc, &fe->colours[0]);
gdk_draw_rectangle(fe->pixmap, gc, 1, 0, 0, fe->pw, fe->ph);
+ gdk_draw_rectangle(widget->window, gc, 1, 0, 0,
+ event->width, event->height);
gdk_gc_unref(gc);
midend_force_redraw(fe->me);
void deactivate_timer(frontend *fe)
{
+ if (!fe)
+ return; /* can happen due to --generate */
if (fe->timer_active)
gtk_timeout_remove(fe->timer_id);
fe->timer_active = FALSE;
void activate_timer(frontend *fe)
{
+ if (!fe)
+ return; /* can happen due to --generate */
if (!fe->timer_active) {
fe->timer_id = gtk_timeout_add(20, timer_func, fe);
gettimeofday(&fe->last_time, NULL);
gtk_main_quit();
}
-static void errmsg_button_clicked(GtkButton *button, gpointer data)
+static void msgbox_button_clicked(GtkButton *button, gpointer data)
{
+ GtkWidget *window = GTK_WIDGET(data);
+ int v, *ip;
+
+ ip = (int *)gtk_object_get_data(GTK_OBJECT(window), "user-data");
+ v = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(button), "user-data"));
+ *ip = v;
+
gtk_widget_destroy(GTK_WIDGET(data));
}
return FALSE;
}
-void message_box(GtkWidget *parent, char *title, char *msg, int centre)
+enum { MB_OK, MB_YESNO };
+
+int message_box(GtkWidget *parent, char *title, char *msg, int centre,
+ int type)
{
- GtkWidget *window, *hbox, *text, *ok;
+ GtkWidget *window, *hbox, *text, *button;
+ char *titles;
+ int i, def, cancel;
window = gtk_dialog_new();
text = gtk_label_new(msg);
gtk_widget_show(hbox);
gtk_window_set_title(GTK_WINDOW(window), title);
gtk_label_set_line_wrap(GTK_LABEL(text), TRUE);
- ok = gtk_button_new_with_label("OK");
- gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area),
- ok, FALSE, FALSE, 0);
- gtk_widget_show(ok);
- GTK_WIDGET_SET_FLAGS(ok, GTK_CAN_DEFAULT);
- gtk_window_set_default(GTK_WINDOW(window), ok);
- gtk_signal_connect(GTK_OBJECT(ok), "clicked",
- GTK_SIGNAL_FUNC(errmsg_button_clicked), window);
+
+ if (type == MB_OK) {
+ titles = "OK\0";
+ def = cancel = 0;
+ } else {
+ assert(type == MB_YESNO);
+ titles = "Yes\0No\0";
+ def = 0;
+ cancel = 1;
+ }
+ i = 0;
+
+ while (*titles) {
+ button = gtk_button_new_with_label(titles);
+ gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area),
+ button, FALSE, FALSE, 0);
+ gtk_widget_show(button);
+ if (i == def) {
+ GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
+ gtk_window_set_default(GTK_WINDOW(window), button);
+ }
+ if (i == cancel) {
+ gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
+ GTK_SIGNAL_FUNC(win_key_press), button);
+ }
+ gtk_signal_connect(GTK_OBJECT(button), "clicked",
+ GTK_SIGNAL_FUNC(msgbox_button_clicked), window);
+ gtk_object_set_data(GTK_OBJECT(button), "user-data",
+ GINT_TO_POINTER(i));
+ titles += strlen(titles)+1;
+ i++;
+ }
+ gtk_object_set_data(GTK_OBJECT(window), "user-data",
+ GINT_TO_POINTER(&i));
gtk_signal_connect(GTK_OBJECT(window), "destroy",
GTK_SIGNAL_FUNC(window_destroy), NULL);
- gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
- GTK_SIGNAL_FUNC(win_key_press), ok);
gtk_window_set_modal(GTK_WINDOW(window), TRUE);
gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(parent));
/* set_transient_window_pos(parent, window); */
gtk_widget_show(window);
+ i = -1;
gtk_main();
+ return (type == MB_YESNO ? i == 0 : TRUE);
}
void error_box(GtkWidget *parent, char *msg)
{
- message_box(parent, "Error", msg, FALSE);
+ message_box(parent, "Error", msg, FALSE, MB_OK);
}
static void config_ok_button_clicked(GtkButton *button, gpointer data)
gdk_window_resize(GTK_WIDGET(win)->window, x, y)
#endif
-static void menu_preset_event(GtkMenuItem *menuitem, gpointer data)
+static void resize_fe(frontend *fe)
{
- frontend *fe = (frontend *)data;
- game_params *params =
- (game_params *)gtk_object_get_data(GTK_OBJECT(menuitem), "user-data");
int x, y;
- midend_set_params(fe->me, params);
- midend_new_game(fe->me);
get_size(fe, &x, &y);
fe->w = x;
fe->h = y;
{
GtkRequisition req;
gtk_widget_size_request(GTK_WIDGET(fe->window), &req);
- gtk_window_resize(GTK_WIDGET(fe->window), req.width, req.height);
+ gtk_window_resize(GTK_WINDOW(fe->window), req.width, req.height);
}
+ /*
+ * Now that we've established the preferred size of the window,
+ * reduce the drawing area's size request so the user can shrink
+ * the window.
+ */
+ gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), 1, 1);
+}
+
+static void menu_preset_event(GtkMenuItem *menuitem, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ game_params *params =
+ (game_params *)gtk_object_get_data(GTK_OBJECT(menuitem), "user-data");
+
+ midend_set_params(fe->me, params);
+ midend_new_game(fe->me);
+ resize_fe(fe);
}
GdkAtom compound_text_atom, utf8_string_atom;
}
}
+static void filesel_ok(GtkButton *button, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+
+ gpointer filesel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
+
+ const char *name =
+ gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel));
+
+ fe->filesel_name = dupstr(name);
+}
+
+static char *file_selector(frontend *fe, char *title, int save)
+{
+ GtkWidget *filesel =
+ gtk_file_selection_new(title);
+
+ fe->filesel_name = NULL;
+
+ gtk_window_set_modal(GTK_WINDOW(filesel), TRUE);
+ gtk_object_set_data
+ (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
+ (gpointer)filesel);
+ gtk_signal_connect
+ (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
+ GTK_SIGNAL_FUNC(filesel_ok), fe);
+ gtk_signal_connect_object
+ (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
+ GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel);
+ gtk_signal_connect_object
+ (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
+ GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel);
+ gtk_signal_connect(GTK_OBJECT(filesel), "destroy",
+ GTK_SIGNAL_FUNC(window_destroy), NULL);
+ gtk_widget_show(filesel);
+ gtk_window_set_transient_for(GTK_WINDOW(filesel), GTK_WINDOW(fe->window));
+ gtk_main();
+
+ return fe->filesel_name;
+}
+
+static void savefile_write(void *wctx, void *buf, int len)
+{
+ FILE *fp = (FILE *)wctx;
+ fwrite(buf, 1, len, fp);
+}
+
+static int savefile_read(void *wctx, void *buf, int len)
+{
+ FILE *fp = (FILE *)wctx;
+ int ret;
+
+ ret = fread(buf, 1, len, fp);
+ return (ret == len);
+}
+
+static void menu_save_event(GtkMenuItem *menuitem, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ char *name;
+
+ name = file_selector(fe, "Enter name of game file to save", TRUE);
+
+ if (name) {
+ FILE *fp;
+
+ if ((fp = fopen(name, "r")) != NULL) {
+ char buf[256 + FILENAME_MAX];
+ fclose(fp);
+ /* file exists */
+
+ sprintf(buf, "Are you sure you want to overwrite the"
+ " file \"%.*s\"?",
+ FILENAME_MAX, name);
+ if (!message_box(fe->window, "Question", buf, TRUE, MB_YESNO))
+ return;
+ }
+
+ fp = fopen(name, "w");
+ sfree(name);
+
+ if (!fp) {
+ error_box(fe->window, "Unable to open save file");
+ return;
+ }
+
+ midend_serialise(fe->me, savefile_write, fp);
+
+ fclose(fp);
+ }
+}
+
+static void menu_load_event(GtkMenuItem *menuitem, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ char *name, *err;
+
+ name = file_selector(fe, "Enter name of saved game file to load", FALSE);
+
+ if (name) {
+ FILE *fp = fopen(name, "r");
+ sfree(name);
+
+ if (!fp) {
+ error_box(fe->window, "Unable to open saved game file");
+ return;
+ }
+
+ err = midend_deserialise(fe->me, savefile_read, fp);
+
+ fclose(fp);
+
+ if (err) {
+ error_box(fe->window, err);
+ return;
+ }
+
+ resize_fe(fe);
+ }
+}
+
static void menu_solve_event(GtkMenuItem *menuitem, gpointer data)
{
frontend *fe = (frontend *)data;
frontend *fe = (frontend *)data;
int which = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(menuitem),
"user-data"));
- int x, y;
if (!get_config(fe, which))
return;
midend_new_game(fe->me);
- get_size(fe, &x, &y);
- fe->w = x;
- fe->h = y;
- gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
- {
- GtkRequisition req;
- gtk_widget_size_request(GTK_WIDGET(fe->window), &req);
- gtk_window_resize(GTK_WIDGET(fe->window), req.width, req.height);
- }
+ resize_fe(fe);
}
static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
"from Simon Tatham's Portable Puzzle Collection\n\n"
"%.500s", thegame.name, ver);
- message_box(fe->window, titlebuf, textbuf, TRUE);
+ message_box(fe->window, titlebuf, textbuf, TRUE, MB_OK);
}
static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont,
char *text, int key)
{
GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
+ int keyqual;
gtk_container_add(cont, menuitem);
gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
GINT_TO_POINTER(key));
gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
GTK_SIGNAL_FUNC(menu_key_event), fe);
+ switch (key & ~0x1F) {
+ case 0x00:
+ key += 0x60;
+ keyqual = GDK_CONTROL_MASK;
+ break;
+ case 0x40:
+ key += 0x20;
+ keyqual = GDK_SHIFT_MASK;
+ break;
+ default:
+ keyqual = 0;
+ break;
+ }
+ gtk_widget_add_accelerator(menuitem,
+ "activate", fe->accelgroup,
+ key, keyqual,
+ GTK_ACCEL_VISIBLE);
gtk_widget_show(menuitem);
return menuitem;
}
gtk_widget_show(menuitem);
}
-static frontend *new_window(char *game_id, char **error)
+enum { ARG_EITHER, ARG_SAVE, ARG_ID }; /* for argtype */
+
+static frontend *new_window(char *arg, int argtype, char **error)
{
frontend *fe;
GtkBox *vbox;
GtkWidget *menubar, *menu, *menuitem;
+ GdkPixmap *iconpm;
+ GList *iconlist;
int x, y, n;
+ char errbuf[1024];
+ extern char *const *const xpm_icons[];
+ extern const int n_xpm_icons;
fe = snew(frontend);
fe->timer_active = FALSE;
fe->timer_id = -1;
- fe->me = midend_new(fe, &thegame);
+ fe->me = midend_new(fe, &thegame, >k_drawing, fe);
- if (game_id) {
- *error = midend_game_id(fe->me, game_id);
- if (*error) {
- midend_free(fe->me);
- sfree(fe);
- return NULL;
- }
+ if (arg) {
+ char *err;
+ FILE *fp;
+
+ errbuf[0] = '\0';
+
+ switch (argtype) {
+ case ARG_ID:
+ err = midend_game_id(fe->me, arg);
+ if (!err)
+ midend_new_game(fe->me);
+ else
+ sprintf(errbuf, "Invalid game ID: %.800s", err);
+ break;
+ case ARG_SAVE:
+ fp = fopen(arg, "r");
+ if (!fp) {
+ sprintf(errbuf, "Error opening file: %.800s", strerror(errno));
+ } else {
+ err = midend_deserialise(fe->me, savefile_read, fp);
+ if (err)
+ sprintf(errbuf, "Invalid save file: %.800s", err);
+ fclose(fp);
+ }
+ break;
+ default /*case ARG_EITHER*/:
+ /*
+ * First try treating the argument as a game ID.
+ */
+ err = midend_game_id(fe->me, arg);
+ if (!err) {
+ /*
+ * It's a valid game ID.
+ */
+ midend_new_game(fe->me);
+ } else {
+ FILE *fp = fopen(arg, "r");
+ if (!fp) {
+ sprintf(errbuf, "Supplied argument is neither a game ID (%.400s)"
+ " nor a save file (%.400s)", err, strerror(errno));
+ } else {
+ err = midend_deserialise(fe->me, savefile_read, fp);
+ if (err)
+ sprintf(errbuf, "%.800s", err);
+ fclose(fp);
+ }
+ }
+ break;
+ }
+ if (*errbuf) {
+ *error = dupstr(errbuf);
+ midend_free(fe->me);
+ sfree(fe);
+ return NULL;
+ }
+
+ } else {
+ midend_new_game(fe->me);
}
- midend_new_game(fe->me);
fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(fe->window), thegame.name);
-#if 0
- gtk_window_set_resizable(GTK_WINDOW(fe->window), FALSE);
-#else
- gtk_window_set_policy(GTK_WINDOW(fe->window), FALSE, FALSE, TRUE);
-#endif
+
vbox = GTK_BOX(gtk_vbox_new(FALSE, 0));
gtk_container_add(GTK_CONTAINER(fe->window), GTK_WIDGET(vbox));
gtk_widget_show(GTK_WIDGET(vbox));
+ fe->accelgroup = gtk_accel_group_new();
+ gtk_window_add_accel_group(GTK_WINDOW(fe->window), fe->accelgroup);
+
menubar = gtk_menu_bar_new();
gtk_box_pack_start(vbox, menubar, FALSE, FALSE, 0);
gtk_widget_show(menubar);
}
}
+ add_menu_separator(GTK_CONTAINER(menu));
+ menuitem = gtk_menu_item_new_with_label("Load...");
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(menu_load_event), fe);
+ gtk_widget_show(menuitem);
+ menuitem = gtk_menu_item_new_with_label("Save...");
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(menu_save_event), fe);
+ gtk_widget_show(menuitem);
add_menu_separator(GTK_CONTAINER(menu));
add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Undo", 'u');
- add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", '\x12');
+ add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", 'r');
if (thegame.can_format_as_text) {
add_menu_separator(GTK_CONTAINER(menu));
menuitem = gtk_menu_item_new_with_label("Copy");
fe->fonts = NULL;
fe->nfonts = fe->fontsize = 0;
- fe->laststatus = NULL;
-
fe->paste_data = NULL;
fe->paste_data_len = 0;
GDK_BUTTON_RELEASE_MASK |
GDK_BUTTON_MOTION_MASK);
+ if (n_xpm_icons) {
+ gtk_widget_realize(fe->window);
+ iconpm = gdk_pixmap_create_from_xpm_d(fe->window->window, NULL,
+ NULL, (gchar **)xpm_icons[0]);
+ gdk_window_set_icon(fe->window->window, NULL, iconpm, NULL);
+ iconlist = NULL;
+ for (n = 0; n < n_xpm_icons; n++) {
+ iconlist =
+ g_list_append(iconlist,
+ gdk_pixbuf_new_from_xpm_data((const gchar **)
+ xpm_icons[n]));
+ }
+ gdk_window_set_icon_list(fe->window->window, iconlist);
+ }
+
gtk_widget_show(fe->area);
gtk_widget_show(fe->window);
+ /*
+ * Now that we've established the preferred size of the window,
+ * reduce the drawing area's size request so the user can shrink
+ * the window.
+ */
+ gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), 1, 1);
+
gdk_window_set_background(fe->area->window, &fe->colours[0]);
gdk_window_set_background(fe->window->window, &fe->colours[0]);
return fe;
}
+char *fgetline(FILE *fp)
+{
+ char *ret = snewn(512, char);
+ int size = 512, len = 0;
+ while (fgets(ret + len, size - len, fp)) {
+ len += strlen(ret + len);
+ if (ret[len-1] == '\n')
+ break; /* got a newline, we're done */
+ size = len + 512;
+ ret = sresize(ret, size, char);
+ }
+ if (len == 0) { /* first fgets returned NULL */
+ sfree(ret);
+ return NULL;
+ }
+ ret[len] = '\0';
+ return ret;
+}
+
int main(int argc, char **argv)
{
char *pname = argv[0];
char *error;
+ int ngenerate = 0, print = FALSE, px = 1, py = 1;
+ int soln = FALSE, colour = FALSE;
+ float scale = 1.0F;
+ float redo_proportion = 0.0F;
+ char *arg = NULL;
+ int argtype = ARG_EITHER;
+ char *screenshot_file = NULL;
+ int doing_opts = TRUE;
+ int ac = argc;
+ char **av = argv;
+ char errbuf[500];
- if (argc > 1 && !strcmp(argv[1], "--version")) {
- printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n",
- thegame.name, ver);
- return 0;
+ /*
+ * Command line parsing in this function is rather fiddly,
+ * because GTK wants to have a go at argc/argv _first_ - and
+ * yet we can't let it, because gtk_init() will bomb out if it
+ * can't open an X display, whereas in fact we want to permit
+ * our --generate and --print modes to run without an X
+ * display.
+ *
+ * So what we do is:
+ * - we parse the command line ourselves, without modifying
+ * argc/argv
+ * - if we encounter an error which might plausibly be the
+ * result of a GTK command line (i.e. not detailed errors in
+ * particular options of ours) we store the error message
+ * and terminate parsing.
+ * - if we got enough out of the command line to know it
+ * specifies a non-X mode of operation, we either display
+ * the stored error and return failure, or if there is no
+ * stored error we do the non-X operation and return
+ * success.
+ * - otherwise, we go straight to gtk_init().
+ */
+
+ errbuf[0] = '\0';
+ while (--ac > 0) {
+ char *p = *++av;
+ if (doing_opts && !strcmp(p, "--version")) {
+ printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n",
+ thegame.name, ver);
+ return 0;
+ } else if (doing_opts && !strcmp(p, "--generate")) {
+ if (--ac > 0) {
+ ngenerate = atoi(*++av);
+ if (!ngenerate) {
+ fprintf(stderr, "%s: '--generate' expected a number\n",
+ pname);
+ return 1;
+ }
+ } else
+ ngenerate = 1;
+ } else if (doing_opts && !strcmp(p, "--print")) {
+ if (!thegame.can_print) {
+ fprintf(stderr, "%s: this game does not support printing\n",
+ pname);
+ return 1;
+ }
+ print = TRUE;
+ if (--ac > 0) {
+ char *dim = *++av;
+ if (sscanf(dim, "%dx%d", &px, &py) != 2) {
+ fprintf(stderr, "%s: unable to parse argument '%s' to "
+ "'--print'\n", pname, dim);
+ return 1;
+ }
+ } else {
+ px = py = 1;
+ }
+ } else if (doing_opts && !strcmp(p, "--scale")) {
+ if (--ac > 0) {
+ scale = atof(*++av);
+ } else {
+ fprintf(stderr, "%s: no argument supplied to '--scale'\n",
+ pname);
+ return 1;
+ }
+ } else if (doing_opts && !strcmp(p, "--redo")) {
+ /*
+ * This is an internal option which I don't expect
+ * users to have any particular use for. The effect of
+ * --redo is that once the game has been loaded and
+ * initialised, the next move in the redo chain is
+ * replayed, and the game screen is redrawn part way
+ * through the making of the move. This is only
+ * meaningful if there _is_ a next move in the redo
+ * chain, which means in turn that this option is only
+ * useful if you're also passing a save file on the
+ * command line.
+ *
+ * This option is used by the script which generates
+ * the puzzle icons and website screenshots, and I
+ * don't imagine it's useful for anything else.
+ * (Unless, I suppose, users don't like my screenshots
+ * and want to generate their own in the same way for
+ * some repackaged version of the puzzles.)
+ */
+ if (--ac > 0) {
+ redo_proportion = atof(*++av);
+ } else {
+ fprintf(stderr, "%s: no argument supplied to '--redo'\n",
+ pname);
+ return 1;
+ }
+ } else if (doing_opts && !strcmp(p, "--screenshot")) {
+ /*
+ * Another internal option for the icon building
+ * script. This causes a screenshot of the central
+ * drawing area (i.e. not including the menu bar or
+ * status bar) to be saved to a PNG file once the
+ * window has been drawn, and then the application
+ * quits immediately.
+ */
+ if (--ac > 0) {
+ screenshot_file = *++av;
+ } else {
+ fprintf(stderr, "%s: no argument supplied to '--screenshot'\n",
+ pname);
+ return 1;
+ }
+ } else if (doing_opts && (!strcmp(p, "--with-solutions") ||
+ !strcmp(p, "--with-solution") ||
+ !strcmp(p, "--with-solns") ||
+ !strcmp(p, "--with-soln") ||
+ !strcmp(p, "--solutions") ||
+ !strcmp(p, "--solution") ||
+ !strcmp(p, "--solns") ||
+ !strcmp(p, "--soln"))) {
+ soln = TRUE;
+ } else if (doing_opts && !strcmp(p, "--colour")) {
+ if (!thegame.can_print_in_colour) {
+ fprintf(stderr, "%s: this game does not support colour"
+ " printing\n", pname);
+ return 1;
+ }
+ colour = TRUE;
+ } else if (doing_opts && !strcmp(p, "--load")) {
+ argtype = ARG_SAVE;
+ } else if (doing_opts && !strcmp(p, "--game")) {
+ argtype = ARG_ID;
+ } else if (doing_opts && !strcmp(p, "--")) {
+ doing_opts = FALSE;
+ } else if (!doing_opts || p[0] != '-') {
+ if (arg) {
+ fprintf(stderr, "%s: more than one argument supplied\n",
+ pname);
+ return 1;
+ }
+ arg = p;
+ } else {
+ sprintf(errbuf, "%.100s: unrecognised option '%.100s'\n",
+ pname, p);
+ break;
+ }
+ }
+
+ if (*errbuf) {
+ fputs(errbuf, stderr);
+ return 1;
}
/*
* command line. Useful for generating puzzles to be printed
* out and solved offline (for puzzles where that even makes
* sense - Solo, for example, is a lot more pencil-and-paper
- * friendly than Net!)
+ * friendly than Twiddle!)
*
* Usage:
*
* you may specify it to be 1). Sorry; that was the
* simplest-to-parse command-line syntax I came up with.
*/
- if (argc > 1 && !strcmp(argv[1], "--generate")) {
- int n = 1;
- char *params = NULL, *seed = NULL;
- game_params *par;
- random_state *rs;
- char *parstr;
-
- if (argc > 2)
- n = atoi(argv[2]);
- if (argc > 3)
- params = argv[3];
-
- par = thegame.default_params();
- if (params) {
- if ( (seed = strchr(params, '#')) != NULL )
- *seed++ = '\0';
- thegame.decode_params(par, params);
- }
- if ((error = thegame.validate_params(par)) != NULL) {
- fprintf(stderr, "%s: %s\n", pname, error);
- return 1;
- }
- parstr = thegame.encode_params(par, FALSE);
-
- {
- void *seeddata;
- int seedlen;
- if (seed) {
- seeddata = seed;
- seedlen = strlen(seed);
- } else {
- get_random_seed(&seeddata, &seedlen);
- }
- rs = random_init(seeddata, seedlen);
+ if (ngenerate > 0 || print) {
+ int i, n = 1;
+ midend *me;
+ char *id;
+ document *doc = NULL;
+
+ n = ngenerate;
+
+ me = midend_new(NULL, &thegame, NULL, NULL);
+ i = 0;
+
+ if (print)
+ doc = document_new(px, py, scale);
+
+ /*
+ * In this loop, we either generate a game ID or read one
+ * from stdin depending on whether we're in generate mode;
+ * then we either write it to stdout or print it, depending
+ * on whether we're in print mode. Thus, this loop handles
+ * generate-to-stdout, print-from-stdin and generate-and-
+ * immediately-print modes.
+ *
+ * (It could also handle a copy-stdin-to-stdout mode,
+ * although there's currently no combination of options
+ * which will cause this loop to be activated in that mode.
+ * It wouldn't be _entirely_ pointless, though, because
+ * stdin could contain bare params strings or random-seed
+ * IDs, and stdout would contain nothing but fully
+ * generated descriptive game IDs.)
+ */
+ while (ngenerate == 0 || i < n) {
+ char *pstr, *err;
+
+ if (ngenerate == 0) {
+ pstr = fgetline(stdin);
+ if (!pstr)
+ break;
+ pstr[strcspn(pstr, "\r\n")] = '\0';
+ } else {
+ if (arg) {
+ pstr = snewn(strlen(arg) + 40, char);
+
+ strcpy(pstr, arg);
+ if (i > 0 && strchr(arg, '#'))
+ sprintf(pstr + strlen(pstr), "-%d", i);
+ } else
+ pstr = NULL;
+ }
+
+ if (pstr) {
+ err = midend_game_id(me, pstr);
+ if (err) {
+ fprintf(stderr, "%s: error parsing '%s': %s\n",
+ pname, pstr, err);
+ return 1;
+ }
+ }
+ sfree(pstr);
+
+ midend_new_game(me);
+
+ if (doc) {
+ err = midend_print_puzzle(me, doc, soln);
+ if (err) {
+ fprintf(stderr, "%s: error in printing: %s\n", pname, err);
+ return 1;
+ }
+ } else {
+ id = midend_get_game_id(me);
+ puts(id);
+ sfree(id);
+ }
+
+ i++;
}
- while (n-- > 0) {
- game_aux_info *aux = NULL;
- char *desc = thegame.new_desc(par, rs, &aux, FALSE);
- printf("%s:%s\n", parstr, desc);
- sfree(desc);
- if (aux)
- thegame.free_aux_info(aux);
+ if (doc) {
+ psdata *ps = ps_init(stdout, colour);
+ document_print(doc, ps_drawing_api(ps));
+ document_free(doc);
+ ps_free(ps);
}
+ midend_free(me);
+
return 0;
} else {
+ frontend *fe;
gtk_init(&argc, &argv);
- if (!new_window(argc > 1 ? argv[1] : NULL, &error)) {
+ fe = new_window(arg, argtype, &error);
+
+ if (!fe) {
fprintf(stderr, "%s: %s\n", pname, error);
return 1;
}
+ if (screenshot_file) {
+ /*
+ * Some puzzles will not redraw their entire area if
+ * given a partially completed animation, which means
+ * we must redraw now and _then_ redraw again after
+ * freezing the move timer.
+ */
+ midend_force_redraw(fe->me);
+ }
+
+ if (redo_proportion) {
+ /* Start a redo. */
+ midend_process_key(fe->me, 0, 0, 'r');
+ /* And freeze the timer at the specified position. */
+ midend_freeze_timer(fe->me, redo_proportion);
+ }
+
+ if (screenshot_file) {
+ GdkPixbuf *pb;
+ GError *gerror = NULL;
+
+ midend_redraw(fe->me);
+
+ pb = gdk_pixbuf_get_from_drawable(NULL, fe->pixmap,
+ NULL, 0, 0, 0, 0, -1, -1);
+ gdk_pixbuf_save(pb, screenshot_file, "png", &gerror, NULL);
+
+ exit(0);
+ }
+
gtk_main();
}