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.
This commit is contained in:
Simon Tatham
2023-04-23 14:54:29 +01:00
parent 35cd44c563
commit 1fa28340e8
2 changed files with 156 additions and 4 deletions

View File

@ -179,10 +179,10 @@ solving it yourself after seeing the answer, you can just press Undo.
\dt \i\e{Preferences} \dt \i\e{Preferences}
\dd Where supported (currently only on Unix), brings up a dialog \dd Where supported (currently only on Windows and Unix), brings up a
allowing you to configure personal preferences about a particular dialog allowing you to configure personal preferences about a
game. Some of these preferences will be specific to a particular game; particular game. Some of these preferences will be specific to a
others will be common to all games. particular game; others will be common to all games.
\lcont{ \lcont{

152
windows.c
View File

@ -7,6 +7,7 @@
#ifndef NO_HTMLHELP #ifndef NO_HTMLHELP
#include <htmlhelp.h> #include <htmlhelp.h>
#endif /* NO_HTMLHELP */ #endif /* NO_HTMLHELP */
#include <io.h>
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
@ -35,6 +36,7 @@
#define IDM_LOAD 0x00F0 #define IDM_LOAD 0x00F0
#define IDM_PRINT 0x0100 #define IDM_PRINT 0x0100
#define IDM_PRESETS 0x0110 #define IDM_PRESETS 0x0110
#define IDM_PREFS 0x0120
#define IDM_GAMES 0x0300 #define IDM_GAMES 0x0300
#define IDM_KEYEMUL 0x0400 #define IDM_KEYEMUL 0x0400
@ -119,6 +121,8 @@ void debug_printf(const char *fmt, ...)
(WS_MAXIMIZEBOX | WS_OVERLAPPED)) (WS_MAXIMIZEBOX | WS_OVERLAPPED))
static void new_game_size(frontend *fe, float scale); static void new_game_size(frontend *fe, float scale);
static void load_prefs(midend *me);
static char *save_prefs(midend *me);
struct font { struct font {
HFONT font; HFONT font;
@ -935,6 +939,7 @@ void print(frontend *fe)
game_params *params; game_params *params;
nme = midend_new(NULL, fe->game, NULL, NULL); nme = midend_new(NULL, fe->game, NULL, NULL);
load_prefs(nme);
/* /*
* Set the non-interactive mid-end to have the same * Set the non-interactive mid-end to have the same
@ -1442,6 +1447,7 @@ static midend *midend_for_new_game(frontend *fe, const game *cgame,
if (!arg) { if (!arg) {
if (me) midend_free(me); if (me) midend_free(me);
me = midend_new(fe, cgame, &win_drawing, fe); me = midend_new(fe, cgame, &win_drawing, fe);
load_prefs(me);
midend_new_game(me); midend_new_game(me);
} else { } else {
FILE *fp; FILE *fp;
@ -1482,6 +1488,7 @@ static midend *midend_for_new_game(frontend *fe, const game *cgame,
if (!err_load) { if (!err_load) {
if (me) midend_free(me); if (me) midend_free(me);
me = midend_new(fe, loadgame, &win_drawing, fe); me = midend_new(fe, loadgame, &win_drawing, fe);
load_prefs(me);
err_load = midend_deserialise(me, savefile_read, fp); err_load = midend_deserialise(me, savefile_read, fp);
} }
} else { } else {
@ -1494,6 +1501,7 @@ static midend *midend_for_new_game(frontend *fe, const game *cgame,
*/ */
if (me) midend_free(me); if (me) midend_free(me);
me = midend_new(fe, cgame, &win_drawing, fe); me = midend_new(fe, cgame, &win_drawing, fe);
load_prefs(me);
err_param = midend_game_id(me, arg); err_param = midend_game_id(me, arg);
if (!err_param) { if (!err_param) {
midend_new_game(me); midend_new_game(me);
@ -1719,6 +1727,8 @@ static int fe_set_midend(frontend *fe, midend *me)
AppendMenu(menu, MF_ENABLED, IDM_SOLVE, TEXT("Sol&ve")); AppendMenu(menu, MF_ENABLED, IDM_SOLVE, TEXT("Sol&ve"));
} }
AppendMenu(menu, MF_SEPARATOR, 0, 0); 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")); AppendMenu(menu, MF_ENABLED, IDM_QUIT, TEXT("E&xit"));
menu = CreateMenu(); menu = CreateMenu();
AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT_PTR)menu, TEXT("&Help")); AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT_PTR)menu, TEXT("&Help"));
@ -2488,6 +2498,138 @@ static void update_type_menu_tick(frontend *fe)
DrawMenuBar(fe->hwnd); 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) static void update_copy_menu_greying(frontend *fe)
{ {
UINT enable = (midend_can_format_as_text_now(fe->me) ? UINT enable = (midend_can_format_as_text_now(fe->me) ?
@ -2582,6 +2724,16 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
if (get_config(fe, CFG_PRINT)) if (get_config(fe, CFG_PRINT))
print(fe); print(fe);
break; 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: case IDM_ABOUT:
about(fe); about(fe);
break; break;