mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 08:01:30 -07:00
Add printing support for GTK.
Printing is only available in GTK versions >= 2.10. We can only embed the page setup dialog on GTK >= 2.18, so on a GTK version less than that, we must use a separate page setup dialog. In GTK, printing is usually done one page at a time, so also modify printing.c to allow printing of a single page at a time. Create a separate drawing API for drawing to the screen and for printing. Create a vtable for functions which need to be different depending on whether they were called from the printing or drawing API. When a function is called from the printing API, it is passed a separate instance of the frontend than if it were called from the drawing API. In that instance of the frontend, an appropriate vtable is available depending on whether it was called from the printing or drawing API. The low-level functions used for printing are enabled even if printing is not enabled. This is in case we ever need to use them for something other than printing with GTK. For example, using Cairo as a printing backend when printing from the command line. Enabling the low-level functions even when GTK printing is not available also allows them to be compiled under as many build settings as possible, and thus lowers the chance of undetected breakage. Move the definition of ROOT2 from ps.c to puzzles.h so other files can use it (gtk.c needs it for hatching). Also add myself to the copyright list. [Committer's note: by 'printing', this log message refers to the GTK GUI printing system, which handles selecting a printer, printing to a file, previewing and so on. The existing facility to generate printable puzzles in Postscript form by running the GTK binaries in command-line mode with the --print option is unaffected. -SGT]
This commit is contained in:

committed by
Simon Tatham

parent
0d77dfc415
commit
b443a84efe
4
LICENCE
4
LICENCE
@ -2,8 +2,8 @@ This software is copyright (c) 2004-2014 Simon Tatham.
|
|||||||
|
|
||||||
Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
|
Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
|
||||||
Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd
|
Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd
|
||||||
Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens and Michael
|
Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens, Michael
|
||||||
Quevillon.
|
Quevillon and Asher Gordon.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
Permission is hereby granted, free of charge, to any person
|
||||||
obtaining a copy of this software and associated documentation files
|
obtaining a copy of this software and associated documentation files
|
||||||
|
643
gtk.c
643
gtk.c
@ -44,6 +44,17 @@
|
|||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined USE_CAIRO && GTK_CHECK_VERSION(2,10,0)
|
||||||
|
/* We can only use printing if we are using Cairo for drawing and we
|
||||||
|
have a GTK version >= 2.10 (when GtkPrintOperation was added). */
|
||||||
|
# define USE_PRINTING
|
||||||
|
# if GTK_CHECK_VERSION(2,18,0)
|
||||||
|
/* We can embed the page setup. Before 2.18, we needed to have a
|
||||||
|
separate page setup. */
|
||||||
|
# define USE_EMBED_PAGE_SETUP
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#if GTK_CHECK_VERSION(3,0,0)
|
#if GTK_CHECK_VERSION(3,0,0)
|
||||||
/* The old names are still more concise! */
|
/* The old names are still more concise! */
|
||||||
#define gtk_hbox_new(x,y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL,y)
|
#define gtk_hbox_new(x,y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL,y)
|
||||||
@ -131,6 +142,18 @@ struct font {
|
|||||||
int size;
|
int size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An internal API for functions which need to be different for
|
||||||
|
* printing and drawing.
|
||||||
|
*/
|
||||||
|
struct internal_drawing_api {
|
||||||
|
void (*set_colour)(frontend *fe, int colour);
|
||||||
|
#ifdef USE_CAIRO
|
||||||
|
void (*fill)(frontend *fe);
|
||||||
|
void (*fill_preserve)(frontend *fe);
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This structure holds all the data relevant to a single window.
|
* This structure holds all the data relevant to a single window.
|
||||||
* In principle this would allow us to open multiple independent
|
* In principle this would allow us to open multiple independent
|
||||||
@ -225,6 +248,23 @@ struct frontend {
|
|||||||
*/
|
*/
|
||||||
bool awaiting_resize_ack;
|
bool awaiting_resize_ack;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_CAIRO
|
||||||
|
int printcount, printw, printh;
|
||||||
|
float printscale;
|
||||||
|
bool printsolns, printcolour;
|
||||||
|
int hatch;
|
||||||
|
float hatchthick, hatchspace;
|
||||||
|
drawing *print_dr;
|
||||||
|
document *doc;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
GtkPrintOperation *printop;
|
||||||
|
GtkPrintContext *printcontext;
|
||||||
|
GtkSpinButton *printcount_spin_button, *printw_spin_button,
|
||||||
|
*printh_spin_button, *printscale_spin_button;
|
||||||
|
GtkCheckButton *soln_check_button, *colour_check_button;
|
||||||
|
#endif
|
||||||
|
const struct internal_drawing_api *dr_api;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct blitter {
|
struct blitter {
|
||||||
@ -327,12 +367,23 @@ static void snaffle_colours(frontend *fe)
|
|||||||
fe->colours = midend_colours(fe->me, &fe->ncolours);
|
fe->colours = midend_colours(fe->me, &fe->ncolours);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_colour(frontend *fe, int colour)
|
static void draw_set_colour(frontend *fe, int colour)
|
||||||
{
|
{
|
||||||
cairo_set_source_rgb(fe->cr,
|
cairo_set_source_rgb(fe->cr,
|
||||||
fe->colours[3*colour + 0],
|
fe->colours[3*colour + 0],
|
||||||
fe->colours[3*colour + 1],
|
fe->colours[3*colour + 1],
|
||||||
fe->colours[3*colour + 2]);
|
fe->colours[3*colour + 2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_set_colour(frontend *fe, int colour)
|
||||||
|
{
|
||||||
|
float r, g, b;
|
||||||
|
|
||||||
|
print_get_colour(fe->print_dr, colour, fe->printcolour,
|
||||||
|
&(fe->hatch), &r, &g, &b);
|
||||||
|
|
||||||
|
if (fe->hatch < 0)
|
||||||
|
cairo_set_source_rgb(fe->cr, r, g, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_window_background(frontend *fe, int colour)
|
static void set_window_background(frontend *fe, int colour)
|
||||||
@ -401,6 +452,82 @@ static void save_screenshot_png(frontend *fe, const char *screenshot_file)
|
|||||||
cairo_surface_write_to_png(fe->image, screenshot_file);
|
cairo_surface_write_to_png(fe->image, screenshot_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void do_hatch(frontend *fe)
|
||||||
|
{
|
||||||
|
double i, x, y, width, height, maxdim;
|
||||||
|
|
||||||
|
/* Get the dimensions of the region to be hatched. */
|
||||||
|
cairo_path_extents(fe->cr, &x, &y, &width, &height);
|
||||||
|
|
||||||
|
maxdim = max(width, height);
|
||||||
|
|
||||||
|
cairo_save(fe->cr);
|
||||||
|
|
||||||
|
/* Set the line color and width. */
|
||||||
|
cairo_set_source_rgb(fe->cr, 0, 0, 0);
|
||||||
|
cairo_set_line_width(fe->cr, fe->hatchthick);
|
||||||
|
/* Clip to the region. */
|
||||||
|
cairo_clip(fe->cr);
|
||||||
|
/* Hatch the bounding area of the fill region. */
|
||||||
|
if (fe->hatch == HATCH_VERT || fe->hatch == HATCH_PLUS) {
|
||||||
|
for (i = 0.0; i <= width; i += fe->hatchspace) {
|
||||||
|
cairo_move_to(fe->cr, i, 0);
|
||||||
|
cairo_rel_line_to(fe->cr, 0, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fe->hatch == HATCH_HORIZ || fe->hatch == HATCH_PLUS) {
|
||||||
|
for (i = 0.0; i <= height; i += fe->hatchspace) {
|
||||||
|
cairo_move_to(fe->cr, 0, i);
|
||||||
|
cairo_rel_line_to(fe->cr, width, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fe->hatch == HATCH_SLASH || fe->hatch == HATCH_X) {
|
||||||
|
for (i = -height; i <= width; i += fe->hatchspace * ROOT2) {
|
||||||
|
cairo_move_to(fe->cr, i, 0);
|
||||||
|
cairo_rel_line_to(fe->cr, maxdim, maxdim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fe->hatch == HATCH_BACKSLASH || fe->hatch == HATCH_X) {
|
||||||
|
for (i = 0.0; i <= width + height; i += fe->hatchspace * ROOT2) {
|
||||||
|
cairo_move_to(fe->cr, i, 0);
|
||||||
|
cairo_rel_line_to(fe->cr, -maxdim, maxdim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cairo_stroke(fe->cr);
|
||||||
|
|
||||||
|
cairo_restore(fe->cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_draw_fill(frontend *fe)
|
||||||
|
{
|
||||||
|
cairo_fill(fe->cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_draw_fill_preserve(frontend *fe)
|
||||||
|
{
|
||||||
|
cairo_fill_preserve(fe->cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_print_fill(frontend *fe)
|
||||||
|
{
|
||||||
|
if (fe->hatch < 0)
|
||||||
|
cairo_fill(fe->cr);
|
||||||
|
else
|
||||||
|
do_hatch(fe);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_print_fill_preserve(frontend *fe)
|
||||||
|
{
|
||||||
|
if (fe->hatch < 0) {
|
||||||
|
cairo_fill_preserve(fe->cr);
|
||||||
|
} else {
|
||||||
|
cairo_path_t *oldpath;
|
||||||
|
oldpath = cairo_copy_path(fe->cr);
|
||||||
|
do_hatch(fe);
|
||||||
|
cairo_append_path(fe->cr, oldpath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void do_clip(frontend *fe, int x, int y, int w, int h)
|
static void do_clip(frontend *fe, int x, int y, int w, int h)
|
||||||
{
|
{
|
||||||
cairo_new_path(fe->cr);
|
cairo_new_path(fe->cr);
|
||||||
@ -419,7 +546,7 @@ static void do_draw_rect(frontend *fe, int x, int y, int w, int h)
|
|||||||
cairo_new_path(fe->cr);
|
cairo_new_path(fe->cr);
|
||||||
cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_NONE);
|
cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_NONE);
|
||||||
cairo_rectangle(fe->cr, x, y, w, h);
|
cairo_rectangle(fe->cr, x, y, w, h);
|
||||||
cairo_fill(fe->cr);
|
fe->dr_api->fill(fe);
|
||||||
cairo_restore(fe->cr);
|
cairo_restore(fe->cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,11 +580,11 @@ static void do_draw_poly(frontend *fe, int *coords, int npoints,
|
|||||||
cairo_line_to(fe->cr, coords[i*2] + 0.5, coords[i*2 + 1] + 0.5);
|
cairo_line_to(fe->cr, coords[i*2] + 0.5, coords[i*2 + 1] + 0.5);
|
||||||
cairo_close_path(fe->cr);
|
cairo_close_path(fe->cr);
|
||||||
if (fillcolour >= 0) {
|
if (fillcolour >= 0) {
|
||||||
set_colour(fe, fillcolour);
|
fe->dr_api->set_colour(fe, fillcolour);
|
||||||
cairo_fill_preserve(fe->cr);
|
fe->dr_api->fill_preserve(fe);
|
||||||
}
|
}
|
||||||
assert(outlinecolour >= 0);
|
assert(outlinecolour >= 0);
|
||||||
set_colour(fe, outlinecolour);
|
fe->dr_api->set_colour(fe, outlinecolour);
|
||||||
cairo_stroke(fe->cr);
|
cairo_stroke(fe->cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -468,11 +595,11 @@ static void do_draw_circle(frontend *fe, int cx, int cy, int radius,
|
|||||||
cairo_arc(fe->cr, cx + 0.5, cy + 0.5, radius, 0, 2*PI);
|
cairo_arc(fe->cr, cx + 0.5, cy + 0.5, radius, 0, 2*PI);
|
||||||
cairo_close_path(fe->cr); /* Just in case... */
|
cairo_close_path(fe->cr); /* Just in case... */
|
||||||
if (fillcolour >= 0) {
|
if (fillcolour >= 0) {
|
||||||
set_colour(fe, fillcolour);
|
fe->dr_api->set_colour(fe, fillcolour);
|
||||||
cairo_fill_preserve(fe->cr);
|
fe->dr_api->fill_preserve(fe);
|
||||||
}
|
}
|
||||||
assert(outlinecolour >= 0);
|
assert(outlinecolour >= 0);
|
||||||
set_colour(fe, outlinecolour);
|
fe->dr_api->set_colour(fe, outlinecolour);
|
||||||
cairo_stroke(fe->cr);
|
cairo_stroke(fe->cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -623,7 +750,7 @@ static void set_window_background(frontend *fe, int colour)
|
|||||||
gdk_window_set_background(fe->window->window, &fe->colours[colour]);
|
gdk_window_set_background(fe->window->window, &fe->colours[colour]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_colour(frontend *fe, int colour)
|
static void draw_set_colour(frontend *fe, int colour)
|
||||||
{
|
{
|
||||||
gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
|
gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
|
||||||
}
|
}
|
||||||
@ -716,11 +843,11 @@ static void do_draw_poly(frontend *fe, int *coords, int npoints,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fillcolour >= 0) {
|
if (fillcolour >= 0) {
|
||||||
set_colour(fe, fillcolour);
|
fe->dr_api->set_colour(fe, fillcolour);
|
||||||
gdk_draw_polygon(fe->pixmap, fe->gc, true, points, npoints);
|
gdk_draw_polygon(fe->pixmap, fe->gc, true, points, npoints);
|
||||||
}
|
}
|
||||||
assert(outlinecolour >= 0);
|
assert(outlinecolour >= 0);
|
||||||
set_colour(fe, outlinecolour);
|
fe->dr_api->set_colour(fe, outlinecolour);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In principle we ought to be able to use gdk_draw_polygon for
|
* In principle we ought to be able to use gdk_draw_polygon for
|
||||||
@ -740,14 +867,14 @@ static void do_draw_circle(frontend *fe, int cx, int cy, int radius,
|
|||||||
int fillcolour, int outlinecolour)
|
int fillcolour, int outlinecolour)
|
||||||
{
|
{
|
||||||
if (fillcolour >= 0) {
|
if (fillcolour >= 0) {
|
||||||
set_colour(fe, fillcolour);
|
fe->dr_api->set_colour(fe, fillcolour);
|
||||||
gdk_draw_arc(fe->pixmap, fe->gc, true,
|
gdk_draw_arc(fe->pixmap, fe->gc, true,
|
||||||
cx - radius, cy - radius,
|
cx - radius, cy - radius,
|
||||||
2 * radius, 2 * radius, 0, 360 * 64);
|
2 * radius, 2 * radius, 0, 360 * 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(outlinecolour >= 0);
|
assert(outlinecolour >= 0);
|
||||||
set_colour(fe, outlinecolour);
|
fe->dr_api->set_colour(fe, outlinecolour);
|
||||||
gdk_draw_arc(fe->pixmap, fe->gc, false,
|
gdk_draw_arc(fe->pixmap, fe->gc, false,
|
||||||
cx - radius, cy - radius,
|
cx - radius, cy - radius,
|
||||||
2 * radius, 2 * radius, 0, 360 * 64);
|
2 * radius, 2 * radius, 0, 360 * 64);
|
||||||
@ -1052,21 +1179,21 @@ void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize,
|
|||||||
/*
|
/*
|
||||||
* Do the job.
|
* Do the job.
|
||||||
*/
|
*/
|
||||||
set_colour(fe, colour);
|
fe->dr_api->set_colour(fe, colour);
|
||||||
align_and_draw_text(fe, i, align, x, y, text);
|
align_and_draw_text(fe, i, align, x, y, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gtk_draw_rect(void *handle, 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;
|
frontend *fe = (frontend *)handle;
|
||||||
set_colour(fe, colour);
|
fe->dr_api->set_colour(fe, colour);
|
||||||
do_draw_rect(fe, x, y, w, h);
|
do_draw_rect(fe, x, y, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gtk_draw_line(void *handle, 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;
|
frontend *fe = (frontend *)handle;
|
||||||
set_colour(fe, colour);
|
fe->dr_api->set_colour(fe, colour);
|
||||||
do_draw_line(fe, x1, y1, x2, y2);
|
do_draw_line(fe, x1, y1, x2, y2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1074,7 +1201,7 @@ void gtk_draw_thick_line(void *handle, float thickness,
|
|||||||
float x1, float y1, float x2, float y2, int colour)
|
float x1, float y1, float x2, float y2, int colour)
|
||||||
{
|
{
|
||||||
frontend *fe = (frontend *)handle;
|
frontend *fe = (frontend *)handle;
|
||||||
set_colour(fe, colour);
|
fe->dr_api->set_colour(fe, colour);
|
||||||
do_draw_thick_line(fe, thickness, x1, y1, x2, y2);
|
do_draw_thick_line(fe, thickness, x1, y1, x2, y2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1168,6 +1295,105 @@ char *gtk_text_fallback(void *handle, const char *const *strings, int nstrings)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
void gtk_begin_doc(void *handle, int pages)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)handle;
|
||||||
|
gtk_print_operation_set_n_pages(fe->printop, pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_begin_page(void *handle, int number)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_begin_puzzle(void *handle, float xm, float xc,
|
||||||
|
float ym, float yc, int pw, int ph, float wmm)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)handle;
|
||||||
|
double ppw, pph, pox, poy, dpmmx, dpmmy;
|
||||||
|
double scale;
|
||||||
|
|
||||||
|
ppw = gtk_print_context_get_width(fe->printcontext);
|
||||||
|
pph = gtk_print_context_get_height(fe->printcontext);
|
||||||
|
dpmmx = gtk_print_context_get_dpi_x(fe->printcontext) / 25.4;
|
||||||
|
dpmmy = gtk_print_context_get_dpi_y(fe->printcontext) / 25.4;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compute the puzzle's position in pixels on the logical page.
|
||||||
|
*/
|
||||||
|
pox = xm * ppw + xc * dpmmx;
|
||||||
|
poy = ym * pph + yc * dpmmy;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* And determine the scale.
|
||||||
|
*
|
||||||
|
* I need a scale such that the maximum puzzle-coordinate
|
||||||
|
* extent of the rectangle (pw * scale) is equal to the pixel
|
||||||
|
* equivalent of the puzzle's millimetre width (wmm * dpmmx).
|
||||||
|
*/
|
||||||
|
scale = wmm * dpmmx / pw;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now instruct Cairo to transform points based on our calculated
|
||||||
|
* values (order here *is* important).
|
||||||
|
*/
|
||||||
|
cairo_save(fe->cr);
|
||||||
|
cairo_translate(fe->cr, pox, poy);
|
||||||
|
cairo_scale(fe->cr, scale, scale);
|
||||||
|
|
||||||
|
fe->hatchthick = 0.2 * pw / wmm;
|
||||||
|
fe->hatchspace = 1.0 * pw / wmm;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_end_puzzle(void *handle)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)handle;
|
||||||
|
cairo_restore(fe->cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_end_page(void *handle, int number)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_end_doc(void *handle)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_line_width(void *handle, float width)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)handle;
|
||||||
|
cairo_set_line_width(fe->cr, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtk_line_dotted(void *handle, bool dotted)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)handle;
|
||||||
|
|
||||||
|
if (dotted) {
|
||||||
|
const double dash = 35.0;
|
||||||
|
cairo_set_dash(fe->cr, &dash, 1, 0);
|
||||||
|
} else {
|
||||||
|
cairo_set_dash(fe->cr, NULL, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* USE_PRINTING */
|
||||||
|
|
||||||
|
const struct internal_drawing_api internal_drawing = {
|
||||||
|
draw_set_colour,
|
||||||
|
#ifdef USE_CAIRO
|
||||||
|
do_draw_fill,
|
||||||
|
do_draw_fill_preserve,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef USE_CAIRO
|
||||||
|
const struct internal_drawing_api internal_printing = {
|
||||||
|
print_set_colour,
|
||||||
|
do_print_fill,
|
||||||
|
do_print_fill_preserve,
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
const struct drawing_api gtk_drawing = {
|
const struct drawing_api gtk_drawing = {
|
||||||
gtk_draw_text,
|
gtk_draw_text,
|
||||||
gtk_draw_rect,
|
gtk_draw_rect,
|
||||||
@ -1184,8 +1410,19 @@ const struct drawing_api gtk_drawing = {
|
|||||||
gtk_blitter_free,
|
gtk_blitter_free,
|
||||||
gtk_blitter_save,
|
gtk_blitter_save,
|
||||||
gtk_blitter_load,
|
gtk_blitter_load,
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
gtk_begin_doc,
|
||||||
|
gtk_begin_page,
|
||||||
|
gtk_begin_puzzle,
|
||||||
|
gtk_end_puzzle,
|
||||||
|
gtk_end_page,
|
||||||
|
gtk_end_doc,
|
||||||
|
gtk_line_width,
|
||||||
|
gtk_line_dotted,
|
||||||
|
#else
|
||||||
NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
|
NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
|
||||||
NULL, NULL, /* line_width, line_dotted */
|
NULL, NULL, /* line_width, line_dotted */
|
||||||
|
#endif
|
||||||
#ifdef USE_PANGO
|
#ifdef USE_PANGO
|
||||||
gtk_text_fallback,
|
gtk_text_fallback,
|
||||||
#else
|
#else
|
||||||
@ -1420,6 +1657,7 @@ static gint configure_area(GtkWidget *widget,
|
|||||||
GdkEventConfigure *event, gpointer data)
|
GdkEventConfigure *event, gpointer data)
|
||||||
{
|
{
|
||||||
frontend *fe = (frontend *)data;
|
frontend *fe = (frontend *)data;
|
||||||
|
|
||||||
resize_puzzle_to_area(fe, event->width, event->height);
|
resize_puzzle_to_area(fe, event->width, event->height);
|
||||||
#if GTK_CHECK_VERSION(3,0,0)
|
#if GTK_CHECK_VERSION(3,0,0)
|
||||||
fe->awaiting_resize_ack = false;
|
fe->awaiting_resize_ack = false;
|
||||||
@ -2252,6 +2490,317 @@ static char *file_selector(frontend *fe, const char *title, bool save)
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
GObject *create_print_widget(GtkPrintOperation *print, gpointer data)
|
||||||
|
{
|
||||||
|
GtkLabel *count_label, *width_label, *height_label,
|
||||||
|
*scale_llabel, *scale_rlabel;
|
||||||
|
GtkBox *scale_hbox;
|
||||||
|
GtkWidget *grid;
|
||||||
|
frontend *fe = (frontend *)data;
|
||||||
|
|
||||||
|
fe->printcount_spin_button =
|
||||||
|
GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 999, 1));
|
||||||
|
gtk_spin_button_set_numeric(fe->printcount_spin_button, true);
|
||||||
|
gtk_spin_button_set_snap_to_ticks(fe->printcount_spin_button, true);
|
||||||
|
fe->printw_spin_button =
|
||||||
|
GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 99, 1));
|
||||||
|
gtk_spin_button_set_numeric(fe->printw_spin_button, true);
|
||||||
|
gtk_spin_button_set_snap_to_ticks(fe->printw_spin_button, true);
|
||||||
|
fe->printh_spin_button =
|
||||||
|
GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 99, 1));
|
||||||
|
gtk_spin_button_set_numeric(fe->printh_spin_button, true);
|
||||||
|
gtk_spin_button_set_snap_to_ticks(fe->printh_spin_button, true);
|
||||||
|
fe->printscale_spin_button =
|
||||||
|
GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 1000, 1));
|
||||||
|
gtk_spin_button_set_digits(fe->printscale_spin_button, 1);
|
||||||
|
gtk_spin_button_set_numeric(fe->printscale_spin_button, true);
|
||||||
|
if (thegame.can_solve) {
|
||||||
|
fe->soln_check_button =
|
||||||
|
GTK_CHECK_BUTTON(
|
||||||
|
gtk_check_button_new_with_label("Print solutions"));
|
||||||
|
}
|
||||||
|
if (thegame.can_print_in_colour) {
|
||||||
|
fe->colour_check_button =
|
||||||
|
GTK_CHECK_BUTTON(
|
||||||
|
gtk_check_button_new_with_label("Print in color"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set defaults to what was selected last time. */
|
||||||
|
gtk_spin_button_set_value(fe->printcount_spin_button,
|
||||||
|
(gdouble)fe->printcount);
|
||||||
|
gtk_spin_button_set_value(fe->printw_spin_button,
|
||||||
|
(gdouble)fe->printw);
|
||||||
|
gtk_spin_button_set_value(fe->printh_spin_button,
|
||||||
|
(gdouble)fe->printh);
|
||||||
|
gtk_spin_button_set_value(fe->printscale_spin_button,
|
||||||
|
(gdouble)fe->printscale);
|
||||||
|
if (thegame.can_solve) {
|
||||||
|
gtk_toggle_button_set_active(
|
||||||
|
GTK_TOGGLE_BUTTON(fe->soln_check_button), fe->printsolns);
|
||||||
|
}
|
||||||
|
if (thegame.can_print_in_colour) {
|
||||||
|
gtk_toggle_button_set_active(
|
||||||
|
GTK_TOGGLE_BUTTON(fe->colour_check_button), fe->printcolour);
|
||||||
|
}
|
||||||
|
|
||||||
|
count_label = GTK_LABEL(gtk_label_new("Puzzles to print:"));
|
||||||
|
width_label = GTK_LABEL(gtk_label_new("Puzzles across:"));
|
||||||
|
height_label = GTK_LABEL(gtk_label_new("Puzzles down:"));
|
||||||
|
scale_llabel = GTK_LABEL(gtk_label_new("Puzzle scale:"));
|
||||||
|
scale_rlabel = GTK_LABEL(gtk_label_new("%"));
|
||||||
|
#if GTK_CHECK_VERSION(3,0,0)
|
||||||
|
gtk_widget_set_halign(GTK_WIDGET(count_label), GTK_ALIGN_START);
|
||||||
|
gtk_widget_set_halign(GTK_WIDGET(width_label), GTK_ALIGN_START);
|
||||||
|
gtk_widget_set_halign(GTK_WIDGET(height_label), GTK_ALIGN_START);
|
||||||
|
gtk_widget_set_halign(GTK_WIDGET(scale_llabel), GTK_ALIGN_START);
|
||||||
|
#else
|
||||||
|
gtk_misc_set_alignment(GTK_MISC(count_label), 0, 0);
|
||||||
|
gtk_misc_set_alignment(GTK_MISC(width_label), 0, 0);
|
||||||
|
gtk_misc_set_alignment(GTK_MISC(height_label), 0, 0);
|
||||||
|
gtk_misc_set_alignment(GTK_MISC(scale_llabel), 0, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
scale_hbox = GTK_BOX(gtk_hbox_new(false, 6));
|
||||||
|
gtk_box_pack_start(scale_hbox, GTK_WIDGET(fe->printscale_spin_button),
|
||||||
|
false, false, 0);
|
||||||
|
gtk_box_pack_start(scale_hbox, GTK_WIDGET(scale_rlabel),
|
||||||
|
false, false, 0);
|
||||||
|
|
||||||
|
#if GTK_CHECK_VERSION(3,0,0)
|
||||||
|
grid = gtk_grid_new();
|
||||||
|
gtk_grid_set_column_spacing(GTK_GRID(grid), 18);
|
||||||
|
gtk_grid_set_row_spacing(GTK_GRID(grid), 18);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(count_label), 0, 0, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(width_label), 0, 1, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(height_label), 0, 2, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(scale_llabel), 0, 3, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printcount_spin_button),
|
||||||
|
1, 0, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printw_spin_button),
|
||||||
|
1, 1, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printh_spin_button),
|
||||||
|
1, 2, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(scale_hbox), 1, 3, 1, 1);
|
||||||
|
if (thegame.can_solve) {
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->soln_check_button),
|
||||||
|
0, 4, 1, 1);
|
||||||
|
}
|
||||||
|
if (thegame.can_print_in_colour) {
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->colour_check_button),
|
||||||
|
thegame.can_solve, 4, 1, 1);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
grid = gtk_table_new((thegame.can_solve || thegame.can_print_in_colour) ?
|
||||||
|
5 : 4, 2, false);
|
||||||
|
gtk_table_set_col_spacings(GTK_TABLE(grid), 18);
|
||||||
|
gtk_table_set_row_spacings(GTK_TABLE(grid), 18);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(count_label), 0, 1, 0, 1,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(width_label), 0, 1, 1, 2,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(height_label), 0, 1, 2, 3,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(scale_llabel), 0, 1, 3, 4,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printcount_spin_button),
|
||||||
|
1, 2, 0, 1,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printw_spin_button),
|
||||||
|
1, 2, 1, 2,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printh_spin_button),
|
||||||
|
1, 2, 2, 3,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(scale_hbox), 1, 2, 3, 4,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
if (thegame.can_solve) {
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->soln_check_button),
|
||||||
|
0, 1, 4, 5,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
}
|
||||||
|
if (thegame.can_print_in_colour) {
|
||||||
|
gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->colour_check_button),
|
||||||
|
thegame.can_solve, thegame.can_solve + 1, 4, 5,
|
||||||
|
GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
gtk_container_set_border_width(GTK_CONTAINER(grid), 12);
|
||||||
|
|
||||||
|
gtk_widget_show_all(grid);
|
||||||
|
|
||||||
|
return G_OBJECT(grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply_print_widget(GtkPrintOperation *print,
|
||||||
|
GtkWidget *widget, gpointer data)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)data;
|
||||||
|
|
||||||
|
/* We ignore `widget' because it is easier and faster to store the
|
||||||
|
widgets we need in `fe' then to get the children of `widget'. */
|
||||||
|
fe->printcount =
|
||||||
|
gtk_spin_button_get_value_as_int(fe->printcount_spin_button);
|
||||||
|
fe->printw = gtk_spin_button_get_value_as_int(fe->printw_spin_button);
|
||||||
|
fe->printh = gtk_spin_button_get_value_as_int(fe->printh_spin_button);
|
||||||
|
fe->printscale = gtk_spin_button_get_value(fe->printscale_spin_button);
|
||||||
|
if (thegame.can_solve) {
|
||||||
|
fe->printsolns =
|
||||||
|
gtk_toggle_button_get_active(
|
||||||
|
GTK_TOGGLE_BUTTON(fe->soln_check_button));
|
||||||
|
}
|
||||||
|
if (thegame.can_print_in_colour) {
|
||||||
|
fe->printcolour =
|
||||||
|
gtk_toggle_button_get_active(
|
||||||
|
GTK_TOGGLE_BUTTON(fe->colour_check_button));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_begin(GtkPrintOperation *printop,
|
||||||
|
GtkPrintContext *context, gpointer data)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)data;
|
||||||
|
midend *nme = NULL; /* non-interactive midend for bulk puzzle generation */
|
||||||
|
int i;
|
||||||
|
|
||||||
|
fe->printcontext = context;
|
||||||
|
fe->cr = gtk_print_context_get_cairo_context(context);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create our document structure and fill it up with puzzles.
|
||||||
|
*/
|
||||||
|
fe->doc = document_new(fe->printw, fe->printh, fe->printscale / 100.0F);
|
||||||
|
|
||||||
|
for (i = 0; i < fe->printcount; i++) {
|
||||||
|
const char *err;
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
err = midend_print_puzzle(fe->me, fe->doc, fe->printsolns);
|
||||||
|
} else {
|
||||||
|
if (!nme) {
|
||||||
|
game_params *params;
|
||||||
|
|
||||||
|
nme = midend_new(NULL, &thegame, NULL, NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the non-interactive mid-end to have the same
|
||||||
|
* parameters as the standard one.
|
||||||
|
*/
|
||||||
|
params = midend_get_params(fe->me);
|
||||||
|
midend_set_params(nme, params);
|
||||||
|
thegame.free_params(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
midend_new_game(nme);
|
||||||
|
err = midend_print_puzzle(nme, fe->doc, fe->printsolns);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
error_box(fe->window, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nme)
|
||||||
|
midend_free(nme);
|
||||||
|
|
||||||
|
/* Begin the document. */
|
||||||
|
document_begin(fe->doc, fe->print_dr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_page(GtkPrintOperation *printop,
|
||||||
|
GtkPrintContext *context,
|
||||||
|
gint page_nr, gpointer data)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)data;
|
||||||
|
document_print_page(fe->doc, fe->print_dr, page_nr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_end(GtkPrintOperation *printop,
|
||||||
|
GtkPrintContext *context, gpointer data)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)data;
|
||||||
|
|
||||||
|
/* End and free the document. */
|
||||||
|
document_end(fe->doc, fe->print_dr);
|
||||||
|
document_free(fe->doc);
|
||||||
|
fe->doc = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_dialog(frontend *fe)
|
||||||
|
{
|
||||||
|
GError *error;
|
||||||
|
static GtkPrintSettings *settings = NULL;
|
||||||
|
static GtkPageSetup *page_setup = NULL;
|
||||||
|
#ifndef USE_EMBED_PAGE_SETUP
|
||||||
|
GtkPageSetup *new_page_setup;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fe->printop = gtk_print_operation_new();
|
||||||
|
gtk_print_operation_set_use_full_page(fe->printop, true);
|
||||||
|
gtk_print_operation_set_custom_tab_label(fe->printop, "Puzzle Settings");
|
||||||
|
g_signal_connect(fe->printop, "create-custom-widget",
|
||||||
|
G_CALLBACK(create_print_widget), fe);
|
||||||
|
g_signal_connect(fe->printop, "custom-widget-apply",
|
||||||
|
G_CALLBACK(apply_print_widget), fe);
|
||||||
|
g_signal_connect(fe->printop, "begin-print", G_CALLBACK(print_begin), fe);
|
||||||
|
g_signal_connect(fe->printop, "draw-page", G_CALLBACK(draw_page), fe);
|
||||||
|
g_signal_connect(fe->printop, "end-print", G_CALLBACK(print_end), fe);
|
||||||
|
#ifdef USE_EMBED_PAGE_SETUP
|
||||||
|
gtk_print_operation_set_embed_page_setup(fe->printop, true);
|
||||||
|
#else
|
||||||
|
if (page_setup == NULL) {
|
||||||
|
page_setup =
|
||||||
|
g_object_ref(
|
||||||
|
gtk_print_operation_get_default_page_setup(fe->printop));
|
||||||
|
}
|
||||||
|
if (settings == NULL) {
|
||||||
|
settings =
|
||||||
|
g_object_ref(gtk_print_operation_get_print_settings(fe->printop));
|
||||||
|
}
|
||||||
|
new_page_setup = gtk_print_run_page_setup_dialog(GTK_WINDOW(fe->window),
|
||||||
|
page_setup, settings);
|
||||||
|
g_object_unref(page_setup);
|
||||||
|
page_setup = new_page_setup;
|
||||||
|
gtk_print_operation_set_default_page_setup(fe->printop, page_setup);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (settings != NULL)
|
||||||
|
gtk_print_operation_set_print_settings(fe->printop, settings);
|
||||||
|
if (page_setup != NULL)
|
||||||
|
gtk_print_operation_set_default_page_setup(fe->printop, page_setup);
|
||||||
|
|
||||||
|
switch (gtk_print_operation_run(fe->printop,
|
||||||
|
GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
|
||||||
|
GTK_WINDOW(fe->window), &error)) {
|
||||||
|
case GTK_PRINT_OPERATION_RESULT_ERROR:
|
||||||
|
error_box(fe->window, error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
break;
|
||||||
|
case GTK_PRINT_OPERATION_RESULT_APPLY:
|
||||||
|
if (settings != NULL)
|
||||||
|
g_object_unref(settings);
|
||||||
|
settings =
|
||||||
|
g_object_ref(gtk_print_operation_get_print_settings(fe->printop));
|
||||||
|
#ifdef USE_EMBED_PAGE_SETUP
|
||||||
|
if (page_setup != NULL)
|
||||||
|
g_object_unref(page_setup);
|
||||||
|
page_setup =
|
||||||
|
g_object_ref(
|
||||||
|
gtk_print_operation_get_default_page_setup(fe->printop));
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Don't error out on -Werror=switch. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref(fe->printop);
|
||||||
|
fe->printop = NULL;
|
||||||
|
fe->printcontext = NULL;
|
||||||
|
}
|
||||||
|
#endif /* USE_PRINTING */
|
||||||
|
|
||||||
struct savefile_write_ctx {
|
struct savefile_write_ctx {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
int error;
|
int error;
|
||||||
@ -2353,6 +2902,15 @@ static void menu_load_event(GtkMenuItem *menuitem, gpointer data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
static void menu_print_event(GtkMenuItem *menuitem, gpointer data)
|
||||||
|
{
|
||||||
|
frontend *fe = (frontend *)data;
|
||||||
|
|
||||||
|
print_dialog(fe);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void menu_solve_event(GtkMenuItem *menuitem, gpointer data)
|
static void menu_solve_event(GtkMenuItem *menuitem, gpointer data)
|
||||||
{
|
{
|
||||||
frontend *fe = (frontend *)data;
|
frontend *fe = (frontend *)data;
|
||||||
@ -2509,6 +3067,9 @@ static frontend *new_window(
|
|||||||
char *arg, int argtype, char **error, bool headless)
|
char *arg, int argtype, char **error, bool headless)
|
||||||
{
|
{
|
||||||
frontend *fe;
|
frontend *fe;
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
frontend *print_fe = NULL;
|
||||||
|
#endif
|
||||||
GtkBox *vbox, *hbox;
|
GtkBox *vbox, *hbox;
|
||||||
GtkWidget *menu, *menuitem;
|
GtkWidget *menu, *menuitem;
|
||||||
GList *iconlist;
|
GList *iconlist;
|
||||||
@ -2535,6 +3096,32 @@ static frontend *new_window(
|
|||||||
|
|
||||||
fe->me = midend_new(fe, &thegame, >k_drawing, fe);
|
fe->me = midend_new(fe, &thegame, >k_drawing, fe);
|
||||||
|
|
||||||
|
fe->dr_api = &internal_drawing;
|
||||||
|
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
if (thegame.can_print) {
|
||||||
|
print_fe = snew(frontend);
|
||||||
|
memset(print_fe, 0, sizeof(frontend));
|
||||||
|
|
||||||
|
/* Defaults */
|
||||||
|
print_fe->printcount = print_fe->printw = print_fe->printh = 1;
|
||||||
|
print_fe->printscale = 100;
|
||||||
|
print_fe->printsolns = false;
|
||||||
|
print_fe->printcolour = thegame.can_print_in_colour;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We need to use the same midend as the main frontend because
|
||||||
|
* we need midend_print_puzzle() to be able to print the
|
||||||
|
* current puzzle.
|
||||||
|
*/
|
||||||
|
print_fe->me = fe->me;
|
||||||
|
|
||||||
|
print_fe->print_dr = drawing_new(>k_drawing, print_fe->me, print_fe);
|
||||||
|
|
||||||
|
print_fe->dr_api = &internal_printing;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (arg) {
|
if (arg) {
|
||||||
const char *err;
|
const char *err;
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
@ -2588,6 +3175,12 @@ static frontend *new_window(
|
|||||||
*error = dupstr(errbuf);
|
*error = dupstr(errbuf);
|
||||||
midend_free(fe->me);
|
midend_free(fe->me);
|
||||||
sfree(fe);
|
sfree(fe);
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
if (thegame.can_print) {
|
||||||
|
drawing_free(print_fe->print_dr);
|
||||||
|
sfree(print_fe);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2731,6 +3324,16 @@ static frontend *new_window(
|
|||||||
g_signal_connect(G_OBJECT(menuitem), "activate",
|
g_signal_connect(G_OBJECT(menuitem), "activate",
|
||||||
G_CALLBACK(menu_save_event), fe);
|
G_CALLBACK(menu_save_event), fe);
|
||||||
gtk_widget_show(menuitem);
|
gtk_widget_show(menuitem);
|
||||||
|
#ifdef USE_PRINTING
|
||||||
|
if (thegame.can_print) {
|
||||||
|
add_menu_separator(GTK_CONTAINER(menu));
|
||||||
|
menuitem = gtk_menu_item_new_with_label("Print...");
|
||||||
|
gtk_container_add(GTK_CONTAINER(menu), menuitem);
|
||||||
|
g_signal_connect(G_OBJECT(menuitem), "activate",
|
||||||
|
G_CALLBACK(menu_print_event), print_fe);
|
||||||
|
gtk_widget_show(menuitem);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifndef STYLUS_BASED
|
#ifndef STYLUS_BASED
|
||||||
add_menu_separator(GTK_CONTAINER(menu));
|
add_menu_separator(GTK_CONTAINER(menu));
|
||||||
add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0);
|
add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0);
|
||||||
|
280
printing.c
280
printing.c
@ -3,6 +3,8 @@
|
|||||||
* setup and layout.
|
* setup and layout.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#include "puzzles.h"
|
#include "puzzles.h"
|
||||||
|
|
||||||
struct puzzle {
|
struct puzzle {
|
||||||
@ -115,133 +117,177 @@ static void get_puzzle_size(document *doc, struct puzzle *pz,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Having accumulated a load of puzzles, actually do the printing.
|
* Calculate the the number of pages for a document.
|
||||||
*/
|
*/
|
||||||
void document_print(document *doc, drawing *dr)
|
int document_npages(document *doc)
|
||||||
{
|
{
|
||||||
int ppp; /* puzzles per page */
|
int ppp; /* puzzles per page */
|
||||||
int pages, passes;
|
int pages, passes;
|
||||||
int page, pass;
|
|
||||||
int pageno;
|
|
||||||
|
|
||||||
ppp = doc->pw * doc->ph;
|
ppp = doc->pw * doc->ph;
|
||||||
pages = (doc->npuzzles + ppp - 1) / ppp;
|
pages = (doc->npuzzles + ppp - 1) / ppp;
|
||||||
passes = (doc->got_solns ? 2 : 1);
|
passes = (doc->got_solns ? 2 : 1);
|
||||||
|
|
||||||
print_begin_doc(dr, pages * passes);
|
return pages * passes;
|
||||||
|
}
|
||||||
|
|
||||||
pageno = 1;
|
/*
|
||||||
for (pass = 0; pass < passes; pass++) {
|
* Begin a document.
|
||||||
for (page = 0; page < pages; page++) {
|
*/
|
||||||
int i, n, offset;
|
void document_begin(document *doc, drawing *dr)
|
||||||
float colsum, rowsum;
|
{
|
||||||
|
print_begin_doc(dr, document_npages(doc));
|
||||||
print_begin_page(dr, pageno);
|
}
|
||||||
|
|
||||||
offset = page * ppp;
|
|
||||||
n = min(ppp, doc->npuzzles - offset);
|
|
||||||
|
|
||||||
for (i = 0; i < doc->pw; i++)
|
|
||||||
doc->colwid[i] = 0;
|
|
||||||
for (i = 0; i < doc->ph; i++)
|
|
||||||
doc->rowht[i] = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Lay the page out by computing all the puzzle sizes.
|
|
||||||
*/
|
|
||||||
for (i = 0; i < n; i++) {
|
|
||||||
struct puzzle *pz = doc->puzzles + offset + i;
|
|
||||||
int x = i % doc->pw, y = i / doc->pw;
|
|
||||||
float w, h, scale;
|
|
||||||
|
|
||||||
get_puzzle_size(doc, pz, &w, &h, &scale);
|
|
||||||
|
|
||||||
/* Update the maximum width/height of this column. */
|
|
||||||
doc->colwid[x] = max(doc->colwid[x], w);
|
|
||||||
doc->rowht[y] = max(doc->rowht[y], h);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add up the maximum column/row widths to get the
|
|
||||||
* total amount of space used up by puzzles on the
|
|
||||||
* page. We will use this to compute gutter widths.
|
|
||||||
*/
|
|
||||||
colsum = 0.0;
|
|
||||||
for (i = 0; i < doc->pw; i++)
|
|
||||||
colsum += doc->colwid[i];
|
|
||||||
rowsum = 0.0;
|
|
||||||
for (i = 0; i < doc->ph; i++)
|
|
||||||
rowsum += doc->rowht[i];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Now do the printing.
|
|
||||||
*/
|
|
||||||
for (i = 0; i < n; i++) {
|
|
||||||
struct puzzle *pz = doc->puzzles + offset + i;
|
|
||||||
int x = i % doc->pw, y = i / doc->pw, j;
|
|
||||||
float w, h, scale, xm, xc, ym, yc;
|
|
||||||
int pixw, pixh, tilesize;
|
|
||||||
|
|
||||||
if (pass == 1 && !pz->st2)
|
|
||||||
continue; /* nothing to do */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The total amount of gutter space is the page
|
|
||||||
* width minus colsum. This is divided into pw+1
|
|
||||||
* gutters, so the amount of horizontal gutter
|
|
||||||
* space appearing to the left of this puzzle
|
|
||||||
* column is
|
|
||||||
*
|
|
||||||
* (width-colsum) * (x+1)/(pw+1)
|
|
||||||
* = width * (x+1)/(pw+1) - (colsum * (x+1)/(pw+1))
|
|
||||||
*/
|
|
||||||
xm = (float)(x+1) / (doc->pw + 1);
|
|
||||||
xc = -xm * colsum;
|
|
||||||
/* And similarly for y. */
|
|
||||||
ym = (float)(y+1) / (doc->ph + 1);
|
|
||||||
yc = -ym * rowsum;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* However, the amount of space to the left of this
|
|
||||||
* puzzle isn't just gutter space: we must also
|
|
||||||
* count the widths of all the previous columns.
|
|
||||||
*/
|
|
||||||
for (j = 0; j < x; j++)
|
|
||||||
xc += doc->colwid[j];
|
|
||||||
/* And similarly for rows. */
|
|
||||||
for (j = 0; j < y; j++)
|
|
||||||
yc += doc->rowht[j];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Now we adjust for this _specific_ puzzle, which
|
|
||||||
* means centring it within the cell we've just
|
|
||||||
* computed.
|
|
||||||
*/
|
|
||||||
get_puzzle_size(doc, pz, &w, &h, &scale);
|
|
||||||
xc += (doc->colwid[x] - w) / 2;
|
|
||||||
yc += (doc->rowht[y] - h) / 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* And now we know where and how big we want to
|
|
||||||
* print the puzzle, just go ahead and do so. For
|
|
||||||
* the moment I'll pick a standard pixel tile size
|
|
||||||
* of 512.
|
|
||||||
*
|
|
||||||
* (FIXME: would it be better to pick this value
|
|
||||||
* with reference to the printer resolution? Or
|
|
||||||
* permit each game to choose its own?)
|
|
||||||
*/
|
|
||||||
tilesize = 512;
|
|
||||||
pz->game->compute_size(pz->par, tilesize, &pixw, &pixh);
|
|
||||||
print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale);
|
|
||||||
pz->game->print(dr, pass == 0 ? pz->st : pz->st2, tilesize);
|
|
||||||
print_end_puzzle(dr);
|
|
||||||
}
|
|
||||||
|
|
||||||
print_end_page(dr, pageno);
|
|
||||||
pageno++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End a document.
|
||||||
|
*/
|
||||||
|
void document_end(document *doc, drawing *dr)
|
||||||
|
{
|
||||||
|
print_end_doc(dr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a single page of a document.
|
||||||
|
*/
|
||||||
|
void document_print_page(document *doc, drawing *dr, int page_nr)
|
||||||
|
{
|
||||||
|
int ppp; /* puzzles per page */
|
||||||
|
int pages;
|
||||||
|
int page, pass;
|
||||||
|
int pageno;
|
||||||
|
int i, n, offset;
|
||||||
|
float colsum, rowsum;
|
||||||
|
|
||||||
|
ppp = doc->pw * doc->ph;
|
||||||
|
pages = (doc->npuzzles + ppp - 1) / ppp;
|
||||||
|
|
||||||
|
/* Get the current page, pass, and pageno based on page_nr. */
|
||||||
|
if (page_nr < pages) {
|
||||||
|
page = page_nr;
|
||||||
|
pass = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(doc->got_solns);
|
||||||
|
page = page_nr - pages;
|
||||||
|
pass = 1;
|
||||||
|
}
|
||||||
|
pageno = page_nr + 1;
|
||||||
|
|
||||||
|
offset = page * ppp;
|
||||||
|
n = min(ppp, doc->npuzzles - offset);
|
||||||
|
|
||||||
|
print_begin_page(dr, pageno);
|
||||||
|
|
||||||
|
for (i = 0; i < doc->pw; i++)
|
||||||
|
doc->colwid[i] = 0;
|
||||||
|
for (i = 0; i < doc->ph; i++)
|
||||||
|
doc->rowht[i] = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lay the page out by computing all the puzzle sizes.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
struct puzzle *pz = doc->puzzles + offset + i;
|
||||||
|
int x = i % doc->pw, y = i / doc->pw;
|
||||||
|
float w, h, scale;
|
||||||
|
|
||||||
|
get_puzzle_size(doc, pz, &w, &h, &scale);
|
||||||
|
|
||||||
|
/* Update the maximum width/height of this column. */
|
||||||
|
doc->colwid[x] = max(doc->colwid[x], w);
|
||||||
|
doc->rowht[y] = max(doc->rowht[y], h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add up the maximum column/row widths to get the
|
||||||
|
* total amount of space used up by puzzles on the
|
||||||
|
* page. We will use this to compute gutter widths.
|
||||||
|
*/
|
||||||
|
colsum = 0.0;
|
||||||
|
for (i = 0; i < doc->pw; i++)
|
||||||
|
colsum += doc->colwid[i];
|
||||||
|
rowsum = 0.0;
|
||||||
|
for (i = 0; i < doc->ph; i++)
|
||||||
|
rowsum += doc->rowht[i];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now do the printing.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
struct puzzle *pz = doc->puzzles + offset + i;
|
||||||
|
int x = i % doc->pw, y = i / doc->pw, j;
|
||||||
|
float w, h, scale, xm, xc, ym, yc;
|
||||||
|
int pixw, pixh, tilesize;
|
||||||
|
|
||||||
|
if (pass == 1 && !pz->st2)
|
||||||
|
continue; /* nothing to do */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The total amount of gutter space is the page
|
||||||
|
* width minus colsum. This is divided into pw+1
|
||||||
|
* gutters, so the amount of horizontal gutter
|
||||||
|
* space appearing to the left of this puzzle
|
||||||
|
* column is
|
||||||
|
*
|
||||||
|
* (width-colsum) * (x+1)/(pw+1)
|
||||||
|
* = width * (x+1)/(pw+1) - (colsum * (x+1)/(pw+1))
|
||||||
|
*/
|
||||||
|
xm = (float)(x+1) / (doc->pw + 1);
|
||||||
|
xc = -xm * colsum;
|
||||||
|
/* And similarly for y. */
|
||||||
|
ym = (float)(y+1) / (doc->ph + 1);
|
||||||
|
yc = -ym * rowsum;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* However, the amount of space to the left of this
|
||||||
|
* puzzle isn't just gutter space: we must also
|
||||||
|
* count the widths of all the previous columns.
|
||||||
|
*/
|
||||||
|
for (j = 0; j < x; j++)
|
||||||
|
xc += doc->colwid[j];
|
||||||
|
/* And similarly for rows. */
|
||||||
|
for (j = 0; j < y; j++)
|
||||||
|
yc += doc->rowht[j];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now we adjust for this _specific_ puzzle, which
|
||||||
|
* means centring it within the cell we've just
|
||||||
|
* computed.
|
||||||
|
*/
|
||||||
|
get_puzzle_size(doc, pz, &w, &h, &scale);
|
||||||
|
xc += (doc->colwid[x] - w) / 2;
|
||||||
|
yc += (doc->rowht[y] - h) / 2;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* And now we know where and how big we want to
|
||||||
|
* print the puzzle, just go ahead and do so. For
|
||||||
|
* the moment I'll pick a standard pixel tile size
|
||||||
|
* of 512.
|
||||||
|
*
|
||||||
|
* (FIXME: would it be better to pick this value
|
||||||
|
* with reference to the printer resolution? Or
|
||||||
|
* permit each game to choose its own?)
|
||||||
|
*/
|
||||||
|
tilesize = 512;
|
||||||
|
pz->game->compute_size(pz->par, tilesize, &pixw, &pixh);
|
||||||
|
print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale);
|
||||||
|
pz->game->print(dr, pass == 0 ? pz->st : pz->st2, tilesize);
|
||||||
|
print_end_puzzle(dr);
|
||||||
|
}
|
||||||
|
|
||||||
|
print_end_page(dr, pageno);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Having accumulated a load of puzzles, actually do the printing.
|
||||||
|
*/
|
||||||
|
void document_print(document *doc, drawing *dr)
|
||||||
|
{
|
||||||
|
int page, pages;
|
||||||
|
pages = document_npages(doc);
|
||||||
|
print_begin_doc(dr, pages);
|
||||||
|
for (page = 0; page < pages; page++)
|
||||||
|
document_print_page(doc, dr, page);
|
||||||
print_end_doc(dr);
|
print_end_doc(dr);
|
||||||
}
|
}
|
||||||
|
2
ps.c
2
ps.c
@ -9,8 +9,6 @@
|
|||||||
|
|
||||||
#include "puzzles.h"
|
#include "puzzles.h"
|
||||||
|
|
||||||
#define ROOT2 1.414213562
|
|
||||||
|
|
||||||
struct psdata {
|
struct psdata {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
bool colour;
|
bool colour;
|
||||||
|
@ -3360,8 +3360,8 @@ This software is \i{copyright} 2004-2014 Simon Tatham.
|
|||||||
|
|
||||||
Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
|
Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
|
||||||
K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou,
|
K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou,
|
||||||
Bernd Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens and
|
Bernd Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens, Michael
|
||||||
Michael Quevillon.
|
Quevillon and Asher Gordon.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
Permission is hereby granted, free of charge, to any person
|
||||||
obtaining a copy of this software and associated documentation files
|
obtaining a copy of this software and associated documentation files
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#define PI 3.141592653589793238462643383279502884197169399
|
#define PI 3.141592653589793238462643383279502884197169399
|
||||||
|
#define ROOT2 1.414213562373095048801688724209698078569672
|
||||||
|
|
||||||
#define lenof(array) ( sizeof(array) / sizeof(*(array)) )
|
#define lenof(array) ( sizeof(array) / sizeof(*(array)) )
|
||||||
|
|
||||||
@ -526,6 +527,10 @@ document *document_new(int pw, int ph, float userscale);
|
|||||||
void document_free(document *doc);
|
void document_free(document *doc);
|
||||||
void document_add_puzzle(document *doc, const game *game, game_params *par,
|
void document_add_puzzle(document *doc, const game *game, game_params *par,
|
||||||
game_state *st, game_state *st2);
|
game_state *st, game_state *st2);
|
||||||
|
int document_npages(document *doc);
|
||||||
|
void document_begin(document *doc, drawing *dr);
|
||||||
|
void document_end(document *doc, drawing *dr);
|
||||||
|
void document_print_page(document *doc, drawing *dr, int page_nr);
|
||||||
void document_print(document *doc, drawing *dr);
|
void document_print(document *doc, drawing *dr);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Reference in New Issue
Block a user