shithub: puzzles

Download patch

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);