+ return (type == MB_YESNO ? i == 0 : TRUE);
+}
+
+void error_box(GtkWidget *parent, char *msg)
+{
+ message_box(parent, "Error", msg, FALSE, MB_OK);
+}
+
+static void config_ok_button_clicked(GtkButton *button, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ char *err;
+
+ err = midend_set_config(fe->me, fe->cfg_which, fe->cfg);
+
+ if (err)
+ error_box(fe->cfgbox, err);
+ else {
+ fe->cfgret = TRUE;
+ gtk_widget_destroy(fe->cfgbox);
+ }
+}
+
+static void config_cancel_button_clicked(GtkButton *button, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+
+ gtk_widget_destroy(fe->cfgbox);
+}
+
+static int editbox_key(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+ /*
+ * GtkEntry has a nasty habit of eating the Return key, which
+ * is unhelpful since it doesn't actually _do_ anything with it
+ * (it calls gtk_widget_activate, but our edit boxes never need
+ * activating). So I catch Return before GtkEntry sees it, and
+ * pass it straight on to the parent widget. Effect: hitting
+ * Return in an edit box will now activate the default button
+ * in the dialog just like it will everywhere else.
+ */
+ if (event->keyval == GDK_Return && widget->parent != NULL) {
+ gint return_val;
+ gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
+ gtk_signal_emit_by_name(GTK_OBJECT(widget->parent), "key_press_event",
+ event, &return_val);
+ return return_val;
+ }
+ return FALSE;
+}
+
+static void editbox_changed(GtkEditable *ed, gpointer data)
+{
+ config_item *i = (config_item *)data;
+
+ sfree(i->sval);
+ i->sval = dupstr(gtk_entry_get_text(GTK_ENTRY(ed)));
+}
+
+static void button_toggled(GtkToggleButton *tb, gpointer data)
+{
+ config_item *i = (config_item *)data;
+
+ i->ival = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tb));
+}
+
+static void droplist_sel(GtkMenuItem *item, gpointer data)
+{
+ config_item *i = (config_item *)data;
+
+ i->ival = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item),
+ "user-data"));
+}
+
+static int get_config(frontend *fe, int which)
+{
+ GtkWidget *w, *table, *cancel;
+ char *title;
+ config_item *i;
+ int y;
+
+ fe->cfg = midend_get_config(fe->me, which, &title);
+ fe->cfg_which = which;
+ fe->cfgret = FALSE;
+
+ fe->cfgbox = gtk_dialog_new();
+ gtk_window_set_title(GTK_WINDOW(fe->cfgbox), title);
+ sfree(title);
+
+ w = gtk_button_new_with_label("OK");
+ gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->action_area),
+ w, FALSE, FALSE, 0);
+ gtk_widget_show(w);
+ GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
+ gtk_window_set_default(GTK_WINDOW(fe->cfgbox), w);
+ gtk_signal_connect(GTK_OBJECT(w), "clicked",
+ GTK_SIGNAL_FUNC(config_ok_button_clicked), fe);
+
+ w = gtk_button_new_with_label("Cancel");
+ gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->action_area),
+ w, FALSE, FALSE, 0);
+ gtk_widget_show(w);
+ gtk_signal_connect(GTK_OBJECT(w), "clicked",
+ GTK_SIGNAL_FUNC(config_cancel_button_clicked), fe);
+ cancel = w;
+
+ table = gtk_table_new(1, 2, FALSE);
+ y = 0;
+ gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->vbox),
+ table, FALSE, FALSE, 0);
+ gtk_widget_show(table);
+
+ for (i = fe->cfg; i->type != C_END; i++) {
+ gtk_table_resize(GTK_TABLE(table), y+1, 2);
+
+ switch (i->type) {
+ case C_STRING:
+ /*
+ * Edit box with a label beside it.
+ */
+
+ w = gtk_label_new(i->name);
+ gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.5);
+ gtk_table_attach(GTK_TABLE(table), w, 0, 1, y, y+1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ 3, 3);
+ gtk_widget_show(w);
+
+ w = gtk_entry_new();
+ gtk_table_attach(GTK_TABLE(table), w, 1, 2, y, y+1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ 3, 3);
+ gtk_entry_set_text(GTK_ENTRY(w), i->sval);
+ gtk_signal_connect(GTK_OBJECT(w), "changed",
+ GTK_SIGNAL_FUNC(editbox_changed), i);
+ gtk_signal_connect(GTK_OBJECT(w), "key_press_event",
+ GTK_SIGNAL_FUNC(editbox_key), NULL);
+ gtk_widget_show(w);
+
+ break;
+
+ case C_BOOLEAN:
+ /*
+ * Simple checkbox.
+ */
+ w = gtk_check_button_new_with_label(i->name);
+ gtk_signal_connect(GTK_OBJECT(w), "toggled",
+ GTK_SIGNAL_FUNC(button_toggled), i);
+ gtk_table_attach(GTK_TABLE(table), w, 0, 2, y, y+1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ 3, 3);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), i->ival);
+ gtk_widget_show(w);
+ break;
+
+ case C_CHOICES:
+ /*
+ * Drop-down list (GtkOptionMenu).
+ */
+
+ w = gtk_label_new(i->name);
+ gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.5);
+ gtk_table_attach(GTK_TABLE(table), w, 0, 1, y, y+1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ 3, 3);
+ gtk_widget_show(w);
+
+ w = gtk_option_menu_new();
+ gtk_table_attach(GTK_TABLE(table), w, 1, 2, y, y+1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ 3, 3);
+ gtk_widget_show(w);
+
+ {
+ int c, val;
+ char *p, *q, *name;
+ GtkWidget *menuitem;
+ GtkWidget *menu = gtk_menu_new();
+
+ gtk_option_menu_set_menu(GTK_OPTION_MENU(w), menu);
+
+ c = *i->sval;
+ p = i->sval+1;
+ val = 0;
+
+ while (*p) {
+ q = p;
+ while (*q && *q != c)
+ q++;
+
+ name = snewn(q-p+1, char);
+ strncpy(name, p, q-p);
+ name[q-p] = '\0';
+
+ if (*q) q++; /* eat delimiter */
+
+ menuitem = gtk_menu_item_new_with_label(name);
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+ GINT_TO_POINTER(val));
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(droplist_sel), i);
+ gtk_widget_show(menuitem);
+
+ val++;
+
+ p = q;
+ }
+
+ gtk_option_menu_set_history(GTK_OPTION_MENU(w), i->ival);
+ }
+
+ break;
+ }
+
+ y++;
+ }
+
+ gtk_signal_connect(GTK_OBJECT(fe->cfgbox), "destroy",
+ GTK_SIGNAL_FUNC(window_destroy), NULL);
+ gtk_signal_connect(GTK_OBJECT(fe->cfgbox), "key_press_event",
+ GTK_SIGNAL_FUNC(win_key_press), cancel);
+ gtk_window_set_modal(GTK_WINDOW(fe->cfgbox), TRUE);
+ gtk_window_set_transient_for(GTK_WINDOW(fe->cfgbox),
+ GTK_WINDOW(fe->window));
+ /* set_transient_window_pos(fe->window, fe->cfgbox); */
+ gtk_widget_show(fe->cfgbox);
+ gtk_main();
+
+ free_cfg(fe->cfg);
+
+ return fe->cfgret;
+}
+
+static void menu_key_event(GtkMenuItem *menuitem, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ int key = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(menuitem),
+ "user-data"));
+ if (!midend_process_key(fe->me, 0, 0, key))
+ gtk_widget_destroy(fe->window);
+}
+
+static void get_size(frontend *fe, int *px, int *py)
+{
+ int x, y;
+
+ /*
+ * Currently I don't want to make the GTK port scale large
+ * puzzles to fit on the screen. This is because X does permit
+ * extremely large windows and many window managers provide a
+ * means of navigating round them, and the users I consulted
+ * before deciding said that they'd rather have enormous puzzle
+ * windows spanning multiple screen pages than have them
+ * shrunk. I could change my mind later or introduce
+ * configurability; this would be the place to do so, by
+ * replacing the initial values of x and y with the screen
+ * dimensions.
+ */
+ x = INT_MAX;
+ y = INT_MAX;
+ midend_size(fe->me, &x, &y, FALSE);
+ *px = x;
+ *py = y;
+}
+
+#if !GTK_CHECK_VERSION(2,0,0)
+#define gtk_window_resize(win, x, y) \
+ gdk_window_resize(GTK_WIDGET(win)->window, x, y)
+#endif
+
+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");
+ 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;
+ 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_WINDOW(fe->window), req.width, req.height);
+ }
+}
+
+GdkAtom compound_text_atom, utf8_string_atom;
+int paste_initialised = FALSE;
+
+void init_paste()
+{
+ unsigned char empty[] = { 0 };
+
+ if (paste_initialised)
+ return;
+
+ if (!compound_text_atom)
+ compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE);
+ if (!utf8_string_atom)
+ utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE);
+
+ /*
+ * Ensure that all the cut buffers exist - according to the
+ * ICCCM, we must do this before we start using cut buffers.
+ */
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0);
+}
+
+/* Store data in a cut-buffer. */
+void store_cutbuffer(char *ptr, int len)
+{
+ /* ICCCM says we must rotate the buffers before storing to buffer 0. */
+ XRotateBuffers(GDK_DISPLAY(), 1);
+ XStoreBytes(GDK_DISPLAY(), ptr, len);
+}
+
+void write_clip(frontend *fe, char *data)
+{
+ init_paste();
+
+ if (fe->paste_data)
+ sfree(fe->paste_data);
+
+ /*
+ * For this simple application we can safely assume that the
+ * data passed to this function is pure ASCII, which means we
+ * can return precisely the same stuff for types STRING,
+ * COMPOUND_TEXT or UTF8_STRING.
+ */
+
+ fe->paste_data = data;
+ fe->paste_data_len = strlen(data);
+
+ store_cutbuffer(fe->paste_data, fe->paste_data_len);
+
+ if (gtk_selection_owner_set(fe->area, GDK_SELECTION_PRIMARY,
+ CurrentTime)) {
+ gtk_selection_add_target(fe->area, GDK_SELECTION_PRIMARY,
+ GDK_SELECTION_TYPE_STRING, 1);
+ gtk_selection_add_target(fe->area, GDK_SELECTION_PRIMARY,
+ compound_text_atom, 1);
+ gtk_selection_add_target(fe->area, GDK_SELECTION_PRIMARY,
+ utf8_string_atom, 1);
+ }
+}
+
+void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
+ guint info, guint time_stamp, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ gtk_selection_data_set(seldata, seldata->target, 8,
+ fe->paste_data, fe->paste_data_len);
+}
+
+gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
+ gpointer data)
+{
+ frontend *fe = (frontend *)data;
+
+ if (fe->paste_data)
+ sfree(fe->paste_data);
+ fe->paste_data = NULL;
+ fe->paste_data_len = 0;
+ return TRUE;
+}
+
+static void menu_copy_event(GtkMenuItem *menuitem, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ char *text;
+
+ text = midend_text_format(fe->me);
+
+ if (text) {
+ write_clip(fe, text);
+ } else {
+ gdk_beep();
+ }
+}
+
+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;
+ int x, y;
+
+ 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;
+ }
+
+ 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_WINDOW(fe->window), req.width, req.height);
+ }
+
+ }
+}
+
+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"));
+ 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_WINDOW(fe->window), req.width, req.height);
+ }
+}
+
+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);
+ 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);
+ 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);
+}
+
+static frontend *new_window(char *game_id, char **error)
+{
+ frontend *fe;
+ GtkBox *vbox;
+ GtkWidget *menubar, *menu, *menuitem;
+ int x, y, n;
+
+ fe = snew(frontend);
+
+ fe->timer_active = FALSE;
+ fe->timer_id = -1;
+
+ fe->me = midend_new(fe, &thegame);
+
+ if (game_id) {
+ *error = midend_game_id(fe->me, game_id);
+ if (*error) {
+ midend_free(fe->me);
+ sfree(fe);
+ return NULL;
+ }
+ }
+ 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));
+
+ menubar = gtk_menu_bar_new();
+ gtk_box_pack_start(vbox, menubar, FALSE, FALSE, 0);
+ gtk_widget_show(menubar);
+
+ menuitem = gtk_menu_item_new_with_label("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);
+
+ if ((n = midend_num_presets(fe->me)) > 0 || thegame.can_configure) {
+ GtkWidget *submenu;
+ int i;
+
+ menuitem = gtk_menu_item_new_with_label("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_menu_item_new_with_label(name);
+ 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 = gtk_menu_item_new_with_label("Custom...");
+ gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+ GPOINTER_TO_INT(CFG_SETTINGS));
+ gtk_container_add(GTK_CONTAINER(submenu), menuitem);
+ 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", '\x12');
+ if (thegame.can_format_as_text) {
+ 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);
+ }
+ 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_label("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);
+
+ {
+ int i, ncolours;
+ float *colours;
+ gboolean *success;
+
+ fe->colmap = gdk_colormap_get_system();
+ colours = midend_colours(fe->me, &ncolours);
+ fe->ncolours = ncolours;
+ fe->colours = snewn(ncolours, GdkColor);
+ for (i = 0; i < ncolours; i++) {
+ fe->colours[i].red = colours[i*3] * 0xFFFF;
+ fe->colours[i].green = colours[i*3+1] * 0xFFFF;
+ fe->colours[i].blue = colours[i*3+2] * 0xFFFF;
+ }
+ success = snewn(ncolours, gboolean);
+ gdk_colormap_alloc_colors(fe->colmap, fe->colours, ncolours,
+ FALSE, FALSE, success);
+ for (i = 0; i < ncolours; i++) {
+ if (!success[i])
+ g_error("couldn't allocate colour %d (#%02x%02x%02x)\n",
+ i, fe->colours[i].red >> 8,
+ fe->colours[i].green >> 8,
+ fe->colours[i].blue >> 8);
+ }
+ }
+
+ if (midend_wants_statusbar(fe->me)) {
+ GtkWidget *viewport;
+ GtkRequisition req;
+
+ viewport = gtk_viewport_new(NULL, NULL);
+ gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
+ fe->statusbar = gtk_statusbar_new();
+ gtk_container_add(GTK_CONTAINER(viewport), fe->statusbar);
+ gtk_widget_show(viewport);
+ gtk_box_pack_end(vbox, viewport, FALSE, FALSE, 0);
+ gtk_widget_show(fe->statusbar);
+ fe->statusctx = gtk_statusbar_get_context_id
+ (GTK_STATUSBAR(fe->statusbar), "game");
+ gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx,
+ "test");
+ gtk_widget_size_request(fe->statusbar, &req);
+#if 0
+ /* For GTK 2.0, should we be using gtk_widget_set_size_request? */
+#endif
+ gtk_widget_set_usize(viewport, -1, req.height);
+ } else
+ fe->statusbar = NULL;
+
+ fe->area = gtk_drawing_area_new();
+ get_size(fe, &x, &y);
+ gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
+ fe->w = x;
+ fe->h = y;
+
+ gtk_box_pack_end(vbox, fe->area, TRUE, TRUE, 0);
+
+ fe->pixmap = NULL;
+ fe->fonts = NULL;
+ fe->nfonts = fe->fontsize = 0;
+
+ fe->laststatus = NULL;
+
+ fe->paste_data = NULL;
+ fe->paste_data_len = 0;
+
+ gtk_signal_connect(GTK_OBJECT(fe->window), "destroy",
+ GTK_SIGNAL_FUNC(destroy), fe);
+ gtk_signal_connect(GTK_OBJECT(fe->window), "key_press_event",
+ GTK_SIGNAL_FUNC(key_event), fe);
+ gtk_signal_connect(GTK_OBJECT(fe->area), "button_press_event",
+ GTK_SIGNAL_FUNC(button_event), fe);
+ gtk_signal_connect(GTK_OBJECT(fe->area), "button_release_event",
+ GTK_SIGNAL_FUNC(button_event), fe);
+ gtk_signal_connect(GTK_OBJECT(fe->area), "motion_notify_event",
+ GTK_SIGNAL_FUNC(motion_event), fe);
+ gtk_signal_connect(GTK_OBJECT(fe->area), "selection_get",
+ GTK_SIGNAL_FUNC(selection_get), fe);
+ gtk_signal_connect(GTK_OBJECT(fe->area), "selection_clear_event",
+ GTK_SIGNAL_FUNC(selection_clear), fe);
+ gtk_signal_connect(GTK_OBJECT(fe->area), "expose_event",
+ GTK_SIGNAL_FUNC(expose_area), fe);
+ gtk_signal_connect(GTK_OBJECT(fe->window), "map_event",
+ GTK_SIGNAL_FUNC(map_window), fe);
+ gtk_signal_connect(GTK_OBJECT(fe->area), "configure_event",
+ GTK_SIGNAL_FUNC(configure_area), fe);
+
+ gtk_widget_add_events(GTK_WIDGET(fe->area),
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_BUTTON_MOTION_MASK);
+
+ gtk_widget_show(fe->area);
+ gtk_widget_show(fe->window);
+
+ gdk_window_set_background(fe->area->window, &fe->colours[0]);
+ gdk_window_set_background(fe->window->window, &fe->colours[0]);
+
+ return fe;
+}
+
+int main(int argc, char **argv)
+{
+ char *pname = argv[0];
+ char *error;
+
+ if (argc > 1 && !strcmp(argv[1], "--version")) {
+ printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n",
+ thegame.name, ver);
+ return 0;
+ }
+
+ /*
+ * Special standalone mode for generating puzzle IDs on the
+ * 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!)
+ *
+ * Usage:
+ *
+ * <puzzle-name> --generate [<n> [<params>]]
+ *
+ * <n>, if present, is the number of puzzle IDs to generate.
+ * <params>, if present, is the same type of parameter string
+ * you would pass to the puzzle when running it in GUI mode,
+ * including optional extras such as the expansion factor in
+ * Rectangles and the difficulty level in Solo.
+ *
+ * If you specify <params>, you must also specify <n> (although
+ * 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, TRUE)) != 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);
+ }
+
+ while (n-- > 0) {
+ char *aux = NULL;
+ char *desc = thegame.new_desc(par, rs, &aux, FALSE);
+ printf("%s:%s\n", parstr, desc);
+ sfree(desc);
+ sfree(aux);
+ }
+
+ return 0;
+ } else {
+
+ gtk_init(&argc, &argv);
+
+ if (!new_window(argc > 1 ? argv[1] : NULL, &error)) {
+ fprintf(stderr, "%s: %s\n", pname, error);
+ return 1;
+ }
+
+ gtk_main();
+ }