#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
-#if GTK_CHECK_VERSION(2,0,0) && !defined HAVE_SENSIBLE_ABSOLUTE_SIZE_FUNCTION
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
-#endif
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
#include "puzzles.h"
+#if GTK_CHECK_VERSION(2,0,0)
+#define USE_PANGO
+#endif
+
+#ifdef DEBUGGING
+static FILE *debug_fp = NULL;
+
+void dputs(char *buf)
+{
+ if (!debug_fp) {
+ debug_fp = fopen("debug.log", "w");
+ }
+
+ fputs(buf, stderr);
+
+ if (debug_fp) {
+ fputs(buf, debug_fp);
+ fflush(debug_fp);
+ }
+}
+
+void debug_printf(char *fmt, ...)
+{
+ char buf[4096];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsprintf(buf, fmt, ap);
+ dputs(buf);
+ va_end(ap);
+}
+#endif
+
/* ----------------------------------------------------------------------
* Error reporting functions used elsewhere.
*/
*/
struct font {
+#ifdef USE_PANGO
+ PangoFontDescription *desc;
+#else
GdkFont *font;
+#endif
int type;
int size;
};
config_item *cfg;
int cfg_which, cfgret;
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)
{
- time_t *tp = snew(time_t);
- time(tp);
- *randseed = (void *)tp;
- *randseedsize = sizeof(time_t);
+ struct timeval *tvp = snew(struct timeval);
+ gettimeofday(tvp, NULL);
+ *randseed = (void *)tvp;
+ *randseedsize = sizeof(struct timeval);
}
void frontend_default_colour(frontend *fe, float *output)
void status_bar(frontend *fe, char *text)
{
+ char *rewritten;
+
assert(fe->statusbar);
- gtk_statusbar_pop(GTK_STATUSBAR(fe->statusbar), fe->statusctx);
- gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx, text);
+ 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);
+ }
}
void start_draw(frontend *fe)
fe->fonts[i].type = fonttype;
fe->fonts[i].size = fontsize;
-#if GTK_CHECK_VERSION(2,0,0)
+#ifdef USE_PANGO
/*
* Use Pango to find the closest match to the requested
* font.
/* `Monospace' and `Sans' are meta-families guaranteed to exist */
pango_font_description_set_family(fd, fonttype == FONT_FIXED ?
"Monospace" : "Sans");
+ pango_font_description_set_weight(fd, PANGO_WEIGHT_BOLD);
/*
* I found some online Pango documentation which
* described a function called
pango_font_description_set_size(fd, resolution * fontsize);
}
#endif
- fe->fonts[i].font = gdk_font_from_description(fd);
- pango_font_description_free(fd);
+ fe->fonts[i].desc = fd;
}
- if (!fe->fonts[i].font)
+#else
+ /*
+ * In GTK 1.2, I don't know of any plausible way to
+ * pick a suitable font, so I'm just going to be
+ * tedious.
+ */
+ fe->fonts[i].font = gdk_font_load(fonttype == FONT_FIXED ?
+ "fixed" : "variable");
#endif
- /*
- * In GTK 1.2, I don't know of any plausible way to
- * pick a suitable font, so I'm just going to be
- * tedious.
- *
- * This is also fallback code called if the Pango
- * approach fails to find an appropriate font.
- */
- fe->fonts[i].font = gdk_font_load(fonttype == FONT_FIXED ?
- "fixed" : "variable");
+
}
/*
+ * Set the colour.
+ */
+ gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
+
+#ifdef USE_PANGO
+
+ {
+ PangoLayout *layout;
+ PangoRectangle rect;
+
+ /*
+ * Create a layout.
+ */
+ layout = pango_layout_new(gtk_widget_get_pango_context(fe->area));
+ pango_layout_set_font_description(layout, fe->fonts[i].desc);
+ pango_layout_set_text(layout, text, strlen(text));
+ pango_layout_get_pixel_extents(layout, NULL, &rect);
+
+ if (align & ALIGN_VCENTRE)
+ rect.y -= rect.height / 2;
+
+ if (align & ALIGN_HCENTRE)
+ rect.x -= rect.width / 2;
+ else if (align & ALIGN_HRIGHT)
+ rect.x -= rect.width;
+
+ gdk_draw_layout(fe->pixmap, fe->gc, rect.x + x, rect.y + y, layout);
+
+ g_object_unref(layout);
+ }
+
+#else
+ /*
* Find string dimensions and process alignment.
*/
{
int lb, rb, wid, asc, desc;
- gdk_string_extents(fe->fonts[i].font, text,
+ /*
+ * Measure vertical string extents with respect to the same
+ * string always...
+ */
+ gdk_string_extents(fe->fonts[i].font,
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
&lb, &rb, &wid, &asc, &desc);
if (align & ALIGN_VCENTRE)
y += asc - (asc+desc)/2;
+ /*
+ * ... but horizontal extents with respect to the provided
+ * string. This means that multiple pieces of text centred
+ * on the same y-coordinate don't have different baselines.
+ */
+ gdk_string_extents(fe->fonts[i].font, text,
+ &lb, &rb, &wid, &asc, &desc);
+
if (align & ALIGN_HCENTRE)
x -= wid / 2;
else if (align & ALIGN_HRIGHT)
}
/*
- * Set colour and actually draw text.
+ * Actually draw the text.
*/
- gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
gdk_draw_string(fe->pixmap, fe->fonts[i].font, fe->gc, x, y, text);
+#endif
+
}
void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
sfree(points);
}
+void draw_circle(frontend *fe, int cx, int cy, int radius,
+ int fill, int colour)
+{
+ 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);
+}
+
+struct blitter {
+ GdkPixmap *pixmap;
+ int w, h, x, y;
+};
+
+blitter *blitter_new(int w, int h)
+{
+ /*
+ * We can't create the pixmap right now, because fe->window
+ * might not yet exist. So we just cache w and h and create it
+ * during the firs call to blitter_save.
+ */
+ blitter *bl = snew(blitter);
+ bl->pixmap = NULL;
+ bl->w = w;
+ bl->h = h;
+ return bl;
+}
+
+void blitter_free(blitter *bl)
+{
+ if (bl->pixmap)
+ gdk_pixmap_unref(bl->pixmap);
+ sfree(bl);
+}
+
+void blitter_save(frontend *fe, blitter *bl, int x, int y)
+{
+ if (!bl->pixmap)
+ bl->pixmap = gdk_pixmap_new(fe->area->window, bl->w, bl->h, -1);
+ bl->x = x;
+ bl->y = y;
+ gdk_draw_pixmap(bl->pixmap,
+ fe->area->style->fg_gc[GTK_WIDGET_STATE(fe->area)],
+ fe->pixmap,
+ x, y, 0, 0, bl->w, bl->h);
+}
+
+void blitter_load(frontend *fe, blitter *bl, int x, int y)
+{
+ assert(bl->pixmap);
+ if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) {
+ x = bl->x;
+ y = bl->y;
+ }
+ gdk_draw_pixmap(fe->pixmap,
+ fe->area->style->fg_gc[GTK_WIDGET_STATE(fe->area)],
+ bl->pixmap,
+ 0, 0, x, y, bl->w, bl->h);
+}
+
void draw_update(frontend *fe, int x, int y, int w, int h)
{
if (fe->bbox_l > x ) fe->bbox_l = x ;
fe->area->style->fg_gc[GTK_WIDGET_STATE(fe->area)],
fe->pixmap,
fe->bbox_l, fe->bbox_u,
- fe->bbox_l, fe->bbox_u,
+ fe->ox + fe->bbox_l, fe->oy + fe->bbox_u,
fe->bbox_r - fe->bbox_l, fe->bbox_d - fe->bbox_u);
}
}
{
frontend *fe = (frontend *)data;
deactivate_timer(fe);
+ midend_free(fe->me);
gtk_main_quit();
}
{
frontend *fe = (frontend *)data;
int keyval;
+ int shift = (event->state & GDK_SHIFT_MASK) ? MOD_SHFT : 0;
+ int ctrl = (event->state & GDK_CONTROL_MASK) ? MOD_CTRL : 0;
if (!fe->pixmap)
return TRUE;
- if (event->string[0] && !event->string[1])
- keyval = (unsigned char)event->string[0];
- else if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
- event->keyval == GDK_KP_8)
- keyval = CURSOR_UP;
- else if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
- event->keyval == GDK_KP_2)
- keyval = CURSOR_DOWN;
- else if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left ||
- event->keyval == GDK_KP_4)
- keyval = CURSOR_LEFT;
- else if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right ||
- event->keyval == GDK_KP_6)
- keyval = CURSOR_RIGHT;
+ 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 | '8';
+ else if (event->keyval == GDK_Down)
+ keyval = shift | ctrl | CURSOR_DOWN;
+ else if (event->keyval == GDK_KP_Down || event->keyval == GDK_KP_2)
+ keyval = MOD_NUM_KEYPAD | '2';
+ else if (event->keyval == GDK_Left)
+ keyval = shift | ctrl | CURSOR_LEFT;
+ else if (event->keyval == GDK_KP_Left || event->keyval == GDK_KP_4)
+ keyval = MOD_NUM_KEYPAD | '4';
+ else if (event->keyval == GDK_Right)
+ keyval = shift | ctrl | CURSOR_RIGHT;
+ else if (event->keyval == GDK_KP_Right || event->keyval == GDK_KP_6)
+ keyval = MOD_NUM_KEYPAD | '6';
else if (event->keyval == GDK_KP_Home || event->keyval == GDK_KP_7)
- keyval = CURSOR_UP_LEFT;
+ keyval = MOD_NUM_KEYPAD | '7';
else if (event->keyval == GDK_KP_End || event->keyval == GDK_KP_1)
- keyval = CURSOR_DOWN_LEFT;
+ keyval = MOD_NUM_KEYPAD | '1';
else if (event->keyval == GDK_KP_Page_Up || event->keyval == GDK_KP_9)
- keyval = CURSOR_UP_RIGHT;
+ keyval = MOD_NUM_KEYPAD | '9';
else if (event->keyval == GDK_KP_Page_Down || event->keyval == GDK_KP_3)
- keyval = CURSOR_DOWN_RIGHT;
+ keyval = MOD_NUM_KEYPAD | '3';
+ else if (event->keyval == GDK_KP_Insert || event->keyval == GDK_KP_0)
+ 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->string[0] && !event->string[1])
+ keyval = (unsigned char)event->string[0];
else
keyval = -1;
if (event->button == 2 || (event->state & GDK_SHIFT_MASK))
button = MIDDLE_BUTTON;
+ else if (event->button == 3 || (event->state & GDK_MOD1_MASK))
+ button = RIGHT_BUTTON;
else if (event->button == 1)
button = LEFT_BUTTON;
- else if (event->button == 3)
- button = RIGHT_BUTTON;
else
return FALSE; /* don't even know what button! */
if (event->type == GDK_BUTTON_RELEASE)
button += LEFT_RELEASE - LEFT_BUTTON;
- if (!midend_process_key(fe->me, event->x, event->y, button))
+ if (!midend_process_key(fe->me, event->x - fe->ox,
+ event->y - fe->oy, button))
gtk_widget_destroy(fe->window);
return TRUE;
else
return FALSE; /* don't even know what button! */
- if (!midend_process_key(fe->me, event->x, event->y, button))
+ if (!midend_process_key(fe->me, event->x - fe->ox,
+ event->y - fe->oy, button))
gtk_widget_destroy(fe->window);
return TRUE;
gdk_draw_pixmap(widget->window,
widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
fe->pixmap,
- event->area.x, event->area.y,
+ event->area.x - fe->ox, event->area.y - fe->oy,
event->area.x, event->area.y,
event->area.width, event->area.height);
}
{
frontend *fe = (frontend *)data;
GdkGC *gc;
+ int x, y;
if (fe->pixmap)
gdk_pixmap_unref(fe->pixmap);
- fe->pixmap = gdk_pixmap_new(widget->window, fe->w, fe->h, -1);
+ x = fe->w = event->width;
+ y = fe->h = event->height;
+ midend_size(fe->me, &x, &y, TRUE);
+ fe->pw = x;
+ fe->ph = y;
+ fe->ox = (fe->w - fe->pw) / 2;
+ fe->oy = (fe->h - fe->ph) / 2;
+
+ fe->pixmap = gdk_pixmap_new(widget->window, fe->pw, fe->ph, -1);
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->w, fe->h);
+ gdk_draw_rectangle(fe->pixmap, gc, 1, 0, 0, fe->pw, fe->ph);
gdk_gc_unref(gc);
- midend_redraw(fe->me);
+ midend_force_redraw(fe->me);
return TRUE;
}
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 error_box(GtkWidget *parent, char *msg)
+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);
hbox, FALSE, FALSE, 20);
gtk_widget_show(text);
gtk_widget_show(hbox);
- gtk_window_set_title(GTK_WINDOW(window), "Error");
+ 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);
+ /* 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, MB_OK);
}
static void config_ok_button_clicked(GtkButton *button, gpointer data)
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);
+ /* set_transient_window_pos(fe->window, fe->cfgbox); */
gtk_widget_show(fe->cfgbox);
gtk_main();
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;
midend_set_params(fe->me, params);
midend_new_game(fe->me);
- midend_size(fe->me, &x, &y);
- gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
+ 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)
return;
midend_new_game(fe->me);
- midend_size(fe->me, &x, &y);
- gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
+ 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,
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, FALSE);
+ *error = midend_game_id(fe->me, game_id);
if (*error) {
midend_free(fe->me);
sfree(fe);
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
add_menu_item_with_key(fe, GTK_CONTAINER(menu), "New", 'n');
- add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Restart", 'r');
+
+ 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",
}
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;
fe->statusbar = NULL;
fe->area = gtk_drawing_area_new();
- midend_size(fe->me, &x, &y);
+ 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, FALSE, FALSE, 0);
+ gtk_box_pack_end(vbox, fe->area, TRUE, TRUE, 0);
fe->pixmap = NULL;
fe->fonts = NULL;
fe->nfonts = fe->fontsize = 0;
- fe->timer_active = FALSE;
+ 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_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_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;
}
char *pname = argv[0];
char *error;
- gtk_init(&argc, &argv);
-
- if (!new_window(argc > 1 ? argv[1] : NULL, &error)) {
- fprintf(stderr, "%s: %s\n", pname, error);
- return 1;
+ if (argc > 1 && !strcmp(argv[1], "--version")) {
+ printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n",
+ thegame.name, ver);
+ return 0;
}
- gtk_main();
+ /*
+ * 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)) != 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();
+ }
return 0;
}