summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/gtk.c
diff options
context:
space:
mode:
authorFranklin Wei <franklin@rockbox.org>2024-07-22 21:43:25 -0400
committerFranklin Wei <franklin@rockbox.org>2024-07-22 21:44:08 -0400
commit09aa8de52cb962f1ceebfb1fd44f2c54a924fc5c (patch)
tree182bd4efb2dc8ca4fcb369d8cccab0c0f290d054 /apps/plugins/puzzles/src/gtk.c
parentc72030f98c953a82ed6f5c7132ad000c3d5f4a16 (diff)
downloadrockbox-09aa8de52cb962f1ceebfb1fd44f2c54a924fc5c.tar.gz
rockbox-09aa8de52cb962f1ceebfb1fd44f2c54a924fc5c.zip
puzzles: resync with upstream
This brings the puzzles source in sync with Simon's branch, commit fd304c5 (from March 2024), with some added Rockbox-specific compatibility changes: https://www.franklinwei.com/git/puzzles/commit/?h=rockbox-devel&id=516830d9d76bdfe64fe5ccf2a9b59c33f5c7c078 There are quite a lot of backend changes, including a new "Mosaic" puzzle. In addition, some new frontend changes were necessary: - New "Preferences" menu to access the user preferences system. - Enabled spacebar input for several games. Change-Id: I94c7df674089c92f32d5f07025f6a1059068af1e
Diffstat (limited to 'apps/plugins/puzzles/src/gtk.c')
-rw-r--r--apps/plugins/puzzles/src/gtk.c550
1 files changed, 459 insertions, 91 deletions
diff --git a/apps/plugins/puzzles/src/gtk.c b/apps/plugins/puzzles/src/gtk.c
index 7588ee0dc6..a40a70187f 100644
--- a/apps/plugins/puzzles/src/gtk.c
+++ b/apps/plugins/puzzles/src/gtk.c
@@ -2,6 +2,10 @@
2 * gtk.c: GTK front end for my puzzle collection. 2 * gtk.c: GTK front end for my puzzle collection.
3 */ 3 */
4 4
5#ifndef _GNU_SOURCE
6#define _GNU_SOURCE 1 /* for strcasestr */
7#endif
8
5#include <stdio.h> 9#include <stdio.h>
6#include <assert.h> 10#include <assert.h>
7#include <stdlib.h> 11#include <stdlib.h>
@@ -9,8 +13,16 @@
9#include <stdarg.h> 13#include <stdarg.h>
10#include <string.h> 14#include <string.h>
11#include <errno.h> 15#include <errno.h>
12#include <math.h> 16#ifdef NO_TGMATH_H
17# include <math.h>
18#else
19# include <tgmath.h>
20#endif
21#include <unistd.h>
13 22
23#include <fcntl.h>
24#include <sys/stat.h>
25#include <sys/types.h>
14#include <sys/time.h> 26#include <sys/time.h>
15#include <sys/resource.h> 27#include <sys/resource.h>
16 28
@@ -25,6 +37,7 @@
25#include <X11/Xatom.h> 37#include <X11/Xatom.h>
26 38
27#include "puzzles.h" 39#include "puzzles.h"
40#include "gtk.h"
28 41
29#if GTK_CHECK_VERSION(2,0,0) 42#if GTK_CHECK_VERSION(2,0,0)
30# define USE_PANGO 43# define USE_PANGO
@@ -82,7 +95,7 @@
82#ifdef DEBUGGING 95#ifdef DEBUGGING
83static FILE *debug_fp = NULL; 96static FILE *debug_fp = NULL;
84 97
85void dputs(const char *buf) 98static void dputs(const char *buf)
86{ 99{
87 if (!debug_fp) { 100 if (!debug_fp) {
88 debug_fp = fopen("debug.log", "w"); 101 debug_fp = fopen("debug.log", "w");
@@ -131,6 +144,8 @@ void fatal(const char *fmt, ...)
131 */ 144 */
132 145
133static void changed_preset(frontend *fe); 146static void changed_preset(frontend *fe);
147static void load_prefs(frontend *fe);
148static char *save_prefs(frontend *fe);
134 149
135struct font { 150struct font {
136#ifdef USE_PANGO 151#ifdef USE_PANGO
@@ -314,7 +329,7 @@ void frontend_default_colour(frontend *fe, float *output)
314 output[0] = output[1] = output[2] = 0.9F; 329 output[0] = output[1] = output[2] = 0.9F;
315} 330}
316 331
317void gtk_status_bar(void *handle, const char *text) 332static void gtk_status_bar(void *handle, const char *text)
318{ 333{
319 frontend *fe = (frontend *)handle; 334 frontend *fe = (frontend *)handle;
320 335
@@ -389,6 +404,21 @@ static void print_set_colour(frontend *fe, int colour)
389 404
390static void set_window_background(frontend *fe, int colour) 405static void set_window_background(frontend *fe, int colour)
391{ 406{
407#if GTK_CHECK_VERSION(3,0,0)
408 /* In case the user's chosen theme is dark, we should not override
409 * the background colour for the whole window as this makes the
410 * menu and status bars unreadable. This might be visible through
411 * the gtk-application-prefer-dark-theme flag or else we have to
412 * work it out from the name. */
413 gboolean dark_theme = false;
414 char *theme_name = NULL;
415 g_object_get(gtk_settings_get_default(),
416 "gtk-application-prefer-dark-theme", &dark_theme,
417 "gtk-theme-name", &theme_name,
418 NULL);
419 if (theme_name && strcasestr(theme_name, "-dark"))
420 dark_theme = true;
421 g_free(theme_name);
392#if GTK_CHECK_VERSION(3,20,0) 422#if GTK_CHECK_VERSION(3,20,0)
393 char css_buf[512]; 423 char css_buf[512];
394 sprintf(css_buf, ".background { " 424 sprintf(css_buf, ".background { "
@@ -401,23 +431,28 @@ static void set_window_background(frontend *fe, int colour)
401 if (!gtk_css_provider_load_from_data( 431 if (!gtk_css_provider_load_from_data(
402 GTK_CSS_PROVIDER(fe->css_provider), css_buf, -1, NULL)) 432 GTK_CSS_PROVIDER(fe->css_provider), css_buf, -1, NULL))
403 assert(0 && "Couldn't load CSS"); 433 assert(0 && "Couldn't load CSS");
404 gtk_style_context_add_provider( 434 if (!dark_theme) {
405 gtk_widget_get_style_context(fe->window), 435 gtk_style_context_add_provider(
406 GTK_STYLE_PROVIDER(fe->css_provider), 436 gtk_widget_get_style_context(fe->window),
407 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); 437 GTK_STYLE_PROVIDER(fe->css_provider),
438 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
439 }
408 gtk_style_context_add_provider( 440 gtk_style_context_add_provider(
409 gtk_widget_get_style_context(fe->area), 441 gtk_widget_get_style_context(fe->area),
410 GTK_STYLE_PROVIDER(fe->css_provider), 442 GTK_STYLE_PROVIDER(fe->css_provider),
411 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); 443 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
412#elif GTK_CHECK_VERSION(3,0,0) 444#else // still at least GTK 3.0 but less than 3.20
413 GdkRGBA rgba; 445 GdkRGBA rgba;
414 rgba.red = fe->colours[3*colour + 0]; 446 rgba.red = fe->colours[3*colour + 0];
415 rgba.green = fe->colours[3*colour + 1]; 447 rgba.green = fe->colours[3*colour + 1];
416 rgba.blue = fe->colours[3*colour + 2]; 448 rgba.blue = fe->colours[3*colour + 2];
417 rgba.alpha = 1.0; 449 rgba.alpha = 1.0;
418 gdk_window_set_background_rgba(gtk_widget_get_window(fe->area), &rgba); 450 gdk_window_set_background_rgba(gtk_widget_get_window(fe->area), &rgba);
419 gdk_window_set_background_rgba(gtk_widget_get_window(fe->window), &rgba); 451 if (!dark_theme)
420#else 452 gdk_window_set_background_rgba(gtk_widget_get_window(fe->window),
453 &rgba);
454#endif // GTK_CHECK_VERSION(3,20,0)
455#else // GTK 2 version comes next
421 GdkColormap *colmap; 456 GdkColormap *colmap;
422 457
423 colmap = gdk_colormap_get_system(); 458 colmap = gdk_colormap_get_system();
@@ -571,7 +606,7 @@ static void do_draw_thick_line(frontend *fe, float thickness,
571 cairo_restore(fe->cr); 606 cairo_restore(fe->cr);
572} 607}
573 608
574static void do_draw_poly(frontend *fe, int *coords, int npoints, 609static void do_draw_poly(frontend *fe, const int *coords, int npoints,
575 int fillcolour, int outlinecolour) 610 int fillcolour, int outlinecolour)
576{ 611{
577 int i; 612 int i;
@@ -832,7 +867,7 @@ static void do_draw_thick_line(frontend *fe, float thickness,
832 save.join_style); 867 save.join_style);
833} 868}
834 869
835static void do_draw_poly(frontend *fe, int *coords, int npoints, 870static void do_draw_poly(frontend *fe, const int *coords, int npoints,
836 int fillcolour, int outlinecolour) 871 int fillcolour, int outlinecolour)
837{ 872{
838 GdkPoint *points = snewn(npoints, GdkPoint); 873 GdkPoint *points = snewn(npoints, GdkPoint);
@@ -1129,7 +1164,7 @@ static void align_and_draw_text(int index, int align, int x, int y,
1129 * The exported drawing functions. 1164 * The exported drawing functions.
1130 */ 1165 */
1131 1166
1132void gtk_start_draw(void *handle) 1167static void gtk_start_draw(void *handle)
1133{ 1168{
1134 frontend *fe = (frontend *)handle; 1169 frontend *fe = (frontend *)handle;
1135 fe->bbox_l = fe->w; 1170 fe->bbox_l = fe->w;
@@ -1139,20 +1174,21 @@ void gtk_start_draw(void *handle)
1139 setup_drawing(fe); 1174 setup_drawing(fe);
1140} 1175}
1141 1176
1142void gtk_clip(void *handle, int x, int y, int w, int h) 1177static void gtk_clip(void *handle, int x, int y, int w, int h)
1143{ 1178{
1144 frontend *fe = (frontend *)handle; 1179 frontend *fe = (frontend *)handle;
1145 do_clip(fe, x, y, w, h); 1180 do_clip(fe, x, y, w, h);
1146} 1181}
1147 1182
1148void gtk_unclip(void *handle) 1183static void gtk_unclip(void *handle)
1149{ 1184{
1150 frontend *fe = (frontend *)handle; 1185 frontend *fe = (frontend *)handle;
1151 do_unclip(fe); 1186 do_unclip(fe);
1152} 1187}
1153 1188
1154void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize, 1189static void gtk_draw_text(void *handle, int x, int y, int fonttype,
1155 int align, int colour, const char *text) 1190 int fontsize, int align, int colour,
1191 const char *text)
1156{ 1192{
1157 frontend *fe = (frontend *)handle; 1193 frontend *fe = (frontend *)handle;
1158 int i; 1194 int i;
@@ -1184,43 +1220,45 @@ void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize,
1184 align_and_draw_text(fe, i, align, x, y, text); 1220 align_and_draw_text(fe, i, align, x, y, text);
1185} 1221}
1186 1222
1187void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour) 1223static void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour)
1188{ 1224{
1189 frontend *fe = (frontend *)handle; 1225 frontend *fe = (frontend *)handle;
1190 fe->dr_api->set_colour(fe, colour); 1226 fe->dr_api->set_colour(fe, colour);
1191 do_draw_rect(fe, x, y, w, h); 1227 do_draw_rect(fe, x, y, w, h);
1192} 1228}
1193 1229
1194void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour) 1230static void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2,
1231 int colour)
1195{ 1232{
1196 frontend *fe = (frontend *)handle; 1233 frontend *fe = (frontend *)handle;
1197 fe->dr_api->set_colour(fe, colour); 1234 fe->dr_api->set_colour(fe, colour);
1198 do_draw_line(fe, x1, y1, x2, y2); 1235 do_draw_line(fe, x1, y1, x2, y2);
1199} 1236}
1200 1237
1201void gtk_draw_thick_line(void *handle, float thickness, 1238static void gtk_draw_thick_line(void *handle, float thickness,
1202 float x1, float y1, float x2, float y2, int colour) 1239 float x1, float y1, float x2, float y2,
1240 int colour)
1203{ 1241{
1204 frontend *fe = (frontend *)handle; 1242 frontend *fe = (frontend *)handle;
1205 fe->dr_api->set_colour(fe, colour); 1243 fe->dr_api->set_colour(fe, colour);
1206 do_draw_thick_line(fe, thickness, x1, y1, x2, y2); 1244 do_draw_thick_line(fe, thickness, x1, y1, x2, y2);
1207} 1245}
1208 1246
1209void gtk_draw_poly(void *handle, int *coords, int npoints, 1247static void gtk_draw_poly(void *handle, const int *coords, int npoints,
1210 int fillcolour, int outlinecolour) 1248 int fillcolour, int outlinecolour)
1211{ 1249{
1212 frontend *fe = (frontend *)handle; 1250 frontend *fe = (frontend *)handle;
1213 do_draw_poly(fe, coords, npoints, fillcolour, outlinecolour); 1251 do_draw_poly(fe, coords, npoints, fillcolour, outlinecolour);
1214} 1252}
1215 1253
1216void gtk_draw_circle(void *handle, int cx, int cy, int radius, 1254static void gtk_draw_circle(void *handle, int cx, int cy, int radius,
1217 int fillcolour, int outlinecolour) 1255 int fillcolour, int outlinecolour)
1218{ 1256{
1219 frontend *fe = (frontend *)handle; 1257 frontend *fe = (frontend *)handle;
1220 do_draw_circle(fe, cx, cy, radius, fillcolour, outlinecolour); 1258 do_draw_circle(fe, cx, cy, radius, fillcolour, outlinecolour);
1221} 1259}
1222 1260
1223blitter *gtk_blitter_new(void *handle, int w, int h) 1261static blitter *gtk_blitter_new(void *handle, int w, int h)
1224{ 1262{
1225 blitter *bl = snew(blitter); 1263 blitter *bl = snew(blitter);
1226 setup_blitter(bl, w, h); 1264 setup_blitter(bl, w, h);
@@ -1229,13 +1267,13 @@ blitter *gtk_blitter_new(void *handle, int w, int h)
1229 return bl; 1267 return bl;
1230} 1268}
1231 1269
1232void gtk_blitter_free(void *handle, blitter *bl) 1270static void gtk_blitter_free(void *handle, blitter *bl)
1233{ 1271{
1234 teardown_blitter(bl); 1272 teardown_blitter(bl);
1235 sfree(bl); 1273 sfree(bl);
1236} 1274}
1237 1275
1238void gtk_blitter_save(void *handle, blitter *bl, int x, int y) 1276static void gtk_blitter_save(void *handle, blitter *bl, int x, int y)
1239{ 1277{
1240 frontend *fe = (frontend *)handle; 1278 frontend *fe = (frontend *)handle;
1241 do_blitter_save(fe, bl, x, y); 1279 do_blitter_save(fe, bl, x, y);
@@ -1243,7 +1281,7 @@ void gtk_blitter_save(void *handle, blitter *bl, int x, int y)
1243 bl->y = y; 1281 bl->y = y;
1244} 1282}
1245 1283
1246void gtk_blitter_load(void *handle, blitter *bl, int x, int y) 1284static void gtk_blitter_load(void *handle, blitter *bl, int x, int y)
1247{ 1285{
1248 frontend *fe = (frontend *)handle; 1286 frontend *fe = (frontend *)handle;
1249 if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) { 1287 if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) {
@@ -1253,7 +1291,7 @@ void gtk_blitter_load(void *handle, blitter *bl, int x, int y)
1253 do_blitter_load(fe, bl, x, y); 1291 do_blitter_load(fe, bl, x, y);
1254} 1292}
1255 1293
1256void gtk_draw_update(void *handle, int x, int y, int w, int h) 1294static void gtk_draw_update(void *handle, int x, int y, int w, int h)
1257{ 1295{
1258 frontend *fe = (frontend *)handle; 1296 frontend *fe = (frontend *)handle;
1259 if (fe->bbox_l > x ) fe->bbox_l = x ; 1297 if (fe->bbox_l > x ) fe->bbox_l = x ;
@@ -1262,7 +1300,7 @@ void gtk_draw_update(void *handle, int x, int y, int w, int h)
1262 if (fe->bbox_d < y+h) fe->bbox_d = y+h; 1300 if (fe->bbox_d < y+h) fe->bbox_d = y+h;
1263} 1301}
1264 1302
1265void gtk_end_draw(void *handle) 1303static void gtk_end_draw(void *handle)
1266{ 1304{
1267 frontend *fe = (frontend *)handle; 1305 frontend *fe = (frontend *)handle;
1268 1306
@@ -1286,7 +1324,8 @@ void gtk_end_draw(void *handle)
1286} 1324}
1287 1325
1288#ifdef USE_PANGO 1326#ifdef USE_PANGO
1289char *gtk_text_fallback(void *handle, const char *const *strings, int nstrings) 1327static char *gtk_text_fallback(void *handle, const char *const *strings,
1328 int nstrings)
1290{ 1329{
1291 /* 1330 /*
1292 * We assume Pango can cope with any UTF-8 likely to be emitted 1331 * We assume Pango can cope with any UTF-8 likely to be emitted
@@ -1297,18 +1336,18 @@ char *gtk_text_fallback(void *handle, const char *const *strings, int nstrings)
1297#endif 1336#endif
1298 1337
1299#ifdef USE_PRINTING 1338#ifdef USE_PRINTING
1300void gtk_begin_doc(void *handle, int pages) 1339static void gtk_begin_doc(void *handle, int pages)
1301{ 1340{
1302 frontend *fe = (frontend *)handle; 1341 frontend *fe = (frontend *)handle;
1303 gtk_print_operation_set_n_pages(fe->printop, pages); 1342 gtk_print_operation_set_n_pages(fe->printop, pages);
1304} 1343}
1305 1344
1306void gtk_begin_page(void *handle, int number) 1345static void gtk_begin_page(void *handle, int number)
1307{ 1346{
1308} 1347}
1309 1348
1310void gtk_begin_puzzle(void *handle, float xm, float xc, 1349static void gtk_begin_puzzle(void *handle, float xm, float xc,
1311 float ym, float yc, int pw, int ph, float wmm) 1350 float ym, float yc, int pw, int ph, float wmm)
1312{ 1351{
1313 frontend *fe = (frontend *)handle; 1352 frontend *fe = (frontend *)handle;
1314 double ppw, pph, pox, poy, dpmmx, dpmmy; 1353 double ppw, pph, pox, poy, dpmmx, dpmmy;
@@ -1346,27 +1385,27 @@ void gtk_begin_puzzle(void *handle, float xm, float xc,
1346 fe->hatchspace = 1.0 * pw / wmm; 1385 fe->hatchspace = 1.0 * pw / wmm;
1347} 1386}
1348 1387
1349void gtk_end_puzzle(void *handle) 1388static void gtk_end_puzzle(void *handle)
1350{ 1389{
1351 frontend *fe = (frontend *)handle; 1390 frontend *fe = (frontend *)handle;
1352 cairo_restore(fe->cr); 1391 cairo_restore(fe->cr);
1353} 1392}
1354 1393
1355void gtk_end_page(void *handle, int number) 1394static void gtk_end_page(void *handle, int number)
1356{ 1395{
1357} 1396}
1358 1397
1359void gtk_end_doc(void *handle) 1398static void gtk_end_doc(void *handle)
1360{ 1399{
1361} 1400}
1362 1401
1363void gtk_line_width(void *handle, float width) 1402static void gtk_line_width(void *handle, float width)
1364{ 1403{
1365 frontend *fe = (frontend *)handle; 1404 frontend *fe = (frontend *)handle;
1366 cairo_set_line_width(fe->cr, width); 1405 cairo_set_line_width(fe->cr, width);
1367} 1406}
1368 1407
1369void gtk_line_dotted(void *handle, bool dotted) 1408static void gtk_line_dotted(void *handle, bool dotted)
1370{ 1409{
1371 frontend *fe = (frontend *)handle; 1410 frontend *fe = (frontend *)handle;
1372 1411
@@ -1379,7 +1418,7 @@ void gtk_line_dotted(void *handle, bool dotted)
1379} 1418}
1380#endif /* USE_PRINTING */ 1419#endif /* USE_PRINTING */
1381 1420
1382const struct internal_drawing_api internal_drawing = { 1421static const struct internal_drawing_api internal_drawing = {
1383 draw_set_colour, 1422 draw_set_colour,
1384#ifdef USE_CAIRO 1423#ifdef USE_CAIRO
1385 do_draw_fill, 1424 do_draw_fill,
@@ -1388,14 +1427,14 @@ const struct internal_drawing_api internal_drawing = {
1388}; 1427};
1389 1428
1390#ifdef USE_CAIRO 1429#ifdef USE_CAIRO
1391const struct internal_drawing_api internal_printing = { 1430static const struct internal_drawing_api internal_printing = {
1392 print_set_colour, 1431 print_set_colour,
1393 do_print_fill, 1432 do_print_fill,
1394 do_print_fill_preserve, 1433 do_print_fill_preserve,
1395}; 1434};
1396#endif 1435#endif
1397 1436
1398const struct drawing_api gtk_drawing = { 1437static const struct drawing_api gtk_drawing = {
1399 gtk_draw_text, 1438 gtk_draw_text,
1400 gtk_draw_rect, 1439 gtk_draw_rect,
1401 gtk_draw_line, 1440 gtk_draw_line,
@@ -1502,13 +1541,18 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
1502 keyval = '\177'; 1541 keyval = '\177';
1503 else if ((event->keyval == 'z' || event->keyval == 'Z') && shift && ctrl) 1542 else if ((event->keyval == 'z' || event->keyval == 'Z') && shift && ctrl)
1504 keyval = UI_REDO; 1543 keyval = UI_REDO;
1544 else if (event->keyval == GDK_KEY_ISO_Left_Tab) {
1545 /* SHIFT+TAB gets special handling. Ref:
1546 * https://mail.gnome.org/archives/gtk-list/1999-August/msg00145.html */
1547 keyval = '\t' | MOD_SHFT;
1548 }
1505 else if (event->string[0] && !event->string[1]) 1549 else if (event->string[0] && !event->string[1])
1506 keyval = (unsigned char)event->string[0]; 1550 keyval = (unsigned char)event->string[0];
1507 else 1551 else
1508 keyval = -1; 1552 keyval = -1;
1509 1553
1510 if (keyval >= 0 && 1554 if (keyval >= 0 &&
1511 !midend_process_key(fe->me, 0, 0, keyval)) 1555 midend_process_key(fe->me, 0, 0, keyval) == PKR_QUIT)
1512 gtk_widget_destroy(fe->window); 1556 gtk_widget_destroy(fe->window);
1513 1557
1514 return true; 1558 return true;
@@ -1542,8 +1586,8 @@ static gint button_event(GtkWidget *widget, GdkEventButton *event,
1542 if (event->type == GDK_BUTTON_RELEASE && button >= LEFT_BUTTON) 1586 if (event->type == GDK_BUTTON_RELEASE && button >= LEFT_BUTTON)
1543 button += LEFT_RELEASE - LEFT_BUTTON; 1587 button += LEFT_RELEASE - LEFT_BUTTON;
1544 1588
1545 if (!midend_process_key(fe->me, event->x - fe->ox, 1589 if (midend_process_key(fe->me, event->x - fe->ox,
1546 event->y - fe->oy, button)) 1590 event->y - fe->oy, button) == PKR_QUIT)
1547 gtk_widget_destroy(fe->window); 1591 gtk_widget_destroy(fe->window);
1548 1592
1549 return true; 1593 return true;
@@ -1567,8 +1611,8 @@ static gint motion_event(GtkWidget *widget, GdkEventMotion *event,
1567 else 1611 else
1568 return false; /* don't even know what button! */ 1612 return false; /* don't even know what button! */
1569 1613
1570 if (!midend_process_key(fe->me, event->x - fe->ox, 1614 if (midend_process_key(fe->me, event->x - fe->ox,
1571 event->y - fe->oy, button)) 1615 event->y - fe->oy, button) == PKR_QUIT)
1572 gtk_widget_destroy(fe->window); 1616 gtk_widget_destroy(fe->window);
1573#if GTK_CHECK_VERSION(2,12,0) 1617#if GTK_CHECK_VERSION(2,12,0)
1574 gdk_event_request_motions(event); 1618 gdk_event_request_motions(event);
@@ -1649,7 +1693,7 @@ static void resize_puzzle_to_area(frontend *fe, int x, int y)
1649 1693
1650 fe->w = x; 1694 fe->w = x;
1651 fe->h = y; 1695 fe->h = y;
1652 midend_size(fe->me, &x, &y, true); 1696 midend_size(fe->me, &x, &y, true, 1.0);
1653 fe->pw = x; 1697 fe->pw = x;
1654 fe->ph = y; 1698 fe->ph = y;
1655#if GTK_CHECK_VERSION(3,10,0) 1699#if GTK_CHECK_VERSION(3,10,0)
@@ -1773,8 +1817,8 @@ static void align_label(GtkLabel *label, double x, double y)
1773} 1817}
1774 1818
1775#if GTK_CHECK_VERSION(3,0,0) 1819#if GTK_CHECK_VERSION(3,0,0)
1776bool message_box(GtkWidget *parent, const char *title, const char *msg, 1820static bool message_box(GtkWidget *parent, const char *title, const char *msg,
1777 bool centre, int type) 1821 bool centre, int type)
1778{ 1822{
1779 GtkWidget *window; 1823 GtkWidget *window;
1780 gint ret; 1824 gint ret;
@@ -1807,7 +1851,7 @@ bool message_box(GtkWidget *parent, const char *title, const char *msg,
1807 bool centre, int type) 1851 bool centre, int type)
1808{ 1852{
1809 GtkWidget *window, *hbox, *text, *button; 1853 GtkWidget *window, *hbox, *text, *button;
1810 char *titles; 1854 const char *titles;
1811 int i, def, cancel; 1855 int i, def, cancel;
1812 1856
1813 window = gtk_dialog_new(); 1857 window = gtk_dialog_new();
@@ -1868,7 +1912,7 @@ bool message_box(GtkWidget *parent, const char *title, const char *msg,
1868} 1912}
1869#endif /* GTK_CHECK_VERSION(3,0,0) */ 1913#endif /* GTK_CHECK_VERSION(3,0,0) */
1870 1914
1871void error_box(GtkWidget *parent, const char *msg) 1915static void error_box(GtkWidget *parent, const char *msg)
1872{ 1916{
1873 message_box(parent, "Error", msg, false, MB_OK); 1917 message_box(parent, "Error", msg, false, MB_OK);
1874} 1918}
@@ -1883,9 +1927,17 @@ static void config_ok_button_clicked(GtkButton *button, gpointer data)
1883 if (err) 1927 if (err)
1884 error_box(fe->cfgbox, err); 1928 error_box(fe->cfgbox, err);
1885 else { 1929 else {
1930 if (fe->cfg_which == CFG_PREFS) {
1931 char *prefs_err = save_prefs(fe);
1932 if (prefs_err) {
1933 error_box(fe->cfgbox, prefs_err);
1934 sfree(prefs_err);
1935 }
1936 }
1886 fe->cfgret = true; 1937 fe->cfgret = true;
1887 gtk_widget_destroy(fe->cfgbox); 1938 gtk_widget_destroy(fe->cfgbox);
1888 changed_preset(fe); 1939 if (fe->cfg_which != CFG_PREFS)
1940 changed_preset(fe);
1889 } 1941 }
1890} 1942}
1891 1943
@@ -2163,7 +2215,7 @@ static void menu_key_event(GtkMenuItem *menuitem, gpointer data)
2163 frontend *fe = (frontend *)data; 2215 frontend *fe = (frontend *)data;
2164 int key = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), 2216 int key = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem),
2165 "user-data")); 2217 "user-data"));
2166 if (!midend_process_key(fe->me, 0, 0, key)) 2218 if (midend_process_key(fe->me, 0, 0, key) == PKR_QUIT)
2167 gtk_widget_destroy(fe->window); 2219 gtk_widget_destroy(fe->window);
2168} 2220}
2169 2221
@@ -2185,7 +2237,7 @@ static void get_size(frontend *fe, int *px, int *py)
2185 */ 2237 */
2186 x = INT_MAX; 2238 x = INT_MAX;
2187 y = INT_MAX; 2239 y = INT_MAX;
2188 midend_size(fe->me, &x, &y, false); 2240 midend_size(fe->me, &x, &y, false, 1.0);
2189 *px = x; 2241 *px = x;
2190 *py = y; 2242 *py = y;
2191} 2243}
@@ -2362,8 +2414,8 @@ static void menu_preset_event(GtkMenuItem *menuitem, gpointer data)
2362 midend_redraw(fe->me); 2414 midend_redraw(fe->me);
2363} 2415}
2364 2416
2365GdkAtom compound_text_atom, utf8_string_atom; 2417static GdkAtom compound_text_atom, utf8_string_atom;
2366bool paste_initialised = false; 2418static bool paste_initialised = false;
2367 2419
2368static void set_selection(frontend *fe, GdkAtom selection) 2420static void set_selection(frontend *fe, GdkAtom selection)
2369{ 2421{
@@ -2380,16 +2432,16 @@ static void set_selection(frontend *fe, GdkAtom selection)
2380 * COMPOUND_TEXT or UTF8_STRING. 2432 * COMPOUND_TEXT or UTF8_STRING.
2381 */ 2433 */
2382 2434
2383 if (gtk_selection_owner_set(fe->area, selection, CurrentTime)) { 2435 if (gtk_selection_owner_set(fe->window, selection, CurrentTime)) {
2384 gtk_selection_clear_targets(fe->area, selection); 2436 gtk_selection_clear_targets(fe->window, selection);
2385 gtk_selection_add_target(fe->area, selection, 2437 gtk_selection_add_target(fe->window, selection,
2386 GDK_SELECTION_TYPE_STRING, 1); 2438 GDK_SELECTION_TYPE_STRING, 1);
2387 gtk_selection_add_target(fe->area, selection, compound_text_atom, 1); 2439 gtk_selection_add_target(fe->window, selection, compound_text_atom, 1);
2388 gtk_selection_add_target(fe->area, selection, utf8_string_atom, 1); 2440 gtk_selection_add_target(fe->window, selection, utf8_string_atom, 1);
2389 } 2441 }
2390} 2442}
2391 2443
2392void write_clip(frontend *fe, char *data) 2444static void write_clip(frontend *fe, char *data)
2393{ 2445{
2394 if (fe->paste_data) 2446 if (fe->paste_data)
2395 sfree(fe->paste_data); 2447 sfree(fe->paste_data);
@@ -2401,16 +2453,16 @@ void write_clip(frontend *fe, char *data)
2401 set_selection(fe, GDK_SELECTION_CLIPBOARD); 2453 set_selection(fe, GDK_SELECTION_CLIPBOARD);
2402} 2454}
2403 2455
2404void selection_get(GtkWidget *widget, GtkSelectionData *seldata, 2456static void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
2405 guint info, guint time_stamp, gpointer data) 2457 guint info, guint time_stamp, gpointer data)
2406{ 2458{
2407 frontend *fe = (frontend *)data; 2459 frontend *fe = (frontend *)data;
2408 gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8, 2460 gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8,
2409 fe->paste_data, fe->paste_data_len); 2461 fe->paste_data, fe->paste_data_len);
2410} 2462}
2411 2463
2412gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, 2464static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
2413 gpointer data) 2465 gpointer data)
2414{ 2466{
2415 frontend *fe = (frontend *)data; 2467 frontend *fe = (frontend *)data;
2416 2468
@@ -2508,7 +2560,7 @@ static char *file_selector(frontend *fe, const char *title, bool save)
2508#endif 2560#endif
2509 2561
2510#ifdef USE_PRINTING 2562#ifdef USE_PRINTING
2511GObject *create_print_widget(GtkPrintOperation *print, gpointer data) 2563static GObject *create_print_widget(GtkPrintOperation *print, gpointer data)
2512{ 2564{
2513 GtkLabel *count_label, *width_label, *height_label, 2565 GtkLabel *count_label, *width_label, *height_label,
2514 *scale_llabel, *scale_rlabel; 2566 *scale_llabel, *scale_rlabel;
@@ -2649,8 +2701,8 @@ GObject *create_print_widget(GtkPrintOperation *print, gpointer data)
2649 return G_OBJECT(grid); 2701 return G_OBJECT(grid);
2650} 2702}
2651 2703
2652void apply_print_widget(GtkPrintOperation *print, 2704static void apply_print_widget(GtkPrintOperation *print,
2653 GtkWidget *widget, gpointer data) 2705 GtkWidget *widget, gpointer data)
2654{ 2706{
2655 frontend *fe = (frontend *)data; 2707 frontend *fe = (frontend *)data;
2656 2708
@@ -2673,8 +2725,8 @@ void apply_print_widget(GtkPrintOperation *print,
2673 } 2725 }
2674} 2726}
2675 2727
2676void print_begin(GtkPrintOperation *printop, 2728static void print_begin(GtkPrintOperation *printop,
2677 GtkPrintContext *context, gpointer data) 2729 GtkPrintContext *context, gpointer data)
2678{ 2730{
2679 frontend *fe = (frontend *)data; 2731 frontend *fe = (frontend *)data;
2680 midend *nme = NULL; /* non-interactive midend for bulk puzzle generation */ 2732 midend *nme = NULL; /* non-interactive midend for bulk puzzle generation */
@@ -2708,6 +2760,8 @@ void print_begin(GtkPrintOperation *printop,
2708 thegame.free_params(params); 2760 thegame.free_params(params);
2709 } 2761 }
2710 2762
2763 load_prefs(fe);
2764
2711 midend_new_game(nme); 2765 midend_new_game(nme);
2712 err = midend_print_puzzle(nme, fe->doc, fe->printsolns); 2766 err = midend_print_puzzle(nme, fe->doc, fe->printsolns);
2713 } 2767 }
@@ -2725,16 +2779,16 @@ void print_begin(GtkPrintOperation *printop,
2725 document_begin(fe->doc, fe->print_dr); 2779 document_begin(fe->doc, fe->print_dr);
2726} 2780}
2727 2781
2728void draw_page(GtkPrintOperation *printop, 2782static void draw_page(GtkPrintOperation *printop,
2729 GtkPrintContext *context, 2783 GtkPrintContext *context,
2730 gint page_nr, gpointer data) 2784 gint page_nr, gpointer data)
2731{ 2785{
2732 frontend *fe = (frontend *)data; 2786 frontend *fe = (frontend *)data;
2733 document_print_page(fe->doc, fe->print_dr, page_nr); 2787 document_print_page(fe->doc, fe->print_dr, page_nr);
2734} 2788}
2735 2789
2736void print_end(GtkPrintOperation *printop, 2790static void print_end(GtkPrintOperation *printop,
2737 GtkPrintContext *context, gpointer data) 2791 GtkPrintContext *context, gpointer data)
2738{ 2792{
2739 frontend *fe = (frontend *)data; 2793 frontend *fe = (frontend *)data;
2740 2794
@@ -2919,6 +2973,206 @@ static void menu_load_event(GtkMenuItem *menuitem, gpointer data)
2919 } 2973 }
2920} 2974}
2921 2975
2976static char *prefs_dir(void)
2977{
2978 const char *var;
2979 if ((var = getenv("SGT_PUZZLES_DIR")) != NULL)
2980 return dupstr(var);
2981 if ((var = getenv("XDG_CONFIG_HOME")) != NULL) {
2982 size_t size = strlen(var) + 20;
2983 char *dir = snewn(size, char);
2984 sprintf(dir, "%s/sgt-puzzles", var);
2985 return dir;
2986 }
2987 if ((var = getenv("HOME")) != NULL) {
2988 size_t size = strlen(var) + 32;
2989 char *dir = snewn(size, char);
2990 sprintf(dir, "%s/.config/sgt-puzzles", var);
2991 return dir;
2992 }
2993 return NULL;
2994}
2995
2996static char *prefs_path_general(const game *game, const char *suffix)
2997{
2998 char *dir, *path;
2999
3000 dir = prefs_dir();
3001 if (!dir)
3002 return NULL;
3003
3004 path = make_prefs_path(dir, "/", game, suffix);
3005
3006 sfree(dir);
3007 return path;
3008}
3009
3010static char *prefs_path(const game *game)
3011{
3012 return prefs_path_general(game, ".conf");
3013}
3014
3015static char *prefs_tmp_path(const game *game)
3016{
3017 return prefs_path_general(game, ".conf.tmp");
3018}
3019
3020static void load_prefs(frontend *fe)
3021{
3022 const game *game = midend_which_game(fe->me);
3023 char *path = prefs_path(game);
3024 if (!path)
3025 return;
3026 FILE *fp = fopen(path, "r");
3027 if (!fp)
3028 return;
3029 const char *err = midend_load_prefs(fe->me, savefile_read, fp);
3030 fclose(fp);
3031 if (err)
3032 fprintf(stderr, "Unable to load preferences file %s:\n%s\n",
3033 path, err);
3034 sfree(path);
3035}
3036
3037static char *save_prefs(frontend *fe)
3038{
3039 const game *game = midend_which_game(fe->me);
3040 char *dir_path = prefs_dir();
3041 char *file_path = prefs_path(game);
3042 char *tmp_path = prefs_tmp_path(game);
3043 struct savefile_write_ctx wctx[1];
3044 int fd;
3045 bool cleanup_dir = false, cleanup_tmpfile = false;
3046 char *err = NULL;
3047
3048 if (!dir_path || !file_path || !tmp_path) {
3049 sprintf(err = snewn(256, char),
3050 "Unable to save preferences:\n"
3051 "Could not determine pathname for configuration files");
3052 goto out;
3053 }
3054
3055 if (mkdir(dir_path, 0777) < 0) {
3056 /* Ignore errors while trying to make the directory. It may
3057 * well already exist, and even if we got some error code
3058 * other than EEXIST, it's still worth at least _trying_ to
3059 * make the file inside it, and see if that goes wrong. */
3060 } else {
3061 cleanup_dir = true;
3062 }
3063
3064 fd = open(tmp_path, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0666);
3065 if (fd < 0) {
3066 const char *os_err = strerror(errno);
3067 sprintf(err = snewn(256 + strlen(tmp_path) + strlen(os_err), char),
3068 "Unable to save preferences:\n"
3069 "Unable to create file '%s': %s", tmp_path, os_err);
3070 goto out;
3071 } else {
3072 cleanup_tmpfile = true;
3073 }
3074
3075 wctx->error = 0;
3076 wctx->fp = fdopen(fd, "w");
3077 midend_save_prefs(fe->me, savefile_write, wctx);
3078 fclose(wctx->fp);
3079 if (wctx->error) {
3080 const char *os_err = strerror(wctx->error);
3081 sprintf(err = snewn(80 + strlen(tmp_path) + strlen(os_err), char),
3082 "Unable to write file '%s': %s", tmp_path, os_err);
3083 goto out;
3084 }
3085
3086 if (rename(tmp_path, file_path) < 0) {
3087 const char *os_err = strerror(errno);
3088 sprintf(err = snewn(256 + strlen(tmp_path) + strlen(file_path) +
3089 strlen(os_err), char),
3090 "Unable to save preferences:\n"
3091 "Unable to rename '%s' to '%s': %s", tmp_path, file_path,
3092 os_err);
3093 goto out;
3094 } else {
3095 cleanup_dir = false;
3096 cleanup_tmpfile = false;
3097 }
3098
3099 out:
3100 if (cleanup_tmpfile) {
3101 if (unlink(tmp_path) < 0) { /* can't do anything about this */ }
3102 }
3103 if (cleanup_dir) {
3104 if (rmdir(dir_path) < 0) { /* can't do anything about this */ }
3105 }
3106 sfree(dir_path);
3107 sfree(file_path);
3108 sfree(tmp_path);
3109 return err;
3110}
3111
3112static bool delete_prefs(const game *game, char **msg)
3113{
3114 char *dir_path = prefs_dir();
3115 char *file_path = prefs_path(game);
3116 char *tmp_path = prefs_tmp_path(game);
3117 char *msgs[3];
3118 int i, len, nmsgs = 0;
3119 char *p;
3120 bool ok = true;
3121
3122 if (unlink(file_path) == 0) {
3123 sprintf(msgs[nmsgs++] = snewn(256 + strlen(file_path), char),
3124 "Removed preferences file %s\n", file_path);
3125 } else if (errno != ENOENT) {
3126 const char *os_err = strerror(errno);
3127 sprintf(msgs[nmsgs++] = snewn(256 + strlen(file_path) + strlen(os_err),
3128 char),
3129 "Failed to remove preferences file %s: %s\n",
3130 file_path, os_err);
3131 ok = false;
3132 }
3133
3134 if (unlink(tmp_path) == 0) {
3135 sprintf(msgs[nmsgs++] = snewn(256 + strlen(tmp_path), char),
3136 "Removed temporary file %s\n", tmp_path);
3137 } else if (errno != ENOENT) {
3138 const char *os_err = strerror(errno);
3139 sprintf(msgs[nmsgs++] = snewn(256 + strlen(tmp_path) + strlen(os_err),
3140 char),
3141 "Failed to remove temporary file %s: %s\n", tmp_path, os_err);
3142 ok = false;
3143 }
3144
3145 if (rmdir(dir_path) == 0) {
3146 sprintf(msgs[nmsgs++] = snewn(256 + strlen(dir_path), char),
3147 "Removed empty preferences directory %s\n", dir_path);
3148 } else if (errno != ENOENT && errno != ENOTEMPTY) {
3149 const char *os_err = strerror(errno);
3150 sprintf(msgs[nmsgs++] = snewn(256 + strlen(dir_path) + strlen(os_err),
3151 char),
3152 "Failed to remove preferences directory %s: %s\n",
3153 dir_path, os_err);
3154 ok = false;
3155 }
3156
3157 for (i = len = 0; i < nmsgs; i++)
3158 len += strlen(msgs[i]);
3159 *msg = snewn(len + 1, char);
3160 p = *msg;
3161 for (i = len = 0; i < nmsgs; i++) {
3162 size_t len = strlen(msgs[i]);
3163 memcpy(p, msgs[i], len);
3164 p += len;
3165 sfree(msgs[i]);
3166 }
3167 *p = '\0';
3168
3169 sfree(dir_path);
3170 sfree(file_path);
3171 sfree(tmp_path);
3172
3173 return ok;
3174}
3175
2922#ifdef USE_PRINTING 3176#ifdef USE_PRINTING
2923static void menu_print_event(GtkMenuItem *menuitem, gpointer data) 3177static void menu_print_event(GtkMenuItem *menuitem, gpointer data)
2924{ 3178{
@@ -2960,11 +3214,85 @@ static void menu_config_event(GtkMenuItem *menuitem, gpointer data)
2960 if (!get_config(fe, which)) 3214 if (!get_config(fe, which))
2961 return; 3215 return;
2962 3216
2963 midend_new_game(fe->me); 3217 if (which != CFG_PREFS)
3218 midend_new_game(fe->me);
3219
2964 resize_fe(fe); 3220 resize_fe(fe);
2965 midend_redraw(fe->me); 3221 midend_redraw(fe->me);
2966} 3222}
2967 3223
3224#ifndef HELP_BROWSER_PATH
3225#define HELP_BROWSER_PATH "xdg-open:sensible-browser:$BROWSER"
3226#endif
3227
3228static bool try_show_help(const char *browser, const char *help_name)
3229{
3230 const char *argv[3] = { browser, help_name, NULL };
3231
3232 return g_spawn_async(NULL, (char **)argv, NULL,
3233 G_SPAWN_SEARCH_PATH,
3234 NULL, NULL, NULL, NULL);
3235}
3236
3237static void show_help(frontend *fe, const char *topic)
3238{
3239 char *path = dupstr(HELP_BROWSER_PATH);
3240 char *path_entry;
3241 char *help_name;
3242 size_t help_name_size;
3243 bool succeeded = true;
3244
3245 help_name_size = strlen(HELP_DIR) + 4 + strlen(topic) + 6;
3246 help_name = snewn(help_name_size, char);
3247 sprintf(help_name, "%s/en/%s.html",
3248 HELP_DIR, topic);
3249
3250 if (access(help_name, R_OK)) {
3251 error_box(fe->window, "Help file is not installed");
3252 sfree(path);
3253 sfree(help_name);
3254 return;
3255 }
3256
3257 path_entry = path;
3258 for (;;) {
3259 size_t len;
3260 bool last;
3261
3262 len = strcspn(path_entry, ":");
3263 last = path_entry[len] == 0;
3264 path_entry[len] = 0;
3265
3266 if (path_entry[0] == '$') {
3267 const char *command = getenv(path_entry + 1);
3268
3269 if (command)
3270 succeeded = try_show_help(command, help_name);
3271 } else {
3272 succeeded = try_show_help(path_entry, help_name);
3273 }
3274
3275 if (last || succeeded)
3276 break;
3277 path_entry += len + 1;
3278 }
3279
3280 if (!succeeded)
3281 error_box(fe->window, "Failed to start a help browser");
3282 sfree(path);
3283 sfree(help_name);
3284}
3285
3286static void menu_help_contents_event(GtkMenuItem *menuitem, gpointer data)
3287{
3288 show_help((frontend *)data, "index");
3289}
3290
3291static void menu_help_specific_event(GtkMenuItem *menuitem, gpointer data)
3292{
3293 show_help((frontend *)data, thegame.htmlhelp_topic);
3294}
3295
2968static void menu_about_event(GtkMenuItem *menuitem, gpointer data) 3296static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
2969{ 3297{
2970 frontend *fe = (frontend *)data; 3298 frontend *fe = (frontend *)data;
@@ -2975,12 +3303,9 @@ static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
2975 "version", ver, \ 3303 "version", ver, \
2976 "comments", "Part of Simon Tatham's Portable Puzzle Collection" 3304 "comments", "Part of Simon Tatham's Portable Puzzle Collection"
2977 3305
2978 extern char *const *const xpm_icons[];
2979 extern const int n_xpm_icons;
2980
2981 if (n_xpm_icons) { 3306 if (n_xpm_icons) {
2982 GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data 3307 GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data
2983 ((const gchar **)xpm_icons[n_xpm_icons-1]); 3308 ((const gchar **)xpm_icons[0]);
2984 3309
2985 gtk_show_about_dialog 3310 gtk_show_about_dialog
2986 (GTK_WINDOW(fe->window), 3311 (GTK_WINDOW(fe->window),
@@ -3092,8 +3417,6 @@ static frontend *new_window(
3092 GList *iconlist; 3417 GList *iconlist;
3093 int x, y, n; 3418 int x, y, n;
3094 char errbuf[1024]; 3419 char errbuf[1024];
3095 extern char *const *const xpm_icons[];
3096 extern const int n_xpm_icons;
3097 struct preset_menu *preset_menu; 3420 struct preset_menu *preset_menu;
3098 3421
3099 fe = snew(frontend); 3422 fe = snew(frontend);
@@ -3113,6 +3436,7 @@ static frontend *new_window(
3113 fe->timer_id = -1; 3436 fe->timer_id = -1;
3114 3437
3115 fe->me = midend_new(fe, &thegame, &gtk_drawing, fe); 3438 fe->me = midend_new(fe, &thegame, &gtk_drawing, fe);
3439 load_prefs(fe);
3116 3440
3117 fe->dr_api = &internal_drawing; 3441 fe->dr_api = &internal_drawing;
3118 3442
@@ -3376,6 +3700,16 @@ static frontend *new_window(
3376 G_CALLBACK(menu_solve_event), fe); 3700 G_CALLBACK(menu_solve_event), fe);
3377 gtk_widget_show(menuitem); 3701 gtk_widget_show(menuitem);
3378 } 3702 }
3703
3704 add_menu_separator(GTK_CONTAINER(menu));
3705 menuitem = gtk_menu_item_new_with_label("Preferences...");
3706 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3707 g_object_set_data(G_OBJECT(menuitem), "user-data",
3708 GINT_TO_POINTER(CFG_PREFS));
3709 g_signal_connect(G_OBJECT(menuitem), "activate",
3710 G_CALLBACK(menu_config_event), fe);
3711 gtk_widget_show(menuitem);
3712
3379 add_menu_separator(GTK_CONTAINER(menu)); 3713 add_menu_separator(GTK_CONTAINER(menu));
3380 add_menu_ui_item(fe, GTK_CONTAINER(menu), "Exit", UI_QUIT, 'q', 0); 3714 add_menu_ui_item(fe, GTK_CONTAINER(menu), "Exit", UI_QUIT, 'q', 0);
3381 3715
@@ -3386,6 +3720,25 @@ static frontend *new_window(
3386 menu = gtk_menu_new(); 3720 menu = gtk_menu_new();
3387 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); 3721 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
3388 3722
3723 menuitem = gtk_menu_item_new_with_label("Contents");
3724 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3725 g_signal_connect(G_OBJECT(menuitem), "activate",
3726 G_CALLBACK(menu_help_contents_event), fe);
3727 gtk_widget_show(menuitem);
3728
3729 if (thegame.htmlhelp_topic) {
3730 char *item;
3731 assert(thegame.name);
3732 item = snewn(9 + strlen(thegame.name), char);
3733 sprintf(item, "Help on %s", thegame.name);
3734 menuitem = gtk_menu_item_new_with_label(item);
3735 sfree(item);
3736 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3737 g_signal_connect(G_OBJECT(menuitem), "activate",
3738 G_CALLBACK(menu_help_specific_event), fe);
3739 gtk_widget_show(menuitem);
3740 }
3741
3389 menuitem = gtk_menu_item_new_with_label("About"); 3742 menuitem = gtk_menu_item_new_with_label("About");
3390 gtk_container_add(GTK_CONTAINER(menu), menuitem); 3743 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3391 g_signal_connect(G_OBJECT(menuitem), "activate", 3744 g_signal_connect(G_OBJECT(menuitem), "activate",
@@ -3503,9 +3856,9 @@ static frontend *new_window(
3503 G_CALLBACK(button_event), fe); 3856 G_CALLBACK(button_event), fe);
3504 g_signal_connect(G_OBJECT(fe->area), "motion_notify_event", 3857 g_signal_connect(G_OBJECT(fe->area), "motion_notify_event",
3505 G_CALLBACK(motion_event), fe); 3858 G_CALLBACK(motion_event), fe);
3506 g_signal_connect(G_OBJECT(fe->area), "selection_get", 3859 g_signal_connect(G_OBJECT(fe->window), "selection_get",
3507 G_CALLBACK(selection_get), fe); 3860 G_CALLBACK(selection_get), fe);
3508 g_signal_connect(G_OBJECT(fe->area), "selection_clear_event", 3861 g_signal_connect(G_OBJECT(fe->window), "selection_clear_event",
3509 G_CALLBACK(selection_clear), fe); 3862 G_CALLBACK(selection_clear), fe);
3510#if GTK_CHECK_VERSION(3,0,0) 3863#if GTK_CHECK_VERSION(3,0,0)
3511 g_signal_connect(G_OBJECT(fe->area), "draw", 3864 g_signal_connect(G_OBJECT(fe->area), "draw",
@@ -3534,7 +3887,7 @@ static frontend *new_window(
3534 if (n_xpm_icons) { 3887 if (n_xpm_icons) {
3535 gtk_window_set_icon(GTK_WINDOW(fe->window), 3888 gtk_window_set_icon(GTK_WINDOW(fe->window),
3536 gdk_pixbuf_new_from_xpm_data 3889 gdk_pixbuf_new_from_xpm_data
3537 ((const gchar **)xpm_icons[0])); 3890 ((const gchar **)xpm_icons[n_xpm_icons-1]));
3538 3891
3539 iconlist = NULL; 3892 iconlist = NULL;
3540 for (n = 0; n < n_xpm_icons; n++) { 3893 for (n = 0; n < n_xpm_icons; n++) {
@@ -3578,10 +3931,10 @@ static void list_presets_from_menu(struct preset_menu *menu)
3578int main(int argc, char **argv) 3931int main(int argc, char **argv)
3579{ 3932{
3580 char *pname = argv[0]; 3933 char *pname = argv[0];
3581 char *error;
3582 int ngenerate = 0, px = 1, py = 1; 3934 int ngenerate = 0, px = 1, py = 1;
3583 bool print = false; 3935 bool print = false;
3584 bool time_generation = false, test_solve = false, list_presets = false; 3936 bool time_generation = false, test_solve = false, list_presets = false;
3937 bool delete_prefs_action = false;
3585 bool soln = false, colour = false; 3938 bool soln = false, colour = false;
3586 float scale = 1.0F; 3939 float scale = 1.0F;
3587 float redo_proportion = 0.0F; 3940 float redo_proportion = 0.0F;
@@ -3640,6 +3993,9 @@ int main(int argc, char **argv)
3640 test_solve = true; 3993 test_solve = true;
3641 } else if (doing_opts && !strcmp(p, "--list-presets")) { 3994 } else if (doing_opts && !strcmp(p, "--list-presets")) {
3642 list_presets = true; 3995 list_presets = true;
3996 } else if (doing_opts && (!strcmp(p, "--delete-prefs") ||
3997 !strcmp(p, "--delete-preferences"))) {
3998 delete_prefs_action = true;
3643 } else if (doing_opts && !strcmp(p, "--save")) { 3999 } else if (doing_opts && !strcmp(p, "--save")) {
3644 if (--ac > 0) { 4000 if (--ac > 0) {
3645 savefile = *++av; 4001 savefile = *++av;
@@ -3988,9 +4344,20 @@ int main(int argc, char **argv)
3988 list_presets_from_menu(menu); 4344 list_presets_from_menu(menu);
3989 midend_free(me); 4345 midend_free(me);
3990 return 0; 4346 return 0;
4347 } else if (delete_prefs_action) {
4348 char *msg = NULL;
4349 bool ok = delete_prefs(&thegame, &msg);
4350 if (!ok) {
4351 fputs(msg, stderr);
4352 return 1;
4353 } else {
4354 fputs(msg, stdout);
4355 return 0;
4356 }
3991 } else { 4357 } else {
3992 frontend *fe; 4358 frontend *fe;
3993 bool headless = screenshot_file != NULL; 4359 bool headless = screenshot_file != NULL;
4360 char *error = NULL;
3994 4361
3995 if (!headless) 4362 if (!headless)
3996 gtk_init(&argc, &argv); 4363 gtk_init(&argc, &argv);
@@ -3999,6 +4366,7 @@ int main(int argc, char **argv)
3999 4366
4000 if (!fe) { 4367 if (!fe) {
4001 fprintf(stderr, "%s: %s\n", pname, error); 4368 fprintf(stderr, "%s: %s\n", pname, error);
4369 sfree(error);
4002 return 1; 4370 return 1;
4003 } 4371 }
4004 4372
@@ -4014,7 +4382,7 @@ int main(int argc, char **argv)
4014 4382
4015 if (redo_proportion) { 4383 if (redo_proportion) {
4016 /* Start a redo. */ 4384 /* Start a redo. */
4017 midend_process_key(fe->me, 0, 0, 'r'); 4385 midend_process_key(fe->me, 0, 0, 'r');
4018 /* And freeze the timer at the specified position. */ 4386 /* And freeze the timer at the specified position. */
4019 midend_freeze_timer(fe->me, redo_proportion); 4387 midend_freeze_timer(fe->me, redo_proportion);
4020 } 4388 }