mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 08:01:30 -07:00
Support preferences in the GTK frontend.
Finally, some user-visible behaviour changes as a payoff for all that preparation work! In this commit, the GTK puzzles get a 'Preferences' option in the menu, which presents a dialog box to configure the preference settings. On closing that dialog box, the puzzle preferences are enacted immediately, and also saved to a configuration file where the next run of the same puzzle will reload them. The default file location is ~/.config/sgt-puzzles/<puzzlename>.conf, although you can override the .config dir via $XDG_CONFIG_HOME or override the Puzzles-specific subdir with $SGT_PUZZLES_DIR. This is the first commit that actually exposes all the new preferences work to the user, and therefore, I've also added documentation of all the current preference options.
This commit is contained in:
166
gtk.c
166
gtk.c
@ -20,6 +20,9 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
|
|
||||||
@ -141,6 +144,8 @@ void fatal(const char *fmt, ...)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
static void changed_preset(frontend *fe);
|
static void changed_preset(frontend *fe);
|
||||||
|
static void load_prefs(frontend *fe);
|
||||||
|
static char *save_prefs(frontend *fe);
|
||||||
|
|
||||||
struct font {
|
struct font {
|
||||||
#ifdef USE_PANGO
|
#ifdef USE_PANGO
|
||||||
@ -1917,9 +1922,17 @@ static void config_ok_button_clicked(GtkButton *button, gpointer data)
|
|||||||
if (err)
|
if (err)
|
||||||
error_box(fe->cfgbox, err);
|
error_box(fe->cfgbox, err);
|
||||||
else {
|
else {
|
||||||
|
if (fe->cfg_which == CFG_PREFS) {
|
||||||
|
char *prefs_err = save_prefs(fe);
|
||||||
|
if (prefs_err) {
|
||||||
|
error_box(fe->cfgbox, prefs_err);
|
||||||
|
sfree(prefs_err);
|
||||||
|
}
|
||||||
|
}
|
||||||
fe->cfgret = true;
|
fe->cfgret = true;
|
||||||
gtk_widget_destroy(fe->cfgbox);
|
gtk_widget_destroy(fe->cfgbox);
|
||||||
changed_preset(fe);
|
if (fe->cfg_which != CFG_PREFS)
|
||||||
|
changed_preset(fe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2742,6 +2755,8 @@ static void print_begin(GtkPrintOperation *printop,
|
|||||||
thegame.free_params(params);
|
thegame.free_params(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
load_prefs(fe);
|
||||||
|
|
||||||
midend_new_game(nme);
|
midend_new_game(nme);
|
||||||
err = midend_print_puzzle(nme, fe->doc, fe->printsolns);
|
err = midend_print_puzzle(nme, fe->doc, fe->printsolns);
|
||||||
}
|
}
|
||||||
@ -2953,6 +2968,140 @@ static void menu_load_event(GtkMenuItem *menuitem, gpointer data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *prefs_dir(void)
|
||||||
|
{
|
||||||
|
const char *var;
|
||||||
|
if ((var = getenv("SGT_PUZZLES_DIR")) != NULL)
|
||||||
|
return dupstr(var);
|
||||||
|
if ((var = getenv("XDG_CONFIG_HOME")) != NULL) {
|
||||||
|
size_t size = strlen(var) + 20;
|
||||||
|
char *dir = snewn(size, char);
|
||||||
|
sprintf(dir, "%s/sgt-puzzles", var);
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
if ((var = getenv("HOME")) != NULL) {
|
||||||
|
size_t size = strlen(var) + 32;
|
||||||
|
char *dir = snewn(size, char);
|
||||||
|
sprintf(dir, "%s/.config/sgt-puzzles", var);
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *prefs_path_general(const char *suffix)
|
||||||
|
{
|
||||||
|
char *dir, *path;
|
||||||
|
|
||||||
|
dir = prefs_dir();
|
||||||
|
if (!dir)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
path = make_prefs_path(dir, "/", &thegame, suffix);
|
||||||
|
|
||||||
|
sfree(dir);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *prefs_path(void)
|
||||||
|
{
|
||||||
|
return prefs_path_general(".conf");
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *prefs_tmp_path(void)
|
||||||
|
{
|
||||||
|
return prefs_path_general(".conf.tmp");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void load_prefs(frontend *fe)
|
||||||
|
{
|
||||||
|
char *path = prefs_path();
|
||||||
|
if (!path)
|
||||||
|
return;
|
||||||
|
FILE *fp = fopen(path, "r");
|
||||||
|
if (!fp)
|
||||||
|
return;
|
||||||
|
const char *err = midend_load_prefs(fe->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(frontend *fe)
|
||||||
|
{
|
||||||
|
char *dir_path = prefs_dir();
|
||||||
|
char *file_path = prefs_path();
|
||||||
|
char *tmp_path = prefs_tmp_path();
|
||||||
|
struct savefile_write_ctx wctx[1];
|
||||||
|
int fd;
|
||||||
|
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 (mkdir(dir_path, 0777) < 0) {
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd = open(tmp_path, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0666);
|
||||||
|
if (fd < 0) {
|
||||||
|
const char *os_err = strerror(errno);
|
||||||
|
sprintf(err = snewn(256 + strlen(tmp_path) + strlen(os_err), char),
|
||||||
|
"Unable to save preferences:\n"
|
||||||
|
"Unable to create file '%s': %s", tmp_path, os_err);
|
||||||
|
goto out;
|
||||||
|
} else {
|
||||||
|
cleanup_tmpfile = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
wctx->error = 0;
|
||||||
|
wctx->fp = fdopen(fd, "w");
|
||||||
|
midend_save_prefs(fe->me, savefile_write, wctx);
|
||||||
|
fclose(wctx->fp);
|
||||||
|
if (wctx->error) {
|
||||||
|
const char *os_err = strerror(wctx->error);
|
||||||
|
sprintf(err = snewn(80 + strlen(tmp_path) + strlen(os_err), char),
|
||||||
|
"Unable to write file '%s': %s", tmp_path, os_err);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rename(tmp_path, file_path) < 0) {
|
||||||
|
const char *os_err = strerror(wctx->error);
|
||||||
|
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': %s", tmp_path, file_path,
|
||||||
|
os_err);
|
||||||
|
goto out;
|
||||||
|
} else {
|
||||||
|
cleanup_dir = false;
|
||||||
|
cleanup_tmpfile = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (cleanup_tmpfile) {
|
||||||
|
if (unlink(tmp_path) < 0) { /* can't do anything about this */ }
|
||||||
|
}
|
||||||
|
if (cleanup_dir) {
|
||||||
|
if (rmdir(dir_path) < 0) { /* can't do anything about this */ }
|
||||||
|
}
|
||||||
|
sfree(dir_path);
|
||||||
|
sfree(file_path);
|
||||||
|
sfree(tmp_path);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_PRINTING
|
#ifdef USE_PRINTING
|
||||||
static void menu_print_event(GtkMenuItem *menuitem, gpointer data)
|
static void menu_print_event(GtkMenuItem *menuitem, gpointer data)
|
||||||
{
|
{
|
||||||
@ -2994,7 +3143,9 @@ static void menu_config_event(GtkMenuItem *menuitem, gpointer data)
|
|||||||
if (!get_config(fe, which))
|
if (!get_config(fe, which))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
midend_new_game(fe->me);
|
if (which != CFG_PREFS)
|
||||||
|
midend_new_game(fe->me);
|
||||||
|
|
||||||
resize_fe(fe);
|
resize_fe(fe);
|
||||||
midend_redraw(fe->me);
|
midend_redraw(fe->me);
|
||||||
}
|
}
|
||||||
@ -3214,6 +3365,7 @@ static frontend *new_window(
|
|||||||
fe->timer_id = -1;
|
fe->timer_id = -1;
|
||||||
|
|
||||||
fe->me = midend_new(fe, &thegame, >k_drawing, fe);
|
fe->me = midend_new(fe, &thegame, >k_drawing, fe);
|
||||||
|
load_prefs(fe);
|
||||||
|
|
||||||
fe->dr_api = &internal_drawing;
|
fe->dr_api = &internal_drawing;
|
||||||
|
|
||||||
@ -3477,6 +3629,16 @@ static frontend *new_window(
|
|||||||
G_CALLBACK(menu_solve_event), fe);
|
G_CALLBACK(menu_solve_event), fe);
|
||||||
gtk_widget_show(menuitem);
|
gtk_widget_show(menuitem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_menu_separator(GTK_CONTAINER(menu));
|
||||||
|
menuitem = gtk_menu_item_new_with_label("Preferences...");
|
||||||
|
gtk_container_add(GTK_CONTAINER(menu), menuitem);
|
||||||
|
g_object_set_data(G_OBJECT(menuitem), "user-data",
|
||||||
|
GINT_TO_POINTER(CFG_PREFS));
|
||||||
|
g_signal_connect(G_OBJECT(menuitem), "activate",
|
||||||
|
G_CALLBACK(menu_config_event), fe);
|
||||||
|
gtk_widget_show(menuitem);
|
||||||
|
|
||||||
add_menu_separator(GTK_CONTAINER(menu));
|
add_menu_separator(GTK_CONTAINER(menu));
|
||||||
add_menu_ui_item(fe, GTK_CONTAINER(menu), "Exit", UI_QUIT, 'q', 0);
|
add_menu_ui_item(fe, GTK_CONTAINER(menu), "Exit", UI_QUIT, 'q', 0);
|
||||||
|
|
||||||
|
31
misc.c
31
misc.c
@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
#ifdef NO_TGMATH_H
|
#ifdef NO_TGMATH_H
|
||||||
# include <math.h>
|
# include <math.h>
|
||||||
#else
|
#else
|
||||||
@ -500,4 +501,34 @@ char *button2label(int button)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *make_prefs_path(const char *dir, const char *sep,
|
||||||
|
const game *game, const char *suffix)
|
||||||
|
{
|
||||||
|
size_t dirlen = strlen(dir);
|
||||||
|
size_t seplen = strlen(sep);
|
||||||
|
size_t gamelen = strlen(game->name);
|
||||||
|
size_t suffixlen = strlen(suffix);
|
||||||
|
char *path, *p;
|
||||||
|
const char *q;
|
||||||
|
|
||||||
|
path = snewn(dirlen + seplen + gamelen + suffixlen + 1, char);
|
||||||
|
p = path;
|
||||||
|
|
||||||
|
memcpy(p, dir, dirlen);
|
||||||
|
p += dirlen;
|
||||||
|
|
||||||
|
memcpy(p, sep, seplen);
|
||||||
|
p += seplen;
|
||||||
|
|
||||||
|
for (q = game->name; *q; q++)
|
||||||
|
if (*q != ' ')
|
||||||
|
*p++ = tolower((unsigned char)*q);
|
||||||
|
|
||||||
|
memcpy(p, suffix, suffixlen);
|
||||||
|
p += suffixlen;
|
||||||
|
|
||||||
|
*p = '\0';
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
/* vim: set shiftwidth=4 tabstop=8: */
|
/* vim: set shiftwidth=4 tabstop=8: */
|
||||||
|
112
puzzles.but
112
puzzles.but
@ -177,6 +177,22 @@ solving it yourself after seeing the answer, you can just press Undo.
|
|||||||
|
|
||||||
\dd Closes the application entirely.
|
\dd Closes the application entirely.
|
||||||
|
|
||||||
|
\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.
|
||||||
|
|
||||||
|
\lcont{
|
||||||
|
|
||||||
|
One option common to all games allows you to turn off the one-key
|
||||||
|
shortcuts like \q{N} for new game or \q{Q} for quit, so that there's
|
||||||
|
less chance of hitting them by accident. You can still access the same
|
||||||
|
shortcuts with the Ctrl key.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
\H{common-id} Specifying games with the \ii{game ID}
|
\H{common-id} Specifying games with the \ii{game ID}
|
||||||
|
|
||||||
There are two ways to save a game specification out of a puzzle and
|
There are two ways to save a game specification out of a puzzle and
|
||||||
@ -621,8 +637,9 @@ A left-click with the mouse in the row or column containing the empty
|
|||||||
space will move as many tiles as necessary to move the space to the
|
space will move as many tiles as necessary to move the space to the
|
||||||
mouse pointer.
|
mouse pointer.
|
||||||
|
|
||||||
The arrow keys will move a tile adjacent to the space in the direction
|
By default, the arrow keys will move a tile adjacent to the space in
|
||||||
indicated (moving the space in the \e{opposite} direction).
|
the direction indicated (moving the space in the \e{opposite}
|
||||||
|
direction).
|
||||||
|
|
||||||
Pressing \q{h} will make a suggested move. Pressing \q{h} enough
|
Pressing \q{h} will make a suggested move. Pressing \q{h} enough
|
||||||
times will solve the game, but it may scramble your progress while
|
times will solve the game, but it may scramble your progress while
|
||||||
@ -636,6 +653,18 @@ The only options available from the \q{Custom...} option on the \q{Type}
|
|||||||
menu are \e{Width} and \e{Height}, which are self-explanatory. (Once
|
menu are \e{Width} and \e{Height}, which are self-explanatory. (Once
|
||||||
you've changed these, it's not a \q{15-puzzle} any more, of course!)
|
you've changed these, it's not a \q{15-puzzle} any more, of course!)
|
||||||
|
|
||||||
|
\H{fifteen-prefs} \I{preferences, for Fifteen}Fifteen user preferences
|
||||||
|
|
||||||
|
On platforms that support user preferences, the \q{Preferences} option
|
||||||
|
on the \q{Game} menu will let you configure the sense of the arrow
|
||||||
|
keys. With the default setting, \q{Move the tile}, the arrow key you
|
||||||
|
press indicates the direction that you want a tile to move, so that
|
||||||
|
(for example) if you want to move the tile left of the gap rightwards
|
||||||
|
into the gap, you'd press Right. With the opposite setting, \q{Move
|
||||||
|
the gap}, the behaviour of the arrow keys is reversed, and you would
|
||||||
|
press Left to move the tile left of the gap into the gap, so that the
|
||||||
|
\e{gap} ends up one square left of where it was.
|
||||||
|
|
||||||
|
|
||||||
\C{sixteen} \i{Sixteen}
|
\C{sixteen} \i{Sixteen}
|
||||||
|
|
||||||
@ -1768,6 +1797,12 @@ don't yet know what that direction is, and this might enable you to
|
|||||||
deduce something about still other squares.) Even at Hard level,
|
deduce something about still other squares.) Even at Hard level,
|
||||||
guesswork and backtracking should never be necessary.
|
guesswork and backtracking should never be necessary.
|
||||||
|
|
||||||
|
\H{slant-prefs} \I{preferences, for Slant}Slant user preferences
|
||||||
|
|
||||||
|
On platforms that support user preferences, the \q{Preferences} option
|
||||||
|
on the \q{Game} menu will let you configure which way round the mouse
|
||||||
|
buttons work.
|
||||||
|
|
||||||
|
|
||||||
\C{lightup} \i{Light Up}
|
\C{lightup} \i{Light Up}
|
||||||
|
|
||||||
@ -1851,6 +1886,12 @@ noticeably.)
|
|||||||
backtracking or guessing, \q{Hard} means that some guesses will
|
backtracking or guessing, \q{Hard} means that some guesses will
|
||||||
probably be necessary.
|
probably be necessary.
|
||||||
|
|
||||||
|
\H{lightup-prefs} \I{preferences, for Light Up}Light Up user preferences
|
||||||
|
|
||||||
|
On platforms that support user preferences, the \q{Preferences} option
|
||||||
|
on the \q{Game} menu will let you configure whether \q{this is not a
|
||||||
|
light} marks are shown when the square is also lit.
|
||||||
|
|
||||||
|
|
||||||
\C{map} \i{Map}
|
\C{map} \i{Map}
|
||||||
|
|
||||||
@ -1944,6 +1985,12 @@ Unreasonable puzzles may require guessing and backtracking.
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
\H{map-prefs} \I{preferences, for Map}Map user preferences
|
||||||
|
|
||||||
|
On platforms that support user preferences, the \q{Preferences} option
|
||||||
|
on the \q{Game} menu will let you configure the style of the victory
|
||||||
|
flash.
|
||||||
|
|
||||||
|
|
||||||
\C{loopy} \i{Loopy}
|
\C{loopy} \i{Loopy}
|
||||||
|
|
||||||
@ -2016,6 +2063,34 @@ same; this makes them the least confusing to play.
|
|||||||
\#{FIXME: what distinguishes Easy, Medium, and Hard? In particular,
|
\#{FIXME: what distinguishes Easy, Medium, and Hard? In particular,
|
||||||
when are backtracking/guesswork required, if ever?}
|
when are backtracking/guesswork required, if ever?}
|
||||||
|
|
||||||
|
\H{loopy-prefs} \I{preferences, for Loopy}Loopy user preferences
|
||||||
|
|
||||||
|
On platforms that support user preferences, the \q{Preferences} option
|
||||||
|
on the \q{Game} menu will let you configure the following things:
|
||||||
|
|
||||||
|
\q{Draw excluded grid lines faintly}. This is on by default: when a
|
||||||
|
line of the grid has been explicitly excluded from the solution by
|
||||||
|
right-clicking it, the line is still drawn, just in a faint grey
|
||||||
|
colour. If you turn this option off, excluded lines are not drawn at
|
||||||
|
all.
|
||||||
|
|
||||||
|
\q{Auto-follow unique paths of edges}. This is off by default. When
|
||||||
|
it's on, clicking to change the status of a single grid line will
|
||||||
|
potentially propagate the change along multiple lines, if one or both
|
||||||
|
ends of the line you clicked connect to only one other line. (The idea
|
||||||
|
is that if two lines meet at a vertex and no other lines do at all,
|
||||||
|
then those lines are either both part of the loop or neither, so
|
||||||
|
there's no reason you should have to click separately to toggle each
|
||||||
|
one.)
|
||||||
|
|
||||||
|
In the mode \q{Based on grid only}, the effects of a click will only
|
||||||
|
propagate across vertices that have degree 2 in the underlying grid.
|
||||||
|
For example, in the square grid, the effect will \e{only} occur at the
|
||||||
|
four grid corners.
|
||||||
|
|
||||||
|
In the mode \q{Based on grid and game state}, the propagation will
|
||||||
|
also take account of edges you've already excluded from the solution,
|
||||||
|
so that it will do even more work for you.
|
||||||
|
|
||||||
\C{inertia} \i{Inertia}
|
\C{inertia} \i{Inertia}
|
||||||
|
|
||||||
@ -2720,6 +2795,14 @@ level, some backtracking will be required, but the solution should
|
|||||||
still be unique. The remaining levels require increasingly complex
|
still be unique. The remaining levels require increasingly complex
|
||||||
reasoning to avoid having to backtrack.
|
reasoning to avoid having to backtrack.
|
||||||
|
|
||||||
|
\H{towers-prefs} \I{preferences, for Towers}Towers user preferences
|
||||||
|
|
||||||
|
On platforms that support user preferences, the \q{Preferences} option
|
||||||
|
on the \q{Game} menu will let you configure the style of the game
|
||||||
|
display. If you don't like the three-dimensional mode, selecting
|
||||||
|
\q{2D} will switch to a simpler display style in which towers are
|
||||||
|
shown by just writing their height in the square.
|
||||||
|
|
||||||
|
|
||||||
\C{singles} \i{Singles}
|
\C{singles} \i{Singles}
|
||||||
|
|
||||||
@ -2926,6 +3009,13 @@ These parameters are available from the \q{Custom...} option on the
|
|||||||
(the start at the top left, and the end at the bottom right). If false the start
|
(the start at the top left, and the end at the bottom right). If false the start
|
||||||
and end squares are placed randomly (although always both shown).
|
and end squares are placed randomly (although always both shown).
|
||||||
|
|
||||||
|
\H{signpost-prefs} \I{preferences, for Signpost}Signpost user preferences
|
||||||
|
|
||||||
|
On platforms that support user preferences, the \q{Preferences} option
|
||||||
|
on the \q{Game} menu will let you configure the style of the victory
|
||||||
|
effect.
|
||||||
|
|
||||||
|
|
||||||
\C{range} \i{Range}
|
\C{range} \i{Range}
|
||||||
|
|
||||||
\cfg{winhelp-topic}{games.range}
|
\cfg{winhelp-topic}{games.range}
|
||||||
@ -2988,6 +3078,13 @@ These parameters are available from the \q{Custom...} option on the
|
|||||||
|
|
||||||
\dd Size of grid in squares.
|
\dd Size of grid in squares.
|
||||||
|
|
||||||
|
\H{range-prefs} \I{preferences, for Range}Range user preferences
|
||||||
|
|
||||||
|
On platforms that support user preferences, the \q{Preferences} option
|
||||||
|
on the \q{Game} menu will let you configure which way round the mouse
|
||||||
|
buttons work.
|
||||||
|
|
||||||
|
|
||||||
\C{pearl} \i{Pearl}
|
\C{pearl} \i{Pearl}
|
||||||
|
|
||||||
\cfg{winhelp-topic}{games.pearl}
|
\cfg{winhelp-topic}{games.pearl}
|
||||||
@ -3078,6 +3175,17 @@ possible to deduce it step by step.
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
\H{pearl-prefs} \I{preferences, for Pearl}Pearl user preferences
|
||||||
|
|
||||||
|
On platforms that support user preferences, the \q{Preferences} option
|
||||||
|
on the \q{Game} menu will let you configure the style of the game
|
||||||
|
display. \q{Traditional} is the default mode, in which the loop runs
|
||||||
|
between centres of grid squares, and each clue occupies a square.
|
||||||
|
\q{Loopy-style} is an alternative mode that looks more like Loopy
|
||||||
|
(\k{loopy}), in which the loop runs between grid \e{vertices}, and the
|
||||||
|
clues also occupy vertices.
|
||||||
|
|
||||||
|
|
||||||
\C{undead} \i{Undead}
|
\C{undead} \i{Undead}
|
||||||
|
|
||||||
\cfg{winhelp-topic}{games.undead}
|
\cfg{winhelp-topic}{games.undead}
|
||||||
|
@ -388,6 +388,8 @@ void free_cfg(config_item *cfg);
|
|||||||
void free_keys(key_label *keys, int nkeys);
|
void free_keys(key_label *keys, int nkeys);
|
||||||
void obfuscate_bitmap(unsigned char *bmp, int bits, bool decode);
|
void obfuscate_bitmap(unsigned char *bmp, int bits, bool decode);
|
||||||
char *fgetline(FILE *fp);
|
char *fgetline(FILE *fp);
|
||||||
|
char *make_prefs_path(const char *dir, const char *sep,
|
||||||
|
const game *game, const char *suffix);
|
||||||
|
|
||||||
/* allocates output each time. len is always in bytes of binary data.
|
/* allocates output each time. len is always in bytes of binary data.
|
||||||
* May assert (or just go wrong) if lengths are unchecked. */
|
* May assert (or just go wrong) if lengths are unchecked. */
|
||||||
|
Reference in New Issue
Block a user