ref: 1fa28340e8e8d8459456469c72f7156ccf1493f1
parent: 35cd44c563ec58e00a89109ef1f5450b08a3faa6
author: Simon Tatham <anakin@pobox.com>
date: Sun Apr 23 10:54:29 EDT 2023
Support user preferences on Windows. This is done using basically the same methods as on Unix, and just translating the system calls in save_prefs to a different API.
--- a/puzzles.but
+++ b/puzzles.but
@@ -179,10 +179,10 @@
\dt \i\e{Preferences}
-\dd Where supported (currently only on Unix), brings up a dialog
-allowing you to configure personal preferences about a particular
-game. Some of these preferences will be specific to a particular game;
-others will be common to all games.
+\dd Where supported (currently only on Windows and Unix), brings up a
+dialog allowing you to configure personal preferences about a
+particular game. Some of these preferences will be specific to a
+particular game; others will be common to all games.
\lcont{
--- a/windows.c
+++ b/windows.c
@@ -7,6 +7,7 @@
#ifndef NO_HTMLHELP
#include <htmlhelp.h>
#endif /* NO_HTMLHELP */
+#include <io.h>
#include <stdio.h>
#include <assert.h>
@@ -35,6 +36,7 @@
#define IDM_LOAD 0x00F0
#define IDM_PRINT 0x0100
#define IDM_PRESETS 0x0110
+#define IDM_PREFS 0x0120
#define IDM_GAMES 0x0300
#define IDM_KEYEMUL 0x0400
@@ -119,6 +121,8 @@
(WS_MAXIMIZEBOX | WS_OVERLAPPED))
static void new_game_size(frontend *fe, float scale);
+static void load_prefs(midend *me);
+static char *save_prefs(midend *me);
struct font {
HFONT font;
@@ -935,6 +939,7 @@
game_params *params;
nme = midend_new(NULL, fe->game, NULL, NULL);
+ load_prefs(nme);
/*
* Set the non-interactive mid-end to have the same
@@ -1442,6 +1447,7 @@
if (!arg) {
if (me) midend_free(me);
me = midend_new(fe, cgame, &win_drawing, fe);
+ load_prefs(me);
midend_new_game(me);
} else {
FILE *fp;
@@ -1482,6 +1488,7 @@
if (!err_load) {
if (me) midend_free(me);
me = midend_new(fe, loadgame, &win_drawing, fe);
+ load_prefs(me);
err_load = midend_deserialise(me, savefile_read, fp);
}
} else {
@@ -1494,6 +1501,7 @@
*/
if (me) midend_free(me);
me = midend_new(fe, cgame, &win_drawing, fe);
+ load_prefs(me);
err_param = midend_game_id(me, arg);
if (!err_param) {
midend_new_game(me);
@@ -1719,6 +1727,8 @@
AppendMenu(menu, MF_ENABLED, IDM_SOLVE, TEXT("Sol&ve"));
}
AppendMenu(menu, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu, MF_ENABLED, IDM_PREFS, TEXT("Pre&ferences"));
+ AppendMenu(menu, MF_SEPARATOR, 0, 0);
AppendMenu(menu, MF_ENABLED, IDM_QUIT, TEXT("E&xit"));
menu = CreateMenu();
AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT_PTR)menu, TEXT("&Help"));
@@ -2488,6 +2498,138 @@
DrawMenuBar(fe->hwnd);
}
+static char *prefs_dir(void)
+{
+ const char *var;
+ if ((var = getenv("APPDATA")) != NULL) {
+ size_t size = strlen(var) + 80;
+ char *dir = snewn(size, char);
+ sprintf(dir, "%s\\Simon Tatham's Portable Puzzle Collection", var);
+ return dir;
+ }
+ return NULL;
+}
+
+static char *prefs_path_general(const game *game, const char *suffix)
+{
+ char *dir, *path;
+
+ dir = prefs_dir();
+ if (!dir)
+ return NULL;
+
+ path = make_prefs_path(dir, "\\", game, suffix);
+
+ sfree(dir);
+ return path;
+}
+
+static char *prefs_path(const game *game)
+{
+ return prefs_path_general(game, ".conf");
+}
+
+static char *prefs_tmp_path(const game *game)
+{
+ return prefs_path_general(game, ".tmp");
+}
+
+static void load_prefs(midend *me)
+{
+ const game *game = midend_which_game(me);
+ char *path = prefs_path(game);
+ if (!path)
+ return;
+ FILE *fp = fopen(path, "r");
+ if (!fp)
+ return;
+ const char *err = midend_load_prefs(me, savefile_read, fp);
+ fclose(fp);
+ if (err)
+ fprintf(stderr, "Unable to load preferences file %s:\n%s\n",
+ path, err);
+ sfree(path);
+}
+
+static char *save_prefs(midend *me)
+{
+ const game *game = midend_which_game(me);
+ char *dir_path = prefs_dir();
+ char *file_path = prefs_path(game);
+ char *tmp_path = prefs_tmp_path(game);
+ HANDLE fh;
+ FILE *fp;
+ bool cleanup_dir = false, cleanup_tmpfile = false;
+ char *err = NULL;
+
+ if (!dir_path || !file_path || !tmp_path) {
+ sprintf(err = snewn(256, char),
+ "Unable to save preferences:\n"
+ "Could not determine pathname for configuration files");
+ goto out;
+ }
+
+ if (!CreateDirectory(dir_path, NULL)) {
+ /* Ignore errors while trying to make the directory. It may
+ * well already exist, and even if we got some error code
+ * other than EEXIST, it's still worth at least _trying_ to
+ * make the file inside it, and see if that goes wrong. */
+ } else {
+ cleanup_dir = true;
+ }
+
+ fh = CreateFile(tmp_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (fh == INVALID_HANDLE_VALUE) {
+ char *os_err = geterrstr();
+ sprintf(err = snewn(256 + strlen(tmp_path) + strlen(os_err), char),
+ "Unable to save preferences:\n"
+ "Unable to create file '%s':\n%s", tmp_path, os_err);
+ sfree(os_err);
+ goto out;
+ } else {
+ cleanup_tmpfile = true;
+ }
+
+ fp = _fdopen(_open_osfhandle((intptr_t)fh, 0), "w");
+ SetLastError(0);
+ midend_save_prefs(me, savefile_write, fp);
+ fclose(fp);
+ if (GetLastError()) {
+ char *os_err = geterrstr();
+ sprintf(err = snewn(80 + strlen(tmp_path) + strlen(os_err), char),
+ "Unable to write file '%s':\n%s", tmp_path, os_err);
+ sfree(os_err);
+ goto out;
+ }
+
+ if (MoveFileEx(tmp_path, file_path, MOVEFILE_REPLACE_EXISTING) < 0) {
+ char *os_err = geterrstr();
+ sprintf(err = snewn(256 + strlen(tmp_path) + strlen(file_path) +
+ strlen(os_err), char),
+ "Unable to save preferences:\n"
+ "Unable to rename '%s' to '%s':\n%s", tmp_path, file_path,
+ os_err);
+ sfree(os_err);
+ goto out;
+ } else {
+ cleanup_dir = false;
+ cleanup_tmpfile = false;
+ }
+
+ out:
+ if (cleanup_tmpfile) {
+ if (!DeleteFile(tmp_path)) { /* can't do anything about this */ }
+ }
+ if (cleanup_dir) {
+ if (!RemoveDirectory(dir_path)) { /* can't do anything about this */ }
+ }
+ sfree(dir_path);
+ sfree(file_path);
+ sfree(tmp_path);
+ return err;
+}
+
static void update_copy_menu_greying(frontend *fe)
{
UINT enable = (midend_can_format_as_text_now(fe->me) ?
@@ -2581,6 +2723,16 @@
case IDM_PRINT:
if (get_config(fe, CFG_PRINT))
print(fe);
+ break;
+ case IDM_PREFS:
+ if (get_config(fe, CFG_PREFS)) {
+ char *prefs_err = save_prefs(fe->me);
+ if (prefs_err) {
+ MessageBox(fe->hwnd, prefs_err, "Error saving preferences",
+ MB_ICONERROR | MB_OK);
+ sfree(prefs_err);
+ }
+ }
break;
case IDM_ABOUT:
about(fe);