diff options
| author | Simon Tatham <anakin@pobox.com> | 2005-08-18 17:50:14 +0000 |
|---|---|---|
| committer | Simon Tatham <anakin@pobox.com> | 2005-08-18 17:50:14 +0000 |
| commit | af59dcf6858264103bbc621761feee3aed5aaf2a (patch) | |
| tree | dcf5ac6c317548f4ee43b6db9d78c55f4c0da8db /gtk.c | |
| parent | ca6950b0f7f7ccb3b8f86673a3626e1b4122bbb0 (diff) | |
| download | puzzles-af59dcf6858264103bbc621761feee3aed5aaf2a.zip puzzles-af59dcf6858264103bbc621761feee3aed5aaf2a.tar.gz puzzles-af59dcf6858264103bbc621761feee3aed5aaf2a.tar.bz2 puzzles-af59dcf6858264103bbc621761feee3aed5aaf2a.tar.xz | |
Substantial infrastructure upheaval. I've separated the drawing API
as seen by the back ends from the one implemented by the front end,
and shoved a piece of middleware (drawing.c) in between to permit
interchange of multiple kinds of the latter. I've also added a
number of functions to the drawing API to permit printing as well as
on-screen drawing, and retired print.py in favour of integrated
printing done by means of that API.
The immediate visible change is that print.py is dead, and each
puzzle now does its own printing: where you would previously have
typed `print.py solo 2x3', you now type `solo --print 2x3' and it
should work in much the same way.
Advantages of the new mechanism available right now:
- Map is now printable, because the new print function can make use
of the output from the existing game ID decoder rather than me
having to replicate all those fiddly algorithms in Python.
- the new print functions can cope with non-initial game states,
which means each puzzle supporting --print also supports
--with-solutions.
- there's also a --scale option permitting users to adjust the size
of the printed puzzles.
Advantages which will be available at some point:
- the new API should permit me to implement native printing
mechanisms on Windows and OS X.
[originally from svn r6190]
Diffstat (limited to 'gtk.c')
| -rw-r--r-- | gtk.c | 344 |
1 files changed, 276 insertions, 68 deletions
@@ -8,6 +8,7 @@ #include <time.h> #include <stdarg.h> #include <string.h> +#include <errno.h> #include <sys/time.h> @@ -103,7 +104,7 @@ struct frontend { int ncolours; GdkColormap *colmap; int w, h; - midend_data *me; + midend *me; GdkGC *gc; int bbox_l, bbox_r, bbox_u, bbox_d; int timer_active, timer_id; @@ -137,8 +138,9 @@ void frontend_default_colour(frontend *fe, float *output) output[2] = col.blue / 65535.0; } -void status_bar(frontend *fe, char *text) +void gtk_status_bar(void *handle, char *text) { + frontend *fe = (frontend *)handle; char *rewritten; assert(fe->statusbar); @@ -154,8 +156,9 @@ void status_bar(frontend *fe, char *text) } } -void start_draw(frontend *fe) +void gtk_start_draw(void *handle) { + frontend *fe = (frontend *)handle; fe->gc = gdk_gc_new(fe->area->window); fe->bbox_l = fe->w; fe->bbox_r = 0; @@ -163,8 +166,9 @@ void start_draw(frontend *fe) fe->bbox_d = 0; } -void clip(frontend *fe, int x, int y, int w, int h) +void gtk_clip(void *handle, int x, int y, int w, int h) { + frontend *fe = (frontend *)handle; GdkRectangle rect; rect.x = x; @@ -175,8 +179,9 @@ void clip(frontend *fe, int x, int y, int w, int h) gdk_gc_set_clip_rectangle(fe->gc, &rect); } -void unclip(frontend *fe) +void gtk_unclip(void *handle) { + frontend *fe = (frontend *)handle; GdkRectangle rect; rect.x = 0; @@ -187,9 +192,10 @@ void unclip(frontend *fe) gdk_gc_set_clip_rectangle(fe->gc, &rect); } -void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize, - int align, int colour, char *text) +void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize, + int align, int colour, char *text) { + frontend *fe = (frontend *)handle; int i; /* @@ -335,21 +341,24 @@ void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize, } -void draw_rect(frontend *fe, int x, int y, int w, int h, int colour) +void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour) { + frontend *fe = (frontend *)handle; gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); gdk_draw_rectangle(fe->pixmap, fe->gc, 1, x, y, w, h); } -void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour) +void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour) { + frontend *fe = (frontend *)handle; gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2); } -void draw_polygon(frontend *fe, int *coords, int npoints, - int fillcolour, int outlinecolour) +void gtk_draw_poly(void *handle, int *coords, int npoints, + int fillcolour, int outlinecolour) { + frontend *fe = (frontend *)handle; GdkPoint *points = snewn(npoints, GdkPoint); int i; @@ -369,9 +378,10 @@ void draw_polygon(frontend *fe, int *coords, int npoints, sfree(points); } -void draw_circle(frontend *fe, int cx, int cy, int radius, - int fillcolour, int outlinecolour) +void gtk_draw_circle(void *handle, int cx, int cy, int radius, + int fillcolour, int outlinecolour) { + frontend *fe = (frontend *)handle; if (fillcolour >= 0) { gdk_gc_set_foreground(fe->gc, &fe->colours[fillcolour]); gdk_draw_arc(fe->pixmap, fe->gc, TRUE, @@ -391,7 +401,7 @@ struct blitter { int w, h, x, y; }; -blitter *blitter_new(int w, int h) +blitter *gtk_blitter_new(void *handle, int w, int h) { /* * We can't create the pixmap right now, because fe->window @@ -405,15 +415,16 @@ blitter *blitter_new(int w, int h) return bl; } -void blitter_free(blitter *bl) +void gtk_blitter_free(void *handle, blitter *bl) { if (bl->pixmap) gdk_pixmap_unref(bl->pixmap); sfree(bl); } -void blitter_save(frontend *fe, blitter *bl, int x, int y) +void gtk_blitter_save(void *handle, blitter *bl, int x, int y) { + frontend *fe = (frontend *)handle; if (!bl->pixmap) bl->pixmap = gdk_pixmap_new(fe->area->window, bl->w, bl->h, -1); bl->x = x; @@ -424,8 +435,9 @@ void blitter_save(frontend *fe, blitter *bl, int x, int y) x, y, 0, 0, bl->w, bl->h); } -void blitter_load(frontend *fe, blitter *bl, int x, int y) +void gtk_blitter_load(void *handle, blitter *bl, int x, int y) { + frontend *fe = (frontend *)handle; assert(bl->pixmap); if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) { x = bl->x; @@ -437,16 +449,18 @@ void blitter_load(frontend *fe, blitter *bl, int x, int y) 0, 0, x, y, bl->w, bl->h); } -void draw_update(frontend *fe, int x, int y, int w, int h) +void gtk_draw_update(void *handle, int x, int y, int w, int h) { + frontend *fe = (frontend *)handle; if (fe->bbox_l > x ) fe->bbox_l = x ; if (fe->bbox_r < x+w) fe->bbox_r = x+w; if (fe->bbox_u > y ) fe->bbox_u = y ; if (fe->bbox_d < y+h) fe->bbox_d = y+h; } -void end_draw(frontend *fe) +void gtk_end_draw(void *handle) { + frontend *fe = (frontend *)handle; gdk_gc_unref(fe->gc); fe->gc = NULL; @@ -460,6 +474,26 @@ void end_draw(frontend *fe) } } +const struct drawing_api gtk_drawing = { + gtk_draw_text, + gtk_draw_rect, + gtk_draw_line, + gtk_draw_poly, + gtk_draw_circle, + gtk_draw_update, + gtk_clip, + gtk_unclip, + gtk_start_draw, + gtk_end_draw, + gtk_status_bar, + gtk_blitter_new, + gtk_blitter_free, + gtk_blitter_save, + gtk_blitter_load, + NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */ + NULL, /* line_width */ +}; + static void destroy(GtkWidget *widget, gpointer data) { frontend *fe = (frontend *)data; @@ -654,6 +688,8 @@ static gint timer_func(gpointer data) void deactivate_timer(frontend *fe) { + if (!fe) + return; /* can happen due to --generate */ if (fe->timer_active) gtk_timeout_remove(fe->timer_id); fe->timer_active = FALSE; @@ -661,6 +697,8 @@ void deactivate_timer(frontend *fe) void activate_timer(frontend *fe) { + if (!fe) + return; /* can happen due to --generate */ if (!fe->timer_active) { fe->timer_id = gtk_timeout_add(20, timer_func, fe); gettimeofday(&fe->last_time, NULL); @@ -1379,16 +1417,20 @@ static frontend *new_window(char *arg, char **error) GtkBox *vbox; GtkWidget *menubar, *menu, *menuitem; int x, y, n; + char errbuf[1024]; fe = snew(frontend); fe->timer_active = FALSE; fe->timer_id = -1; - fe->me = midend_new(fe, &thegame); + fe->me = midend_new(fe, &thegame, >k_drawing, fe); if (arg) { char *err; + + errbuf[0] = '\0'; + /* * Try treating the argument as a game ID. */ @@ -1401,14 +1443,16 @@ static frontend *new_window(char *arg, char **error) } else { FILE *fp = fopen(arg, "r"); if (!fp) { - err = "Supplied argument is neither a game ID nor a save file"; + 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); + sprintf(errbuf, "%.800s", err); fclose(fp); } } - if (err) { - *error = err; + if (*errbuf) { + *error = dupstr(errbuf); midend_free(fe->me); sfree(fe); return NULL; @@ -1647,15 +1691,133 @@ static frontend *new_window(char *arg, char **error) return fe; } +char *fgetline(FILE *fp) +{ + char *ret = snewn(512, char); + int size = 512, len = 0; + while (fgets(ret + len, size - len, fp)) { + len += strlen(ret + len); + if (ret[len-1] == '\n') + break; /* got a newline, we're done */ + size = len + 512; + ret = sresize(ret, size, char); + } + if (len == 0) { /* first fgets returned NULL */ + sfree(ret); + return NULL; + } + ret[len] = '\0'; + return ret; +} + int main(int argc, char **argv) { char *pname = argv[0]; char *error; + int ngenerate = 0, print = FALSE, px = 1, py = 1; + int soln = FALSE, colour = FALSE; + float scale = 1.0F; + char *arg = NULL; + int doing_opts = TRUE; + int ac = argc; + char **av = argv; + char errbuf[500]; - if (argc > 1 && !strcmp(argv[1], "--version")) { - printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n", - thegame.name, ver); - return 0; + /* + * Command line parsing in this function is rather fiddly, + * because GTK wants to have a go at argc/argv _first_ - and + * yet we can't let it, because gtk_init() will bomb out if it + * can't open an X display, whereas in fact we want to permit + * our --generate and --print modes to run without an X + * display. + * + * So what we do is: + * - we parse the command line ourselves, without modifying + * argc/argv + * - if we encounter an error which might plausibly be the + * result of a GTK command line (i.e. not detailed errors in + * particular options of ours) we store the error message + * and terminate parsing. + * - if we got enough out of the command line to know it + * specifies a non-X mode of operation, we either display + * the stored error and return failure, or if there is no + * stored error we do the non-X operation and return + * success. + * - otherwise, we go straight to gtk_init(). + */ + + errbuf[0] = '\0'; + while (--ac > 0) { + char *p = *++av; + if (doing_opts && !strcmp(p, "--version")) { + printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n", + thegame.name, ver); + return 0; + } else if (doing_opts && !strcmp(p, "--generate")) { + if (--ac > 0) { + ngenerate = atoi(*++av); + if (!ngenerate) { + fprintf(stderr, "%s: '--generate' expected a number\n", + pname); + return 1; + } + } else + ngenerate = 1; + } else if (doing_opts && !strcmp(p, "--print")) { + if (!thegame.can_print) { + fprintf(stderr, "%s: this game does not support printing\n", + pname); + return 1; + } + print = TRUE; + if (--ac > 0) { + char *dim = *++av; + if (sscanf(dim, "%dx%d", &px, &py) != 2) { + fprintf(stderr, "%s: unable to parse argument '%s' to " + "'--print'\n", pname, dim); + return 1; + } + } else { + px = py = 1; + } + } else if (doing_opts && !strcmp(p, "--scale")) { + if (--ac > 0) { + scale = atof(*++av); + } else { + fprintf(stderr, "%s: no argument supplied to '--scale'\n", + pname); + return 1; + } + } else if (doing_opts && (!strcmp(p, "--with-solutions") || + !strcmp(p, "--with-solution") || + !strcmp(p, "--with-solns") || + !strcmp(p, "--with-soln") || + !strcmp(p, "--solutions") || + !strcmp(p, "--solution") || + !strcmp(p, "--solns") || + !strcmp(p, "--soln"))) { + soln = TRUE; + } else if (doing_opts && !strcmp(p, "--colour")) { + if (!thegame.can_print_in_colour) { + fprintf(stderr, "%s: this game does not support colour" + " printing\n", pname); + return 1; + } + colour = TRUE; + } else if (doing_opts && !strcmp(p, "--")) { + doing_opts = FALSE; + } else if (!doing_opts || p[0] != '-') { + if (arg) { + fprintf(stderr, "%s: more than one argument supplied\n", + pname); + return 1; + } + arg = p; + } else { + sprintf(errbuf, "%.100s: unrecognised option '%.100s'\n", + pname, p); + break; + } } /* @@ -1663,7 +1825,7 @@ int main(int argc, char **argv) * 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!) + * friendly than Twiddle!) * * Usage: * @@ -1679,50 +1841,96 @@ int main(int argc, char **argv) * 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); + if (ngenerate > 0 || print) { + int i, n = 1; + midend *me; + char *id; + document *doc = NULL; + + if (*errbuf) { + fputs(errbuf, stderr); + return 1; } - 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); + n = ngenerate; + + me = midend_new(NULL, &thegame, NULL, NULL); + i = 0; + + if (print) + doc = document_new(px, py, scale); + + /* + * In this loop, we either generate a game ID or read one + * from stdin depending on whether we're in generate mode; + * then we either write it to stdout or print it, depending + * on whether we're in print mode. Thus, this loop handles + * generate-to-stdout, print-from-stdin and generate-and- + * immediately-print modes. + * + * (It could also handle a copy-stdin-to-stdout mode, + * although there's currently no combination of options + * which will cause this loop to be activated in that mode. + * It wouldn't be _entirely_ pointless, though, because + * stdin could contain bare params strings or random-seed + * IDs, and stdout would contain nothing but fully + * generated descriptive game IDs.) + */ + while (ngenerate == 0 || i < n) { + char *pstr, *err; + + if (ngenerate == 0) { + pstr = fgetline(stdin); + if (!pstr) + break; + pstr[strcspn(pstr, "\r\n")] = '\0'; + } else { + if (arg) { + pstr = snewn(strlen(arg) + 40, char); + + strcpy(pstr, arg); + if (i > 0 && strchr(arg, '#')) + sprintf(pstr + strlen(pstr), "-%d", i); + } else + pstr = NULL; + } + + if (pstr) { + err = midend_game_id(me, pstr); + if (err) { + fprintf(stderr, "%s: error parsing '%s': %s\n", + pname, pstr, err); + return 1; + } + } + sfree(pstr); + + midend_new_game(me); + + if (doc) { + err = midend_print_puzzle(me, doc, soln); + if (err) { + fprintf(stderr, "%s: error in printing: %s\n", pname, err); + return 1; + } + } else { + id = midend_get_game_id(me); + puts(id); + sfree(id); + } + + i++; } + if (doc) { + psdata *ps = ps_init(stdout, colour); + document_print(doc, ps_drawing_api(ps)); + document_free(doc); + ps_free(ps); + } + + midend_free(me); + return 0; } else { |