summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/gtk.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/gtk.c')
-rw-r--r--apps/plugins/puzzles/src/gtk.c753
1 files changed, 697 insertions, 56 deletions
diff --git a/apps/plugins/puzzles/src/gtk.c b/apps/plugins/puzzles/src/gtk.c
index d41f8677b9..7588ee0dc6 100644
--- a/apps/plugins/puzzles/src/gtk.c
+++ b/apps/plugins/puzzles/src/gtk.c
@@ -44,6 +44,17 @@
44# endif 44# endif
45#endif 45#endif
46 46
47#if defined USE_CAIRO && GTK_CHECK_VERSION(2,10,0)
48/* We can only use printing if we are using Cairo for drawing and we
49 have a GTK version >= 2.10 (when GtkPrintOperation was added). */
50# define USE_PRINTING
51# if GTK_CHECK_VERSION(2,18,0)
52/* We can embed the page setup. Before 2.18, we needed to have a
53 separate page setup. */
54# define USE_EMBED_PAGE_SETUP
55# endif
56#endif
57
47#if GTK_CHECK_VERSION(3,0,0) 58#if GTK_CHECK_VERSION(3,0,0)
48/* The old names are still more concise! */ 59/* The old names are still more concise! */
49#define gtk_hbox_new(x,y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL,y) 60#define gtk_hbox_new(x,y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL,y)
@@ -132,6 +143,18 @@ struct font {
132}; 143};
133 144
134/* 145/*
146 * An internal API for functions which need to be different for
147 * printing and drawing.
148 */
149struct internal_drawing_api {
150 void (*set_colour)(frontend *fe, int colour);
151#ifdef USE_CAIRO
152 void (*fill)(frontend *fe);
153 void (*fill_preserve)(frontend *fe);
154#endif
155};
156
157/*
135 * This structure holds all the data relevant to a single window. 158 * This structure holds all the data relevant to a single window.
136 * In principle this would allow us to open multiple independent 159 * In principle this would allow us to open multiple independent
137 * puzzle windows, although I can't currently see any real point in 160 * puzzle windows, although I can't currently see any real point in
@@ -180,7 +203,7 @@ struct frontend {
180 GtkWidget *cfgbox; 203 GtkWidget *cfgbox;
181 void *paste_data; 204 void *paste_data;
182 int paste_data_len; 205 int paste_data_len;
183 int pw, ph; /* pixmap size (w, h are area size) */ 206 int pw, ph, ps; /* pixmap size (w, h are area size, s is GDK scale) */
184 int ox, oy; /* offset of pixmap in drawing area */ 207 int ox, oy; /* offset of pixmap in drawing area */
185#ifdef OLD_FILESEL 208#ifdef OLD_FILESEL
186 char *filesel_name; 209 char *filesel_name;
@@ -225,6 +248,23 @@ struct frontend {
225 */ 248 */
226 bool awaiting_resize_ack; 249 bool awaiting_resize_ack;
227#endif 250#endif
251#ifdef USE_CAIRO
252 int printcount, printw, printh;
253 float printscale;
254 bool printsolns, printcolour;
255 int hatch;
256 float hatchthick, hatchspace;
257 drawing *print_dr;
258 document *doc;
259#endif
260#ifdef USE_PRINTING
261 GtkPrintOperation *printop;
262 GtkPrintContext *printcontext;
263 GtkSpinButton *printcount_spin_button, *printw_spin_button,
264 *printh_spin_button, *printscale_spin_button;
265 GtkCheckButton *soln_check_button, *colour_check_button;
266#endif
267 const struct internal_drawing_api *dr_api;
228}; 268};
229 269
230struct blitter { 270struct blitter {
@@ -247,15 +287,19 @@ void get_random_seed(void **randseed, int *randseedsize)
247void frontend_default_colour(frontend *fe, float *output) 287void frontend_default_colour(frontend *fe, float *output)
248{ 288{
249#if !GTK_CHECK_VERSION(3,0,0) 289#if !GTK_CHECK_VERSION(3,0,0)
250 /* 290 if (!fe->headless) {
251 * Use the widget style's default background colour as the 291 /*
252 * background for the puzzle drawing area. 292 * If we have a widget and it has a style that specifies a
253 */ 293 * default background colour, use that as the background for
254 GdkColor col = gtk_widget_get_style(fe->window)->bg[GTK_STATE_NORMAL]; 294 * the puzzle drawing area.
255 output[0] = col.red / 65535.0; 295 */
256 output[1] = col.green / 65535.0; 296 GdkColor col = gtk_widget_get_style(fe->window)->bg[GTK_STATE_NORMAL];
257 output[2] = col.blue / 65535.0; 297 output[0] = col.red / 65535.0;
258#else 298 output[1] = col.green / 65535.0;
299 output[2] = col.blue / 65535.0;
300 }
301#endif
302
259 /* 303 /*
260 * GTK 3 has decided that there's no such thing as a 'default 304 * GTK 3 has decided that there's no such thing as a 'default
261 * background colour' any more, because widget styles might set 305 * background colour' any more, because widget styles might set
@@ -263,9 +307,11 @@ void frontend_default_colour(frontend *fe, float *output)
263 * image. We don't want to get into overlaying our entire puzzle 307 * image. We don't want to get into overlaying our entire puzzle
264 * on an arbitrary background image, so we'll just make up a 308 * on an arbitrary background image, so we'll just make up a
265 * reasonable shade of grey. 309 * reasonable shade of grey.
310 *
311 * This is also what we do on GTK 2 in headless mode, where we
312 * don't have a widget style to query.
266 */ 313 */
267 output[0] = output[1] = output[2] = 0.9F; 314 output[0] = output[1] = output[2] = 0.9F;
268#endif
269} 315}
270 316
271void gtk_status_bar(void *handle, const char *text) 317void gtk_status_bar(void *handle, const char *text)
@@ -290,6 +336,7 @@ void gtk_status_bar(void *handle, const char *text)
290static void setup_drawing(frontend *fe) 336static void setup_drawing(frontend *fe)
291{ 337{
292 fe->cr = cairo_create(fe->image); 338 fe->cr = cairo_create(fe->image);
339 cairo_scale(fe->cr, fe->ps, fe->ps);
293 cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_GRAY); 340 cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_GRAY);
294 cairo_set_line_width(fe->cr, 1.0); 341 cairo_set_line_width(fe->cr, 1.0);
295 cairo_set_line_cap(fe->cr, CAIRO_LINE_CAP_SQUARE); 342 cairo_set_line_cap(fe->cr, CAIRO_LINE_CAP_SQUARE);
@@ -321,12 +368,23 @@ static void snaffle_colours(frontend *fe)
321 fe->colours = midend_colours(fe->me, &fe->ncolours); 368 fe->colours = midend_colours(fe->me, &fe->ncolours);
322} 369}
323 370
324static void set_colour(frontend *fe, int colour) 371static void draw_set_colour(frontend *fe, int colour)
325{ 372{
326 cairo_set_source_rgb(fe->cr, 373 cairo_set_source_rgb(fe->cr,
327 fe->colours[3*colour + 0], 374 fe->colours[3*colour + 0],
328 fe->colours[3*colour + 1], 375 fe->colours[3*colour + 1],
329 fe->colours[3*colour + 2]); 376 fe->colours[3*colour + 2]);
377}
378
379static void print_set_colour(frontend *fe, int colour)
380{
381 float r, g, b;
382
383 print_get_colour(fe->print_dr, colour, fe->printcolour,
384 &(fe->hatch), &r, &g, &b);
385
386 if (fe->hatch < 0)
387 cairo_set_source_rgb(fe->cr, r, g, b);
330} 388}
331 389
332static void set_window_background(frontend *fe, int colour) 390static void set_window_background(frontend *fe, int colour)
@@ -395,6 +453,82 @@ static void save_screenshot_png(frontend *fe, const char *screenshot_file)
395 cairo_surface_write_to_png(fe->image, screenshot_file); 453 cairo_surface_write_to_png(fe->image, screenshot_file);
396} 454}
397 455
456static void do_hatch(frontend *fe)
457{
458 double i, x, y, width, height, maxdim;
459
460 /* Get the dimensions of the region to be hatched. */
461 cairo_path_extents(fe->cr, &x, &y, &width, &height);
462
463 maxdim = max(width, height);
464
465 cairo_save(fe->cr);
466
467 /* Set the line color and width. */
468 cairo_set_source_rgb(fe->cr, 0, 0, 0);
469 cairo_set_line_width(fe->cr, fe->hatchthick);
470 /* Clip to the region. */
471 cairo_clip(fe->cr);
472 /* Hatch the bounding area of the fill region. */
473 if (fe->hatch == HATCH_VERT || fe->hatch == HATCH_PLUS) {
474 for (i = 0.0; i <= width; i += fe->hatchspace) {
475 cairo_move_to(fe->cr, i, 0);
476 cairo_rel_line_to(fe->cr, 0, height);
477 }
478 }
479 if (fe->hatch == HATCH_HORIZ || fe->hatch == HATCH_PLUS) {
480 for (i = 0.0; i <= height; i += fe->hatchspace) {
481 cairo_move_to(fe->cr, 0, i);
482 cairo_rel_line_to(fe->cr, width, 0);
483 }
484 }
485 if (fe->hatch == HATCH_SLASH || fe->hatch == HATCH_X) {
486 for (i = -height; i <= width; i += fe->hatchspace * ROOT2) {
487 cairo_move_to(fe->cr, i, 0);
488 cairo_rel_line_to(fe->cr, maxdim, maxdim);
489 }
490 }
491 if (fe->hatch == HATCH_BACKSLASH || fe->hatch == HATCH_X) {
492 for (i = 0.0; i <= width + height; i += fe->hatchspace * ROOT2) {
493 cairo_move_to(fe->cr, i, 0);
494 cairo_rel_line_to(fe->cr, -maxdim, maxdim);
495 }
496 }
497 cairo_stroke(fe->cr);
498
499 cairo_restore(fe->cr);
500}
501
502static void do_draw_fill(frontend *fe)
503{
504 cairo_fill(fe->cr);
505}
506
507static void do_draw_fill_preserve(frontend *fe)
508{
509 cairo_fill_preserve(fe->cr);
510}
511
512static void do_print_fill(frontend *fe)
513{
514 if (fe->hatch < 0)
515 cairo_fill(fe->cr);
516 else
517 do_hatch(fe);
518}
519
520static void do_print_fill_preserve(frontend *fe)
521{
522 if (fe->hatch < 0) {
523 cairo_fill_preserve(fe->cr);
524 } else {
525 cairo_path_t *oldpath;
526 oldpath = cairo_copy_path(fe->cr);
527 do_hatch(fe);
528 cairo_append_path(fe->cr, oldpath);
529 }
530}
531
398static void do_clip(frontend *fe, int x, int y, int w, int h) 532static void do_clip(frontend *fe, int x, int y, int w, int h)
399{ 533{
400 cairo_new_path(fe->cr); 534 cairo_new_path(fe->cr);
@@ -413,7 +547,7 @@ static void do_draw_rect(frontend *fe, int x, int y, int w, int h)
413 cairo_new_path(fe->cr); 547 cairo_new_path(fe->cr);
414 cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_NONE); 548 cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_NONE);
415 cairo_rectangle(fe->cr, x, y, w, h); 549 cairo_rectangle(fe->cr, x, y, w, h);
416 cairo_fill(fe->cr); 550 fe->dr_api->fill(fe);
417 cairo_restore(fe->cr); 551 cairo_restore(fe->cr);
418} 552}
419 553
@@ -447,11 +581,11 @@ static void do_draw_poly(frontend *fe, int *coords, int npoints,
447 cairo_line_to(fe->cr, coords[i*2] + 0.5, coords[i*2 + 1] + 0.5); 581 cairo_line_to(fe->cr, coords[i*2] + 0.5, coords[i*2 + 1] + 0.5);
448 cairo_close_path(fe->cr); 582 cairo_close_path(fe->cr);
449 if (fillcolour >= 0) { 583 if (fillcolour >= 0) {
450 set_colour(fe, fillcolour); 584 fe->dr_api->set_colour(fe, fillcolour);
451 cairo_fill_preserve(fe->cr); 585 fe->dr_api->fill_preserve(fe);
452 } 586 }
453 assert(outlinecolour >= 0); 587 assert(outlinecolour >= 0);
454 set_colour(fe, outlinecolour); 588 fe->dr_api->set_colour(fe, outlinecolour);
455 cairo_stroke(fe->cr); 589 cairo_stroke(fe->cr);
456} 590}
457 591
@@ -462,11 +596,11 @@ static void do_draw_circle(frontend *fe, int cx, int cy, int radius,
462 cairo_arc(fe->cr, cx + 0.5, cy + 0.5, radius, 0, 2*PI); 596 cairo_arc(fe->cr, cx + 0.5, cy + 0.5, radius, 0, 2*PI);
463 cairo_close_path(fe->cr); /* Just in case... */ 597 cairo_close_path(fe->cr); /* Just in case... */
464 if (fillcolour >= 0) { 598 if (fillcolour >= 0) {
465 set_colour(fe, fillcolour); 599 fe->dr_api->set_colour(fe, fillcolour);
466 cairo_fill_preserve(fe->cr); 600 fe->dr_api->fill_preserve(fe);
467 } 601 }
468 assert(outlinecolour >= 0); 602 assert(outlinecolour >= 0);
469 set_colour(fe, outlinecolour); 603 fe->dr_api->set_colour(fe, outlinecolour);
470 cairo_stroke(fe->cr); 604 cairo_stroke(fe->cr);
471} 605}
472 606
@@ -512,23 +646,24 @@ static void wipe_and_maybe_destroy_cairo(frontend *fe, cairo_t *cr,
512static void setup_backing_store(frontend *fe) 646static void setup_backing_store(frontend *fe)
513{ 647{
514#ifndef USE_CAIRO_WITHOUT_PIXMAP 648#ifndef USE_CAIRO_WITHOUT_PIXMAP
515 if (fe->headless) { 649 if (!fe->headless) {
516 fprintf(stderr, "headless mode does not work with GDK pixmaps\n"); 650 fe->pixmap = gdk_pixmap_new(gtk_widget_get_window(fe->area),
517 exit(1); 651 fe->pw*fe->ps, fe->ph*fe->ps, -1);
652 } else {
653 fe->pixmap = NULL;
518 } 654 }
519
520 fe->pixmap = gdk_pixmap_new(gtk_widget_get_window(fe->area),
521 fe->pw, fe->ph, -1);
522#endif 655#endif
656
523 fe->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 657 fe->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
524 fe->pw, fe->ph); 658 fe->pw*fe->ps, fe->ph*fe->ps);
525 659
526 wipe_and_maybe_destroy_cairo(fe, cairo_create(fe->image), true); 660 wipe_and_maybe_destroy_cairo(fe, cairo_create(fe->image), true);
527#ifndef USE_CAIRO_WITHOUT_PIXMAP 661#ifndef USE_CAIRO_WITHOUT_PIXMAP
528 wipe_and_maybe_destroy_cairo(fe, gdk_cairo_create(fe->pixmap), true); 662 if (!fe->headless)
663 wipe_and_maybe_destroy_cairo(fe, gdk_cairo_create(fe->pixmap), true);
529#endif 664#endif
530#if GTK_CHECK_VERSION(3,22,0)
531 if (!fe->headless) { 665 if (!fe->headless) {
666#if GTK_CHECK_VERSION(3,22,0)
532 GdkWindow *gdkwin; 667 GdkWindow *gdkwin;
533 cairo_region_t *region; 668 cairo_region_t *region;
534 GdkDrawingContext *drawctx; 669 GdkDrawingContext *drawctx;
@@ -541,11 +676,11 @@ static void setup_backing_store(frontend *fe)
541 wipe_and_maybe_destroy_cairo(fe, cr, false); 676 wipe_and_maybe_destroy_cairo(fe, cr, false);
542 gdk_window_end_draw_frame(gdkwin, drawctx); 677 gdk_window_end_draw_frame(gdkwin, drawctx);
543 cairo_region_destroy(region); 678 cairo_region_destroy(region);
544 }
545#else 679#else
546 wipe_and_maybe_destroy_cairo( 680 wipe_and_maybe_destroy_cairo(
547 fe, gdk_cairo_create(gtk_widget_get_window(fe->area)), true); 681 fe, gdk_cairo_create(gtk_widget_get_window(fe->area)), true);
548#endif 682#endif
683 }
549} 684}
550 685
551static bool backing_store_ok(frontend *fe) 686static bool backing_store_ok(frontend *fe)
@@ -616,7 +751,7 @@ static void set_window_background(frontend *fe, int colour)
616 gdk_window_set_background(fe->window->window, &fe->colours[colour]); 751 gdk_window_set_background(fe->window->window, &fe->colours[colour]);
617} 752}
618 753
619static void set_colour(frontend *fe, int colour) 754static void draw_set_colour(frontend *fe, int colour)
620{ 755{
621 gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); 756 gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
622} 757}
@@ -709,11 +844,11 @@ static void do_draw_poly(frontend *fe, int *coords, int npoints,
709 } 844 }
710 845
711 if (fillcolour >= 0) { 846 if (fillcolour >= 0) {
712 set_colour(fe, fillcolour); 847 fe->dr_api->set_colour(fe, fillcolour);
713 gdk_draw_polygon(fe->pixmap, fe->gc, true, points, npoints); 848 gdk_draw_polygon(fe->pixmap, fe->gc, true, points, npoints);
714 } 849 }
715 assert(outlinecolour >= 0); 850 assert(outlinecolour >= 0);
716 set_colour(fe, outlinecolour); 851 fe->dr_api->set_colour(fe, outlinecolour);
717 852
718 /* 853 /*
719 * In principle we ought to be able to use gdk_draw_polygon for 854 * In principle we ought to be able to use gdk_draw_polygon for
@@ -733,14 +868,14 @@ static void do_draw_circle(frontend *fe, int cx, int cy, int radius,
733 int fillcolour, int outlinecolour) 868 int fillcolour, int outlinecolour)
734{ 869{
735 if (fillcolour >= 0) { 870 if (fillcolour >= 0) {
736 set_colour(fe, fillcolour); 871 fe->dr_api->set_colour(fe, fillcolour);
737 gdk_draw_arc(fe->pixmap, fe->gc, true, 872 gdk_draw_arc(fe->pixmap, fe->gc, true,
738 cx - radius, cy - radius, 873 cx - radius, cy - radius,
739 2 * radius, 2 * radius, 0, 360 * 64); 874 2 * radius, 2 * radius, 0, 360 * 64);
740 } 875 }
741 876
742 assert(outlinecolour >= 0); 877 assert(outlinecolour >= 0);
743 set_colour(fe, outlinecolour); 878 fe->dr_api->set_colour(fe, outlinecolour);
744 gdk_draw_arc(fe->pixmap, fe->gc, false, 879 gdk_draw_arc(fe->pixmap, fe->gc, false,
745 cx - radius, cy - radius, 880 cx - radius, cy - radius,
746 2 * radius, 2 * radius, 0, 360 * 64); 881 2 * radius, 2 * radius, 0, 360 * 64);
@@ -1045,21 +1180,21 @@ void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize,
1045 /* 1180 /*
1046 * Do the job. 1181 * Do the job.
1047 */ 1182 */
1048 set_colour(fe, colour); 1183 fe->dr_api->set_colour(fe, colour);
1049 align_and_draw_text(fe, i, align, x, y, text); 1184 align_and_draw_text(fe, i, align, x, y, text);
1050} 1185}
1051 1186
1052void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour) 1187void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour)
1053{ 1188{
1054 frontend *fe = (frontend *)handle; 1189 frontend *fe = (frontend *)handle;
1055 set_colour(fe, colour); 1190 fe->dr_api->set_colour(fe, colour);
1056 do_draw_rect(fe, x, y, w, h); 1191 do_draw_rect(fe, x, y, w, h);
1057} 1192}
1058 1193
1059void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour) 1194void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
1060{ 1195{
1061 frontend *fe = (frontend *)handle; 1196 frontend *fe = (frontend *)handle;
1062 set_colour(fe, colour); 1197 fe->dr_api->set_colour(fe, colour);
1063 do_draw_line(fe, x1, y1, x2, y2); 1198 do_draw_line(fe, x1, y1, x2, y2);
1064} 1199}
1065 1200
@@ -1067,7 +1202,7 @@ void gtk_draw_thick_line(void *handle, float thickness,
1067 float x1, float y1, float x2, float y2, int colour) 1202 float x1, float y1, float x2, float y2, int colour)
1068{ 1203{
1069 frontend *fe = (frontend *)handle; 1204 frontend *fe = (frontend *)handle;
1070 set_colour(fe, colour); 1205 fe->dr_api->set_colour(fe, colour);
1071 do_draw_thick_line(fe, thickness, x1, y1, x2, y2); 1206 do_draw_thick_line(fe, thickness, x1, y1, x2, y2);
1072} 1207}
1073 1208
@@ -1161,6 +1296,105 @@ char *gtk_text_fallback(void *handle, const char *const *strings, int nstrings)
1161} 1296}
1162#endif 1297#endif
1163 1298
1299#ifdef USE_PRINTING
1300void gtk_begin_doc(void *handle, int pages)
1301{
1302 frontend *fe = (frontend *)handle;
1303 gtk_print_operation_set_n_pages(fe->printop, pages);
1304}
1305
1306void gtk_begin_page(void *handle, int number)
1307{
1308}
1309
1310void gtk_begin_puzzle(void *handle, float xm, float xc,
1311 float ym, float yc, int pw, int ph, float wmm)
1312{
1313 frontend *fe = (frontend *)handle;
1314 double ppw, pph, pox, poy, dpmmx, dpmmy;
1315 double scale;
1316
1317 ppw = gtk_print_context_get_width(fe->printcontext);
1318 pph = gtk_print_context_get_height(fe->printcontext);
1319 dpmmx = gtk_print_context_get_dpi_x(fe->printcontext) / 25.4;
1320 dpmmy = gtk_print_context_get_dpi_y(fe->printcontext) / 25.4;
1321
1322 /*
1323 * Compute the puzzle's position in pixels on the logical page.
1324 */
1325 pox = xm * ppw + xc * dpmmx;
1326 poy = ym * pph + yc * dpmmy;
1327
1328 /*
1329 * And determine the scale.
1330 *
1331 * I need a scale such that the maximum puzzle-coordinate
1332 * extent of the rectangle (pw * scale) is equal to the pixel
1333 * equivalent of the puzzle's millimetre width (wmm * dpmmx).
1334 */
1335 scale = wmm * dpmmx / pw;
1336
1337 /*
1338 * Now instruct Cairo to transform points based on our calculated
1339 * values (order here *is* important).
1340 */
1341 cairo_save(fe->cr);
1342 cairo_translate(fe->cr, pox, poy);
1343 cairo_scale(fe->cr, scale, scale);
1344
1345 fe->hatchthick = 0.2 * pw / wmm;
1346 fe->hatchspace = 1.0 * pw / wmm;
1347}
1348
1349void gtk_end_puzzle(void *handle)
1350{
1351 frontend *fe = (frontend *)handle;
1352 cairo_restore(fe->cr);
1353}
1354
1355void gtk_end_page(void *handle, int number)
1356{
1357}
1358
1359void gtk_end_doc(void *handle)
1360{
1361}
1362
1363void gtk_line_width(void *handle, float width)
1364{
1365 frontend *fe = (frontend *)handle;
1366 cairo_set_line_width(fe->cr, width);
1367}
1368
1369void gtk_line_dotted(void *handle, bool dotted)
1370{
1371 frontend *fe = (frontend *)handle;
1372
1373 if (dotted) {
1374 const double dash = 35.0;
1375 cairo_set_dash(fe->cr, &dash, 1, 0);
1376 } else {
1377 cairo_set_dash(fe->cr, NULL, 0, 0);
1378 }
1379}
1380#endif /* USE_PRINTING */
1381
1382const struct internal_drawing_api internal_drawing = {
1383 draw_set_colour,
1384#ifdef USE_CAIRO
1385 do_draw_fill,
1386 do_draw_fill_preserve,
1387#endif
1388};
1389
1390#ifdef USE_CAIRO
1391const struct internal_drawing_api internal_printing = {
1392 print_set_colour,
1393 do_print_fill,
1394 do_print_fill_preserve,
1395};
1396#endif
1397
1164const struct drawing_api gtk_drawing = { 1398const struct drawing_api gtk_drawing = {
1165 gtk_draw_text, 1399 gtk_draw_text,
1166 gtk_draw_rect, 1400 gtk_draw_rect,
@@ -1177,8 +1411,19 @@ const struct drawing_api gtk_drawing = {
1177 gtk_blitter_free, 1411 gtk_blitter_free,
1178 gtk_blitter_save, 1412 gtk_blitter_save,
1179 gtk_blitter_load, 1413 gtk_blitter_load,
1414#ifdef USE_PRINTING
1415 gtk_begin_doc,
1416 gtk_begin_page,
1417 gtk_begin_puzzle,
1418 gtk_end_puzzle,
1419 gtk_end_page,
1420 gtk_end_doc,
1421 gtk_line_width,
1422 gtk_line_dotted,
1423#else
1180 NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */ 1424 NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
1181 NULL, NULL, /* line_width, line_dotted */ 1425 NULL, NULL, /* line_width, line_dotted */
1426#endif
1182#ifdef USE_PANGO 1427#ifdef USE_PANGO
1183 gtk_text_fallback, 1428 gtk_text_fallback,
1184#else 1429#else
@@ -1340,12 +1585,22 @@ static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
1340 frontend *fe = (frontend *)data; 1585 frontend *fe = (frontend *)data;
1341 GdkRectangle dirtyrect; 1586 GdkRectangle dirtyrect;
1342 1587
1588 cairo_surface_t *target_surface = cairo_get_target(cr);
1589 cairo_matrix_t m;
1590 cairo_get_matrix(cr, &m);
1591 double orig_sx, orig_sy;
1592 cairo_surface_get_device_scale(target_surface, &orig_sx, &orig_sy);
1593 cairo_surface_set_device_scale(target_surface, 1.0, 1.0);
1594 cairo_translate(cr, m.x0 * (orig_sx - 1.0), m.y0 * (orig_sy - 1.0));
1595
1343 gdk_cairo_get_clip_rectangle(cr, &dirtyrect); 1596 gdk_cairo_get_clip_rectangle(cr, &dirtyrect);
1344 cairo_set_source_surface(cr, fe->image, fe->ox, fe->oy); 1597 cairo_set_source_surface(cr, fe->image, fe->ox, fe->oy);
1345 cairo_rectangle(cr, dirtyrect.x, dirtyrect.y, 1598 cairo_rectangle(cr, dirtyrect.x, dirtyrect.y,
1346 dirtyrect.width, dirtyrect.height); 1599 dirtyrect.width, dirtyrect.height);
1347 cairo_fill(cr); 1600 cairo_fill(cr);
1348 1601
1602 cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy);
1603
1349 return true; 1604 return true;
1350} 1605}
1351#else 1606#else
@@ -1390,16 +1645,22 @@ static gint map_window(GtkWidget *widget, GdkEvent *event,
1390static void resize_puzzle_to_area(frontend *fe, int x, int y) 1645static void resize_puzzle_to_area(frontend *fe, int x, int y)
1391{ 1646{
1392 int oldw = fe->w, oldpw = fe->pw, oldh = fe->h, oldph = fe->ph; 1647 int oldw = fe->w, oldpw = fe->pw, oldh = fe->h, oldph = fe->ph;
1648 int oldps = fe->ps;
1393 1649
1394 fe->w = x; 1650 fe->w = x;
1395 fe->h = y; 1651 fe->h = y;
1396 midend_size(fe->me, &x, &y, true); 1652 midend_size(fe->me, &x, &y, true);
1397 fe->pw = x; 1653 fe->pw = x;
1398 fe->ph = y; 1654 fe->ph = y;
1655#if GTK_CHECK_VERSION(3,10,0)
1656 fe->ps = gtk_widget_get_scale_factor(fe->area);
1657#else
1658 fe->ps = 1;
1659#endif
1399 fe->ox = (fe->w - fe->pw) / 2; 1660 fe->ox = (fe->w - fe->pw) / 2;
1400 fe->oy = (fe->h - fe->ph) / 2; 1661 fe->oy = (fe->h - fe->ph) / 2;
1401 1662
1402 if (oldw != fe->w || oldpw != fe->pw || 1663 if (oldw != fe->w || oldpw != fe->pw || oldps != fe->ps ||
1403 oldh != fe->h || oldph != fe->ph || !backing_store_ok(fe)) { 1664 oldh != fe->h || oldph != fe->ph || !backing_store_ok(fe)) {
1404 if (backing_store_ok(fe)) 1665 if (backing_store_ok(fe))
1405 teardown_backing_store(fe); 1666 teardown_backing_store(fe);
@@ -1413,6 +1674,7 @@ static gint configure_area(GtkWidget *widget,
1413 GdkEventConfigure *event, gpointer data) 1674 GdkEventConfigure *event, gpointer data)
1414{ 1675{
1415 frontend *fe = (frontend *)data; 1676 frontend *fe = (frontend *)data;
1677
1416 resize_puzzle_to_area(fe, event->width, event->height); 1678 resize_puzzle_to_area(fe, event->width, event->height);
1417#if GTK_CHECK_VERSION(3,0,0) 1679#if GTK_CHECK_VERSION(3,0,0)
1418 fe->awaiting_resize_ack = false; 1680 fe->awaiting_resize_ack = false;
@@ -2245,6 +2507,317 @@ static char *file_selector(frontend *fe, const char *title, bool save)
2245 2507
2246#endif 2508#endif
2247 2509
2510#ifdef USE_PRINTING
2511GObject *create_print_widget(GtkPrintOperation *print, gpointer data)
2512{
2513 GtkLabel *count_label, *width_label, *height_label,
2514 *scale_llabel, *scale_rlabel;
2515 GtkBox *scale_hbox;
2516 GtkWidget *grid;
2517 frontend *fe = (frontend *)data;
2518
2519 fe->printcount_spin_button =
2520 GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 999, 1));
2521 gtk_spin_button_set_numeric(fe->printcount_spin_button, true);
2522 gtk_spin_button_set_snap_to_ticks(fe->printcount_spin_button, true);
2523 fe->printw_spin_button =
2524 GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 99, 1));
2525 gtk_spin_button_set_numeric(fe->printw_spin_button, true);
2526 gtk_spin_button_set_snap_to_ticks(fe->printw_spin_button, true);
2527 fe->printh_spin_button =
2528 GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 99, 1));
2529 gtk_spin_button_set_numeric(fe->printh_spin_button, true);
2530 gtk_spin_button_set_snap_to_ticks(fe->printh_spin_button, true);
2531 fe->printscale_spin_button =
2532 GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 1000, 1));
2533 gtk_spin_button_set_digits(fe->printscale_spin_button, 1);
2534 gtk_spin_button_set_numeric(fe->printscale_spin_button, true);
2535 if (thegame.can_solve) {
2536 fe->soln_check_button =
2537 GTK_CHECK_BUTTON(
2538 gtk_check_button_new_with_label("Print solutions"));
2539 }
2540 if (thegame.can_print_in_colour) {
2541 fe->colour_check_button =
2542 GTK_CHECK_BUTTON(
2543 gtk_check_button_new_with_label("Print in color"));
2544 }
2545
2546 /* Set defaults to what was selected last time. */
2547 gtk_spin_button_set_value(fe->printcount_spin_button,
2548 (gdouble)fe->printcount);
2549 gtk_spin_button_set_value(fe->printw_spin_button,
2550 (gdouble)fe->printw);
2551 gtk_spin_button_set_value(fe->printh_spin_button,
2552 (gdouble)fe->printh);
2553 gtk_spin_button_set_value(fe->printscale_spin_button,
2554 (gdouble)fe->printscale);
2555 if (thegame.can_solve) {
2556 gtk_toggle_button_set_active(
2557 GTK_TOGGLE_BUTTON(fe->soln_check_button), fe->printsolns);
2558 }
2559 if (thegame.can_print_in_colour) {
2560 gtk_toggle_button_set_active(
2561 GTK_TOGGLE_BUTTON(fe->colour_check_button), fe->printcolour);
2562 }
2563
2564 count_label = GTK_LABEL(gtk_label_new("Puzzles to print:"));
2565 width_label = GTK_LABEL(gtk_label_new("Puzzles across:"));
2566 height_label = GTK_LABEL(gtk_label_new("Puzzles down:"));
2567 scale_llabel = GTK_LABEL(gtk_label_new("Puzzle scale:"));
2568 scale_rlabel = GTK_LABEL(gtk_label_new("%"));
2569#if GTK_CHECK_VERSION(3,0,0)
2570 gtk_widget_set_halign(GTK_WIDGET(count_label), GTK_ALIGN_START);
2571 gtk_widget_set_halign(GTK_WIDGET(width_label), GTK_ALIGN_START);
2572 gtk_widget_set_halign(GTK_WIDGET(height_label), GTK_ALIGN_START);
2573 gtk_widget_set_halign(GTK_WIDGET(scale_llabel), GTK_ALIGN_START);
2574#else
2575 gtk_misc_set_alignment(GTK_MISC(count_label), 0, 0);
2576 gtk_misc_set_alignment(GTK_MISC(width_label), 0, 0);
2577 gtk_misc_set_alignment(GTK_MISC(height_label), 0, 0);
2578 gtk_misc_set_alignment(GTK_MISC(scale_llabel), 0, 0);
2579#endif
2580
2581 scale_hbox = GTK_BOX(gtk_hbox_new(false, 6));
2582 gtk_box_pack_start(scale_hbox, GTK_WIDGET(fe->printscale_spin_button),
2583 false, false, 0);
2584 gtk_box_pack_start(scale_hbox, GTK_WIDGET(scale_rlabel),
2585 false, false, 0);
2586
2587#if GTK_CHECK_VERSION(3,0,0)
2588 grid = gtk_grid_new();
2589 gtk_grid_set_column_spacing(GTK_GRID(grid), 18);
2590 gtk_grid_set_row_spacing(GTK_GRID(grid), 18);
2591 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(count_label), 0, 0, 1, 1);
2592 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(width_label), 0, 1, 1, 1);
2593 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(height_label), 0, 2, 1, 1);
2594 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(scale_llabel), 0, 3, 1, 1);
2595 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printcount_spin_button),
2596 1, 0, 1, 1);
2597 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printw_spin_button),
2598 1, 1, 1, 1);
2599 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printh_spin_button),
2600 1, 2, 1, 1);
2601 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(scale_hbox), 1, 3, 1, 1);
2602 if (thegame.can_solve) {
2603 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->soln_check_button),
2604 0, 4, 1, 1);
2605 }
2606 if (thegame.can_print_in_colour) {
2607 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->colour_check_button),
2608 thegame.can_solve, 4, 1, 1);
2609 }
2610#else
2611 grid = gtk_table_new((thegame.can_solve || thegame.can_print_in_colour) ?
2612 5 : 4, 2, false);
2613 gtk_table_set_col_spacings(GTK_TABLE(grid), 18);
2614 gtk_table_set_row_spacings(GTK_TABLE(grid), 18);
2615 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(count_label), 0, 1, 0, 1,
2616 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2617 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(width_label), 0, 1, 1, 2,
2618 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2619 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(height_label), 0, 1, 2, 3,
2620 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2621 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(scale_llabel), 0, 1, 3, 4,
2622 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2623 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printcount_spin_button),
2624 1, 2, 0, 1,
2625 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2626 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printw_spin_button),
2627 1, 2, 1, 2,
2628 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2629 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printh_spin_button),
2630 1, 2, 2, 3,
2631 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2632 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(scale_hbox), 1, 2, 3, 4,
2633 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2634 if (thegame.can_solve) {
2635 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->soln_check_button),
2636 0, 1, 4, 5,
2637 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2638 }
2639 if (thegame.can_print_in_colour) {
2640 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->colour_check_button),
2641 thegame.can_solve, thegame.can_solve + 1, 4, 5,
2642 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2643 }
2644#endif
2645 gtk_container_set_border_width(GTK_CONTAINER(grid), 12);
2646
2647 gtk_widget_show_all(grid);
2648
2649 return G_OBJECT(grid);
2650}
2651
2652void apply_print_widget(GtkPrintOperation *print,
2653 GtkWidget *widget, gpointer data)
2654{
2655 frontend *fe = (frontend *)data;
2656
2657 /* We ignore `widget' because it is easier and faster to store the
2658 widgets we need in `fe' then to get the children of `widget'. */
2659 fe->printcount =
2660 gtk_spin_button_get_value_as_int(fe->printcount_spin_button);
2661 fe->printw = gtk_spin_button_get_value_as_int(fe->printw_spin_button);
2662 fe->printh = gtk_spin_button_get_value_as_int(fe->printh_spin_button);
2663 fe->printscale = gtk_spin_button_get_value(fe->printscale_spin_button);
2664 if (thegame.can_solve) {
2665 fe->printsolns =
2666 gtk_toggle_button_get_active(
2667 GTK_TOGGLE_BUTTON(fe->soln_check_button));
2668 }
2669 if (thegame.can_print_in_colour) {
2670 fe->printcolour =
2671 gtk_toggle_button_get_active(
2672 GTK_TOGGLE_BUTTON(fe->colour_check_button));
2673 }
2674}
2675
2676void print_begin(GtkPrintOperation *printop,
2677 GtkPrintContext *context, gpointer data)
2678{
2679 frontend *fe = (frontend *)data;
2680 midend *nme = NULL; /* non-interactive midend for bulk puzzle generation */
2681 int i;
2682
2683 fe->printcontext = context;
2684 fe->cr = gtk_print_context_get_cairo_context(context);
2685
2686 /*
2687 * Create our document structure and fill it up with puzzles.
2688 */
2689 fe->doc = document_new(fe->printw, fe->printh, fe->printscale / 100.0F);
2690
2691 for (i = 0; i < fe->printcount; i++) {
2692 const char *err;
2693
2694 if (i == 0) {
2695 err = midend_print_puzzle(fe->me, fe->doc, fe->printsolns);
2696 } else {
2697 if (!nme) {
2698 game_params *params;
2699
2700 nme = midend_new(NULL, &thegame, NULL, NULL);
2701
2702 /*
2703 * Set the non-interactive mid-end to have the same
2704 * parameters as the standard one.
2705 */
2706 params = midend_get_params(fe->me);
2707 midend_set_params(nme, params);
2708 thegame.free_params(params);
2709 }
2710
2711 midend_new_game(nme);
2712 err = midend_print_puzzle(nme, fe->doc, fe->printsolns);
2713 }
2714
2715 if (err) {
2716 error_box(fe->window, err);
2717 return;
2718 }
2719 }
2720
2721 if (nme)
2722 midend_free(nme);
2723
2724 /* Begin the document. */
2725 document_begin(fe->doc, fe->print_dr);
2726}
2727
2728void draw_page(GtkPrintOperation *printop,
2729 GtkPrintContext *context,
2730 gint page_nr, gpointer data)
2731{
2732 frontend *fe = (frontend *)data;
2733 document_print_page(fe->doc, fe->print_dr, page_nr);
2734}
2735
2736void print_end(GtkPrintOperation *printop,
2737 GtkPrintContext *context, gpointer data)
2738{
2739 frontend *fe = (frontend *)data;
2740
2741 /* End and free the document. */
2742 document_end(fe->doc, fe->print_dr);
2743 document_free(fe->doc);
2744 fe->doc = NULL;
2745}
2746
2747static void print_dialog(frontend *fe)
2748{
2749 GError *error;
2750 static GtkPrintSettings *settings = NULL;
2751 static GtkPageSetup *page_setup = NULL;
2752#ifndef USE_EMBED_PAGE_SETUP
2753 GtkPageSetup *new_page_setup;
2754#endif
2755
2756 fe->printop = gtk_print_operation_new();
2757 gtk_print_operation_set_use_full_page(fe->printop, true);
2758 gtk_print_operation_set_custom_tab_label(fe->printop, "Puzzle Settings");
2759 g_signal_connect(fe->printop, "create-custom-widget",
2760 G_CALLBACK(create_print_widget), fe);
2761 g_signal_connect(fe->printop, "custom-widget-apply",
2762 G_CALLBACK(apply_print_widget), fe);
2763 g_signal_connect(fe->printop, "begin-print", G_CALLBACK(print_begin), fe);
2764 g_signal_connect(fe->printop, "draw-page", G_CALLBACK(draw_page), fe);
2765 g_signal_connect(fe->printop, "end-print", G_CALLBACK(print_end), fe);
2766#ifdef USE_EMBED_PAGE_SETUP
2767 gtk_print_operation_set_embed_page_setup(fe->printop, true);
2768#else
2769 if (page_setup == NULL) {
2770 page_setup =
2771 g_object_ref(
2772 gtk_print_operation_get_default_page_setup(fe->printop));
2773 }
2774 if (settings == NULL) {
2775 settings =
2776 g_object_ref(gtk_print_operation_get_print_settings(fe->printop));
2777 }
2778 new_page_setup = gtk_print_run_page_setup_dialog(GTK_WINDOW(fe->window),
2779 page_setup, settings);
2780 g_object_unref(page_setup);
2781 page_setup = new_page_setup;
2782 gtk_print_operation_set_default_page_setup(fe->printop, page_setup);
2783#endif
2784
2785 if (settings != NULL)
2786 gtk_print_operation_set_print_settings(fe->printop, settings);
2787 if (page_setup != NULL)
2788 gtk_print_operation_set_default_page_setup(fe->printop, page_setup);
2789
2790 switch (gtk_print_operation_run(fe->printop,
2791 GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
2792 GTK_WINDOW(fe->window), &error)) {
2793 case GTK_PRINT_OPERATION_RESULT_ERROR:
2794 error_box(fe->window, error->message);
2795 g_error_free(error);
2796 break;
2797 case GTK_PRINT_OPERATION_RESULT_APPLY:
2798 if (settings != NULL)
2799 g_object_unref(settings);
2800 settings =
2801 g_object_ref(gtk_print_operation_get_print_settings(fe->printop));
2802#ifdef USE_EMBED_PAGE_SETUP
2803 if (page_setup != NULL)
2804 g_object_unref(page_setup);
2805 page_setup =
2806 g_object_ref(
2807 gtk_print_operation_get_default_page_setup(fe->printop));
2808#endif
2809 break;
2810 default:
2811 /* Don't error out on -Werror=switch. */
2812 break;
2813 }
2814
2815 g_object_unref(fe->printop);
2816 fe->printop = NULL;
2817 fe->printcontext = NULL;
2818}
2819#endif /* USE_PRINTING */
2820
2248struct savefile_write_ctx { 2821struct savefile_write_ctx {
2249 FILE *fp; 2822 FILE *fp;
2250 int error; 2823 int error;
@@ -2346,6 +2919,15 @@ static void menu_load_event(GtkMenuItem *menuitem, gpointer data)
2346 } 2919 }
2347} 2920}
2348 2921
2922#ifdef USE_PRINTING
2923static void menu_print_event(GtkMenuItem *menuitem, gpointer data)
2924{
2925 frontend *fe = (frontend *)data;
2926
2927 print_dialog(fe);
2928}
2929#endif
2930
2349static void menu_solve_event(GtkMenuItem *menuitem, gpointer data) 2931static void menu_solve_event(GtkMenuItem *menuitem, gpointer data)
2350{ 2932{
2351 frontend *fe = (frontend *)data; 2933 frontend *fe = (frontend *)data;
@@ -2388,18 +2970,31 @@ static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
2388 frontend *fe = (frontend *)data; 2970 frontend *fe = (frontend *)data;
2389 2971
2390#if GTK_CHECK_VERSION(3,0,0) 2972#if GTK_CHECK_VERSION(3,0,0)
2973# define ABOUT_PARAMS \
2974 "program-name", thegame.name, \
2975 "version", ver, \
2976 "comments", "Part of Simon Tatham's Portable Puzzle Collection"
2977
2391 extern char *const *const xpm_icons[]; 2978 extern char *const *const xpm_icons[];
2392 extern const int n_xpm_icons; 2979 extern const int n_xpm_icons;
2393 GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data 2980
2394 ((const gchar **)xpm_icons[n_xpm_icons-1]); 2981 if (n_xpm_icons) {
2395 gtk_show_about_dialog 2982 GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data
2396 (GTK_WINDOW(fe->window), 2983 ((const gchar **)xpm_icons[n_xpm_icons-1]);
2397 "program-name", thegame.name, 2984
2398 "version", ver, 2985 gtk_show_about_dialog
2399 "comments", "Part of Simon Tatham's Portable Puzzle Collection", 2986 (GTK_WINDOW(fe->window),
2400 "logo", icon, 2987 ABOUT_PARAMS,
2401 (const gchar *)NULL); 2988 "logo", icon,
2402 g_object_unref(G_OBJECT(icon)); 2989 (const gchar *)NULL);
2990 g_object_unref(G_OBJECT(icon));
2991 }
2992 else {
2993 gtk_show_about_dialog
2994 (GTK_WINDOW(fe->window),
2995 ABOUT_PARAMS,
2996 (const gchar *)NULL);
2997 }
2403#else 2998#else
2404 char titlebuf[256]; 2999 char titlebuf[256];
2405 char textbuf[1024]; 3000 char textbuf[1024];
@@ -2489,6 +3084,9 @@ static frontend *new_window(
2489 char *arg, int argtype, char **error, bool headless) 3084 char *arg, int argtype, char **error, bool headless)
2490{ 3085{
2491 frontend *fe; 3086 frontend *fe;
3087#ifdef USE_PRINTING
3088 frontend *print_fe = NULL;
3089#endif
2492 GtkBox *vbox, *hbox; 3090 GtkBox *vbox, *hbox;
2493 GtkWidget *menu, *menuitem; 3091 GtkWidget *menu, *menuitem;
2494 GList *iconlist; 3092 GList *iconlist;
@@ -2501,13 +3099,14 @@ static frontend *new_window(
2501 fe = snew(frontend); 3099 fe = snew(frontend);
2502 memset(fe, 0, sizeof(frontend)); 3100 memset(fe, 0, sizeof(frontend));
2503 3101
2504#if !GTK_CHECK_VERSION(3,0,0) 3102#ifndef USE_CAIRO
2505 if (headless) { 3103 if (headless) {
2506 fprintf(stderr, "headless mode not supported below GTK 3\n"); 3104 fprintf(stderr, "headless mode not supported for non-Cairo drawing\n");
2507 exit(1); 3105 exit(1);
2508 } 3106 }
2509#else 3107#else
2510 fe->headless = headless; 3108 fe->headless = headless;
3109 fe->ps = 1; /* in headless mode, configure_area won't have set this */
2511#endif 3110#endif
2512 3111
2513 fe->timer_active = false; 3112 fe->timer_active = false;
@@ -2515,6 +3114,32 @@ static frontend *new_window(
2515 3114
2516 fe->me = midend_new(fe, &thegame, &gtk_drawing, fe); 3115 fe->me = midend_new(fe, &thegame, &gtk_drawing, fe);
2517 3116
3117 fe->dr_api = &internal_drawing;
3118
3119#ifdef USE_PRINTING
3120 if (thegame.can_print) {
3121 print_fe = snew(frontend);
3122 memset(print_fe, 0, sizeof(frontend));
3123
3124 /* Defaults */
3125 print_fe->printcount = print_fe->printw = print_fe->printh = 1;
3126 print_fe->printscale = 100;
3127 print_fe->printsolns = false;
3128 print_fe->printcolour = thegame.can_print_in_colour;
3129
3130 /*
3131 * We need to use the same midend as the main frontend because
3132 * we need midend_print_puzzle() to be able to print the
3133 * current puzzle.
3134 */
3135 print_fe->me = fe->me;
3136
3137 print_fe->print_dr = drawing_new(&gtk_drawing, print_fe->me, print_fe);
3138
3139 print_fe->dr_api = &internal_printing;
3140 }
3141#endif
3142
2518 if (arg) { 3143 if (arg) {
2519 const char *err; 3144 const char *err;
2520 FILE *fp; 3145 FILE *fp;
@@ -2568,6 +3193,12 @@ static frontend *new_window(
2568 *error = dupstr(errbuf); 3193 *error = dupstr(errbuf);
2569 midend_free(fe->me); 3194 midend_free(fe->me);
2570 sfree(fe); 3195 sfree(fe);
3196#ifdef USE_PRINTING
3197 if (thegame.can_print) {
3198 drawing_free(print_fe->print_dr);
3199 sfree(print_fe);
3200 }
3201#endif
2571 return NULL; 3202 return NULL;
2572 } 3203 }
2573 3204
@@ -2711,6 +3342,16 @@ static frontend *new_window(
2711 g_signal_connect(G_OBJECT(menuitem), "activate", 3342 g_signal_connect(G_OBJECT(menuitem), "activate",
2712 G_CALLBACK(menu_save_event), fe); 3343 G_CALLBACK(menu_save_event), fe);
2713 gtk_widget_show(menuitem); 3344 gtk_widget_show(menuitem);
3345#ifdef USE_PRINTING
3346 if (thegame.can_print) {
3347 add_menu_separator(GTK_CONTAINER(menu));
3348 menuitem = gtk_menu_item_new_with_label("Print...");
3349 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3350 g_signal_connect(G_OBJECT(menuitem), "activate",
3351 G_CALLBACK(menu_print_event), print_fe);
3352 gtk_widget_show(menuitem);
3353 }
3354#endif
2714#ifndef STYLUS_BASED 3355#ifndef STYLUS_BASED
2715 add_menu_separator(GTK_CONTAINER(menu)); 3356 add_menu_separator(GTK_CONTAINER(menu));
2716 add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0); 3357 add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0);