#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 "puzzles.h"
/* ----------------------------------------------------------------------
GtkWidget *cfgbox;
};
+void get_random_seed(void **randseed, int *randseedsize)
+{
+ time_t *tp = snew(time_t);
+ time(tp);
+ *randseed = (void *)tp;
+ *randseedsize = sizeof(time_t);
+}
+
void frontend_default_colour(frontend *fe, float *output)
{
GdkColor col = fe->window->style->bg[GTK_STATE_NORMAL];
fe->fonts[i].type = fonttype;
fe->fonts[i].size = fontsize;
+#if GTK_CHECK_VERSION(2,0,0)
/*
- * FIXME: Really I should make at least _some_ effort to
- * pick the correct font.
+ * Use Pango to find the closest match to the requested
+ * font.
*/
- fe->fonts[i].font = gdk_font_load("variable");
+ {
+ PangoFontDescription *fd;
+
+ fd = pango_font_description_new();
+ /* `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_absolute_size(), which is
+ * _exactly_ what I want here. Unfortunately, none of
+ * my local Pango installations have it (presumably
+ * they're too old), so I'm going to have to hack round
+ * it by figuring out the point size myself. This
+ * limits me to X and probably also breaks in later
+ * Pango installations, so ideally I should add another
+ * CHECK_VERSION type ifdef and use set_absolute_size
+ * where available. All very annoying.
+ */
+#ifdef HAVE_SENSIBLE_ABSOLUTE_SIZE_FUNCTION
+ pango_font_description_set_absolute_size(fd, PANGO_SCALE*fontsize);
+#else
+ {
+ Display *d = GDK_DISPLAY();
+ int s = DefaultScreen(d);
+ double resolution =
+ (PANGO_SCALE * 72.27 / 25.4) *
+ ((double) DisplayWidthMM(d, s) / DisplayWidth (d, s));
+ pango_font_description_set_size(fd, resolution * fontsize);
+ }
+#endif
+ fe->fonts[i].font = gdk_font_from_description(fd);
+ pango_font_description_free(fd);
+ }
+
+ if (!fe->fonts[i].font)
+#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");
}
/*
{
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)
static void destroy(GtkWidget *widget, gpointer data)
{
+ frontend *fe = (frontend *)data;
+ deactivate_timer(fe);
gtk_main_quit();
}
if (!fe->pixmap)
return TRUE;
- if (event->type != GDK_BUTTON_PRESS)
+ if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE)
return TRUE;
if (event->button == 2 || (event->state & GDK_SHIFT_MASK))
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))
+ gtk_widget_destroy(fe->window);
+
+ return TRUE;
+}
+
+static gint motion_event(GtkWidget *widget, GdkEventMotion *event,
+ gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ int button;
+
+ if (!fe->pixmap)
+ return TRUE;
+
+ if (event->state & (GDK_BUTTON2_MASK | GDK_SHIFT_MASK))
+ button = MIDDLE_DRAG;
+ else if (event->state & GDK_BUTTON1_MASK)
+ button = LEFT_DRAG;
+ else if (event->state & GDK_BUTTON3_MASK)
+ button = RIGHT_DRAG;
+ else
+ return FALSE; /* don't even know what button! */
+
if (!midend_process_key(fe->me, event->x, event->y, button))
gtk_widget_destroy(fe->window);
gtk_window_set_default(GTK_WINDOW(window), ok);
gtk_signal_connect(GTK_OBJECT(ok), "clicked",
GTK_SIGNAL_FUNC(errmsg_button_clicked), window);
+ 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_widget_show(menuitem);
}
-static frontend *new_window(void)
+static frontend *new_window(char *game_id, char **error)
{
frontend *fe;
GtkBox *vbox;
GtkWidget *menubar, *menu, *menuitem;
int x, y, n;
- time_t t;
fe = snew(frontend);
- time(&t);
- fe->me = midend_new(fe, &t, sizeof(t));
+ fe->me = midend_new(fe, &thegame);
+ if (game_id) {
+ *error = midend_game_id(fe->me, game_id, FALSE);
+ 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), game_name);
+ gtk_window_set_title(GTK_WINDOW(fe->window), thegame.name);
#if 0
gtk_window_set_resizable(GTK_WINDOW(fe->window), FALSE);
#else
GTK_SIGNAL_FUNC(menu_config_event), fe);
gtk_widget_show(menuitem);
- if ((n = midend_num_presets(fe->me)) > 0 || game_can_configure) {
+ if ((n = midend_num_presets(fe->me)) > 0 || thegame.can_configure) {
GtkWidget *submenu;
int i;
gtk_widget_show(menuitem);
}
- if (game_can_configure) {
+ 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));
#if 0
/* For GTK 2.0, should we be using gtk_widget_set_size_request? */
#endif
- gtk_widget_set_usize(viewport, x, req.height);
+ gtk_widget_set_usize(viewport, -1, req.height);
} else
fe->statusbar = NULL;
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), "expose_event",
GTK_SIGNAL_FUNC(expose_area), fe);
gtk_signal_connect(GTK_OBJECT(fe->window), "map_event",
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);
+ 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);
int main(int argc, char **argv)
{
- gtk_init(&argc, &argv);
- (void) new_window();
- gtk_main();
+ char *pname = argv[0];
+ char *error;
+
+ /*
+ * 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;
+ game_params *par;
+ random_state *rs;
+ char *parstr;
+
+ {
+ void *seed;
+ int seedlen;
+ get_random_seed(&seed, &seedlen);
+ rs = random_init(seed, seedlen);
+ }
+
+ if (argc > 2)
+ n = atoi(argv[2]);
+ if (argc > 3)
+ params = argv[3];
+
+ if (params)
+ par = thegame.decode_params(params);
+ else
+ par = thegame.default_params();
+ parstr = thegame.encode_params(par);
+
+ while (n-- > 0) {
+ char *seed = thegame.new_seed(par, rs);
+ printf("%s:%s\n", parstr, seed);
+ sfree(seed);
+ }
+
+ 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;
}