#include <math.h>
#include <sys/time.h>
+#include <sys/resource.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
# define USE_CAIRO
#endif
+/* #undef USE_CAIRO */
+/* #define NO_THICK_LINE */
#ifdef DEBUGGING
static FILE *debug_fp = NULL;
GtkAccelGroup *accelgroup;
GtkWidget *area;
GtkWidget *statusbar;
+ GtkWidget *menubar;
guint statusctx;
int w, h;
midend *me;
cairo_t *cr;
cairo_surface_t *image;
GdkPixmap *pixmap;
+ GdkColor background; /* for painting outside puzzle area */
#else
GdkPixmap *pixmap;
GdkGC *gc;
GdkColor *colours;
GdkColormap *colmap;
+ int backgroundindex; /* which of colours[] is background */
#endif
int ncolours;
int bbox_l, bbox_r, bbox_u, bbox_d;
#ifdef OLD_FILESEL
char *filesel_name;
#endif
+ int drawing_area_shrink_pending;
GSList *preset_radio;
int n_preset_menu_items;
int preset_threaded;
static void set_window_background(frontend *fe, int colour)
{
GdkColormap *colmap;
- GdkColor backg;
colmap = gdk_colormap_get_system();
- backg.red = fe->colours[3*colour + 0] * 65535;
- backg.green = fe->colours[3*colour + 1] * 65535;
- backg.blue = fe->colours[3*colour + 2] * 65535;
- if (!gdk_colormap_alloc_color(colmap, &backg, FALSE, FALSE)) {
+ fe->background.red = fe->colours[3*colour + 0] * 65535;
+ fe->background.green = fe->colours[3*colour + 1] * 65535;
+ fe->background.blue = fe->colours[3*colour + 2] * 65535;
+ if (!gdk_colormap_alloc_color(colmap, &fe->background, FALSE, FALSE)) {
g_error("couldn't allocate background (#%02x%02x%02x)\n",
- backg.red >> 8, backg.green >> 8, backg.blue >> 8);
+ fe->background.red >> 8, fe->background.green >> 8,
+ fe->background.blue >> 8);
}
- gdk_window_set_background(fe->area->window, &backg);
- gdk_window_set_background(fe->window->window, &backg);
+ gdk_window_set_background(fe->area->window, &fe->background);
+ gdk_window_set_background(fe->window->window, &fe->background);
}
static PangoLayout *make_pango_layout(frontend *fe)
cairo_stroke(fe->cr);
}
+static void do_draw_thick_line(frontend *fe, float thickness,
+ float x1, float y1, float x2, float y2)
+{
+ cairo_save(fe->cr);
+ cairo_set_line_width(fe->cr, thickness);
+ cairo_new_path(fe->cr);
+ cairo_move_to(fe->cr, x1, y1);
+ cairo_line_to(fe->cr, x2, y2);
+ cairo_stroke(fe->cr);
+ cairo_restore(fe->cr);
+}
+
static void do_draw_poly(frontend *fe, int *coords, int npoints,
int fillcolour, int outlinecolour)
{
fe->image = NULL;
}
-static void repaint_rectangle(frontend *fe, GtkWidget *widget,
- int x, int y, int w, int h)
-{
- gdk_draw_pixmap(widget->window,
- widget->style->fg_gc[GTK_WIDGET_STATE(fe->area)],
- fe->pixmap,
- x - fe->ox, y - fe->oy, x, y, w, h);
-}
-
#endif
/* ----------------------------------------------------------------------
static void set_window_background(frontend *fe, int colour)
{
+ fe->backgroundindex = colour;
gdk_window_set_background(fe->area->window, &fe->colours[colour]);
gdk_window_set_background(fe->window->window, &fe->colours[colour]);
}
gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2);
}
+static void do_draw_thick_line(frontend *fe, float thickness,
+ float x1, float y1, float x2, float y2)
+{
+ GdkGCValues save;
+
+ gdk_gc_get_values(fe->gc, &save);
+ gdk_gc_set_line_attributes(fe->gc,
+ thickness,
+ GDK_LINE_SOLID,
+ GDK_CAP_BUTT,
+ GDK_JOIN_BEVEL);
+ gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2);
+ gdk_gc_set_line_attributes(fe->gc,
+ save.line_width,
+ save.line_style,
+ save.cap_style,
+ save.join_style);
+}
+
static void do_draw_poly(frontend *fe, int *coords, int npoints,
int fillcolour, int outlinecolour)
{
fe->pixmap = NULL;
}
+#endif
+
static void repaint_rectangle(frontend *fe, GtkWidget *widget,
int x, int y, int w, int h)
{
- gdk_draw_pixmap(widget->window,
- widget->style->fg_gc[GTK_WIDGET_STATE(fe->area)],
- fe->pixmap,
+ GdkGC *gc = gdk_gc_new(widget->window);
+#ifdef USE_CAIRO
+ gdk_gc_set_foreground(gc, &fe->background);
+#else
+ gdk_gc_set_foreground(gc, &fe->colours[fe->backgroundindex]);
+#endif
+ if (x < fe->ox) {
+ gdk_draw_rectangle(widget->window, gc,
+ TRUE, x, y, fe->ox - x, h);
+ w -= (fe->ox - x);
+ x = fe->ox;
+ }
+ if (y < fe->oy) {
+ gdk_draw_rectangle(widget->window, gc,
+ TRUE, x, y, w, fe->oy - y);
+ h -= (fe->oy - y);
+ y = fe->oy;
+ }
+ if (w > fe->pw) {
+ gdk_draw_rectangle(widget->window, gc,
+ TRUE, x + fe->pw, y, w - fe->pw, h);
+ w = fe->pw;
+ }
+ if (h > fe->ph) {
+ gdk_draw_rectangle(widget->window, gc,
+ TRUE, x, y + fe->ph, w, h - fe->ph);
+ h = fe->ph;
+ }
+ gdk_draw_pixmap(widget->window, gc, fe->pixmap,
x - fe->ox, y - fe->oy, x, y, w, h);
+ gdk_gc_unref(gc);
}
-#endif
-
/* ----------------------------------------------------------------------
* Pango font functions.
*/
do_draw_line(fe, x1, y1, x2, y2);
}
+void gtk_draw_thick_line(void *handle, float thickness,
+ float x1, float y1, float x2, float y2, int colour)
+{
+ frontend *fe = (frontend *)handle;
+ set_colour(fe, colour);
+ do_draw_thick_line(fe, thickness, x1, y1, x2, y2);
+}
+
void gtk_draw_poly(void *handle, int *coords, int npoints,
int fillcolour, int outlinecolour)
{
#else
NULL,
#endif
+#ifdef NO_THICK_LINE
+ NULL,
+#else
+ gtk_draw_thick_line,
+#endif
};
static void destroy(GtkWidget *widget, gpointer data)
}
}
+static gboolean not_size_allocated_yet(GtkWidget *w)
+{
+ /*
+ * This function tests whether a widget has not yet taken up space
+ * on the screen which it will occupy in future. (Therefore, it
+ * returns true only if the widget does exist but does not have a
+ * size allocation. A null widget is already taking up all the
+ * space it ever will.)
+ */
+ if (!w)
+ return FALSE; /* nonexistent widgets aren't a problem */
+
+#if GTK_CHECK_VERSION(2,18,0) /* skip if no gtk_widget_get_allocation */
+ {
+ GtkAllocation a;
+ gtk_widget_get_allocation(w, &a);
+ if (a.height == 0 || a.width == 0)
+ return TRUE; /* widget exists but has no size yet */
+ }
+#endif
+
+ return FALSE;
+}
+
+static void try_shrink_drawing_area(frontend *fe)
+{
+ if (fe->drawing_area_shrink_pending &&
+ !not_size_allocated_yet(fe->menubar) &&
+ !not_size_allocated_yet(fe->statusbar)) {
+ /*
+ * In order to permit the user to resize the window smaller as
+ * well as bigger, we call this function after the window size
+ * has ended up where we want it. This shouldn't shrink the
+ * window immediately; it just arranges that the next time the
+ * user tries to shrink it, they can.
+ *
+ * However, at puzzle creation time, we defer the first of
+ * these operations until after the menu bar and status bar
+ * are actually visible. On Ubuntu 12.04 I've found that these
+ * can take a while to be displayed, and that it's a mistake
+ * to reduce the drawing area's size allocation before they've
+ * turned up or else the drawing area makes room for them by
+ * shrinking to less than the size we intended.
+ */
+ gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), 1, 1);
+ fe->drawing_area_shrink_pending = FALSE;
+ }
+}
+
+static gint configure_window(GtkWidget *widget,
+ GdkEventConfigure *event, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ /*
+ * When the main puzzle window changes size, it might be because
+ * the menu bar or status bar has turned up after starting off
+ * absent, in which case we should have another go at enacting a
+ * pending shrink of the drawing area.
+ */
+ try_shrink_drawing_area(fe);
+ return FALSE;
+}
+
static void resize_fe(frontend *fe)
{
int x, y;
get_size(fe, &x, &y);
fe->w = x;
fe->h = y;
+ fe->drawing_area_shrink_pending = FALSE;
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);
}
- /*
- * 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);
+ fe->drawing_area_shrink_pending = TRUE;
+ try_shrink_drawing_area(fe);
}
static void menu_preset_event(GtkMenuItem *menuitem, gpointer data)
{
frontend *fe;
GtkBox *vbox, *hbox;
- GtkWidget *menubar, *menu, *menuitem;
+ GtkWidget *menu, *menuitem;
GdkPixmap *iconpm;
GList *iconlist;
int x, y, n;
gtk_box_pack_start(vbox, GTK_WIDGET(hbox), FALSE, FALSE, 0);
gtk_widget_show(GTK_WIDGET(hbox));
- menubar = gtk_menu_bar_new();
- gtk_box_pack_start(hbox, menubar, TRUE, TRUE, 0);
- gtk_widget_show(menubar);
+ fe->menubar = gtk_menu_bar_new();
+ gtk_box_pack_start(hbox, fe->menubar, TRUE, TRUE, 0);
+ gtk_widget_show(fe->menubar);
menuitem = gtk_menu_item_new_with_mnemonic("_Game");
- gtk_container_add(GTK_CONTAINER(menubar), menuitem);
+ gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem);
gtk_widget_show(menuitem);
menu = gtk_menu_new();
int i;
menuitem = gtk_menu_item_new_with_mnemonic("_Type");
- gtk_container_add(GTK_CONTAINER(menubar), menuitem);
+ gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem);
gtk_widget_show(menuitem);
submenu = gtk_menu_new();
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_container_add(GTK_CONTAINER(fe->menubar), menuitem);
gtk_widget_show(menuitem);
menu = gtk_menu_new();
GTK_WIDGET_UNSET_FLAGS(fe->area, GTK_DOUBLE_BUFFERED);
#endif
get_size(fe, &x, &y);
+ fe->drawing_area_shrink_pending = FALSE;
gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
fe->w = x;
fe->h = y;
GTK_SIGNAL_FUNC(map_window), fe);
gtk_signal_connect(GTK_OBJECT(fe->area), "configure_event",
GTK_SIGNAL_FUNC(configure_area), fe);
+ gtk_signal_connect(GTK_OBJECT(fe->window), "configure_event",
+ GTK_SIGNAL_FUNC(configure_window), fe);
gtk_widget_add_events(GTK_WIDGET(fe->area),
GDK_BUTTON_PRESS_MASK |
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);
+ fe->drawing_area_shrink_pending = TRUE;
+ try_shrink_drawing_area(fe);
set_window_background(fe, 0);
return fe;
char *pname = argv[0];
char *error;
int ngenerate = 0, print = FALSE, px = 1, py = 1;
+ int time_generation = FALSE, test_solve = FALSE, list_presets = FALSE;
int soln = FALSE, colour = FALSE;
float scale = 1.0F;
float redo_proportion = 0.0F;
}
} else
ngenerate = 1;
+ } else if (doing_opts && !strcmp(p, "--time-generation")) {
+ time_generation = TRUE;
+ } else if (doing_opts && !strcmp(p, "--test-solve")) {
+ test_solve = TRUE;
+ } else if (doing_opts && !strcmp(p, "--list-presets")) {
+ list_presets = TRUE;
} else if (doing_opts && !strcmp(p, "--save")) {
if (--ac > 0) {
savefile = *++av;
}
}
- if (*errbuf) {
- fputs(errbuf, stderr);
- return 1;
- }
-
/*
* Special standalone mode for generating puzzle IDs on the
* command line. Useful for generating puzzles to be printed
char *id;
document *doc = NULL;
+ /*
+ * If we're in this branch, we should display any pending
+ * error message from the command line, since GTK isn't going
+ * to take another crack at making sense of it.
+ */
+ if (*errbuf) {
+ fputs(errbuf, stderr);
+ return 1;
+ }
+
n = ngenerate;
me = midend_new(NULL, &thegame, NULL, NULL);
* generated descriptive game IDs.)
*/
while (ngenerate == 0 || i < n) {
- char *pstr, *err;
+ char *pstr, *err, *seed;
+ struct rusage before, after;
if (ngenerate == 0) {
pstr = fgetline(stdin);
return 1;
}
}
- sfree(pstr);
- midend_new_game(me);
+ if (time_generation)
+ getrusage(RUSAGE_SELF, &before);
+
+ midend_new_game(me);
+
+ seed = midend_get_random_seed(me);
+
+ if (time_generation) {
+ double elapsed;
+
+ getrusage(RUSAGE_SELF, &after);
+
+ elapsed = (after.ru_utime.tv_sec -
+ before.ru_utime.tv_sec);
+ elapsed += (after.ru_utime.tv_usec -
+ before.ru_utime.tv_usec) / 1000000.0;
+
+ printf("%s %s: %.6f\n", thegame.name, seed, elapsed);
+ }
+
+ if (test_solve && thegame.can_solve) {
+ /*
+ * Now destroy the aux_info in the midend, by means of
+ * re-entering the same game id, and then try to solve
+ * it.
+ */
+ char *game_id, *err;
+
+ game_id = midend_get_game_id(me);
+ err = midend_game_id(me, game_id);
+ if (err) {
+ fprintf(stderr, "%s %s: game id re-entry error: %s\n",
+ thegame.name, seed, err);
+ return 1;
+ }
+ midend_new_game(me);
+ sfree(game_id);
+
+ err = midend_solve(me);
+ /*
+ * If the solve operation returned the error "Solution
+ * not known for this puzzle", that's OK, because that
+ * just means it's a puzzle for which we don't have an
+ * algorithmic solver and hence can't solve it without
+ * the aux_info, e.g. Netslide. Any other error is a
+ * problem, though.
+ */
+ if (err && strcmp(err, "Solution not known for this puzzle")) {
+ fprintf(stderr, "%s %s: solve error: %s\n",
+ thegame.name, seed, err);
+ return 1;
+ }
+ }
+
+ sfree(pstr);
+ sfree(seed);
if (doc) {
err = midend_print_puzzle(me, doc, soln);
char *realname = snewn(40 + strlen(savefile) +
strlen(savesuffix), char);
sprintf(realname, "%s%d%s", savefile, i, savesuffix);
+
+ if (soln) {
+ char *err = midend_solve(me);
+ if (err) {
+ fprintf(stderr, "%s: unable to show solution: %s\n",
+ realname, err);
+ return 1;
+ }
+ }
+
ctx.fp = fopen(realname, "w");
if (!ctx.fp) {
fprintf(stderr, "%s: open: %s\n", realname,
strerror(errno));
return 1;
}
- sfree(realname);
+ ctx.error = 0;
midend_serialise(me, savefile_write, &ctx);
if (ctx.error) {
fprintf(stderr, "%s: write: %s\n", realname,
strerror(errno));
return 1;
}
+ sfree(realname);
}
- if (!doc && !savefile) {
+ if (!doc && !savefile && !time_generation) {
id = midend_get_game_id(me);
puts(id);
sfree(id);
midend_free(me);
return 0;
+ } else if (list_presets) {
+ /*
+ * Another specialist mode which causes the puzzle to list the
+ * game_params strings for all its preset configurations.
+ */
+ int i, npresets;
+ midend *me;
+
+ me = midend_new(NULL, &thegame, NULL, NULL);
+ npresets = midend_num_presets(me);
+
+ for (i = 0; i < npresets; i++) {
+ game_params *params;
+ char *name, *paramstr;
+
+ midend_fetch_preset(me, i, &name, ¶ms);
+ paramstr = thegame.encode_params(params, TRUE);
+
+ printf("%s %s\n", paramstr, name);
+ sfree(paramstr);
+ }
+
+ midend_free(me);
+ return 0;
} else {
frontend *fe;