+struct savefile_write_ctx {
+ FILE *fp;
+ int error;
+};
+
+static void savefile_write(void *wctx, void *buf, int len)
+{
+ struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)wctx;
+ if (fwrite(buf, 1, len, ctx->fp) < len)
+ ctx->error = errno;
+}
+
+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;
+ }
+
+ {
+ struct savefile_write_ctx ctx;
+ ctx.fp = fp;
+ ctx.error = 0;
+ midend_serialise(fe->me, savefile_write, &ctx);
+ fclose(fp);
+ if (ctx.error) {
+ char boxmsg[512];
+ sprintf(boxmsg, "Error writing save file: %.400s",
+ strerror(errno));
+ error_box(fe->window, boxmsg);
+ return;
+ }
+ }
+
+ }
+}
+
+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;
+ }
+
+ changed_preset(fe);
+ resize_fe(fe);
+ }
+}
+
+static void menu_solve_event(GtkMenuItem *menuitem, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ char *msg;
+
+ msg = midend_solve(fe->me);
+
+ if (msg)
+ error_box(fe->window, msg);
+}
+
+static void menu_restart_event(GtkMenuItem *menuitem, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+
+ midend_restart_game(fe->me);
+}
+
+static void menu_config_event(GtkMenuItem *menuitem, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ int which = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(menuitem),
+ "user-data"));
+
+ if (fe->preset_threaded ||
+ (GTK_IS_CHECK_MENU_ITEM(menuitem) &&
+ !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))))
+ return;
+ changed_preset(fe); /* Put the old preset back! */
+ if (!get_config(fe, which))
+ return;
+
+ midend_new_game(fe->me);
+ resize_fe(fe);
+}
+
+static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ char titlebuf[256];
+ char textbuf[1024];
+
+ sprintf(titlebuf, "About %.200s", thegame.name);
+ sprintf(textbuf,
+ "%.200s\n\n"
+ "from Simon Tatham's Portable Puzzle Collection\n\n"
+ "%.500s", thegame.name, ver);
+
+ 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;
+}
+
+static void add_menu_separator(GtkContainer *cont)
+{
+ GtkWidget *menuitem = gtk_menu_item_new();
+ gtk_container_add(cont, menuitem);
+ gtk_widget_show(menuitem);
+}
+
+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, >k_drawing, fe);
+
+ 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);
+ }
+
+ fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(fe->window), thegame.name);
+
+ 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);
+
+ menuitem = gtk_menu_item_new_with_mnemonic("_Game");
+ gtk_container_add(GTK_CONTAINER(menubar), menuitem);
+ gtk_widget_show(menuitem);
+
+ menu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+
+ add_menu_item_with_key(fe, GTK_CONTAINER(menu), "New", 'n');
+
+ menuitem = gtk_menu_item_new_with_label("Restart");
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(menu_restart_event), fe);
+ gtk_widget_show(menuitem);
+
+ menuitem = gtk_menu_item_new_with_label("Specific...");
+ gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+ GINT_TO_POINTER(CFG_DESC));
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(menu_config_event), fe);
+ gtk_widget_show(menuitem);
+
+ menuitem = gtk_menu_item_new_with_label("Random Seed...");
+ gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+ GINT_TO_POINTER(CFG_SEED));
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(menu_config_event), fe);
+ gtk_widget_show(menuitem);
+
+ fe->preset_radio = NULL;
+ fe->preset_custom = NULL;
+ fe->n_preset_menu_items = 0;
+ fe->preset_threaded = FALSE;
+ if ((n = midend_num_presets(fe->me)) > 0 || thegame.can_configure) {
+ GtkWidget *submenu;
+ int i;
+
+ menuitem = gtk_menu_item_new_with_mnemonic("_Type");
+ gtk_container_add(GTK_CONTAINER(menubar), menuitem);
+ gtk_widget_show(menuitem);
+
+ submenu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
+
+ for (i = 0; i < n; i++) {
+ char *name;
+ game_params *params;
+
+ midend_fetch_preset(fe->me, i, &name, ¶ms);
+
+ menuitem =
+ gtk_radio_menu_item_new_with_label(fe->preset_radio, name);
+ fe->preset_radio =
+ gtk_radio_menu_item_group(GTK_RADIO_MENU_ITEM(menuitem));
+ fe->n_preset_menu_items++;
+ gtk_container_add(GTK_CONTAINER(submenu), menuitem);
+ gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", params);
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(menu_preset_event), fe);
+ gtk_widget_show(menuitem);
+ }
+
+ if (thegame.can_configure) {
+ menuitem = fe->preset_custom =
+ gtk_radio_menu_item_new_with_label(fe->preset_radio,
+ "Custom...");
+ fe->preset_radio =
+ gtk_radio_menu_item_group(GTK_RADIO_MENU_ITEM(menuitem));
+ gtk_container_add(GTK_CONTAINER(submenu), menuitem);
+ gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+ GPOINTER_TO_INT(CFG_SETTINGS));
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(menu_config_event), fe);
+ gtk_widget_show(menuitem);
+ }
+
+ }
+
+ 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", 'r');
+ if (thegame.can_format_as_text_ever) {
+ add_menu_separator(GTK_CONTAINER(menu));
+ menuitem = gtk_menu_item_new_with_label("Copy");
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(menu_copy_event), fe);
+ gtk_widget_show(menuitem);
+ fe->copy_menu_item = menuitem;
+ } else {
+ fe->copy_menu_item = NULL;
+ }
+ if (thegame.can_solve) {
+ add_menu_separator(GTK_CONTAINER(menu));
+ menuitem = gtk_menu_item_new_with_label("Solve");
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(menu_solve_event), fe);
+ gtk_widget_show(menuitem);
+ }
+ add_menu_separator(GTK_CONTAINER(menu));
+ add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Exit", 'q');
+
+ menuitem = gtk_menu_item_new_with_mnemonic("_Help");
+ gtk_container_add(GTK_CONTAINER(menubar), menuitem);
+ gtk_widget_show(menuitem);
+
+ menu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+
+ menuitem = gtk_menu_item_new_with_label("About");
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(menu_about_event), fe);
+ gtk_widget_show(menuitem);
+
+ changed_preset(fe);
+