summaryrefslogtreecommitdiff
path: root/apps/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins')
-rw-r--r--apps/plugins/puzzles/SOURCES16
-rw-r--r--apps/plugins/puzzles/SOURCES.games2
-rw-r--r--apps/plugins/puzzles/SOURCES.rockbox4
-rwxr-xr-xapps/plugins/puzzles/resync.sh45
-rw-r--r--apps/plugins/puzzles/src/devel.but6122
-rw-r--r--apps/plugins/puzzles/src/emcc.c1149
-rw-r--r--apps/plugins/puzzles/src/emcccopy.but128
-rw-r--r--apps/plugins/puzzles/src/fuzzpuzz.c250
-rw-r--r--apps/plugins/puzzles/src/gtk.c4399
-rw-r--r--apps/plugins/puzzles/src/list.c17
-rw-r--r--apps/plugins/puzzles/src/malloc.c64
-rw-r--r--apps/plugins/puzzles/src/nestedvm.c486
-rw-r--r--apps/plugins/puzzles/src/no-icon.c10
-rw-r--r--apps/plugins/puzzles/src/nullfe.c79
-rw-r--r--apps/plugins/puzzles/src/nullgame.c263
-rw-r--r--apps/plugins/puzzles/src/osx-help.but14
-rw-r--r--apps/plugins/puzzles/src/ps.c432
-rw-r--r--apps/plugins/puzzles/src/windows.c3458
18 files changed, 54 insertions, 16884 deletions
diff --git a/apps/plugins/puzzles/SOURCES b/apps/plugins/puzzles/SOURCES
index 58a16bc9b9..6fd787b3f1 100644
--- a/apps/plugins/puzzles/SOURCES
+++ b/apps/plugins/puzzles/SOURCES
@@ -1,33 +1,29 @@
1/* Auto-generated by resync.sh */
1rockbox.c 2rockbox.c
2rbwrappers.c 3rbwrappers.c
3rbmalloc.c 4rbmalloc.c
4lz4tiny.c 5lz4tiny.c
5 6
7/* puzzles core sources */
6src/combi.c 8src/combi.c
7src/divvy.c 9src/divvy.c
8src/drawing.c 10src/drawing.c
9src/dsf.c 11src/dsf.c
10src/findloop.c 12src/findloop.c
11src/grid.c 13src/grid.c
14src/hat.c
12src/latin.c 15src/latin.c
13src/laydomino.c 16src/laydomino.c
14src/loopgen.c 17src/loopgen.c
15/*src/malloc.c*/ /* we have our own */
16src/matching.c 18src/matching.c
17src/midend.c 19src/midend.c
18src/misc.c 20src/misc.c
19src/penrose.c
20src/penrose-legacy.c 21src/penrose-legacy.c
21src/printing.c 22src/penrose.c
22src/random.c 23src/random.c
23src/sort.c 24src/sort.c
25src/spectre.c
24src/tdq.c 26src/tdq.c
25src/tree234.c 27src/tree234.c
26src/version.c 28src/version.c
27 29src/printing.c
28src/hat.c
29src/spectre.c
30
31#ifdef COMBINED
32src/list.c
33#endif
diff --git a/apps/plugins/puzzles/SOURCES.games b/apps/plugins/puzzles/SOURCES.games
index 29af18e7b8..190412295b 100644
--- a/apps/plugins/puzzles/SOURCES.games
+++ b/apps/plugins/puzzles/SOURCES.games
@@ -1,5 +1,3 @@
1/* every game works! :) */
2
3src/blackbox.c 1src/blackbox.c
4src/bridges.c 2src/bridges.c
5src/cube.c 3src/cube.c
diff --git a/apps/plugins/puzzles/SOURCES.rockbox b/apps/plugins/puzzles/SOURCES.rockbox
new file mode 100644
index 0000000000..c5bbb9af70
--- /dev/null
+++ b/apps/plugins/puzzles/SOURCES.rockbox
@@ -0,0 +1,4 @@
1rockbox.c
2rbwrappers.c
3rbmalloc.c
4lz4tiny.c
diff --git a/apps/plugins/puzzles/resync.sh b/apps/plugins/puzzles/resync.sh
index 384fc79d1f..7c2df45c7e 100755
--- a/apps/plugins/puzzles/resync.sh
+++ b/apps/plugins/puzzles/resync.sh
@@ -25,12 +25,55 @@ read ans
25if [ "YES" == $ans ] 25if [ "YES" == $ans ]
26then 26then
27 pushd "$(dirname "$0")" > /dev/null 27 pushd "$(dirname "$0")" > /dev/null
28 ROOT="$PWD"
28 29
29 echo "[1/5] Removing current src/ directory" 30 echo "[1/5] Removing current src/ directory"
30 rm -rf src 31 rm -rf src
31 echo "[2/5] Copying new sources" 32 echo "[2/5] Copying new sources"
32 mkdir src 33 mkdir src
33 cp -r "$1"/{*.c,*.h,*.but,LICENCE,README,CMakeLists.txt} src 34 cp -r "$1"/{*.h,puzzles.but,LICENCE,README,CMakeLists.txt} src
35
36 # Parse out definitions of core, core_obj, and common from
37 # CMakeLists. Extract the .c filenames, except malloc.c, and store
38 # in SOURCES.core.
39 cat src/CMakeLists.txt | awk '/add_library\(/{p=1} p{printf $0" "} /\)/{if(p) print; p=0}' | grep -E "core|common" | grep -Po "[a-z0-9\-]*?\.c" | sort -n | grep -vE 'malloc\.c|ps\.c' | awk '{print "src/"$0}' | uniq > SOURCES.core
40 echo "src/printing.c" >> SOURCES.core
41
42 # Parse out puzzle definitions to build SOURCES.games, but
43 # preserve the ability to disable puzzles based on memory size.
44 cat src/CMakeLists.txt | awk '/puzzle\(/{p=1} p{print} /\)/{p=0}' | grep -Eo "\(.*$" | tr -dc "a-z\n" | grep -v nullgame | awk '$0!~/loopy|pearl|solo/' | awk '{print "src/"$0".c"}' > SOURCES.games
45
46 SRC="$(cat SOURCES.games SOURCES.core | sed 's/src\///' | tr '\n' ' ' | head -c-1) loopy.c pearl.c solo.c"
47 echo "Detected sources:" $SRC
48 pushd "$1" > /dev/null
49 cp $SRC "$ROOT"/src
50 popd > /dev/null
51
52 cat <<EOF >> SOURCES.games
53
54/* Disabled for now. Fix puzzles.make and CATEGORIES to accomodate these. */
55/* The help system would also need to be patched to compile these. */
56/*src/unfinished/group.c*/
57/*src/unfinished/separate.c*/
58/*src/unfinished/slide.c*/
59/*src/unfinished/sokoban.c*/
60
61/* no c200v2 */
62#if PLUGIN_BUFFER_SIZE > 0x14000
63src/loopy.c
64src/pearl.c
65src/solo.c
66#endif
67EOF
68
69 cat <<EOF > SOURCES
70/* Auto-generated by resync.sh */
71EOF
72 cat SOURCES.rockbox | cpp | grep -vE "^#" >> SOURCES
73 echo -e "\n/* puzzles core sources */" >> SOURCES
74 cat SOURCES.core >> SOURCES
75 rm SOURCES.core
76
34 echo "[3/5] Regenerating help" 77 echo "[3/5] Regenerating help"
35 rm -rf help 78 rm -rf help
36 ./genhelp.sh 79 ./genhelp.sh
diff --git a/apps/plugins/puzzles/src/devel.but b/apps/plugins/puzzles/src/devel.but
deleted file mode 100644
index c201a3e6c9..0000000000
--- a/apps/plugins/puzzles/src/devel.but
+++ /dev/null
@@ -1,6122 +0,0 @@
1\cfg{text-indent}{0}
2\cfg{text-width}{72}
3\cfg{text-title-align}{left}
4\cfg{text-chapter-align}{left}
5\cfg{text-chapter-numeric}{true}
6\cfg{text-chapter-suffix}{. }
7\cfg{text-chapter-underline}{-}
8\cfg{text-section-align}{0}{left}
9\cfg{text-section-numeric}{0}{true}
10\cfg{text-section-suffix}{0}{. }
11\cfg{text-section-underline}{0}{-}
12\cfg{text-section-align}{1}{left}
13\cfg{text-section-numeric}{1}{true}
14\cfg{text-section-suffix}{1}{. }
15\cfg{text-section-underline}{1}{-}
16\cfg{text-versionid}{0}
17
18\cfg{html-contents-filename}{index.html}
19\cfg{html-template-filename}{%k.html}
20\cfg{html-index-filename}{docindex.html}
21\cfg{html-leaf-level}{1}
22\cfg{html-contents-depth-0}{1}
23\cfg{html-contents-depth-1}{3}
24\cfg{html-leaf-contains-contents}{true}
25
26\define{dash} \u2013{-}
27
28\title Developer documentation for Simon Tatham's puzzle collection
29
30This is a guide to the internal structure of Simon Tatham's Portable
31Puzzle Collection (henceforth referred to simply as \q{Puzzles}),
32for use by anyone attempting to implement a new puzzle or port to a
33new platform.
34
35This guide is believed correct as of \cw{git} commit
36\cw{a2212e82aa2f4b9a4ee22783d6fed2761c213432}. Hopefully it will be
37updated along with the code in future, but if not, I've at least left
38this version number in here so you can figure out what's changed by
39tracking commit comments from there onwards.
40
41\C{intro} Introduction
42
43The Puzzles code base is divided into four parts: a set of
44interchangeable front ends, a set of interchangeable back ends, a
45universal \q{middle end} which acts as a buffer between the two, and
46a bunch of miscellaneous utility functions. In the following
47sections I give some general discussion of each of these parts.
48
49\H{intro-frontend} Front end
50
51The front end is the non-portable part of the code: it's the bit
52that you replace completely when you port to a different platform.
53So it's responsible for all system calls, all GUI interaction, and
54anything else platform-specific.
55
56The front end contains \cw{main()} or the local platform's
57equivalent. Top-level control over the application's execution flow
58belongs to the front end (it isn't, for example, a set of functions
59called by a universal \cw{main()} somewhere else).
60
61The front end has complete freedom to design the GUI for any given
62port of Puzzles. There is no centralised mechanism for maintaining the
63menu layout, for example. This has a cost in consistency (when I
64\e{do} want the same menu layout on more than one platform, I have to
65edit N pieces of code in parallel every time I make a change), but the
66advantage is that local GUI conventions can be conformed to and local
67constraints adapted to. For example, MacOS has strict human interface
68guidelines which specify a different menu layout from the one I've
69used on Windows and GTK; there's nothing stopping the MacOS front end
70from providing a menu layout consistent with those guidelines.
71
72Although the front end is mostly caller rather than the callee in
73its interactions with other parts of the code, it is required to
74implement a small API for other modules to call, mostly of drawing
75functions for games to use when drawing their graphics. The drawing
76API is documented in \k{drawing}; the other miscellaneous front end
77API functions are documented in \k{frontend-api}.
78
79\H{intro-backend} Back end
80
81A \q{back end}, in this collection, is synonymous with a \q{puzzle}.
82Each back end implements a different game.
83
84At the top level, a back end is simply a data structure, containing
85a few constants (flag words, preferred pixel size) and a large
86number of function pointers. Back ends are almost invariably callee
87rather than caller, which means there's a limitation on what a back
88end can do on its own initiative.
89
90The persistent state in a back end is divided into a number of data
91structures, which are used for different purposes and therefore
92likely to be switched around, changed without notice, and otherwise
93updated by the rest of the code. It is important when designing a
94back end to put the right pieces of data into the right structures,
95or standard midend-provided features (such as Undo) may fail to
96work.
97
98The functions and variables provided in the back end data structure
99are documented in \k{backend}.
100
101\H{intro-midend} Middle end
102
103Puzzles has a single and universal \q{middle end}. This code is
104common to all platforms and all games; it sits in between the front
105end and the back end and provides standard functionality everywhere.
106
107People adding new back ends or new front ends should generally not
108need to edit the middle end. On rare occasions there might be a
109change that can be made to the middle end to permit a new game to do
110something not currently anticipated by the middle end's present
111design; however, this is terribly easy to get wrong and should
112probably not be undertaken without consulting the primary maintainer
113(me). Patch submissions containing unannounced mid-end changes will
114be treated on their merits like any other patch; this is just a
115friendly warning that mid-end changes will need quite a lot of
116merits to make them acceptable.
117
118Functionality provided by the mid-end includes:
119
120\b Maintaining a list of game state structures and moving back and
121forth along that list to provide Undo and Redo.
122
123\b Handling timers (for move animations, flashes on completion, and
124in some cases actually timing the game).
125
126\b Handling the container format of game IDs: receiving them,
127picking them apart into parameters, description and/or random seed,
128and so on. The game back end need only handle the individual parts
129of a game ID (encoded parameters and encoded game description);
130everything else is handled centrally by the mid-end.
131
132\b Handling standard keystrokes and menu commands, such as \q{New
133Game}, \q{Restart Game} and \q{Quit}.
134
135\b Pre-processing mouse events so that the game back ends can rely
136on them arriving in a sensible order (no missing button-release
137events, no sudden changes of which button is currently pressed,
138etc).
139
140\b Handling the dialog boxes which ask the user for a game ID.
141
142\b Handling serialisation of entire games (for loading and saving a
143half-finished game to a disk file; for handling application shutdown
144and restart on platforms such as PalmOS where state is expected to be
145saved; for storing the previous game in order to undo and redo across
146a New Game event).
147
148Thus, there's a lot of work done once by the mid-end so that
149individual back ends don't have to worry about it. All the back end
150has to do is cooperate in ensuring the mid-end can do its work
151properly.
152
153The API of functions provided by the mid-end to be called by the
154front end is documented in \k{midend}.
155
156\H{intro-utils} Miscellaneous utilities
157
158In addition to these three major structural components, the Puzzles
159code also contains a variety of utility modules usable by all of the
160above components. There is a set of functions to provide
161platform-independent random number generation; functions to make
162memory allocation easier; functions which implement a balanced tree
163structure to be used as necessary in complex algorithms; and a few
164other miscellaneous functions. All of these are documented in
165\k{utils}.
166
167\H{intro-structure} Structure of this guide
168
169There are a number of function call interfaces within Puzzles, and
170this guide will discuss each one in a chapter of its own. After
171that, \k{writing} discusses how to design new games, with some
172general design thoughts and tips.
173
174\C{backend} Interface to the back end
175
176This chapter gives a detailed discussion of the interface that each
177back end must implement.
178
179At the top level, each back end source file exports a single global
180symbol, which is a \c{const struct game} containing a large number
181of function pointers and a small amount of constant data. This
182structure is called by different names depending on what kind of
183platform the puzzle set is being compiled on:
184
185\b On platforms such as Windows and GTK, which build a separate
186binary for each puzzle, the game structure in every back end has the
187same name, \cq{thegame}; the front end refers directly to this name,
188so that compiling the same front end module against a different back
189end module builds a different puzzle.
190
191\b On platforms such as MacOS X and PalmOS, which build all the
192puzzles into a single monolithic binary, the game structure in each
193back end must have a different name, and there's a helper module
194\c{list.c} which constructs a complete list of those game structures
195from a header file generated by CMake.
196
197On the latter type of platform, source files may assume that the
198preprocessor symbol \c{COMBINED} has been defined. Thus, the usual
199code to declare the game structure looks something like this:
200
201\c #ifdef COMBINED
202\c #define thegame net /* or whatever this game is called */
203\e iii iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
204\c #endif
205\c
206\c const struct game thegame = {
207\c /* lots of structure initialisation in here */
208\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
209\c };
210
211Game back ends must also internally define a number of data
212structures, for storing their various persistent state. This chapter
213will first discuss the nature and use of those structures, and then
214go on to give details of every element of the game structure.
215
216\H{backend-structs} Data structures
217
218Each game is required to define four separate data structures. This
219section discusses each one and suggests what sorts of things need to
220be put in it.
221
222\S{backend-game-params} \c{game_params}
223
224The \c{game_params} structure contains anything which affects the
225automatic generation of new puzzles. So if puzzle generation is
226parametrised in any way, those parameters need to be stored in
227\c{game_params}.
228
229Most puzzles currently in this collection are played on a grid of
230squares, meaning that the most obvious parameter is the grid size.
231Many puzzles have additional parameters; for example, Mines allows
232you to control the number of mines in the grid independently of its
233size, Net can be wrapping or non-wrapping, Solo has difficulty
234levels and symmetry settings, and so on.
235
236A simple rule for deciding whether a data item needs to go in
237\c{game_params} is: would the user expect to be able to control this
238data item from either the preset-game-types menu or the \q{Custom}
239game type configuration? If so, it's part of \c{game_params}.
240
241\c{game_params} structures are permitted to contain pointers to
242subsidiary data if they need to. The back end is required to provide
243functions to create and destroy \c{game_params}, and those functions
244can allocate and free additional memory if necessary. (It has not
245yet been necessary to do this in any puzzle so far, but the
246capability is there just in case.)
247
248\c{game_params} is also the only structure which the game's
249\cw{compute_size()} function may refer to; this means that any aspect
250of the game which affects the size of the window it needs to be drawn
251in (other than the magnification level) must be stored in
252\c{game_params}. In particular, this imposes the fundamental
253limitation that random game generation may not have a random effect on
254the window size: game generation algorithms are constrained to work by
255starting from the grid size rather than generating it as an emergent
256phenomenon. (Although this is a restriction in theory, it has not yet
257seemed to be a problem.)
258
259\S{backend-game-state} \c{game_state}
260
261While the user is actually playing a puzzle, the \c{game_state}
262structure stores all the data corresponding to the current state of
263play.
264
265The mid-end keeps \c{game_state}s in a list, and adds to the list
266every time the player makes a move; the Undo and Redo functions step
267back and forth through that list.
268
269Therefore, a good means of deciding whether a data item needs to go in
270\c{game_state} is: would a player expect that data item to be restored
271on undo? If so, put it in \c{game_state}, and this will automatically
272happen without you having to lift a finger. If not, then you might
273have found a data item that needs to go in \c{game_ui} instead.
274
275Two quite different examples of this:
276
277\b if the game provides an interface for making moves by moving a
278cursor around the grid with the keyboard and pressing some other key
279when you get to a square you want to change, then the location of that
280cursor belongs in \c{game_ui}, because the player will want to undo
281one \e{square change} at a time, not one \e{cursor movement} at a
282time.
283
284\b Mines tracks the number of times you opened a mine square and died.
285Every time you do that, you can only continue the game by pressing
286Undo. So the deaths counter belongs in \c{game_ui}, because otherwise,
287it would revert to 0 every time you undid your mistaken move.
288
289During play, \c{game_state}s are often passed around without an
290accompanying \c{game_params} structure. Therefore, any information
291in \c{game_params} which is important during play (such as the grid
292size) must be duplicated within the \c{game_state}. One simple
293method of doing this is to have the \c{game_state} structure
294\e{contain} a \c{game_params} structure as one of its members,
295although this isn't obligatory if you prefer to do it another way.
296
297\S{backend-game-drawstate} \c{game_drawstate}
298
299\c{game_drawstate} carries persistent state relating to the current
300graphical contents of the puzzle window. The same \c{game_drawstate}
301is passed to every call to the game redraw function, so that it can
302remember what it has already drawn and what needs redrawing.
303
304A typical use for a \c{game_drawstate} is to have an array mirroring
305the array of grid squares in the \c{game_state}, but describing what
306was drawn in the window on the most recent redraw. This is used to
307identify the squares that need redrawing next time, by deciding what
308the new value in that array should be, and comparing it to what was
309drawn last time. See \k{writing-howto-redraw} for more on this
310subject.
311
312\c{game_drawstate} is occasionally completely torn down and
313reconstructed by the mid-end, if the user somehow forces a full
314redraw. Therefore, no data should be stored in \c{game_drawstate}
315which is \e{not} related to the state of the puzzle window, because
316it might be unexpectedly destroyed.
317
318The back end provides functions to create and destroy
319\c{game_drawstate}, which means it can contain pointers to
320subsidiary allocated data if it needs to. A common thing to want to
321allocate in a \c{game_drawstate} is a \c{blitter}; see
322\k{drawing-blitter} for more on this subject.
323
324\S{backend-game-ui} \c{game_ui}
325
326\c{game_ui} contains whatever doesn't fit into the above three
327structures!
328
329A new \c{game_ui} is created when the user begins playing a new
330instance of a puzzle (i.e. during \q{New Game} or after entering a
331game ID etc). It persists until the user finishes playing that game
332and begins another one (or closes the window); in particular,
333\q{Restart Game} does \e{not} destroy the \c{game_ui}.
334
335There are various things that you might store in \c{game_ui}, which
336are conceptually different from each other, but I haven't yet found a
337need to split them out into smaller sub-structures for different
338purposes:
339
340\dt Transient UI state:
341
342\dd Storing a piece of UI state in \c{game_state} means that you can
343only update it by appending a move to the undo chain. Some UI state
344shouldn't really be treated this way. For example, if your puzzle has
345a keyboard-controlled cursor, you probably don't want every cursor
346movement to be an undoable action, because the history of where the
347cursor went just isn't interesting. More likely the cursor should just
348move freely, and the only undoable actions are the ones where you
349modify the element under the cursor. So you'd store the cursor
350position in \c{game_ui} rather than \c{game_state}. See
351\k{writing-keyboard-cursor} for more details.
352
353\lcont{ Another example of this is the state of an ongoing mouse drag.
354If there's an undoable action involved, it will probably occur when
355the drag is released. In between, you still need to store state that
356the redraw function will use to update the display \dash and that can
357live in \c{game_ui}. See \k{writing-howto-dragging} for more details
358of this. }
359
360\dt Persistent UI state:
361
362\dd An example of this is the counter of deaths in Mines or Inertia.
363This shouldn't be reverted by pressing Undo, for the opposite reason
364to the cursor position: the cursor position is too boring to store the
365history of, but the deaths counter is too \e{important}!
366
367\dt Information about recent changes to the game state:
368
369\dd This is used in Mines, for example, to indicate whether a
370requested \q{flash} should be a white flash for victory or a red flash
371for defeat; see \k{writing-flash-types}.
372
373\dt User preferences:
374
375\dd Any user preference about display or UI handled by
376\cw{get_prefs()} and \cw{set_prefs()} will need to live in
377\c{game_ui}, because that's the structure that those functions access.
378
379\H{backend-simple} Simple data in the back end
380
381In this section I begin to discuss each individual element in the
382back end structure. To begin with, here are some simple
383self-contained data elements.
384
385\S{backend-name} \c{name}
386
387\c const char *name;
388
389This is a simple ASCII string giving the name of the puzzle. This
390name will be used in window titles, in game selection menus on
391monolithic platforms, and anywhere else that the front end needs to
392know the name of a game.
393
394\S{backend-winhelp} \c{winhelp_topic} and \c{htmlhelp_topic}
395
396\c const char *winhelp_topic, *htmlhelp_topic;
397
398These members are used on Windows only, to provide online help.
399Although the Windows front end provides a separate binary for each
400puzzle, it has a single monolithic help file; so when a user selects
401\q{Help} from the menu, the program needs to open the help file and
402jump to the chapter describing that particular puzzle.
403
404This code base still supports the legacy \cw{.HLP} Windows Help format
405as well as the less old \cw{.CHM} HTML Help format. The two use
406different methods of identifying topics, so you have to specify both.
407
408Each chapter about a puzzle in \c{puzzles.but} is labelled with a
409\e{help topic} name for Windows Help, which typically appears just
410after the \cw{\\C} chapter title paragraph, similar to this:
411
412\c \C{net} \i{Net}
413\c
414\c \cfg{winhelp-topic}{games.net}
415
416But HTML Help is able to use the Halibut identifier for the chapter
417itself, i.e. the keyword that appears in braces immediatey after the
418\cw{\\C}.
419
420So the corresponding game back end encodes the \c{winhelp-topic}
421string (here \cq{games.net}) in the \c{winhelp_topic} element of the
422game structure, and puts the chapter identifier (here \cq{net}) in the
423\c{htmlhelp_topic} element. For example:
424
425\c const struct game thegame = {
426\c "Net", "games.net", "net",
427\c // ...
428\c };
429
430\H{backend-params} Handling game parameter sets
431
432In this section I present the various functions which handle the
433\c{game_params} structure.
434
435\S{backend-default-params} \cw{default_params()}
436
437\c game_params *(*default_params)(void);
438
439This function allocates a new \c{game_params} structure, fills it
440with the default values, and returns a pointer to it.
441
442\S{backend-fetch-preset} \cw{fetch_preset()}
443
444\c bool (*fetch_preset)(int i, char **name, game_params **params);
445
446This function is one of the two APIs a back end can provide to
447populate the \q{Type} menu, which provides a list of conveniently
448accessible preset parameters for most games.
449
450The function is called with \c{i} equal to the index of the preset
451required (numbering from zero). It returns \cw{false} if that preset
452does not exist (if \c{i} is less than zero or greater than the
453largest preset index). Otherwise, it sets \c{*params} to point at a
454newly allocated \c{game_params} structure containing the preset
455information, sets \c{*name} to point at a newly allocated C string
456containing the preset title (to go on the \q{Type} menu), and
457returns \cw{true}.
458
459If the game does not wish to support any presets at all, this
460function is permitted to return \cw{false} always.
461
462If the game wants to return presets in the form of a hierarchical menu
463instead of a flat list (and, indeed, even if it doesn't), then it may
464set this function pointer to \cw{NULL}, and instead fill in the
465alternative function pointer \cw{preset_menu}
466(\k{backend-preset-menu}).
467
468\S{backend-preset-menu} \cw{preset_menu()}
469
470\c struct preset_menu *(*preset_menu)(void);
471
472This function is the more flexible of the two APIs by which a back end
473can define a collection of preset game parameters.
474
475This function simply returns a complete menu hierarchy, in the form of
476a \c{struct preset_menu} (see \k{midend-get-presets}) and further
477submenus (if it wishes) dangling off it. There are utility functions
478described in \k{utils-presets} to make it easy for the back end to
479construct this menu.
480
481If the game has no need to return a hierarchy of menus, it may instead
482opt to implement the \cw{fetch_preset()} function (see
483\k{backend-fetch-preset}).
484
485The game need not fill in the \c{id} fields in the preset menu
486structures. The mid-end will do that after it receives the structure
487from the game, and before passing it on to the front end.
488
489\S{backend-encode-params} \cw{encode_params()}
490
491\c char *(*encode_params)(const game_params *params, bool full);
492
493The job of this function is to take a \c{game_params}, and encode it
494in a printable ASCII string form for use in game IDs. The return value must
495be a newly allocated C string, and \e{must} not contain a colon or a hash
496(since those characters are used to mark the end of the parameter
497section in a game ID).
498
499Ideally, it should also not contain any other potentially
500controversial punctuation; bear in mind when designing a string
501parameter format that it will probably be used on both Windows and
502Unix command lines under a variety of exciting shell quoting and
503metacharacter rules. Sticking entirely to alphanumerics is the
504safest thing; if you really need punctuation, you can probably get
505away with commas, periods or underscores without causing anybody any
506major inconvenience. If you venture far beyond that, you're likely
507to irritate \e{somebody}.
508
509(At the time of writing this, most existing games have purely
510alphanumeric string parameter formats. Usually these involve a
511letter denoting a parameter, followed optionally by a number giving
512the value of that parameter, with a few mandatory parts at the
513beginning such as numeric width and height separated by \cq{x}.)
514
515If the \c{full} parameter is \cw{true}, this function should encode
516absolutely everything in the \c{game_params}, such that a subsequent
517call to \cw{decode_params()} (\k{backend-decode-params}) will yield
518an identical structure. If \c{full} is \cw{false}, however, you
519should leave out anything which is not necessary to describe a
520\e{specific puzzle instance}, i.e. anything which only takes effect
521when a new puzzle is \e{generated}.
522
523For example, the Solo \c{game_params} includes a difficulty rating
524used when constructing new puzzles; but a Solo game ID need not
525explicitly include the difficulty, since to describe a puzzle once
526generated it's sufficient to give the grid dimensions and the location
527and contents of the clue squares. (Indeed, one might very easily type
528in a puzzle out of a newspaper without \e{knowing} what its difficulty
529level is in Solo's terminology.) Therefore, Solo's
530\cw{encode_params()} only encodes the difficulty level if \c{full} is
531set.
532
533\S{backend-decode-params} \cw{decode_params()}
534
535\c void (*decode_params)(game_params *params, char const *string);
536
537This function is the inverse of \cw{encode_params()}
538(\k{backend-encode-params}). It parses the supplied string and fills
539in the supplied \c{game_params} structure. Note that the structure
540will \e{already} have been allocated: this function is not expected
541to create a \e{new} \c{game_params}, but to modify an existing one.
542
543This function can receive a string which only encodes a subset of
544the parameters. The most obvious way in which this can happen is if
545the string was constructed by \cw{encode_params()} with its \c{full}
546parameter set to \cw{false}; however, it could also happen if the
547user typed in a parameter set manually and missed something out. Be
548prepared to deal with a wide range of possibilities.
549
550When dealing with a parameter which is not specified in the input
551string, what to do requires a judgment call on the part of the
552programmer. Sometimes it makes sense to adjust other parameters to
553bring them into line with the new ones. In Mines, for example, you
554would probably not want to keep the same mine count if the user
555dropped the grid size and didn't specify one, since you might easily
556end up with more mines than would actually fit in the grid! On the
557other hand, sometimes it makes sense to leave the parameter alone: a
558Solo player might reasonably expect to be able to configure size and
559difficulty independently of one another.
560
561This function currently has no direct means of returning an error if
562the string cannot be parsed at all. However, the returned
563\c{game_params} is almost always subsequently passed to
564\cw{validate_params()} (\k{backend-validate-params}), so if you
565really want to signal parse errors, you could always have a \c{char
566*} in your parameters structure which stored an error message, and
567have \cw{validate_params()} return it if it is non-\cw{NULL}.
568
569\S{backend-free-params} \cw{free_params()}
570
571\c void (*free_params)(game_params *params);
572
573This function frees a \c{game_params} structure, and any subsidiary
574allocations contained within it.
575
576\S{backend-dup-params} \cw{dup_params()}
577
578\c game_params *(*dup_params)(const game_params *params);
579
580This function allocates a new \c{game_params} structure and
581initialises it with an exact copy of the information in the one
582provided as input. It returns a pointer to the new duplicate.
583
584\S{backend-can-configure} \c{can_configure}
585
586\c bool can_configure;
587
588This data element is set to \cw{true} if the back end supports custom
589parameter configuration via a dialog box. If it is \cw{true}, then the
590functions \cw{configure()} and \cw{custom_params()} are expected to
591work. See \k{backend-configure} and \k{backend-custom-params} for more
592details.
593
594\S{backend-configure} \cw{configure()}
595
596\c config_item *(*configure)(const game_params *params);
597
598This function is called when the user requests a dialog box for
599custom parameter configuration. It returns a newly allocated array
600of \cw{config_item} structures, describing the GUI elements required
601in the dialog box. The array should have one more element than the
602number of controls, since it is terminated with a \cw{C_END} marker
603(see below). Each array element describes the control together with
604its initial value; the front end will modify the value fields and
605return the updated array to \cw{custom_params()} (see
606\k{backend-custom-params}).
607
608The \cw{config_item} structure contains the following elements used by
609this function:
610
611\c const char *name;
612\c int type;
613\c union { /* type-specific fields */ } u;
614\e iiiiiiiiiiiiiiiiiiiiiiiiii
615
616\c{name} is an ASCII string giving the textual label for a GUI
617control. It is \e{not} expected to be dynamically allocated.
618
619\c{type} contains one of a small number of \c{enum} values defining
620what type of control is being described. The usable member of the
621union field \c{u} depends on \c{type}. The valid type values are:
622
623\dt \c{C_STRING}
624
625\dd Describes a text input box. (This is also used for numeric
626input. The back end does not bother informing the front end that the
627box is numeric rather than textual; some front ends do have the
628capacity to take this into account, but I decided it wasn't worth
629the extra complexity in the interface.)
630
631\lcont{
632
633For controls of this type, \c{u.string} contains a single field
634
635\c char *sval;
636
637which stores a dynamically allocated string representing the contents
638of the input box.
639
640}
641
642\dt \c{C_BOOLEAN}
643
644\dd Describes a simple checkbox.
645
646\lcont{
647
648For controls of this type, \c{u.boolean} contains a single field
649
650\c bool bval;
651
652}
653
654\dt \c{C_CHOICES}
655
656\dd Describes a drop-down list presenting one of a small number of
657fixed choices.
658
659\lcont{
660
661For controls of this type, \c{u.choices} contains two fields:
662
663\c const char *choicenames;
664\c int selected;
665
666\c{choicenames} contains a list of strings describing the choices. The
667very first character of \c{sval} is used as a delimiter when
668processing the rest (so that the strings \cq{:zero:one:two},
669\cq{!zero!one!two} and \cq{xzeroxonextwo} all define a three-element
670list containing \cq{zero}, \cq{one} and \cq{two}).
671
672\c{selected} contains the index of the currently selected element,
673numbering from zero (so that in the above example, 0 would mean
674\cq{zero} and 2 would mean \cq{two}).
675
676Note that \c{u.choices.choicenames} is \e{not} dynamically allocated,
677unlike \c{u.string.sval}.
678
679}
680
681\dt \c{C_END}
682
683\dd Marks the end of the array of \c{config_item}s. There is no
684associated member of the union field \c{u} for this type.
685
686The array returned from this function is expected to have filled in
687the initial values of all the controls according to the input
688\c{game_params} structure.
689
690If the game's \c{can_configure} flag is set to \cw{false}, this
691function is never called and can be \cw{NULL}.
692
693\S{backend-custom-params} \cw{custom_params()}
694
695\c game_params *(*custom_params)(const config_item *cfg);
696
697This function is the counterpart to \cw{configure()}
698(\k{backend-configure}). It receives as input an array of
699\c{config_item}s which was originally created by \cw{configure()},
700but in which the control values have since been changed in
701accordance with user input. Its function is to read the new values
702out of the controls and return a newly allocated \c{game_params}
703structure representing the user's chosen parameter set.
704
705(The front end will have modified the controls' \e{values}, but
706there will still always be the same set of controls, in the same
707order, as provided by \cw{configure()}. It is not necessary to check
708the \c{name} and \c{type} fields, although you could use
709\cw{assert()} if you were feeling energetic.)
710
711This function is not expected to (and indeed \e{must not}) free the
712input \c{config_item} array. (If the parameters fail to validate,
713the dialog box will stay open.)
714
715If the game's \c{can_configure} flag is set to \cw{false}, this
716function is never called and can be \cw{NULL}.
717
718\S{backend-get-prefs} \cw{get_prefs()}
719
720\c config_item *(*get_prefs)(game_ui *ui);
721
722This function works very like \cw{configure()}, but instead of
723receiving a \c{game_params} and returning GUI elements describing the
724data in it, this function receives a \c{game_ui} and returns GUI
725elements describing any user preferences stored in that.
726
727This function should only deal with fields of \c{game_ui} that are
728user-settable preferences. In-game state like cursor position and
729mouse drags, or per-game state like death counters, are nothing to do
730with this function.
731
732If there are no user preferences, you can set both this function
733pointer and \c{set_prefs} to \cw{NULL}.
734
735If you implement these functions, you must also ensure that your
736game's \cw{new_ui()} function can be called with a null \c{game_state}
737pointer. (See \k{backend-new-ui}.)
738
739In every \c{config_item} returned from this function, you must set an
740additional field beyond the ones described in \k{backend-configure}:
741
742\c const char *kw;
743
744This should be an identifying keyword for the user preference in
745question, suitable for use in configuration files. That means it
746should remain stable, even if the user-facing wording in the \c{name}
747field is reworded for clarity. If it doesn't stay stable, old
748configuration files will not be read correctly.
749
750For \c{config_item}s of type \cw{C_CHOICES}, you must also set an
751extra field in \c{u.choices}:
752
753\c const char *choicekws;
754
755This has the same structure as the \c{choicenames} field (a list of
756values delimited by the first character in the whole string), and it
757provides an identifying keyword for each individual choice in the
758list, in the same order as the entries of \c{choicenames}.
759
760\S{backend-set-prefs} \cw{set_prefs()}
761
762\c void (*set_prefs)(game_ui *ui, const config_item *cfg);
763
764This function is the counterpart to \cw{set_prefs()}, as
765\cw{custom_params()} is to \cw{configure()}. It receives an array of
766\c{config_item}s which was originally created by \cw{get_prefs()},
767with the controls' values updated from user input, and it should
768transcribe the new settings into the provided \c{game_ui}.
769
770If there are no user preferences, you can set both this function
771pointer and \c{get_prefs} to \cw{NULL}.
772
773\S{backend-validate-params} \cw{validate_params()}
774
775\c const char *(*validate_params)(const game_params *params,
776\c bool full);
777
778This function takes a \c{game_params} structure as input, and checks
779that the parameters described in it fall within sensible limits. (At
780the very least, grid dimensions should almost certainly be strictly
781positive, for example.)
782
783Return value is \cw{NULL} if no problems were found, or
784alternatively a (non-dynamically-allocated) ASCII string describing
785the error in human-readable form.
786
787If the \c{full} parameter is set, full validation should be
788performed: any set of parameters which would not permit generation
789of a sensible puzzle should be faulted. If \c{full} is \e{not} set,
790the implication is that these parameters are not going to be used
791for \e{generating} a puzzle; so parameters which can't even sensibly
792\e{describe} a valid puzzle should still be faulted, but parameters
793which only affect puzzle generation should not be.
794
795(The \c{full} option makes a difference when parameter combinations
796are non-orthogonal. For example, Net has a boolean option
797controlling whether it enforces a unique solution; it turns out that
798it's impossible to generate a uniquely soluble puzzle with wrapping
799walls and width 2, so \cw{validate_params()} will complain if you
800ask for one. However, if the user had just been playing a unique
801wrapping puzzle of a more sensible width, and then pastes in a game
802ID acquired from somebody else which happens to describe a
803\e{non}-unique wrapping width-2 puzzle, then \cw{validate_params()}
804will be passed a \c{game_params} containing the width and wrapping
805settings from the new game ID and the uniqueness setting from the
806old one. This would be faulted, if it weren't for the fact that
807\c{full} is not set during this call, so Net ignores the
808inconsistency. The resulting \c{game_params} is never subsequently
809used to generate a puzzle; this is a promise made by the mid-end
810when it asks for a non-full validation.)
811
812\H{backend-descs} Handling game descriptions
813
814In this section I present the functions that deal with a textual
815description of a puzzle, i.e. the part that comes after the colon in
816a descriptive-format game ID.
817
818\S{backend-new-desc} \cw{new_desc()}
819
820\c char *(*new_desc)(const game_params *params, random_state *rs,
821\c char **aux, bool interactive);
822
823This function is where all the really hard work gets done. This is
824the function whose job is to randomly generate a new puzzle,
825ensuring solubility and uniqueness as appropriate.
826
827As input it is given a \c{game_params} structure and a random state
828(see \k{utils-random} for the random number API). It must invent a
829puzzle instance, encode it in printable ASCII string form, and
830return a dynamically allocated C string containing that encoding.
831
832Additionally, it may return a second dynamically allocated string in
833\c{*aux}. (If it doesn't want to, then it can leave that parameter
834completely alone; it isn't required to set it to \cw{NULL}, although
835doing so is harmless.) That string, if present, will be passed to
836\cw{solve()} (\k{backend-solve}) later on; so if the puzzle is
837generated in such a way that a solution is known, then information
838about that solution can be saved in \c{*aux} for \cw{solve()} to
839use.
840
841The \c{interactive} parameter should be ignored by almost all
842puzzles. Its purpose is to distinguish between generating a puzzle
843within a GUI context for immediate play, and generating a puzzle in
844a command-line context for saving to be played later. The only
845puzzle that currently uses this distinction (and, I fervently hope,
846the only one which will \e{ever} need to use it) is Mines, which
847chooses a random first-click location when generating puzzles
848non-interactively, but which waits for the user to place the first
849click when interactive. If you think you have come up with another
850puzzle which needs to make use of this parameter, please think for
851at least ten minutes about whether there is \e{any} alternative!
852
853Note that game description strings are not required to contain an
854encoding of parameters such as grid size; a game description is
855never separated from the \c{game_params} it was generated with, so
856any information contained in that structure need not be encoded
857again in the game description.
858
859\S{backend-validate-desc} \cw{validate_desc()}
860
861\c const char *(*validate_desc)(const game_params *params,
862\c const char *desc);
863
864This function is given a game description, and its job is to
865validate that it describes a puzzle which makes sense.
866
867To some extent it's up to the user exactly how far they take the
868phrase \q{makes sense}; there are no particularly strict rules about
869how hard the user is permitted to shoot themself in the foot when
870typing in a bogus game description by hand. (For example, Rectangles
871will not verify that the sum of all the numbers in the grid equals
872the grid's area. So a user could enter a puzzle which was provably
873not soluble, and the program wouldn't complain; there just wouldn't
874happen to be any sequence of moves which solved it.)
875
876The one non-negotiable criterion is that any game description which
877makes it through \cw{validate_desc()} \e{must not} subsequently
878cause a crash or an assertion failure when fed to \cw{new_game()}
879and thence to the rest of the back end.
880
881The return value is \cw{NULL} on success, or a
882non-dynamically-allocated C string containing an error message.
883
884\S{backend-new-game} \cw{new_game()}
885
886\c game_state *(*new_game)(midend *me, const game_params *params,
887\c const char *desc);
888
889This function takes a game description as input, together with its
890accompanying \c{game_params}, and constructs a \c{game_state}
891describing the initial state of the puzzle. It returns a newly
892allocated \c{game_state} structure.
893
894Almost all puzzles should ignore the \c{me} parameter. It is
895required by Mines, which needs it for later passing to
896\cw{midend_supersede_game_desc()} (see \k{backend-supersede}) once
897the user has placed the first click. I fervently hope that no other
898puzzle will be awkward enough to require it, so everybody else
899should ignore it. As with the \c{interactive} parameter in
900\cw{new_desc()} (\k{backend-new-desc}), if you think you have a
901reason to need this parameter, please try very hard to think of an
902alternative approach!
903
904\H{backend-states} Handling game states
905
906This section describes the functions which create and destroy
907\c{game_state} structures.
908
909(Well, except \cw{new_game()}, which is in \k{backend-new-game}
910instead of under here; but it deals with game descriptions \e{and}
911game states and it had to go in one section or the other.)
912
913\S{backend-dup-game} \cw{dup_game()}
914
915\c game_state *(*dup_game)(const game_state *state);
916
917This function allocates a new \c{game_state} structure and
918initialises it with an exact copy of the information in the one
919provided as input. It returns a pointer to the new duplicate.
920
921\S{backend-free-game} \cw{free_game()}
922
923\c void (*free_game)(game_state *state);
924
925This function frees a \c{game_state} structure, and any subsidiary
926allocations contained within it.
927
928\H{backend-ui} Handling \c{game_ui}
929
930\S{backend-new-ui} \cw{new_ui()}
931
932\c game_ui *(*new_ui)(const game_state *state);
933
934This function allocates and returns a new \c{game_ui} structure for
935playing a particular puzzle.
936
937Usually, this function is passed a pointer to the initial
938\c{game_state}, in case it needs to refer to that when setting up the
939initial values for the new game.
940
941However, if the puzzle defines \c{get_prefs()} and \c{set_prefs()}
942functions, then this function may also be called with
943\cw{state==NULL}. In this situation it must still allocate a
944\c{game_ui} which can be used by \c{get_prefs()} and \c{set_prefs()},
945although it need not be usable for actually playing a game.
946
947\S{backend-free-ui} \cw{free_ui()}
948
949\c void (*free_ui)(game_ui *ui);
950
951This function frees a \c{game_ui} structure, and any subsidiary
952allocations contained within it.
953
954\S{backend-encode-ui} \cw{encode_ui()}
955
956\c char *(*encode_ui)(const game_ui *ui);
957
958This function encodes any \e{important} data in a \c{game_ui}
959structure in printable ASCII string form. It is only called when
960saving a half-finished game to a file.
961
962It should be used sparingly. Almost all data in a \c{game_ui} is not
963important enough to save. The location of the keyboard-controlled
964cursor, for example, can be reset to a default position on reloading
965the game without impacting the user experience. If the user should
966somehow manage to save a game while a mouse drag was in progress,
967then discarding that mouse drag would be an outright \e{feature}.
968
969A typical thing that \e{would} be worth encoding in this function is
970the Mines death counter: it's in the \c{game_ui} rather than the
971\c{game_state} because it's too important to allow the user to
972revert it by using Undo, and therefore it's also too important to
973allow the user to revert it by saving and reloading. (Of course, the
974user could edit the save file by hand... But if the user is \e{that}
975determined to cheat, they could just as easily modify the game's
976source.)
977
978The \cw{encode_ui()} function is optional. If a back-end doesn't need
979this function it can just set the pointer to \cw{NULL}.
980
981\S{backend-decode-ui} \cw{decode_ui()}
982
983\c void (*decode_ui)(game_ui *ui, const char *encoding,
984\c const game_state *state);
985
986This function parses a string previously output by \cw{encode_ui()},
987and writes the decoded data back into the freshly-created \c{game_ui}
988structure provided. If the string is invalid, the function should do
989the best it can, which might just mean not changing the \c{game_ui}
990structure at all. This might happen if a save file is corrupted, or
991simply from a newer version that encodes more \c{game_ui} data. The
992current \c{game_state} is provided in case the function needs to
993refer to it for validation.
994
995Like \cw{encode_ui()}, \cw{decode_ui()} is optional. If a back-end
996doesn't need this function it can just set the pointer to \cw{NULL}.
997
998\S{backend-changed-state} \cw{changed_state()}
999
1000\c void (*changed_state)(game_ui *ui, const game_state *oldstate,
1001\c const game_state *newstate);
1002
1003This function is called by the mid-end whenever the current game
1004state changes, for any reason. Those reasons include:
1005
1006\b a fresh move being made by \cw{interpret_move()} and
1007\cw{execute_move()}
1008
1009\b a solve operation being performed by \cw{solve()} and
1010\cw{execute_move()}
1011
1012\b the user moving back and forth along the undo list by means of
1013the Undo and Redo operations
1014
1015\b the user selecting Restart to go back to the initial game state.
1016
1017The job of \cw{changed_state()} is to update the \c{game_ui} for
1018consistency with the new game state, if any update is necessary. For
1019example, Same Game stores data about the currently selected tile
1020group in its \c{game_ui}, and this data is intrinsically related to
1021the game state it was derived from. So it's very likely to become
1022invalid when the game state changes; thus, Same Game's
1023\cw{changed_state()} function clears the current selection whenever
1024it is called.
1025
1026When \cw{anim_length()} or \cw{flash_length()} are called, you can
1027be sure that there has been a previous call to \cw{changed_state()}.
1028So \cw{changed_state()} can set up data in the \c{game_ui} which will
1029be read by \cw{anim_length()} and \cw{flash_length()}, and those
1030functions will not have to worry about being called without the data
1031having been initialised.
1032
1033\H{backend-moves} Making moves
1034
1035This section describes the functions which actually make moves in
1036the game: that is, the functions which process user input and end up
1037producing new \c{game_state}s.
1038
1039\S{backend-interpret-move} \cw{interpret_move()}
1040
1041\c char *(*interpret_move)(const game_state *state, game_ui *ui,
1042\c const game_drawstate *ds,
1043\c int x, int y, int button);
1044
1045This function receives user input and processes it. Its input
1046parameters are the current \c{game_state}, the current \c{game_ui}
1047and the current \c{game_drawstate}, plus details of the input event.
1048\c{button} is either an ASCII value or a special code (listed below)
1049indicating an arrow or function key or a mouse event; when
1050\c{button} is a mouse event, \c{x} and \c{y} contain the pixel
1051coordinates of the mouse pointer relative to the top left of the
1052puzzle's drawing area.
1053
1054(The pointer to the \c{game_drawstate} is marked \c{const}, because
1055\c{interpret_move} should not write to it. The normal use of that
1056pointer will be to read the game's tile size parameter in order to
1057divide mouse coordinates by it.)
1058
1059\cw{interpret_move()} may return in four different ways:
1060
1061\b Returning \cw{MOVE_UNUSED} or \cw{MOVE_NO_EFFECT} indicates that no
1062action whatsoever occurred in response to the input event; the puzzle
1063was not interested in it at all. The distinction between this is that
1064\cw{MOVE_NO_EFFECT} implies that the state of the game is what makes
1065the event uninteresting, while \cw{MOVE_NO_EFFECT} means that the
1066event is intrinsically uninteresting. For example, a mouse click on
1067an already-revealed square in Mines might return \cw{MOVE_NO_EFFECT}
1068while a click outside the board would return \cw{MOVE_UNUSED}.
1069
1070\b Returning the special value \cw{MOVE_UI_UPDATE} indicates that the input
1071event has resulted in a change being made to the \c{game_ui} which
1072will require a redraw of the game window, but that no actual \e{move}
1073was made (i.e. no new \c{game_state} needs to be created).
1074
1075\b Returning anything else indicates that a move was made and that a
1076new \c{game_state} must be created. However, instead of actually
1077constructing a new \c{game_state} itself, this function is required
1078to return a printable ASCII string description of the details of the
1079move. This string will be passed to \cw{execute_move()}
1080(\k{backend-execute-move}) to actually create the new
1081\c{game_state}. (Encoding moves as strings in this way means that
1082the mid-end can keep the strings as well as the game states, and the
1083strings can be written to disk when saving the game and fed to
1084\cw{execute_move()} again on reloading.)
1085
1086The return value from \cw{interpret_move()} is expected to be
1087dynamically allocated if and only if it is not either \cw{NULL}
1088\e{or} one of the special string constants \cw{MOVE_UNUSED},
1089\cw{MOVE_NO_EFFECT}, or \cw{MOVE_UI_UPDATE}.
1090
1091After this function is called, the back end is permitted to rely on
1092some subsequent operations happening in sequence:
1093
1094\b \cw{execute_move()} will be called to convert this move
1095description into a new \c{game_state}
1096
1097\b \cw{changed_state()} will be called with the new \c{game_state}.
1098
1099This means that if \cw{interpret_move()} needs to do updates to the
1100\c{game_ui} which are easier to perform by referring to the new
1101\c{game_state}, it can safely leave them to be done in
1102\cw{changed_state()} and not worry about them failing to happen.
1103
1104(Note, however, that \cw{execute_move()} may \e{also} be called in
1105other circumstances. It is only \cw{interpret_move()} which can rely
1106on a subsequent call to \cw{changed_state()}.)
1107
1108The special key codes supported by this function are:
1109
1110\dt \cw{LEFT_BUTTON}, \cw{MIDDLE_BUTTON}, \cw{RIGHT_BUTTON}
1111
1112\dd Indicate that one of the mouse buttons was pressed down.
1113
1114\dt \cw{LEFT_DRAG}, \cw{MIDDLE_DRAG}, \cw{RIGHT_DRAG}
1115
1116\dd Indicate that the mouse was moved while one of the mouse buttons
1117was still down. The mid-end guarantees that when one of these events
1118is received, it will always have been preceded by a button-down
1119event (and possibly other drag events) for the same mouse button,
1120and no event involving another mouse button will have appeared in
1121between.
1122
1123\dt \cw{LEFT_RELEASE}, \cw{MIDDLE_RELEASE}, \cw{RIGHT_RELEASE}
1124
1125\dd Indicate that a mouse button was released. The mid-end
1126guarantees that when one of these events is received, it will always
1127have been preceded by a button-down event (and possibly some drag
1128events) for the same mouse button, and no event involving another
1129mouse button will have appeared in between.
1130
1131\dt \cw{CURSOR_UP}, \cw{CURSOR_DOWN}, \cw{CURSOR_LEFT},
1132\cw{CURSOR_RIGHT}
1133
1134\dd Indicate that an arrow key was pressed.
1135
1136\dt \cw{CURSOR_SELECT}, \cw{CURSOR_SELECT2}
1137
1138\dd On platforms which have one or two prominent \q{select} button
1139alongside their cursor keys, indicates that one of those buttons was
1140pressed. On other platforms, these represent the Enter (or Return)
1141and Space keys respectively.
1142
1143In addition, there are some modifiers which can be bitwise-ORed into
1144the \c{button} parameter:
1145
1146\dt \cw{MOD_CTRL}, \cw{MOD_SHFT}
1147
1148\dd These indicate that the Control or Shift key was pressed alongside
1149the key. They only apply to the cursor keys and the ASCII horizontal
1150tab character \cw{\\t}, not to mouse buttons or anything else.
1151
1152\dt \cw{MOD_NUM_KEYPAD}
1153
1154\dd This applies to some ASCII values, and indicates that the key
1155code was input via the numeric keypad rather than the main keyboard.
1156Some puzzles may wish to treat this differently (for example, a
1157puzzle might want to use the numeric keypad as an eight-way
1158directional pad), whereas others might not (a game involving numeric
1159input probably just wants to treat the numeric keypad as numbers).
1160
1161\dt \cw{MOD_MASK}
1162
1163\dd This mask is the bitwise OR of all the available modifiers; you
1164can bitwise-AND with \cw{~MOD_MASK} to strip all the modifiers off any
1165input value; as this is a common operation, the
1166\cw{STRIP_BUTTON_MODIFIERS()} macro can do this for you (see
1167\k{utils-strip-button-modifiers}).
1168
1169\S{backend-execute-move} \cw{execute_move()}
1170
1171\c game_state *(*execute_move)(const game_state *state, char *move);
1172
1173This function takes an input \c{game_state} and a move string as
1174output from \cw{interpret_move()}. It returns a newly allocated
1175\c{game_state} which contains the result of applying the specified
1176move to the input game state.
1177
1178This function may return \cw{NULL} if it cannot parse the move
1179string (and this is definitely preferable to crashing or failing an
1180assertion, since one way this can happen is if loading a corrupt
1181save file). However, it must not return \cw{NULL} for any move
1182string that really was output from \cw{interpret_move()}: this is
1183punishable by assertion failure in the mid-end.
1184
1185\S{backend-can-solve} \c{can_solve}
1186
1187\c bool can_solve;
1188
1189This field is set to \cw{true} if the game's \cw{solve()} function
1190does something. If it's set to \cw{false}, the game will not even
1191offer the \q{Solve} menu option.
1192
1193\S{backend-solve} \cw{solve()}
1194
1195\c char *(*solve)(const game_state *orig, const game_state *curr,
1196\c const char *aux, const char **error);
1197
1198This function is called when the user selects the \q{Solve} option
1199from the menu. If \cw{can_solve} is \cw{false} then it will never
1200be called and can be \cw{NULL}.
1201
1202It is passed two input game states: \c{orig} is the game state from
1203the very start of the puzzle, and \c{curr} is the current one.
1204(Different games find one or other or both of these convenient.) It
1205is also passed the \c{aux} string saved by \cw{new_desc()}
1206(\k{backend-new-desc}), in case that encodes important information
1207needed to provide the solution.
1208
1209If this function is unable to produce a solution (perhaps, for
1210example, the game has no in-built solver so it can only solve
1211puzzles it invented internally and has an \c{aux} string for) then
1212it may return \cw{NULL}. If it does this, it must also set
1213\c{*error} to an error message to be presented to the user (such as
1214\q{Solution not known for this puzzle}); that error message is not
1215expected to be dynamically allocated.
1216
1217If this function \e{does} produce a solution, it returns a printable
1218ASCII move string suitable for feeding to \cw{execute_move()}
1219(\k{backend-execute-move}). Like a (non-empty) string returned from
1220\cw{interpret_move()}, the returned string should be dynamically
1221allocated.
1222
1223\H{backend-drawing} Drawing the game graphics
1224
1225This section discusses the back end functions that deal with
1226drawing.
1227
1228\S{backend-new-drawstate} \cw{new_drawstate()}
1229
1230\c game_drawstate *(*new_drawstate)(drawing *dr,
1231\c const game_state *state);
1232
1233This function allocates and returns a new \c{game_drawstate}
1234structure for drawing a particular puzzle. It is passed a pointer to
1235a \c{game_state}, in case it needs to refer to that when setting up
1236any initial data.
1237
1238This function may not rely on the puzzle having been newly started;
1239a new draw state can be constructed at any time if the front end
1240requests a forced redraw. For games like Pattern, in which initial
1241game states are much simpler than general ones, this might be
1242important to keep in mind.
1243
1244The parameter \c{dr} is a drawing object (see \k{drawing}) which the
1245function might need to use to allocate blitters. (However, this
1246isn't recommended; it's usually more sensible to wait to allocate a
1247blitter until \cw{set_size()} is called, because that way you can
1248tailor it to the scale at which the puzzle is being drawn.)
1249
1250\S{backend-free-drawstate} \cw{free_drawstate()}
1251
1252\c void (*free_drawstate)(drawing *dr, game_drawstate *ds);
1253
1254This function frees a \c{game_drawstate} structure, and any
1255subsidiary allocations contained within it.
1256
1257The parameter \c{dr} is a drawing object (see \k{drawing}), which
1258might be required if you are freeing a blitter.
1259
1260\S{backend-preferred-tilesize} \c{preferred_tilesize}
1261
1262\c int preferred_tilesize;
1263
1264Each game is required to define a single integer parameter which
1265expresses, in some sense, the scale at which it is drawn. This is
1266described in the APIs as \cq{tilesize}, since most puzzles are on a
1267square (or possibly triangular or hexagonal) grid and hence a
1268sensible interpretation of this parameter is to define it as the
1269size of one grid tile in pixels; however, there's no actual
1270requirement that the \q{tile size} be proportional to the game
1271window size. Window size is required to increase monotonically with
1272\q{tile size}, however.
1273
1274The data element \c{preferred_tilesize} indicates the tile size which
1275should be used in the absence of a good reason to do otherwise (such
1276as the screen being too small to fit the whole puzzle, or the user
1277explicitly requesting a resize).
1278
1279\S{backend-compute-size} \cw{compute_size()}
1280
1281\c void (*compute_size)(const game_params *params, int tilesize,
1282\c const game_ui *ui, int *x, int *y);
1283
1284This function is passed a \c{game_params} structure and a tile size.
1285It returns, in \c{*x} and \c{*y}, the size in pixels of the drawing
1286area that would be required to render a puzzle with those parameters
1287at that tile size.
1288
1289\S{backend-set-size} \cw{set_size()}
1290
1291\c void (*set_size)(drawing *dr, game_drawstate *ds,
1292\c const game_params *params, int tilesize);
1293
1294This function is responsible for setting up a \c{game_drawstate} to
1295draw at a given tile size. Typically this will simply involve
1296copying the supplied \c{tilesize} parameter into a \c{tilesize}
1297field inside the draw state; for some more complex games it might
1298also involve setting up other dimension fields, or possibly
1299allocating a blitter (see \k{drawing-blitter}).
1300
1301The parameter \c{dr} is a drawing object (see \k{drawing}), which is
1302required if a blitter needs to be allocated.
1303
1304Back ends may assume (and may enforce by assertion) that this
1305function will be called at most once for any \c{game_drawstate}. If
1306a puzzle needs to be redrawn at a different size, the mid-end will
1307create a fresh drawstate.
1308
1309\S{backend-colours} \cw{colours()}
1310
1311\c float *(*colours)(frontend *fe, int *ncolours);
1312
1313This function is responsible for telling the front end what colours
1314the puzzle will need to draw itself.
1315
1316It returns the number of colours required in \c{*ncolours}, and the
1317return value from the function itself is a dynamically allocated
1318array of three times that many \c{float}s, containing the red, green
1319and blue components of each colour respectively as numbers in the
1320range [0,1].
1321
1322The second parameter passed to this function is a front end handle.
1323The only things it is permitted to do with this handle are to call
1324the front-end function called \cw{frontend_default_colour()} (see
1325\k{frontend-default-colour}) or the utility function called
1326\cw{game_mkhighlight()} (see \k{utils-game-mkhighlight}). (The
1327latter is a wrapper on the former, so front end implementors only
1328need to provide \cw{frontend_default_colour()}.) This allows
1329\cw{colours()} to take local configuration into account when
1330deciding on its own colour allocations. Most games use the front
1331end's default colour as their background, apart from a few which
1332depend on drawing relief highlights so they adjust the background
1333colour if it's too light for highlights to show up against it.
1334
1335The first colour in the list is slightly special. The mid-end fills
1336the drawing area with it before the first call to \cw{redraw()} (see
1337\k{backend-redraw}). Some front ends also use it fill the part of the
1338puzzle window outside the puzzle. This means that it is usually
1339sensible to make colour 0 the background colour for the puzzle.
1340
1341Note that the colours returned from this function are for
1342\e{drawing}, not for printing. Printing has an entirely different
1343colour allocation policy.
1344
1345\S{backend-anim-length} \cw{anim_length()}
1346
1347\c float (*anim_length)(const game_state *oldstate,
1348\c const game_state *newstate,
1349\c int dir, game_ui *ui);
1350
1351This function is called when a move is made, undone or redone. It is
1352given the old and the new \c{game_state}, and its job is to decide
1353whether the transition between the two needs to be animated or can
1354be instant.
1355
1356\c{oldstate} is the state that was current until this call;
1357\c{newstate} is the state that will be current after it. \c{dir}
1358specifies the chronological order of those states: if it is
1359positive, then the transition is the result of a move or a redo (and
1360so \c{newstate} is the later of the two moves), whereas if it is
1361negative then the transition is the result of an undo (so that
1362\c{newstate} is the \e{earlier} move).
1363
1364If this function decides the transition should be animated, it
1365returns the desired length of the animation in seconds. If not, it
1366returns zero.
1367
1368State changes as a result of a Restart operation are never animated;
1369the mid-end will handle them internally and never consult this
1370function at all. State changes as a result of Solve operations are
1371also not animated by default, although you can change this for a
1372particular game by setting a flag in \c{flags} (\k{backend-flags}).
1373
1374The function is also passed a pointer to the local \c{game_ui}. It
1375may refer to information in here to help with its decision (see
1376\k{writing-conditional-anim} for an example of this), and/or it may
1377\e{write} information about the nature of the animation which will
1378be read later by \cw{redraw()}.
1379
1380When this function is called, it may rely on \cw{changed_state()}
1381having been called previously, so if \cw{anim_length()} needs to
1382refer to information in the \c{game_ui}, then \cw{changed_state()}
1383is a reliable place to have set that information up.
1384
1385Move animations do not inhibit further input events. If the user
1386continues playing before a move animation is complete, the animation
1387will be abandoned and the display will jump straight to the final
1388state.
1389
1390\S{backend-flash-length} \cw{flash_length()}
1391
1392\c float (*flash_length)(const game_state *oldstate,
1393\c const game_state *newstate,
1394\c int dir, game_ui *ui);
1395
1396This function is called when a move is completed. (\q{Completed}
1397means that not only has the move been made, but any animation which
1398accompanied it has finished.) It decides whether the transition from
1399\c{oldstate} to \c{newstate} merits a \q{flash}.
1400
1401A flash is much like a move animation, but it is \e{not} interrupted
1402by further user interface activity; it runs to completion in
1403parallel with whatever else might be going on on the display. The
1404only thing which will rush a flash to completion is another flash.
1405
1406The purpose of flashes is to indicate that the game has been
1407completed. They were introduced as a separate concept from move
1408animations because of Net: the habit of most Net players (and
1409certainly me) is to rotate a tile into place and immediately lock
1410it, then move on to another tile. When you make your last move, at
1411the instant the final tile is rotated into place the screen starts
1412to flash to indicate victory \dash but if you then press the lock
1413button out of habit, then the move animation is cancelled, and the
1414victory flash does not complete. (And if you \e{don't} press the
1415lock button, the completed grid will look untidy because there will
1416be one unlocked square.) Therefore, I introduced a specific concept
1417of a \q{flash} which is separate from a move animation and can
1418proceed in parallel with move animations and any other display
1419activity, so that the victory flash in Net is not cancelled by that
1420final locking move.
1421
1422The input parameters to \cw{flash_length()} are exactly the same as
1423the ones to \cw{anim_length()}: see \k{backend-anim-length}.
1424
1425Just like \cw{anim_length()}, when this function is called, it may
1426rely on \cw{changed_state()} having been called previously, so if it
1427needs to refer to information in the \c{game_ui} then
1428\cw{changed_state()} is a reliable place to have set that
1429information up.
1430
1431(Some games use flashes to indicate defeat as well as victory;
1432Mines, for example, flashes in a different colour when you tread on
1433a mine from the colour it uses when you complete the game. In order
1434to achieve this, its \cw{flash_length()} function has to store a
1435flag in the \c{game_ui} to indicate which flash type is required.)
1436
1437\S{backend-get-cursor-location} \cw{get_cursor_location()}
1438
1439\c void (*get_cursor_location)(const game_ui *ui,
1440\c const game_drawstate *ds,
1441\c const game_state *state,
1442\c const game_params *params,
1443\c int *x, int *y,
1444\c int *w, int *h);
1445
1446This function queries the backend for the rectangular region
1447containing the cursor (in games that have one), or other region of
1448interest.
1449
1450This function is called by only
1451\cw{midend_get_cursor_location()} (\k{midend-get-cursor-location}). Its
1452purpose is to allow front ends to query the location of the backend's
1453cursor. With knowledge of this location, a front end can, for example,
1454ensure that the region of interest remains visible if the puzzle is
1455too big to fit on the screen at once.
1456
1457On returning, \cw{*x}, \cw{*y} should be set to the X and Y
1458coordinates of the upper-left corner of the rectangular region of
1459interest, and \cw{*w} and \cw{*h} should be the width and height of
1460that region, respectively. In the event that a cursor is not visible
1461on screen, this function should return and leave the return parameters
1462untouched \dash the midend will notice this. The backend need not
1463bother checking that \cw{x}, \cw{y}, \cw{w} and \cw{h} are
1464non-\cw{NULL} \dash the midend guarantees that they will not be.
1465
1466Defining what constitutes a \q{region of interest} is left up to the
1467backend. If a game provides a conventional cursor \dash such as Mines,
1468Solo, or any of the other grid-based games \dash the most logical
1469choice is of course the location of the cursor itself. However, in
1470other cases such as Cube or Inertia, there is no \q{cursor} in the
1471conventional sense \dash the player instead controls an object moving
1472around the screen. In these cases, it makes sense to define the region
1473of interest as the bounding box of the player object or another
1474sensible region \dash such as the grid square the player is sitting on
1475in Cube.
1476
1477If a backend does not provide a cursor mechanism at all, the backend
1478is free to provide an empty implementation of this function, or a
1479\cw{NULL} pointer in the \cw{game} structure \dash the midend will
1480notice either of these cases and behave appropriately.
1481
1482\S{backend-status} \cw{status()}
1483
1484\c int (*status)(const game_state *state);
1485
1486This function returns a status value indicating whether the current
1487game is still in play, or has been won, or has been conclusively lost.
1488The mid-end uses this to implement \cw{midend_status()}
1489(\k{midend-status}).
1490
1491The return value should be +1 if the game has been successfully
1492solved. If the game has been lost in a situation where further play is
1493unlikely, the return value should be -1. If neither is true (so play
1494is still ongoing), return zero.
1495
1496Front ends may wish to use a non-zero status as a cue to proactively
1497offer the option of starting a new game. Therefore, back ends should
1498not return -1 if the game has been \e{technically} lost but undoing
1499and continuing is still a realistic possibility.
1500
1501(For instance, games with hidden information such as Guess or Mines
1502might well return a non-zero status whenever they reveal the solution,
1503whether or not the player guessed it correctly, on the grounds that a
1504player would be unlikely to hide the solution and continue playing
1505after the answer was spoiled. On the other hand, games where you can
1506merely get into a dead end such as Same Game or Inertia might choose
1507to return 0 in that situation, on the grounds that the player would
1508quite likely press Undo and carry on playing.)
1509
1510\S{backend-redraw} \cw{redraw()}
1511
1512\c void (*redraw)(drawing *dr, game_drawstate *ds,
1513\c const game_state *oldstate,
1514\c const game_state *newstate,
1515\c int dir, const game_ui *ui,
1516\c float anim_time, float flash_time);
1517
1518This function is responsible for actually drawing the contents of
1519the game window, and for redrawing every time the game state or the
1520\c{game_ui} changes.
1521
1522The parameter \c{dr} is a drawing object which may be passed to the
1523drawing API functions (see \k{drawing} for documentation of the
1524drawing API). This function may not save \c{dr} and use it
1525elsewhere; it must only use it for calling back to the drawing API
1526functions within its own lifetime.
1527
1528\c{ds} is the local \c{game_drawstate}, of course, and \c{ui} is the
1529local \c{game_ui}.
1530
1531\c{newstate} is the semantically-current game state, and is always
1532non-\cw{NULL}. If \c{oldstate} is also non-\cw{NULL}, it means that
1533a move has recently been made and the game is still in the process
1534of displaying an animation linking the old and new states; in this
1535situation, \c{anim_time} will give the length of time (in seconds)
1536that the animation has already been running. If \c{oldstate} is
1537\cw{NULL}, then \c{anim_time} is unused (and will hopefully be set
1538to zero to avoid confusion).
1539
1540\c{dir} specifies the chronological order of those states: if it is
1541positive, then the transition is the result of a move or a redo (and
1542so \c{newstate} is the later of the two moves), whereas if it is
1543negative then the transition is the result of an undo (so that
1544\c{newstate} is the \e{earlier} move). This allows move animations
1545that are not time-symmetric (such as Inertia, where gems are consumed
1546during the animation) to be drawn the right way round.
1547
1548\c{flash_time}, if it is is non-zero, denotes that the game is in
1549the middle of a flash, and gives the time since the start of the
1550flash. See \k{backend-flash-length} for general discussion of
1551flashes.
1552
1553The very first time this function is called for a new
1554\c{game_drawstate}, it is expected to redraw the \e{entire} drawing
1555area. Since this often involves drawing visual furniture which is
1556never subsequently altered, it is often simplest to arrange this by
1557having a special \q{first time} flag in the draw state, and
1558resetting it after the first redraw. This function can assume that
1559the mid-end has filled the drawing area with colour 0 before the first
1560call.
1561
1562When this function (or any subfunction) calls the drawing API, it is
1563expected to pass colour indices which were previously defined by the
1564\cw{colours()} function.
1565
1566\H{backend-printing} Printing functions
1567
1568This section discusses the back end functions that deal with
1569printing puzzles out on paper.
1570
1571\S{backend-can-print} \c{can_print}
1572
1573\c bool can_print;
1574
1575This flag is set to \cw{true} if the puzzle is capable of printing
1576itself on paper. (This makes sense for some puzzles, such as Solo,
1577which can be filled in with a pencil. Other puzzles, such as
1578Twiddle, inherently involve moving things around and so would not
1579make sense to print.)
1580
1581If this flag is \cw{false}, then the functions \cw{print_size()}
1582and \cw{print()} will never be called and can be \cw{NULL}.
1583
1584\S{backend-can-print-in-colour} \c{can_print_in_colour}
1585
1586\c bool can_print_in_colour;
1587
1588This flag is set to \cw{true} if the puzzle is capable of printing
1589itself differently when colour is available. For example, Map can
1590actually print coloured regions in different \e{colours} rather than
1591resorting to cross-hatching.
1592
1593If the \c{can_print} flag is \cw{false}, then this flag will be
1594ignored.
1595
1596\S{backend-print-size} \cw{print_size()}
1597
1598\c void (*print_size)(const game_params *params, const game_ui *ui,
1599\c float *x, float *y);
1600
1601This function is passed a \c{game_params} structure and a tile size.
1602It returns, in \c{*x} and \c{*y}, the preferred size in
1603\e{millimetres} of that puzzle if it were to be printed out on paper.
1604
1605If the \c{can_print} flag is \cw{false}, this function will never be
1606called.
1607
1608\S{backend-print} \cw{print()}
1609
1610\c void (*print)(drawing *dr, const game_state *state,
1611\c const game_ui *ui, int tilesize);
1612
1613This function is called when a puzzle is to be printed out on paper.
1614It should use the drawing API functions (see \k{drawing}) to print
1615itself.
1616
1617This function is separate from \cw{redraw()} because it is often
1618very different:
1619
1620\b The printing function may not depend on pixel accuracy, since
1621printer resolution is variable. Draw as if your canvas had infinite
1622resolution.
1623
1624\b The printing function sometimes needs to display things in a
1625completely different style. Net, for example, is very different as
1626an on-screen puzzle and as a printed one.
1627
1628\b The printing function is often much simpler since it has no need
1629to deal with repeated partial redraws.
1630
1631However, there's no reason the printing and redraw functions can't
1632share some code if they want to.
1633
1634When this function (or any subfunction) calls the drawing API, the
1635colour indices it passes should be colours which have been allocated
1636by the \cw{print_*_colour()} functions within this execution of
1637\cw{print()}. This is very different from the fixed small number of
1638colours used in \cw{redraw()}, because printers do not have a
1639limitation on the total number of colours that may be used. Some
1640puzzles' printing functions might wish to allocate only one \q{ink}
1641colour and use it for all drawing; others might wish to allocate
1642\e{more} colours than are used on screen.
1643
1644One possible colour policy worth mentioning specifically is that a
1645puzzle's printing function might want to allocate the \e{same}
1646colour indices as are used by the redraw function, so that code
1647shared between drawing and printing does not have to keep switching
1648its colour indices. In order to do this, the simplest thing is to
1649make use of the fact that colour indices returned from
1650\cw{print_*_colour()} are guaranteed to be in increasing order from
1651zero. So if you have declared an \c{enum} defining three colours
1652\cw{COL_BACKGROUND}, \cw{COL_THIS} and \cw{COL_THAT}, you might then
1653write
1654
1655\c int c;
1656\c c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND);
1657\c c = print_mono_colour(dr, 0); assert(c == COL_THIS);
1658\c c = print_mono_colour(dr, 0); assert(c == COL_THAT);
1659
1660If the \c{can_print} flag is \cw{false}, this function will never be
1661called.
1662
1663\H{backend-misc} Miscellaneous
1664
1665\S{backend-can-format-as-text-ever} \c{can_format_as_text_ever}
1666
1667\c bool can_format_as_text_ever;
1668
1669This field is \cw{true} if the game supports formatting a
1670game state as ASCII text (typically ASCII art) for copying to the
1671clipboard and pasting into other applications. If it is \cw{false},
1672front ends will not offer the \q{Copy} command at all.
1673
1674If this field is \cw{true}, the game does not necessarily have to
1675support text formatting for \e{all} games: e.g. a game which can be
1676played on a square grid or a triangular one might only support copy
1677and paste for the former, because triangular grids in ASCII art are
1678just too difficult.
1679
1680If this field is \cw{false}, the functions
1681\cw{can_format_as_text_now()} (\k{backend-can-format-as-text-now})
1682and \cw{text_format()} (\k{backend-text-format}) are never called
1683and can be \cw{NULL}.
1684
1685\S{backend-can-format-as-text-now} \c{can_format_as_text_now()}
1686
1687\c bool (*can_format_as_text_now)(const game_params *params);
1688
1689This function is passed a \c{game_params}, and returns \cw{true} if
1690the game can support ASCII text output for this particular game type.
1691If it returns \cw{false}, front ends will grey out or otherwise
1692disable the \q{Copy} command.
1693
1694Games may enable and disable the copy-and-paste function for
1695different game \e{parameters}, but are currently constrained to
1696return the same answer from this function for all game \e{states}
1697sharing the same parameters. In other words, the \q{Copy} function
1698may enable or disable itself when the player changes game preset,
1699but will never change during play of a single game or when another
1700game of exactly the same type is generated.
1701
1702This function should not take into account aspects of the game
1703parameters which are not encoded by \cw{encode_params()}
1704(\k{backend-encode-params}) when the \c{full} parameter is set to
1705\cw{false}. Such parameters will not necessarily match up between a
1706call to this function and a subsequent call to \cw{text_format()}
1707itself. (For instance, game \e{difficulty} should not affect whether
1708the game can be copied to the clipboard. Only the actual visible
1709\e{shape} of the game can affect that.)
1710
1711\S{backend-text-format} \cw{text_format()}
1712
1713\c char *(*text_format)(const game_state *state);
1714
1715This function is passed a \c{game_state}, and returns a newly
1716allocated C string containing an ASCII representation of that game
1717state. It is used to implement the \q{Copy} operation in many front
1718ends.
1719
1720This function will only ever be called if the back end field
1721\c{can_format_as_text_ever} (\k{backend-can-format-as-text-ever}) is
1722\cw{true} \e{and} the function \cw{can_format_as_text_now()}
1723(\k{backend-can-format-as-text-now}) has returned \cw{true} for the
1724currently selected game parameters.
1725
1726The returned string may contain line endings (and will probably want
1727to), using the normal C internal \cq{\\n} convention. For
1728consistency between puzzles, all multi-line textual puzzle
1729representations should \e{end} with a newline as well as containing
1730them internally. (There are currently no puzzles which have a
1731one-line ASCII representation, so there's no precedent yet for
1732whether that should come with a newline or not.)
1733
1734\S{backend-wants-statusbar} \cw{wants_statusbar}
1735
1736\c bool wants_statusbar;
1737
1738This field is set to \cw{true} if the puzzle has a use for a textual
1739status line (to display score, completion status, currently active
1740tiles, etc). If the \c{redraw()} function ever intends to call
1741\c{status_bar()} in the drawing API (\k{drawing-status-bar}), then it
1742should set this flag to \c{true}.
1743
1744\S{backend-is-timed} \c{is_timed}
1745
1746\c bool is_timed;
1747
1748This field is \cw{true} if the puzzle is time-critical. If
1749so, the mid-end will maintain a game timer while the user plays.
1750
1751If this field is \cw{false}, then \cw{timing_state()} will never be
1752called and can be \cw{NULL}.
1753
1754\S{backend-timing-state} \cw{timing_state()}
1755
1756\c bool (*timing_state)(const game_state *state, game_ui *ui);
1757
1758This function is passed the current \c{game_state} and the local
1759\c{game_ui}; it returns \cw{true} if the game timer should currently
1760be running.
1761
1762A typical use for the \c{game_ui} in this function is to note when
1763the game was first completed (by setting a flag in
1764\cw{changed_state()} \dash see \k{backend-changed-state}), and
1765freeze the timer thereafter so that the user can undo back through
1766their solution process without altering their time.
1767
1768\S{backend-request-keys} \cw{request_keys()}
1769
1770\c key_label *(*request_keys)(const game_params *params, int *nkeys);
1771
1772This function returns a dynamically allocated array of \cw{key_label}
1773items containing the buttons the back end deems absolutely
1774\e{necessary} for gameplay, not an exhaustive list of every button the
1775back end could accept. For example, Keen only returns the digits up to
1776the game size and the backspace character, \cw{\\b}, even though it
1777\e{could} accept \cw{M}, as only these buttons are actually needed to
1778play the game. Each \cw{key_label} item contains the following fields:
1779
1780\c struct key_label {
1781\c char *label; /* label for frontend use */
1782\c int button; /* button to pass to midend */
1783\c } key_label;
1784
1785The \cw{label} field of this structure can (and often will) be set by
1786the backend to \cw{NULL}, in which case the midend will instead call
1787\c{button2label()} (\k{utils-button2label}) and fill in a generic
1788label. The \cw{button} field is the associated code that can be passed
1789to the midend when the frontend deems appropriate.
1790
1791If \cw{label} is not \cw{NULL}, then it's a dynamically allocated
1792string. Therefore, freeing an array of these structures needs more
1793than just a single free operatio. The function \c{free_keys()}
1794(\k{utils-free-keys}) can be used to free a whole array of these
1795structures conveniently.
1796
1797The backend should set \cw{*nkeys} to the number of elements in the
1798returned array.
1799
1800The field for this function pointer in the \cw{game} structure might
1801be set to \cw{NULL} (and indeed it is for the majority of the games)
1802to indicate that no additional buttons (apart from the cursor keys)
1803are required to play the game.
1804
1805This function should not be called directly by frontends. Instead,
1806frontends should use \cw{midend_request_keys()}
1807(\k{midend-request-keys}).
1808
1809\S{backend-current-key-label} \cw{current_key_label()}
1810
1811\c const char *(*current_key_label)(const game_ui *ui,
1812\c const game_state *state,
1813\c int button);
1814
1815This function is called to ask the back-end how certain keys should be
1816labelled on platforms (such a feature phones) where this is
1817conventional.
1818These labels are expected to reflect what the keys will do right now,
1819so they can change depending on the game and UI state.
1820
1821The \c{ui} and \c{state} arguments describe the state of the game for
1822which key labels are required.
1823The \c{button} argument is the same as the one passed to
1824\cw{interpret_move()}.
1825At present, the only values of \c{button} that can be passed to
1826\cw{current_key_label()} are \cw{CURSOR_SELECT} and \cw{CURSOR_SELECT2}.
1827The return value is a short string describing what the requested key
1828will do if pressed.
1829Usually the string should be a static string constant.
1830If it's really necessary to use a dynamically-allocated string, it
1831should remain valid until the next call to \cw{current_key_label()} or
1832\cw{free_ui()} with the same \cw{game_ui} (so it can be referenced from
1833the \cw{game_ui} and freed at the next one of those calls).
1834
1835There's no fixed upper limit on the length of string that this
1836function can return, but more than about 12 characters is likely to
1837cause problems for front-ends. If two buttons have the same effect,
1838their labels should be identical so that the front end can detect
1839this. Similarly, keys that do different things should have different
1840labels. The label should be an empty string (\cw{""}) if the key does
1841nothing.
1842
1843Like \cw{request_keys()}, the \cw{current_key_label} pointer in the
1844\c{game} structure is allowed to be \cw{NULL}, in which case the
1845mid-end will treat it as though it always returned \cw{""}.
1846
1847\S{backend-flags} \c{flags}
1848
1849\c int flags;
1850
1851This field contains miscellaneous per-backend flags. It consists of
1852the bitwise OR of some combination of the following:
1853
1854\dt \cw{BUTTON_BEATS(x,y)}
1855
1856\dd Given any \cw{x} and \cw{y} from the set \{\cw{LEFT_BUTTON},
1857\cw{MIDDLE_BUTTON}, \cw{RIGHT_BUTTON}\}, this macro evaluates to a
1858bit flag which indicates that when buttons \cw{x} and \cw{y} are
1859both pressed simultaneously, the mid-end should consider \cw{x} to
1860have priority. (In the absence of any such flags, the mid-end will
1861always consider the most recently pressed button to have priority.)
1862
1863\dt \cw{SOLVE_ANIMATES}
1864
1865\dd This flag indicates that moves generated by \cw{solve()}
1866(\k{backend-solve}) are candidates for animation just like any other
1867move. For most games, solve moves should not be animated, so the
1868mid-end doesn't even bother calling \cw{anim_length()}
1869(\k{backend-anim-length}), thus saving some special-case code in
1870each game. On the rare occasion that animated solve moves are
1871actually required, you can set this flag.
1872
1873\dt \cw{REQUIRE_RBUTTON}
1874
1875\dd This flag indicates that the puzzle cannot be usefully played
1876without the use of mouse buttons other than the left one. On some
1877PDA platforms, this flag is used by the front end to enable
1878right-button emulation through an appropriate gesture. Note that a
1879puzzle is not required to set this just because it \e{uses} the
1880right button, but only if its use of the right button is critical to
1881playing the game. (Slant, for example, uses the right button to
1882cycle through the three square states in the opposite order from the
1883left button, and hence can manage fine without it.)
1884
1885\dt \cw{REQUIRE_NUMPAD}
1886
1887\dd This flag indicates that the puzzle cannot be usefully played
1888without the use of number-key input. On some PDA platforms it causes
1889an emulated number pad to appear on the screen. Similarly to
1890\cw{REQUIRE_RBUTTON}, a puzzle need not specify this simply if its
1891use of the number keys is not critical.
1892
1893\H{backend-initiative} Things a back end may do on its own initiative
1894
1895This section describes a couple of things that a back end may choose
1896to do by calling functions elsewhere in the program, which would not
1897otherwise be obvious.
1898
1899\S{backend-newrs} Create a random state
1900
1901If a back end needs random numbers at some point during normal play,
1902it can create a fresh \c{random_state} by first calling
1903\c{get_random_seed} (\k{frontend-get-random-seed}) and then passing
1904the returned seed data to \cw{random_new()}.
1905
1906This is likely not to be what you want. If a puzzle needs randomness
1907in the middle of play, it's likely to be more sensible to store some
1908sort of random state within the \c{game_state}, so that the random
1909numbers are tied to the particular game state and hence the player
1910can't simply keep undoing their move until they get numbers they
1911like better.
1912
1913This facility is currently used only in Net, to implement the
1914\q{jumble} command, which sets every unlocked tile to a new random
1915orientation. This randomness \e{is} a reasonable use of the feature,
1916because it's non-adversarial \dash there's no advantage to the user
1917in getting different random numbers.
1918
1919\S{backend-supersede} Supersede its own game description
1920
1921In response to a move, a back end is (reluctantly) permitted to call
1922\cw{midend_supersede_game_desc()}:
1923
1924\c void midend_supersede_game_desc(midend *me,
1925\c char *desc, char *privdesc);
1926
1927When the user selects \q{New Game}, the mid-end calls
1928\cw{new_desc()} (\k{backend-new-desc}) to get a new game
1929description, and (as well as using that to generate an initial game
1930state) stores it for the save file and for telling to the user. The
1931function above overwrites that game description, and also splits it
1932in two. \c{desc} becomes the new game description which is provided
1933to the user on request, and is also the one used to construct a new
1934initial game state if the user selects \q{Restart}. \c{privdesc} is
1935a \q{private} game description, used to reconstruct the game's
1936initial state when reloading.
1937
1938The distinction between the two, as well as the need for this
1939function at all, comes from Mines. Mines begins with a blank grid
1940and no idea of where the mines actually are; \cw{new_desc()} does
1941almost no work in interactive mode, and simply returns a string
1942encoding the \c{random_state}. When the user first clicks to open a
1943tile, \e{then} Mines generates the mine positions, in such a way
1944that the game is soluble from that starting point. Then it uses this
1945function to supersede the random-state game description with a
1946proper one. But it needs two: one containing the initial click
1947location (because that's what you want to happen if you restart the
1948game, and also what you want to send to a friend so that they play
1949\e{the same game} as you), and one without the initial click
1950location (because when you save and reload the game, you expect to
1951see the same blank initial state as you had before saving).
1952
1953I should stress again that this function is a horrid hack. Nobody
1954should use it if they're not Mines; if you think you need to use it,
1955think again repeatedly in the hope of finding a better way to do
1956whatever it was you needed to do.
1957
1958\C{drawing} The drawing API
1959
1960The back end function \cw{redraw()} (\k{backend-redraw}) is required
1961to draw the puzzle's graphics on the window's drawing area. The back
1962end function \cw{print()} similarly draws the puzzle on paper, if the
1963puzzle is printable. To do this portably, the back end is provided
1964with a drawing API allowing it to talk directly to the front end. In
1965this chapter I document that API, both for the benefit of back end
1966authors trying to use it and for front end authors trying to implement
1967it.
1968
1969The drawing API as seen by the back end is a collection of global
1970functions, each of which takes a pointer to a \c{drawing} structure
1971(a \q{drawing object}). These objects are supplied as parameters to
1972the back end's \cw{redraw()} and \cw{print()} functions.
1973
1974In fact these global functions are not implemented directly by the
1975front end; instead, they are implemented centrally in \c{drawing.c}
1976and form a small piece of middleware. The drawing API as supplied by
1977the front end is a structure containing a set of function pointers,
1978plus a \cq{void *} handle which is passed to each of those
1979functions. This enables a single front end to switch between
1980multiple implementations of the drawing API if necessary. For
1981example, the Windows API supplies a printing mechanism integrated
1982into the same GDI which deals with drawing in windows, and therefore
1983the same API implementation can handle both drawing and printing;
1984but on Unix, the most common way for applications to print is by
1985producing PostScript output directly, and although it would be
1986\e{possible} to write a single (say) \cw{draw_rect()} function which
1987checked a global flag to decide whether to do GTK drawing operations
1988or output PostScript to a file, it's much nicer to have two separate
1989functions and switch between them as appropriate.
1990
1991When drawing, the puzzle window is indexed by pixel coordinates,
1992with the top left pixel defined as \cw{(0,0)} and the bottom right
1993pixel \cw{(w-1,h-1)}, where \c{w} and \c{h} are the width and height
1994values returned by the back end function \cw{compute_size()}
1995(\k{backend-compute-size}).
1996
1997When printing, the puzzle's print area is indexed in exactly the
1998same way (with an arbitrary tile size provided by the printing
1999module \c{printing.c}), to facilitate sharing of code between the
2000drawing and printing routines. However, when printing, puzzles may
2001no longer assume that the coordinate unit has any relationship to a
2002pixel; the printer's actual resolution might very well not even be
2003known at print time, so the coordinate unit might be smaller or
2004larger than a pixel. Puzzles' print functions should restrict
2005themselves to drawing geometric shapes rather than fiddly pixel
2006manipulation.
2007
2008\e{Puzzles' redraw functions may assume that the surface they draw
2009on is persistent}. It is the responsibility of every front end to
2010preserve the puzzle's window contents in the face of GUI window
2011expose issues and similar. It is not permissible to request that the
2012back end redraw any part of a window that it has already drawn,
2013unless something has actually changed as a result of making moves in
2014the puzzle.
2015
2016Most front ends accomplish this by having the drawing routines draw
2017on a stored bitmap rather than directly on the window, and copying
2018the bitmap to the window every time a part of the window needs to be
2019redrawn. Therefore, it is vitally important that whenever the back
2020end does any drawing it informs the front end of which parts of the
2021window it has accessed, and hence which parts need repainting. This
2022is done by calling \cw{draw_update()} (\k{drawing-draw-update}).
2023
2024Persistence of old drawing is convenient. However, a puzzle should
2025be very careful about how it updates its drawing area. The problem
2026is that some front ends do anti-aliased drawing: rather than simply
2027choosing between leaving each pixel untouched or painting it a
2028specified colour, an antialiased drawing function will \e{blend} the
2029original and new colours in pixels at a figure's boundary according
2030to the proportion of the pixel occupied by the figure (probably
2031modified by some heuristic fudge factors). All of this produces a
2032smoother appearance for curves and diagonal lines.
2033
2034An unfortunate effect of drawing an anti-aliased figure repeatedly
2035is that the pixels around the figure's boundary come steadily more
2036saturated with \q{ink} and the boundary appears to \q{spread out}.
2037Worse, redrawing a figure in a different colour won't fully paint
2038over the old boundary pixels, so the end result is a rather ugly
2039smudge.
2040
2041A good strategy to avoid unpleasant anti-aliasing artifacts is to
2042identify a number of rectangular areas which need to be redrawn,
2043clear them to the background colour, and then redraw their contents
2044from scratch, being careful all the while not to stray beyond the
2045boundaries of the original rectangles. The \cw{clip()} function
2046(\k{drawing-clip}) comes in very handy here. Games based on a square
2047grid can often do this fairly easily. Other games may need to be
2048somewhat more careful. For example, Loopy's redraw function first
2049identifies portions of the display which need to be updated. Then,
2050if the changes are fairly well localised, it clears and redraws a
2051rectangle containing each changed area. Otherwise, it gives up and
2052redraws the entire grid from scratch.
2053
2054It is possible to avoid clearing to background and redrawing from
2055scratch if one is very careful about which drawing functions one
2056uses: if a function is documented as not anti-aliasing under some
2057circumstances, you can rely on each pixel in a drawing either being
2058left entirely alone or being set to the requested colour, with no
2059blending being performed.
2060
2061In the following sections I first discuss the drawing API as seen by
2062the back end, and then the \e{almost} identical function-pointer
2063form seen by the front end.
2064
2065\H{drawing-backend} Drawing API as seen by the back end
2066
2067This section documents the back-end drawing API, in the form of
2068functions which take a \c{drawing} object as an argument.
2069
2070\S{drawing-draw-rect} \cw{draw_rect()}
2071
2072\c void draw_rect(drawing *dr, int x, int y, int w, int h,
2073\c int colour);
2074
2075Draws a filled rectangle in the puzzle window.
2076
2077\c{x} and \c{y} give the coordinates of the top left pixel of the
2078rectangle. \c{w} and \c{h} give its width and height. Thus, the
2079horizontal extent of the rectangle runs from \c{x} to \c{x+w-1}
2080inclusive, and the vertical extent from \c{y} to \c{y+h-1}
2081inclusive.
2082
2083\c{colour} is an integer index into the colours array returned by
2084the back end function \cw{colours()} (\k{backend-colours}).
2085
2086There is no separate pixel-plotting function. If you want to plot a
2087single pixel, the approved method is to use \cw{draw_rect()} with
2088width and height set to 1.
2089
2090Unlike many of the other drawing functions, this function is
2091guaranteed to be pixel-perfect: the rectangle will be sharply
2092defined and not anti-aliased or anything like that.
2093
2094This function may be used for both drawing and printing.
2095
2096\S{drawing-draw-rect-outline} \cw{draw_rect_outline()}
2097
2098\c void draw_rect_outline(drawing *dr, int x, int y, int w, int h,
2099\c int colour);
2100
2101Draws an outline rectangle in the puzzle window.
2102
2103\c{x} and \c{y} give the coordinates of the top left pixel of the
2104rectangle. \c{w} and \c{h} give its width and height. Thus, the
2105horizontal extent of the rectangle runs from \c{x} to \c{x+w-1}
2106inclusive, and the vertical extent from \c{y} to \c{y+h-1}
2107inclusive.
2108
2109\c{colour} is an integer index into the colours array returned by
2110the back end function \cw{colours()} (\k{backend-colours}).
2111
2112From a back end perspective, this function may be considered to be
2113part of the drawing API. However, front ends are not required to
2114implement it, since it is actually implemented centrally (in
2115\cw{misc.c}) as a wrapper on \cw{draw_polygon()}.
2116
2117This function may be used for both drawing and printing.
2118
2119\S{drawing-draw-rect-corner} \cw{draw_rect_corners()}
2120
2121\c void draw_rect_corners(drawing *dr, int cx, int cy, int r, int col);
2122
2123Draws four L-shapes at the corners of a square, in the manner of a
2124target reticule. This is a convenience function for back ends to use
2125to display a keyboard cursor (if they want one in that style).
2126
2127\c{cx} and \c{cy} give the coordinates of the centre of the square.
2128\c{r} is half the side length of the square, so that the corners are
2129at \cw{(cx-r,cy-r)}, \cw{(cx+r,cy-r)}, \cw{(cx-r,cy+r)} and
2130\cw{(cx+r,cy+r)}.
2131
2132\c{colour} is an integer index into the colours array returned by
2133the back end function \cw{colours()} (\k{backend-colours}).
2134
2135\S{drawing-draw-line} \cw{draw_line()}
2136
2137\c void draw_line(drawing *dr, int x1, int y1, int x2, int y2,
2138\c int colour);
2139
2140Draws a straight line in the puzzle window.
2141
2142\c{x1} and \c{y1} give the coordinates of one end of the line.
2143\c{x2} and \c{y2} give the coordinates of the other end. The line
2144drawn includes both those points.
2145
2146\c{colour} is an integer index into the colours array returned by
2147the back end function \cw{colours()} (\k{backend-colours}).
2148
2149Some platforms may perform anti-aliasing on this function.
2150Therefore, do not assume that you can erase a line by drawing the
2151same line over it in the background colour; anti-aliasing might lead
2152to perceptible ghost artefacts around the vanished line. Horizontal
2153and vertical lines, however, are pixel-perfect and not anti-aliased.
2154
2155This function may be used for both drawing and printing.
2156
2157\S{drawing-draw-polygon} \cw{draw_polygon()}
2158
2159\c void draw_polygon(drawing *dr, const int *coords, int npoints,
2160\c int fillcolour, int outlinecolour);
2161
2162Draws an outlined or filled polygon in the puzzle window.
2163
2164\c{coords} is an array of \cw{(2*npoints)} integers, containing the
2165\c{x} and \c{y} coordinates of \c{npoints} vertices.
2166
2167\c{fillcolour} and \c{outlinecolour} are integer indices into the
2168colours array returned by the back end function \cw{colours()}
2169(\k{backend-colours}). \c{fillcolour} may also be \cw{-1} to
2170indicate that the polygon should be outlined only.
2171
2172The polygon defined by the specified list of vertices is first
2173filled in \c{fillcolour}, if specified, and then outlined in
2174\c{outlinecolour}.
2175
2176\c{outlinecolour} may \e{not} be \cw{-1}; it must be a valid colour
2177(and front ends are permitted to enforce this by assertion). This is
2178because different platforms disagree on whether a filled polygon
2179should include its boundary line or not, so drawing \e{only} a
2180filled polygon would have non-portable effects. If you want your
2181filled polygon not to have a visible outline, you must set
2182\c{outlinecolour} to the same as \c{fillcolour}.
2183
2184Some platforms may perform anti-aliasing on this function.
2185Therefore, do not assume that you can erase a polygon by drawing the
2186same polygon over it in the background colour. Also, be prepared for
2187the polygon to extend a pixel beyond its obvious bounding box as a
2188result of this; if you really need it not to do this to avoid
2189interfering with other delicate graphics, you should probably use
2190\cw{clip()} (\k{drawing-clip}). You can rely on horizontal and
2191vertical lines not being anti-aliased.
2192
2193This function may be used for both drawing and printing.
2194
2195\S{drawing-draw-circle} \cw{draw_circle()}
2196
2197\c void draw_circle(drawing *dr, int cx, int cy, int radius,
2198\c int fillcolour, int outlinecolour);
2199
2200Draws an outlined or filled circle in the puzzle window.
2201
2202\c{cx} and \c{cy} give the coordinates of the centre of the circle.
2203\c{radius} gives its radius. The total horizontal pixel extent of
2204the circle is from \c{cx-radius+1} to \c{cx+radius-1} inclusive, and
2205the vertical extent similarly around \c{cy}.
2206
2207\c{fillcolour} and \c{outlinecolour} are integer indices into the
2208colours array returned by the back end function \cw{colours()}
2209(\k{backend-colours}). \c{fillcolour} may also be \cw{-1} to
2210indicate that the circle should be outlined only.
2211
2212The circle is first filled in \c{fillcolour}, if specified, and then
2213outlined in \c{outlinecolour}.
2214
2215\c{outlinecolour} may \e{not} be \cw{-1}; it must be a valid colour
2216(and front ends are permitted to enforce this by assertion). This is
2217because different platforms disagree on whether a filled circle
2218should include its boundary line or not, so drawing \e{only} a
2219filled circle would have non-portable effects. If you want your
2220filled circle not to have a visible outline, you must set
2221\c{outlinecolour} to the same as \c{fillcolour}.
2222
2223Some platforms may perform anti-aliasing on this function.
2224Therefore, do not assume that you can erase a circle by drawing the
2225same circle over it in the background colour. Also, be prepared for
2226the circle to extend a pixel beyond its obvious bounding box as a
2227result of this; if you really need it not to do this to avoid
2228interfering with other delicate graphics, you should probably use
2229\cw{clip()} (\k{drawing-clip}).
2230
2231This function may be used for both drawing and printing.
2232
2233\S{drawing-draw-thick-line} \cw{draw_thick_line()}
2234
2235\c void draw_thick_line(drawing *dr, float thickness,
2236\c float x1, float y1, float x2, float y2,
2237\c int colour)
2238
2239Draws a line in the puzzle window, giving control over the line's
2240thickness.
2241
2242\c{x1} and \c{y1} give the coordinates of one end of the line.
2243\c{x2} and \c{y2} give the coordinates of the other end.
2244\c{thickness} gives the thickness of the line, in pixels.
2245
2246Note that the coordinates and thickness are floating-point: the
2247continuous coordinate system is in effect here. It's important to
2248be able to address points with better-than-pixel precision in this
2249case, because one can't otherwise properly express the endpoints of
2250lines with both odd and even thicknesses.
2251
2252Some platforms may perform anti-aliasing on this function. The
2253precise pixels affected by a thick-line drawing operation may vary
2254between platforms, and no particular guarantees are provided.
2255Indeed, even horizontal or vertical lines may be anti-aliased.
2256
2257This function may be used for both drawing and printing.
2258
2259If the specified thickness is less than 1.0, 1.0 is used.
2260This ensures that thin lines are visible even at small scales.
2261
2262\S{drawing-draw-text} \cw{draw_text()}
2263
2264\c void draw_text(drawing *dr, int x, int y, int fonttype,
2265\c int fontsize, int align, int colour,
2266\c const char *text);
2267
2268Draws text in the puzzle window.
2269
2270\c{x} and \c{y} give the coordinates of a point. The relation of
2271this point to the location of the text is specified by \c{align},
2272which is a bitwise OR of horizontal and vertical alignment flags:
2273
2274\dt \cw{ALIGN_VNORMAL}
2275
2276\dd Indicates that \c{y} is aligned with the baseline of the text.
2277
2278\dt \cw{ALIGN_VCENTRE}
2279
2280\dd Indicates that \c{y} is aligned with the vertical centre of the
2281text. (In fact, it's aligned with the vertical centre of normal
2282\e{capitalised} text: displaying two pieces of text with
2283\cw{ALIGN_VCENTRE} at the same \cw{y}-coordinate will cause their
2284baselines to be aligned with one another, even if one is an ascender
2285and the other a descender.)
2286
2287\dt \cw{ALIGN_HLEFT}
2288
2289\dd Indicates that \c{x} is aligned with the left-hand end of the
2290text.
2291
2292\dt \cw{ALIGN_HCENTRE}
2293
2294\dd Indicates that \c{x} is aligned with the horizontal centre of
2295the text.
2296
2297\dt \cw{ALIGN_HRIGHT}
2298
2299\dd Indicates that \c{x} is aligned with the right-hand end of the
2300text.
2301
2302\c{fonttype} is either \cw{FONT_FIXED} or \cw{FONT_VARIABLE}, for a
2303monospaced or proportional font respectively. (No more detail than
2304that may be specified; it would only lead to portability issues
2305between different platforms.)
2306
2307\c{fontsize} is the desired size, in pixels, of the text. This size
2308corresponds to the overall point size of the text, not to any
2309internal dimension such as the cap-height.
2310
2311\c{colour} is an integer index into the colours array returned by
2312the back end function \cw{colours()} (\k{backend-colours}).
2313
2314This function may be used for both drawing and printing.
2315
2316The character set used to encode the text passed to this function is
2317specified \e{by the drawing object}, although it must be a superset
2318of ASCII. If a puzzle wants to display text that is not contained in
2319ASCII, it should use the \cw{text_fallback()} function
2320(\k{drawing-text-fallback}) to query the drawing object for an
2321appropriate representation of the characters it wants.
2322
2323\S{drawing-text-fallback} \cw{text_fallback()}
2324
2325\c char *text_fallback(drawing *dr, const char *const *strings,
2326\c int nstrings);
2327
2328This function is used to request a translation of UTF-8 text into
2329whatever character encoding is expected by the drawing object's
2330implementation of \cw{draw_text()}.
2331
2332The input is a list of strings encoded in UTF-8: \cw{nstrings} gives
2333the number of strings in the list, and \cw{strings[0]},
2334\cw{strings[1]}, ..., \cw{strings[nstrings-1]} are the strings
2335themselves.
2336
2337The returned string (which is dynamically allocated and must be
2338freed when finished with) is derived from the first string in the
2339list that the drawing object expects to be able to display reliably;
2340it will consist of that string translated into the character set
2341expected by \cw{draw_text()}.
2342
2343Drawing implementations are not required to handle anything outside
2344ASCII, but are permitted to assume that \e{some} string will be
2345successfully translated. So every call to this function must include
2346a string somewhere in the list (presumably the last element) which
2347consists of nothing but ASCII, to be used by any front end which
2348cannot handle anything else.
2349
2350For example, if a puzzle wished to display a string including a
2351multiplication sign (U+00D7 in Unicode, represented by the bytes C3
235297 in UTF-8), it might do something like this:
2353
2354\c static const char *const times_signs[] = { "\xC3\x97", "x" };
2355\c char *times_sign = text_fallback(dr, times_signs, 2);
2356\c sprintf(buffer, "%d%s%d", width, times_sign, height);
2357\c sfree(times_sign);
2358\c draw_text(dr, x, y, font, size, align, colour, buffer);
2359\c sfree(buffer);
2360
2361which would draw a string with a times sign in the middle on
2362platforms that support it, and fall back to a simple ASCII \cq{x}
2363where there was no alternative.
2364
2365\S{drawing-clip} \cw{clip()}
2366
2367\c void clip(drawing *dr, int x, int y, int w, int h);
2368
2369Establishes a clipping rectangle in the puzzle window.
2370
2371\c{x} and \c{y} give the coordinates of the top left pixel of the
2372clipping rectangle. \c{w} and \c{h} give its width and height. Thus,
2373the horizontal extent of the rectangle runs from \c{x} to \c{x+w-1}
2374inclusive, and the vertical extent from \c{y} to \c{y+h-1}
2375inclusive. (These are exactly the same semantics as
2376\cw{draw_rect()}.)
2377
2378After this call, no drawing operation will affect anything outside
2379the specified rectangle. The effect can be reversed by calling
2380\cw{unclip()} (\k{drawing-unclip}). The clipping rectangle is
2381pixel-perfect: pixels within the rectangle are affected as usual by
2382drawing functions; pixels outside are completely untouched.
2383
2384Back ends should not assume that a clipping rectangle will be
2385automatically cleared up by the front end if it's left lying around;
2386that might work on current front ends, but shouldn't be relied upon.
2387Always explicitly call \cw{unclip()}.
2388
2389This function may be used for both drawing and printing.
2390
2391\S{drawing-unclip} \cw{unclip()}
2392
2393\c void unclip(drawing *dr);
2394
2395Reverts the effect of a previous call to \cw{clip()}. After this
2396call, all drawing operations will be able to affect the entire
2397puzzle window again.
2398
2399This function may be used for both drawing and printing.
2400
2401\S{drawing-draw-update} \cw{draw_update()}
2402
2403\c void draw_update(drawing *dr, int x, int y, int w, int h);
2404
2405Informs the front end that a rectangular portion of the puzzle
2406window has been drawn on and needs to be updated.
2407
2408\c{x} and \c{y} give the coordinates of the top left pixel of the
2409update rectangle. \c{w} and \c{h} give its width and height. Thus,
2410the horizontal extent of the rectangle runs from \c{x} to \c{x+w-1}
2411inclusive, and the vertical extent from \c{y} to \c{y+h-1}
2412inclusive. (These are exactly the same semantics as
2413\cw{draw_rect()}.)
2414
2415The back end redraw function \e{must} call this function to report
2416any changes it has made to the window. Otherwise, those changes may
2417not become immediately visible, and may then appear at an
2418unpredictable subsequent time such as the next time the window is
2419covered and re-exposed.
2420
2421This function is only important when drawing. It may be called when
2422printing as well, but doing so is not compulsory, and has no effect.
2423(So if you have a shared piece of code between the drawing and
2424printing routines, that code may safely call \cw{draw_update()}.)
2425
2426\S{drawing-status-bar} \cw{status_bar()}
2427
2428\c void status_bar(drawing *dr, const char *text);
2429
2430Sets the text in the game's status bar to \c{text}. The text is copied
2431from the supplied buffer, so the caller is free to deallocate or
2432modify the buffer after use.
2433
2434(This function is not exactly a \e{drawing} function, but it shares
2435with the drawing API the property that it may only be called from
2436within the back end redraw function. And it's implemented by front
2437ends via the \c{drawing_api} function pointer table. So this is the
2438best place to document it.)
2439
2440The supplied text is filtered through the mid-end for optional
2441rewriting before being passed on to the front end; the mid-end will
2442prepend the current game time if the game is timed (and may in
2443future perform other rewriting if it seems like a good idea).
2444
2445This function is for drawing only; it must never be called during
2446printing.
2447
2448\S{drawing-blitter} Blitter functions
2449
2450This section describes a group of related functions which save and
2451restore a section of the puzzle window. This is most commonly used
2452to implement user interfaces involving dragging a puzzle element
2453around the window: at the end of each call to \cw{redraw()}, if an
2454object is currently being dragged, the back end saves the window
2455contents under that location and then draws the dragged object, and
2456at the start of the next \cw{redraw()} the first thing it does is to
2457restore the background.
2458
2459The front end defines an opaque type called a \c{blitter}, which is
2460capable of storing a rectangular area of a specified size.
2461
2462Blitter functions are for drawing only; they must never be called
2463during printing.
2464
2465\S2{drawing-blitter-new} \cw{blitter_new()}
2466
2467\c blitter *blitter_new(drawing *dr, int w, int h);
2468
2469Creates a new blitter object which stores a rectangle of size \c{w}
2470by \c{h} pixels. Returns a pointer to the blitter object.
2471
2472Blitter objects are best stored in the \c{game_drawstate}. A good
2473time to create them is in the \cw{set_size()} function
2474(\k{backend-set-size}), since it is at this point that you first
2475know how big a rectangle they will need to save.
2476
2477\S2{drawing-blitter-free} \cw{blitter_free()}
2478
2479\c void blitter_free(drawing *dr, blitter *bl);
2480
2481Disposes of a blitter object. Best called in \cw{free_drawstate()}.
2482(However, check that the blitter object is not \cw{NULL} before
2483attempting to free it; it is possible that a draw state might be
2484created and freed without ever having \cw{set_size()} called on it
2485in between.)
2486
2487\S2{drawing-blitter-save} \cw{blitter_save()}
2488
2489\c void blitter_save(drawing *dr, blitter *bl, int x, int y);
2490
2491This is a true drawing API function, in that it may only be called
2492from within the game redraw routine. It saves a rectangular portion
2493of the puzzle window into the specified blitter object.
2494
2495\c{x} and \c{y} give the coordinates of the top left corner of the
2496saved rectangle. The rectangle's width and height are the ones
2497specified when the blitter object was created.
2498
2499This function is required to cope and do the right thing if \c{x}
2500and \c{y} are out of range. (The right thing probably means saving
2501whatever part of the blitter rectangle overlaps with the visible
2502area of the puzzle window.)
2503
2504\S2{drawing-blitter-load} \cw{blitter_load()}
2505
2506\c void blitter_load(drawing *dr, blitter *bl, int x, int y);
2507
2508This is a true drawing API function, in that it may only be called
2509from within the game redraw routine. It restores a rectangular
2510portion of the puzzle window from the specified blitter object.
2511
2512\c{x} and \c{y} give the coordinates of the top left corner of the
2513rectangle to be restored. The rectangle's width and height are the
2514ones specified when the blitter object was created.
2515
2516Alternatively, you can specify both \c{x} and \c{y} as the special
2517value \cw{BLITTER_FROMSAVED}, in which case the rectangle will be
2518restored to exactly where it was saved from. (This is probably what
2519you want to do almost all the time, if you're using blitters to
2520implement draggable puzzle elements.)
2521
2522This function is required to cope and do the right thing if \c{x}
2523and \c{y} (or the equivalent ones saved in the blitter) are out of
2524range. (The right thing probably means restoring whatever part of
2525the blitter rectangle overlaps with the visible area of the puzzle
2526window.)
2527
2528If this function is called on a blitter which had previously been
2529saved from a partially out-of-range rectangle, then the parts of the
2530saved bitmap which were not visible at save time are undefined. If
2531the blitter is restored to a different position so as to make those
2532parts visible, the effect on the drawing area is undefined.
2533
2534\S{print-mono-colour} \cw{print_mono_colour()}
2535
2536\c int print_mono_colour(drawing *dr, int grey);
2537
2538This function allocates a colour index for a simple monochrome
2539colour during printing.
2540
2541\c{grey} must be 0 or 1. If \c{grey} is 0, the colour returned is
2542black; if \c{grey} is 1, the colour is white.
2543
2544\S{print-grey-colour} \cw{print_grey_colour()}
2545
2546\c int print_grey_colour(drawing *dr, float grey);
2547
2548This function allocates a colour index for a grey-scale colour
2549during printing.
2550
2551\c{grey} may be any number between 0 (black) and 1 (white); for
2552example, 0.5 indicates a medium grey.
2553
2554The chosen colour will be rendered to the limits of the printer's
2555halftoning capability.
2556
2557\S{print-hatched-colour} \cw{print_hatched_colour()}
2558
2559\c int print_hatched_colour(drawing *dr, int hatch);
2560
2561This function allocates a colour index which does not represent a
2562literal \e{colour}. Instead, regions shaded in this colour will be
2563hatched with parallel lines. The \c{hatch} parameter defines what
2564type of hatching should be used in place of this colour:
2565
2566\dt \cw{HATCH_SLASH}
2567
2568\dd This colour will be hatched by lines slanting to the right at 45
2569degrees.
2570
2571\dt \cw{HATCH_BACKSLASH}
2572
2573\dd This colour will be hatched by lines slanting to the left at 45
2574degrees.
2575
2576\dt \cw{HATCH_HORIZ}
2577
2578\dd This colour will be hatched by horizontal lines.
2579
2580\dt \cw{HATCH_VERT}
2581
2582\dd This colour will be hatched by vertical lines.
2583
2584\dt \cw{HATCH_PLUS}
2585
2586\dd This colour will be hatched by criss-crossing horizontal and
2587vertical lines.
2588
2589\dt \cw{HATCH_X}
2590
2591\dd This colour will be hatched by criss-crossing diagonal lines.
2592
2593Colours defined to use hatching may not be used for drawing lines or
2594text; they may only be used for filling areas. That is, they may be
2595used as the \c{fillcolour} parameter to \cw{draw_circle()} and
2596\cw{draw_polygon()}, and as the colour parameter to
2597\cw{draw_rect()}, but may not be used as the \c{outlinecolour}
2598parameter to \cw{draw_circle()} or \cw{draw_polygon()}, or with
2599\cw{draw_line()} or \cw{draw_text()}.
2600
2601\S{print-rgb-mono-colour} \cw{print_rgb_mono_colour()}
2602
2603\c int print_rgb_mono_colour(drawing *dr, float r, float g,
2604\c float b, float grey);
2605
2606This function allocates a colour index for a fully specified RGB
2607colour during printing.
2608
2609\c{r}, \c{g} and \c{b} may each be anywhere in the range from 0 to 1.
2610
2611If printing in black and white only, these values will be ignored,
2612and either pure black or pure white will be used instead, according
2613to the \q{grey} parameter. (The fallback colour is the same as the
2614one which would be allocated by \cw{print_mono_colour(grey)}.)
2615
2616\S{print-rgb-grey-colour} \cw{print_rgb_grey_colour()}
2617
2618\c int print_rgb_grey_colour(drawing *dr, float r, float g,
2619\c float b, float grey);
2620
2621This function allocates a colour index for a fully specified RGB
2622colour during printing.
2623
2624\c{r}, \c{g} and \c{b} may each be anywhere in the range from 0 to 1.
2625
2626If printing in black and white only, these values will be ignored,
2627and a shade of grey given by the \c{grey} parameter will be used
2628instead. (The fallback colour is the same as the one which would be
2629allocated by \cw{print_grey_colour(grey)}.)
2630
2631\S{print-rgb-hatched-colour} \cw{print_rgb_hatched_colour()}
2632
2633\c int print_rgb_hatched_colour(drawing *dr, float r, float g,
2634\c float b, float hatched);
2635
2636This function allocates a colour index for a fully specified RGB
2637colour during printing.
2638
2639\c{r}, \c{g} and \c{b} may each be anywhere in the range from 0 to 1.
2640
2641If printing in black and white only, these values will be ignored,
2642and a form of cross-hatching given by the \c{hatch} parameter will
2643be used instead; see \k{print-hatched-colour} for the possible
2644values of this parameter. (The fallback colour is the same as the
2645one which would be allocated by \cw{print_hatched_colour(hatch)}.)
2646
2647\S{print-line-width} \cw{print_line_width()}
2648
2649\c void print_line_width(drawing *dr, int width);
2650
2651This function is called to set the thickness of lines drawn during
2652printing. It is meaningless in drawing: all lines drawn by
2653\cw{draw_line()}, \cw{draw_circle} and \cw{draw_polygon()} are one
2654pixel in thickness. However, in printing there is no clear
2655definition of a pixel and so line widths must be explicitly
2656specified.
2657
2658The line width is specified in the usual coordinate system. Note,
2659however, that it is a hint only: the central printing system may
2660choose to vary line thicknesses at user request or due to printer
2661capabilities.
2662
2663\S{print-line-dotted} \cw{print_line_dotted()}
2664
2665\c void print_line_dotted(drawing *dr, bool dotted);
2666
2667This function is called to toggle the drawing of dotted lines during
2668printing. It is not supported during drawing.
2669
2670Setting \cq{dotted} to \cw{true} means that future lines drawn by
2671\cw{draw_line()}, \cw{draw_circle} and \cw{draw_polygon()} will be
2672dotted. Setting it to \cw{false} means that they will be solid.
2673
2674Some front ends may impose restrictions on the width of dotted
2675lines. Asking for a dotted line via this front end will override any
2676line width request if the front end requires it.
2677
2678\H{drawing-frontend} The drawing API as implemented by the front end
2679
2680This section describes the drawing API in the function-pointer form
2681in which it is implemented by a front end.
2682
2683(It isn't only platform-specific front ends which implement this
2684API; the platform-independent module \c{ps.c} also provides an
2685implementation of it which outputs PostScript. Thus, any platform
2686which wants to do PS printing can do so with minimum fuss.)
2687
2688The following entries all describe function pointer fields in a
2689structure called \c{drawing_api}. Each of the functions takes a
2690\cq{void *} context pointer, which it should internally cast back to
2691a more useful type. Thus, a drawing \e{object} (\c{drawing *)}
2692suitable for passing to the back end redraw or printing functions
2693is constructed by passing a \c{drawing_api} and a \cq{void *} to the
2694function \cw{drawing_new()} (see \k{drawing-new}).
2695
2696\S{drawingapi-draw-text} \cw{draw_text()}
2697
2698\c void (*draw_text)(void *handle, int x, int y, int fonttype,
2699\c int fontsize, int align, int colour,
2700\c const char *text);
2701
2702This function behaves exactly like the back end \cw{draw_text()}
2703function; see \k{drawing-draw-text}.
2704
2705\S{drawingapi-draw-rect} \cw{draw_rect()}
2706
2707\c void (*draw_rect)(void *handle, int x, int y, int w, int h,
2708\c int colour);
2709
2710This function behaves exactly like the back end \cw{draw_rect()}
2711function; see \k{drawing-draw-rect}.
2712
2713\S{drawingapi-draw-line} \cw{draw_line()}
2714
2715\c void (*draw_line)(void *handle, int x1, int y1, int x2, int y2,
2716\c int colour);
2717
2718This function behaves exactly like the back end \cw{draw_line()}
2719function; see \k{drawing-draw-line}.
2720
2721\S{drawingapi-draw-polygon} \cw{draw_polygon()}
2722
2723\c void (*draw_polygon)(void *handle, const int *coords, int npoints,
2724\c int fillcolour, int outlinecolour);
2725
2726This function behaves exactly like the back end \cw{draw_polygon()}
2727function; see \k{drawing-draw-polygon}.
2728
2729\S{drawingapi-draw-circle} \cw{draw_circle()}
2730
2731\c void (*draw_circle)(void *handle, int cx, int cy, int radius,
2732\c int fillcolour, int outlinecolour);
2733
2734This function behaves exactly like the back end \cw{draw_circle()}
2735function; see \k{drawing-draw-circle}.
2736
2737\S{drawingapi-draw-thick-line} \cw{draw_thick_line()}
2738
2739\c void draw_thick_line(drawing *dr, float thickness,
2740\c float x1, float y1, float x2, float y2,
2741\c int colour)
2742
2743This function behaves exactly like the back end
2744\cw{draw_thick_line()} function; see \k{drawing-draw-thick-line}.
2745
2746An implementation of this API which doesn't provide high-quality
2747rendering of thick lines is permitted to define this function
2748pointer to be \cw{NULL}. The middleware in \cw{drawing.c} will notice
2749and provide a low-quality alternative using \cw{draw_polygon()}.
2750
2751\S{drawingapi-draw-update} \cw{draw_update()}
2752
2753\c void (*draw_update)(void *handle, int x, int y, int w, int h);
2754
2755This function behaves exactly like the back end \cw{draw_update()}
2756function; see \k{drawing-draw-update}.
2757
2758An implementation of this API which only supports printing is
2759permitted to define this function pointer to be \cw{NULL} rather
2760than bothering to define an empty function. The middleware in
2761\cw{drawing.c} will notice and avoid calling it.
2762
2763\S{drawingapi-clip} \cw{clip()}
2764
2765\c void (*clip)(void *handle, int x, int y, int w, int h);
2766
2767This function behaves exactly like the back end \cw{clip()}
2768function; see \k{drawing-clip}.
2769
2770\S{drawingapi-unclip} \cw{unclip()}
2771
2772\c void (*unclip)(void *handle);
2773
2774This function behaves exactly like the back end \cw{unclip()}
2775function; see \k{drawing-unclip}.
2776
2777\S{drawingapi-start-draw} \cw{start_draw()}
2778
2779\c void (*start_draw)(void *handle);
2780
2781This function is called at the start of drawing. It allows the front
2782end to initialise any temporary data required to draw with, such as
2783device contexts.
2784
2785Implementations of this API which do not provide drawing services
2786may define this function pointer to be \cw{NULL}; it will never be
2787called unless drawing is attempted.
2788
2789\S{drawingapi-end-draw} \cw{end_draw()}
2790
2791\c void (*end_draw)(void *handle);
2792
2793This function is called at the end of drawing. It allows the front
2794end to do cleanup tasks such as deallocating device contexts and
2795scheduling appropriate GUI redraw events.
2796
2797Implementations of this API which do not provide drawing services
2798may define this function pointer to be \cw{NULL}; it will never be
2799called unless drawing is attempted.
2800
2801\S{drawingapi-status-bar} \cw{status_bar()}
2802
2803\c void (*status_bar)(void *handle, const char *text);
2804
2805This function behaves exactly like the back end \cw{status_bar()}
2806function; see \k{drawing-status-bar}.
2807
2808Front ends implementing this function need not worry about it being
2809called repeatedly with the same text; the middleware code in
2810\cw{status_bar()} will take care of this.
2811
2812Implementations of this API which do not provide drawing services
2813may define this function pointer to be \cw{NULL}; it will never be
2814called unless drawing is attempted.
2815
2816\S{drawingapi-blitter-new} \cw{blitter_new()}
2817
2818\c blitter *(*blitter_new)(void *handle, int w, int h);
2819
2820This function behaves exactly like the back end \cw{blitter_new()}
2821function; see \k{drawing-blitter-new}.
2822
2823Implementations of this API which do not provide drawing services
2824may define this function pointer to be \cw{NULL}; it will never be
2825called unless drawing is attempted.
2826
2827\S{drawingapi-blitter-free} \cw{blitter_free()}
2828
2829\c void (*blitter_free)(void *handle, blitter *bl);
2830
2831This function behaves exactly like the back end \cw{blitter_free()}
2832function; see \k{drawing-blitter-free}.
2833
2834Implementations of this API which do not provide drawing services
2835may define this function pointer to be \cw{NULL}; it will never be
2836called unless drawing is attempted.
2837
2838\S{drawingapi-blitter-save} \cw{blitter_save()}
2839
2840\c void (*blitter_save)(void *handle, blitter *bl, int x, int y);
2841
2842This function behaves exactly like the back end \cw{blitter_save()}
2843function; see \k{drawing-blitter-save}.
2844
2845Implementations of this API which do not provide drawing services
2846may define this function pointer to be \cw{NULL}; it will never be
2847called unless drawing is attempted.
2848
2849\S{drawingapi-blitter-load} \cw{blitter_load()}
2850
2851\c void (*blitter_load)(void *handle, blitter *bl, int x, int y);
2852
2853This function behaves exactly like the back end \cw{blitter_load()}
2854function; see \k{drawing-blitter-load}.
2855
2856Implementations of this API which do not provide drawing services
2857may define this function pointer to be \cw{NULL}; it will never be
2858called unless drawing is attempted.
2859
2860\S{drawingapi-begin-doc} \cw{begin_doc()}
2861
2862\c void (*begin_doc)(void *handle, int pages);
2863
2864This function is called at the beginning of a printing run. It gives
2865the front end an opportunity to initialise any required printing
2866subsystem. It also provides the number of pages in advance.
2867
2868Implementations of this API which do not provide printing services
2869may define this function pointer to be \cw{NULL}; it will never be
2870called unless printing is attempted.
2871
2872\S{drawingapi-begin-page} \cw{begin_page()}
2873
2874\c void (*begin_page)(void *handle, int number);
2875
2876This function is called during printing, at the beginning of each
2877page. It gives the page number (numbered from 1 rather than 0, so
2878suitable for use in user-visible contexts).
2879
2880Implementations of this API which do not provide printing services
2881may define this function pointer to be \cw{NULL}; it will never be
2882called unless printing is attempted.
2883
2884\S{drawingapi-begin-puzzle} \cw{begin_puzzle()}
2885
2886\c void (*begin_puzzle)(void *handle, float xm, float xc,
2887\c float ym, float yc, int pw, int ph, float wmm);
2888
2889This function is called during printing, just before printing a
2890single puzzle on a page. It specifies the size and location of the
2891puzzle on the page.
2892
2893\c{xm} and \c{xc} specify the horizontal position of the puzzle on
2894the page, as a linear function of the page width. The front end is
2895expected to multiply the page width by \c{xm}, add \c{xc} (measured
2896in millimetres), and use the resulting x-coordinate as the left edge
2897of the puzzle.
2898
2899Similarly, \c{ym} and \c{yc} specify the vertical position of the
2900puzzle as a function of the page height: the page height times
2901\c{ym}, plus \c{yc} millimetres, equals the desired distance from
2902the top of the page to the top of the puzzle.
2903
2904(This unwieldy mechanism is required because not all printing
2905systems can communicate the page size back to the software. The
2906PostScript back end, for example, writes out PS which determines the
2907page size at print time by means of calling \cq{clippath}, and
2908centres the puzzles within that. Thus, exactly the same PS file
2909works on A4 or on US Letter paper without needing local
2910configuration, which simplifies matters.)
2911
2912\cw{pw} and \cw{ph} give the size of the puzzle in drawing API
2913coordinates. The printing system will subsequently call the puzzle's
2914own print function, which will in turn call drawing API functions in
2915the expectation that an area \cw{pw} by \cw{ph} units is available
2916to draw the puzzle on.
2917
2918Finally, \cw{wmm} gives the desired width of the puzzle in
2919millimetres. (The aspect ratio is expected to be preserved, so if
2920the desired puzzle height is also needed then it can be computed as
2921\cw{wmm*ph/pw}.)
2922
2923Implementations of this API which do not provide printing services
2924may define this function pointer to be \cw{NULL}; it will never be
2925called unless printing is attempted.
2926
2927\S{drawingapi-end-puzzle} \cw{end_puzzle()}
2928
2929\c void (*end_puzzle)(void *handle);
2930
2931This function is called after the printing of a specific puzzle is
2932complete.
2933
2934Implementations of this API which do not provide printing services
2935may define this function pointer to be \cw{NULL}; it will never be
2936called unless printing is attempted.
2937
2938\S{drawingapi-end-page} \cw{end_page()}
2939
2940\c void (*end_page)(void *handle, int number);
2941
2942This function is called after the printing of a page is finished.
2943
2944Implementations of this API which do not provide printing services
2945may define this function pointer to be \cw{NULL}; it will never be
2946called unless printing is attempted.
2947
2948\S{drawingapi-end-doc} \cw{end_doc()}
2949
2950\c void (*end_doc)(void *handle);
2951
2952This function is called after the printing of the entire document is
2953finished. This is the moment to close files, send things to the
2954print spooler, or whatever the local convention is.
2955
2956Implementations of this API which do not provide printing services
2957may define this function pointer to be \cw{NULL}; it will never be
2958called unless printing is attempted.
2959
2960\S{drawingapi-line-width} \cw{line_width()}
2961
2962\c void (*line_width)(void *handle, float width);
2963
2964This function is called to set the line thickness, during printing
2965only. Note that the width is a \cw{float} here, where it was an
2966\cw{int} as seen by the back end. This is because \cw{drawing.c} may
2967have scaled it on the way past.
2968
2969However, the width is still specified in the same coordinate system
2970as the rest of the drawing.
2971
2972Implementations of this API which do not provide printing services
2973may define this function pointer to be \cw{NULL}; it will never be
2974called unless printing is attempted.
2975
2976\S{drawingapi-line-dotted} \cw{line_dotted()}
2977
2978\c void (*line_dotted)(void *handle, bool dotted);
2979
2980This function is called to toggle drawing of dotted lines, during
2981printing only.
2982
2983Implementations of this API which do not provide printing services
2984may define this function pointer to be \cw{NULL}; it will never be
2985called unless printing is attempted.
2986
2987\S{drawingapi-text-fallback} \cw{text_fallback()}
2988
2989\c char *(*text_fallback)(void *handle, const char *const *strings,
2990\c int nstrings);
2991
2992This function behaves exactly like the back end \cw{text_fallback()}
2993function; see \k{drawing-text-fallback}.
2994
2995Implementations of this API which do not support any characters
2996outside ASCII may define this function pointer to be \cw{NULL}, in
2997which case the central code in \cw{drawing.c} will provide a default
2998implementation.
2999
3000\H{drawingapi-frontend} The drawing API as called by the front end
3001
3002There are a small number of functions provided in \cw{drawing.c}
3003which the front end needs to \e{call}, rather than helping to
3004implement. They are described in this section.
3005
3006\S{drawing-new} \cw{drawing_new()}
3007
3008\c drawing *drawing_new(const drawing_api *api, midend *me,
3009\c void *handle);
3010
3011This function creates a drawing object. It is passed a
3012\c{drawing_api}, which is a structure containing nothing but
3013function pointers; and also a \cq{void *} handle. The handle is
3014passed back to each function pointer when it is called.
3015
3016The \c{midend} parameter is used for rewriting the status bar
3017contents: \cw{status_bar()} (see \k{drawing-status-bar}) has to call
3018a function in the mid-end which might rewrite the status bar text.
3019If the drawing object is to be used only for printing, or if the
3020game is known not to call \cw{status_bar()}, this parameter may be
3021\cw{NULL}.
3022
3023\S{drawing-free} \cw{drawing_free()}
3024
3025\c void drawing_free(drawing *dr);
3026
3027This function frees a drawing object. Note that the \cq{void *}
3028handle is not freed; if that needs cleaning up it must be done by
3029the front end.
3030
3031\S{drawing-print-get-colour} \cw{print_get_colour()}
3032
3033\c void print_get_colour(drawing *dr, int colour,
3034\c bool printing_in_colour,
3035\c int *hatch, float *r, float *g, float *b);
3036
3037This function is called by the implementations of the drawing API
3038functions when they are called in a printing context. It takes a
3039colour index as input, and returns the description of the colour as
3040requested by the back end.
3041
3042\c{printing_in_colour} is \cw{true} iff the implementation is printing
3043in colour. This will alter the results returned if the colour in
3044question was specified with a black-and-white fallback value.
3045
3046If the colour should be rendered by hatching, \c{*hatch} is filled
3047with the type of hatching desired. See \k{print-grey-colour} for
3048details of the values this integer can take.
3049
3050If the colour should be rendered as solid colour, \c{*hatch} is
3051given a negative value, and \c{*r}, \c{*g} and \c{*b} are filled
3052with the RGB values of the desired colour (if printing in colour),
3053or all filled with the grey-scale value (if printing in black and
3054white).
3055
3056\C{midend} The API provided by the mid-end
3057
3058This chapter documents the API provided by the mid-end to be called
3059by the front end. You probably only need to read this if you are a
3060front end implementor, i.e. you are porting Puzzles to a new
3061platform. If you're only interested in writing new puzzles, you can
3062safely skip this chapter.
3063
3064All the persistent state in the mid-end is encapsulated within a
3065\c{midend} structure, to facilitate having multiple mid-ends in any
3066port which supports multiple puzzle windows open simultaneously.
3067Each \c{midend} is intended to handle the contents of a single
3068puzzle window.
3069
3070\H{midend-new} \cw{midend_new()}
3071
3072\c midend *midend_new(frontend *fe, const game *ourgame,
3073\c const drawing_api *drapi, void *drhandle);
3074
3075Allocates and returns a new mid-end structure.
3076
3077The \c{fe} argument is stored in the mid-end. It will be used when
3078calling back to functions such as \cw{activate_timer()}
3079(\k{frontend-activate-timer}), and will be passed on to the back end
3080function \cw{colours()} (\k{backend-colours}).
3081
3082The parameters \c{drapi} and \c{drhandle} are passed to
3083\cw{drawing_new()} (\k{drawing-new}) to construct a drawing object
3084which will be passed to the back end function \cw{redraw()}
3085(\k{backend-redraw}). Hence, all drawing-related function pointers
3086defined in \c{drapi} can expect to be called with \c{drhandle} as
3087their first argument.
3088
3089The \c{ourgame} argument points to a container structure describing
3090a game back end. The mid-end thus created will only be capable of
3091handling that one game. (So even in a monolithic front end
3092containing all the games, this imposes the constraint that any
3093individual puzzle window is tied to a single game. Unless, of
3094course, you feel brave enough to change the mid-end for the window
3095without closing the window...)
3096
3097\H{midend-free} \cw{midend_free()}
3098
3099\c void midend_free(midend *me);
3100
3101Frees a mid-end structure and all its associated data.
3102
3103\H{midend-tilesize} \cw{midend_tilesize()}
3104
3105\c int midend_tilesize(midend *me);
3106
3107Returns the \cq{tilesize} parameter being used to display the
3108current puzzle (\k{backend-preferred-tilesize}).
3109
3110\H{midend-set-params} \cw{midend_set_params()}
3111
3112\c void midend_set_params(midend *me, game_params *params);
3113
3114Sets the current game parameters for a mid-end. Subsequent games
3115generated by \cw{midend_new_game()} (\k{midend-new-game}) will use
3116these parameters until further notice.
3117
3118The usual way in which the front end will have an actual
3119\c{game_params} structure to pass to this function is if it had
3120previously got it from \cw{midend_get_presets()}
3121(\k{midend-get-presets}). Thus, this function is usually called in
3122response to the user making a selection from the presets menu.
3123
3124\H{midend-get-params} \cw{midend_get_params()}
3125
3126\c game_params *midend_get_params(midend *me);
3127
3128Returns the current game parameters stored in this mid-end.
3129
3130The returned value is dynamically allocated, and should be freed
3131when finished with by passing it to the game's own
3132\cw{free_params()} function (see \k{backend-free-params}).
3133
3134\H{midend-size} \cw{midend_size()}
3135
3136\c void midend_size(midend *me, int *x, int *y,
3137\c bool user_size, double device_pixel_ratio);
3138
3139Tells the mid-end to figure out its window size.
3140
3141On input, \c{*x} and \c{*y} should contain the maximum or requested
3142size for the window. (Typically this will be the size of the screen
3143that the window has to fit on, or similar.) The mid-end will
3144repeatedly call the back end function \cw{compute_size()}
3145(\k{backend-compute-size}), searching for a tile size that best
3146satisfies the requirements. On exit, \c{*x} and \c{*y} will contain
3147the size needed for the puzzle window's drawing area. (It is of
3148course up to the front end to adjust this for any additional window
3149furniture such as menu bars and window borders, if necessary. The
3150status bar is also not included in this size.)
3151
3152Use \c{user_size} to indicate whether \c{*x} and \c{*y} are a
3153requested size, or just a maximum size.
3154
3155If \c{user_size} is set to \cw{true}, the mid-end will treat the
3156input size as a request, and will pick a tile size which
3157approximates it \e{as closely as possible}, going over the game's
3158preferred tile size if necessary to achieve this. The mid-end will
3159also use the resulting tile size as its preferred one until further
3160notice, on the assumption that this size was explicitly requested
3161by the user. Use this option if you want your front end to support
3162dynamic resizing of the puzzle window with automatic scaling of the
3163puzzle to fit.
3164
3165If \c{user_size} is set to \cw{false}, then the game's tile size
3166will never go over its preferred one, although it may go under in
3167order to fit within the maximum bounds specified by \c{*x} and
3168\c{*y}. This is the recommended approach when opening a new window
3169at default size: the game will use its preferred size unless it has
3170to use a smaller one to fit on the screen. If the tile size is
3171shrunk for this reason, the change will not persist; if a smaller
3172grid is subsequently chosen, the tile size will recover.
3173
3174The mid-end will try as hard as it can to return a size which is
3175less than or equal to the input size, in both dimensions. In extreme
3176circumstances it may fail (if even the lowest possible tile size
3177gives window dimensions greater than the input), in which case it
3178will return a size greater than the input size. Front ends should be
3179prepared for this to happen (i.e. don't crash or fail an assertion),
3180but may handle it in any way they see fit: by rejecting the game
3181parameters which caused the problem, by opening a window larger than
3182the screen regardless of inconvenience, by introducing scroll bars
3183on the window, by drawing on a large bitmap and scaling it into a
3184smaller window, or by any other means you can think of. It is likely
3185that when the tile size is that small the game will be unplayable
3186anyway, so don't put \e{too} much effort into handling it
3187creatively.
3188
3189If your platform has no limit on window size (or if you're planning
3190to use scroll bars for large puzzles), you can pass dimensions of
3191\cw{INT_MAX} as input to this function. You should probably not do
3192that \e{and} set the \c{user_size} flag, though!
3193
3194The \cw{device_pixel_ratio} allows the front end to specify that its
3195pixels are unusually large or small (or should be treated as such).
3196The mid-end uses this to adjust the tile size, both at startup (if the
3197ratio is not 1) and if the ratio changes.
3198
3199A \cw{device_pixel_ratio} of 1 indicates normal-sized pixels.
3200\q{Normal} is not precisely defined, but it's about 4 pixels per
3201millimetre on a screen designed to be viewed from a metre away, or a
3202size such that text 15 pixels high is comfortably readable. Some
3203platforms have a concept of a logical pixel that this can be mapped
3204onto. For instance, Cascading Style Sheets (CSS) has a unit called
3205\cq{px} that only matches physical pixels at a \cw{device_pixel_ratio}
3206of 1.
3207
3208The \cw{device_pixel_ratio} indicates the number of physical pixels in
3209a normal-sized pixel, so values less than 1 indicate unusually large
3210pixels and values greater than 1 indicate unusually small pixels.
3211
3212The midend relies on the frontend calling \cw{midend_new_game()}
3213(\k{midend-new-game}) before calling \cw{midend_size()}.
3214
3215\H{midend-reset-tilesize} \cw{midend_reset_tilesize()}
3216
3217\c void midend_reset_tilesize(midend *me);
3218
3219This function resets the midend's preferred tile size to that of the
3220standard puzzle.
3221
3222As discussed in \k{midend-size}, puzzle resizes are typically
3223'sticky', in that once the user has dragged the puzzle to a different
3224window size, the resulting tile size will be remembered and used when
3225the puzzle configuration changes. If you \e{don't} want that, e.g. if
3226you want to provide a command to explicitly reset the puzzle size back
3227to its default, then you can call this just before calling
3228\cw{midend_size()} (which, in turn, you would probably call with
3229\c{user_size} set to \cw{false}).
3230
3231\H{midend-new-game} \cw{midend_new_game()}
3232
3233\c void midend_new_game(midend *me);
3234
3235Causes the mid-end to begin a new game. Normally the game will be a
3236new randomly generated puzzle. However, if you have previously
3237called \cw{midend_game_id()} or \cw{midend_set_config()}, the game
3238generated might be dictated by the results of those functions. (In
3239particular, you \e{must} call \cw{midend_new_game()} after calling
3240either of those functions, or else no immediate effect will be
3241visible.)
3242
3243You will probably need to call \cw{midend_size()} after calling this
3244function, because if the game parameters have been changed since the
3245last new game then the window size might need to change. (If you
3246know the parameters \e{haven't} changed, you don't need to do this.)
3247
3248This function will create a new \c{game_drawstate}, but does not
3249actually perform a redraw (since you often need to call
3250\cw{midend_size()} before the redraw can be done). So after calling
3251this function and after calling \cw{midend_size()}, you should then
3252call \cw{midend_redraw()}. (It is not necessary to call
3253\cw{midend_force_redraw()}; that will discard the draw state and
3254create a fresh one, which is unnecessary in this case since there's
3255a fresh one already. It would work, but it's usually excessive.)
3256
3257\H{midend-restart-game} \cw{midend_restart_game()}
3258
3259\c void midend_restart_game(midend *me);
3260
3261This function causes the current game to be restarted. This is done
3262by placing a new copy of the original game state on the end of the
3263undo list (so that an accidental restart can be undone).
3264
3265This function automatically causes a redraw, i.e. the front end can
3266expect its drawing API to be called from \e{within} a call to this
3267function. Some back ends require that \cw{midend_size()}
3268(\k{midend-size}) is called before \cw{midend_restart_game()}.
3269
3270\H{midend-force-redraw} \cw{midend_force_redraw()}
3271
3272\c void midend_force_redraw(midend *me);
3273
3274Forces a complete redraw of the puzzle window, by means of
3275discarding the current \c{game_drawstate} and creating a new one
3276from scratch before calling the game's \cw{redraw()} function.
3277
3278The front end can expect its drawing API to be called from within a
3279call to this function. Some back ends require that \cw{midend_size()}
3280(\k{midend-size}) is called before \cw{midend_force_redraw()}.
3281
3282\H{midend-redraw} \cw{midend_redraw()}
3283
3284\c void midend_redraw(midend *me);
3285
3286Causes a partial redraw of the puzzle window, by means of simply
3287calling the game's \cw{redraw()} function. (That is, the only things
3288redrawn will be things that have changed since the last redraw.)
3289
3290The front end can expect its drawing API to be called from within a
3291call to this function. Some back ends require that \cw{midend_size()}
3292(\k{midend-size}) is called before \cw{midend_redraw()}.
3293
3294\H{midend-process-key} \cw{midend_process_key()}
3295
3296\c int midend_process_key(midend *me, int x, int y, int button)
3297
3298The front end calls this function to report a mouse or keyboard event.
3299The parameters \c{x} and \c{y} are identical to the ones passed to the
3300back end function \cw{interpret_move()} (\k{backend-interpret-move}).
3301
3302\c{button} is similar to the parameter passed to
3303\cw{interpret_move()}. However, the midend is more relaxed about
3304values passed to in, and some additional special button values
3305are defined for the front end to pass to the midend (see below).
3306
3307Also, the front end is \e{not} required to provide guarantees about
3308mouse event ordering. The mid-end will sort out multiple simultaneous
3309button presses and changes of button; the front end's responsibility
3310is simply to pass on the mouse events it receives as accurately as
3311possible.
3312
3313(Some platforms may need to emulate absent mouse buttons by means of
3314using a modifier key such as Shift with another mouse button. This
3315tends to mean that if Shift is pressed or released in the middle of
3316a mouse drag, the mid-end will suddenly stop receiving, say,
3317\cw{LEFT_DRAG} events and start receiving \cw{RIGHT_DRAG}s, with no
3318intervening button release or press events. This too is something
3319which the mid-end will sort out for you; the front end has no
3320obligation to maintain sanity in this area.)
3321
3322The front end \e{should}, however, always eventually send some kind
3323of button release. On some platforms this requires special effort:
3324Windows, for example, requires a call to the system API function
3325\cw{SetCapture()} in order to ensure that your window receives a
3326mouse-up event even if the pointer has left the window by the time
3327the mouse button is released. On any platform that requires this
3328sort of thing, the front end \e{is} responsible for doing it.
3329
3330Calling this function is very likely to result in calls back to the
3331front end's drawing API and/or \cw{activate_timer()}
3332(\k{frontend-activate-timer}).
3333
3334The return value from \cw{midend_process_key()} is one of the
3335following constants:
3336
3337\dt \cw{PKR_QUIT}
3338
3339\dd Means that the effect of the keypress was to request termination
3340of the program. A front end should shut down the puzzle in response
3341to a \cw{PKR_QUIT} return.
3342
3343\dt \cw{PKR_SOME_EFFECT}
3344
3345\dd The keypress had some other effect, either in the mid-end or in
3346the puzzle itself.
3347
3348\dt \cw{PKR_NO_EFFECT}
3349
3350\dd The keypress had no effect, but might have had an effect in
3351slightly different circumstances. For instance it requested a move
3352that wasn't possible.
3353
3354\dt \cw{PKR_UNUSED}
3355
3356\dd The key was one that neither the mid-end nor the back-end has any
3357use for at all.
3358
3359A front end might respond to the last value by passing the key on to
3360something else that might be interested in it.
3361
3362The following additional values of \c{button} are permitted to be
3363passed to this function by the front end, but are never passed on to
3364the back end. They indicate front-end specific UI operations, such as
3365selecting an option from a drop-down menu. (Otherwise the front end
3366would have to translate the \q{New Game} menu item into an \cq{n}
3367keypress, for example.)
3368
3369\dt \cw{UI_NEWGAME}
3370
3371\dd Indicates that the user requested a new game, similar to pressing
3372\cq{n}.
3373
3374\dt \cw{UI_SOLVE}
3375
3376\dd Indicates that the user requested the solution of the current game.
3377
3378\dt \cw{UI_UNDO}
3379
3380\dd Indicates that the user attempted to undo a move.
3381
3382\dt \cw{UI_REDO}
3383
3384\dd Indicates that the user attempted to redo an undone move.
3385
3386\dt \cw{UI_QUIT}
3387
3388\dd Indicates that the user asked to quit the game. (Of course, a
3389front end might perfectly well handle this on its own. But including
3390it in this enumeration allows the front end to treat all these menu
3391items the same, by translating each of them into a button code passed
3392to the midend, and handle quitting by noticing the \c{false} return
3393value from \cw{midend_process_key()}.)
3394
3395The midend tolerates any modifier being set on any key and removes
3396them as necessary before passing the key on to the backend. It will
3397also handle translating printable characters combined with
3398\cw{MOD_CTRL} into control characters.
3399
3400\H{midend-request-keys} \cw{midend_request_keys()}
3401
3402\c key_label *midend_request_keys(midend *me, int *nkeys);
3403
3404This function behaves similarly to the backend's \cw{request_keys()}
3405function (\k{backend-request-keys}). If the backend does not provide
3406\cw{request_keys()}, this function will return \cw{NULL} and set
3407\cw{*nkeys} to zero. Otherwise, this function will fill in the generic
3408labels (i.e. the \cw{key_label} items that have their \cw{label}
3409fields set to \cw{NULL}) by using \cw{button2label()}
3410(\k{utils-button2label}).
3411
3412\H{midend-current-key-label} \cw{midend_current_key_label()}
3413
3414\c const char *midend_current_key_label(midend *me, int button);
3415
3416This is a thin wrapper around the backend's \cw{current_key_label()}
3417function (\k{backend-current-key-label}). Front ends that need to
3418label \cw{CURSOR_SELECT} or \cw{CURSOR_SELECT2} should call this
3419function after each move (at least after each call to
3420\cw{midend_process_key()}) to get the current labels. The front end
3421should arrange to copy the returned string somewhere before the next
3422call to the mid-end, just in case it's dynamically allocated. If the
3423button supplied does nothing, the label returned will be an empty
3424string.
3425
3426\H{midend-colours} \cw{midend_colours()}
3427
3428\c float *midend_colours(midend *me, int *ncolours);
3429
3430Returns an array of the colours required by the game, in exactly the
3431same format as that returned by the back end function \cw{colours()}
3432(\k{backend-colours}). Front ends should call this function rather
3433than calling the back end's version directly, since the mid-end adds
3434standard customisation facilities. (At the time of writing, those
3435customisation facilities are implemented hackily by means of
3436environment variables, but it's not impossible that they may become
3437more full and formal in future.)
3438
3439\H{midend-timer} \cw{midend_timer()}
3440
3441\c void midend_timer(midend *me, float tplus);
3442
3443If the mid-end has called \cw{activate_timer()}
3444(\k{frontend-activate-timer}) to request regular callbacks for
3445purposes of animation or timing, this is the function the front end
3446should call on a regular basis. The argument \c{tplus} gives the
3447time, in seconds, since the last time either this function was
3448called or \cw{activate_timer()} was invoked.
3449
3450One of the major purposes of timing in the mid-end is to perform
3451move animation. Therefore, calling this function is very likely to
3452result in calls back to the front end's drawing API.
3453
3454\H{midend-get-presets} \cw{midend_get_presets()}
3455
3456\c struct preset_menu *midend_get_presets(midend *me, int *id_limit);
3457
3458Returns a data structure describing this game's collection of preset
3459game parameters, organised into a hierarchical structure of menus and
3460submenus.
3461
3462The return value is a pointer to a data structure containing the
3463following fields (among others, which are not intended for front end
3464use):
3465
3466\c struct preset_menu {
3467\c int n_entries;
3468\c struct preset_menu_entry *entries;
3469\c /* and other things */
3470\e iiiiiiiiiiiiiiiiiiiiii
3471\c };
3472
3473Those fields describe the intended contents of one particular menu in
3474the hierarchy. \cq{entries} points to an array of \cq{n_entries}
3475items, each of which is a structure containing the following fields:
3476
3477\c struct preset_menu_entry {
3478\c char *title;
3479\c game_params *params;
3480\c struct preset_menu *submenu;
3481\c int id;
3482\c };
3483
3484Of these fields, \cq{title} and \cq{id} are present in every entry,
3485giving (respectively) the textual name of the menu item and an integer
3486identifier for it. The integer id will correspond to the one returned
3487by \c{midend_which_preset} (\k{midend-which-preset}), when that preset
3488is the one selected.
3489
3490The other two fields are mutually exclusive. Each \c{struct
3491preset_menu_entry} will have one of those fields \cw{NULL} and the
3492other one non-null. If the menu item is an actual preset, then
3493\cq{params} will point to the set of game parameters that go with the
3494name; if it's a submenu, then \cq{submenu} instead will be non-null,
3495and will point at a subsidiary \c{struct preset_menu}.
3496
3497The complete hierarchy of these structures is owned by the mid-end,
3498and will be freed when the mid-end is freed. The front end should not
3499attempt to free any of it.
3500
3501The integer identifiers will be allocated densely from 0 upwards, so
3502that it's reasonable for the front end to allocate an array which uses
3503them as indices, if it needs to store information per preset menu
3504item. For this purpose, the front end may pass the second parameter
3505\cq{id_limit} to \cw{midend_get_presets} as the address of an \c{int}
3506variable, into which \cw{midend_get_presets} will write an integer one
3507larger than the largest id number actually used (i.e. the number of
3508elements the front end would need in the array).
3509
3510Submenu-type entries also have integer identifiers.
3511
3512\H{midend-which-preset} \cw{midend_which_preset()}
3513
3514\c int midend_which_preset(midend *me);
3515
3516Returns the numeric index of the preset game parameter structure
3517which matches the current game parameters, or a negative number if
3518no preset matches. Front ends could use this to maintain a tick
3519beside one of the items in the menu (or tick the \q{Custom} option
3520if the return value is less than zero).
3521
3522The returned index value (if non-negative) will match the \c{id} field
3523of the corresponding \cw{struct preset_menu_entry} returned by
3524\c{midend_get_presets()} (\k{midend-get-presets}).
3525
3526\H{midend-wants-statusbar} \cw{midend_wants_statusbar()}
3527
3528\c bool midend_wants_statusbar(midend *me);
3529
3530This function returns \cw{true} if the puzzle has a use for a
3531textual status line (to display score, completion status, currently
3532active tiles, time, or anything else).
3533
3534Front ends should call this function rather than talking directly to
3535the back end.
3536
3537\H{midend-get-config} \cw{midend_get_config()}
3538
3539\c config_item *midend_get_config(midend *me, int which,
3540\c char **wintitle);
3541
3542Returns a dialog box description for user configuration.
3543
3544On input, \cw{which} should be set to one of three values, which
3545select which of the various dialog box descriptions is returned:
3546
3547\dt \cw{CFG_SETTINGS}
3548
3549\dd Requests the GUI parameter configuration box generated by the
3550puzzle itself. This should be used when the user selects \q{Custom}
3551from the game types menu (or equivalent). The mid-end passes this
3552request on to the back end function \cw{configure()}
3553(\k{backend-configure}).
3554
3555\dt \cw{CFG_DESC}
3556
3557\dd Requests a box suitable for entering a descriptive game ID (and
3558viewing the existing one). The mid-end generates this dialog box
3559description itself. This should be used when the user selects
3560\q{Specific} from the game menu (or equivalent).
3561
3562\dt \cw{CFG_SEED}
3563
3564\dd Requests a box suitable for entering a random-seed game ID (and
3565viewing the existing one). The mid-end generates this dialog box
3566description itself. This should be used when the user selects
3567\q{Random Seed} from the game menu (or equivalent).
3568
3569\dt \cw{CFG_PREFS}
3570
3571\dd Requests a box suitable for configuring user preferences.
3572
3573(An additional value \cw{CFG_FRONTEND_SPECIFIC} is provided in this
3574enumeration, so that frontends can extend it for their own internal
3575use. For example, you might wrap this function with a
3576\cw{frontend_get_config} which handles some values of \c{which} itself
3577and hands others on to the midend, depending on whether \cw{which <
3578CFG_FRONTEND_SPECIFIC}.)
3579
3580The returned value is an array of \cw{config_item}s, exactly as
3581described in \k{backend-configure}. Another returned value is an
3582ASCII string giving a suitable title for the configuration window,
3583in \c{*wintitle}.
3584
3585Both returned values are dynamically allocated and will need to be
3586freed. The window title can be freed in the obvious way; the
3587\cw{config_item} array is a slightly complex structure, so a utility
3588function \cw{free_cfg()} is provided to free it for you. See
3589\k{utils-free-cfg}.
3590
3591(Of course, you will probably not want to free the \cw{config_item}
3592array until the dialog box is dismissed, because before then you
3593will probably need to pass it to \cw{midend_set_config}.)
3594
3595\H{midend-set-config} \cw{midend_set_config()}
3596
3597\c const char *midend_set_config(midend *me, int which,
3598\c config_item *cfg);
3599
3600Passes the mid-end the results of a configuration dialog box.
3601\c{which} should have the same value which it had when
3602\cw{midend_get_config()} was called; \c{cfg} should be the array of
3603\c{config_item}s returned from \cw{midend_get_config()}, modified to
3604contain the results of the user's editing operations.
3605
3606This function returns \cw{NULL} on success, or otherwise (if the
3607configuration data was in some way invalid) an ASCII string
3608containing an error message suitable for showing to the user.
3609
3610If the function succeeds, it is likely that the game parameters will
3611have been changed and it is certain that a new game will be
3612requested. The front end should therefore call
3613\cw{midend_new_game()}, and probably also re-think the window size
3614using \cw{midend_size()} and eventually perform a refresh using
3615\cw{midend_redraw()}.
3616
3617\H{midend-game-id} \cw{midend_game_id()}
3618
3619\c const char *midend_game_id(midend *me, const char *id);
3620
3621Passes the mid-end a string game ID (of any of the valid forms
3622\cq{params}, \cq{params:description} or \cq{params#seed}) which the
3623mid-end will process and use for the next generated game.
3624
3625This function returns \cw{NULL} on success, or otherwise (if the
3626configuration data was in some way invalid) an ASCII string
3627containing an error message (not dynamically allocated) suitable for
3628showing to the user. In the event of an error, the mid-end's
3629internal state will be left exactly as it was before the call.
3630
3631If the function succeeds, it is likely that the game parameters will
3632have been changed and it is certain that a new game will be
3633requested. The front end should therefore call
3634\cw{midend_new_game()}, and probably also re-think the window size
3635using \cw{midend_size()} and eventually case a refresh using
3636\cw{midend_redraw()}.
3637
3638\H{midend-get-game-id} \cw{midend_get_game_id()}
3639
3640\c char *midend_get_game_id(midend *me);
3641
3642Returns a descriptive game ID (i.e. one in the form
3643\cq{params:description}) describing the game currently active in the
3644mid-end. The returned string is dynamically allocated.
3645
3646\H{midend-get-random-seed} \cw{midend_get_random_seed()}
3647
3648\c char *midend_get_random_seed(midend *me);
3649
3650Returns a random game ID (i.e. one in the form \cq{params#seedstring})
3651describing the game currently active in the mid-end, if there is one.
3652If the game was created by entering a description, no random seed will
3653currently exist and this function will return \cw{NULL}.
3654
3655The returned string, if it is non-\cw{NULL}, is dynamically allocated.
3656
3657Unlike the descriptive game ID, the random seed can contain characters
3658outside the printable ASCII set.
3659
3660\H{midend-can-format-as-text-now} \cw{midend_can_format_as_text_now()}
3661
3662\c bool midend_can_format_as_text_now(midend *me);
3663
3664Returns \cw{true} if the game code is capable of formatting puzzles
3665of the currently selected game type as ASCII.
3666
3667If this returns \cw{false}, then \cw{midend_text_format()}
3668(\k{midend-text-format}) will return \cw{NULL}.
3669
3670\H{midend-text-format} \cw{midend_text_format()}
3671
3672\c char *midend_text_format(midend *me);
3673
3674Formats the current game's current state as ASCII text suitable for
3675copying to the clipboard. The returned string is dynamically
3676allocated.
3677
3678If the game's \c{can_format_as_text_ever} flag is \cw{false}, or if
3679its \cw{can_format_as_text_now()} function returns \cw{false}, then
3680this function will return \cw{NULL}.
3681
3682If the returned string contains multiple lines (which is likely), it
3683will use the normal C line ending convention (\cw{\\n} only). On
3684platforms which use a different line ending convention for data in
3685the clipboard, it is the front end's responsibility to perform the
3686conversion.
3687
3688\H{midend-solve} \cw{midend_solve()}
3689
3690\c const char *midend_solve(midend *me);
3691
3692Requests the mid-end to perform a Solve operation.
3693
3694On success, \cw{NULL} is returned. On failure, an error message (not
3695dynamically allocated) is returned, suitable for showing to the
3696user.
3697
3698The front end can expect its drawing API and/or
3699\cw{activate_timer()} to be called from within a call to this
3700function. Some back ends require that \cw{midend_size()}
3701(\k{midend-size}) is called before \cw{midend_solve()}.
3702
3703\H{midend-get-cursor-location} \cw{midend_get_cursor_location()}
3704
3705\c bool midend_get_cursor_location(midend *me,
3706\c int *x, int *y,
3707\c int *w, int *h);
3708
3709This function requests the location of the back end's on-screen cursor
3710or other region of interest.
3711
3712What exactly this region contains is up to the backend, but in general
3713the region will be an area that the player is controlling with the
3714cursor keys \dash such as the player location in Cube and Inertia, or
3715the cursor in any of the conventional grid-based games. With knowledge
3716of this location, a front end can, for example, ensure that the region
3717of interest remains visible even if the entire puzzle is too big to
3718fit on the screen.
3719
3720On success, this function returns \cw{true}, and the locations pointed
3721to by \cw{x}, \cw{y}, \cw{w} and \cw{h} are updated to describe the
3722cursor region, which has an upper-left corner located at \cw{(*x,*y)}
3723and a size of \cw{*w} pixels wide by \cw{*h} pixels tall. The caller
3724may pass \cw{NULL} for any number of these pointers, which will be
3725ignored.
3726
3727On failure, this function returns \cw{false}. Failure can occur if
3728there is currently no active cursor region, or if the back end lacks
3729cursor support.
3730
3731\H{midend-status} \cw{midend_status()}
3732
3733\c int midend_status(midend *me);
3734
3735This function returns +1 if the midend is currently displaying a game
3736in a solved state, -1 if the game is in a permanently lost state, or 0
3737otherwise. This function just calls the back end's \cw{status()}
3738function. Front ends may wish to use this as a cue to proactively
3739offer the option of starting a new game.
3740
3741(See \k{backend-status} for more detail about the back end's
3742\cw{status()} function and discussion of what should count as which
3743status code.)
3744
3745\H{midend-can-undo} \cw{midend_can_undo()}
3746
3747\c bool midend_can_undo(midend *me);
3748
3749Returns \cw{true} if the midend is currently in a state where the undo
3750operation is meaningful (i.e. at least one position exists on the undo
3751chain before the present one). Front ends may wish to use this to
3752visually activate and deactivate an undo button.
3753
3754\H{midend-can-redo} \cw{midend_can_redo()}
3755
3756\c bool midend_can_redo(midend *me);
3757
3758Returns \cw{true} if the midend is currently in a state where the redo
3759operation is meaningful (i.e. at least one position exists on the redo
3760chain after the present one). Front ends may wish to use this to
3761visually activate and deactivate a redo button.
3762
3763\H{midend-serialise} \cw{midend_serialise()}
3764
3765\c void midend_serialise(midend *me,
3766\c void (*write)(void *ctx, const void *buf, int len), void *wctx);
3767
3768Calling this function causes the mid-end to convert its entire
3769internal state into a long ASCII text string, and to pass that
3770string (piece by piece) to the supplied \c{write} function.
3771The string will consist of printable ASCII characters and line
3772feeds.
3773
3774Desktop implementations can use this function to save a game in any
3775state (including half-finished) to a disk file, by supplying a
3776\c{write} function which is a wrapper on \cw{fwrite()} (or local
3777equivalent). Other implementations may find other uses for it, such
3778as compressing the large and sprawling mid-end state into a
3779manageable amount of memory when a palmtop application is suspended
3780so that another one can run; in this case \cw{write} might want to
3781write to a memory buffer rather than a file. There may be other uses
3782for it as well.
3783
3784This function will call back to the supplied \c{write} function a
3785number of times, with the first parameter (\c{ctx}) equal to
3786\c{wctx}, and the other two parameters pointing at a piece of the
3787output string.
3788
3789\H{midend-deserialise} \cw{midend_deserialise()}
3790
3791\c const char *midend_deserialise(midend *me,
3792\c bool (*read)(void *ctx, void *buf, int len), void *rctx);
3793
3794This function is the counterpart to \cw{midend_serialise()}. It
3795calls the supplied \cw{read} function repeatedly to read a quantity
3796of data, and attempts to interpret that data as a serialised mid-end
3797as output by \cw{midend_serialise()}.
3798
3799The \cw{read} function is called with the first parameter (\c{ctx})
3800equal to \c{rctx}, and should attempt to read \c{len} bytes of data
3801into the buffer pointed to by \c{buf}. It should return \cw{false}
3802on failure or \cw{true} on success. It should not report success
3803unless it has filled the entire buffer; on platforms which might be
3804reading from a pipe or other blocking data source, \c{read} is
3805responsible for looping until the whole buffer has been filled.
3806
3807If the de-serialisation operation is successful, the mid-end's
3808internal data structures will be replaced by the results of the
3809load, and \cw{NULL} will be returned. Otherwise, the mid-end's state
3810will be completely unchanged and an error message (typically some
3811variation on \q{save file is corrupt}) will be returned. As usual,
3812the error message string is not dynamically allocated.
3813
3814If this function succeeds, it is likely that the game parameters
3815will have been changed. The front end should therefore probably
3816re-think the window size using \cw{midend_size()}, and probably
3817cause a refresh using \cw{midend_redraw()}.
3818
3819Because each mid-end is tied to a specific game back end, this
3820function will fail if you attempt to read in a save file generated by
3821a different game from the one configured in this mid-end, even if your
3822application is a monolithic one containing all the puzzles. See
3823\k{identify-game} for a helper function which will allow you to
3824identify a save file before you instantiate your mid-end in the first
3825place.
3826
3827\H{midend-save-prefs} \cw{midend_save_prefs()}
3828
3829\c void midend_save_prefs(
3830\c midend *me, void (*write)(void *ctx, const void *buf, int len),
3831\c void *wctx);
3832
3833Calling this function causes the mid-end to write out the states of
3834all user-settable preference options, including its own cross-platform
3835preferences and ones exported by a particular game via
3836\cw{get_prefs()} and \cw{set_prefs()} (\k{backend-get-prefs},
3837\k{backend-set-prefs}). The output is a textual format suitable for
3838writing into a configuration file on disk.
3839
3840The \c{write} and \c{wctx} parameters have the same semantics as for
3841\cw{midend_serialise()} (\k{midend-serialise}).
3842
3843\H{midend-load-prefs} \cw{midend_load_prefs()}
3844
3845\c const char *midend_load_prefs(
3846\c midend *me, bool (*read)(void *ctx, void *buf, int len),
3847\c void *rctx);
3848
3849This function is used to load a configuration file in the same format
3850emitted by \cw{midend_save_prefs()}, and import all the preferences
3851described in the file into the current mid-end.
3852
3853\H{identify-game} \cw{identify_game()}
3854
3855\c const char *identify_game(char **name,
3856\c bool (*read)(void *ctx, void *buf, int len), void *rctx);
3857
3858This function examines a serialised midend stream, of the same kind
3859used by \cw{midend_serialise()} and \cw{midend_deserialise()}, and
3860returns the \cw{name} field of the game back end from which it was
3861saved.
3862
3863You might want this if your front end was a monolithic one containing
3864all the puzzles, and you wanted to be able to load an arbitrary save
3865file and automatically switch to the right game. Probably your next
3866step would be to iterate through \cw{gamelist} (\k{frontend-backend})
3867looking for a game structure whose \cw{name} field matched the
3868returned string, and give an error if you didn't find one.
3869
3870On success, the return value of this function is \cw{NULL}, and the
3871game name string is written into \cw{*name}. The caller should free
3872that string after using it.
3873
3874On failure, \cw{*name} is \cw{NULL}, and the return value is an error
3875message (which does not need freeing at all).
3876
3877(This isn't strictly speaking a midend function, since it doesn't
3878accept or return a pointer to a midend. You'd probably call it just
3879\e{before} deciding what kind of midend you wanted to instantiate.)
3880
3881\H{midend-request-id-changes} \cw{midend_request_id_changes()}
3882
3883\c void midend_request_id_changes(midend *me,
3884\c void (*notify)(void *), void *ctx);
3885
3886This function is called by the front end to request notification by
3887the mid-end when the current game IDs (either descriptive or
3888random-seed) change. This can occur as a result of keypresses ('n' for
3889New Game, for example) or when a puzzle supersedes its game
3890description (see \k{backend-supersede}). After this function is
3891called, any change of the game ids will cause the mid-end to call
3892\cw{notify(ctx)} after the change.
3893
3894This is for use by puzzles which want to present the game description
3895to the user constantly (e.g. as an HTML hyperlink) instead of only
3896showing it when the user explicitly requests it.
3897
3898This is a function I anticipate few front ends needing to implement,
3899so I make it a callback rather than a static function in order to
3900relieve most front ends of the need to provide an empty
3901implementation.
3902
3903\H{midend-which-game} \cw{midend_which_game()}
3904
3905\c const game *midend_which_preset(midend *me);
3906
3907This function returns the \c{game} structure for the puzzle type this
3908midend is committed to.
3909
3910\H{frontend-backend} Direct reference to the back end structure by
3911the front end
3912
3913Although \e{most} things the front end needs done should be done by
3914calling the mid-end, there are a few situations in which the front
3915end needs to refer directly to the game back end structure.
3916
3917The most obvious of these is
3918
3919\b passing the game back end as a parameter to \cw{midend_new()}.
3920
3921There are a few other back end features which are not wrapped by the
3922mid-end because there didn't seem much point in doing so:
3923
3924\b fetching the \c{name} field to use in window titles and similar
3925
3926\b reading the \c{can_configure}, \c{can_solve} and
3927\c{can_format_as_text_ever} fields to decide whether to add those
3928items to the menu bar or equivalent
3929
3930\b reading the \c{winhelp_topic} field (Windows only)
3931
3932\b the GTK front end provides a \cq{--generate} command-line option
3933which directly calls the back end to do most of its work. This is
3934not really part of the main front end code, though, and I'm not sure
3935it counts.
3936
3937In order to find the game back end structure, the front end does one
3938of two things:
3939
3940\b If the particular front end is compiling a separate binary per
3941game, then the back end structure is a global variable with the
3942standard name \cq{thegame}:
3943
3944\lcont{
3945
3946\c extern const game thegame;
3947
3948}
3949
3950\b If the front end is compiled as a monolithic application
3951containing all the puzzles together (in which case the preprocessor
3952symbol \cw{COMBINED} must be defined when compiling most of the code
3953base), then there will be two global variables defined:
3954
3955\lcont{
3956
3957\c extern const game *gamelist[];
3958\c extern const int gamecount;
3959
3960\c{gamelist} will be an array of \c{gamecount} game structures,
3961declared in the automatically constructed source module \c{list.c}.
3962The application should search that array for the game it wants,
3963probably by reaching into each game structure and looking at its
3964\c{name} field.
3965
3966}
3967
3968\H{frontend-api} Mid-end to front-end calls
3969
3970This section describes the small number of functions which a front
3971end must provide to be called by the mid-end or other standard
3972utility modules.
3973
3974\H{frontend-get-random-seed} \cw{get_random_seed()}
3975
3976\c void get_random_seed(void **randseed, int *randseedsize);
3977
3978This function is called by a new mid-end, and also occasionally by
3979game back ends. Its job is to return a piece of data suitable for
3980using as a seed for initialisation of a new \c{random_state}.
3981
3982On exit, \c{*randseed} should be set to point at a newly allocated
3983piece of memory containing some seed data, and \c{*randseedsize}
3984should be set to the length of that data.
3985
3986A simple and entirely adequate implementation is to return a piece
3987of data containing the current system time at the highest
3988conveniently available resolution.
3989
3990\H{frontend-activate-timer} \cw{activate_timer()}
3991
3992\c void activate_timer(frontend *fe);
3993
3994This is called by the mid-end to request that the front end begin
3995calling it back at regular intervals.
3996
3997The timeout interval is left up to the front end; the finer it is,
3998the smoother move animations will be, but the more CPU time will be
3999used. Current front ends use values around 20ms (i.e. 50Hz).
4000
4001After this function is called, the mid-end will expect to receive
4002calls to \cw{midend_timer()} on a regular basis.
4003
4004\H{frontend-deactivate-timer} \cw{deactivate_timer()}
4005
4006\c void deactivate_timer(frontend *fe);
4007
4008This is called by the mid-end to request that the front end stop
4009calling \cw{midend_timer()}.
4010
4011\H{frontend-fatal} \cw{fatal()}
4012
4013\c void fatal(const char *fmt, ...);
4014
4015This is called by some utility functions if they encounter a
4016genuinely fatal error such as running out of memory. It is a
4017variadic function in the style of \cw{printf()}, and is expected to
4018show the formatted error message to the user any way it can and then
4019terminate the application. It must not return.
4020
4021\H{frontend-default-colour} \cw{frontend_default_colour()}
4022
4023\c void frontend_default_colour(frontend *fe, float *output);
4024
4025This function expects to be passed a pointer to an array of three
4026\cw{float}s. It returns the platform's local preferred background
4027colour in those three floats, as red, green and blue values (in that
4028order) ranging from \cw{0.0} to \cw{1.0}.
4029
4030This function should only ever be called by the back end function
4031\cw{colours()} (\k{backend-colours}). (Thus, it isn't a
4032\e{midend}-to-frontend function as such, but there didn't seem to be
4033anywhere else particularly good to put it. Sorry.)
4034
4035\C{utils} Utility APIs
4036
4037This chapter documents a variety of utility APIs provided for the
4038general use of the rest of the Puzzles code.
4039
4040\H{utils-random} Random number generation
4041
4042Platforms' local random number generators vary widely in quality and
4043seed size. Puzzles therefore supplies its own high-quality random
4044number generator, with the additional advantage of giving the same
4045results if fed the same seed data on different platforms. This
4046allows game random seeds to be exchanged between different ports of
4047Puzzles and still generate the same games.
4048
4049Unlike the ANSI C \cw{rand()} function, the Puzzles random number
4050generator has an \e{explicit} state object called a
4051\c{random_state}. One of these is managed by each mid-end, for
4052example, and passed to the back end to generate a game with.
4053
4054\S{utils-random-init} \cw{random_new()}
4055
4056\c random_state *random_new(char *seed, int len);
4057
4058Allocates, initialises and returns a new \c{random_state}. The input
4059data is used as the seed for the random number stream (i.e. using
4060the same seed at a later time will generate the same stream).
4061
4062The seed data can be any data at all; there is no requirement to use
4063printable ASCII, or NUL-terminated strings, or anything like that.
4064
4065\S{utils-random-copy} \cw{random_copy()}
4066
4067\c random_state *random_copy(random_state *tocopy);
4068
4069Allocates a new \c{random_state}, copies the contents of another
4070\c{random_state} into it, and returns the new state. If exactly the
4071same sequence of functions is subsequently called on both the copy and
4072the original, the results will be identical. This may be useful for
4073speculatively performing some operation using a given random state,
4074and later replaying that operation precisely.
4075
4076\S{utils-random-free} \cw{random_free()}
4077
4078\c void random_free(random_state *state);
4079
4080Frees a \c{random_state}.
4081
4082\S{utils-random-bits} \cw{random_bits()}
4083
4084\c unsigned long random_bits(random_state *state, int bits);
4085
4086Returns a random number from 0 to \cw{2^bits-1} inclusive. \c{bits}
4087should be between 1 and 32 inclusive.
4088
4089\S{utils-random-upto} \cw{random_upto()}
4090
4091\c unsigned long random_upto(random_state *state, unsigned long limit);
4092
4093Returns a random number from 0 to \cw{limit-1} inclusive. \c{limit}
4094may not be zero.
4095
4096\S{utils-random-state-encode} \cw{random_state_encode()}
4097
4098\c char *random_state_encode(random_state *state);
4099
4100Encodes the entire contents of a \c{random_state} in printable
4101ASCII. Returns a dynamically allocated string containing that
4102encoding. This can subsequently be passed to
4103\cw{random_state_decode()} to reconstruct the same \c{random_state}.
4104
4105\S{utils-random-state-decode} \cw{random_state_decode()}
4106
4107\c random_state *random_state_decode(char *input);
4108
4109Decodes a string generated by \cw{random_state_encode()} and
4110reconstructs an equivalent \c{random_state} to the one encoded, i.e.
4111it should produce the same stream of random numbers.
4112
4113This function has no error reporting; if you pass it an invalid
4114string it will simply generate an arbitrary random state, which may
4115turn out to be noticeably non-random.
4116
4117\S{utils-shuffle} \cw{shuffle()}
4118
4119\c void shuffle(void *array, int nelts, int eltsize, random_state *rs);
4120
4121Shuffles an array into a random order. The interface is much like
4122ANSI C \cw{qsort()}, except that there's no need for a compare
4123function.
4124
4125\c{array} is a pointer to the first element of the array. \c{nelts}
4126is the number of elements in the array; \c{eltsize} is the size of a
4127single element (typically measured using \c{sizeof}). \c{rs} is a
4128\c{random_state} used to generate all the random numbers for the
4129shuffling process.
4130
4131\H{utils-presets} Presets menu management
4132
4133The function \c{midend_get_presets()} (\k{midend-get-presets}) returns
4134a data structure describing a menu hierarchy. Back ends can also
4135choose to provide such a structure to the mid-end, if they want to
4136group their presets hierarchically. To make this easy, there are a few
4137utility functions to construct preset menu structures, and also one
4138intended for front-end use.
4139
4140\S{utils-preset-menu-new} \cw{preset_menu_new()}
4141
4142\c struct preset_menu *preset_menu_new(void);
4143
4144Allocates a new \c{struct preset_menu}, and initialises it to hold no
4145menu items.
4146
4147\S{utils-preset-menu-add_submenu} \cw{preset_menu_add_submenu()}
4148
4149\c struct preset_menu *preset_menu_add_submenu
4150\c (struct preset_menu *parent, char *title);
4151
4152Adds a new submenu to the end of an existing preset menu, and returns
4153a pointer to a newly allocated \c{struct preset_menu} describing the
4154submenu.
4155
4156The string parameter \cq{title} must be dynamically allocated by the
4157caller. The preset-menu structure will take ownership of it, so the
4158caller must not free it.
4159
4160\S{utils-preset-menu-add-preset} \cw{preset_menu_add_preset()}
4161
4162\c void preset_menu_add_preset
4163\c (struct preset_menu *menu, char *title, game_params *params);
4164
4165Adds a preset game configuration to the end of a preset menu.
4166
4167Both the string parameter \cq{title} and the game parameter structure
4168\cq{params} itself must be dynamically allocated by the caller. The
4169preset-menu structure will take ownership of it, so the caller must
4170not free it.
4171
4172\S{utils-preset-menu-lookup-by-id} \cw{preset_menu_lookup_by_id()}
4173
4174\c game_params *preset_menu_lookup_by_id
4175\c (struct preset_menu *menu, int id);
4176
4177Given a numeric index, searches recursively through a preset menu
4178hierarchy to find the corresponding menu entry, and returns a pointer
4179to its existing \c{game_params} structure.
4180
4181This function is intended for front end use (but front ends need not
4182use it if they prefer to do things another way). If a front end finds
4183it inconvenient to store anything more than a numeric index alongside
4184each menu item, then this function provides an easy way for the front
4185end to get back the actual game parameters corresponding to a menu
4186item that the user has selected.
4187
4188\H{utils-alloc} Memory allocation
4189
4190Puzzles has some central wrappers on the standard memory allocation
4191functions, which provide compile-time type checking, and run-time
4192error checking by means of quitting the application if it runs out
4193of memory. This doesn't provide the best possible recovery from
4194memory shortage, but on the other hand it greatly simplifies the
4195rest of the code, because nothing else anywhere needs to worry about
4196\cw{NULL} returns from allocation.
4197
4198\S{utils-snew} \cw{snew()}
4199
4200\c var = snew(type);
4201\e iii iiii
4202
4203This macro takes a single argument which is a \e{type name}. It
4204allocates space for one object of that type. If allocation fails it
4205will call \cw{fatal()} and not return; so if it does return, you can
4206be confident that its return value is non-\cw{NULL}.
4207
4208The return value is cast to the specified type, so that the compiler
4209will type-check it against the variable you assign it into. Thus,
4210this ensures you don't accidentally allocate memory the size of the
4211wrong type and assign it into a variable of the right one (or vice
4212versa!).
4213
4214\S{utils-snewn} \cw{snewn()}
4215
4216\c var = snewn(n, type);
4217\e iii i iiii
4218
4219This macro is the array form of \cw{snew()}. It takes two arguments;
4220the first is a number, and the second is a type name. It allocates
4221space for that many objects of that type, and returns a type-checked
4222non-\cw{NULL} pointer just as \cw{snew()} does.
4223
4224\S{utils-sresize} \cw{sresize()}
4225
4226\c var = sresize(var, n, type);
4227\e iii iii i iiii
4228
4229This macro is a type-checked form of \cw{realloc()}. It takes three
4230arguments: an input memory block, a new size in elements, and a
4231type. It re-sizes the input memory block to a size sufficient to
4232contain that many elements of that type. It returns a type-checked
4233non-\cw{NULL} pointer, like \cw{snew()} and \cw{snewn()}.
4234
4235The input memory block can be \cw{NULL}, in which case this function
4236will behave exactly like \cw{snewn()}. (In principle any
4237ANSI-compliant \cw{realloc()} implementation ought to cope with
4238this, but I've never quite trusted it to work everywhere.)
4239
4240\S{utils-sfree} \cw{sfree()}
4241
4242\c void sfree(void *p);
4243
4244This function is pretty much equivalent to \cw{free()}. It is
4245provided with a dynamically allocated block, and frees it.
4246
4247The input memory block can be \cw{NULL}, in which case this function
4248will do nothing. (In principle any ANSI-compliant \cw{free()}
4249implementation ought to cope with this, but I've never quite trusted
4250it to work everywhere.)
4251
4252\S{utils-dupstr} \cw{dupstr()}
4253
4254\c char *dupstr(const char *s);
4255
4256This function dynamically allocates a duplicate of a C string. Like
4257the \cw{snew()} functions, it guarantees to return non-\cw{NULL} or
4258not return at all.
4259
4260(Many platforms provide the function \cw{strdup()}. As well as
4261guaranteeing never to return \cw{NULL}, my version has the advantage
4262of being defined \e{everywhere}, rather than inconveniently not
4263quite everywhere.)
4264
4265\S{utils-free-cfg} \cw{free_cfg()}
4266
4267\c void free_cfg(config_item *cfg);
4268
4269This function correctly frees an array of \c{config_item}s, including
4270walking the array until it gets to the end and freeing any subsidiary
4271data items in each \c{u} sub-union which are expected to be
4272dynamically allocated.
4273
4274(See \k{backend-configure} for details of the \c{config_item}
4275structure.)
4276
4277\S{utils-free-keys} \cw{free_keys()}
4278
4279\c void free_keys(key_label *keys, int nkeys);
4280
4281This function correctly frees an array of \c{key_label}s, including
4282the dynamically allocated label string for each key.
4283
4284(See \k{backend-request-keys} for details of the \c{key_label}
4285structure.)
4286
4287\H{utils-tree234} Sorted and counted tree functions
4288
4289Many games require complex algorithms for generating random puzzles,
4290and some require moderately complex algorithms even during play. A
4291common requirement during these algorithms is for a means of
4292maintaining sorted or unsorted lists of items, such that items can
4293be removed and added conveniently.
4294
4295For general use, Puzzles provides the following set of functions
4296which maintain 2-3-4 trees in memory. (A 2-3-4 tree is a balanced
4297tree structure, with the property that all lookups, insertions,
4298deletions, splits and joins can be done in \cw{O(log N)} time.)
4299
4300All these functions expect you to be storing a tree of \c{void *}
4301pointers. You can put anything you like in those pointers.
4302
4303By the use of per-node element counts, these tree structures have
4304the slightly unusual ability to look elements up by their numeric
4305index within the list represented by the tree. This means that they
4306can be used to store an unsorted list (in which case, every time you
4307insert a new element, you must explicitly specify the position where
4308you wish to insert it). They can also do numeric lookups in a sorted
4309tree, which might be useful for (for example) tracking the median of
4310a changing data set.
4311
4312As well as storing sorted lists, these functions can be used for
4313storing \q{maps} (associative arrays), by defining each element of a
4314tree to be a (key, value) pair.
4315
4316\S{utils-newtree234} \cw{newtree234()}
4317
4318\c tree234 *newtree234(cmpfn234 cmp);
4319
4320Creates a new empty tree, and returns a pointer to it.
4321
4322The parameter \c{cmp} determines the sorting criterion on the tree.
4323Its prototype is
4324
4325\c typedef int (*cmpfn234)(void *, void *);
4326
4327If you want a sorted tree, you should provide a function matching
4328this prototype, which returns like \cw{strcmp()} does (negative if
4329the first argument is smaller than the second, positive if it is
4330bigger, zero if they compare equal). In this case, the function
4331\cw{addpos234()} will not be usable on your tree (because all
4332insertions must respect the sorting order).
4333
4334If you want an unsorted tree, pass \cw{NULL}. In this case you will
4335not be able to use either \cw{add234()} or \cw{del234()}, or any
4336other function such as \cw{find234()} which depends on a sorting
4337order. Your tree will become something more like an array, except
4338that it will efficiently support insertion and deletion as well as
4339lookups by numeric index.
4340
4341\S{utils-freetree234} \cw{freetree234()}
4342
4343\c void freetree234(tree234 *t);
4344
4345Frees a tree. This function will not free the \e{elements} of the
4346tree (because they might not be dynamically allocated, or you might
4347be storing the same set of elements in more than one tree); it will
4348just free the tree structure itself. If you want to free all the
4349elements of a tree, you should empty it before passing it to
4350\cw{freetree234()}, by means of code along the lines of
4351
4352\c while ((element = delpos234(tree, 0)) != NULL)
4353\c sfree(element); /* or some more complicated free function */
4354\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
4355
4356\S{utils-add234} \cw{add234()}
4357
4358\c void *add234(tree234 *t, void *e);
4359
4360Inserts a new element \c{e} into the tree \c{t}. This function
4361expects the tree to be sorted; the new element is inserted according
4362to the sort order.
4363
4364If an element comparing equal to \c{e} is already in the tree, then
4365the insertion will fail, and the return value will be the existing
4366element. Otherwise, the insertion succeeds, and \c{e} is returned.
4367
4368\S{utils-addpos234} \cw{addpos234()}
4369
4370\c void *addpos234(tree234 *t, void *e, int index);
4371
4372Inserts a new element into an unsorted tree. Since there is no
4373sorting order to dictate where the new element goes, you must
4374specify where you want it to go. Setting \c{index} to zero puts the
4375new element right at the start of the list; setting \c{index} to the
4376current number of elements in the tree puts the new element at the
4377end.
4378
4379Return value is \c{e}, in line with \cw{add234()} (although this
4380function cannot fail except by running out of memory, in which case
4381it will bomb out and die rather than returning an error indication).
4382
4383\S{utils-index234} \cw{index234()}
4384
4385\c void *index234(tree234 *t, int index);
4386
4387Returns a pointer to the \c{index}th element of the tree, or
4388\cw{NULL} if \c{index} is out of range. Elements of the tree are
4389numbered from zero.
4390
4391\S{utils-find234} \cw{find234()}
4392
4393\c void *find234(tree234 *t, void *e, cmpfn234 cmp);
4394
4395Searches for an element comparing equal to \c{e} in a sorted tree.
4396
4397If \c{cmp} is \cw{NULL}, the tree's ordinary comparison function
4398will be used to perform the search. However, sometimes you don't
4399want that; suppose, for example, each of your elements is a big
4400structure containing a \c{char *} name field, and you want to find
4401the element with a given name. You \e{could} achieve this by
4402constructing a fake element structure, setting its name field
4403appropriately, and passing it to \cw{find234()}, but you might find
4404it more convenient to pass \e{just} a name string to \cw{find234()},
4405supplying an alternative comparison function which expects one of
4406its arguments to be a bare name and the other to be a large
4407structure containing a name field.
4408
4409Therefore, if \c{cmp} is not \cw{NULL}, then it will be used to
4410compare \c{e} to elements of the tree. The first argument passed to
4411\c{cmp} will always be \c{e}; the second will be an element of the
4412tree.
4413
4414(See \k{utils-newtree234} for the definition of the \c{cmpfn234}
4415function pointer type.)
4416
4417The returned value is the element found, or \cw{NULL} if the search
4418is unsuccessful.
4419
4420\S{utils-findrel234} \cw{findrel234()}
4421
4422\c void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation);
4423
4424This function is like \cw{find234()}, but has the additional ability
4425to do a \e{relative} search. The additional parameter \c{relation}
4426can be one of the following values:
4427
4428\dt \cw{REL234_EQ}
4429
4430\dd Find only an element that compares equal to \c{e}. This is
4431exactly the behaviour of \cw{find234()}.
4432
4433\dt \cw{REL234_LT}
4434
4435\dd Find the greatest element that compares strictly less than
4436\c{e}. \c{e} may be \cw{NULL}, in which case it finds the greatest
4437element in the whole tree (which could also be done by
4438\cw{index234(t, count234(t)-1)}).
4439
4440\dt \cw{REL234_LE}
4441
4442\dd Find the greatest element that compares less than or equal to
4443\c{e}. (That is, find an element that compares equal to \c{e} if
4444possible, but failing that settle for something just less than it.)
4445
4446\dt \cw{REL234_GT}
4447
4448\dd Find the smallest element that compares strictly greater than
4449\c{e}. \c{e} may be \cw{NULL}, in which case it finds the smallest
4450element in the whole tree (which could also be done by
4451\cw{index234(t, 0)}).
4452
4453\dt \cw{REL234_GE}
4454
4455\dd Find the smallest element that compares greater than or equal to
4456\c{e}. (That is, find an element that compares equal to \c{e} if
4457possible, but failing that settle for something just bigger than
4458it.)
4459
4460Return value, as before, is the element found or \cw{NULL} if no
4461element satisfied the search criterion.
4462
4463\S{utils-findpos234} \cw{findpos234()}
4464
4465\c void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index);
4466
4467This function is like \cw{find234()}, but has the additional feature
4468of returning the index of the element found in the tree; that index
4469is written to \c{*index} in the event of a successful search (a
4470non-\cw{NULL} return value).
4471
4472\c{index} may be \cw{NULL}, in which case this function behaves
4473exactly like \cw{find234()}.
4474
4475\S{utils-findrelpos234} \cw{findrelpos234()}
4476
4477\c void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp, int relation,
4478\c int *index);
4479
4480This function combines all the features of \cw{findrel234()} and
4481\cw{findpos234()}.
4482
4483\S{utils-del234} \cw{del234()}
4484
4485\c void *del234(tree234 *t, void *e);
4486
4487Finds an element comparing equal to \c{e} in the tree, deletes it,
4488and returns it.
4489
4490The input tree must be sorted.
4491
4492The element found might be \c{e} itself, or might merely compare
4493equal to it.
4494
4495Return value is \cw{NULL} if no such element is found.
4496
4497\S{utils-delpos234} \cw{delpos234()}
4498
4499\c void *delpos234(tree234 *t, int index);
4500
4501Deletes the element at position \c{index} in the tree, and returns
4502it.
4503
4504Return value is \cw{NULL} if the index is out of range.
4505
4506\S{utils-count234} \cw{count234()}
4507
4508\c int count234(tree234 *t);
4509
4510Returns the number of elements currently in the tree.
4511
4512\S{utils-splitpos234} \cw{splitpos234()}
4513
4514\c tree234 *splitpos234(tree234 *t, int index, bool before);
4515
4516Splits the input tree into two pieces at a given position, and
4517creates a new tree containing all the elements on one side of that
4518position.
4519
4520If \c{before} is \cw{true}, then all the items at or after position
4521\c{index} are left in the input tree, and the items before that
4522point are returned in the new tree. Otherwise, the reverse happens:
4523all the items at or after \c{index} are moved into the new tree, and
4524those before that point are left in the old one.
4525
4526If \c{index} is equal to 0 or to the number of elements in the input
4527tree, then one of the two trees will end up empty (and this is not
4528an error condition). If \c{index} is further out of range in either
4529direction, the operation will fail completely and return \cw{NULL}.
4530
4531This operation completes in \cw{O(log N)} time, no matter how large
4532the tree or how balanced or unbalanced the split.
4533
4534\S{utils-split234} \cw{split234()}
4535
4536\c tree234 *split234(tree234 *t, void *e, cmpfn234 cmp, int rel);
4537
4538Splits a sorted tree according to its sort order.
4539
4540\c{rel} can be any of the relation constants described in
4541\k{utils-findrel234}, \e{except} for \cw{REL234_EQ}. All the
4542elements having that relation to \c{e} will be transferred into the
4543new tree; the rest will be left in the old one.
4544
4545The parameter \c{cmp} has the same semantics as it does in
4546\cw{find234()}: if it is not \cw{NULL}, it will be used in place of
4547the tree's own comparison function when comparing elements to \c{e},
4548in such a way that \c{e} itself is always the first of its two
4549operands.
4550
4551Again, this operation completes in \cw{O(log N)} time, no matter how
4552large the tree or how balanced or unbalanced the split.
4553
4554\S{utils-join234} \cw{join234()}
4555
4556\c tree234 *join234(tree234 *t1, tree234 *t2);
4557
4558Joins two trees together by concatenating the lists they represent.
4559All the elements of \c{t2} are moved into \c{t1}, in such a way that
4560they appear \e{after} the elements of \c{t1}. The tree \c{t2} is
4561freed; the return value is \c{t1}.
4562
4563If you apply this function to a sorted tree and it violates the sort
4564order (i.e. the smallest element in \c{t2} is smaller than or equal
4565to the largest element in \c{t1}), the operation will fail and
4566return \cw{NULL}.
4567
4568This operation completes in \cw{O(log N)} time, no matter how large
4569the trees being joined together.
4570
4571\S{utils-join234r} \cw{join234r()}
4572
4573\c tree234 *join234r(tree234 *t1, tree234 *t2);
4574
4575Joins two trees together in exactly the same way as \cw{join234()},
4576but this time the combined tree is returned in \c{t2}, and \c{t1} is
4577destroyed. The elements in \c{t1} still appear before those in
4578\c{t2}.
4579
4580Again, this operation completes in \cw{O(log N)} time, no matter how
4581large the trees being joined together.
4582
4583\S{utils-copytree234} \cw{copytree234()}
4584
4585\c tree234 *copytree234(tree234 *t, copyfn234 copyfn,
4586\c void *copyfnstate);
4587
4588Makes a copy of an entire tree.
4589
4590If \c{copyfn} is \cw{NULL}, the tree will be copied but the elements
4591will not be; i.e. the new tree will contain pointers to exactly the
4592same physical elements as the old one.
4593
4594If you want to copy each actual element during the operation, you
4595can instead pass a function in \c{copyfn} which makes a copy of each
4596element. That function has the prototype
4597
4598\c typedef void *(*copyfn234)(void *state, void *element);
4599
4600and every time it is called, the \c{state} parameter will be set to
4601the value you passed in as \c{copyfnstate}.
4602
4603\H{utils-dsf} Disjoint set forests
4604
4605This section describes a set of functions implementing the data
4606structure variously known as \q{union-find} or \q{Tarjan's disjoint
4607set forest}. In this code base, it's universally abbreviated as a
4608\q{dsf}.
4609
4610A dsf represents a collection of elements partitioned into
4611\q{equivalence classes}, in circumstances where equivalences are added
4612incrementally. That is, all elements start off considered to be
4613different, and you gradually declare more and more of them to be equal
4614via the \cw{dsf_merge()} operation, which says that two particular
4615elements should be regarded as equal from now on.
4616
4617For example, if I start off with A,B,U,V all distinct, and I merge A
4618with B and merge U with V, then the structure will tell me that A and
4619U are not equivalent. But if I then merge B with V, then after that,
4620the structure will tell me that A and U \e{are} equivalent, by
4621following the transitive chain of equivalences it knows about.
4622
4623The dsf data structure is therefore ideal for tracking incremental
4624connectivity in an undirected graph (again, \q{incremental} meaning
4625that you only ever add edges, never delete them), and other
4626applications in which you gradually acquire knowledge you didn't
4627previously have about what things are the same as each other. It's
4628used extensively in puzzle solver and generator algorithms, and
4629sometimes during gameplay as well.
4630
4631The time complexity of dsf operations is not \e{quite} constant time,
4632in theory, but it's so close to it as to make no difference in
4633practice. In particular, any time a dsf has to do non-trivial work, it
4634updates the structure so that that work won't be needed a second time.
4635Use dsf operations without worrying about how long they take!
4636
4637For some puzzle-game applications, it's useful to augment this data
4638structure with extra information about how the elements of an
4639equivalence class relate to each other. There's more than one way you
4640might do this; the one supported here is useful in cases where the
4641objects you're tracking are going to end up in one of two states (say,
4642black/white, or on/off), and for any two objects you either know that
4643they're in the same one of those states, or you know they're in
4644opposite states, or you don't know which yet. Puzzles calls this a
4645\q{flip dsf}: it tracks whether objects in the same equivalence class
4646are flipped relative to each other.
4647
4648As well as querying whether two elements are equivalent, this dsf
4649implementation also allows you to ask for the number of elements in a
4650given equivalence class, and the smallest element in the class. (The
4651latter is used, for example, to decide which square to print the clue
4652in each region of a Keen puzzle.)
4653
4654\S{utils-dsf-new} \cw{dsf_new()}, \cw{dsf_new_flip()}, \cw{dsf_new_min()}
4655
4656\c DSF *dsf_new(int size);
4657\c DSF *dsf_new_flip(int size);
4658\c DSF *dsf_new_min(int size);
4659
4660Each of these functions allocates space for a dsf describing \c{size}
4661elements, and initialises it so that every element is in an
4662equivalence class by itself.
4663
4664The elements described by the dsf are represented by the integers from
4665\cw{0} to \cw{size-1} inclusive.
4666
4667\cw{dsf_new_flip()} will create a dsf which has the extra ability to
4668track whether objects in the same equivalence class are flipped
4669relative to each other.
4670
4671\cw{dsf_new_min()} will create a dsf which has the extra ability to
4672track the smallest element of each equivalence class.
4673
4674The returned object from any of these functions must be freed using
4675\cw{dsf_free()}.
4676
4677\S{utils-dsf-free} \cw{dsf_free()}
4678
4679\c void dsf_free(DSF *dsf);
4680
4681Frees a dsf allocated by any of the \cw{dsf_new()} functions.
4682
4683\S{utils-dsf-reinit} \cw{dsf_reinit()}
4684
4685\c void dsf_reinit(DSF *dsf);
4686
4687Reinitialises an existing dsf to the state in which all elements are
4688distinct, without having to free and reallocate it.
4689
4690\S{utils-dsf-copy} \cw{dsf_copy()}
4691
4692\c void dsf_copy(DSF *to, DSF *from);
4693
4694Copies the contents of one dsf over the top of another. Everything
4695previously stored in \c{to} is overwritten.
4696
4697The two dsfs must have been created with the same size, and the
4698destination dsf may not have any extra information that the source dsf
4699does not have.
4700
4701\S{utils-dsf-merge} \cw{dsf_merge()}
4702
4703\c void dsf_merge(DSF *dsf, int v1, int v2);
4704
4705Updates a dsf so that elements \c{v1} and \c{v2} will now be
4706considered to be in the same equivalence class. If they were already
4707in the same class, this function will safely do nothing.
4708
4709This function may not be called on a flip dsf. Use \cw{dsf_merge_flip}
4710instead.
4711
4712\S{utils-dsf-canonify} \cw{dsf_canonify()}
4713
4714\c int dsf_canonify(DSF *dsf, int val);
4715
4716Returns the \q{canonical} element of the equivalence class in the dsf
4717containing \c{val}. This will be some element of the same equivalence
4718class. So in order to determine whether two elements are in the same
4719equivalence class, you can call \cw{dsf_canonify} on both of them, and
4720compare the results.
4721
4722Canonical elements don't necessarily stay the same if the dsf is
4723mutated via \c{dsf_merge}. But between two calls to \c{dsf_merge},
4724they stay the same.
4725
4726\S{utils-dsf-size} \cw{dsf_size()}
4727
4728\c int dsf_size(DSF *dsf, int val);
4729
4730Returns the number of elements currently in the equivalence class
4731containing \c{val}.
4732
4733\c{val} itself counts, so in a newly created dsf, the return value
4734will be 1.
4735
4736\S{utils-dsf-merge-flip} \cw{dsf_merge_flip()}
4737
4738\c void edsf_merge(DSF *dsf, int v1, int v2, bool flip);
4739
4740Updates a flip dsf so that elements \c{v1} and \c{v2} are in the same
4741equivalence class. If \c{flip} is \cw{false}, they will be regarded as
4742in the same state as each other; if \c{flip} is \cw{true} then they
4743will be regarded as being in opposite states.
4744
4745If \c{v1} and \c{v2} were already in the same equivalence class, then
4746the new value of \c{flip} will be checked against what the edsf
4747previously believed, and an assertion failure will occur if you
4748contradict that.
4749
4750For example, if you start from a blank flip dsf and do this:
4751
4752\c dsf_merge_flip(dsf, 0, 1, false);
4753\c dsf_merge_flip(dsf, 1, 2, true);
4754
4755then it will create a dsf in which elements 0,1,2 are all in the same
4756class, with 0,1 in the same state as each other and 2 in the opposite
4757state from both. And then this call will do nothing, because it agrees
4758with what the dsf already knew:
4759
4760\c dsf_merge_flip(dsf, 0, 2, true);
4761
4762But this call will fail an assertion:
4763
4764\c dsf_merge_flip(dsf, 0, 2, false);
4765
4766\S{utils-dsf-canonify-flip} \cw{dsf_canonify_flip()}
4767
4768\c int dsf_canonify_flip(DSF *dsf, int val, bool *inverse);
4769
4770Like \c{dsf_canonify()}, this returns the canonical element of the
4771equivalence class of a dsf containing \c{val}.
4772
4773However, it may only be called on a flip dsf, and it also fills in
4774\c{*flip} with a flag indicating whether \c{val} and the canonical
4775element are in opposite states: \cw{true} if they are in opposite
4776states, or \cw{false} if they're in the same state.
4777
4778So if you want to know the relationship between \c{v1} and \c{v2}, you
4779can do this:
4780
4781\c bool inv1, inv2;
4782\c int canon1 = dsf_canonify_flip(dsf, v1, &inv1);
4783\c int canon2 = dsf_canonify_flip(dsf, v2, &inv2);
4784\c if (canon1 != canon2) {
4785\c // v1 and v2 have no known relation
4786\c } else if (inv1 == inv2) {
4787\c // v1 and v2 are known to be in the same state as each other
4788\c } else {
4789\c // v1 and v2 are known to be in opposite states
4790\c }
4791
4792\S{utils-dsf-minimal} \cw{dsf_minimal()}
4793
4794\c int dsf_minimal(DSF *dsf, int val);
4795
4796Returns the smallest element of the equivalence class in the dsf
4797containing \c{val}.
4798
4799For this function to work, the dsf must have been created using
4800\cw{dsf_new_min()}.
4801
4802\H{utils-tdq} To-do queues
4803
4804This section describes a set of functions implementing a \q{to-do
4805queue}, a simple de-duplicating to-do list mechanism. The code calls
4806this a \q{tdq}.
4807
4808A tdq can store integers up to a given size (specified at creation
4809time). But it can't store the same integer more than once. So you can
4810quickly \e{make sure} an integer is in the queue (which will do
4811nothing if it's already there), and you can quickly pop an integer
4812from the queue and return it, both in constant time.
4813
4814The idea is that you might use this in a game solver, in the kind of
4815game where updating your knowledge about one square of a grid means
4816there's a specific other set of squares (such as its neighbours) where
4817it's now worth attempting further deductions. So you keep a tdq of all
4818the grid squares you plan to look at next, and every time you make a
4819deduction in one square, you add the neighbouring squares to the tdq
4820to make sure they get looked at again after that.
4821
4822In solvers where deductions are mostly localised, this avoids the
4823slowdown of having to find the next thing to do every time by looping
4824over the whole grid: instead, you can keep checking the tdq for
4825\e{specific} squares to look at, until you run out.
4826
4827However, it's common to have games in which \e{most} deductions are
4828localised, but not all. In that situation, when your tdq is empty, you
4829can re-fill it with every square in the grid using \cw{tdq_fill()},
4830which will force an iteration over everything again. And then if the
4831tdq becomes empty \e{again} without you having made any progress, give
4832up.
4833
4834\S{utils-tdq-new} \cw{tdq_new()}
4835
4836\c tdq *tdq_new(int n);
4837
4838Allocates space for a tdq that tracks items from \cw{0} to \cw{size-1}
4839inclusive.
4840
4841\S{utils-tdq-free} \cw{tdq_free()}
4842
4843\c void tdq_free(tdq *tdq);
4844
4845Frees a tdq.
4846
4847\S{utils-tdq-add} \cw{tdq_add()}
4848
4849\c void tdq_add(tdq *tdq, int k);
4850
4851Adds the value \c{k} to a tdq. If \c{k} was already in the to-do list,
4852does nothing.
4853
4854\S{utils-tdq-remove} \cw{tdq_remove()}
4855
4856\c int tdq_remove(tdq *tdq);
4857
4858Removes one item from the tdq, and returns it. If the tdq is empty,
4859returns \cw{-1}.
4860
4861\S{utils-tdq-fill} \cw{tdq_fill()}
4862
4863\c void tdq_fill(tdq *tdq);
4864
4865Fills a tdq with every element it can possibly keep track of.
4866
4867\H{utils-findloop} Finding loops in graphs and grids
4868
4869Many puzzles played on grids or graphs have a common gameplay element
4870of connecting things together into paths in such a way that you need
4871to avoid making loops (or, perhaps, making the \e{wrong} kind of
4872loop).
4873
4874Just determining \e{whether} a loop exists in a graph is easy, using a
4875dsf tracking connectivity between the vertices. Simply iterate over
4876each edge of the graph, merging the two vertices at each end of the
4877edge \dash but before you do that, check whether those vertices are
4878\e{already} known to be connected to each other, and if they are, then
4879the new edge is about to complete a loop.
4880
4881But if you also want to identify \e{exactly} the set of edges that are
4882part of any loop, e.g. to highlight the whole loop red during
4883gameplay, then that's a harder problem. This API is provided here for
4884all puzzles to use for that purpose.
4885
4886\S{utils-findloop-new-state} \cw{findloop_new_state()}
4887
4888\c struct findloopstate *findloop_new_state(int nvertices);
4889
4890Allocates a new state structure for the findloop algorithm, capable of
4891handling a graph with up to \c{nvertices} vertices. The vertices will
4892be represented by integers between \c{0} and \c{nvertices-1} inclusive.
4893
4894\S{utils-findloop-free-state} \cw{findloop_free_state()}
4895
4896\c void findloop_free_state(struct findloopstate *state);
4897
4898Frees a state structure allocated by \cw{findloop_new_state()}.
4899
4900\S{utils-findloop-run} \cw{findloop_run()}
4901
4902\c bool findloop_run(struct findloopstate *state, int nvertices,
4903\c neighbour_fn_t neighbour, void *ctx);
4904
4905Runs the loop-finding algorithm, which will explore the graph and
4906identify whether each edge is or is not part of any loop.
4907
4908The algorithm will call the provided function \c{neighbour} to list
4909the neighbouring vertices of each vertex. It should have this
4910prototype:
4911
4912\c int neighbour(int vertex, void *ctx);
4913
4914In this callback, \c{vertex} will be the index of a vertex when the
4915algorithm \e{first} calls it for a given vertex. The function should
4916return the index of one of that vertex's neighbours, or a negative
4917number if there are none.
4918
4919If the function returned a vertex, the algorithm will then call
4920\c{neighbour} again with a \e{negative} number as the \c{vertex}
4921parameter, which means \q{please give me another neighbour of the same
4922vertex as last time}. Again, the function should return a vertex
4923index, or a negative number to indicate that there are no more
4924vertices.
4925
4926The \c{ctx} parameter passed to \cw{findloop_run()} is passed on
4927unchanged to \c{neighbour}, so you can point that at your game state
4928or solver state or whatever.
4929
4930The return value is \cw{true} if at least one loop exists in the
4931graph, and \cw{false} if no loop exists. Also, the algorithm state
4932will have been filled in with information that the following query
4933functions can use to ask about individual graph edges.
4934
4935\S{utils-findloop-is-loop-edge} \cw{findloop_is_loop_edge()}
4936
4937\c bool findloop_is_loop_edge(struct findloopstate *state,
4938\c int u, int v);
4939
4940Queries whether the graph edge between vertices \c{u} and \c{v} is
4941part of a loop. If so, the return value is \cw{true}, otherwise
4942\cw{false}.
4943
4944\S{utils-findloop-is-bridge} \cw{findloop_is_bridge()}
4945
4946\c bool findloop_is_bridge(struct findloopstate *pv,
4947\c int u, int v, int *u_vertices, int *v_vertices);
4948
4949Queries whether the graph edge between vertices \c{u} and \c{v} is a
4950\q{bridge}, i.e. an edge which would break the graph into (more)
4951disconnected components if it were removed.
4952
4953This is the exact inverse of the \q{loop edge} criterion: a vertex
4954returns \cw{true} from \cw{findloop_is_loop_edge()} if and only if it
4955returns \cw{false} from \cw{findloop_is_bridge()}, and vice versa.
4956
4957However, \cw{findloop_is_bridge()} returns more information. If it
4958returns \cw{true}, then it also fills in \c{*u_vertices} and
4959\c{*v_vertices} with the number of vertices connected to the \c{u} and
4960\c{v} sides of the bridge respectively.
4961
4962For example, if you have three vertices A,B,C all connected to each
4963other, and four vertices U,V,W,X all connected to each other, and a
4964single edge between A and V, then calling \cw{findloop_is_bridge()} on
4965the pair A,V will return true (removing that edge would separate the
4966two sets from each other), and will report that there are three
4967vertices on the A side and four on the V side.
4968
4969\H{utils-combi} Choosing r things out of n
4970
4971This section describes a small API for iterating over all combinations
4972of r things out of n.
4973
4974For example, if you asked for all combinations of 3 things out of 5,
4975you'd get back the sets \{0,1,2\}, \{0,1,3\}, \{0,1,4\}, \{0,2,3\},
4976\{0,2,4\}, \{0,3,4\}, \{1,2,3\}, \{1,2,4\}, \{1,3,4\}, and \{2,3,4\}.
4977
4978These functions use a structure called a \c{combi_ctx}, which contains
4979an element \c{int *a} holding each returned combination, plus other
4980fields for implementation use only.
4981
4982\S{utils-combi-new} \cw{new_combi()}
4983
4984\c combi_ctx *new_combi(int r, int n);
4985
4986Allocates a new \c{combi_ctx} structure for enumerating r things out
4987of n.
4988
4989\S{utils-combi-free} \cw{free_combi()}
4990
4991\c void free_combi(combi_ctx *combi);
4992
4993Frees a \c{combi_ctx} structure.
4994
4995\S{utils-combi-reset} \cw{reset_combi()}
4996
4997\c void reset_combi(combi_ctx *combi);
4998
4999Resets an existing \c{combi_ctx} structure to the start of its
5000iteration
5001
5002\S{utils-combi-next} \cw{next_combi()}
5003
5004\c combi_ctx *next_combi(combi_ctx *combi);
5005
5006Requests a combination from a \c{combi_ctx}.
5007
5008If there are none left to return, the return value is \cw{NULL}.
5009Otherwise, it returns the input structure \c{combi}, indicating that
5010it has filled in \cw{combi->a[0]}, \cw{combi->a[1]}, ...,
5011\cw{combi->a[r-1]} with an increasing sequence of distinct integers
5012from \cw{0} to \cw{n-1} inclusive.
5013
5014\H{utils-misc} Miscellaneous utility functions and macros
5015
5016This section contains all the utility functions which didn't
5017sensibly fit anywhere else.
5018
5019\S{utils-maxmin} \cw{max()} and \cw{min()}
5020
5021The main Puzzles header file defines the pretty standard macros
5022\cw{max()} and \cw{min()}, each of which is given two arguments and
5023returns the one which compares greater or less respectively.
5024
5025These macros may evaluate their arguments multiple times. Avoid side
5026effects.
5027
5028\S{utils-max-digits} \cw{MAX_DIGITS()}
5029
5030The \cw{MAX_DIGITS()} macro, defined in the main Puzzles header file,
5031takes a type (or a variable of that type) and expands to an integer
5032constant representing a reasonable upper bound on the number of
5033characters that a number of that type could expand to when formatted
5034as a decimal number using the \c{%u} or \c{%d} format of
5035\cw{printf()}. This is useful for allocating a fixed-size buffer
5036that's guaranteed to be big enough to \cw{sprintf()} a value into.
5037Don't forget to add one for the trailing \cw{'\\0'}!
5038
5039\S{utils-pi} \cw{PI}
5040
5041The main Puzzles header file defines a macro \cw{PI} which expands
5042to a floating-point constant representing pi.
5043
5044(I've never understood why ANSI's \cw{<math.h>} doesn't define this.
5045It'd be so useful!)
5046
5047\S{utils-obfuscate-bitmap} \cw{obfuscate_bitmap()}
5048
5049\c void obfuscate_bitmap(unsigned char *bmp, int bits, bool decode);
5050
5051This function obscures the contents of a piece of data, by
5052cryptographic methods. It is useful for games of hidden information
5053(such as Mines, Guess or Black Box), in which the game ID
5054theoretically reveals all the information the player is supposed to
5055be trying to guess. So in order that players should be able to send
5056game IDs to one another without accidentally spoiling the resulting
5057game by looking at them, these games obfuscate their game IDs using
5058this function.
5059
5060Although the obfuscation function is cryptographic, it cannot
5061properly be called encryption because it has no key. Therefore,
5062anybody motivated enough can re-implement it, or hack it out of the
5063Puzzles source, and strip the obfuscation off one of these game IDs
5064to see what lies beneath. (Indeed, they could usually do it much
5065more easily than that, by entering the game ID into their own copy
5066of the puzzle and hitting Solve.) The aim is not to protect against
5067a determined attacker; the aim is simply to protect people who
5068wanted to play the game honestly from \e{accidentally} spoiling
5069their own fun.
5070
5071The input argument \c{bmp} points at a piece of memory to be
5072obfuscated. \c{bits} gives the length of the data. Note that that
5073length is in \e{bits} rather than bytes: if you ask for obfuscation
5074of a partial number of bytes, then you will get it. Bytes are
5075considered to be used from the top down: thus, for example, setting
5076\c{bits} to 10 will cover the whole of \cw{bmp[0]} and the \e{top
5077two} bits of \cw{bmp[1]}. The remainder of a partially used byte is
5078undefined (i.e. it may be corrupted by the function).
5079
5080The parameter \c{decode} is \cw{false} for an encoding operation,
5081and \cw{true} for a decoding operation. Each is the inverse of the
5082other. (There's no particular reason you shouldn't obfuscate by
5083decoding and restore cleartext by encoding, if you really wanted to;
5084it should still work.)
5085
5086The input bitmap is processed in place.
5087
5088\S{utils-bin2hex} \cw{bin2hex()}
5089
5090\c char *bin2hex(const unsigned char *in, int inlen);
5091
5092This function takes an input byte array and converts it into an
5093ASCII string encoding those bytes in (lower-case) hex. It returns a
5094dynamically allocated string containing that encoding.
5095
5096This function is useful for encoding the result of
5097\cw{obfuscate_bitmap()} in printable ASCII for use in game IDs.
5098
5099\S{utils-hex2bin} \cw{hex2bin()}
5100
5101\c unsigned char *hex2bin(const char *in, int outlen);
5102
5103This function takes an ASCII string containing hex digits, and
5104converts it back into a byte array of length \c{outlen}. If there
5105aren't enough hex digits in the string, the contents of the
5106resulting array will be undefined.
5107
5108This function is the inverse of \cw{bin2hex()}.
5109
5110\S{utils-fgetline} \cw{fgetline()}
5111
5112\c char *fgetline(FILE *fp);
5113
5114This function reads a single line of text from a standard C input
5115stream, and returns it as a dynamically allocated string. The returned
5116string still has a newline on the end.
5117
5118\S{utils-arraysort} \cw{arraysort()}
5119
5120Sorts an array, with slightly more flexibility than the standard C
5121\cw{qsort()}.
5122
5123This function is really implemented as a macro, so it doesn't have a
5124prototype as such. But you could imagine it having a prototype like
5125this:
5126
5127\c void arraysort(element_t *array, size_t nmemb,
5128\c arraysort_cmpfn_t cmp, void *ctx);
5129
5130in which \c{element_t} is an unspecified type.
5131
5132(Really, there's an underlying function that takes an extra parameter
5133giving the size of each array element. But callers are encouraged to
5134use this macro version, which fills that in automatically using
5135\c{sizeof}.)
5136
5137This function behaves essentially like \cw{qsort()}: it expects
5138\c{array} to point to an array of \c{nmemb} elements, and it will sort
5139them in place into the order specified by the comparison function
5140\c{cmp}.
5141
5142The comparison function should have this prototype:
5143
5144\c int cmp(const void *a, const void *b, void *ctx);
5145
5146in which \c{a} and \c{b} point at the two elements to be compared, and
5147the return value is negative if \cw{a<b} (that is, \c{a} should appear
5148before \c{b} in the output array), positive if \cw{a>b}, or zero if
5149\c{a=b}.
5150
5151The \c{ctx} parameter to \cw{arraysort()} is passed directly to the
5152comparison function. This is the feature that makes \cw{arraysort()}
5153more flexible than standard \cw{qsort()}: it lets you vary the sorting
5154criterion in a dynamic manner without having to write global variables
5155in the caller for the compare function to read.
5156
5157\S{utils-colour-mix} \cw{colour_mix()}
5158
5159\c void colour_mix(const float src1[3], const float src2[3], float p,
5160\c float dst[3]);
5161
5162This function mixes the colours \c{src1} and \c{src2} in specified
5163proportions, producing \c{dst}. \c{p} is the proportion of \c{src2}
5164in the result. So if \c{p} is \cw{1.0}, \cw{dst} will be the same as
5165\c{src2}. If \c{p} is \cw{0.0}, \cw{dst} will be the same as
5166\c{src1}. And if \c{p} is somewhere in between, so will \c{dst} be.
5167\c{p} is not restricted to the range \cw{0.0} to \cw{1.0}. Values
5168outside that range will produce extrapolated colours, which may be
5169useful for some purposes, but may also produce impossible colours.
5170
5171\S{utils-game-mkhighlight} \cw{game_mkhighlight()}
5172
5173\c void game_mkhighlight(frontend *fe, float *ret,
5174\c int background, int highlight, int lowlight);
5175
5176It's reasonably common for a puzzle game's graphics to use
5177highlights and lowlights to indicate \q{raised} or \q{lowered}
5178sections. Fifteen, Sixteen and Twiddle are good examples of this.
5179
5180Puzzles using this graphical style are running a risk if they just
5181use whatever background colour is supplied to them by the front end,
5182because that background colour might be too light or dark to see any
5183highlights on at all. (In particular, it's not unheard of for the
5184front end to specify a default background colour of white.)
5185
5186Therefore, such puzzles can call this utility function from their
5187\cw{colours()} routine (\k{backend-colours}). You pass it your front
5188end handle, a pointer to the start of your return array, and three
5189colour indices. It will:
5190
5191\b call \cw{frontend_default_colour()} (\k{frontend-default-colour})
5192to fetch the front end's default background colour
5193
5194\b alter the brightness of that colour if it's unsuitable
5195
5196\b define brighter and darker variants of the colour to be used as
5197highlights and lowlights
5198
5199\b write those results into the relevant positions in the \c{ret}
5200array.
5201
5202Thus, \cw{ret[background*3]} to \cw{ret[background*3+2]} will be set
5203to RGB values defining a sensible background colour, and similary
5204\c{highlight} and \c{lowlight} will be set to sensible colours.
5205
5206Either \c{highlight} or \c{lowlight} may be passed in as \cw{-1} to
5207indicate that the back-end does not require a highlight or lowlight
5208colour, respectively.
5209
5210\S{utils-game-mkhighlight-specific} \cw{game_mkhighlight_specific()}
5211
5212\c void game_mkhighlight_specific(frontend *fe, float *ret,
5213\c int background, int highlight, int lowlight);
5214
5215This function behaves exactly like \cw{game_mkhighlight()}, except
5216that it expects the background colour to have been filled in
5217\e{already} in the elements \cw{ret[background*3]} to
5218\cw{ret[background*3+2]}. It will fill in the other two colours as
5219brighter and darker versions of that.
5220
5221This is useful if you want to show relief sections of a puzzle in more
5222than one base colour.
5223
5224\S{utils-button2label} \cw{button2label()}
5225
5226\c char *button2label(int button);
5227
5228This function generates a descriptive text label for \cw{button},
5229which should be a button code that can be passed to the midend. For
5230example, calling this function with \cw{CURSOR_UP} will result in the
5231string \cw{"Up"}. This function should only be called when the
5232\cw{key_label} item returned by a backend's \cw{request_keys()}
5233(\k{backend-request-keys}) function has its \cw{label} field set to
5234\cw{NULL}; in this case, the corresponding \cw{button} field can be
5235passed to this function to obtain an appropriate label. If, however,
5236the field is not \cw{NULL}, this function should not be called with
5237the corresponding \cw{button} field.
5238
5239The returned string is dynamically allocated and should be
5240\cw{sfree}'d by the caller.
5241
5242\S{utils-move-cursor} \cw{move_cursor()}
5243
5244\c char *move_cursor(int button, int *x, int *y, int w, int h,
5245\c bool wrap, bool *visible);
5246
5247This function can be called by \cw{interpret_move()} to implement the
5248default keyboard API for moving a cursor around a grid.
5249
5250\c{button} is the same value passed in to \cw{interpret_move()}. If
5251it's not any of \cw{CURSOR_UP}, \cw{CURSOR_DOWN}, \cw{CURSOR_LEFT} or
5252\cw{CURSOR_RIGHT}, the function will do nothing.
5253
5254\c{x} and \c{y} point to two integers which on input give the current
5255location of a cursor in a square grid. \c{w} and \c{h} give the
5256dimensions of the grid. On return, \c{x} and \c{y} are updated to give
5257the cursor's new position according to which arrow key was pressed.
5258
5259This function assumes that the grid coordinates run from \cw{0} to
5260\cw{w-1} inclusive (left to right), and from \cw{0} to \cw{h-1}
5261inclusive (top to bottom).
5262
5263If \c{wrap} is \cw{true}, then trying to move the cursor off any edge
5264of the grid will result in it wrapping round to the corresponding
5265square on the opposite edge. If \c{wrap} is \cw{false}, such a move
5266will have no effect.
5267
5268If \c{visible} is not \cw{NULL}, it points to a flag indicating
5269whether the cursor is visible. This will be set to \cw{true} if
5270\c{button} represents a cursor-movement key.
5271
5272The function returns one of the special constants that can be returned
5273by \cw{interpret_move()}. The return value is \cw{MOVE_UNUSED} if
5274\c{button} is unrecognised, \cw{MOVE_UI_UPDATE} if \c{x}, \c{y}, or
5275\c{visible} was updated, and \cw{MOVE_NO EFFECT} otherwise.
5276
5277\S{utils-divvy-rectangle} \cw{divvy_rectangle()}
5278
5279\c int *divvy_rectangle(int w, int h, int k, random_state *rs);
5280
5281Invents a random division of a rectangle into same-sized polyominoes,
5282such as is found in the block layout of a Solo puzzle in jigsaw mode,
5283or the solution to a Palisade puzzle.
5284
5285\c{w} and \c{h} are the dimensions of the rectangle. \c{k} is the size
5286of polyomino desired. It must be a factor of \c{w*h}.
5287
5288\c{rs} is a \cw{random_state} used to supply the random numbers to
5289select a random division of the rectangle.
5290
5291The return value is a dsf (see \k{utils-dsf}) whose equivalence
5292classes correspond to the polyominoes that the rectangle is divided
5293into. The indices of the dsf are of the form \c{y*w+x}, for the cell
5294with coordinates \cw{x,y}.
5295
5296\S{utils-domino-layout} \cw{domino_layout()}
5297
5298\c int *domino_layout(int w, int h, random_state *rs);
5299
5300Invents a random tiling of a rectangle with dominoes.
5301
5302\c{w} and \c{h} are the dimensions of the rectangle. If they are both
5303odd, then one square will be left untiled.
5304
5305\c{rs} is a \cw{random_state} used to supply the random numbers to
5306select a random division of the rectangle.
5307
5308The return value is an array in which element \c{y*w+x} represents the
5309cell with coordinates \cw{x,y}. Each element of the array gives the
5310index (in the same representation) of the other end of its domino. If
5311there's a left-over square, then that element contains its own index.
5312
5313\S{utils-domino-layout-prealloc} \cw{domino_layout_prealloc()}
5314
5315\c void domino_layout_prealloc(int w, int h, random_state *rs,
5316\c int *grid, int *grid2, int *list);
5317
5318Just like \cw{domino_layout()}, but does no memory allocation. You can
5319use this to save allocator overhead if you expect to need to generate
5320many domino tilings of the same grid.
5321
5322\c{grid} and \c{grid2} should each have space for \cw{w*h} ints.
5323\c{list} should have space for \c{2*w*h} ints.
5324
5325The returned array is delivered in \c{grid}.
5326
5327\S{utils-strip-button-modifiers} \cw{STRIP_BUTTON_MODIFIERS()}
5328
5329This macro, defined in the main Puzzles header file, strips the
5330modifier flags from the key code passed as an argument. It is
5331equivalent to a bitwise-AND with \cw{~MOD_MASK}.
5332
5333\S{utils-swap-regions} \cw{swap_regions()}
5334
5335\c void swap_regions(void *av, void *bv, size_t size);
5336
5337Swap two regions of memory of \cw{size} bytes. The two regions must
5338not overlap.
5339
5340\C{writing} How to write a new puzzle
5341
5342This chapter gives a guide to how to actually write a new puzzle:
5343where to start, what to do first, how to solve common problems.
5344
5345The previous chapters have been largely composed of facts. This one
5346is mostly advice.
5347
5348\H{writing-editorial} Choosing a puzzle
5349
5350Before you start writing a puzzle, you have to choose one. Your
5351taste in puzzle games is up to you, of course; and, in fact, you're
5352probably reading this guide because you've \e{already} thought of a
5353game you want to write. But if you want to get it accepted into the
5354official Puzzles distribution, then there's a criterion it has to
5355meet.
5356
5357The current Puzzles editorial policy is that all games should be
5358\e{fair}. A fair game is one which a player can only fail to complete
5359through demonstrable lack of skill \dash that is, such that a better
5360player presented with the same game state would have \e{known} to do
5361something different.
5362
5363For a start, that means every game presented to the user must have
5364\e{at least one solution}. Giving the unsuspecting user a puzzle which
5365is actually impossible is not acceptable.
5366
5367(An exception to this: if the user has selected some non-default
5368option which is clearly labelled as potentially unfair, \e{then}
5369you're allowed to generate possibly insoluble puzzles, because the
5370user isn't unsuspecting any more. Same Game and Mines both have
5371options of this type.)
5372
5373Secondly, if the game includes hidden information, then it must be
5374possible to deduce a correct move at every stage from the currently
5375available information. It's not enough that there should exist some
5376sequence of moves which will get from the start state to the solved
5377state, if the player doesn't necessarily have enough information to
5378\e{find} that solution. For example, in the card solitaire game
5379Klondike, it's possible to reach a dead end because you had an
5380arbitrary choice to make on no information, and made it the wrong way,
5381which violates the fairness criterion, because a better player
5382couldn't have known they needed to make the other choice.
5383
5384(Of course, games in this collection always have an Undo function, so
5385if you did take the wrong route through a Klondike game, you could use
5386Undo to back up and try a different choice. This doesn't count. In a
5387fair game, you should be able to determine a correct move from the
5388information visible \e{now}, without having to make moves to get more
5389information that you can then back up and use.)
5390
5391Sometimes you can adjust the rules of an unfair puzzle to make it meet
5392this definition of fairness. For example, more than one implementation
5393of solitaire-style games (including card solitaires and Mahjong
5394Solitaire) include a UI action to shuffle the remaining cards or tiles
5395without changing their position; this action might be available at any
5396time with a time or points penalty, or it might be illegal to use
5397unless you have no other possible move. Adding an option like this
5398would make a game \e{technically} fair, but it's better to avoid even
5399that if you can.
5400
5401Providing a \e{unique} solution is a little more negotiable; it
5402depends on the puzzle. Solo would have been of unacceptably low
5403quality if it didn't always have a unique solution, whereas Twiddle
5404inherently has multiple solutions by its very nature and it would
5405have been meaningless to even \e{suggest} making it uniquely
5406soluble. Somewhere in between, Flip could reasonably be made to have
5407unique solutions (by enforcing a zero-dimension kernel in every
5408generated matrix) but it doesn't seem like a serious quality problem
5409that it doesn't.
5410
5411Of course, you don't \e{have} to care about all this. There's
5412nothing stopping you implementing any puzzle you want to if you're
5413happy to maintain your puzzle yourself, distribute it from your own
5414web site, fork the Puzzles code completely, or anything like that.
5415It's free software; you can do what you like with it. But any game
5416that you want to be accepted into \e{my} Puzzles code base has to
5417satisfy the fairness criterion, which means all randomly generated
5418puzzles must have a solution (unless the user has deliberately
5419chosen otherwise) and it must be possible \e{in theory} to find that
5420solution without having to guess.
5421
5422\H{writing-gs} Getting started
5423
5424The simplest way to start writing a new puzzle is to copy
5425\c{nullgame.c}. This is a template puzzle source file which does
5426almost nothing, but which contains all the back end function
5427prototypes and declares the back end data structure correctly. It is
5428built every time the rest of Puzzles is built, to ensure that it
5429doesn't get out of sync with the code and remains buildable.
5430
5431So start by copying \c{nullgame.c} into your new source file. Then
5432you'll gradually add functionality until the very boring Null Game
5433turns into your real game.
5434
5435Next you'll need to add your puzzle to the build scripts, in order to
5436compile it conveniently. Puzzles is a CMake project, so you do this by
5437adding a \cw{puzzle()} statement to CMakeLists.txt. Look at the
5438existing ones to see what those look like, and add one that looks
5439similar.
5440
5441Once your source file is building, you can move on to the fun bit.
5442
5443\S{writing-generation} Puzzle generation
5444
5445Randomly generating instances of your puzzle is almost certain to be
5446the most difficult part of the code, and also the task with the
5447highest chance of turning out to be completely infeasible. Therefore
5448I strongly recommend doing it \e{first}, so that if it all goes
5449horribly wrong you haven't wasted any more time than you absolutely
5450had to. What I usually do is to take an unmodified \c{nullgame.c},
5451and start adding code to \cw{new_game_desc()} which tries to
5452generate a puzzle instance and print it out using \cw{printf()}.
5453Once that's working, \e{then} I start connecting it up to the return
5454value of \cw{new_game_desc()}, populating other structures like
5455\c{game_params}, and generally writing the rest of the source file.
5456
5457There are many ways to generate a puzzle which is known to be
5458soluble. In this section I list all the methods I currently know of,
5459in case any of them can be applied to your puzzle. (Not all of these
5460methods will work, or in some cases even make sense, for all
5461puzzles.)
5462
5463Some puzzles are mathematically tractable, meaning you can work out
5464in advance which instances are soluble. Sixteen, for example, has a
5465parity constraint in some settings which renders exactly half the
5466game space unreachable, but it can be mathematically proved that any
5467position not in that half \e{is} reachable. Therefore, Sixteen's
5468grid generation simply consists of selecting at random from a well
5469defined subset of the game space. Cube in its default state is even
5470easier: \e{every} possible arrangement of the blue squares and the
5471cube's starting position is soluble!
5472
5473Another option is to redefine what you mean by \q{soluble}. Black
5474Box takes this approach. There are layouts of balls in the box which
5475are completely indistinguishable from one another no matter how many
5476beams you fire into the box from which angles, which would normally
5477be grounds for declaring those layouts unfair; but fortunately,
5478detecting that indistinguishability is computationally easy. So
5479Black Box doesn't demand that your ball placements match its own; it
5480merely demands that your ball placements be \e{indistinguishable}
5481from the ones it was thinking of. If you have an ambiguous puzzle,
5482then any of the possible answers is considered to be a solution.
5483Having redefined the rules in that way, any puzzle is soluble again.
5484
5485Those are the simple techniques. If they don't work, you have to get
5486cleverer.
5487
5488One way to generate a soluble puzzle is to start from the solved
5489state and make inverse moves until you reach a starting state. Then
5490you know there's a solution, because you can just list the inverse
5491moves you made and make them in the opposite order to return to the
5492solved state.
5493
5494This method can be simple and effective for puzzles where you get to
5495decide what's a starting state and what's not. In Pegs, for example,
5496the generator begins with one peg in the centre of the board and
5497makes inverse moves until it gets bored; in this puzzle, valid
5498inverse moves are easy to detect, and \e{any} state that's reachable
5499from the solved state by inverse moves is a reasonable starting
5500position. So Pegs just continues making inverse moves until the
5501board satisfies some criteria about extent and density, and then
5502stops and declares itself done.
5503
5504For other puzzles, it can be a lot more difficult. Same Game uses
5505this strategy too, and it's lucky to get away with it at all: valid
5506inverse moves aren't easy to find (because although it's easy to
5507insert additional squares in a Same Game position, it's difficult to
5508arrange that \e{after} the insertion they aren't adjacent to any
5509other squares of the same colour), so you're constantly at risk of
5510running out of options and having to backtrack or start again. Also,
5511Same Game grids never start off half-empty, which means you can't
5512just stop when you run out of moves \dash you have to find a way to
5513fill the grid up \e{completely}.
5514
5515The other way to generate a puzzle that's soluble is to start from
5516the other end, and actually write a \e{solver}. This tends to ensure
5517that a puzzle has a \e{unique} solution over and above having a
5518solution at all, so it's a good technique to apply to puzzles for
5519which that's important.
5520
5521One theoretical drawback of generating soluble puzzles by using a
5522solver is that your puzzles are restricted in difficulty to those
5523which the solver can handle. (Most solvers are not fully general:
5524many sets of puzzle rules are NP-complete or otherwise nasty, so
5525most solvers can only handle a subset of the theoretically soluble
5526puzzles.) It's been my experience in practice, however, that this
5527usually isn't a problem; computers are good at very different things
5528from humans, and what the computer thinks is nice and easy might
5529still be pleasantly challenging for a human. For example, when
5530solving Dominosa puzzles I frequently find myself using a variety of
5531reasoning techniques that my solver doesn't know about; in
5532principle, therefore, I should be able to solve the puzzle using
5533only those techniques it \e{does} know about, but this would involve
5534repeatedly searching the entire grid for the one simple deduction I
5535can make. Computers are good at this sort of exhaustive search, but
5536it's been my experience that human solvers prefer to do more complex
5537deductions than to spend ages searching for simple ones. So in many
5538cases I don't find my own playing experience to be limited by the
5539restrictions on the solver.
5540
5541(This isn't \e{always} the case. Solo is a counter-example;
5542generating Solo puzzles using a simple solver does lead to
5543qualitatively easier puzzles. Therefore I had to make the Solo
5544solver rather more advanced than most of them.)
5545
5546There are several different ways to apply a solver to the problem of
5547generating a soluble puzzle. I list a few of them below.
5548
5549The simplest approach is brute force: randomly generate a puzzle,
5550use the solver to see if it's soluble, and if not, throw it away and
5551try again until you get lucky. This is often a viable technique if
5552all else fails, but it tends not to scale well: for many puzzle
5553types, the probability of finding a uniquely soluble instance
5554decreases sharply as puzzle size goes up, so this technique might
5555work reasonably fast for small puzzles but take (almost) forever at
5556larger sizes. Still, if there's no other alternative it can be
5557usable: Pattern and Dominosa both use this technique. (However,
5558Dominosa has a means of tweaking the randomly generated grids to
5559increase the \e{probability} of them being soluble, by ruling out
5560one of the most common ambiguous cases. This improved generation
5561speed by over a factor of 10 on the highest preset!)
5562
5563An approach which can be more scalable involves generating a grid
5564and then tweaking it to make it soluble. This is the technique used
5565by Mines and also by Net: first a random puzzle is generated, and
5566then the solver is run to see how far it gets. Sometimes the solver
5567will get stuck; when that happens, examine the area it's having
5568trouble with, and make a small random change in that area to allow
5569it to make more progress. Continue solving (possibly even without
5570restarting the solver), tweaking as necessary, until the solver
5571finishes. Then restart the solver from the beginning to ensure that
5572the tweaks haven't caused new problems in the process of solving old
5573ones (which can sometimes happen).
5574
5575This strategy works well in situations where the usual solver
5576failure mode is to get stuck in an easily localised spot. Thus it
5577works well for Net and Mines, whose most common failure mode tends
5578to be that most of the grid is fine but there are a few widely
5579separated ambiguous sections; but it would work less well for
5580Dominosa, in which the way you get stuck is to have scoured the
5581whole grid and not found anything you can deduce \e{anywhere}. Also,
5582it relies on there being a low probability that tweaking the grid
5583introduces a new problem at the same time as solving the old one;
5584Mines and Net also have the property that most of their deductions
5585are local, so that it's very unlikely for a tweak to affect
5586something half way across the grid from the location where it was
5587applied. In Dominosa, by contrast, a lot of deductions use
5588information about half the grid (\q{out of all the sixes, only one
5589is next to a three}, which can depend on the values of up to 32 of
5590the 56 squares in the default setting!), so this tweaking strategy
5591would be rather less likely to work well.
5592
5593A more specialised strategy is that used in Solo and Slant. These
5594puzzles have the property that they derive their difficulty from not
5595presenting all the available clues. (In Solo's case, if all the
5596possible clues were provided then the puzzle would already be
5597solved; in Slant it would still require user action to fill in the
5598lines, but it would present no challenge at all). Therefore, a
5599simple generation technique is to leave the decision of which clues
5600to provide until the last minute. In other words, first generate a
5601random \e{filled} grid with all possible clues present, and then
5602gradually remove clues for as long as the solver reports that it's
5603still soluble. Unlike the methods described above, this technique
5604\e{cannot} fail \dash once you've got a filled grid, nothing can
5605stop you from being able to convert it into a viable puzzle.
5606However, it wouldn't even be meaningful to apply this technique to
5607(say) Pattern, in which clues can never be left out, so the only way
5608to affect the set of clues is by altering the solution.
5609
5610(Unfortunately, Solo is complicated by the need to provide puzzles
5611at varying difficulty levels. It's easy enough to generate a puzzle
5612of \e{at most} a given level of difficulty; you just have a solver
5613with configurable intelligence, and you set it to a given level and
5614apply the above technique, thus guaranteeing that the resulting grid
5615is solvable by someone with at most that much intelligence. However,
5616generating a puzzle of \e{at least} a given level of difficulty is
5617rather harder; if you go for \e{at most} Intermediate level, you're
5618likely to find that you've accidentally generated a Trivial grid a
5619lot of the time, because removing just one number is sufficient to
5620take the puzzle from Trivial straight to Ambiguous. In that
5621situation Solo has no remaining options but to throw the puzzle away
5622and start again.)
5623
5624A final strategy is to use the solver \e{during} puzzle
5625construction: lay out a bit of the grid, run the solver to see what
5626it allows you to deduce, and then lay out a bit more to allow the
5627solver to make more progress. There are articles on the web that
5628recommend constructing Sudoku puzzles by this method (which is
5629completely the opposite way round to how Solo does it); for Sudoku
5630it has the advantage that you get to specify your clue squares in
5631advance (so you can have them make pretty patterns).
5632
5633Rectangles uses a strategy along these lines. First it generates a
5634grid by placing the actual rectangles; then it has to decide where
5635in each rectangle to place a number. It uses a solver to help it
5636place the numbers in such a way as to ensure a unique solution. It
5637does this by means of running a test solver, but it runs the solver
5638\e{before} it's placed any of the numbers \dash which means the
5639solver must be capable of coping with uncertainty about exactly
5640where the numbers are! It runs the solver as far as it can until it
5641gets stuck; then it narrows down the possible positions of a number
5642in order to allow the solver to make more progress, and so on. Most
5643of the time this process terminates with the grid fully solved, at
5644which point any remaining number-placement decisions can be made at
5645random from the options not so far ruled out. Note that unlike the
5646Net/Mines tweaking strategy described above, this algorithm does not
5647require a checking run after it completes: if it finishes
5648successfully at all, then it has definitely produced a uniquely
5649soluble puzzle.
5650
5651Most of the strategies described above are not 100% reliable. Each
5652one has a failure rate: every so often it has to throw out the whole
5653grid and generate a fresh one from scratch. (Solo's strategy would
5654be the exception, if it weren't for the need to provide configurable
5655difficulty levels.) Occasional failures are not a fundamental
5656problem in this sort of work, however: it's just a question of
5657dividing the grid generation time by the success rate (if it takes
565810ms to generate a candidate grid and 1/5 of them work, then it will
5659take 50ms on average to generate a viable one), and seeing whether
5660the expected time taken to \e{successfully} generate a puzzle is
5661unacceptably slow. Dominosa's generator has a very low success rate
5662(about 1 out of 20 candidate grids turn out to be usable, and if you
5663think \e{that's} bad then go and look at the source code and find
5664the comment showing what the figures were before the generation-time
5665tweaks!), but the generator itself is very fast so this doesn't
5666matter. Rectangles has a slower generator, but fails well under 50%
5667of the time.
5668
5669So don't be discouraged if you have an algorithm that doesn't always
5670work: if it \e{nearly} always works, that's probably good enough.
5671The one place where reliability is important is that your algorithm
5672must never produce false positives: it must not claim a puzzle is
5673soluble when it isn't. It can produce false negatives (failing to
5674notice that a puzzle is soluble), and it can fail to generate a
5675puzzle at all, provided it doesn't do either so often as to become
5676slow.
5677
5678One last piece of advice: for grid-based puzzles, when writing and
5679testing your generation algorithm, it's almost always a good idea
5680\e{not} to test it initially on a grid that's square (i.e.
5681\cw{w==h}), because if the grid is square then you won't notice if
5682you mistakenly write \c{h} instead of \c{w} (or vice versa)
5683somewhere in the code. Use a rectangular grid for testing, and any
5684size of grid will be likely to work after that.
5685
5686\S{writing-textformats} Designing textual description formats
5687
5688Another aspect of writing a puzzle which is worth putting some
5689thought into is the design of the various text description formats:
5690the format of the game parameter encoding, the game description
5691encoding, and the move encoding.
5692
5693The first two of these should be reasonably intuitive for a user to
5694type in; so provide some flexibility where possible. Suppose, for
5695example, your parameter format consists of two numbers separated by
5696an \c{x} to specify the grid dimensions (\c{10x10} or \c{20x15}),
5697and then has some suffixes to specify other aspects of the game
5698type. It's almost always a good idea in this situation to arrange
5699that \cw{decode_params()} can handle the suffixes appearing in any
5700order, even if \cw{encode_params()} only ever generates them in one
5701order.
5702
5703These formats will also be expected to be reasonably stable: users
5704will expect to be able to exchange game IDs with other users who
5705aren't running exactly the same version of your game. So make them
5706robust and stable: don't build too many assumptions into the game ID
5707format which will have to be changed every time something subtle
5708changes in the puzzle code.
5709
5710\H{writing-howto} Common how-to questions
5711
5712This section lists some common things people want to do when writing
5713a puzzle, and describes how to achieve them within the Puzzles
5714framework.
5715
5716\S{writing-howto-redraw} Redrawing just the changed parts of the window
5717
5718Redrawing the entire window on every move is wasteful. If the user
5719makes a move changing only one square of a grid, it's better to redraw
5720just that square.
5721
5722(Yes, computers are fast these days, but these puzzles still try to be
5723portable to devices at the less fast end of the spectrum, so it's
5724still worth saving effort where it's easy. On the other hand, some
5725puzzles just \e{can't} do this easily \dash Untangle is an example
5726that really does have no better option than to redraw everything.)
5727
5728For a typical grid-oriented puzzle, a robust way to do this is:
5729
5730\b Invent a data representation that describes everything about the
5731appearance of a grid cell in the puzzle window.
5732
5733\b Have \c{game_drawstate} contain an array of those, describing the
5734current appearance of each cell, as it was last drawn in the window.
5735
5736\b In \cw{redraw()}, loop over each cell deciding what the new
5737appearance should be. If it's not the same as the value stored in
5738\c{game_drawstate}, then redraw that cell, and update the entry in the
5739\c{game_drawstate} array.
5740
5741Where possible, I generally make my data representation an integer
5742full of bit flags, to save space, and to make it easy to compare the
5743old and new versions. If yours needs to be bigger than that, you may
5744have to define a small \cw{struct} and write an equality-checking
5745function.
5746
5747The data representation of the \e{appearance} of a square in
5748\c{game_drawstate} will not generally be identical to the
5749representation of the \e{logical state} of a square in \c{game_state},
5750because many things contribute to a square's appearance other than its
5751logical state. For example:
5752
5753\b Extra information overlaid on the square by the user interface,
5754such as a keyboard-controlled cursor, or highlighting of squares
5755currently involved in a mouse drag action.
5756
5757\b Error highlights marking violations of the puzzle constraints.
5758
5759\b Visual intrusions into one square because of things in nearby
5760squares. For example, if you draw thick lines along the edges between
5761grid squares, then the corners of those lines will be visible in
5762logically unrelated squares. An entry in the \c{game_drawstate} array
5763should describe a specific \e{rectangular area of the screen}, so that
5764those areas can be erased and redrawn independently \dash so it must
5765represent anything that appears in that area, even if it's sticking
5766out from a graphic that logically lives in some other square.
5767
5768\b Temporary changes to the appearance of a square because of an
5769ongoing completion flash.
5770
5771\b The current display mode, if a game provides more than one. (For
5772example, the optional letters distinguishing the different coloured
5773pegs in Guess.)
5774
5775All of this must be included in the \c{game_drawstate} representation,
5776but should not be in the \c{game_state} at all. \cw{redraw()} will
5777pull it all together from the \c{game_state}, the \c{game_ui}, and the
5778animation and flash parameters.
5779
5780To make sure that \e{everything} affecting a square's appearance is
5781included in this representation, it's a good idea to have a separate
5782function for drawing a grid square, and deliberately \e{not} pass it a
5783copy of the \c{game_state} or the \c{game_ui} at all. That way, if you
5784want that function to draw anything differently, you \e{have} to do it
5785by including that information in the representation of a square's
5786appearance.
5787
5788But of course there are a couple of exceptions to this rule. A few
5789things \e{don't} have to go in the \c{game_drawstate} array, and can
5790safely be passed separately to the redraw-square function:
5791
5792\b Anything that remains completely fixed throughout the whole of a
5793game, such as the clues provided by the puzzle. This is safe because a
5794\c{game_drawstate} is never reused between puzzle instances: when you
5795press New Game, a new \c{game_drawstate} will always be created from
5796scratch. So the \c{game_drawstate} only needs to describe everything
5797that might \e{change} during gameplay. If you have a sub-\cw{struct}
5798in your \c{game_state} that describes immutable properties of the
5799current game, as suggested in \k{writing-ref-counting}, then it's safe
5800to pass \e{that substructure} to the redraw-square function, and have
5801it retrieve that information directly.
5802
5803\b How far through a move animation the last redraw was. When
5804\cw{redraw()} is called multiple times during an animated move, it's
5805much easier to just assume that any square involved in the animation
5806will \e{always} need redrawing. So \c{anim_length} can safely be
5807passed separately to the redraw-square function \dash but you also
5808have to remember to redraw a square if \e{either} its appearance is
5809different from the last redraw \e{or} it's involved in an animation.
5810
5811\S{writing-howto-cursor} Drawing an object at only one position
5812
5813A common phenomenon is to have an object described in the
5814\c{game_state} or the \c{game_ui} which can only be at one position.
5815A cursor \dash probably specified in the \c{game_ui} \dash is a good
5816example.
5817
5818In the \c{game_ui}, it would \e{obviously} be silly to have an array
5819covering the whole game grid with a boolean flag stating whether the
5820cursor was at each position. Doing that would waste space, would
5821make it difficult to find the cursor in order to do anything with
5822it, and would introduce the potential for synchronisation bugs in
5823which you ended up with two cursors or none. The obviously sensible
5824way to store a cursor in the \c{game_ui} is to have fields directly
5825encoding the cursor's coordinates.
5826
5827However, it is a mistake to assume that the same logic applies to the
5828\c{game_drawstate}. If you replicate the cursor position fields in the
5829draw state, the redraw code will get very complicated. In the draw
5830state, in fact, it \e{is} probably the right thing to have a cursor
5831flag for every position in the grid, and make it part of the
5832representation of each square's appearance, as described in
5833\k{writing-howto-redraw}. So when you iterate over each square in
5834\c{redraw()} working out its position, you set the \q{cursor here}
5835flag in the representation of the square's appearance, if its
5836coordinates match the cursor coordinates stored in the \c{game_ui}.
5837This will automatically ensure that when the cursor moves, the redraw
5838loop will redraw the square that \e{previously} contained the cursor
5839and doesn't any more, and the one that now contains the cursor.
5840
5841\S{writing-keyboard-cursor} Implementing a keyboard-controlled cursor
5842
5843It is often useful to provide a keyboard control method in a
5844basically mouse-controlled game. A keyboard-controlled cursor is
5845best implemented by storing its location in the \c{game_ui} (since
5846if it were in the \c{game_state} then the user would have to
5847separately undo every cursor move operation). So the procedure would
5848be:
5849
5850\b Put cursor position fields in the \c{game_ui}.
5851
5852\b \cw{interpret_move()} responds to arrow keys by modifying the
5853cursor position fields and returning \cw{MOVE_UI_UPDATE}.
5854
5855\b \cw{interpret_move()} responds to some other button \dash either
5856\cw{CURSOR_SELECT} or some more specific thing like a number key \dash
5857by actually performing a move based on the current cursor location.
5858
5859\b You might want an additional \c{game_ui} field stating whether
5860the cursor is currently visible, and having it disappear when a
5861mouse action occurs (so that it doesn't clutter the display when not
5862actually in use).
5863
5864\b You might also want to automatically hide the cursor in
5865\cw{changed_state()} when the current game state changes to one in
5866which there is no move to make (which is the case in some types of
5867completed game).
5868
5869\b \cw{redraw()} draws the cursor using the technique described in
5870\k{writing-howto-cursor}.
5871
5872\S{writing-howto-dragging} Implementing draggable sprites
5873
5874Some games have a user interface which involves dragging some sort
5875of game element around using the mouse. If you need to show a
5876graphic moving smoothly over the top of other graphics, use a
5877blitter (see \k{drawing-blitter} for the blitter API) to save the
5878background underneath it. The typical scenario goes:
5879
5880\b Have a blitter field in the \c{game_drawstate}.
5881
5882\b Set the blitter field to \cw{NULL} in the game's
5883\cw{new_drawstate()} function, since you don't yet know how big the
5884piece of saved background needs to be.
5885
5886\b In the game's \cw{set_size()} function, once you know the size of
5887the object you'll be dragging around the display and hence the
5888required size of the blitter, actually allocate the blitter.
5889
5890\b In \cw{free_drawstate()}, free the blitter if it's not \cw{NULL}.
5891
5892\b In \cw{interpret_move()}, respond to mouse-down and mouse-drag
5893events by updating some fields in the \cw{game_ui} which indicate
5894that a drag is in progress.
5895
5896\b At the \e{very end} of \cw{redraw()}, after all other drawing has
5897been done, draw the moving object if there is one. First save the
5898background under the object in the blitter; then set a clip
5899rectangle covering precisely the area you just saved (just in case
5900anti-aliasing or some other error causes your drawing to go beyond
5901the area you saved). Then draw the object, and call \cw{unclip()}.
5902Finally, set a flag in the \cw{game_drawstate} that indicates that
5903the blitter needs restoring.
5904
5905\b At the very start of \cw{redraw()}, before doing anything else at
5906all, check the flag in the \cw{game_drawstate}, and if it says the
5907blitter needs restoring then restore it. (Then clear the flag, so
5908that this won't happen again in the next redraw if no moving object
5909is drawn this time.)
5910
5911This way, you will be able to write the rest of the redraw function
5912completely ignoring the dragged object, as if it were floating above
5913your bitmap and being completely separate.
5914
5915\S{writing-ref-counting} Sharing large invariant data between all
5916game states
5917
5918In some puzzles, there is a large amount of data which never changes
5919between game states. The array of numbers in Dominosa is a good
5920example.
5921
5922You \e{could} dynamically allocate a copy of that array in every
5923\c{game_state}, and have \cw{dup_game()} make a fresh copy of it for
5924every new \c{game_state}; but it would waste memory and time. A
5925more efficient way is to use a reference-counted structure.
5926
5927\b Define a structure type containing the data in question, and also
5928containing an integer reference count.
5929
5930\b Have a field in \c{game_state} which is a pointer to this
5931structure.
5932
5933\b In \cw{new_game()}, when creating a fresh game state at the start
5934of a new game, create an instance of this structure, initialise it
5935with the invariant data, and set its reference count to 1.
5936
5937\b In \cw{dup_game()}, rather than making a copy of the structure
5938for the new game state, simply set the new game state to point at
5939the same copy of the structure, and increment its reference count.
5940
5941\b In \cw{free_game()}, decrement the reference count in the
5942structure pointed to by the game state; if the count reaches zero,
5943free the structure.
5944
5945This way, the invariant data will persist for only as long as it's
5946genuinely needed; \e{as soon} as the last game state for a
5947particular puzzle instance is freed, the invariant data for that
5948puzzle will vanish as well. Reference counting is a very efficient
5949form of garbage collection, when it works at all. (Which it does in
5950this instance, of course, because there's no possibility of circular
5951references.)
5952
5953\S{writing-flash-types} Implementing multiple types of flash
5954
5955In some games you need to flash in more than one different way.
5956Mines, for example, flashes white when you win, and flashes red when
5957you tread on a mine and die.
5958
5959The simple way to do this is:
5960
5961\b Have a field in the \c{game_ui} which describes the type of flash.
5962
5963\b In \cw{flash_length()}, examine the old and new game states to
5964decide whether a flash is required and what type. Write the type of
5965flash to the \c{game_ui} field whenever you return non-zero.
5966
5967\b In \cw{redraw()}, when you detect that \c{flash_time} is
5968non-zero, examine the field in \c{game_ui} to decide which type of
5969flash to draw.
5970
5971\cw{redraw()} will never be called with \c{flash_time} non-zero
5972unless \cw{flash_length()} was first called to tell the mid-end that
5973a flash was required; so whenever \cw{redraw()} notices that
5974\c{flash_time} is non-zero, you can be sure that the field in
5975\c{game_ui} is correctly set.
5976
5977\S{writing-move-anim} Animating game moves
5978
5979A number of puzzle types benefit from a quick animation of each move
5980you make.
5981
5982For some games, such as Fifteen, this is particularly easy. Whenever
5983\cw{redraw()} is called with \c{oldstate} non-\cw{NULL}, Fifteen
5984simply compares the position of each tile in the two game states,
5985and if the tile is not in the same place then it draws it some
5986fraction of the way from its old position to its new position. This
5987method copes automatically with undo.
5988
5989Other games are less obvious. In Sixteen, for example, you can't
5990just draw each tile a fraction of the way from its old to its new
5991position: if you did that, the end tile would zip very rapidly past
5992all the others to get to the other end and that would look silly.
5993(Worse, it would look inconsistent if the end tile was drawn on top
5994going one way and on the bottom going the other way.)
5995
5996A useful trick here is to define a field or two in the game state
5997that indicates what the last move was.
5998
5999\b Add a \q{last move} field to the \c{game_state} (or two or more
6000fields if the move is complex enough to need them).
6001
6002\b \cw{new_game()} initialises this field to a null value for a new
6003game state.
6004
6005\b \cw{execute_move()} sets up the field to reflect the move it just
6006performed.
6007
6008\b \cw{redraw()} now needs to examine its \c{dir} parameter. If
6009\c{dir} is positive, it determines the move being animated by
6010looking at the last-move field in \c{newstate}; but if \c{dir} is
6011negative, it has to look at the last-move field in \c{oldstate}, and
6012invert whatever move it finds there.
6013
6014Note also that Sixteen needs to store the \e{direction} of the move,
6015because you can't quite determine it by examining the row or column
6016in question. You can in almost all cases, but when the row is
6017precisely two squares long it doesn't work since a move in either
6018direction looks the same. (You could argue that since moving a
60192-element row left and right has the same effect, it doesn't matter
6020which one you animate; but in fact it's very disorienting to click
6021the arrow left and find the row moving right, and almost as bad to
6022undo a move to the right and find the game animating \e{another}
6023move to the right.)
6024
6025\S{writing-conditional-anim} Animating drag operations
6026
6027In Untangle, moves are made by dragging a node from an old position
6028to a new position. Therefore, at the time when the move is initially
6029made, it should not be animated, because the node has already been
6030dragged to the right place and doesn't need moving there. However,
6031it's nice to animate the same move if it's later undone or redone.
6032This requires a bit of fiddling.
6033
6034The obvious approach is to have a flag in the \c{game_ui} which
6035inhibits move animation, and to set that flag in
6036\cw{interpret_move()}. The question is, when would the flag be reset
6037again? The obvious place to do so is \cw{changed_state()}, which
6038will be called once per move. But it will be called \e{before}
6039\cw{anim_length()}, so if it resets the flag then \cw{anim_length()}
6040will never see the flag set at all.
6041
6042The solution is to have \e{two} flags in a queue.
6043
6044\b Define two flags in \c{game_ui}; let's call them \q{current} and
6045\q{next}.
6046
6047\b Set both to \cw{false} in \c{new_ui()}.
6048
6049\b When a drag operation completes in \cw{interpret_move()}, set the
6050\q{next} flag to \cw{true}.
6051
6052\b Every time \cw{changed_state()} is called, set the value of
6053\q{current} to the value in \q{next}, and then set the value of
6054\q{next} to \cw{false}.
6055
6056\b That way, \q{current} will be \cw{true} \e{after} a call to
6057\cw{changed_state()} if and only if that call to
6058\cw{changed_state()} was the result of a drag operation processed by
6059\cw{interpret_move()}. Any other call to \cw{changed_state()}, due
6060to an Undo or a Redo or a Restart or a Solve, will leave \q{current}
6061\cw{false}.
6062
6063\b So now \cw{anim_length()} can request a move animation if and
6064only if the \q{current} flag is \e{not} set.
6065
6066\S{writing-cheating} Inhibiting the victory flash when Solve is used
6067
6068Many games flash when you complete them, as a visual congratulation
6069for having got to the end of the puzzle. It often seems like a good
6070idea to disable that flash when the puzzle is brought to a solved
6071state by means of the Solve operation.
6072
6073This is easily done:
6074
6075\b Add a \q{cheated} flag to the \c{game_state}.
6076
6077\b Set this flag to \cw{false} in \cw{new_game()}.
6078
6079\b Have \cw{solve()} return a move description string which clearly
6080identifies the move as a solve operation.
6081
6082\b Have \cw{execute_move()} respond to that clear identification by
6083setting the \q{cheated} flag in the returned \c{game_state}. The
6084flag will then be propagated to all subsequent game states, even if
6085the user continues fiddling with the game after it is solved.
6086
6087\b \cw{flash_length()} now returns non-zero if \c{oldstate} is not
6088completed and \c{newstate} is, \e{and} neither state has the
6089\q{cheated} flag set.
6090
6091\H{writing-testing} Things to test once your puzzle is written
6092
6093Puzzle implementations written in this framework are self-testing as
6094far as I could make them.
6095
6096Textual game and move descriptions, for example, are generated and
6097parsed as part of the normal process of play. Therefore, if you can
6098make moves in the game \e{at all} you can be reasonably confident
6099that the mid-end serialisation interface will function correctly and
6100you will be able to save your game. (By contrast, if I'd stuck with
6101a single \cw{make_move()} function performing the jobs of both
6102\cw{interpret_move()} and \cw{execute_move()}, and had separate
6103functions to encode and decode a game state in string form, then
6104those functions would not be used during normal play; so they could
6105have been completely broken, and you'd never know it until you tried
6106to save the game \dash which would have meant you'd have to test
6107game saving \e{extensively} and make sure to test every possible
6108type of game state. As an added bonus, doing it the way I did leads
6109to smaller save files.)
6110
6111There is one exception to this, which is the string encoding of the
6112\c{game_ui}. Most games do not store anything permanent in the
6113\c{game_ui}, and hence do not need to put anything in its encode and
6114decode functions; but if there is anything in there, you do need to
6115test game loading and saving to ensure those functions work
6116properly.
6117
6118It's also worth testing undo and redo of all operations, to ensure
6119that the redraw and the animations (if any) work properly. Failing
6120to animate undo properly seems to be a common error.
6121
6122Other than that, just use your common sense.
diff --git a/apps/plugins/puzzles/src/emcc.c b/apps/plugins/puzzles/src/emcc.c
deleted file mode 100644
index 6aa9c6b093..0000000000
--- a/apps/plugins/puzzles/src/emcc.c
+++ /dev/null
@@ -1,1149 +0,0 @@
1/*
2 * emcc.c: the C component of an Emscripten-based web/Javascript front
3 * end for Puzzles.
4 *
5 * The Javascript parts of this system live in emcclib.js and
6 * emccpre.js. It also depends on being run in the context of a web
7 * page containing an appropriate collection of bits and pieces (a
8 * canvas, some buttons and links etc), which is generated for each
9 * puzzle by the script html/jspage.pl.
10 */
11
12/*
13 * Further thoughts on possible enhancements:
14 *
15 * - I should think about whether these webified puzzles can support
16 * touchscreen-based tablet browsers.
17 *
18 * - think about making use of localStorage. It might be useful to
19 * let the user save games into there as an alternative to disk
20 * files - disk files are all very well for getting the save right
21 * out of your browser to (e.g.) email to me as a bug report, but
22 * for just resuming a game you were in the middle of, you'd
23 * probably rather have a nice simple 'quick save' and 'quick load'
24 * button pair.
25 *
26 * - this is a downright silly idea, but it does occur to me that if
27 * I were to write a PDF output driver for the Puzzles printing
28 * API, then I might be able to implement a sort of 'printing'
29 * feature in this front end, using data: URIs again. (Ask the user
30 * exactly what they want printed, then construct an appropriate
31 * PDF and embed it in a gigantic data: URI. Then they can print
32 * that using whatever they normally use to print PDFs!)
33 */
34
35#include <assert.h>
36#include <stdio.h>
37#include <string.h>
38#include <stdarg.h>
39
40#include "puzzles.h"
41
42/*
43 * Extern references to Javascript functions provided in emcclib.js.
44 */
45extern void js_init_puzzle(void);
46extern void js_post_init(void);
47extern void js_debug(const char *);
48extern void js_error_box(const char *message);
49extern void js_remove_type_dropdown(void);
50extern void js_remove_solve_button(void);
51extern void js_add_preset(int menuid, const char *name, int value);
52extern int js_add_preset_submenu(int menuid, const char *name);
53extern int js_get_selected_preset(void);
54extern void js_select_preset(int n);
55extern void js_default_colour(float *output);
56extern void js_set_colour(int colour_number, const char *colour_string);
57extern void js_get_date_64(unsigned *p);
58extern void js_update_permalinks(const char *desc, const char *seed);
59extern void js_enable_undo_redo(bool undo, bool redo);
60extern void js_update_key_labels(const char *lsk, const char *csk);
61extern void js_activate_timer(void);
62extern void js_deactivate_timer(void);
63extern void js_canvas_start_draw(void);
64extern void js_canvas_draw_update(int x, int y, int w, int h);
65extern void js_canvas_end_draw(void);
66extern void js_canvas_draw_rect(int x, int y, int w, int h, int colour);
67extern void js_canvas_clip_rect(int x, int y, int w, int h);
68extern void js_canvas_unclip(void);
69extern void js_canvas_draw_line(float x1, float y1, float x2, float y2,
70 int width, int colour);
71extern void js_canvas_draw_poly(const int *points, int npoints,
72 int fillcolour, int outlinecolour);
73extern void js_canvas_draw_circle(int x, int y, int r,
74 int fillcolour, int outlinecolour);
75extern int js_canvas_find_font_midpoint(int height, bool monospaced);
76extern void js_canvas_draw_text(int x, int y, int halign,
77 int colour, int height,
78 bool monospaced, const char *text);
79extern int js_canvas_new_blitter(int w, int h);
80extern void js_canvas_free_blitter(int id);
81extern void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h);
82extern void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h);
83extern void js_canvas_remove_statusbar(void);
84extern void js_canvas_set_statusbar(const char *text);
85extern bool js_canvas_get_preferred_size(int *wp, int *hp);
86extern void js_canvas_set_size(int w, int h);
87extern double js_get_device_pixel_ratio(void);
88
89extern void js_dialog_init(const char *title);
90extern void js_dialog_string(int i, const char *title, const char *initvalue);
91extern void js_dialog_choices(int i, const char *title, const char *choicelist,
92 int initvalue);
93extern void js_dialog_boolean(int i, const char *title, bool initvalue);
94extern void js_dialog_launch(void);
95extern void js_dialog_cleanup(void);
96extern void js_focus_canvas(void);
97
98extern bool js_savefile_read(void *buf, int len);
99
100extern void js_save_prefs(const char *);
101extern void js_load_prefs(midend *);
102
103/*
104 * These functions are called from JavaScript, so their prototypes
105 * need to be kept in sync with emccpre.js.
106 */
107bool mouseup(int x, int y, int button);
108bool mousedown(int x, int y, int button);
109bool mousemove(int x, int y, int buttons);
110bool key(int keycode, const char *key, const char *chr, int location,
111 bool shift, bool ctrl);
112void timer_callback(double tplus);
113void command(int n);
114char *get_text_format(void);
115void free_save_file(char *buffer);
116char *get_save_file(void);
117void free_save_file(char *buffer);
118void load_game(void);
119void dlg_return_sval(int index, const char *val);
120void dlg_return_ival(int index, int val);
121void resize_puzzle(int w, int h);
122void restore_puzzle_size(int w, int h);
123void rescale_puzzle(void);
124
125/*
126 * Internal forward references.
127 */
128static void save_prefs(midend *me);
129
130/*
131 * Call JS to get the date, and use that to initialise our random
132 * number generator to invent the first game seed.
133 */
134void get_random_seed(void **randseed, int *randseedsize)
135{
136 unsigned *ret = snewn(2, unsigned);
137 js_get_date_64(ret);
138 *randseed = ret;
139 *randseedsize = 2*sizeof(unsigned);
140}
141
142/*
143 * Fatal error, called in cases of complete despair such as when
144 * malloc() has returned NULL.
145 */
146void fatal(const char *fmt, ...)
147{
148 char buf[512];
149 va_list ap;
150
151 strcpy(buf, "puzzle fatal error: ");
152
153 va_start(ap, fmt);
154 vsnprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), fmt, ap);
155 va_end(ap);
156
157 js_error_box(buf);
158}
159
160#ifdef DEBUGGING
161void debug_printf(const char *fmt, ...)
162{
163 char buf[512];
164 va_list ap;
165 va_start(ap, fmt);
166 vsnprintf(buf, sizeof(buf), fmt, ap);
167 va_end(ap);
168 js_debug(buf);
169}
170#endif
171
172/*
173 * Helper function that makes it easy to test strings that might be
174 * NULL.
175 */
176static int strnullcmp(const char *a, const char *b)
177{
178 if (a == NULL || b == NULL)
179 return a != NULL ? +1 : b != NULL ? -1 : 0;
180 return strcmp(a, b);
181}
182
183/*
184 * The global midend object.
185 */
186static midend *me;
187
188/* ----------------------------------------------------------------------
189 * Timing functions.
190 */
191static bool timer_active = false;
192void deactivate_timer(frontend *fe)
193{
194 js_deactivate_timer();
195 timer_active = false;
196}
197void activate_timer(frontend *fe)
198{
199 if (!timer_active) {
200 js_activate_timer();
201 timer_active = true;
202 }
203}
204void timer_callback(double tplus)
205{
206 if (timer_active)
207 midend_timer(me, tplus);
208}
209
210/* ----------------------------------------------------------------------
211 * Helper functions to resize the canvas, and variables to remember
212 * its size for other functions (e.g. trimming blitter rectangles).
213 */
214static int canvas_w, canvas_h;
215
216/*
217 * Called when we resize as a result of changing puzzle settings
218 * or device pixel ratio.
219 */
220static void resize(void)
221{
222 int w, h;
223 bool user;
224 w = h = INT_MAX;
225 user = js_canvas_get_preferred_size(&w, &h);
226 midend_size(me, &w, &h, user, js_get_device_pixel_ratio());
227 js_canvas_set_size(w, h);
228 canvas_w = w;
229 canvas_h = h;
230}
231
232/* Called from JS when the device pixel ratio changes */
233void rescale_puzzle(void)
234{
235 resize();
236 midend_force_redraw(me);
237}
238
239/* Called from JS when the user uses the resize handle */
240void resize_puzzle(int w, int h)
241{
242 midend_size(me, &w, &h, true, js_get_device_pixel_ratio());
243 if (canvas_w != w || canvas_h != h) {
244 js_canvas_set_size(w, h);
245 canvas_w = w;
246 canvas_h = h;
247 midend_force_redraw(me);
248 }
249}
250
251/* Called from JS when the user uses the restore button */
252void restore_puzzle_size(int w, int h)
253{
254 midend_reset_tilesize(me);
255 resize();
256 midend_force_redraw(me);
257}
258
259/*
260 * Try to extract a background colour from the canvas's CSS. In case
261 * it doesn't have a usable one, make up a lightish grey ourselves.
262 */
263void frontend_default_colour(frontend *fe, float *output)
264{
265 output[0] = output[1] = output[2] = 0.9F;
266 js_default_colour(output);
267}
268
269/*
270 * Helper function called from all over the place to ensure the undo
271 * and redo buttons get properly enabled and disabled after every move
272 * or undo or new-game event.
273 */
274static void post_move(void)
275{
276 js_enable_undo_redo(midend_can_undo(me), midend_can_redo(me));
277 js_update_key_labels(midend_current_key_label(me, CURSOR_SELECT2),
278 midend_current_key_label(me, CURSOR_SELECT));
279}
280
281/*
282 * Mouse event handlers called from JS.
283 */
284bool mousedown(int x, int y, int button)
285{
286 bool handled;
287
288 button = (button == 0 ? LEFT_BUTTON :
289 button == 1 ? MIDDLE_BUTTON : RIGHT_BUTTON);
290 handled = midend_process_key(me, x, y, button) != PKR_UNUSED;
291 post_move();
292 return handled;
293}
294
295bool mouseup(int x, int y, int button)
296{
297 bool handled;
298
299 button = (button == 0 ? LEFT_RELEASE :
300 button == 1 ? MIDDLE_RELEASE : RIGHT_RELEASE);
301 handled = midend_process_key(me, x, y, button) != PKR_UNUSED;
302 post_move();
303 return handled;
304}
305
306bool mousemove(int x, int y, int buttons)
307{
308 int button = (buttons & 2 ? MIDDLE_DRAG :
309 buttons & 4 ? RIGHT_DRAG : LEFT_DRAG);
310 bool handled;
311
312 handled = midend_process_key(me, x, y, button) != PKR_UNUSED;
313 post_move();
314 return handled;
315}
316
317/*
318 * Keyboard handler called from JS. Returns true if the key was
319 * handled and hence the keydown event should be cancelled.
320 */
321bool key(int keycode, const char *key, const char *chr, int location,
322 bool shift, bool ctrl)
323{
324 /* Key location constants from JavaScript. */
325 #define DOM_KEY_LOCATION_STANDARD 0
326 #define DOM_KEY_LOCATION_LEFT 1
327 #define DOM_KEY_LOCATION_RIGHT 2
328 #define DOM_KEY_LOCATION_NUMPAD 3
329 int keyevent = -1;
330 int process_key_result;
331
332 if (!strnullcmp(key, "Backspace") || !strnullcmp(key, "Delete") ||
333 !strnullcmp(key, "Del"))
334 keyevent = 127; /* Backspace / Delete */
335 else if (!strnullcmp(key, "Enter"))
336 keyevent = 13; /* return */
337 else if (!strnullcmp(key, "Spacebar"))
338 keyevent = ' ';
339 else if (!strnullcmp(key, "Escape"))
340 keyevent = 27;
341 else if (!strnullcmp(key, "ArrowLeft") || !strnullcmp(key, "Left"))
342 keyevent = CURSOR_LEFT;
343 else if (!strnullcmp(key, "ArrowUp") || !strnullcmp(key, "Up"))
344 keyevent = CURSOR_UP;
345 else if (!strnullcmp(key, "ArrowRight") || !strnullcmp(key, "Right"))
346 keyevent = CURSOR_RIGHT;
347 else if (!strnullcmp(key, "ArrowDown") || !strnullcmp(key, "Down"))
348 keyevent = CURSOR_DOWN;
349 else if (!strnullcmp(key, "SoftLeft"))
350 /* Left soft key on KaiOS. */
351 keyevent = CURSOR_SELECT2;
352 else if (!strnullcmp(key, "End"))
353 /*
354 * We interpret Home, End, PgUp and PgDn as numeric keypad
355 * controls regardless of whether they're the ones on the
356 * numeric keypad (since we can't tell). The effect of
357 * this should only be that the non-numeric-pad versions
358 * of those keys generate directions in 8-way movement
359 * puzzles like Cube and Inertia.
360 */
361 keyevent = MOD_NUM_KEYPAD | '1';
362 else if (!strnullcmp(key, "PageDown"))
363 keyevent = MOD_NUM_KEYPAD | '3';
364 else if (!strnullcmp(key, "Home"))
365 keyevent = MOD_NUM_KEYPAD | '7';
366 else if (!strnullcmp(key, "PageUp"))
367 keyevent = MOD_NUM_KEYPAD | '9';
368 else if (shift && ctrl && (!strnullcmp(key, "Z") || !strnullcmp(key, "z")))
369 keyevent = UI_REDO;
370 else if (key && (unsigned char)key[0] < 0x80 && key[1] == '\0')
371 /* Key generating a single ASCII character. */
372 keyevent = key[0];
373 /*
374 * In modern browsers (since about 2017), all keys that Puzzles
375 * cares about should be matched by one of the clauses above. The
376 * code below that checks keycode and chr should be relavent only
377 * in older browsers.
378 */
379 else if (keycode == 8 || keycode == 46)
380 keyevent = 127; /* Backspace / Delete */
381 else if (keycode == 13)
382 keyevent = 13; /* return */
383 else if (keycode == 37)
384 keyevent = CURSOR_LEFT;
385 else if (keycode == 38)
386 keyevent = CURSOR_UP;
387 else if (keycode == 39)
388 keyevent = CURSOR_RIGHT;
389 else if (keycode == 40)
390 keyevent = CURSOR_DOWN;
391 else if (keycode == 35)
392 keyevent = MOD_NUM_KEYPAD | '1';
393 else if (keycode == 34)
394 keyevent = MOD_NUM_KEYPAD | '3';
395 else if (keycode == 36)
396 keyevent = MOD_NUM_KEYPAD | '7';
397 else if (keycode == 33)
398 keyevent = MOD_NUM_KEYPAD | '9';
399 else if (shift && ctrl && (keycode & 0x1F) == 26)
400 keyevent = UI_REDO;
401 else if (chr && chr[0] && !chr[1])
402 keyevent = chr[0] & 0xFF;
403 else if (keycode >= 96 && keycode < 106)
404 keyevent = MOD_NUM_KEYPAD | ('0' + keycode - 96);
405 else if (keycode >= 65 && keycode <= 90)
406 keyevent = keycode + (shift ? 0 : 32);
407 else if (keycode >= 48 && keycode <= 57)
408 keyevent = keycode;
409 else if (keycode == 32) /* space / CURSOR_SELECT2 */
410 keyevent = keycode;
411
412 if (keyevent >= 0) {
413 if (shift) keyevent |= MOD_SHFT;
414 if (ctrl) keyevent |= MOD_CTRL;
415 if (location == DOM_KEY_LOCATION_NUMPAD) keyevent |= MOD_NUM_KEYPAD;
416
417 process_key_result = midend_process_key(me, 0, 0, keyevent);
418 post_move();
419 /*
420 * Treat Backspace specially because that's expected on KaiOS.
421 * https://developer.kaiostech.com/docs/design-guide/key
422 */
423 if (process_key_result == PKR_NO_EFFECT &&
424 !strnullcmp(key, "Backspace"))
425 return false;
426 return process_key_result != PKR_UNUSED;
427 }
428 return false; /* Event not handled, because we don't even recognise it. */
429}
430
431/*
432 * Helper function called from several places to update the permalinks
433 * whenever a new game is created.
434 */
435static void update_permalinks(void)
436{
437 char *desc, *seed;
438 desc = midend_get_game_id(me);
439 seed = midend_get_random_seed(me);
440 js_update_permalinks(desc, seed);
441 sfree(desc);
442 sfree(seed);
443}
444
445/*
446 * Callback from the midend when the game ids change, so we can update
447 * the permalinks.
448 */
449static void ids_changed(void *ignored)
450{
451 update_permalinks();
452}
453
454/* ----------------------------------------------------------------------
455 * Implementation of the drawing API by calling Javascript canvas
456 * drawing functions. (Well, half of it; the other half is on the JS
457 * side.)
458 */
459static void js_start_draw(void *handle)
460{
461 js_canvas_start_draw();
462}
463
464static void js_clip(void *handle, int x, int y, int w, int h)
465{
466 js_canvas_clip_rect(x, y, w, h);
467}
468
469static void js_unclip(void *handle)
470{
471 js_canvas_unclip();
472}
473
474static void js_draw_text(void *handle, int x, int y, int fonttype,
475 int fontsize, int align, int colour,
476 const char *text)
477{
478 int halign;
479
480 if (align & ALIGN_VCENTRE)
481 y += js_canvas_find_font_midpoint(fontsize, fonttype == FONT_FIXED);
482
483 if (align & ALIGN_HCENTRE)
484 halign = 1;
485 else if (align & ALIGN_HRIGHT)
486 halign = 2;
487 else
488 halign = 0;
489
490 js_canvas_draw_text(x, y, halign, colour,
491 fontsize, fonttype == FONT_FIXED, text);
492}
493
494static void js_draw_rect(void *handle, int x, int y, int w, int h, int colour)
495{
496 js_canvas_draw_rect(x, y, w, h, colour);
497}
498
499static void js_draw_line(void *handle, int x1, int y1, int x2, int y2,
500 int colour)
501{
502 js_canvas_draw_line(x1, y1, x2, y2, 1, colour);
503}
504
505static void js_draw_thick_line(void *handle, float thickness,
506 float x1, float y1, float x2, float y2,
507 int colour)
508{
509 js_canvas_draw_line(x1, y1, x2, y2, thickness, colour);
510}
511
512static void js_draw_poly(void *handle, const int *coords, int npoints,
513 int fillcolour, int outlinecolour)
514{
515 js_canvas_draw_poly(coords, npoints, fillcolour, outlinecolour);
516}
517
518static void js_draw_circle(void *handle, int cx, int cy, int radius,
519 int fillcolour, int outlinecolour)
520{
521 js_canvas_draw_circle(cx, cy, radius, fillcolour, outlinecolour);
522}
523
524struct blitter {
525 int id; /* allocated on the js side */
526 int w, h; /* easier to retain here */
527};
528
529static blitter *js_blitter_new(void *handle, int w, int h)
530{
531 blitter *bl = snew(blitter);
532 bl->w = w;
533 bl->h = h;
534 bl->id = js_canvas_new_blitter(w, h);
535 return bl;
536}
537
538static void js_blitter_free(void *handle, blitter *bl)
539{
540 js_canvas_free_blitter(bl->id);
541 sfree(bl);
542}
543
544static void trim_rect(int *x, int *y, int *w, int *h)
545{
546 int x0, x1, y0, y1;
547
548 /*
549 * Reduce the size of the copied rectangle to stop it going
550 * outside the bounds of the canvas.
551 */
552
553 /* Transform from x,y,w,h form into coordinates of all edges */
554 x0 = *x;
555 y0 = *y;
556 x1 = *x + *w;
557 y1 = *y + *h;
558
559 /* Clip each coordinate at both extremes of the canvas */
560 x0 = (x0 < 0 ? 0 : x0 > canvas_w ? canvas_w : x0);
561 x1 = (x1 < 0 ? 0 : x1 > canvas_w ? canvas_w : x1);
562 y0 = (y0 < 0 ? 0 : y0 > canvas_h ? canvas_h : y0);
563 y1 = (y1 < 0 ? 0 : y1 > canvas_h ? canvas_h : y1);
564
565 /* Transform back into x,y,w,h to return */
566 *x = x0;
567 *y = y0;
568 *w = x1 - x0;
569 *h = y1 - y0;
570}
571
572static void js_blitter_save(void *handle, blitter *bl, int x, int y)
573{
574 int w = bl->w, h = bl->h;
575 trim_rect(&x, &y, &w, &h);
576 if (w > 0 && h > 0)
577 js_canvas_copy_to_blitter(bl->id, x, y, w, h);
578}
579
580static void js_blitter_load(void *handle, blitter *bl, int x, int y)
581{
582 int w = bl->w, h = bl->h;
583 trim_rect(&x, &y, &w, &h);
584 if (w > 0 && h > 0)
585 js_canvas_copy_from_blitter(bl->id, x, y, w, h);
586}
587
588static void js_draw_update(void *handle, int x, int y, int w, int h)
589{
590 trim_rect(&x, &y, &w, &h);
591 if (w > 0 && h > 0)
592 js_canvas_draw_update(x, y, w, h);
593}
594
595static void js_end_draw(void *handle)
596{
597 js_canvas_end_draw();
598}
599
600static void js_status_bar(void *handle, const char *text)
601{
602 js_canvas_set_statusbar(text);
603}
604
605static char *js_text_fallback(void *handle, const char *const *strings,
606 int nstrings)
607{
608 return dupstr(strings[0]); /* Emscripten has no trouble with UTF-8 */
609}
610
611static const struct drawing_api js_drawing = {
612 js_draw_text,
613 js_draw_rect,
614 js_draw_line,
615 js_draw_poly,
616 js_draw_circle,
617 js_draw_update,
618 js_clip,
619 js_unclip,
620 js_start_draw,
621 js_end_draw,
622 js_status_bar,
623 js_blitter_new,
624 js_blitter_free,
625 js_blitter_save,
626 js_blitter_load,
627 NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
628 NULL, NULL, /* line_width, line_dotted */
629 js_text_fallback,
630 js_draw_thick_line,
631};
632
633/* ----------------------------------------------------------------------
634 * Presets and game-configuration dialog support.
635 */
636static game_params **presets;
637static int npresets;
638static bool have_presets_dropdown;
639
640static void populate_js_preset_menu(int menuid, struct preset_menu *menu)
641{
642 int i;
643 for (i = 0; i < menu->n_entries; i++) {
644 struct preset_menu_entry *entry = &menu->entries[i];
645 if (entry->params) {
646 presets[entry->id] = entry->params;
647 js_add_preset(menuid, entry->title, entry->id);
648 } else {
649 int js_submenu = js_add_preset_submenu(menuid, entry->title);
650 populate_js_preset_menu(js_submenu, entry->submenu);
651 }
652 }
653}
654
655static void select_appropriate_preset(void)
656{
657 if (have_presets_dropdown) {
658 int preset = midend_which_preset(me);
659 js_select_preset(preset < 0 ? -1 : preset);
660 }
661}
662
663static config_item *cfg = NULL;
664static int cfg_which;
665
666/*
667 * Set up a dialog box. This is pretty easy on the C side; most of the
668 * work is done in JS.
669 */
670static void cfg_start(int which)
671{
672 char *title;
673 int i;
674
675 cfg = midend_get_config(me, which, &title);
676 cfg_which = which;
677
678 js_dialog_init(title);
679 sfree(title);
680
681 for (i = 0; cfg[i].type != C_END; i++) {
682 switch (cfg[i].type) {
683 case C_STRING:
684 js_dialog_string(i, cfg[i].name, cfg[i].u.string.sval);
685 break;
686 case C_BOOLEAN:
687 js_dialog_boolean(i, cfg[i].name, cfg[i].u.boolean.bval);
688 break;
689 case C_CHOICES:
690 js_dialog_choices(i, cfg[i].name, cfg[i].u.choices.choicenames,
691 cfg[i].u.choices.selected);
692 break;
693 }
694 }
695
696 js_dialog_launch();
697}
698
699/*
700 * Callbacks from JS when the OK button is clicked, to return the
701 * final state of each control.
702 */
703void dlg_return_sval(int index, const char *val)
704{
705 config_item *i = cfg + index;
706 switch (i->type) {
707 case C_STRING:
708 sfree(i->u.string.sval);
709 i->u.string.sval = dupstr(val);
710 break;
711 default:
712 assert(0 && "Bad type for return_sval");
713 }
714}
715void dlg_return_ival(int index, int val)
716{
717 config_item *i = cfg + index;
718 switch (i->type) {
719 case C_BOOLEAN:
720 i->u.boolean.bval = val;
721 break;
722 case C_CHOICES:
723 i->u.choices.selected = val;
724 break;
725 default:
726 assert(0 && "Bad type for return_ival");
727 }
728}
729
730/*
731 * Called when the user clicks OK or Cancel. use_results will be true
732 * or false respectively, in those cases. We terminate the dialog box,
733 * unless the user selected an invalid combination of parameters.
734 */
735static void cfg_end(bool use_results)
736{
737 if (use_results) {
738 /*
739 * User hit OK.
740 */
741 const char *err = midend_set_config(me, cfg_which, cfg);
742
743 if (err) {
744 /*
745 * The settings were unacceptable, so leave the config box
746 * open for the user to adjust them and try again.
747 */
748 js_error_box(err);
749 } else if (cfg_which == CFG_PREFS) {
750 /*
751 * Acceptable settings for user preferences: enact them
752 * without blowing away the current game.
753 */
754 resize();
755 midend_redraw(me);
756 free_cfg(cfg);
757 js_dialog_cleanup();
758 save_prefs(me);
759 } else {
760 /*
761 * Acceptable settings for the remaining configuration
762 * types: start a new game and close the dialog.
763 */
764 select_appropriate_preset();
765 midend_new_game(me);
766 resize();
767 midend_redraw(me);
768 free_cfg(cfg);
769 js_dialog_cleanup();
770 }
771 } else {
772 /*
773 * User hit Cancel. Close the dialog, but also we must still
774 * reselect the right element of the dropdown list.
775 *
776 * (Because: imagine you have a preset selected, and then you
777 * select Custom from the list, but change your mind and hit
778 * Esc. The Custom option will now still be selected in the
779 * list, whereas obviously it should show the preset you still
780 * _actually_ have selected.)
781 */
782 select_appropriate_preset();
783
784 free_cfg(cfg);
785 js_dialog_cleanup();
786 }
787}
788
789/* ----------------------------------------------------------------------
790 * Called from JS when a command is given to the puzzle by clicking a
791 * button or control of some sort.
792 */
793void command(int n)
794{
795 switch (n) {
796 case 0: /* specific game ID */
797 cfg_start(CFG_DESC);
798 break;
799 case 1: /* random game seed */
800 cfg_start(CFG_SEED);
801 break;
802 case 2: /* game parameter dropdown changed */
803 {
804 int i = js_get_selected_preset();
805 if (i < 0) {
806 /*
807 * The user selected 'Custom', so launch the config
808 * box.
809 */
810 if (thegame.can_configure) /* (double-check just in case) */
811 cfg_start(CFG_SETTINGS);
812 } else {
813 /*
814 * The user selected a preset, so just switch straight
815 * to that.
816 */
817 assert(i < npresets);
818 midend_set_params(me, presets[i]);
819 midend_new_game(me);
820 resize();
821 midend_redraw(me);
822 post_move();
823 js_focus_canvas();
824 select_appropriate_preset();
825 }
826 }
827 break;
828 case 3: /* OK clicked in a config box */
829 cfg_end(true);
830 post_move();
831 break;
832 case 4: /* Cancel clicked in a config box */
833 cfg_end(false);
834 post_move();
835 break;
836 case 5: /* New Game */
837 midend_process_key(me, 0, 0, UI_NEWGAME);
838 post_move();
839 js_focus_canvas();
840 break;
841 case 6: /* Restart */
842 midend_restart_game(me);
843 post_move();
844 js_focus_canvas();
845 break;
846 case 7: /* Undo */
847 midend_process_key(me, 0, 0, UI_UNDO);
848 post_move();
849 js_focus_canvas();
850 break;
851 case 8: /* Redo */
852 midend_process_key(me, 0, 0, UI_REDO);
853 post_move();
854 js_focus_canvas();
855 break;
856 case 9: /* Solve */
857 if (thegame.can_solve) {
858 const char *msg = midend_solve(me);
859 if (msg)
860 js_error_box(msg);
861 }
862 post_move();
863 js_focus_canvas();
864 break;
865 case 10: /* user preferences */
866 cfg_start(CFG_PREFS);
867 break;
868 }
869}
870
871char *get_text_format(void)
872{
873 return midend_text_format(me);
874}
875
876void free_text_format(char *buffer)
877{
878 sfree(buffer);
879}
880
881/* ----------------------------------------------------------------------
882 * Called from JS to prepare a save-game file, and free one after it's
883 * been used.
884 */
885
886struct savefile_write_ctx {
887 char *buffer;
888 size_t pos;
889};
890
891static void savefile_write(void *vctx, const void *buf, int len)
892{
893 struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)vctx;
894 if (ctx->buffer)
895 memcpy(ctx->buffer + ctx->pos, buf, len);
896 ctx->pos += len;
897}
898
899char *get_save_file(void)
900{
901 struct savefile_write_ctx ctx;
902 size_t size;
903
904 /* First pass, to count up the size */
905 ctx.buffer = NULL;
906 ctx.pos = 0;
907 midend_serialise(me, savefile_write, &ctx);
908 size = ctx.pos;
909
910 /* Second pass, to actually write out the data. We have to put a
911 * terminating \0 on the end (which we expect never to show up in
912 * the actual serialisation format - it's text, not binary) so
913 * that the Javascript side can easily find out the length. */
914 ctx.buffer = snewn(size+1, char);
915 ctx.pos = 0;
916 midend_serialise(me, savefile_write, &ctx);
917 assert(ctx.pos == size);
918 ctx.buffer[ctx.pos] = '\0';
919
920 return ctx.buffer;
921}
922
923void free_save_file(char *buffer)
924{
925 sfree(buffer);
926}
927
928static bool savefile_read(void *vctx, void *buf, int len)
929{
930 return js_savefile_read(buf, len);
931}
932
933void load_game(void)
934{
935 const char *err;
936
937 /*
938 * savefile_read_callback in JavaScript was set up by our caller
939 * as a closure that knows what file we're loading.
940 */
941 err = midend_deserialise(me, savefile_read, NULL);
942
943 if (err) {
944 js_error_box(err);
945 } else {
946 select_appropriate_preset();
947 resize();
948 midend_redraw(me);
949 update_permalinks();
950 post_move();
951 }
952}
953
954/* ----------------------------------------------------------------------
955 * Functions to load and save preferences, calling out to JS to access
956 * the appropriate localStorage slot.
957 */
958
959static void save_prefs(midend *me)
960{
961 struct savefile_write_ctx ctx;
962 size_t size;
963
964 /* First pass, to count up the size */
965 ctx.buffer = NULL;
966 ctx.pos = 0;
967 midend_save_prefs(me, savefile_write, &ctx);
968 size = ctx.pos;
969
970 /* Second pass, to actually write out the data. As with
971 * get_save_file, we append a terminating \0. */
972 ctx.buffer = snewn(size+1, char);
973 ctx.pos = 0;
974 midend_save_prefs(me, savefile_write, &ctx);
975 assert(ctx.pos == size);
976 ctx.buffer[ctx.pos] = '\0';
977
978 js_save_prefs(ctx.buffer);
979
980 sfree(ctx.buffer);
981}
982
983struct prefs_read_ctx {
984 const char *buffer;
985 size_t pos, len;
986};
987
988static bool prefs_read(void *vctx, void *buf, int len)
989{
990 struct prefs_read_ctx *ctx = (struct prefs_read_ctx *)vctx;
991
992 if (len < 0)
993 return false;
994 if (ctx->len - ctx->pos < len)
995 return false;
996 memcpy(buf, ctx->buffer + ctx->pos, len);
997 ctx->pos += len;
998 return true;
999}
1000
1001void prefs_load_callback(midend *me, const char *prefs)
1002{
1003 struct prefs_read_ctx ctx;
1004
1005 ctx.buffer = prefs;
1006 ctx.len = strlen(prefs);
1007 ctx.pos = 0;
1008
1009 midend_load_prefs(me, prefs_read, &ctx);
1010}
1011
1012/* ----------------------------------------------------------------------
1013 * Setup function called at page load time. It's called main() because
1014 * that's the most convenient thing in Emscripten, but it's not main()
1015 * in the usual sense of bounding the program's entire execution.
1016 * Instead, this function returns once the initial puzzle is set up
1017 * and working, and everything thereafter happens by means of JS event
1018 * handlers sending us callbacks.
1019 */
1020int main(int argc, char **argv)
1021{
1022 const char *param_err;
1023 float *colours;
1024 int i, ncolours;
1025
1026 /*
1027 * Initialise JavaScript event handlers.
1028 */
1029 js_init_puzzle();
1030
1031 /*
1032 * Instantiate a midend.
1033 */
1034 me = midend_new(NULL, &thegame, &js_drawing, NULL);
1035 js_load_prefs(me);
1036
1037 /*
1038 * Chuck in the HTML fragment ID if we have one (trimming the
1039 * leading # off the front first). If that's invalid, we retain
1040 * the error message and will display it at the end, after setting
1041 * up a random puzzle as usual.
1042 */
1043 if (argc > 1 && argv[1][0] == '#' && argv[1][1] != '\0')
1044 param_err = midend_game_id(me, argv[1] + 1);
1045 else
1046 param_err = NULL;
1047
1048 /*
1049 * Create either a random game or the specified one, and set the
1050 * canvas size appropriately.
1051 */
1052 midend_new_game(me);
1053 resize();
1054
1055 /*
1056 * Remove the status bar, if not needed.
1057 */
1058 if (!midend_wants_statusbar(me))
1059 js_canvas_remove_statusbar();
1060
1061 /*
1062 * Set up the game-type dropdown with presets and/or the Custom
1063 * option.
1064 */
1065 {
1066 struct preset_menu *menu = midend_get_presets(me, &npresets);
1067 bool may_configure = false;
1068 presets = snewn(npresets, game_params *);
1069 for (i = 0; i < npresets; i++)
1070 presets[i] = NULL;
1071
1072 populate_js_preset_menu(0, menu);
1073
1074 /*
1075 * Crude hack to allow the "Custom..." item to be hidden on
1076 * KaiOS, where dialogs don't yet work.
1077 */
1078 if (thegame.can_configure && getenv_bool("PUZZLES_ALLOW_CUSTOM", true))
1079 may_configure = true;
1080 if (may_configure)
1081 js_add_preset(0, "Custom...", -1);
1082
1083 have_presets_dropdown = npresets > 1 || may_configure;
1084
1085 if (have_presets_dropdown)
1086 /*
1087 * Now ensure the appropriate element of the presets menu
1088 * starts off selected, in case it isn't the first one in the
1089 * list (e.g. Slant).
1090 */
1091 select_appropriate_preset();
1092 else
1093 js_remove_type_dropdown();
1094 }
1095
1096 /*
1097 * Remove the Solve button if the game doesn't support it.
1098 */
1099 if (!thegame.can_solve)
1100 js_remove_solve_button();
1101
1102 /*
1103 * Retrieve the game's colours, and convert them into #abcdef type
1104 * hex ID strings.
1105 */
1106 colours = midend_colours(me, &ncolours);
1107 for (i = 0; i < ncolours; i++) {
1108 char col[40];
1109 sprintf(col, "#%02x%02x%02x",
1110 (unsigned)(0.5F + 255 * colours[i*3+0]),
1111 (unsigned)(0.5F + 255 * colours[i*3+1]),
1112 (unsigned)(0.5F + 255 * colours[i*3+2]));
1113 js_set_colour(i, col);
1114 }
1115
1116 /*
1117 * Request notification when the game ids change (e.g. if the user
1118 * presses 'n', and also when Mines supersedes its game
1119 * description), so that we can proactively update the permalink.
1120 */
1121 midend_request_id_changes(me, ids_changed, NULL);
1122
1123 /*
1124 * Draw the puzzle's initial state, and set up the permalinks and
1125 * undo/redo greying out.
1126 */
1127 midend_redraw(me);
1128 update_permalinks();
1129 post_move();
1130
1131 /*
1132 * If we were given an erroneous game ID in argv[1], now's the
1133 * time to put up the error box about it, after we've fully set up
1134 * a random puzzle. Then when the user clicks 'ok', we have a
1135 * puzzle for them.
1136 */
1137 if (param_err)
1138 js_error_box(param_err);
1139
1140 /*
1141 * Reveal the puzzle!
1142 */
1143 js_post_init();
1144
1145 /*
1146 * Done. Return to JS, and await callbacks!
1147 */
1148 return 0;
1149}
diff --git a/apps/plugins/puzzles/src/emcccopy.but b/apps/plugins/puzzles/src/emcccopy.but
deleted file mode 100644
index 5332e0df38..0000000000
--- a/apps/plugins/puzzles/src/emcccopy.but
+++ /dev/null
@@ -1,128 +0,0 @@
1\A{thirdparty} Third-party software licences
2
3\# This file should contain the copyright notices for third-party code
4included in the Emscripten builds of Puzzles. To get a list of
5relevant source files, you can build Puzzles with "-gsource-map" and
6then do something like:
7
8\# jq -r '.sources[]' *.map | sort -u
9
10\# This file is based on a build of Git commit
112e48ce132e011e83517a9fc4905edcc8f9a5ef58 using Emscripten 3.1.35
12
13\# system/lib/compiler-rt/lib/builtins/*
14\# upstream/lib/clang/17/include/tgmath.h
15
16\# These are under the Apache Licence v2.0 with LLVM Exceptions. The
17LLVM Exceptions allow us not to mention them in binary distributions.
18
19\# system/lib/dlmalloc.c
20
21\# dlmalloc is in the public domain and so needs no acknowledgement.
22
23The JavaScript and KaiOS versions of Puzzles incorporate some third
24party software. Most of it is licensed under the \i{MIT licence} (see
25\k{licence}) and requires the following \i{copyright} notices:
26
27\quote{
28
29\# system/lib/libc/emscripten_get_heap_size.c
30\# system/lib/libc/emscripten_memcpy.c
31\# system/lib/libc/emscripten_syscall_stubs.c
32\# system/lib/libc/wasi-helpers.c
33\# system/lib/pthread/library_pthread_stub.c
34\# system/lib/pthread/pthread_self_stub.c
35\# system/lib/sbrk.c
36
37\# These are parts of Emscripten and either refer explicitly to the
38Emscripten LICENSE file or make no mention of a licence. LICENSE
39allows use under the MIT licence and specifies this copyright notice:
40
41Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
42
43\# cache/sysroot/include/math.h
44\# system/lib/libc/musl/src/ctype/*
45\# system/lib/libc/musl/src/env/*
46\# system/lib/libc/musl/src/errno/*
47\# system/lib/libc/musl/src/internal/atomic.h
48\# system/lib/libc/musl/src/internal/floatscan.c
49\# system/lib/libc/musl/src/internal/intscan.c
50\# system/lib/libc/musl/src/internal/shgetc.c
51\# system/lib/libc/musl/src/math/copysignl.c
52\# system/lib/libc/musl/src/math/fabs.c
53\# system/lib/libc/musl/src/math/fabsl.c
54\# system/lib/libc/musl/src/math/floor.c
55\# system/lib/libc/musl/src/math/fmodl.c
56\# system/lib/libc/musl/src/math/__fpclassifyl.c
57\# system/lib/libc/musl/src/math/frexp.c
58\# system/lib/libc/musl/src/math/scalbn.c
59\# system/lib/libc/musl/src/math/scalbnl.c
60\# system/lib/libc/musl/src/math/sqrtf.c
61\# system/lib/libc/musl/src/multibyte/*
62\# system/lib/libc/musl/src/stdio/*
63\# system/lib/libc/musl/src/stdlib/abs.c
64\# system/lib/libc/musl/src/stdlib/atof.c
65\# system/lib/libc/musl/src/stdlib/atoi.c
66\# system/lib/libc/musl/src/stdlib/atol.c
67\# system/lib/libc/musl/src/stdlib/labs.c
68\# system/lib/libc/musl/src/stdlib/qsort_nr.c
69\# system/lib/libc/musl/src/stdlib/strtod.c
70\# system/lib/libc/musl/src/stdlib/strtol.c
71\# system/lib/libc/musl/src/string/*
72\# system/lib/libc/musl/src/unistd/getpid.c
73
74\# These are parts of musl, which is licensed "as a whole" under the
75MIT licence. These parts don't carry any licence notice themselves.
76This is the copyright notice from musl's COPYRIGHT file, modified to
77allow for non-Unicode targets:
78
79Copyright \u00A9{(C)} 2005-2020 Rich Felker, et al.
80
81\# system/lib/libc/musl/src/stdlib/qsort.c
82
83\# This is part of musl, but has its own copyright notice and MIT
84licence in its source file.
85
86Copyright (C) 2011 by Valentin Ochs
87
88}
89
90Other incorporated software requires these notices:
91
92\quote{
93
94\# system/lib/libc/musl/src/math/acosf.c
95\# system/lib/libc/musl/src/math/atan.c
96\# system/lib/libc/musl/src/math/cos.c
97\# system/lib/libc/musl/src/math/__cosdf.c
98\# system/lib/libc/musl/src/math/cosf.c
99\# system/lib/libc/musl/src/math/__rem_pio2f.c
100\# system/lib/libc/musl/src/math/sin.c
101\# system/lib/libc/musl/src/math/__sindf.c
102\# system/lib/libc/musl/src/math/sinf.c
103
104\# These are parts of musl with a SunPro copyright notice and licence.
105
106Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
107
108Developed at SunPro, a Sun Microsystems, Inc. business.
109Permission to use, copy, modify, and distribute this
110software is freely granted, provided that this notice
111is preserved.
112
113\# system/lib/libc/musl/src/math/atan2.c
114\# system/lib/libc/musl/src/math/__cos.c
115\# system/lib/libc/musl/src/math/__rem_pio2.c
116\# system/lib/libc/musl/src/math/__rem_pio2_large.c
117\# system/lib/libc/musl/src/math/__sin.c
118
119\# These are parts of musl with a SunSoft copyright notice and licence.
120
121Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
122
123Developed at SunSoft, a Sun Microsystems, Inc. business.
124Permission to use, copy, modify, and distribute this
125software is freely granted, provided that this notice
126is preserved.
127
128} \ No newline at end of file
diff --git a/apps/plugins/puzzles/src/fuzzpuzz.c b/apps/plugins/puzzles/src/fuzzpuzz.c
deleted file mode 100644
index 3fb632ec57..0000000000
--- a/apps/plugins/puzzles/src/fuzzpuzz.c
+++ /dev/null
@@ -1,250 +0,0 @@
1/*
2 * fuzzpuzz.c: Fuzzing frontend to all puzzles.
3 */
4
5/*
6 * The idea here is that this front-end supports all back-ends and can
7 * feed them save files. It then asks the back-end to draw the puzzle
8 * (through a null drawing API) and reserialises the state. This
9 * tests the deserialiser, the code for loading game descriptions, the
10 * processing of move strings, the redraw code, and the serialisation
11 * routines, but is still pretty quick.
12 *
13 * To use AFL++ to drive fuzzpuzz, you can do something like:
14 *
15 * CC=afl-cc cmake -B build-afl
16 * cmake --build build-afl --target fuzzpuzz
17 * mkdir fuzz-in && ln icons/''*.sav fuzz-in
18 * afl-fuzz -i fuzz-in -o fuzz-out -x fuzzpuzz.dict -- build-afl/fuzzpuzz
19 *
20 * Similarly with Honggfuzz:
21 *
22 * CC=hfuzz-cc cmake -B build-honggfuzz
23 * cmake --build build-honggfuzz --target fuzzpuzz
24 * mkdir fuzz-corpus && ln icons/''*.sav fuzz-corpus
25 * honggfuzz -s -i fuzz-corpus -w fuzzpuzz.dict -- build-honggfuzz/fuzzpuzz
26 *
27 * You can also use libFuzzer, though it's not really a good fit for
28 * Puzzles. The experimental forking mode seems to work OK:
29 *
30 * CC=clang cmake -B build-clang -DWITH_LIBFUZZER=Y
31 * cmake --build build-clang --target fuzzpuzz
32 * mkdir fuzz-corpus && ln icons/''*.sav fuzz-corpus
33 * build-clang/fuzzpuzz -fork=1 -ignore_crashes=1 -dict=fuzzpuzz.dict \
34 * fuzz-corpus
35 */
36
37#include <stdbool.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#ifdef __AFL_FUZZ_TESTCASE_LEN
42# include <unistd.h> /* read() is used by __AFL_FUZZ_TESTCASE_LEN. */
43#endif
44
45#include "puzzles.h"
46
47#ifdef __AFL_FUZZ_INIT
48__AFL_FUZZ_INIT();
49#endif
50
51#ifdef HAVE_HF_ITER
52extern int HF_ITER(unsigned char **, size_t *);
53#endif
54
55/* This function is expected by libFuzzer. */
56
57int LLVMFuzzerTestOneInput(unsigned char *data, size_t size);
58
59static const char *fuzz_one(bool (*readfn)(void *, void *, int), void *rctx,
60 void (*rewindfn)(void *),
61 void (*writefn)(void *, const void *, int),
62 void *wctx)
63{
64 const char *err;
65 char *gamename;
66 int i, w, h;
67 const game *ourgame = NULL;
68 static const drawing_api drapi = { NULL };
69 midend *me;
70
71 err = identify_game(&gamename, readfn, rctx);
72 if (err != NULL) return err;
73
74 for (i = 0; i < gamecount; i++)
75 if (strcmp(gamename, gamelist[i]->name) == 0)
76 ourgame = gamelist[i];
77 sfree(gamename);
78 if (ourgame == NULL)
79 return "Game not recognised";
80
81 me = midend_new(NULL, ourgame, &drapi, NULL);
82
83 rewindfn(rctx);
84 err = midend_deserialise(me, readfn, rctx);
85 if (err != NULL) {
86 midend_free(me);
87 return err;
88 }
89 w = h = INT_MAX;
90 midend_size(me, &w, &h, false, 1);
91 midend_redraw(me);
92 midend_serialise(me, writefn, wctx);
93 midend_free(me);
94 return NULL;
95}
96
97#if defined(__AFL_FUZZ_TESTCASE_LEN) || defined(HAVE_HF_ITER) || \
98 !defined(OMIT_MAIN)
99static void savefile_write(void *wctx, const void *buf, int len)
100{
101 FILE *fp = (FILE *)wctx;
102
103 fwrite(buf, 1, len, fp);
104}
105#endif
106
107struct memread {
108 const unsigned char *buf;
109 size_t pos;
110 size_t len;
111};
112
113static bool mem_read(void *wctx, void *buf, int len)
114{
115 struct memread *ctx = wctx;
116
117 if (ctx->pos + len > ctx->len) return false;
118 memcpy(buf, ctx->buf + ctx->pos, len);
119 ctx->pos += len;
120 return true;
121}
122
123static void mem_rewind(void *wctx)
124{
125 struct memread *ctx = wctx;
126
127 ctx->pos = 0;
128}
129
130static void null_write(void *wctx, const void *buf, int len)
131{
132}
133
134int LLVMFuzzerTestOneInput(unsigned char *data, size_t size) {
135 struct memread ctx;
136
137 ctx.buf = data;
138 ctx.len = size;
139 ctx.pos = 0;
140 fuzz_one(mem_read, &ctx, mem_rewind, null_write, NULL);
141 return 0;
142}
143
144#if defined(__AFL_FUZZ_TESTCASE_LEN) || defined(HAVE_HF_ITER)
145static const char *fuzz_one_mem(unsigned char *data, size_t size) {
146 struct memread ctx;
147
148 ctx.buf = data;
149 ctx.len = size;
150 ctx.pos = 0;
151 return fuzz_one(mem_read, &ctx, mem_rewind, savefile_write, stdout);
152}
153#endif
154
155/*
156 * Three different versions of main(), for standalone, AFL, and
157 * Honggfuzz modes. LibFuzzer brings its own main().
158 */
159
160#ifdef OMIT_MAIN
161/* Nothing. */
162#elif defined(__AFL_FUZZ_TESTCASE_LEN)
163/*
164 * AFL persistent mode, where we fuzz from a RAM buffer provided
165 * by AFL in a loop. This version can still be run standalone if
166 * necessary, for instance to diagnose a crash.
167 */
168int main(int argc, char **argv)
169{
170 const char *err;
171 int ret;
172
173 if (argc != 1) {
174 fprintf(stderr, "usage: %s\n", argv[0]);
175 return 1;
176 }
177#ifdef __AFL_HAVE_MANUAL_CONTROL
178 __AFL_INIT();
179#endif
180 while (__AFL_LOOP(10000)) {
181 err = fuzz_one_mem(__AFL_FUZZ_TESTCASE_BUF, __AFL_FUZZ_TESTCASE_LEN);
182 if (err != NULL) {
183 fprintf(stderr, "%s\n", err);
184 ret = 1;
185 } else
186 ret = 0;
187 }
188 return ret;
189}
190#elif defined(HAVE_HF_ITER)
191/*
192 * Honggfuzz persistent mode. Unlike AFL persistent mode, the
193 * resulting executable cannot be run outside of Honggfuzz.
194 */
195int main(int argc, char **argv)
196{
197 if (argc != 1) {
198 fprintf(stderr, "usage: %s\n", argv[0]);
199 return 1;
200 }
201 while (true) {
202 unsigned char *testcase_buf;
203 size_t testcase_len;
204 HF_ITER(&testcase_buf, &testcase_len);
205 fuzz_one_mem(testcase_buf, testcase_len);
206 }
207}
208#else
209/*
210 * Stand-alone mode: just handle a single test case on stdin.
211 */
212static bool savefile_read(void *wctx, void *buf, int len)
213{
214 FILE *fp = (FILE *)wctx;
215 int ret;
216
217 ret = fread(buf, 1, len, fp);
218 return (ret == len);
219}
220
221static void savefile_rewind(void *wctx)
222{
223 FILE *fp = (FILE *)wctx;
224
225 rewind(fp);
226}
227
228int main(int argc, char **argv)
229{
230 const char *err;
231
232 if (argc != 1) {
233 fprintf(stderr, "usage: %s\n", argv[0]);
234 return 1;
235 }
236
237 /* Might in theory use this mode under AFL. */
238#ifdef __AFL_HAVE_MANUAL_CONTROL
239 __AFL_INIT();
240#endif
241
242 err = fuzz_one(savefile_read, stdin, savefile_rewind,
243 savefile_write, stdout);
244 if (err != NULL) {
245 fprintf(stderr, "%s\n", err);
246 return 1;
247 }
248 return 0;
249}
250#endif
diff --git a/apps/plugins/puzzles/src/gtk.c b/apps/plugins/puzzles/src/gtk.c
deleted file mode 100644
index a40a70187f..0000000000
--- a/apps/plugins/puzzles/src/gtk.c
+++ /dev/null
@@ -1,4399 +0,0 @@
1/*
2 * gtk.c: GTK front end for my puzzle collection.
3 */
4
5#ifndef _GNU_SOURCE
6#define _GNU_SOURCE 1 /* for strcasestr */
7#endif
8
9#include <stdio.h>
10#include <assert.h>
11#include <stdlib.h>
12#include <time.h>
13#include <stdarg.h>
14#include <string.h>
15#include <errno.h>
16#ifdef NO_TGMATH_H
17# include <math.h>
18#else
19# include <tgmath.h>
20#endif
21#include <unistd.h>
22
23#include <fcntl.h>
24#include <sys/stat.h>
25#include <sys/types.h>
26#include <sys/time.h>
27#include <sys/resource.h>
28
29#include <gtk/gtk.h>
30#include <gdk/gdkkeysyms.h>
31
32#include <gdk-pixbuf/gdk-pixbuf.h>
33
34#include <gdk/gdkx.h>
35#include <X11/Xlib.h>
36#include <X11/Xutil.h>
37#include <X11/Xatom.h>
38
39#include "puzzles.h"
40#include "gtk.h"
41
42#if GTK_CHECK_VERSION(2,0,0)
43# define USE_PANGO
44# ifdef PANGO_VERSION_CHECK
45# if PANGO_VERSION_CHECK(1,8,0)
46# define HAVE_SENSIBLE_ABSOLUTE_SIZE_FUNCTION
47# endif
48# endif
49#endif
50#if !GTK_CHECK_VERSION(2,4,0)
51# define OLD_FILESEL
52#endif
53#if GTK_CHECK_VERSION(2,8,0)
54# define USE_CAIRO
55# if GTK_CHECK_VERSION(3,0,0) || defined(GDK_DISABLE_DEPRECATED)
56# define USE_CAIRO_WITHOUT_PIXMAP
57# endif
58#endif
59
60#if defined USE_CAIRO && GTK_CHECK_VERSION(2,10,0)
61/* We can only use printing if we are using Cairo for drawing and we
62 have a GTK version >= 2.10 (when GtkPrintOperation was added). */
63# define USE_PRINTING
64# if GTK_CHECK_VERSION(2,18,0)
65/* We can embed the page setup. Before 2.18, we needed to have a
66 separate page setup. */
67# define USE_EMBED_PAGE_SETUP
68# endif
69#endif
70
71#if GTK_CHECK_VERSION(3,0,0)
72/* The old names are still more concise! */
73#define gtk_hbox_new(x,y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL,y)
74#define gtk_vbox_new(x,y) gtk_box_new(GTK_ORIENTATION_VERTICAL,y)
75/* GTK 3 has retired stock button labels */
76#define LABEL_OK "_OK"
77#define LABEL_CANCEL "_Cancel"
78#define LABEL_NO "_No"
79#define LABEL_YES "_Yes"
80#define LABEL_SAVE "_Save"
81#define LABEL_OPEN "_Open"
82#define gtk_button_new_with_our_label gtk_button_new_with_mnemonic
83#else
84#define LABEL_OK GTK_STOCK_OK
85#define LABEL_CANCEL GTK_STOCK_CANCEL
86#define LABEL_NO GTK_STOCK_NO
87#define LABEL_YES GTK_STOCK_YES
88#define LABEL_SAVE GTK_STOCK_SAVE
89#define LABEL_OPEN GTK_STOCK_OPEN
90#define gtk_button_new_with_our_label gtk_button_new_from_stock
91#endif
92
93/* #undef USE_CAIRO */
94/* #define NO_THICK_LINE */
95#ifdef DEBUGGING
96static FILE *debug_fp = NULL;
97
98static void dputs(const char *buf)
99{
100 if (!debug_fp) {
101 debug_fp = fopen("debug.log", "w");
102 }
103
104 fputs(buf, stderr);
105
106 if (debug_fp) {
107 fputs(buf, debug_fp);
108 fflush(debug_fp);
109 }
110}
111
112void debug_printf(const char *fmt, ...)
113{
114 char buf[4096];
115 va_list ap;
116
117 va_start(ap, fmt);
118 vsprintf(buf, fmt, ap);
119 dputs(buf);
120 va_end(ap);
121}
122#endif
123
124/* ----------------------------------------------------------------------
125 * Error reporting functions used elsewhere.
126 */
127
128void fatal(const char *fmt, ...)
129{
130 va_list ap;
131
132 fprintf(stderr, "fatal error: ");
133
134 va_start(ap, fmt);
135 vfprintf(stderr, fmt, ap);
136 va_end(ap);
137
138 fprintf(stderr, "\n");
139 exit(1);
140}
141
142/* ----------------------------------------------------------------------
143 * GTK front end to puzzles.
144 */
145
146static void changed_preset(frontend *fe);
147static void load_prefs(frontend *fe);
148static char *save_prefs(frontend *fe);
149
150struct font {
151#ifdef USE_PANGO
152 PangoFontDescription *desc;
153#else
154 GdkFont *font;
155#endif
156 int type;
157 int size;
158};
159
160/*
161 * An internal API for functions which need to be different for
162 * printing and drawing.
163 */
164struct internal_drawing_api {
165 void (*set_colour)(frontend *fe, int colour);
166#ifdef USE_CAIRO
167 void (*fill)(frontend *fe);
168 void (*fill_preserve)(frontend *fe);
169#endif
170};
171
172/*
173 * This structure holds all the data relevant to a single window.
174 * In principle this would allow us to open multiple independent
175 * puzzle windows, although I can't currently see any real point in
176 * doing so. I'm just coding cleanly because there's no
177 * particularly good reason not to.
178 */
179struct frontend {
180 bool headless; /* true if we're running without GTK, for --screenshot */
181
182 GtkWidget *window;
183 GtkAccelGroup *dummy_accelgroup;
184 GtkWidget *area;
185 GtkWidget *statusbar;
186 GtkWidget *menubar;
187#if GTK_CHECK_VERSION(3,20,0)
188 GtkCssProvider *css_provider;
189#endif
190 guint statusctx;
191 int w, h;
192 midend *me;
193#ifdef USE_CAIRO
194 const float *colours;
195 cairo_t *cr;
196 cairo_surface_t *image;
197#ifndef USE_CAIRO_WITHOUT_PIXMAP
198 GdkPixmap *pixmap;
199#endif
200 GdkColor background; /* for painting outside puzzle area */
201#else
202 GdkPixmap *pixmap;
203 GdkGC *gc;
204 GdkColor *colours;
205 GdkColormap *colmap;
206 int backgroundindex; /* which of colours[] is background */
207#endif
208 int ncolours;
209 int bbox_l, bbox_r, bbox_u, bbox_d;
210 bool timer_active;
211 int timer_id;
212 struct timeval last_time;
213 struct font *fonts;
214 int nfonts, fontsize;
215 config_item *cfg;
216 int cfg_which;
217 bool cfgret;
218 GtkWidget *cfgbox;
219 void *paste_data;
220 int paste_data_len;
221 int pw, ph, ps; /* pixmap size (w, h are area size, s is GDK scale) */
222 int ox, oy; /* offset of pixmap in drawing area */
223#ifdef OLD_FILESEL
224 char *filesel_name;
225#endif
226 GSList *preset_radio;
227 bool preset_threaded;
228 GtkWidget *preset_custom;
229 GtkWidget *copy_menu_item;
230#if !GTK_CHECK_VERSION(3,0,0)
231 bool drawing_area_shrink_pending;
232 bool menubar_is_local;
233#endif
234#if GTK_CHECK_VERSION(3,0,0)
235 /*
236 * This is used to get round an annoying lack of GTK notification
237 * message. If we request a window resize with
238 * gtk_window_resize(), we normally get back a "configure" event
239 * on the window and on its drawing area, and we respond to the
240 * latter by doing an appropriate resize of the puzzle. If the
241 * window is maximised, so that gtk_window_resize() _doesn't_
242 * change its size, then that configure event never shows up. But
243 * if we requested the resize in response to a change of puzzle
244 * parameters (say, the user selected a differently-sized preset
245 * from the menu), then we would still like to be _notified_ that
246 * the window size was staying the same, so that we can respond by
247 * choosing an appropriate tile size for the new puzzle preset in
248 * the existing window size.
249 *
250 * Fortunately, in GTK 3, we may not get a "configure" event on
251 * the drawing area in this situation, but we still get a
252 * "size_allocate" event on the whole window (which, in other
253 * situations when we _do_ get a "configure" on the area, turns up
254 * second). So we treat _that_ event as indicating that if the
255 * "configure" event hasn't already shown up then it's not going
256 * to arrive.
257 *
258 * This flag is where we bookkeep this system. On
259 * gtk_window_resize we set this flag to true; the area's
260 * configure handler sets it back to false; then if that doesn't
261 * happen, the window's size_allocate handler does a fallback
262 * puzzle resize when it sees this flag still set to true.
263 */
264 bool awaiting_resize_ack;
265#endif
266#ifdef USE_CAIRO
267 int printcount, printw, printh;
268 float printscale;
269 bool printsolns, printcolour;
270 int hatch;
271 float hatchthick, hatchspace;
272 drawing *print_dr;
273 document *doc;
274#endif
275#ifdef USE_PRINTING
276 GtkPrintOperation *printop;
277 GtkPrintContext *printcontext;
278 GtkSpinButton *printcount_spin_button, *printw_spin_button,
279 *printh_spin_button, *printscale_spin_button;
280 GtkCheckButton *soln_check_button, *colour_check_button;
281#endif
282 const struct internal_drawing_api *dr_api;
283};
284
285struct blitter {
286#ifdef USE_CAIRO
287 cairo_surface_t *image;
288#else
289 GdkPixmap *pixmap;
290#endif
291 int w, h, x, y;
292};
293
294void get_random_seed(void **randseed, int *randseedsize)
295{
296 struct timeval *tvp = snew(struct timeval);
297 gettimeofday(tvp, NULL);
298 *randseed = (void *)tvp;
299 *randseedsize = sizeof(struct timeval);
300}
301
302void frontend_default_colour(frontend *fe, float *output)
303{
304#if !GTK_CHECK_VERSION(3,0,0)
305 if (!fe->headless) {
306 /*
307 * If we have a widget and it has a style that specifies a
308 * default background colour, use that as the background for
309 * the puzzle drawing area.
310 */
311 GdkColor col = gtk_widget_get_style(fe->window)->bg[GTK_STATE_NORMAL];
312 output[0] = col.red / 65535.0;
313 output[1] = col.green / 65535.0;
314 output[2] = col.blue / 65535.0;
315 }
316#endif
317
318 /*
319 * GTK 3 has decided that there's no such thing as a 'default
320 * background colour' any more, because widget styles might set
321 * the background to something more complicated like a background
322 * image. We don't want to get into overlaying our entire puzzle
323 * on an arbitrary background image, so we'll just make up a
324 * reasonable shade of grey.
325 *
326 * This is also what we do on GTK 2 in headless mode, where we
327 * don't have a widget style to query.
328 */
329 output[0] = output[1] = output[2] = 0.9F;
330}
331
332static void gtk_status_bar(void *handle, const char *text)
333{
334 frontend *fe = (frontend *)handle;
335
336 if (fe->headless)
337 return;
338
339 assert(fe->statusbar);
340
341 gtk_statusbar_pop(GTK_STATUSBAR(fe->statusbar), fe->statusctx);
342 gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx, text);
343}
344
345/* ----------------------------------------------------------------------
346 * Cairo drawing functions.
347 */
348
349#ifdef USE_CAIRO
350
351static void setup_drawing(frontend *fe)
352{
353 fe->cr = cairo_create(fe->image);
354 cairo_scale(fe->cr, fe->ps, fe->ps);
355 cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_GRAY);
356 cairo_set_line_width(fe->cr, 1.0);
357 cairo_set_line_cap(fe->cr, CAIRO_LINE_CAP_SQUARE);
358 cairo_set_line_join(fe->cr, CAIRO_LINE_JOIN_ROUND);
359}
360
361static void teardown_drawing(frontend *fe)
362{
363 cairo_destroy(fe->cr);
364 fe->cr = NULL;
365
366#ifndef USE_CAIRO_WITHOUT_PIXMAP
367 if (!fe->headless) {
368 cairo_t *cr = gdk_cairo_create(fe->pixmap);
369 cairo_set_source_surface(cr, fe->image, 0, 0);
370 cairo_rectangle(cr,
371 fe->bbox_l - 1,
372 fe->bbox_u - 1,
373 fe->bbox_r - fe->bbox_l + 2,
374 fe->bbox_d - fe->bbox_u + 2);
375 cairo_fill(cr);
376 cairo_destroy(cr);
377 }
378#endif
379}
380
381static void snaffle_colours(frontend *fe)
382{
383 fe->colours = midend_colours(fe->me, &fe->ncolours);
384}
385
386static void draw_set_colour(frontend *fe, int colour)
387{
388 cairo_set_source_rgb(fe->cr,
389 fe->colours[3*colour + 0],
390 fe->colours[3*colour + 1],
391 fe->colours[3*colour + 2]);
392}
393
394static void print_set_colour(frontend *fe, int colour)
395{
396 float r, g, b;
397
398 print_get_colour(fe->print_dr, colour, fe->printcolour,
399 &(fe->hatch), &r, &g, &b);
400
401 if (fe->hatch < 0)
402 cairo_set_source_rgb(fe->cr, r, g, b);
403}
404
405static void set_window_background(frontend *fe, int colour)
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);
422#if GTK_CHECK_VERSION(3,20,0)
423 char css_buf[512];
424 sprintf(css_buf, ".background { "
425 "background-color: #%02x%02x%02x; }",
426 (unsigned)(fe->colours[3*colour + 0] * 255),
427 (unsigned)(fe->colours[3*colour + 1] * 255),
428 (unsigned)(fe->colours[3*colour + 2] * 255));
429 if (!fe->css_provider)
430 fe->css_provider = gtk_css_provider_new();
431 if (!gtk_css_provider_load_from_data(
432 GTK_CSS_PROVIDER(fe->css_provider), css_buf, -1, NULL))
433 assert(0 && "Couldn't load CSS");
434 if (!dark_theme) {
435 gtk_style_context_add_provider(
436 gtk_widget_get_style_context(fe->window),
437 GTK_STYLE_PROVIDER(fe->css_provider),
438 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
439 }
440 gtk_style_context_add_provider(
441 gtk_widget_get_style_context(fe->area),
442 GTK_STYLE_PROVIDER(fe->css_provider),
443 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
444#else // still at least GTK 3.0 but less than 3.20
445 GdkRGBA rgba;
446 rgba.red = fe->colours[3*colour + 0];
447 rgba.green = fe->colours[3*colour + 1];
448 rgba.blue = fe->colours[3*colour + 2];
449 rgba.alpha = 1.0;
450 gdk_window_set_background_rgba(gtk_widget_get_window(fe->area), &rgba);
451 if (!dark_theme)
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
456 GdkColormap *colmap;
457
458 colmap = gdk_colormap_get_system();
459 fe->background.red = fe->colours[3*colour + 0] * 65535;
460 fe->background.green = fe->colours[3*colour + 1] * 65535;
461 fe->background.blue = fe->colours[3*colour + 2] * 65535;
462 if (!gdk_colormap_alloc_color(colmap, &fe->background, false, false)) {
463 g_error("couldn't allocate background (#%02x%02x%02x)\n",
464 fe->background.red >> 8, fe->background.green >> 8,
465 fe->background.blue >> 8);
466 }
467 gdk_window_set_background(gtk_widget_get_window(fe->area),
468 &fe->background);
469 gdk_window_set_background(gtk_widget_get_window(fe->window),
470 &fe->background);
471#endif
472}
473
474static PangoLayout *make_pango_layout(frontend *fe)
475{
476 return (pango_cairo_create_layout(fe->cr));
477}
478
479static void draw_pango_layout(frontend *fe, PangoLayout *layout,
480 int x, int y)
481{
482 cairo_move_to(fe->cr, x, y);
483 pango_cairo_show_layout(fe->cr, layout);
484}
485
486static void save_screenshot_png(frontend *fe, const char *screenshot_file)
487{
488 cairo_surface_write_to_png(fe->image, screenshot_file);
489}
490
491static void do_hatch(frontend *fe)
492{
493 double i, x, y, width, height, maxdim;
494
495 /* Get the dimensions of the region to be hatched. */
496 cairo_path_extents(fe->cr, &x, &y, &width, &height);
497
498 maxdim = max(width, height);
499
500 cairo_save(fe->cr);
501
502 /* Set the line color and width. */
503 cairo_set_source_rgb(fe->cr, 0, 0, 0);
504 cairo_set_line_width(fe->cr, fe->hatchthick);
505 /* Clip to the region. */
506 cairo_clip(fe->cr);
507 /* Hatch the bounding area of the fill region. */
508 if (fe->hatch == HATCH_VERT || fe->hatch == HATCH_PLUS) {
509 for (i = 0.0; i <= width; i += fe->hatchspace) {
510 cairo_move_to(fe->cr, i, 0);
511 cairo_rel_line_to(fe->cr, 0, height);
512 }
513 }
514 if (fe->hatch == HATCH_HORIZ || fe->hatch == HATCH_PLUS) {
515 for (i = 0.0; i <= height; i += fe->hatchspace) {
516 cairo_move_to(fe->cr, 0, i);
517 cairo_rel_line_to(fe->cr, width, 0);
518 }
519 }
520 if (fe->hatch == HATCH_SLASH || fe->hatch == HATCH_X) {
521 for (i = -height; i <= width; i += fe->hatchspace * ROOT2) {
522 cairo_move_to(fe->cr, i, 0);
523 cairo_rel_line_to(fe->cr, maxdim, maxdim);
524 }
525 }
526 if (fe->hatch == HATCH_BACKSLASH || fe->hatch == HATCH_X) {
527 for (i = 0.0; i <= width + height; i += fe->hatchspace * ROOT2) {
528 cairo_move_to(fe->cr, i, 0);
529 cairo_rel_line_to(fe->cr, -maxdim, maxdim);
530 }
531 }
532 cairo_stroke(fe->cr);
533
534 cairo_restore(fe->cr);
535}
536
537static void do_draw_fill(frontend *fe)
538{
539 cairo_fill(fe->cr);
540}
541
542static void do_draw_fill_preserve(frontend *fe)
543{
544 cairo_fill_preserve(fe->cr);
545}
546
547static void do_print_fill(frontend *fe)
548{
549 if (fe->hatch < 0)
550 cairo_fill(fe->cr);
551 else
552 do_hatch(fe);
553}
554
555static void do_print_fill_preserve(frontend *fe)
556{
557 if (fe->hatch < 0) {
558 cairo_fill_preserve(fe->cr);
559 } else {
560 cairo_path_t *oldpath;
561 oldpath = cairo_copy_path(fe->cr);
562 do_hatch(fe);
563 cairo_append_path(fe->cr, oldpath);
564 }
565}
566
567static void do_clip(frontend *fe, int x, int y, int w, int h)
568{
569 cairo_new_path(fe->cr);
570 cairo_rectangle(fe->cr, x, y, w, h);
571 cairo_clip(fe->cr);
572}
573
574static void do_unclip(frontend *fe)
575{
576 cairo_reset_clip(fe->cr);
577}
578
579static void do_draw_rect(frontend *fe, int x, int y, int w, int h)
580{
581 cairo_save(fe->cr);
582 cairo_new_path(fe->cr);
583 cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_NONE);
584 cairo_rectangle(fe->cr, x, y, w, h);
585 fe->dr_api->fill(fe);
586 cairo_restore(fe->cr);
587}
588
589static void do_draw_line(frontend *fe, int x1, int y1, int x2, int y2)
590{
591 cairo_new_path(fe->cr);
592 cairo_move_to(fe->cr, x1 + 0.5, y1 + 0.5);
593 cairo_line_to(fe->cr, x2 + 0.5, y2 + 0.5);
594 cairo_stroke(fe->cr);
595}
596
597static void do_draw_thick_line(frontend *fe, float thickness,
598 float x1, float y1, float x2, float y2)
599{
600 cairo_save(fe->cr);
601 cairo_set_line_width(fe->cr, thickness);
602 cairo_new_path(fe->cr);
603 cairo_move_to(fe->cr, x1, y1);
604 cairo_line_to(fe->cr, x2, y2);
605 cairo_stroke(fe->cr);
606 cairo_restore(fe->cr);
607}
608
609static void do_draw_poly(frontend *fe, const int *coords, int npoints,
610 int fillcolour, int outlinecolour)
611{
612 int i;
613
614 cairo_new_path(fe->cr);
615 for (i = 0; i < npoints; i++)
616 cairo_line_to(fe->cr, coords[i*2] + 0.5, coords[i*2 + 1] + 0.5);
617 cairo_close_path(fe->cr);
618 if (fillcolour >= 0) {
619 fe->dr_api->set_colour(fe, fillcolour);
620 fe->dr_api->fill_preserve(fe);
621 }
622 assert(outlinecolour >= 0);
623 fe->dr_api->set_colour(fe, outlinecolour);
624 cairo_stroke(fe->cr);
625}
626
627static void do_draw_circle(frontend *fe, int cx, int cy, int radius,
628 int fillcolour, int outlinecolour)
629{
630 cairo_new_path(fe->cr);
631 cairo_arc(fe->cr, cx + 0.5, cy + 0.5, radius, 0, 2*PI);
632 cairo_close_path(fe->cr); /* Just in case... */
633 if (fillcolour >= 0) {
634 fe->dr_api->set_colour(fe, fillcolour);
635 fe->dr_api->fill_preserve(fe);
636 }
637 assert(outlinecolour >= 0);
638 fe->dr_api->set_colour(fe, outlinecolour);
639 cairo_stroke(fe->cr);
640}
641
642static void setup_blitter(blitter *bl, int w, int h)
643{
644 bl->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h);
645}
646
647static void teardown_blitter(blitter *bl)
648{
649 cairo_surface_destroy(bl->image);
650}
651
652static void do_blitter_save(frontend *fe, blitter *bl, int x, int y)
653{
654 cairo_t *cr = cairo_create(bl->image);
655
656 cairo_set_source_surface(cr, fe->image, -x, -y);
657 cairo_paint(cr);
658 cairo_destroy(cr);
659}
660
661static void do_blitter_load(frontend *fe, blitter *bl, int x, int y)
662{
663 cairo_set_source_surface(fe->cr, bl->image, x, y);
664 cairo_paint(fe->cr);
665}
666
667static void clear_backing_store(frontend *fe)
668{
669 fe->image = NULL;
670}
671
672static void wipe_and_maybe_destroy_cairo(frontend *fe, cairo_t *cr,
673 bool destroy)
674{
675 cairo_set_source_rgb(cr, fe->colours[0], fe->colours[1], fe->colours[2]);
676 cairo_paint(cr);
677 if (destroy)
678 cairo_destroy(cr);
679}
680
681static void setup_backing_store(frontend *fe)
682{
683#ifndef USE_CAIRO_WITHOUT_PIXMAP
684 if (!fe->headless) {
685 fe->pixmap = gdk_pixmap_new(gtk_widget_get_window(fe->area),
686 fe->pw*fe->ps, fe->ph*fe->ps, -1);
687 } else {
688 fe->pixmap = NULL;
689 }
690#endif
691
692 fe->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
693 fe->pw*fe->ps, fe->ph*fe->ps);
694
695 wipe_and_maybe_destroy_cairo(fe, cairo_create(fe->image), true);
696#ifndef USE_CAIRO_WITHOUT_PIXMAP
697 if (!fe->headless)
698 wipe_and_maybe_destroy_cairo(fe, gdk_cairo_create(fe->pixmap), true);
699#endif
700 if (!fe->headless) {
701#if GTK_CHECK_VERSION(3,22,0)
702 GdkWindow *gdkwin;
703 cairo_region_t *region;
704 GdkDrawingContext *drawctx;
705 cairo_t *cr;
706
707 gdkwin = gtk_widget_get_window(fe->area);
708 region = gdk_window_get_clip_region(gdkwin);
709 drawctx = gdk_window_begin_draw_frame(gdkwin, region);
710 cr = gdk_drawing_context_get_cairo_context(drawctx);
711 wipe_and_maybe_destroy_cairo(fe, cr, false);
712 gdk_window_end_draw_frame(gdkwin, drawctx);
713 cairo_region_destroy(region);
714#else
715 wipe_and_maybe_destroy_cairo(
716 fe, gdk_cairo_create(gtk_widget_get_window(fe->area)), true);
717#endif
718 }
719}
720
721static bool backing_store_ok(frontend *fe)
722{
723 return fe->image != NULL;
724}
725
726static void teardown_backing_store(frontend *fe)
727{
728 cairo_surface_destroy(fe->image);
729#ifndef USE_CAIRO_WITHOUT_PIXMAP
730 gdk_pixmap_unref(fe->pixmap);
731#endif
732 fe->image = NULL;
733}
734
735#endif
736
737/* ----------------------------------------------------------------------
738 * GDK drawing functions.
739 */
740
741#ifndef USE_CAIRO
742
743static void setup_drawing(frontend *fe)
744{
745 fe->gc = gdk_gc_new(fe->area->window);
746}
747
748static void teardown_drawing(frontend *fe)
749{
750 gdk_gc_unref(fe->gc);
751 fe->gc = NULL;
752}
753
754static void snaffle_colours(frontend *fe)
755{
756 int i, ncolours;
757 float *colours;
758 gboolean *success;
759
760 fe->colmap = gdk_colormap_get_system();
761 colours = midend_colours(fe->me, &ncolours);
762 fe->ncolours = ncolours;
763 fe->colours = snewn(ncolours, GdkColor);
764 for (i = 0; i < ncolours; i++) {
765 fe->colours[i].red = colours[i*3] * 0xFFFF;
766 fe->colours[i].green = colours[i*3+1] * 0xFFFF;
767 fe->colours[i].blue = colours[i*3+2] * 0xFFFF;
768 }
769 success = snewn(ncolours, gboolean);
770 gdk_colormap_alloc_colors(fe->colmap, fe->colours, ncolours,
771 false, false, success);
772 for (i = 0; i < ncolours; i++) {
773 if (!success[i]) {
774 g_error("couldn't allocate colour %d (#%02x%02x%02x)\n",
775 i, fe->colours[i].red >> 8,
776 fe->colours[i].green >> 8,
777 fe->colours[i].blue >> 8);
778 }
779 }
780}
781
782static void set_window_background(frontend *fe, int colour)
783{
784 fe->backgroundindex = colour;
785 gdk_window_set_background(fe->area->window, &fe->colours[colour]);
786 gdk_window_set_background(fe->window->window, &fe->colours[colour]);
787}
788
789static void draw_set_colour(frontend *fe, int colour)
790{
791 gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
792}
793
794#ifdef USE_PANGO
795static PangoLayout *make_pango_layout(frontend *fe)
796{
797 return (pango_layout_new(gtk_widget_get_pango_context(fe->area)));
798}
799
800static void draw_pango_layout(frontend *fe, PangoLayout *layout,
801 int x, int y)
802{
803 gdk_draw_layout(fe->pixmap, fe->gc, x, y, layout);
804}
805#endif
806
807static void save_screenshot_png(frontend *fe, const char *screenshot_file)
808{
809 GdkPixbuf *pb;
810 GError *gerror = NULL;
811
812 midend_redraw(fe->me);
813
814 pb = gdk_pixbuf_get_from_drawable(NULL, fe->pixmap,
815 NULL, 0, 0, 0, 0, -1, -1);
816 gdk_pixbuf_save(pb, screenshot_file, "png", &gerror, NULL);
817}
818
819static void do_clip(frontend *fe, int x, int y, int w, int h)
820{
821 GdkRectangle rect;
822
823 rect.x = x;
824 rect.y = y;
825 rect.width = w;
826 rect.height = h;
827 gdk_gc_set_clip_rectangle(fe->gc, &rect);
828}
829
830static void do_unclip(frontend *fe)
831{
832 GdkRectangle rect;
833
834 rect.x = 0;
835 rect.y = 0;
836 rect.width = fe->w;
837 rect.height = fe->h;
838 gdk_gc_set_clip_rectangle(fe->gc, &rect);
839}
840
841static void do_draw_rect(frontend *fe, int x, int y, int w, int h)
842{
843 gdk_draw_rectangle(fe->pixmap, fe->gc, 1, x, y, w, h);
844}
845
846static void do_draw_line(frontend *fe, int x1, int y1, int x2, int y2)
847{
848 gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2);
849}
850
851static void do_draw_thick_line(frontend *fe, float thickness,
852 float x1, float y1, float x2, float y2)
853{
854 GdkGCValues save;
855
856 gdk_gc_get_values(fe->gc, &save);
857 gdk_gc_set_line_attributes(fe->gc,
858 thickness,
859 GDK_LINE_SOLID,
860 GDK_CAP_BUTT,
861 GDK_JOIN_BEVEL);
862 gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2);
863 gdk_gc_set_line_attributes(fe->gc,
864 save.line_width,
865 save.line_style,
866 save.cap_style,
867 save.join_style);
868}
869
870static void do_draw_poly(frontend *fe, const int *coords, int npoints,
871 int fillcolour, int outlinecolour)
872{
873 GdkPoint *points = snewn(npoints, GdkPoint);
874 int i;
875
876 for (i = 0; i < npoints; i++) {
877 points[i].x = coords[i*2];
878 points[i].y = coords[i*2+1];
879 }
880
881 if (fillcolour >= 0) {
882 fe->dr_api->set_colour(fe, fillcolour);
883 gdk_draw_polygon(fe->pixmap, fe->gc, true, points, npoints);
884 }
885 assert(outlinecolour >= 0);
886 fe->dr_api->set_colour(fe, outlinecolour);
887
888 /*
889 * In principle we ought to be able to use gdk_draw_polygon for
890 * the outline as well. In fact, it turns out to interact badly
891 * with a clipping region, for no terribly obvious reason, so I
892 * draw the outline as a sequence of lines instead.
893 */
894 for (i = 0; i < npoints; i++)
895 gdk_draw_line(fe->pixmap, fe->gc,
896 points[i].x, points[i].y,
897 points[(i+1)%npoints].x, points[(i+1)%npoints].y);
898
899 sfree(points);
900}
901
902static void do_draw_circle(frontend *fe, int cx, int cy, int radius,
903 int fillcolour, int outlinecolour)
904{
905 if (fillcolour >= 0) {
906 fe->dr_api->set_colour(fe, fillcolour);
907 gdk_draw_arc(fe->pixmap, fe->gc, true,
908 cx - radius, cy - radius,
909 2 * radius, 2 * radius, 0, 360 * 64);
910 }
911
912 assert(outlinecolour >= 0);
913 fe->dr_api->set_colour(fe, outlinecolour);
914 gdk_draw_arc(fe->pixmap, fe->gc, false,
915 cx - radius, cy - radius,
916 2 * radius, 2 * radius, 0, 360 * 64);
917}
918
919static void setup_blitter(blitter *bl, int w, int h)
920{
921 /*
922 * We can't create the pixmap right now, because fe->window
923 * might not yet exist. So we just cache w and h and create it
924 * during the firs call to blitter_save.
925 */
926 bl->pixmap = NULL;
927}
928
929static void teardown_blitter(blitter *bl)
930{
931 if (bl->pixmap)
932 gdk_pixmap_unref(bl->pixmap);
933}
934
935static void do_blitter_save(frontend *fe, blitter *bl, int x, int y)
936{
937 if (!bl->pixmap)
938 bl->pixmap = gdk_pixmap_new(fe->area->window, bl->w, bl->h, -1);
939 gdk_draw_pixmap(bl->pixmap,
940 fe->area->style->fg_gc[GTK_WIDGET_STATE(fe->area)],
941 fe->pixmap,
942 x, y, 0, 0, bl->w, bl->h);
943}
944
945static void do_blitter_load(frontend *fe, blitter *bl, int x, int y)
946{
947 assert(bl->pixmap);
948 gdk_draw_pixmap(fe->pixmap,
949 fe->area->style->fg_gc[GTK_WIDGET_STATE(fe->area)],
950 bl->pixmap,
951 0, 0, x, y, bl->w, bl->h);
952}
953
954static void clear_backing_store(frontend *fe)
955{
956 fe->pixmap = NULL;
957}
958
959static void setup_backing_store(frontend *fe)
960{
961 GdkGC *gc;
962
963 if (fe->headless) {
964 fprintf(stderr, "headless mode does not work with GDK drawing\n");
965 exit(1);
966 }
967
968 fe->pixmap = gdk_pixmap_new(fe->area->window, fe->pw, fe->ph, -1);
969
970 gc = gdk_gc_new(fe->area->window);
971 gdk_gc_set_foreground(gc, &fe->colours[0]);
972 gdk_draw_rectangle(fe->pixmap, gc, 1, 0, 0, fe->pw, fe->ph);
973 gdk_draw_rectangle(fe->area->window, gc, 1, 0, 0, fe->w, fe->h);
974 gdk_gc_unref(gc);
975}
976
977static int backing_store_ok(frontend *fe)
978{
979 return (!!fe->pixmap);
980}
981
982static void teardown_backing_store(frontend *fe)
983{
984 gdk_pixmap_unref(fe->pixmap);
985 fe->pixmap = NULL;
986}
987
988#endif
989
990#ifndef USE_CAIRO_WITHOUT_PIXMAP
991static void repaint_rectangle(frontend *fe, GtkWidget *widget,
992 int x, int y, int w, int h)
993{
994 GdkGC *gc = gdk_gc_new(gtk_widget_get_window(widget));
995#ifdef USE_CAIRO
996 gdk_gc_set_foreground(gc, &fe->background);
997#else
998 gdk_gc_set_foreground(gc, &fe->colours[fe->backgroundindex]);
999#endif
1000 if (x < fe->ox) {
1001 gdk_draw_rectangle(gtk_widget_get_window(widget), gc,
1002 true, x, y, fe->ox - x, h);
1003 w -= (fe->ox - x);
1004 x = fe->ox;
1005 }
1006 if (y < fe->oy) {
1007 gdk_draw_rectangle(gtk_widget_get_window(widget), gc,
1008 true, x, y, w, fe->oy - y);
1009 h -= (fe->oy - y);
1010 y = fe->oy;
1011 }
1012 if (w > fe->pw) {
1013 gdk_draw_rectangle(gtk_widget_get_window(widget), gc,
1014 true, x + fe->pw, y, w - fe->pw, h);
1015 w = fe->pw;
1016 }
1017 if (h > fe->ph) {
1018 gdk_draw_rectangle(gtk_widget_get_window(widget), gc,
1019 true, x, y + fe->ph, w, h - fe->ph);
1020 h = fe->ph;
1021 }
1022 gdk_draw_pixmap(gtk_widget_get_window(widget), gc, fe->pixmap,
1023 x - fe->ox, y - fe->oy, x, y, w, h);
1024 gdk_gc_unref(gc);
1025}
1026#endif
1027
1028/* ----------------------------------------------------------------------
1029 * Pango font functions.
1030 */
1031
1032#ifdef USE_PANGO
1033
1034static void add_font(frontend *fe, int index, int fonttype, int fontsize)
1035{
1036 /*
1037 * Use Pango to find the closest match to the requested
1038 * font.
1039 */
1040 PangoFontDescription *fd;
1041
1042 fd = pango_font_description_new();
1043 /* `Monospace' and `Sans' are meta-families guaranteed to exist */
1044 pango_font_description_set_family(fd, fonttype == FONT_FIXED ?
1045 "Monospace" : "Sans");
1046 pango_font_description_set_weight(fd, PANGO_WEIGHT_BOLD);
1047 /*
1048 * I found some online Pango documentation which
1049 * described a function called
1050 * pango_font_description_set_absolute_size(), which is
1051 * _exactly_ what I want here. Unfortunately, none of
1052 * my local Pango installations have it (presumably
1053 * they're too old), so I'm going to have to hack round
1054 * it by figuring out the point size myself. This
1055 * limits me to X and probably also breaks in later
1056 * Pango installations, so ideally I should add another
1057 * CHECK_VERSION type ifdef and use set_absolute_size
1058 * where available. All very annoying.
1059 */
1060#ifdef HAVE_SENSIBLE_ABSOLUTE_SIZE_FUNCTION
1061 pango_font_description_set_absolute_size(fd, PANGO_SCALE*fontsize);
1062#else
1063 {
1064 Display *d = GDK_DISPLAY();
1065 int s = DefaultScreen(d);
1066 double resolution =
1067 (PANGO_SCALE * 72.27 / 25.4) *
1068 ((double) DisplayWidthMM(d, s) / DisplayWidth (d, s));
1069 pango_font_description_set_size(fd, resolution * fontsize);
1070 }
1071#endif
1072 fe->fonts[index].desc = fd;
1073}
1074
1075static void align_and_draw_text(frontend *fe,
1076 int index, int align, int x, int y,
1077 const char *text)
1078{
1079 PangoLayout *layout;
1080 PangoRectangle rect;
1081
1082 layout = make_pango_layout(fe);
1083
1084 /*
1085 * Create a layout.
1086 */
1087 pango_layout_set_font_description(layout, fe->fonts[index].desc);
1088 pango_layout_set_text(layout, text, strlen(text));
1089 pango_layout_get_pixel_extents(layout, NULL, &rect);
1090
1091 if (align & ALIGN_VCENTRE)
1092 rect.y -= rect.height / 2;
1093 else
1094 rect.y -= rect.height;
1095
1096 if (align & ALIGN_HCENTRE)
1097 rect.x -= rect.width / 2;
1098 else if (align & ALIGN_HRIGHT)
1099 rect.x -= rect.width;
1100
1101 draw_pango_layout(fe, layout, rect.x + x, rect.y + y);
1102
1103 g_object_unref(layout);
1104}
1105
1106#endif
1107
1108/* ----------------------------------------------------------------------
1109 * Old-fashioned font functions.
1110 */
1111
1112#ifndef USE_PANGO
1113
1114static void add_font(int index, int fonttype, int fontsize)
1115{
1116 /*
1117 * In GTK 1.2, I don't know of any plausible way to
1118 * pick a suitable font, so I'm just going to be
1119 * tedious.
1120 */
1121 fe->fonts[i].font = gdk_font_load(fonttype == FONT_FIXED ?
1122 "fixed" : "variable");
1123}
1124
1125static void align_and_draw_text(int index, int align, int x, int y,
1126 const char *text)
1127{
1128 int lb, rb, wid, asc, desc;
1129
1130 /*
1131 * Measure vertical string extents with respect to the same
1132 * string always...
1133 */
1134 gdk_string_extents(fe->fonts[i].font,
1135 "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
1136 &lb, &rb, &wid, &asc, &desc);
1137 if (align & ALIGN_VCENTRE)
1138 y += asc - (asc+desc)/2;
1139 else
1140 y += asc;
1141
1142 /*
1143 * ... but horizontal extents with respect to the provided
1144 * string. This means that multiple pieces of text centred
1145 * on the same y-coordinate don't have different baselines.
1146 */
1147 gdk_string_extents(fe->fonts[i].font, text,
1148 &lb, &rb, &wid, &asc, &desc);
1149
1150 if (align & ALIGN_HCENTRE)
1151 x -= wid / 2;
1152 else if (align & ALIGN_HRIGHT)
1153 x -= wid;
1154
1155 /*
1156 * Actually draw the text.
1157 */
1158 gdk_draw_string(fe->pixmap, fe->fonts[i].font, fe->gc, x, y, text);
1159}
1160
1161#endif
1162
1163/* ----------------------------------------------------------------------
1164 * The exported drawing functions.
1165 */
1166
1167static void gtk_start_draw(void *handle)
1168{
1169 frontend *fe = (frontend *)handle;
1170 fe->bbox_l = fe->w;
1171 fe->bbox_r = 0;
1172 fe->bbox_u = fe->h;
1173 fe->bbox_d = 0;
1174 setup_drawing(fe);
1175}
1176
1177static void gtk_clip(void *handle, int x, int y, int w, int h)
1178{
1179 frontend *fe = (frontend *)handle;
1180 do_clip(fe, x, y, w, h);
1181}
1182
1183static void gtk_unclip(void *handle)
1184{
1185 frontend *fe = (frontend *)handle;
1186 do_unclip(fe);
1187}
1188
1189static void gtk_draw_text(void *handle, int x, int y, int fonttype,
1190 int fontsize, int align, int colour,
1191 const char *text)
1192{
1193 frontend *fe = (frontend *)handle;
1194 int i;
1195
1196 /*
1197 * Find or create the font.
1198 */
1199 for (i = 0; i < fe->nfonts; i++)
1200 if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
1201 break;
1202
1203 if (i == fe->nfonts) {
1204 if (fe->fontsize <= fe->nfonts) {
1205 fe->fontsize = fe->nfonts + 10;
1206 fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
1207 }
1208
1209 fe->nfonts++;
1210
1211 fe->fonts[i].type = fonttype;
1212 fe->fonts[i].size = fontsize;
1213 add_font(fe, i, fonttype, fontsize);
1214 }
1215
1216 /*
1217 * Do the job.
1218 */
1219 fe->dr_api->set_colour(fe, colour);
1220 align_and_draw_text(fe, i, align, x, y, text);
1221}
1222
1223static void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour)
1224{
1225 frontend *fe = (frontend *)handle;
1226 fe->dr_api->set_colour(fe, colour);
1227 do_draw_rect(fe, x, y, w, h);
1228}
1229
1230static void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2,
1231 int colour)
1232{
1233 frontend *fe = (frontend *)handle;
1234 fe->dr_api->set_colour(fe, colour);
1235 do_draw_line(fe, x1, y1, x2, y2);
1236}
1237
1238static void gtk_draw_thick_line(void *handle, float thickness,
1239 float x1, float y1, float x2, float y2,
1240 int colour)
1241{
1242 frontend *fe = (frontend *)handle;
1243 fe->dr_api->set_colour(fe, colour);
1244 do_draw_thick_line(fe, thickness, x1, y1, x2, y2);
1245}
1246
1247static void gtk_draw_poly(void *handle, const int *coords, int npoints,
1248 int fillcolour, int outlinecolour)
1249{
1250 frontend *fe = (frontend *)handle;
1251 do_draw_poly(fe, coords, npoints, fillcolour, outlinecolour);
1252}
1253
1254static void gtk_draw_circle(void *handle, int cx, int cy, int radius,
1255 int fillcolour, int outlinecolour)
1256{
1257 frontend *fe = (frontend *)handle;
1258 do_draw_circle(fe, cx, cy, radius, fillcolour, outlinecolour);
1259}
1260
1261static blitter *gtk_blitter_new(void *handle, int w, int h)
1262{
1263 blitter *bl = snew(blitter);
1264 setup_blitter(bl, w, h);
1265 bl->w = w;
1266 bl->h = h;
1267 return bl;
1268}
1269
1270static void gtk_blitter_free(void *handle, blitter *bl)
1271{
1272 teardown_blitter(bl);
1273 sfree(bl);
1274}
1275
1276static void gtk_blitter_save(void *handle, blitter *bl, int x, int y)
1277{
1278 frontend *fe = (frontend *)handle;
1279 do_blitter_save(fe, bl, x, y);
1280 bl->x = x;
1281 bl->y = y;
1282}
1283
1284static void gtk_blitter_load(void *handle, blitter *bl, int x, int y)
1285{
1286 frontend *fe = (frontend *)handle;
1287 if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) {
1288 x = bl->x;
1289 y = bl->y;
1290 }
1291 do_blitter_load(fe, bl, x, y);
1292}
1293
1294static void gtk_draw_update(void *handle, int x, int y, int w, int h)
1295{
1296 frontend *fe = (frontend *)handle;
1297 if (fe->bbox_l > x ) fe->bbox_l = x ;
1298 if (fe->bbox_r < x+w) fe->bbox_r = x+w;
1299 if (fe->bbox_u > y ) fe->bbox_u = y ;
1300 if (fe->bbox_d < y+h) fe->bbox_d = y+h;
1301}
1302
1303static void gtk_end_draw(void *handle)
1304{
1305 frontend *fe = (frontend *)handle;
1306
1307 teardown_drawing(fe);
1308
1309 if (fe->bbox_l < fe->bbox_r && fe->bbox_u < fe->bbox_d && !fe->headless) {
1310#ifdef USE_CAIRO_WITHOUT_PIXMAP
1311 gtk_widget_queue_draw_area(fe->area,
1312 fe->bbox_l - 1 + fe->ox,
1313 fe->bbox_u - 1 + fe->oy,
1314 fe->bbox_r - fe->bbox_l + 2,
1315 fe->bbox_d - fe->bbox_u + 2);
1316#else
1317 repaint_rectangle(fe, fe->area,
1318 fe->bbox_l - 1 + fe->ox,
1319 fe->bbox_u - 1 + fe->oy,
1320 fe->bbox_r - fe->bbox_l + 2,
1321 fe->bbox_d - fe->bbox_u + 2);
1322#endif
1323 }
1324}
1325
1326#ifdef USE_PANGO
1327static char *gtk_text_fallback(void *handle, const char *const *strings,
1328 int nstrings)
1329{
1330 /*
1331 * We assume Pango can cope with any UTF-8 likely to be emitted
1332 * by a puzzle.
1333 */
1334 return dupstr(strings[0]);
1335}
1336#endif
1337
1338#ifdef USE_PRINTING
1339static void gtk_begin_doc(void *handle, int pages)
1340{
1341 frontend *fe = (frontend *)handle;
1342 gtk_print_operation_set_n_pages(fe->printop, pages);
1343}
1344
1345static void gtk_begin_page(void *handle, int number)
1346{
1347}
1348
1349static void gtk_begin_puzzle(void *handle, float xm, float xc,
1350 float ym, float yc, int pw, int ph, float wmm)
1351{
1352 frontend *fe = (frontend *)handle;
1353 double ppw, pph, pox, poy, dpmmx, dpmmy;
1354 double scale;
1355
1356 ppw = gtk_print_context_get_width(fe->printcontext);
1357 pph = gtk_print_context_get_height(fe->printcontext);
1358 dpmmx = gtk_print_context_get_dpi_x(fe->printcontext) / 25.4;
1359 dpmmy = gtk_print_context_get_dpi_y(fe->printcontext) / 25.4;
1360
1361 /*
1362 * Compute the puzzle's position in pixels on the logical page.
1363 */
1364 pox = xm * ppw + xc * dpmmx;
1365 poy = ym * pph + yc * dpmmy;
1366
1367 /*
1368 * And determine the scale.
1369 *
1370 * I need a scale such that the maximum puzzle-coordinate
1371 * extent of the rectangle (pw * scale) is equal to the pixel
1372 * equivalent of the puzzle's millimetre width (wmm * dpmmx).
1373 */
1374 scale = wmm * dpmmx / pw;
1375
1376 /*
1377 * Now instruct Cairo to transform points based on our calculated
1378 * values (order here *is* important).
1379 */
1380 cairo_save(fe->cr);
1381 cairo_translate(fe->cr, pox, poy);
1382 cairo_scale(fe->cr, scale, scale);
1383
1384 fe->hatchthick = 0.2 * pw / wmm;
1385 fe->hatchspace = 1.0 * pw / wmm;
1386}
1387
1388static void gtk_end_puzzle(void *handle)
1389{
1390 frontend *fe = (frontend *)handle;
1391 cairo_restore(fe->cr);
1392}
1393
1394static void gtk_end_page(void *handle, int number)
1395{
1396}
1397
1398static void gtk_end_doc(void *handle)
1399{
1400}
1401
1402static void gtk_line_width(void *handle, float width)
1403{
1404 frontend *fe = (frontend *)handle;
1405 cairo_set_line_width(fe->cr, width);
1406}
1407
1408static void gtk_line_dotted(void *handle, bool dotted)
1409{
1410 frontend *fe = (frontend *)handle;
1411
1412 if (dotted) {
1413 const double dash = 35.0;
1414 cairo_set_dash(fe->cr, &dash, 1, 0);
1415 } else {
1416 cairo_set_dash(fe->cr, NULL, 0, 0);
1417 }
1418}
1419#endif /* USE_PRINTING */
1420
1421static const struct internal_drawing_api internal_drawing = {
1422 draw_set_colour,
1423#ifdef USE_CAIRO
1424 do_draw_fill,
1425 do_draw_fill_preserve,
1426#endif
1427};
1428
1429#ifdef USE_CAIRO
1430static const struct internal_drawing_api internal_printing = {
1431 print_set_colour,
1432 do_print_fill,
1433 do_print_fill_preserve,
1434};
1435#endif
1436
1437static const struct drawing_api gtk_drawing = {
1438 gtk_draw_text,
1439 gtk_draw_rect,
1440 gtk_draw_line,
1441 gtk_draw_poly,
1442 gtk_draw_circle,
1443 gtk_draw_update,
1444 gtk_clip,
1445 gtk_unclip,
1446 gtk_start_draw,
1447 gtk_end_draw,
1448 gtk_status_bar,
1449 gtk_blitter_new,
1450 gtk_blitter_free,
1451 gtk_blitter_save,
1452 gtk_blitter_load,
1453#ifdef USE_PRINTING
1454 gtk_begin_doc,
1455 gtk_begin_page,
1456 gtk_begin_puzzle,
1457 gtk_end_puzzle,
1458 gtk_end_page,
1459 gtk_end_doc,
1460 gtk_line_width,
1461 gtk_line_dotted,
1462#else
1463 NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
1464 NULL, NULL, /* line_width, line_dotted */
1465#endif
1466#ifdef USE_PANGO
1467 gtk_text_fallback,
1468#else
1469 NULL,
1470#endif
1471#ifdef NO_THICK_LINE
1472 NULL,
1473#else
1474 gtk_draw_thick_line,
1475#endif
1476};
1477
1478static void destroy(GtkWidget *widget, gpointer data)
1479{
1480 frontend *fe = (frontend *)data;
1481 deactivate_timer(fe);
1482 midend_free(fe->me);
1483 gtk_main_quit();
1484}
1485
1486static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
1487{
1488 frontend *fe = (frontend *)data;
1489 int keyval;
1490 int shift = (event->state & GDK_SHIFT_MASK) ? MOD_SHFT : 0;
1491 int ctrl = (event->state & GDK_CONTROL_MASK) ? MOD_CTRL : 0;
1492
1493 if (!backing_store_ok(fe))
1494 return true;
1495
1496 /* Handle mnemonics. */
1497 if (gtk_window_activate_key(GTK_WINDOW(fe->window), event))
1498 return true;
1499
1500 if (event->keyval == GDK_KEY_Up)
1501 keyval = shift | ctrl | CURSOR_UP;
1502 else if (event->keyval == GDK_KEY_KP_Up ||
1503 event->keyval == GDK_KEY_KP_8)
1504 keyval = MOD_NUM_KEYPAD | '8';
1505 else if (event->keyval == GDK_KEY_Down)
1506 keyval = shift | ctrl | CURSOR_DOWN;
1507 else if (event->keyval == GDK_KEY_KP_Down ||
1508 event->keyval == GDK_KEY_KP_2)
1509 keyval = MOD_NUM_KEYPAD | '2';
1510 else if (event->keyval == GDK_KEY_Left)
1511 keyval = shift | ctrl | CURSOR_LEFT;
1512 else if (event->keyval == GDK_KEY_KP_Left ||
1513 event->keyval == GDK_KEY_KP_4)
1514 keyval = MOD_NUM_KEYPAD | '4';
1515 else if (event->keyval == GDK_KEY_Right)
1516 keyval = shift | ctrl | CURSOR_RIGHT;
1517 else if (event->keyval == GDK_KEY_KP_Right ||
1518 event->keyval == GDK_KEY_KP_6)
1519 keyval = MOD_NUM_KEYPAD | '6';
1520 else if (event->keyval == GDK_KEY_KP_Home ||
1521 event->keyval == GDK_KEY_KP_7)
1522 keyval = MOD_NUM_KEYPAD | '7';
1523 else if (event->keyval == GDK_KEY_KP_End ||
1524 event->keyval == GDK_KEY_KP_1)
1525 keyval = MOD_NUM_KEYPAD | '1';
1526 else if (event->keyval == GDK_KEY_KP_Page_Up ||
1527 event->keyval == GDK_KEY_KP_9)
1528 keyval = MOD_NUM_KEYPAD | '9';
1529 else if (event->keyval == GDK_KEY_KP_Page_Down ||
1530 event->keyval == GDK_KEY_KP_3)
1531 keyval = MOD_NUM_KEYPAD | '3';
1532 else if (event->keyval == GDK_KEY_KP_Insert ||
1533 event->keyval == GDK_KEY_KP_0)
1534 keyval = MOD_NUM_KEYPAD | '0';
1535 else if (event->keyval == GDK_KEY_KP_Begin ||
1536 event->keyval == GDK_KEY_KP_5)
1537 keyval = MOD_NUM_KEYPAD | '5';
1538 else if (event->keyval == GDK_KEY_BackSpace ||
1539 event->keyval == GDK_KEY_Delete ||
1540 event->keyval == GDK_KEY_KP_Delete)
1541 keyval = '\177';
1542 else if ((event->keyval == 'z' || event->keyval == 'Z') && shift && ctrl)
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 }
1549 else if (event->string[0] && !event->string[1])
1550 keyval = (unsigned char)event->string[0];
1551 else
1552 keyval = -1;
1553
1554 if (keyval >= 0 &&
1555 midend_process_key(fe->me, 0, 0, keyval) == PKR_QUIT)
1556 gtk_widget_destroy(fe->window);
1557
1558 return true;
1559}
1560
1561static gint button_event(GtkWidget *widget, GdkEventButton *event,
1562 gpointer data)
1563{
1564 frontend *fe = (frontend *)data;
1565 int button;
1566
1567 if (!backing_store_ok(fe))
1568 return true;
1569
1570 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE)
1571 return true;
1572
1573 if (event->button == 2 || (event->state & GDK_SHIFT_MASK))
1574 button = MIDDLE_BUTTON;
1575 else if (event->button == 3 || (event->state & GDK_MOD1_MASK))
1576 button = RIGHT_BUTTON;
1577 else if (event->button == 1)
1578 button = LEFT_BUTTON;
1579 else if (event->button == 8 && event->type == GDK_BUTTON_PRESS)
1580 button = 'u';
1581 else if (event->button == 9 && event->type == GDK_BUTTON_PRESS)
1582 button = 'r';
1583 else
1584 return false; /* don't even know what button! */
1585
1586 if (event->type == GDK_BUTTON_RELEASE && button >= LEFT_BUTTON)
1587 button += LEFT_RELEASE - LEFT_BUTTON;
1588
1589 if (midend_process_key(fe->me, event->x - fe->ox,
1590 event->y - fe->oy, button) == PKR_QUIT)
1591 gtk_widget_destroy(fe->window);
1592
1593 return true;
1594}
1595
1596static gint motion_event(GtkWidget *widget, GdkEventMotion *event,
1597 gpointer data)
1598{
1599 frontend *fe = (frontend *)data;
1600 int button;
1601
1602 if (!backing_store_ok(fe))
1603 return true;
1604
1605 if (event->state & (GDK_BUTTON2_MASK | GDK_SHIFT_MASK))
1606 button = MIDDLE_DRAG;
1607 else if (event->state & GDK_BUTTON1_MASK)
1608 button = LEFT_DRAG;
1609 else if (event->state & GDK_BUTTON3_MASK)
1610 button = RIGHT_DRAG;
1611 else
1612 return false; /* don't even know what button! */
1613
1614 if (midend_process_key(fe->me, event->x - fe->ox,
1615 event->y - fe->oy, button) == PKR_QUIT)
1616 gtk_widget_destroy(fe->window);
1617#if GTK_CHECK_VERSION(2,12,0)
1618 gdk_event_request_motions(event);
1619#else
1620 gdk_window_get_pointer(gtk_widget_get_window(widget), NULL, NULL, NULL);
1621#endif
1622
1623 return true;
1624}
1625
1626#if GTK_CHECK_VERSION(3,0,0)
1627static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
1628{
1629 frontend *fe = (frontend *)data;
1630 GdkRectangle dirtyrect;
1631
1632 cairo_surface_t *target_surface = cairo_get_target(cr);
1633 cairo_matrix_t m;
1634 cairo_get_matrix(cr, &m);
1635 double orig_sx, orig_sy;
1636 cairo_surface_get_device_scale(target_surface, &orig_sx, &orig_sy);
1637 cairo_surface_set_device_scale(target_surface, 1.0, 1.0);
1638 cairo_translate(cr, m.x0 * (orig_sx - 1.0), m.y0 * (orig_sy - 1.0));
1639
1640 gdk_cairo_get_clip_rectangle(cr, &dirtyrect);
1641 cairo_set_source_surface(cr, fe->image, fe->ox, fe->oy);
1642 cairo_rectangle(cr, dirtyrect.x, dirtyrect.y,
1643 dirtyrect.width, dirtyrect.height);
1644 cairo_fill(cr);
1645
1646 cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy);
1647
1648 return true;
1649}
1650#else
1651static gint expose_area(GtkWidget *widget, GdkEventExpose *event,
1652 gpointer data)
1653{
1654 frontend *fe = (frontend *)data;
1655
1656 if (backing_store_ok(fe)) {
1657#ifdef USE_CAIRO_WITHOUT_PIXMAP
1658 cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget));
1659 cairo_set_source_surface(cr, fe->image, fe->ox, fe->oy);
1660 cairo_rectangle(cr, event->area.x, event->area.y,
1661 event->area.width, event->area.height);
1662 cairo_fill(cr);
1663 cairo_destroy(cr);
1664#else
1665 repaint_rectangle(fe, widget,
1666 event->area.x, event->area.y,
1667 event->area.width, event->area.height);
1668#endif
1669 }
1670 return true;
1671}
1672#endif
1673
1674static gint map_window(GtkWidget *widget, GdkEvent *event,
1675 gpointer data)
1676{
1677 frontend *fe = (frontend *)data;
1678
1679 /*
1680 * Apparently we need to do this because otherwise the status
1681 * bar will fail to update immediately. Annoying, but there we
1682 * go.
1683 */
1684 gtk_widget_queue_draw(fe->window);
1685
1686 return true;
1687}
1688
1689static void resize_puzzle_to_area(frontend *fe, int x, int y)
1690{
1691 int oldw = fe->w, oldpw = fe->pw, oldh = fe->h, oldph = fe->ph;
1692 int oldps = fe->ps;
1693
1694 fe->w = x;
1695 fe->h = y;
1696 midend_size(fe->me, &x, &y, true, 1.0);
1697 fe->pw = x;
1698 fe->ph = y;
1699#if GTK_CHECK_VERSION(3,10,0)
1700 fe->ps = gtk_widget_get_scale_factor(fe->area);
1701#else
1702 fe->ps = 1;
1703#endif
1704 fe->ox = (fe->w - fe->pw) / 2;
1705 fe->oy = (fe->h - fe->ph) / 2;
1706
1707 if (oldw != fe->w || oldpw != fe->pw || oldps != fe->ps ||
1708 oldh != fe->h || oldph != fe->ph || !backing_store_ok(fe)) {
1709 if (backing_store_ok(fe))
1710 teardown_backing_store(fe);
1711 setup_backing_store(fe);
1712 }
1713
1714 midend_force_redraw(fe->me);
1715}
1716
1717static gint configure_area(GtkWidget *widget,
1718 GdkEventConfigure *event, gpointer data)
1719{
1720 frontend *fe = (frontend *)data;
1721
1722 resize_puzzle_to_area(fe, event->width, event->height);
1723#if GTK_CHECK_VERSION(3,0,0)
1724 fe->awaiting_resize_ack = false;
1725#endif
1726 return true;
1727}
1728
1729#if GTK_CHECK_VERSION(3,0,0)
1730static void window_size_alloc(GtkWidget *widget, GtkAllocation *allocation,
1731 gpointer data)
1732{
1733 frontend *fe = (frontend *)data;
1734 if (fe->awaiting_resize_ack) {
1735 GtkAllocation a;
1736 gtk_widget_get_allocation(fe->area, &a);
1737 resize_puzzle_to_area(fe, a.width, a.height);
1738 fe->awaiting_resize_ack = false;
1739 }
1740}
1741#endif
1742
1743static gint timer_func(gpointer data)
1744{
1745 frontend *fe = (frontend *)data;
1746
1747 if (fe->timer_active) {
1748 struct timeval now;
1749 float elapsed;
1750 gettimeofday(&now, NULL);
1751 elapsed = ((now.tv_usec - fe->last_time.tv_usec) * 0.000001F +
1752 (now.tv_sec - fe->last_time.tv_sec));
1753 midend_timer(fe->me, elapsed); /* may clear timer_active */
1754 fe->last_time = now;
1755 }
1756
1757 return fe->timer_active;
1758}
1759
1760void deactivate_timer(frontend *fe)
1761{
1762 if (!fe)
1763 return; /* can happen due to --generate */
1764 if (fe->timer_active)
1765 g_source_remove(fe->timer_id);
1766 fe->timer_active = false;
1767}
1768
1769void activate_timer(frontend *fe)
1770{
1771 if (!fe)
1772 return; /* can happen due to --generate */
1773 if (!fe->timer_active) {
1774 fe->timer_id = g_timeout_add(20, timer_func, fe);
1775 gettimeofday(&fe->last_time, NULL);
1776 }
1777 fe->timer_active = true;
1778}
1779
1780static void window_destroy(GtkWidget *widget, gpointer data)
1781{
1782 gtk_main_quit();
1783}
1784
1785static gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
1786{
1787 GObject *cancelbutton = G_OBJECT(data);
1788
1789 /*
1790 * `Escape' effectively clicks the cancel button
1791 */
1792 if (event->keyval == GDK_KEY_Escape) {
1793 g_signal_emit_by_name(cancelbutton, "clicked");
1794 return true;
1795 }
1796
1797 return false;
1798}
1799
1800enum { MB_OK, MB_YESNO };
1801
1802static void align_label(GtkLabel *label, double x, double y)
1803{
1804#if GTK_CHECK_VERSION(3,16,0)
1805 gtk_label_set_xalign(label, x);
1806 gtk_label_set_yalign(label, y);
1807#elif GTK_CHECK_VERSION(3,14,0)
1808 gtk_widget_set_halign(GTK_WIDGET(label),
1809 x == 0 ? GTK_ALIGN_START :
1810 x == 1 ? GTK_ALIGN_END : GTK_ALIGN_CENTER);
1811 gtk_widget_set_valign(GTK_WIDGET(label),
1812 y == 0 ? GTK_ALIGN_START :
1813 y == 1 ? GTK_ALIGN_END : GTK_ALIGN_CENTER);
1814#else
1815 gtk_misc_set_alignment(GTK_MISC(label), x, y);
1816#endif
1817}
1818
1819#if GTK_CHECK_VERSION(3,0,0)
1820static bool message_box(GtkWidget *parent, const char *title, const char *msg,
1821 bool centre, int type)
1822{
1823 GtkWidget *window;
1824 gint ret;
1825
1826 window = gtk_message_dialog_new
1827 (GTK_WINDOW(parent),
1828 (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1829 (type == MB_OK ? GTK_MESSAGE_INFO : GTK_MESSAGE_QUESTION),
1830 (type == MB_OK ? GTK_BUTTONS_OK : GTK_BUTTONS_YES_NO),
1831 "%s", msg);
1832 gtk_window_set_title(GTK_WINDOW(window), title);
1833 ret = gtk_dialog_run(GTK_DIALOG(window));
1834 gtk_widget_destroy(window);
1835 return (type == MB_OK ? true : (ret == GTK_RESPONSE_YES));
1836}
1837#else /* GTK_CHECK_VERSION(3,0,0) */
1838static void msgbox_button_clicked(GtkButton *button, gpointer data)
1839{
1840 GtkWidget *window = GTK_WIDGET(data);
1841 int v, *ip;
1842
1843 ip = (int *)g_object_get_data(G_OBJECT(window), "user-data");
1844 v = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "user-data"));
1845 *ip = v;
1846
1847 gtk_widget_destroy(GTK_WIDGET(data));
1848}
1849
1850bool message_box(GtkWidget *parent, const char *title, const char *msg,
1851 bool centre, int type)
1852{
1853 GtkWidget *window, *hbox, *text, *button;
1854 const char *titles;
1855 int i, def, cancel;
1856
1857 window = gtk_dialog_new();
1858 text = gtk_label_new(msg);
1859 align_label(GTK_LABEL(text), 0.0, 0.0);
1860 hbox = gtk_hbox_new(false, 0);
1861 gtk_box_pack_start(GTK_BOX(hbox), text, false, false, 20);
1862 gtk_box_pack_start
1863 (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(window))),
1864 hbox, false, false, 20);
1865 gtk_widget_show(text);
1866 gtk_widget_show(hbox);
1867 gtk_window_set_title(GTK_WINDOW(window), title);
1868 gtk_label_set_line_wrap(GTK_LABEL(text), true);
1869
1870 if (type == MB_OK) {
1871 titles = LABEL_OK "\0";
1872 def = cancel = 0;
1873 } else {
1874 assert(type == MB_YESNO);
1875 titles = LABEL_NO "\0" LABEL_YES "\0";
1876 def = 1;
1877 cancel = 0;
1878 }
1879 i = 0;
1880
1881 while (*titles) {
1882 button = gtk_button_new_with_our_label(titles);
1883 gtk_box_pack_end
1884 (GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(window))),
1885 button, false, false, 0);
1886 gtk_widget_show(button);
1887 if (i == def) {
1888 gtk_widget_set_can_default(button, true);
1889 gtk_window_set_default(GTK_WINDOW(window), button);
1890 }
1891 if (i == cancel) {
1892 g_signal_connect(G_OBJECT(window), "key_press_event",
1893 G_CALLBACK(win_key_press), button);
1894 }
1895 g_signal_connect(G_OBJECT(button), "clicked",
1896 G_CALLBACK(msgbox_button_clicked), window);
1897 g_object_set_data(G_OBJECT(button), "user-data",
1898 GINT_TO_POINTER(i));
1899 titles += strlen(titles)+1;
1900 i++;
1901 }
1902 g_object_set_data(G_OBJECT(window), "user-data", &i);
1903 g_signal_connect(G_OBJECT(window), "destroy",
1904 G_CALLBACK(window_destroy), NULL);
1905 gtk_window_set_modal(GTK_WINDOW(window), true);
1906 gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(parent));
1907 /* set_transient_window_pos(parent, window); */
1908 gtk_widget_show(window);
1909 i = -1;
1910 gtk_main();
1911 return (type == MB_YESNO ? i == 1 : true);
1912}
1913#endif /* GTK_CHECK_VERSION(3,0,0) */
1914
1915static void error_box(GtkWidget *parent, const char *msg)
1916{
1917 message_box(parent, "Error", msg, false, MB_OK);
1918}
1919
1920static void config_ok_button_clicked(GtkButton *button, gpointer data)
1921{
1922 frontend *fe = (frontend *)data;
1923 const char *err;
1924
1925 err = midend_set_config(fe->me, fe->cfg_which, fe->cfg);
1926
1927 if (err)
1928 error_box(fe->cfgbox, err);
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 }
1937 fe->cfgret = true;
1938 gtk_widget_destroy(fe->cfgbox);
1939 if (fe->cfg_which != CFG_PREFS)
1940 changed_preset(fe);
1941 }
1942}
1943
1944static void config_cancel_button_clicked(GtkButton *button, gpointer data)
1945{
1946 frontend *fe = (frontend *)data;
1947
1948 gtk_widget_destroy(fe->cfgbox);
1949}
1950
1951static gint editbox_key(GtkWidget *widget, GdkEventKey *event, gpointer data)
1952{
1953 /*
1954 * GtkEntry has a nasty habit of eating the Return key, which
1955 * is unhelpful since it doesn't actually _do_ anything with it
1956 * (it calls gtk_widget_activate, but our edit boxes never need
1957 * activating). So I catch Return before GtkEntry sees it, and
1958 * pass it straight on to the parent widget. Effect: hitting
1959 * Return in an edit box will now activate the default button
1960 * in the dialog just like it will everywhere else.
1961 */
1962 if (event->keyval == GDK_KEY_Return &&
1963 gtk_widget_get_parent(widget) != NULL) {
1964 gint return_val;
1965 g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
1966 g_signal_emit_by_name(G_OBJECT(gtk_widget_get_parent(widget)),
1967 "key_press_event", event, &return_val);
1968 return return_val;
1969 }
1970 return false;
1971}
1972
1973static void editbox_changed(GtkEditable *ed, gpointer data)
1974{
1975 config_item *i = (config_item *)data;
1976
1977 assert(i->type == C_STRING);
1978 sfree(i->u.string.sval);
1979 i->u.string.sval = dupstr(gtk_entry_get_text(GTK_ENTRY(ed)));
1980}
1981
1982static void button_toggled(GtkToggleButton *tb, gpointer data)
1983{
1984 config_item *i = (config_item *)data;
1985
1986 assert(i->type == C_BOOLEAN);
1987 i->u.boolean.bval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tb));
1988}
1989
1990static void droplist_sel(GtkComboBox *combo, gpointer data)
1991{
1992 config_item *i = (config_item *)data;
1993
1994 assert(i->type == C_CHOICES);
1995 i->u.choices.selected = gtk_combo_box_get_active(combo);
1996}
1997
1998static bool get_config(frontend *fe, int which)
1999{
2000 GtkWidget *w, *table, *cancel;
2001 GtkBox *content_box, *button_box;
2002 char *title;
2003 config_item *i;
2004 int y;
2005
2006 fe->cfg = midend_get_config(fe->me, which, &title);
2007 fe->cfg_which = which;
2008 fe->cfgret = false;
2009
2010#if GTK_CHECK_VERSION(3,0,0)
2011 /* GtkDialog isn't quite flexible enough */
2012 fe->cfgbox = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2013 content_box = GTK_BOX(gtk_vbox_new(false, 8));
2014 g_object_set(G_OBJECT(content_box), "margin", 8, (const char *)NULL);
2015 gtk_widget_show(GTK_WIDGET(content_box));
2016 gtk_container_add(GTK_CONTAINER(fe->cfgbox), GTK_WIDGET(content_box));
2017 button_box = GTK_BOX(gtk_hbox_new(false, 8));
2018 gtk_widget_show(GTK_WIDGET(button_box));
2019 gtk_box_pack_end(content_box, GTK_WIDGET(button_box), false, false, 0);
2020 {
2021 GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
2022 gtk_widget_show(sep);
2023 gtk_box_pack_end(content_box, sep, false, false, 0);
2024 }
2025#else
2026 fe->cfgbox = gtk_dialog_new();
2027 content_box = GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(fe->cfgbox)));
2028 button_box = GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(fe->cfgbox)));
2029#endif
2030 gtk_window_set_title(GTK_WINDOW(fe->cfgbox), title);
2031 sfree(title);
2032
2033 w = gtk_button_new_with_our_label(LABEL_CANCEL);
2034 gtk_box_pack_end(button_box, w, false, false, 0);
2035 gtk_widget_show(w);
2036 g_signal_connect(G_OBJECT(w), "clicked",
2037 G_CALLBACK(config_cancel_button_clicked), fe);
2038 cancel = w;
2039
2040 w = gtk_button_new_with_our_label(LABEL_OK);
2041 gtk_box_pack_end(button_box, w, false, false, 0);
2042 gtk_widget_show(w);
2043 gtk_widget_set_can_default(w, true);
2044 gtk_window_set_default(GTK_WINDOW(fe->cfgbox), w);
2045 g_signal_connect(G_OBJECT(w), "clicked",
2046 G_CALLBACK(config_ok_button_clicked), fe);
2047
2048#if GTK_CHECK_VERSION(3,0,0)
2049 table = gtk_grid_new();
2050#else
2051 table = gtk_table_new(1, 2, false);
2052#endif
2053 y = 0;
2054 gtk_box_pack_start(content_box, table, false, false, 0);
2055 gtk_widget_show(table);
2056
2057 for (i = fe->cfg; i->type != C_END; i++) {
2058#if !GTK_CHECK_VERSION(3,0,0)
2059 gtk_table_resize(GTK_TABLE(table), y+1, 2);
2060#endif
2061
2062 switch (i->type) {
2063 case C_STRING:
2064 /*
2065 * Edit box with a label beside it.
2066 */
2067
2068 w = gtk_label_new(i->name);
2069 align_label(GTK_LABEL(w), 0.0, 0.5);
2070#if GTK_CHECK_VERSION(3,0,0)
2071 gtk_grid_attach(GTK_GRID(table), w, 0, y, 1, 1);
2072#else
2073 gtk_table_attach(GTK_TABLE(table), w, 0, 1, y, y+1,
2074 GTK_SHRINK | GTK_FILL,
2075 GTK_EXPAND | GTK_SHRINK | GTK_FILL,
2076 3, 3);
2077#endif
2078 gtk_widget_show(w);
2079
2080 w = gtk_entry_new();
2081#if GTK_CHECK_VERSION(3,0,0)
2082 gtk_grid_attach(GTK_GRID(table), w, 1, y, 1, 1);
2083 g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
2084#else
2085 gtk_table_attach(GTK_TABLE(table), w, 1, 2, y, y+1,
2086 GTK_EXPAND | GTK_SHRINK | GTK_FILL,
2087 GTK_EXPAND | GTK_SHRINK | GTK_FILL,
2088 3, 3);
2089#endif
2090 gtk_entry_set_text(GTK_ENTRY(w), i->u.string.sval);
2091 g_signal_connect(G_OBJECT(w), "changed",
2092 G_CALLBACK(editbox_changed), i);
2093 g_signal_connect(G_OBJECT(w), "key_press_event",
2094 G_CALLBACK(editbox_key), NULL);
2095 gtk_widget_show(w);
2096
2097 break;
2098
2099 case C_BOOLEAN:
2100 /*
2101 * Simple checkbox.
2102 */
2103 w = gtk_check_button_new_with_label(i->name);
2104 g_signal_connect(G_OBJECT(w), "toggled",
2105 G_CALLBACK(button_toggled), i);
2106#if GTK_CHECK_VERSION(3,0,0)
2107 gtk_grid_attach(GTK_GRID(table), w, 0, y, 2, 1);
2108 g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
2109#else
2110 gtk_table_attach(GTK_TABLE(table), w, 0, 2, y, y+1,
2111 GTK_EXPAND | GTK_SHRINK | GTK_FILL,
2112 GTK_EXPAND | GTK_SHRINK | GTK_FILL,
2113 3, 3);
2114#endif
2115 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
2116 i->u.boolean.bval);
2117 gtk_widget_show(w);
2118 break;
2119
2120 case C_CHOICES:
2121 /*
2122 * Drop-down list (GtkComboBox).
2123 */
2124
2125 w = gtk_label_new(i->name);
2126 align_label(GTK_LABEL(w), 0.0, 0.5);
2127#if GTK_CHECK_VERSION(3,0,0)
2128 gtk_grid_attach(GTK_GRID(table), w, 0, y, 1, 1);
2129#else
2130 gtk_table_attach(GTK_TABLE(table), w, 0, 1, y, y+1,
2131 GTK_SHRINK | GTK_FILL,
2132 GTK_EXPAND | GTK_SHRINK | GTK_FILL ,
2133 3, 3);
2134#endif
2135 gtk_widget_show(w);
2136
2137 {
2138 int c;
2139 const char *p, *q;
2140 char *name;
2141 GtkListStore *model;
2142 GtkCellRenderer *cr;
2143 GtkTreeIter iter;
2144
2145 model = gtk_list_store_new(1, G_TYPE_STRING);
2146
2147 c = *i->u.choices.choicenames;
2148 p = i->u.choices.choicenames+1;
2149
2150 while (*p) {
2151 q = p;
2152 while (*q && *q != c)
2153 q++;
2154
2155 name = snewn(q-p+1, char);
2156 strncpy(name, p, q-p);
2157 name[q-p] = '\0';
2158
2159 if (*q) q++; /* eat delimiter */
2160
2161 gtk_list_store_append(model, &iter);
2162 gtk_list_store_set(model, &iter, 0, name, -1);
2163
2164 p = q;
2165 }
2166
2167 w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
2168
2169 gtk_combo_box_set_active(GTK_COMBO_BOX(w),
2170 i->u.choices.selected);
2171
2172 cr = gtk_cell_renderer_text_new();
2173 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, true);
2174 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr,
2175 "text", 0, NULL);
2176
2177 g_signal_connect(G_OBJECT(w), "changed",
2178 G_CALLBACK(droplist_sel), i);
2179 }
2180
2181#if GTK_CHECK_VERSION(3,0,0)
2182 gtk_grid_attach(GTK_GRID(table), w, 1, y, 1, 1);
2183 g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
2184#else
2185 gtk_table_attach(GTK_TABLE(table), w, 1, 2, y, y+1,
2186 GTK_EXPAND | GTK_SHRINK | GTK_FILL,
2187 GTK_EXPAND | GTK_SHRINK | GTK_FILL,
2188 3, 3);
2189#endif
2190 gtk_widget_show(w);
2191 break;
2192 }
2193
2194 y++;
2195 }
2196
2197 g_signal_connect(G_OBJECT(fe->cfgbox), "destroy",
2198 G_CALLBACK(window_destroy), NULL);
2199 g_signal_connect(G_OBJECT(fe->cfgbox), "key_press_event",
2200 G_CALLBACK(win_key_press), cancel);
2201 gtk_window_set_modal(GTK_WINDOW(fe->cfgbox), true);
2202 gtk_window_set_transient_for(GTK_WINDOW(fe->cfgbox),
2203 GTK_WINDOW(fe->window));
2204 /* set_transient_window_pos(fe->window, fe->cfgbox); */
2205 gtk_widget_show(fe->cfgbox);
2206 gtk_main();
2207
2208 free_cfg(fe->cfg);
2209
2210 return fe->cfgret;
2211}
2212
2213static void menu_key_event(GtkMenuItem *menuitem, gpointer data)
2214{
2215 frontend *fe = (frontend *)data;
2216 int key = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem),
2217 "user-data"));
2218 if (midend_process_key(fe->me, 0, 0, key) == PKR_QUIT)
2219 gtk_widget_destroy(fe->window);
2220}
2221
2222static void get_size(frontend *fe, int *px, int *py)
2223{
2224 int x, y;
2225
2226 /*
2227 * Currently I don't want to make the GTK port scale large
2228 * puzzles to fit on the screen. This is because X does permit
2229 * extremely large windows and many window managers provide a
2230 * means of navigating round them, and the users I consulted
2231 * before deciding said that they'd rather have enormous puzzle
2232 * windows spanning multiple screen pages than have them
2233 * shrunk. I could change my mind later or introduce
2234 * configurability; this would be the place to do so, by
2235 * replacing the initial values of x and y with the screen
2236 * dimensions.
2237 */
2238 x = INT_MAX;
2239 y = INT_MAX;
2240 midend_size(fe->me, &x, &y, false, 1.0);
2241 *px = x;
2242 *py = y;
2243}
2244
2245#if !GTK_CHECK_VERSION(2,0,0)
2246#define gtk_window_resize(win, x, y) \
2247 gdk_window_resize(GTK_WIDGET(win)->window, x, y)
2248#endif
2249
2250/*
2251 * Called when any other code in this file has changed the
2252 * selected game parameters.
2253 */
2254static void changed_preset(frontend *fe)
2255{
2256 int n = midend_which_preset(fe->me);
2257
2258 fe->preset_threaded = true;
2259 if (n < 0 && fe->preset_custom) {
2260 gtk_check_menu_item_set_active(
2261 GTK_CHECK_MENU_ITEM(fe->preset_custom),
2262 true);
2263 } else {
2264 GSList *gs = fe->preset_radio;
2265 GSList *found = NULL;
2266
2267 for (gs = fe->preset_radio; gs; gs = gs->next) {
2268 struct preset_menu_entry *entry =
2269 (struct preset_menu_entry *)g_object_get_data(
2270 G_OBJECT(gs->data), "user-data");
2271 if (!entry || entry->id != n)
2272 gtk_check_menu_item_set_active(
2273 GTK_CHECK_MENU_ITEM(gs->data), false);
2274 else
2275 found = gs;
2276 }
2277 if (found)
2278 gtk_check_menu_item_set_active(
2279 GTK_CHECK_MENU_ITEM(found->data), true);
2280 }
2281 fe->preset_threaded = false;
2282
2283 /*
2284 * Update the greying on the Copy menu option.
2285 */
2286 if (fe->copy_menu_item) {
2287 bool enabled = midend_can_format_as_text_now(fe->me);
2288 gtk_widget_set_sensitive(fe->copy_menu_item, enabled);
2289 }
2290}
2291
2292#if !GTK_CHECK_VERSION(3,0,0)
2293static bool not_size_allocated_yet(GtkWidget *w)
2294{
2295 /*
2296 * This function tests whether a widget has not yet taken up space
2297 * on the screen which it will occupy in future. (Therefore, it
2298 * returns true only if the widget does exist but does not have a
2299 * size allocation. A null widget is already taking up all the
2300 * space it ever will.)
2301 */
2302 if (!w)
2303 return false; /* nonexistent widgets aren't a problem */
2304
2305#if GTK_CHECK_VERSION(2,18,0) /* skip if no gtk_widget_get_allocation */
2306 {
2307 GtkAllocation a;
2308 gtk_widget_get_allocation(w, &a);
2309 if (a.height == 0 || a.width == 0)
2310 return true; /* widget exists but has no size yet */
2311 }
2312#endif
2313
2314 return false;
2315}
2316
2317static void try_shrink_drawing_area(frontend *fe)
2318{
2319 if (fe->drawing_area_shrink_pending &&
2320 (!fe->menubar_is_local || !not_size_allocated_yet(fe->menubar)) &&
2321 !not_size_allocated_yet(fe->statusbar)) {
2322 /*
2323 * In order to permit the user to resize the window smaller as
2324 * well as bigger, we call this function after the window size
2325 * has ended up where we want it. This shouldn't shrink the
2326 * window immediately; it just arranges that the next time the
2327 * user tries to shrink it, they can.
2328 *
2329 * However, at puzzle creation time, we defer the first of
2330 * these operations until after the menu bar and status bar
2331 * are actually visible. On Ubuntu 12.04 I've found that these
2332 * can take a while to be displayed, and that it's a mistake
2333 * to reduce the drawing area's size allocation before they've
2334 * turned up or else the drawing area makes room for them by
2335 * shrinking to less than the size we intended.
2336 */
2337 gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), 1, 1);
2338 fe->drawing_area_shrink_pending = false;
2339 }
2340}
2341#endif /* !GTK_CHECK_VERSION(3,0,0) */
2342
2343static gint configure_window(GtkWidget *widget,
2344 GdkEventConfigure *event, gpointer data)
2345{
2346#if !GTK_CHECK_VERSION(3,0,0)
2347 /*
2348 * When the main puzzle window changes size, it might be because
2349 * the menu bar or status bar has turned up after starting off
2350 * absent, in which case we should have another go at enacting a
2351 * pending shrink of the drawing area.
2352 */
2353 frontend *fe = (frontend *)data;
2354 try_shrink_drawing_area(fe);
2355#endif
2356 return false;
2357}
2358
2359#if GTK_CHECK_VERSION(3,0,0)
2360static int window_extra_height(frontend *fe)
2361{
2362 int ret = 0;
2363 if (fe->menubar) {
2364 GtkRequisition req;
2365 gtk_widget_get_preferred_size(fe->menubar, &req, NULL);
2366 ret += req.height;
2367 }
2368 if (fe->statusbar) {
2369 GtkRequisition req;
2370 gtk_widget_get_preferred_size(fe->statusbar, &req, NULL);
2371 ret += req.height;
2372 }
2373 return ret;
2374}
2375#endif
2376
2377static void resize_fe(frontend *fe)
2378{
2379 int x, y;
2380
2381 get_size(fe, &x, &y);
2382
2383#if GTK_CHECK_VERSION(3,0,0)
2384 gtk_window_resize(GTK_WINDOW(fe->window), x, y + window_extra_height(fe));
2385 fe->awaiting_resize_ack = true;
2386#else
2387 fe->drawing_area_shrink_pending = false;
2388 gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
2389 {
2390 GtkRequisition req;
2391 gtk_widget_size_request(GTK_WIDGET(fe->window), &req);
2392 gtk_window_resize(GTK_WINDOW(fe->window), req.width, req.height);
2393 }
2394 fe->drawing_area_shrink_pending = true;
2395 try_shrink_drawing_area(fe);
2396#endif
2397}
2398
2399static void menu_preset_event(GtkMenuItem *menuitem, gpointer data)
2400{
2401 frontend *fe = (frontend *)data;
2402 struct preset_menu_entry *entry =
2403 (struct preset_menu_entry *)g_object_get_data(
2404 G_OBJECT(menuitem), "user-data");
2405
2406 if (fe->preset_threaded ||
2407 (GTK_IS_CHECK_MENU_ITEM(menuitem) &&
2408 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))))
2409 return;
2410 midend_set_params(fe->me, entry->params);
2411 midend_new_game(fe->me);
2412 changed_preset(fe);
2413 resize_fe(fe);
2414 midend_redraw(fe->me);
2415}
2416
2417static GdkAtom compound_text_atom, utf8_string_atom;
2418static bool paste_initialised = false;
2419
2420static void set_selection(frontend *fe, GdkAtom selection)
2421{
2422 if (!paste_initialised) {
2423 compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", false);
2424 utf8_string_atom = gdk_atom_intern("UTF8_STRING", false);
2425 paste_initialised = true;
2426 }
2427
2428 /*
2429 * For this simple application we can safely assume that the
2430 * data passed to this function is pure ASCII, which means we
2431 * can return precisely the same stuff for types STRING,
2432 * COMPOUND_TEXT or UTF8_STRING.
2433 */
2434
2435 if (gtk_selection_owner_set(fe->window, selection, CurrentTime)) {
2436 gtk_selection_clear_targets(fe->window, selection);
2437 gtk_selection_add_target(fe->window, selection,
2438 GDK_SELECTION_TYPE_STRING, 1);
2439 gtk_selection_add_target(fe->window, selection, compound_text_atom, 1);
2440 gtk_selection_add_target(fe->window, selection, utf8_string_atom, 1);
2441 }
2442}
2443
2444static void write_clip(frontend *fe, char *data)
2445{
2446 if (fe->paste_data)
2447 sfree(fe->paste_data);
2448
2449 fe->paste_data = data;
2450 fe->paste_data_len = strlen(data);
2451
2452 set_selection(fe, GDK_SELECTION_PRIMARY);
2453 set_selection(fe, GDK_SELECTION_CLIPBOARD);
2454}
2455
2456static void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
2457 guint info, guint time_stamp, gpointer data)
2458{
2459 frontend *fe = (frontend *)data;
2460 gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8,
2461 fe->paste_data, fe->paste_data_len);
2462}
2463
2464static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
2465 gpointer data)
2466{
2467 frontend *fe = (frontend *)data;
2468
2469 if (fe->paste_data)
2470 sfree(fe->paste_data);
2471 fe->paste_data = NULL;
2472 fe->paste_data_len = 0;
2473 return true;
2474}
2475
2476static void menu_copy_event(GtkMenuItem *menuitem, gpointer data)
2477{
2478 frontend *fe = (frontend *)data;
2479 char *text;
2480
2481 text = midend_text_format(fe->me);
2482
2483 if (text) {
2484 write_clip(fe, text);
2485 } else {
2486 gdk_display_beep(gdk_display_get_default());
2487 }
2488}
2489
2490#ifdef OLD_FILESEL
2491
2492static void filesel_ok(GtkButton *button, gpointer data)
2493{
2494 frontend *fe = (frontend *)data;
2495
2496 gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data");
2497
2498 const char *name =
2499 gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel));
2500
2501 fe->filesel_name = dupstr(name);
2502}
2503
2504static char *file_selector(frontend *fe, const char *title, int save)
2505{
2506 GtkWidget *filesel =
2507 gtk_file_selection_new(title);
2508
2509 fe->filesel_name = NULL;
2510
2511 gtk_window_set_modal(GTK_WINDOW(filesel), true);
2512 g_object_set_data
2513 (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
2514 (gpointer)filesel);
2515 g_signal_connect
2516 (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
2517 G_CALLBACK(filesel_ok), fe);
2518 g_signal_connect_swapped
2519 (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
2520 G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
2521 g_signal_connect_object
2522 (G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
2523 G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
2524 g_signal_connect(G_OBJECT(filesel), "destroy",
2525 G_CALLBACK(window_destroy), NULL);
2526 gtk_widget_show(filesel);
2527 gtk_window_set_transient_for(GTK_WINDOW(filesel), GTK_WINDOW(fe->window));
2528 gtk_main();
2529
2530 return fe->filesel_name;
2531}
2532
2533#else
2534
2535static char *file_selector(frontend *fe, const char *title, bool save)
2536{
2537 char *filesel_name = NULL;
2538
2539 GtkWidget *filesel =
2540 gtk_file_chooser_dialog_new(title,
2541 GTK_WINDOW(fe->window),
2542 save ? GTK_FILE_CHOOSER_ACTION_SAVE :
2543 GTK_FILE_CHOOSER_ACTION_OPEN,
2544 LABEL_CANCEL, GTK_RESPONSE_CANCEL,
2545 save ? LABEL_SAVE : LABEL_OPEN,
2546 GTK_RESPONSE_ACCEPT,
2547 NULL);
2548
2549 if (gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) {
2550 char *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel));
2551 filesel_name = dupstr(name);
2552 g_free(name);
2553 }
2554
2555 gtk_widget_destroy(filesel);
2556
2557 return filesel_name;
2558}
2559
2560#endif
2561
2562#ifdef USE_PRINTING
2563static GObject *create_print_widget(GtkPrintOperation *print, gpointer data)
2564{
2565 GtkLabel *count_label, *width_label, *height_label,
2566 *scale_llabel, *scale_rlabel;
2567 GtkBox *scale_hbox;
2568 GtkWidget *grid;
2569 frontend *fe = (frontend *)data;
2570
2571 fe->printcount_spin_button =
2572 GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 999, 1));
2573 gtk_spin_button_set_numeric(fe->printcount_spin_button, true);
2574 gtk_spin_button_set_snap_to_ticks(fe->printcount_spin_button, true);
2575 fe->printw_spin_button =
2576 GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 99, 1));
2577 gtk_spin_button_set_numeric(fe->printw_spin_button, true);
2578 gtk_spin_button_set_snap_to_ticks(fe->printw_spin_button, true);
2579 fe->printh_spin_button =
2580 GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 99, 1));
2581 gtk_spin_button_set_numeric(fe->printh_spin_button, true);
2582 gtk_spin_button_set_snap_to_ticks(fe->printh_spin_button, true);
2583 fe->printscale_spin_button =
2584 GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 1000, 1));
2585 gtk_spin_button_set_digits(fe->printscale_spin_button, 1);
2586 gtk_spin_button_set_numeric(fe->printscale_spin_button, true);
2587 if (thegame.can_solve) {
2588 fe->soln_check_button =
2589 GTK_CHECK_BUTTON(
2590 gtk_check_button_new_with_label("Print solutions"));
2591 }
2592 if (thegame.can_print_in_colour) {
2593 fe->colour_check_button =
2594 GTK_CHECK_BUTTON(
2595 gtk_check_button_new_with_label("Print in color"));
2596 }
2597
2598 /* Set defaults to what was selected last time. */
2599 gtk_spin_button_set_value(fe->printcount_spin_button,
2600 (gdouble)fe->printcount);
2601 gtk_spin_button_set_value(fe->printw_spin_button,
2602 (gdouble)fe->printw);
2603 gtk_spin_button_set_value(fe->printh_spin_button,
2604 (gdouble)fe->printh);
2605 gtk_spin_button_set_value(fe->printscale_spin_button,
2606 (gdouble)fe->printscale);
2607 if (thegame.can_solve) {
2608 gtk_toggle_button_set_active(
2609 GTK_TOGGLE_BUTTON(fe->soln_check_button), fe->printsolns);
2610 }
2611 if (thegame.can_print_in_colour) {
2612 gtk_toggle_button_set_active(
2613 GTK_TOGGLE_BUTTON(fe->colour_check_button), fe->printcolour);
2614 }
2615
2616 count_label = GTK_LABEL(gtk_label_new("Puzzles to print:"));
2617 width_label = GTK_LABEL(gtk_label_new("Puzzles across:"));
2618 height_label = GTK_LABEL(gtk_label_new("Puzzles down:"));
2619 scale_llabel = GTK_LABEL(gtk_label_new("Puzzle scale:"));
2620 scale_rlabel = GTK_LABEL(gtk_label_new("%"));
2621#if GTK_CHECK_VERSION(3,0,0)
2622 gtk_widget_set_halign(GTK_WIDGET(count_label), GTK_ALIGN_START);
2623 gtk_widget_set_halign(GTK_WIDGET(width_label), GTK_ALIGN_START);
2624 gtk_widget_set_halign(GTK_WIDGET(height_label), GTK_ALIGN_START);
2625 gtk_widget_set_halign(GTK_WIDGET(scale_llabel), GTK_ALIGN_START);
2626#else
2627 gtk_misc_set_alignment(GTK_MISC(count_label), 0, 0);
2628 gtk_misc_set_alignment(GTK_MISC(width_label), 0, 0);
2629 gtk_misc_set_alignment(GTK_MISC(height_label), 0, 0);
2630 gtk_misc_set_alignment(GTK_MISC(scale_llabel), 0, 0);
2631#endif
2632
2633 scale_hbox = GTK_BOX(gtk_hbox_new(false, 6));
2634 gtk_box_pack_start(scale_hbox, GTK_WIDGET(fe->printscale_spin_button),
2635 false, false, 0);
2636 gtk_box_pack_start(scale_hbox, GTK_WIDGET(scale_rlabel),
2637 false, false, 0);
2638
2639#if GTK_CHECK_VERSION(3,0,0)
2640 grid = gtk_grid_new();
2641 gtk_grid_set_column_spacing(GTK_GRID(grid), 18);
2642 gtk_grid_set_row_spacing(GTK_GRID(grid), 18);
2643 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(count_label), 0, 0, 1, 1);
2644 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(width_label), 0, 1, 1, 1);
2645 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(height_label), 0, 2, 1, 1);
2646 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(scale_llabel), 0, 3, 1, 1);
2647 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printcount_spin_button),
2648 1, 0, 1, 1);
2649 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printw_spin_button),
2650 1, 1, 1, 1);
2651 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printh_spin_button),
2652 1, 2, 1, 1);
2653 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(scale_hbox), 1, 3, 1, 1);
2654 if (thegame.can_solve) {
2655 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->soln_check_button),
2656 0, 4, 1, 1);
2657 }
2658 if (thegame.can_print_in_colour) {
2659 gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->colour_check_button),
2660 thegame.can_solve, 4, 1, 1);
2661 }
2662#else
2663 grid = gtk_table_new((thegame.can_solve || thegame.can_print_in_colour) ?
2664 5 : 4, 2, false);
2665 gtk_table_set_col_spacings(GTK_TABLE(grid), 18);
2666 gtk_table_set_row_spacings(GTK_TABLE(grid), 18);
2667 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(count_label), 0, 1, 0, 1,
2668 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2669 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(width_label), 0, 1, 1, 2,
2670 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2671 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(height_label), 0, 1, 2, 3,
2672 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2673 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(scale_llabel), 0, 1, 3, 4,
2674 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2675 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printcount_spin_button),
2676 1, 2, 0, 1,
2677 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2678 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printw_spin_button),
2679 1, 2, 1, 2,
2680 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2681 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printh_spin_button),
2682 1, 2, 2, 3,
2683 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2684 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(scale_hbox), 1, 2, 3, 4,
2685 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2686 if (thegame.can_solve) {
2687 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->soln_check_button),
2688 0, 1, 4, 5,
2689 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2690 }
2691 if (thegame.can_print_in_colour) {
2692 gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->colour_check_button),
2693 thegame.can_solve, thegame.can_solve + 1, 4, 5,
2694 GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
2695 }
2696#endif
2697 gtk_container_set_border_width(GTK_CONTAINER(grid), 12);
2698
2699 gtk_widget_show_all(grid);
2700
2701 return G_OBJECT(grid);
2702}
2703
2704static void apply_print_widget(GtkPrintOperation *print,
2705 GtkWidget *widget, gpointer data)
2706{
2707 frontend *fe = (frontend *)data;
2708
2709 /* We ignore `widget' because it is easier and faster to store the
2710 widgets we need in `fe' then to get the children of `widget'. */
2711 fe->printcount =
2712 gtk_spin_button_get_value_as_int(fe->printcount_spin_button);
2713 fe->printw = gtk_spin_button_get_value_as_int(fe->printw_spin_button);
2714 fe->printh = gtk_spin_button_get_value_as_int(fe->printh_spin_button);
2715 fe->printscale = gtk_spin_button_get_value(fe->printscale_spin_button);
2716 if (thegame.can_solve) {
2717 fe->printsolns =
2718 gtk_toggle_button_get_active(
2719 GTK_TOGGLE_BUTTON(fe->soln_check_button));
2720 }
2721 if (thegame.can_print_in_colour) {
2722 fe->printcolour =
2723 gtk_toggle_button_get_active(
2724 GTK_TOGGLE_BUTTON(fe->colour_check_button));
2725 }
2726}
2727
2728static void print_begin(GtkPrintOperation *printop,
2729 GtkPrintContext *context, gpointer data)
2730{
2731 frontend *fe = (frontend *)data;
2732 midend *nme = NULL; /* non-interactive midend for bulk puzzle generation */
2733 int i;
2734
2735 fe->printcontext = context;
2736 fe->cr = gtk_print_context_get_cairo_context(context);
2737
2738 /*
2739 * Create our document structure and fill it up with puzzles.
2740 */
2741 fe->doc = document_new(fe->printw, fe->printh, fe->printscale / 100.0F);
2742
2743 for (i = 0; i < fe->printcount; i++) {
2744 const char *err;
2745
2746 if (i == 0) {
2747 err = midend_print_puzzle(fe->me, fe->doc, fe->printsolns);
2748 } else {
2749 if (!nme) {
2750 game_params *params;
2751
2752 nme = midend_new(NULL, &thegame, NULL, NULL);
2753
2754 /*
2755 * Set the non-interactive mid-end to have the same
2756 * parameters as the standard one.
2757 */
2758 params = midend_get_params(fe->me);
2759 midend_set_params(nme, params);
2760 thegame.free_params(params);
2761 }
2762
2763 load_prefs(fe);
2764
2765 midend_new_game(nme);
2766 err = midend_print_puzzle(nme, fe->doc, fe->printsolns);
2767 }
2768
2769 if (err) {
2770 error_box(fe->window, err);
2771 return;
2772 }
2773 }
2774
2775 if (nme)
2776 midend_free(nme);
2777
2778 /* Begin the document. */
2779 document_begin(fe->doc, fe->print_dr);
2780}
2781
2782static void draw_page(GtkPrintOperation *printop,
2783 GtkPrintContext *context,
2784 gint page_nr, gpointer data)
2785{
2786 frontend *fe = (frontend *)data;
2787 document_print_page(fe->doc, fe->print_dr, page_nr);
2788}
2789
2790static void print_end(GtkPrintOperation *printop,
2791 GtkPrintContext *context, gpointer data)
2792{
2793 frontend *fe = (frontend *)data;
2794
2795 /* End and free the document. */
2796 document_end(fe->doc, fe->print_dr);
2797 document_free(fe->doc);
2798 fe->doc = NULL;
2799}
2800
2801static void print_dialog(frontend *fe)
2802{
2803 GError *error;
2804 static GtkPrintSettings *settings = NULL;
2805 static GtkPageSetup *page_setup = NULL;
2806#ifndef USE_EMBED_PAGE_SETUP
2807 GtkPageSetup *new_page_setup;
2808#endif
2809
2810 fe->printop = gtk_print_operation_new();
2811 gtk_print_operation_set_use_full_page(fe->printop, true);
2812 gtk_print_operation_set_custom_tab_label(fe->printop, "Puzzle Settings");
2813 g_signal_connect(fe->printop, "create-custom-widget",
2814 G_CALLBACK(create_print_widget), fe);
2815 g_signal_connect(fe->printop, "custom-widget-apply",
2816 G_CALLBACK(apply_print_widget), fe);
2817 g_signal_connect(fe->printop, "begin-print", G_CALLBACK(print_begin), fe);
2818 g_signal_connect(fe->printop, "draw-page", G_CALLBACK(draw_page), fe);
2819 g_signal_connect(fe->printop, "end-print", G_CALLBACK(print_end), fe);
2820#ifdef USE_EMBED_PAGE_SETUP
2821 gtk_print_operation_set_embed_page_setup(fe->printop, true);
2822#else
2823 if (page_setup == NULL) {
2824 page_setup =
2825 g_object_ref(
2826 gtk_print_operation_get_default_page_setup(fe->printop));
2827 }
2828 if (settings == NULL) {
2829 settings =
2830 g_object_ref(gtk_print_operation_get_print_settings(fe->printop));
2831 }
2832 new_page_setup = gtk_print_run_page_setup_dialog(GTK_WINDOW(fe->window),
2833 page_setup, settings);
2834 g_object_unref(page_setup);
2835 page_setup = new_page_setup;
2836 gtk_print_operation_set_default_page_setup(fe->printop, page_setup);
2837#endif
2838
2839 if (settings != NULL)
2840 gtk_print_operation_set_print_settings(fe->printop, settings);
2841 if (page_setup != NULL)
2842 gtk_print_operation_set_default_page_setup(fe->printop, page_setup);
2843
2844 switch (gtk_print_operation_run(fe->printop,
2845 GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
2846 GTK_WINDOW(fe->window), &error)) {
2847 case GTK_PRINT_OPERATION_RESULT_ERROR:
2848 error_box(fe->window, error->message);
2849 g_error_free(error);
2850 break;
2851 case GTK_PRINT_OPERATION_RESULT_APPLY:
2852 if (settings != NULL)
2853 g_object_unref(settings);
2854 settings =
2855 g_object_ref(gtk_print_operation_get_print_settings(fe->printop));
2856#ifdef USE_EMBED_PAGE_SETUP
2857 if (page_setup != NULL)
2858 g_object_unref(page_setup);
2859 page_setup =
2860 g_object_ref(
2861 gtk_print_operation_get_default_page_setup(fe->printop));
2862#endif
2863 break;
2864 default:
2865 /* Don't error out on -Werror=switch. */
2866 break;
2867 }
2868
2869 g_object_unref(fe->printop);
2870 fe->printop = NULL;
2871 fe->printcontext = NULL;
2872}
2873#endif /* USE_PRINTING */
2874
2875struct savefile_write_ctx {
2876 FILE *fp;
2877 int error;
2878};
2879
2880static void savefile_write(void *wctx, const void *buf, int len)
2881{
2882 struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)wctx;
2883 if (fwrite(buf, 1, len, ctx->fp) < len)
2884 ctx->error = errno;
2885}
2886
2887static bool savefile_read(void *wctx, void *buf, int len)
2888{
2889 FILE *fp = (FILE *)wctx;
2890 int ret;
2891
2892 ret = fread(buf, 1, len, fp);
2893 return (ret == len);
2894}
2895
2896static void menu_save_event(GtkMenuItem *menuitem, gpointer data)
2897{
2898 frontend *fe = (frontend *)data;
2899 char *name;
2900
2901 name = file_selector(fe, "Enter name of game file to save", true);
2902
2903 if (name) {
2904 FILE *fp;
2905
2906 if ((fp = fopen(name, "r")) != NULL) {
2907 char buf[256 + FILENAME_MAX];
2908 fclose(fp);
2909 /* file exists */
2910
2911 sprintf(buf, "Are you sure you want to overwrite the"
2912 " file \"%.*s\"?",
2913 FILENAME_MAX, name);
2914 if (!message_box(fe->window, "Question", buf, true, MB_YESNO))
2915 goto free_and_return;
2916 }
2917
2918 fp = fopen(name, "w");
2919
2920 if (!fp) {
2921 error_box(fe->window, "Unable to open save file");
2922 goto free_and_return;
2923 }
2924
2925 {
2926 struct savefile_write_ctx ctx;
2927 ctx.fp = fp;
2928 ctx.error = 0;
2929 midend_serialise(fe->me, savefile_write, &ctx);
2930 fclose(fp);
2931 if (ctx.error) {
2932 char boxmsg[512];
2933 sprintf(boxmsg, "Error writing save file: %.400s",
2934 strerror(ctx.error));
2935 error_box(fe->window, boxmsg);
2936 goto free_and_return;
2937 }
2938 }
2939 free_and_return:
2940 sfree(name);
2941 }
2942}
2943
2944static void menu_load_event(GtkMenuItem *menuitem, gpointer data)
2945{
2946 frontend *fe = (frontend *)data;
2947 char *name;
2948 const char *err;
2949
2950 name = file_selector(fe, "Enter name of saved game file to load", false);
2951
2952 if (name) {
2953 FILE *fp = fopen(name, "r");
2954 sfree(name);
2955
2956 if (!fp) {
2957 error_box(fe->window, "Unable to open saved game file");
2958 return;
2959 }
2960
2961 err = midend_deserialise(fe->me, savefile_read, fp);
2962
2963 fclose(fp);
2964
2965 if (err) {
2966 error_box(fe->window, err);
2967 return;
2968 }
2969
2970 changed_preset(fe);
2971 resize_fe(fe);
2972 midend_redraw(fe->me);
2973 }
2974}
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
3176#ifdef USE_PRINTING
3177static void menu_print_event(GtkMenuItem *menuitem, gpointer data)
3178{
3179 frontend *fe = (frontend *)data;
3180
3181 print_dialog(fe);
3182}
3183#endif
3184
3185static void menu_solve_event(GtkMenuItem *menuitem, gpointer data)
3186{
3187 frontend *fe = (frontend *)data;
3188 const char *msg;
3189
3190 msg = midend_solve(fe->me);
3191
3192 if (msg)
3193 error_box(fe->window, msg);
3194}
3195
3196static void menu_restart_event(GtkMenuItem *menuitem, gpointer data)
3197{
3198 frontend *fe = (frontend *)data;
3199
3200 midend_restart_game(fe->me);
3201}
3202
3203static void menu_config_event(GtkMenuItem *menuitem, gpointer data)
3204{
3205 frontend *fe = (frontend *)data;
3206 int which = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem),
3207 "user-data"));
3208
3209 if (fe->preset_threaded ||
3210 (GTK_IS_CHECK_MENU_ITEM(menuitem) &&
3211 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))))
3212 return;
3213 changed_preset(fe); /* Put the old preset back! */
3214 if (!get_config(fe, which))
3215 return;
3216
3217 if (which != CFG_PREFS)
3218 midend_new_game(fe->me);
3219
3220 resize_fe(fe);
3221 midend_redraw(fe->me);
3222}
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
3296static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
3297{
3298 frontend *fe = (frontend *)data;
3299
3300#if GTK_CHECK_VERSION(3,0,0)
3301# define ABOUT_PARAMS \
3302 "program-name", thegame.name, \
3303 "version", ver, \
3304 "comments", "Part of Simon Tatham's Portable Puzzle Collection"
3305
3306 if (n_xpm_icons) {
3307 GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data
3308 ((const gchar **)xpm_icons[0]);
3309
3310 gtk_show_about_dialog
3311 (GTK_WINDOW(fe->window),
3312 ABOUT_PARAMS,
3313 "logo", icon,
3314 (const gchar *)NULL);
3315 g_object_unref(G_OBJECT(icon));
3316 }
3317 else {
3318 gtk_show_about_dialog
3319 (GTK_WINDOW(fe->window),
3320 ABOUT_PARAMS,
3321 (const gchar *)NULL);
3322 }
3323#else
3324 char titlebuf[256];
3325 char textbuf[1024];
3326
3327 sprintf(titlebuf, "About %.200s", thegame.name);
3328 sprintf(textbuf,
3329 "%.200s\n\n"
3330 "from Simon Tatham's Portable Puzzle Collection\n\n"
3331 "%.500s", thegame.name, ver);
3332
3333 message_box(fe->window, titlebuf, textbuf, true, MB_OK);
3334#endif
3335}
3336
3337static GtkWidget *add_menu_ui_item(
3338 frontend *fe, GtkContainer *cont, const char *text, int action,
3339 int accel_key, int accel_keyqual)
3340{
3341 GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
3342 gtk_container_add(cont, menuitem);
3343 g_object_set_data(G_OBJECT(menuitem), "user-data",
3344 GINT_TO_POINTER(action));
3345 g_signal_connect(G_OBJECT(menuitem), "activate",
3346 G_CALLBACK(menu_key_event), fe);
3347
3348 if (accel_key) {
3349 /*
3350 * Display a keyboard accelerator alongside this menu item.
3351 * Actually this won't be processed via the usual GTK
3352 * accelerator system, because we add it to a dummy
3353 * accelerator group which is never actually activated on the
3354 * main window; this permits back ends to override special
3355 * keys like 'n' and 'r' and 'u' in some UI states. So
3356 * whatever keystroke we display here will still go to
3357 * key_event and be handled in the normal way.
3358 */
3359 gtk_widget_add_accelerator(menuitem,
3360 "activate", fe->dummy_accelgroup,
3361 accel_key, accel_keyqual,
3362 GTK_ACCEL_VISIBLE | GTK_ACCEL_LOCKED);
3363 }
3364
3365 gtk_widget_show(menuitem);
3366 return menuitem;
3367}
3368
3369static void add_menu_separator(GtkContainer *cont)
3370{
3371 GtkWidget *menuitem = gtk_menu_item_new();
3372 gtk_container_add(cont, menuitem);
3373 gtk_widget_show(menuitem);
3374}
3375
3376static void populate_gtk_preset_menu(frontend *fe, struct preset_menu *menu,
3377 GtkWidget *gtkmenu)
3378{
3379 int i;
3380
3381 for (i = 0; i < menu->n_entries; i++) {
3382 struct preset_menu_entry *entry = &menu->entries[i];
3383 GtkWidget *menuitem;
3384
3385 if (entry->params) {
3386 menuitem = gtk_radio_menu_item_new_with_label(
3387 fe->preset_radio, entry->title);
3388 fe->preset_radio = gtk_radio_menu_item_get_group(
3389 GTK_RADIO_MENU_ITEM(menuitem));
3390 g_object_set_data(G_OBJECT(menuitem), "user-data", entry);
3391 g_signal_connect(G_OBJECT(menuitem), "activate",
3392 G_CALLBACK(menu_preset_event), fe);
3393 } else {
3394 GtkWidget *submenu;
3395 menuitem = gtk_menu_item_new_with_label(entry->title);
3396 submenu = gtk_menu_new();
3397 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
3398 populate_gtk_preset_menu(fe, entry->submenu, submenu);
3399 }
3400
3401 gtk_container_add(GTK_CONTAINER(gtkmenu), menuitem);
3402 gtk_widget_show(menuitem);
3403 }
3404}
3405
3406enum { ARG_EITHER, ARG_SAVE, ARG_ID }; /* for argtype */
3407
3408static frontend *new_window(
3409 char *arg, int argtype, char **error, bool headless)
3410{
3411 frontend *fe;
3412#ifdef USE_PRINTING
3413 frontend *print_fe = NULL;
3414#endif
3415 GtkBox *vbox, *hbox;
3416 GtkWidget *menu, *menuitem;
3417 GList *iconlist;
3418 int x, y, n;
3419 char errbuf[1024];
3420 struct preset_menu *preset_menu;
3421
3422 fe = snew(frontend);
3423 memset(fe, 0, sizeof(frontend));
3424
3425#ifndef USE_CAIRO
3426 if (headless) {
3427 fprintf(stderr, "headless mode not supported for non-Cairo drawing\n");
3428 exit(1);
3429 }
3430#else
3431 fe->headless = headless;
3432 fe->ps = 1; /* in headless mode, configure_area won't have set this */
3433#endif
3434
3435 fe->timer_active = false;
3436 fe->timer_id = -1;
3437
3438 fe->me = midend_new(fe, &thegame, &gtk_drawing, fe);
3439 load_prefs(fe);
3440
3441 fe->dr_api = &internal_drawing;
3442
3443#ifdef USE_PRINTING
3444 if (thegame.can_print) {
3445 print_fe = snew(frontend);
3446 memset(print_fe, 0, sizeof(frontend));
3447
3448 /* Defaults */
3449 print_fe->printcount = print_fe->printw = print_fe->printh = 1;
3450 print_fe->printscale = 100;
3451 print_fe->printsolns = false;
3452 print_fe->printcolour = thegame.can_print_in_colour;
3453
3454 /*
3455 * We need to use the same midend as the main frontend because
3456 * we need midend_print_puzzle() to be able to print the
3457 * current puzzle.
3458 */
3459 print_fe->me = fe->me;
3460
3461 print_fe->print_dr = drawing_new(&gtk_drawing, print_fe->me, print_fe);
3462
3463 print_fe->dr_api = &internal_printing;
3464 }
3465#endif
3466
3467 if (arg) {
3468 const char *err;
3469 FILE *fp;
3470
3471 errbuf[0] = '\0';
3472
3473 switch (argtype) {
3474 case ARG_ID:
3475 err = midend_game_id(fe->me, arg);
3476 if (!err)
3477 midend_new_game(fe->me);
3478 else
3479 sprintf(errbuf, "Invalid game ID: %.800s", err);
3480 break;
3481 case ARG_SAVE:
3482 fp = fopen(arg, "r");
3483 if (!fp) {
3484 sprintf(errbuf, "Error opening file: %.800s", strerror(errno));
3485 } else {
3486 err = midend_deserialise(fe->me, savefile_read, fp);
3487 if (err)
3488 sprintf(errbuf, "Invalid save file: %.800s", err);
3489 fclose(fp);
3490 }
3491 break;
3492 default /*case ARG_EITHER*/:
3493 /*
3494 * First try treating the argument as a game ID.
3495 */
3496 err = midend_game_id(fe->me, arg);
3497 if (!err) {
3498 /*
3499 * It's a valid game ID.
3500 */
3501 midend_new_game(fe->me);
3502 } else {
3503 FILE *fp = fopen(arg, "r");
3504 if (!fp) {
3505 sprintf(errbuf, "Supplied argument is neither a game ID (%.400s)"
3506 " nor a save file (%.400s)", err, strerror(errno));
3507 } else {
3508 err = midend_deserialise(fe->me, savefile_read, fp);
3509 if (err)
3510 sprintf(errbuf, "%.800s", err);
3511 fclose(fp);
3512 }
3513 }
3514 break;
3515 }
3516 if (*errbuf) {
3517 *error = dupstr(errbuf);
3518 midend_free(fe->me);
3519 sfree(fe);
3520#ifdef USE_PRINTING
3521 if (thegame.can_print) {
3522 drawing_free(print_fe->print_dr);
3523 sfree(print_fe);
3524 }
3525#endif
3526 return NULL;
3527 }
3528
3529 } else {
3530 midend_new_game(fe->me);
3531 }
3532
3533 if (headless) {
3534 snaffle_colours(fe);
3535 get_size(fe, &fe->pw, &fe->ph);
3536 setup_backing_store(fe);
3537 return fe;
3538 }
3539
3540#if !GTK_CHECK_VERSION(3,0,0)
3541 {
3542 /*
3543 * try_shrink_drawing_area() will do some fiddling with the
3544 * window size request (see comment in that function) after
3545 * all the bits and pieces such as the menu bar and status bar
3546 * have appeared in the puzzle window.
3547 *
3548 * However, on Unity systems, the menu bar _doesn't_ appear in
3549 * the puzzle window, because the Unity shell hijacks it into
3550 * the menu bar at the very top of the screen. We therefore
3551 * try to detect that situation here, so that we don't sit
3552 * here forever waiting for a menu bar.
3553 */
3554 const char prop[] = "gtk-shell-shows-menubar";
3555 GtkSettings *settings = gtk_settings_get_default();
3556 if (!g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
3557 prop)) {
3558 fe->menubar_is_local = true;
3559 } else {
3560 int unity_mode;
3561 g_object_get(gtk_settings_get_default(),
3562 prop, &unity_mode,
3563 (const gchar *)NULL);
3564 fe->menubar_is_local = !unity_mode;
3565 }
3566 }
3567#endif
3568
3569#if GTK_CHECK_VERSION(3,0,0)
3570 fe->awaiting_resize_ack = false;
3571#endif
3572
3573 fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3574 gtk_window_set_title(GTK_WINDOW(fe->window), thegame.name);
3575
3576 vbox = GTK_BOX(gtk_vbox_new(false, 0));
3577 gtk_container_add(GTK_CONTAINER(fe->window), GTK_WIDGET(vbox));
3578 gtk_widget_show(GTK_WIDGET(vbox));
3579
3580 fe->dummy_accelgroup = gtk_accel_group_new();
3581 /*
3582 * Intentionally _not_ added to the window via
3583 * gtk_window_add_accel_group; see menu_key_event
3584 */
3585
3586 hbox = GTK_BOX(gtk_hbox_new(false, 0));
3587 gtk_box_pack_start(vbox, GTK_WIDGET(hbox), false, false, 0);
3588 gtk_widget_show(GTK_WIDGET(hbox));
3589
3590 fe->menubar = gtk_menu_bar_new();
3591 gtk_box_pack_start(hbox, fe->menubar, true, true, 0);
3592 gtk_widget_show(fe->menubar);
3593
3594 menuitem = gtk_menu_item_new_with_mnemonic("_Game");
3595 gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem);
3596 gtk_widget_show(menuitem);
3597
3598 menu = gtk_menu_new();
3599 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
3600
3601 add_menu_ui_item(fe, GTK_CONTAINER(menu), "New", UI_NEWGAME, 'n', 0);
3602
3603 menuitem = gtk_menu_item_new_with_label("Restart");
3604 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3605 g_signal_connect(G_OBJECT(menuitem), "activate",
3606 G_CALLBACK(menu_restart_event), fe);
3607 gtk_widget_show(menuitem);
3608
3609 menuitem = gtk_menu_item_new_with_label("Specific...");
3610 g_object_set_data(G_OBJECT(menuitem), "user-data",
3611 GINT_TO_POINTER(CFG_DESC));
3612 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3613 g_signal_connect(G_OBJECT(menuitem), "activate",
3614 G_CALLBACK(menu_config_event), fe);
3615 gtk_widget_show(menuitem);
3616
3617 menuitem = gtk_menu_item_new_with_label("Random Seed...");
3618 g_object_set_data(G_OBJECT(menuitem), "user-data",
3619 GINT_TO_POINTER(CFG_SEED));
3620 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3621 g_signal_connect(G_OBJECT(menuitem), "activate",
3622 G_CALLBACK(menu_config_event), fe);
3623 gtk_widget_show(menuitem);
3624
3625 fe->preset_radio = NULL;
3626 fe->preset_custom = NULL;
3627 fe->preset_threaded = false;
3628
3629 preset_menu = midend_get_presets(fe->me, NULL);
3630 if (preset_menu->n_entries > 0 || thegame.can_configure) {
3631 GtkWidget *submenu;
3632
3633 menuitem = gtk_menu_item_new_with_mnemonic("_Type");
3634 gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem);
3635 gtk_widget_show(menuitem);
3636
3637 submenu = gtk_menu_new();
3638 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
3639
3640 populate_gtk_preset_menu(fe, preset_menu, submenu);
3641
3642 if (thegame.can_configure) {
3643 menuitem = fe->preset_custom =
3644 gtk_radio_menu_item_new_with_label(fe->preset_radio,
3645 "Custom...");
3646 fe->preset_radio =
3647 gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
3648 gtk_container_add(GTK_CONTAINER(submenu), menuitem);
3649 g_object_set_data(G_OBJECT(menuitem), "user-data",
3650 GINT_TO_POINTER(CFG_SETTINGS));
3651 g_signal_connect(G_OBJECT(menuitem), "activate",
3652 G_CALLBACK(menu_config_event), fe);
3653 gtk_widget_show(menuitem);
3654 }
3655
3656 }
3657
3658 add_menu_separator(GTK_CONTAINER(menu));
3659 menuitem = gtk_menu_item_new_with_label("Load...");
3660 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3661 g_signal_connect(G_OBJECT(menuitem), "activate",
3662 G_CALLBACK(menu_load_event), fe);
3663 gtk_widget_show(menuitem);
3664 menuitem = gtk_menu_item_new_with_label("Save...");
3665 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3666 g_signal_connect(G_OBJECT(menuitem), "activate",
3667 G_CALLBACK(menu_save_event), fe);
3668 gtk_widget_show(menuitem);
3669#ifdef USE_PRINTING
3670 if (thegame.can_print) {
3671 add_menu_separator(GTK_CONTAINER(menu));
3672 menuitem = gtk_menu_item_new_with_label("Print...");
3673 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3674 g_signal_connect(G_OBJECT(menuitem), "activate",
3675 G_CALLBACK(menu_print_event), print_fe);
3676 gtk_widget_show(menuitem);
3677 }
3678#endif
3679#ifndef STYLUS_BASED
3680 add_menu_separator(GTK_CONTAINER(menu));
3681 add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0);
3682 add_menu_ui_item(fe, GTK_CONTAINER(menu), "Redo", UI_REDO, 'r', 0);
3683#endif
3684 if (thegame.can_format_as_text_ever) {
3685 add_menu_separator(GTK_CONTAINER(menu));
3686 menuitem = gtk_menu_item_new_with_label("Copy");
3687 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3688 g_signal_connect(G_OBJECT(menuitem), "activate",
3689 G_CALLBACK(menu_copy_event), fe);
3690 gtk_widget_show(menuitem);
3691 fe->copy_menu_item = menuitem;
3692 } else {
3693 fe->copy_menu_item = NULL;
3694 }
3695 if (thegame.can_solve) {
3696 add_menu_separator(GTK_CONTAINER(menu));
3697 menuitem = gtk_menu_item_new_with_label("Solve");
3698 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3699 g_signal_connect(G_OBJECT(menuitem), "activate",
3700 G_CALLBACK(menu_solve_event), fe);
3701 gtk_widget_show(menuitem);
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
3713 add_menu_separator(GTK_CONTAINER(menu));
3714 add_menu_ui_item(fe, GTK_CONTAINER(menu), "Exit", UI_QUIT, 'q', 0);
3715
3716 menuitem = gtk_menu_item_new_with_mnemonic("_Help");
3717 gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem);
3718 gtk_widget_show(menuitem);
3719
3720 menu = gtk_menu_new();
3721 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
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
3742 menuitem = gtk_menu_item_new_with_label("About");
3743 gtk_container_add(GTK_CONTAINER(menu), menuitem);
3744 g_signal_connect(G_OBJECT(menuitem), "activate",
3745 G_CALLBACK(menu_about_event), fe);
3746 gtk_widget_show(menuitem);
3747
3748#ifdef STYLUS_BASED
3749 menuitem=gtk_button_new_with_mnemonic("_Redo");
3750 g_object_set_data(G_OBJECT(menuitem), "user-data",
3751 GINT_TO_POINTER(UI_REDO));
3752 g_signal_connect(G_OBJECT(menuitem), "clicked",
3753 G_CALLBACK(menu_key_event), fe);
3754 gtk_box_pack_end(hbox, menuitem, false, false, 0);
3755 gtk_widget_show(menuitem);
3756
3757 menuitem=gtk_button_new_with_mnemonic("_Undo");
3758 g_object_set_data(G_OBJECT(menuitem), "user-data",
3759 GINT_TO_POINTER(UI_UNDO));
3760 g_signal_connect(G_OBJECT(menuitem), "clicked",
3761 G_CALLBACK(menu_key_event), fe);
3762 gtk_box_pack_end(hbox, menuitem, false, false, 0);
3763 gtk_widget_show(menuitem);
3764
3765 if (thegame.flags & REQUIRE_NUMPAD) {
3766 hbox = GTK_BOX(gtk_hbox_new(false, 0));
3767 gtk_box_pack_start(vbox, GTK_WIDGET(hbox), false, false, 0);
3768 gtk_widget_show(GTK_WIDGET(hbox));
3769
3770 *((int*)errbuf)=0;
3771 errbuf[1]='\0';
3772 for(errbuf[0]='0';errbuf[0]<='9';errbuf[0]++) {
3773 menuitem=gtk_button_new_with_label(errbuf);
3774 g_object_set_data(G_OBJECT(menuitem), "user-data",
3775 GINT_TO_POINTER((int)(errbuf[0])));
3776 g_signal_connect(G_OBJECT(menuitem), "clicked",
3777 G_CALLBACK(menu_key_event), fe);
3778 gtk_box_pack_start(hbox, menuitem, true, true, 0);
3779 gtk_widget_show(menuitem);
3780 }
3781 }
3782#endif /* STYLUS_BASED */
3783
3784 changed_preset(fe);
3785
3786 snaffle_colours(fe);
3787
3788 if (midend_wants_statusbar(fe->me)) {
3789 GtkWidget *viewport;
3790 GtkRequisition req;
3791
3792 viewport = gtk_viewport_new(NULL, NULL);
3793 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
3794 fe->statusbar = gtk_statusbar_new();
3795 gtk_container_add(GTK_CONTAINER(viewport), fe->statusbar);
3796 gtk_widget_show(viewport);
3797 gtk_box_pack_end(vbox, viewport, false, false, 0);
3798 gtk_widget_show(fe->statusbar);
3799 fe->statusctx = gtk_statusbar_get_context_id
3800 (GTK_STATUSBAR(fe->statusbar), "game");
3801 gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx,
3802 DEFAULT_STATUSBAR_TEXT);
3803#if GTK_CHECK_VERSION(3,0,0)
3804 gtk_widget_get_preferred_size(fe->statusbar, &req, NULL);
3805#else
3806 gtk_widget_size_request(fe->statusbar, &req);
3807#endif
3808 gtk_widget_set_size_request(viewport, -1, req.height);
3809 } else
3810 fe->statusbar = NULL;
3811
3812 fe->area = gtk_drawing_area_new();
3813#if GTK_CHECK_VERSION(2,0,0) && !GTK_CHECK_VERSION(3,0,0)
3814 gtk_widget_set_double_buffered(fe->area, false);
3815#endif
3816 {
3817 GdkGeometry geom;
3818 geom.base_width = 0;
3819#if GTK_CHECK_VERSION(3,0,0)
3820 geom.base_height = window_extra_height(fe);
3821 gtk_window_set_geometry_hints(GTK_WINDOW(fe->window), NULL,
3822 &geom, GDK_HINT_BASE_SIZE);
3823#else
3824 geom.base_height = 0;
3825 gtk_window_set_geometry_hints(GTK_WINDOW(fe->window), fe->area,
3826 &geom, GDK_HINT_BASE_SIZE);
3827#endif
3828 }
3829 fe->w = -1;
3830 fe->h = -1;
3831 get_size(fe, &x, &y);
3832#if GTK_CHECK_VERSION(3,0,0)
3833 gtk_window_set_default_size(GTK_WINDOW(fe->window),
3834 x, y + window_extra_height(fe));
3835#else
3836 fe->drawing_area_shrink_pending = false;
3837 gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
3838#endif
3839
3840 gtk_box_pack_end(vbox, fe->area, true, true, 0);
3841
3842 clear_backing_store(fe);
3843 fe->fonts = NULL;
3844 fe->nfonts = fe->fontsize = 0;
3845
3846 fe->paste_data = NULL;
3847 fe->paste_data_len = 0;
3848
3849 g_signal_connect(G_OBJECT(fe->window), "destroy",
3850 G_CALLBACK(destroy), fe);
3851 g_signal_connect(G_OBJECT(fe->window), "key_press_event",
3852 G_CALLBACK(key_event), fe);
3853 g_signal_connect(G_OBJECT(fe->area), "button_press_event",
3854 G_CALLBACK(button_event), fe);
3855 g_signal_connect(G_OBJECT(fe->area), "button_release_event",
3856 G_CALLBACK(button_event), fe);
3857 g_signal_connect(G_OBJECT(fe->area), "motion_notify_event",
3858 G_CALLBACK(motion_event), fe);
3859 g_signal_connect(G_OBJECT(fe->window), "selection_get",
3860 G_CALLBACK(selection_get), fe);
3861 g_signal_connect(G_OBJECT(fe->window), "selection_clear_event",
3862 G_CALLBACK(selection_clear), fe);
3863#if GTK_CHECK_VERSION(3,0,0)
3864 g_signal_connect(G_OBJECT(fe->area), "draw",
3865 G_CALLBACK(draw_area), fe);
3866#else
3867 g_signal_connect(G_OBJECT(fe->area), "expose_event",
3868 G_CALLBACK(expose_area), fe);
3869#endif
3870 g_signal_connect(G_OBJECT(fe->window), "map_event",
3871 G_CALLBACK(map_window), fe);
3872 g_signal_connect(G_OBJECT(fe->area), "configure_event",
3873 G_CALLBACK(configure_area), fe);
3874 g_signal_connect(G_OBJECT(fe->window), "configure_event",
3875 G_CALLBACK(configure_window), fe);
3876#if GTK_CHECK_VERSION(3,0,0)
3877 g_signal_connect(G_OBJECT(fe->window), "size_allocate",
3878 G_CALLBACK(window_size_alloc), fe);
3879#endif
3880
3881 gtk_widget_add_events(GTK_WIDGET(fe->area),
3882 GDK_BUTTON_PRESS_MASK |
3883 GDK_BUTTON_RELEASE_MASK |
3884 GDK_BUTTON_MOTION_MASK |
3885 GDK_POINTER_MOTION_HINT_MASK);
3886
3887 if (n_xpm_icons) {
3888 gtk_window_set_icon(GTK_WINDOW(fe->window),
3889 gdk_pixbuf_new_from_xpm_data
3890 ((const gchar **)xpm_icons[n_xpm_icons-1]));
3891
3892 iconlist = NULL;
3893 for (n = 0; n < n_xpm_icons; n++) {
3894 iconlist =
3895 g_list_append(iconlist,
3896 gdk_pixbuf_new_from_xpm_data((const gchar **)
3897 xpm_icons[n]));
3898 }
3899 gtk_window_set_icon_list(GTK_WINDOW(fe->window), iconlist);
3900 }
3901
3902 gtk_widget_show(fe->area);
3903 gtk_widget_show(fe->window);
3904
3905#if !GTK_CHECK_VERSION(3,0,0)
3906 fe->drawing_area_shrink_pending = true;
3907 try_shrink_drawing_area(fe);
3908#endif
3909
3910 set_window_background(fe, 0);
3911
3912 return fe;
3913}
3914
3915static void list_presets_from_menu(struct preset_menu *menu)
3916{
3917 int i;
3918
3919 for (i = 0; i < menu->n_entries; i++) {
3920 if (menu->entries[i].params) {
3921 char *paramstr = thegame.encode_params(
3922 menu->entries[i].params, true);
3923 printf("%s %s\n", paramstr, menu->entries[i].title);
3924 sfree(paramstr);
3925 } else {
3926 list_presets_from_menu(menu->entries[i].submenu);
3927 }
3928 }
3929}
3930
3931int main(int argc, char **argv)
3932{
3933 char *pname = argv[0];
3934 int ngenerate = 0, px = 1, py = 1;
3935 bool print = false;
3936 bool time_generation = false, test_solve = false, list_presets = false;
3937 bool delete_prefs_action = false;
3938 bool soln = false, colour = false;
3939 float scale = 1.0F;
3940 float redo_proportion = 0.0F;
3941 const char *savefile = NULL, *savesuffix = NULL;
3942 char *arg = NULL;
3943 int argtype = ARG_EITHER;
3944 char *screenshot_file = NULL;
3945 bool doing_opts = true;
3946 int ac = argc;
3947 char **av = argv;
3948 char errbuf[500];
3949
3950 /*
3951 * Command line parsing in this function is rather fiddly,
3952 * because GTK wants to have a go at argc/argv _first_ - and
3953 * yet we can't let it, because gtk_init() will bomb out if it
3954 * can't open an X display, whereas in fact we want to permit
3955 * our --generate and --print modes to run without an X
3956 * display.
3957 *
3958 * So what we do is:
3959 * - we parse the command line ourselves, without modifying
3960 * argc/argv
3961 * - if we encounter an error which might plausibly be the
3962 * result of a GTK command line (i.e. not detailed errors in
3963 * particular options of ours) we store the error message
3964 * and terminate parsing.
3965 * - if we got enough out of the command line to know it
3966 * specifies a non-X mode of operation, we either display
3967 * the stored error and return failure, or if there is no
3968 * stored error we do the non-X operation and return
3969 * success.
3970 * - otherwise, we go straight to gtk_init().
3971 */
3972
3973 errbuf[0] = '\0';
3974 while (--ac > 0) {
3975 char *p = *++av;
3976 if (doing_opts && !strcmp(p, "--version")) {
3977 printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n",
3978 thegame.name, ver);
3979 return 0;
3980 } else if (doing_opts && !strcmp(p, "--generate")) {
3981 if (--ac > 0) {
3982 ngenerate = atoi(*++av);
3983 if (!ngenerate) {
3984 fprintf(stderr, "%s: '--generate' expected a number\n",
3985 pname);
3986 return 1;
3987 }
3988 } else
3989 ngenerate = 1;
3990 } else if (doing_opts && !strcmp(p, "--time-generation")) {
3991 time_generation = true;
3992 } else if (doing_opts && !strcmp(p, "--test-solve")) {
3993 test_solve = true;
3994 } else if (doing_opts && !strcmp(p, "--list-presets")) {
3995 list_presets = true;
3996 } else if (doing_opts && (!strcmp(p, "--delete-prefs") ||
3997 !strcmp(p, "--delete-preferences"))) {
3998 delete_prefs_action = true;
3999 } else if (doing_opts && !strcmp(p, "--save")) {
4000 if (--ac > 0) {
4001 savefile = *++av;
4002 } else {
4003 fprintf(stderr, "%s: '--save' expected a filename\n",
4004 pname);
4005 return 1;
4006 }
4007 } else if (doing_opts && (!strcmp(p, "--save-suffix") ||
4008 !strcmp(p, "--savesuffix"))) {
4009 if (--ac > 0) {
4010 savesuffix = *++av;
4011 } else {
4012 fprintf(stderr, "%s: '--save-suffix' expected a filename\n",
4013 pname);
4014 return 1;
4015 }
4016 } else if (doing_opts && !strcmp(p, "--print")) {
4017 if (!thegame.can_print) {
4018 fprintf(stderr, "%s: this game does not support printing\n",
4019 pname);
4020 return 1;
4021 }
4022 print = true;
4023 if (--ac > 0) {
4024 char *dim = *++av;
4025 if (sscanf(dim, "%dx%d", &px, &py) != 2) {
4026 fprintf(stderr, "%s: unable to parse argument '%s' to "
4027 "'--print'\n", pname, dim);
4028 return 1;
4029 }
4030 } else {
4031 px = py = 1;
4032 }
4033 } else if (doing_opts && !strcmp(p, "--scale")) {
4034 if (--ac > 0) {
4035 scale = atof(*++av);
4036 } else {
4037 fprintf(stderr, "%s: no argument supplied to '--scale'\n",
4038 pname);
4039 return 1;
4040 }
4041 } else if (doing_opts && !strcmp(p, "--redo")) {
4042 /*
4043 * This is an internal option which I don't expect
4044 * users to have any particular use for. The effect of
4045 * --redo is that once the game has been loaded and
4046 * initialised, the next move in the redo chain is
4047 * replayed, and the game screen is redrawn part way
4048 * through the making of the move. This is only
4049 * meaningful if there _is_ a next move in the redo
4050 * chain, which means in turn that this option is only
4051 * useful if you're also passing a save file on the
4052 * command line.
4053 *
4054 * This option is used by the script which generates
4055 * the puzzle icons and website screenshots, and I
4056 * don't imagine it's useful for anything else.
4057 * (Unless, I suppose, users don't like my screenshots
4058 * and want to generate their own in the same way for
4059 * some repackaged version of the puzzles.)
4060 */
4061 if (--ac > 0) {
4062 redo_proportion = atof(*++av);
4063 } else {
4064 fprintf(stderr, "%s: no argument supplied to '--redo'\n",
4065 pname);
4066 return 1;
4067 }
4068 } else if (doing_opts && !strcmp(p, "--screenshot")) {
4069 /*
4070 * Another internal option for the icon building
4071 * script. This causes a screenshot of the central
4072 * drawing area (i.e. not including the menu bar or
4073 * status bar) to be saved to a PNG file once the
4074 * window has been drawn, and then the application
4075 * quits immediately.
4076 */
4077 if (--ac > 0) {
4078 screenshot_file = *++av;
4079 } else {
4080 fprintf(stderr, "%s: no argument supplied to '--screenshot'\n",
4081 pname);
4082 return 1;
4083 }
4084 } else if (doing_opts && (!strcmp(p, "--with-solutions") ||
4085 !strcmp(p, "--with-solution") ||
4086 !strcmp(p, "--with-solns") ||
4087 !strcmp(p, "--with-soln") ||
4088 !strcmp(p, "--solutions") ||
4089 !strcmp(p, "--solution") ||
4090 !strcmp(p, "--solns") ||
4091 !strcmp(p, "--soln"))) {
4092 soln = true;
4093 } else if (doing_opts && !strcmp(p, "--colour")) {
4094 if (!thegame.can_print_in_colour) {
4095 fprintf(stderr, "%s: this game does not support colour"
4096 " printing\n", pname);
4097 return 1;
4098 }
4099 colour = true;
4100 } else if (doing_opts && !strcmp(p, "--load")) {
4101 argtype = ARG_SAVE;
4102 } else if (doing_opts && !strcmp(p, "--game")) {
4103 argtype = ARG_ID;
4104 } else if (doing_opts && !strcmp(p, "--")) {
4105 doing_opts = false;
4106 } else if (!doing_opts || p[0] != '-') {
4107 if (arg) {
4108 fprintf(stderr, "%s: more than one argument supplied\n",
4109 pname);
4110 return 1;
4111 }
4112 arg = p;
4113 } else {
4114 sprintf(errbuf, "%.100s: unrecognised option '%.100s'\n",
4115 pname, p);
4116 break;
4117 }
4118 }
4119
4120 /*
4121 * Special standalone mode for generating puzzle IDs on the
4122 * command line. Useful for generating puzzles to be printed
4123 * out and solved offline (for puzzles where that even makes
4124 * sense - Solo, for example, is a lot more pencil-and-paper
4125 * friendly than Twiddle!)
4126 *
4127 * Usage:
4128 *
4129 * <puzzle-name> --generate [<n> [<params>]]
4130 *
4131 * <n>, if present, is the number of puzzle IDs to generate.
4132 * <params>, if present, is the same type of parameter string
4133 * you would pass to the puzzle when running it in GUI mode,
4134 * including optional extras such as the expansion factor in
4135 * Rectangles and the difficulty level in Solo.
4136 *
4137 * If you specify <params>, you must also specify <n> (although
4138 * you may specify it to be 1). Sorry; that was the
4139 * simplest-to-parse command-line syntax I came up with.
4140 */
4141 if (ngenerate > 0 || print || savefile || savesuffix) {
4142 int i, n = 1;
4143 midend *me;
4144 char *id;
4145 document *doc = NULL;
4146
4147 /*
4148 * If we're in this branch, we should display any pending
4149 * error message from the command line, since GTK isn't going
4150 * to take another crack at making sense of it.
4151 */
4152 if (*errbuf) {
4153 fputs(errbuf, stderr);
4154 return 1;
4155 }
4156
4157 n = ngenerate;
4158
4159 me = midend_new(NULL, &thegame, NULL, NULL);
4160 i = 0;
4161
4162 if (savefile && !savesuffix)
4163 savesuffix = "";
4164 if (!savefile && savesuffix)
4165 savefile = "";
4166
4167 if (print)
4168 doc = document_new(px, py, scale);
4169
4170 /*
4171 * In this loop, we either generate a game ID or read one
4172 * from stdin depending on whether we're in generate mode;
4173 * then we either write it to stdout or print it, depending
4174 * on whether we're in print mode. Thus, this loop handles
4175 * generate-to-stdout, print-from-stdin and generate-and-
4176 * immediately-print modes.
4177 *
4178 * (It could also handle a copy-stdin-to-stdout mode,
4179 * although there's currently no combination of options
4180 * which will cause this loop to be activated in that mode.
4181 * It wouldn't be _entirely_ pointless, though, because
4182 * stdin could contain bare params strings or random-seed
4183 * IDs, and stdout would contain nothing but fully
4184 * generated descriptive game IDs.)
4185 */
4186 while (ngenerate == 0 || i < n) {
4187 char *pstr, *seed;
4188 const char *err;
4189 struct rusage before, after;
4190
4191 if (ngenerate == 0) {
4192 pstr = fgetline(stdin);
4193 if (!pstr)
4194 break;
4195 pstr[strcspn(pstr, "\r\n")] = '\0';
4196 } else {
4197 if (arg) {
4198 pstr = snewn(strlen(arg) + 40, char);
4199
4200 strcpy(pstr, arg);
4201 if (i > 0 && strchr(arg, '#'))
4202 sprintf(pstr + strlen(pstr), "-%d", i);
4203 } else
4204 pstr = NULL;
4205 }
4206
4207 if (pstr) {
4208 err = midend_game_id(me, pstr);
4209 if (err) {
4210 fprintf(stderr, "%s: error parsing '%s': %s\n",
4211 pname, pstr, err);
4212 return 1;
4213 }
4214 }
4215
4216 if (time_generation)
4217 getrusage(RUSAGE_SELF, &before);
4218
4219 midend_new_game(me);
4220
4221 seed = midend_get_random_seed(me);
4222
4223 if (time_generation) {
4224 double elapsed;
4225
4226 getrusage(RUSAGE_SELF, &after);
4227
4228 elapsed = (after.ru_utime.tv_sec -
4229 before.ru_utime.tv_sec);
4230 elapsed += (after.ru_utime.tv_usec -
4231 before.ru_utime.tv_usec) / 1000000.0;
4232
4233 printf("%s %s: %.6f\n", thegame.name, seed, elapsed);
4234 }
4235
4236 if (test_solve && thegame.can_solve) {
4237 /*
4238 * Now destroy the aux_info in the midend, by means of
4239 * re-entering the same game id, and then try to solve
4240 * it.
4241 */
4242 char *game_id;
4243
4244 game_id = midend_get_game_id(me);
4245 err = midend_game_id(me, game_id);
4246 if (err) {
4247 fprintf(stderr, "%s %s: game id re-entry error: %s\n",
4248 thegame.name, seed, err);
4249 return 1;
4250 }
4251 midend_new_game(me);
4252 sfree(game_id);
4253
4254 err = midend_solve(me);
4255 /*
4256 * If the solve operation returned the error "Solution
4257 * not known for this puzzle", that's OK, because that
4258 * just means it's a puzzle for which we don't have an
4259 * algorithmic solver and hence can't solve it without
4260 * the aux_info, e.g. Netslide. Any other error is a
4261 * problem, though.
4262 */
4263 if (err && strcmp(err, "Solution not known for this puzzle")) {
4264 fprintf(stderr, "%s %s: solve error: %s\n",
4265 thegame.name, seed, err);
4266 return 1;
4267 }
4268 }
4269
4270 sfree(pstr);
4271 sfree(seed);
4272
4273 if (doc) {
4274 err = midend_print_puzzle(me, doc, soln);
4275 if (err) {
4276 fprintf(stderr, "%s: error in printing: %s\n", pname, err);
4277 return 1;
4278 }
4279 }
4280 if (savefile) {
4281 struct savefile_write_ctx ctx;
4282 char *realname = snewn(40 + strlen(savefile) +
4283 strlen(savesuffix), char);
4284 sprintf(realname, "%s%d%s", savefile, i, savesuffix);
4285
4286 if (soln) {
4287 const char *err = midend_solve(me);
4288 if (err) {
4289 fprintf(stderr, "%s: unable to show solution: %s\n",
4290 realname, err);
4291 return 1;
4292 }
4293 }
4294
4295 ctx.fp = fopen(realname, "w");
4296 if (!ctx.fp) {
4297 fprintf(stderr, "%s: open: %s\n", realname,
4298 strerror(errno));
4299 return 1;
4300 }
4301 ctx.error = 0;
4302 midend_serialise(me, savefile_write, &ctx);
4303 if (ctx.error) {
4304 fprintf(stderr, "%s: write: %s\n", realname,
4305 strerror(ctx.error));
4306 return 1;
4307 }
4308 if (fclose(ctx.fp)) {
4309 fprintf(stderr, "%s: close: %s\n", realname,
4310 strerror(errno));
4311 return 1;
4312 }
4313 sfree(realname);
4314 }
4315 if (!doc && !savefile && !time_generation) {
4316 id = midend_get_game_id(me);
4317 puts(id);
4318 sfree(id);
4319 }
4320
4321 i++;
4322 }
4323
4324 if (doc) {
4325 psdata *ps = ps_init(stdout, colour);
4326 document_print(doc, ps_drawing_api(ps));
4327 document_free(doc);
4328 ps_free(ps);
4329 }
4330
4331 midend_free(me);
4332
4333 return 0;
4334 } else if (list_presets) {
4335 /*
4336 * Another specialist mode which causes the puzzle to list the
4337 * game_params strings for all its preset configurations.
4338 */
4339 midend *me;
4340 struct preset_menu *menu;
4341
4342 me = midend_new(NULL, &thegame, NULL, NULL);
4343 menu = midend_get_presets(me, NULL);
4344 list_presets_from_menu(menu);
4345 midend_free(me);
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 }
4357 } else {
4358 frontend *fe;
4359 bool headless = screenshot_file != NULL;
4360 char *error = NULL;
4361
4362 if (!headless)
4363 gtk_init(&argc, &argv);
4364
4365 fe = new_window(arg, argtype, &error, headless);
4366
4367 if (!fe) {
4368 fprintf(stderr, "%s: %s\n", pname, error);
4369 sfree(error);
4370 return 1;
4371 }
4372
4373 if (screenshot_file) {
4374 /*
4375 * Some puzzles will not redraw their entire area if
4376 * given a partially completed animation, which means
4377 * we must redraw now and _then_ redraw again after
4378 * freezing the move timer.
4379 */
4380 midend_force_redraw(fe->me);
4381 }
4382
4383 if (redo_proportion) {
4384 /* Start a redo. */
4385 midend_process_key(fe->me, 0, 0, 'r');
4386 /* And freeze the timer at the specified position. */
4387 midend_freeze_timer(fe->me, redo_proportion);
4388 }
4389
4390 if (screenshot_file) {
4391 save_screenshot_png(fe, screenshot_file);
4392 exit(0);
4393 }
4394
4395 gtk_main();
4396 }
4397
4398 return 0;
4399}
diff --git a/apps/plugins/puzzles/src/list.c b/apps/plugins/puzzles/src/list.c
deleted file mode 100644
index 28cefca017..0000000000
--- a/apps/plugins/puzzles/src/list.c
+++ /dev/null
@@ -1,17 +0,0 @@
1/*
2 * list.c: List of pointers to puzzle structures, for monolithic
3 * platforms.
4 *
5 * This file depends on the header "generated-games.h", which is
6 * constructed by CMakeLists.txt.
7 */
8
9#include "puzzles.h"
10
11#define GAME(x) &x,
12const game *gamelist[] = {
13#include "generated-games.h"
14};
15#undef GAME
16
17const int gamecount = lenof(gamelist);
diff --git a/apps/plugins/puzzles/src/malloc.c b/apps/plugins/puzzles/src/malloc.c
deleted file mode 100644
index 39bcfac25b..0000000000
--- a/apps/plugins/puzzles/src/malloc.c
+++ /dev/null
@@ -1,64 +0,0 @@
1/*
2 * malloc.c: safe wrappers around malloc, realloc, free, strdup
3 */
4
5#ifndef NO_STDINT_H
6#include <stdint.h>
7#endif
8#include <stdlib.h>
9#include <string.h>
10#include "puzzles.h"
11
12/*
13 * smalloc should guarantee to return a useful pointer - we
14 * can do nothing except die when it's out of memory anyway.
15 */
16void *smalloc(size_t size) {
17 void *p;
18#ifdef PTRDIFF_MAX
19 if (size > PTRDIFF_MAX)
20 fatal("allocation too large");
21#endif
22 p = malloc(size);
23 if (!p)
24 fatal("out of memory");
25 return p;
26}
27
28/*
29 * sfree should guaranteeably deal gracefully with freeing NULL
30 */
31void sfree(void *p) {
32 if (p) {
33 free(p);
34 }
35}
36
37/*
38 * srealloc should guaranteeably be able to realloc NULL
39 */
40void *srealloc(void *p, size_t size) {
41 void *q;
42#ifdef PTRDIFF_MAX
43 if (size > PTRDIFF_MAX)
44 fatal("allocation too large");
45#endif
46 if (p) {
47 q = realloc(p, size);
48 } else {
49 q = malloc(size);
50 }
51 if (!q)
52 fatal("out of memory");
53 return q;
54}
55
56/*
57 * dupstr is like strdup, but with the never-return-NULL property
58 * of smalloc (and also reliably defined in all environments :-)
59 */
60char *dupstr(const char *s) {
61 char *r = smalloc(1+strlen(s));
62 strcpy(r,s);
63 return r;
64}
diff --git a/apps/plugins/puzzles/src/nestedvm.c b/apps/plugins/puzzles/src/nestedvm.c
deleted file mode 100644
index 0a9fdbcfed..0000000000
--- a/apps/plugins/puzzles/src/nestedvm.c
+++ /dev/null
@@ -1,486 +0,0 @@
1/*
2 * nestedvm.c: NestedVM front end for my puzzle collection.
3 */
4
5#include <stdio.h>
6#include <assert.h>
7#include <stdlib.h>
8#include <time.h>
9#include <stdarg.h>
10#include <string.h>
11#include <errno.h>
12
13#include <sys/time.h>
14
15#include "puzzles.h"
16
17extern void _pause();
18extern int _call_java(int cmd, int arg1, int arg2, int arg3);
19
20void fatal(const char *fmt, ...)
21{
22 va_list ap;
23 fprintf(stderr, "fatal error: ");
24 va_start(ap, fmt);
25 vfprintf(stderr, fmt, ap);
26 va_end(ap);
27 fprintf(stderr, "\n");
28 exit(1);
29}
30
31struct frontend {
32 // TODO kill unneeded members!
33 midend *me;
34 bool timer_active;
35 struct timeval last_time;
36 config_item *cfg;
37 int cfg_which;
38 bool cfgret;
39 int ox, oy, w, h;
40};
41
42static frontend *_fe;
43
44void get_random_seed(void **randseed, int *randseedsize)
45{
46 struct timeval *tvp = snew(struct timeval);
47 gettimeofday(tvp, NULL);
48 *randseed = (void *)tvp;
49 *randseedsize = sizeof(struct timeval);
50}
51
52void frontend_default_colour(frontend *fe, float *output)
53{
54 output[0] = output[1]= output[2] = 0.8f;
55}
56
57void nestedvm_status_bar(void *handle, const char *text)
58{
59 _call_java(4,0,(int)text,0);
60}
61
62void nestedvm_start_draw(void *handle)
63{
64 frontend *fe = (frontend *)handle;
65 _call_java(5, 0, fe->w, fe->h);
66 _call_java(4, 1, fe->ox, fe->oy);
67}
68
69void nestedvm_clip(void *handle, int x, int y, int w, int h)
70{
71 frontend *fe = (frontend *)handle;
72 _call_java(5, w, h, 0);
73 _call_java(4, 3, x + fe->ox, y + fe->oy);
74}
75
76void nestedvm_unclip(void *handle)
77{
78 frontend *fe = (frontend *)handle;
79 _call_java(4, 4, fe->ox, fe->oy);
80}
81
82void nestedvm_draw_text(void *handle, int x, int y, int fonttype, int fontsize,
83 int align, int colour, const char *text)
84{
85 frontend *fe = (frontend *)handle;
86 _call_java(5, x + fe->ox, y + fe->oy,
87 (fonttype == FONT_FIXED ? 0x10 : 0x0) | align);
88 _call_java(7, fontsize, colour, (int)text);
89}
90
91void nestedvm_draw_rect(void *handle, int x, int y, int w, int h, int colour)
92{
93 frontend *fe = (frontend *)handle;
94 _call_java(5, w, h, colour);
95 _call_java(4, 5, x + fe->ox, y + fe->oy);
96}
97
98void nestedvm_draw_line(void *handle, int x1, int y1, int x2, int y2,
99 int colour)
100{
101 frontend *fe = (frontend *)handle;
102 _call_java(5, x2 + fe->ox, y2 + fe->oy, colour);
103 _call_java(4, 6, x1 + fe->ox, y1 + fe->oy);
104}
105
106void nestedvm_draw_poly(void *handle, int *coords, int npoints,
107 int fillcolour, int outlinecolour)
108{
109 frontend *fe = (frontend *)handle;
110 int i;
111 _call_java(4, 7, npoints, 0);
112 for (i = 0; i < npoints; i++) {
113 _call_java(6, i, coords[i*2] + fe->ox, coords[i*2+1] + fe->oy);
114 }
115 _call_java(4, 8, outlinecolour, fillcolour);
116}
117
118void nestedvm_draw_circle(void *handle, int cx, int cy, int radius,
119 int fillcolour, int outlinecolour)
120{
121 frontend *fe = (frontend *)handle;
122 _call_java(5, cx+fe->ox, cy+fe->oy, radius);
123 _call_java(4, 9, outlinecolour, fillcolour);
124}
125
126struct blitter {
127 int handle, w, h, x, y;
128};
129
130blitter *nestedvm_blitter_new(void *handle, int w, int h)
131{
132 blitter *bl = snew(blitter);
133 bl->handle = -1;
134 bl->w = w;
135 bl->h = h;
136 return bl;
137}
138
139void nestedvm_blitter_free(void *handle, blitter *bl)
140{
141 if (bl->handle != -1)
142 _call_java(4, 11, bl->handle, 0);
143 sfree(bl);
144}
145
146void nestedvm_blitter_save(void *handle, blitter *bl, int x, int y)
147{
148 frontend *fe = (frontend *)handle;
149 if (bl->handle == -1)
150 bl->handle = _call_java(4,10,bl->w, bl->h);
151 bl->x = x;
152 bl->y = y;
153 _call_java(8, bl->handle, x + fe->ox, y + fe->oy);
154}
155
156void nestedvm_blitter_load(void *handle, blitter *bl, int x, int y)
157{
158 frontend *fe = (frontend *)handle;
159 assert(bl->handle != -1);
160 if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) {
161 x = bl->x;
162 y = bl->y;
163 }
164 _call_java(9, bl->handle, x + fe->ox, y + fe->oy);
165}
166
167void nestedvm_end_draw(void *handle)
168{
169 _call_java(4,2,0,0);
170}
171
172char *nestedvm_text_fallback(void *handle, const char *const *strings,
173 int nstrings)
174{
175 /*
176 * We assume Java can cope with any UTF-8 likely to be emitted
177 * by a puzzle.
178 */
179 return dupstr(strings[0]);
180}
181
182const struct drawing_api nestedvm_drawing = {
183 nestedvm_draw_text,
184 nestedvm_draw_rect,
185 nestedvm_draw_line,
186 nestedvm_draw_poly,
187 nestedvm_draw_circle,
188 NULL, // draw_update,
189 nestedvm_clip,
190 nestedvm_unclip,
191 nestedvm_start_draw,
192 nestedvm_end_draw,
193 nestedvm_status_bar,
194 nestedvm_blitter_new,
195 nestedvm_blitter_free,
196 nestedvm_blitter_save,
197 nestedvm_blitter_load,
198 NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
199 NULL, NULL, /* line_width, line_dotted */
200 nestedvm_text_fallback,
201};
202
203int jcallback_key_event(int x, int y, int keyval)
204{
205 frontend *fe = (frontend *)_fe;
206 if (fe->ox == -1)
207 return 1;
208 if (keyval >= 0 &&
209 midend_process_key(fe->me, x - fe->ox, y - fe->oy, keyval) == PKR_QUIT)
210 return 42;
211 return 1;
212}
213
214int jcallback_resize(int width, int height)
215{
216 frontend *fe = (frontend *)_fe;
217 int x, y;
218 x = width;
219 y = height;
220 midend_size(fe->me, &x, &y, true, 1.0);
221 fe->ox = (width - x) / 2;
222 fe->oy = (height - y) / 2;
223 fe->w = x;
224 fe->h = y;
225 midend_force_redraw(fe->me);
226 return 0;
227}
228
229int jcallback_timer_func()
230{
231 frontend *fe = (frontend *)_fe;
232 if (fe->timer_active) {
233 struct timeval now;
234 float elapsed;
235 gettimeofday(&now, NULL);
236 elapsed = ((now.tv_usec - fe->last_time.tv_usec) * 0.000001F +
237 (now.tv_sec - fe->last_time.tv_sec));
238 midend_timer(fe->me, elapsed); /* may clear timer_active */
239 fe->last_time = now;
240 }
241 return fe->timer_active;
242}
243
244void deactivate_timer(frontend *fe)
245{
246 if (fe->timer_active)
247 _call_java(4, 13, 0, 0);
248 fe->timer_active = false;
249}
250
251void activate_timer(frontend *fe)
252{
253 if (!fe->timer_active) {
254 _call_java(4, 12, 0, 0);
255 gettimeofday(&fe->last_time, NULL);
256 }
257 fe->timer_active = true;
258}
259
260void jcallback_config_ok()
261{
262 frontend *fe = (frontend *)_fe;
263 const char *err;
264
265 err = midend_set_config(fe->me, fe->cfg_which, fe->cfg);
266
267 if (err)
268 _call_java(2, (int) "Error", (int)err, 1);
269 else {
270 fe->cfgret = true;
271 }
272}
273
274void jcallback_config_set_string(int item_ptr, int char_ptr) {
275 config_item *i = (config_item *)item_ptr;
276 char* newval = (char*) char_ptr;
277 assert(i->type == C_STRING);
278 sfree(i->u.string.sval);
279 i->u.string.sval = dupstr(newval);
280 free(newval);
281}
282
283void jcallback_config_set_boolean(int item_ptr, int selected) {
284 config_item *i = (config_item *)item_ptr;
285 assert(i->type == C_BOOLEAN);
286 i->u.boolean.bval = selected != 0 ? true : false;
287}
288
289void jcallback_config_set_choice(int item_ptr, int selected) {
290 config_item *i = (config_item *)item_ptr;
291 assert(i->type == C_CHOICES);
292 i->u.choices.selected = selected;
293}
294
295static bool get_config(frontend *fe, int which)
296{
297 char *title;
298 config_item *i;
299 fe->cfg = midend_get_config(fe->me, which, &title);
300 fe->cfg_which = which;
301 fe->cfgret = false;
302 _call_java(10, (int)title, 0, 0);
303 for (i = fe->cfg; i->type != C_END; i++) {
304 _call_java(5, (int)i, i->type, (int)i->name);
305 switch (i->type) {
306 case C_STRING:
307 _call_java(11, (int)i->u.string.sval, 0, 0);
308 break;
309 case C_BOOLEAN:
310 _call_java(11, 0, i->u.boolean.bval, 0);
311 break;
312 case C_CHOICES:
313 _call_java(11, (int)i->u.choices.choicenames,
314 i->u.choices.selected, 0);
315 break;
316 }
317 }
318 _call_java(12,0,0,0);
319 free_cfg(fe->cfg);
320 return fe->cfgret;
321}
322
323int jcallback_newgame_event(void)
324{
325 frontend *fe = (frontend *)_fe;
326 if (midend_process_key(fe->me, 0, 0, UI_NEWGAME) == PKR_QUIT)
327 return 42;
328 return 0;
329}
330
331int jcallback_undo_event(void)
332{
333 frontend *fe = (frontend *)_fe;
334 if (midend_process_key(fe->me, 0, 0, UI_UNDO) == PKR_QUIT)
335 return 42;
336 return 0;
337}
338
339int jcallback_redo_event(void)
340{
341 frontend *fe = (frontend *)_fe;
342 if (midend_process_key(fe->me, 0, 0, UI_REDO) == PKR_QUIT)
343 return 42;
344 return 0;
345}
346
347int jcallback_quit_event(void)
348{
349 frontend *fe = (frontend *)_fe;
350 if (midend_process_key(fe->me, 0, 0, UI_QUIT) == PKR_QUIT)
351 return 42;
352 return 0;
353}
354
355static void resize_fe(frontend *fe)
356{
357 int x, y;
358
359 x = INT_MAX;
360 y = INT_MAX;
361 midend_size(fe->me, &x, &y, false, 1.0);
362 _call_java(3, x, y, 0);
363}
364
365int jcallback_preset_event(int ptr_game_params)
366{
367 frontend *fe = (frontend *)_fe;
368 game_params *params =
369 (game_params *)ptr_game_params;
370
371 midend_set_params(fe->me, params);
372 midend_new_game(fe->me);
373 resize_fe(fe);
374 _call_java(13, midend_which_preset(fe->me), 0, 0);
375 return 0;
376}
377
378int jcallback_solve_event()
379{
380 frontend *fe = (frontend *)_fe;
381 const char *msg;
382
383 msg = midend_solve(fe->me);
384
385 if (msg)
386 _call_java(2, (int) "Error", (int)msg, 1);
387 return 0;
388}
389
390int jcallback_restart_event()
391{
392 frontend *fe = (frontend *)_fe;
393
394 midend_restart_game(fe->me);
395 return 0;
396}
397
398int jcallback_config_event(int which)
399{
400 frontend *fe = (frontend *)_fe;
401 _call_java(13, midend_which_preset(fe->me), 0, 0);
402 if (!get_config(fe, which))
403 return 0;
404 midend_new_game(fe->me);
405 resize_fe(fe);
406 _call_java(13, midend_which_preset(fe->me), 0, 0);
407 return 0;
408}
409
410int jcallback_about_event()
411{
412 char titlebuf[256];
413 char textbuf[1024];
414
415 sprintf(titlebuf, "About %.200s", thegame.name);
416 sprintf(textbuf,
417 "%.200s\n\n"
418 "from Simon Tatham's Portable Puzzle Collection\n\n"
419 "%.500s", thegame.name, ver);
420 _call_java(2, (int)&titlebuf, (int)&textbuf, 0);
421 return 0;
422}
423
424void preset_menu_populate(struct preset_menu *menu, int menuid)
425{
426 int i;
427
428 for (i = 0; i < menu->n_entries; i++) {
429 struct preset_menu_entry *entry = &menu->entries[i];
430 if (entry->params) {
431 _call_java(5, (int)entry->params, 0, 0);
432 _call_java(1, (int)entry->title, menuid, entry->id);
433 } else {
434 _call_java(5, 0, 0, 0);
435 _call_java(1, (int)entry->title, menuid, entry->id);
436 preset_menu_populate(entry->submenu, entry->id);
437 }
438 }
439}
440
441int main(int argc, char **argv)
442{
443 int i, n;
444 float* colours;
445
446 _fe = snew(frontend);
447 _fe->timer_active = false;
448 _fe->me = midend_new(_fe, &thegame, &nestedvm_drawing, _fe);
449 if (argc > 1)
450 midend_game_id(_fe->me, argv[1]); /* ignore failure */
451 midend_new_game(_fe->me);
452
453 {
454 struct preset_menu *menu;
455 int nids, topmenu;
456 menu = midend_get_presets(_fe->me, &nids);
457 topmenu = _call_java(1, 0, nids, 0);
458 preset_menu_populate(menu, topmenu);
459 }
460
461 colours = midend_colours(_fe->me, &n);
462 _fe->ox = -1;
463
464 _call_java(0, (int)thegame.name,
465 (thegame.can_configure ? 1 : 0) |
466 (midend_wants_statusbar(_fe->me) ? 2 : 0) |
467 (thegame.can_solve ? 4 : 0), n);
468 for (i = 0; i < n; i++) {
469 _call_java(1024+ i,
470 (int)(colours[i*3] * 0xFF),
471 (int)(colours[i*3+1] * 0xFF),
472 (int)(colours[i*3+2] * 0xFF));
473 }
474 resize_fe(_fe);
475
476 _call_java(13, midend_which_preset(_fe->me), 0, 0);
477
478 // Now pause the vm. The VM will be call()ed when
479 // an input event occurs.
480 _pause();
481
482 // shut down when the VM is resumed.
483 deactivate_timer(_fe);
484 midend_free(_fe->me);
485 return 0;
486}
diff --git a/apps/plugins/puzzles/src/no-icon.c b/apps/plugins/puzzles/src/no-icon.c
deleted file mode 100644
index 5091dca426..0000000000
--- a/apps/plugins/puzzles/src/no-icon.c
+++ /dev/null
@@ -1,10 +0,0 @@
1
2/*
3 * Dummy source file which replaces the files generated in the
4 * `icons' subdirectory, when they're absent.
5 */
6
7#include "gtk.h"
8
9const char *const *const xpm_icons[] = { 0 };
10const int n_xpm_icons = 0;
diff --git a/apps/plugins/puzzles/src/nullfe.c b/apps/plugins/puzzles/src/nullfe.c
deleted file mode 100644
index 9a57832b6e..0000000000
--- a/apps/plugins/puzzles/src/nullfe.c
+++ /dev/null
@@ -1,79 +0,0 @@
1/*
2 * nullfe.c: Null front-end code containing a bunch of boring stub
3 * functions. Used to ensure successful linking when building the
4 * various stand-alone solver binaries.
5 */
6
7#include <stdarg.h>
8
9#include "puzzles.h"
10
11void frontend_default_colour(frontend *fe, float *output) {}
12void get_random_seed(void **randseed, int *randseedsize)
13{ char *c = snewn(1, char); *c = 0; *randseed = c; *randseedsize = 1; }
14void deactivate_timer(frontend *fe) {}
15void activate_timer(frontend *fe) {}
16struct drawing { char dummy; };
17drawing *drawing_new(const drawing_api *api, midend *me, void *handle)
18{ return snew(drawing); }
19void drawing_free(drawing *dr) { sfree(dr); }
20void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize,
21 int align, int colour, const char *text) {}
22void draw_rect(drawing *dr, int x, int y, int w, int h, int colour) {}
23void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour) {}
24void draw_thick_line(drawing *dr, float thickness,
25 float x1, float y1, float x2, float y2, int colour) {}
26void draw_polygon(drawing *dr, const int *coords, int npoints,
27 int fillcolour, int outlinecolour) {}
28void draw_circle(drawing *dr, int cx, int cy, int radius,
29 int fillcolour, int outlinecolour) {}
30char *text_fallback(drawing *dr, const char *const *strings, int nstrings)
31{ return dupstr(strings[0]); }
32void clip(drawing *dr, int x, int y, int w, int h) {}
33void unclip(drawing *dr) {}
34void start_draw(drawing *dr) {}
35void draw_update(drawing *dr, int x, int y, int w, int h) {}
36void end_draw(drawing *dr) {}
37struct blitter { char dummy; };
38blitter *blitter_new(drawing *dr, int w, int h) { return snew(blitter); }
39void blitter_free(drawing *dr, blitter *bl) { sfree(bl); }
40void blitter_save(drawing *dr, blitter *bl, int x, int y) {}
41void blitter_load(drawing *dr, blitter *bl, int x, int y) {}
42int print_mono_colour(drawing *dr, int grey) { return 0; }
43int print_grey_colour(drawing *dr, float grey) { return 0; }
44int print_hatched_colour(drawing *dr, int hatch) { return 0; }
45int print_rgb_mono_colour(drawing *dr, float r, float g, float b, int grey)
46{ return 0; }
47int print_rgb_grey_colour(drawing *dr, float r, float g, float b, float grey)
48{ return 0; }
49int print_rgb_hatched_colour(drawing *dr, float r, float g, float b, int hatch)
50{ return 0; }
51void print_line_width(drawing *dr, int width) {}
52void print_line_dotted(drawing *dr, bool dotted) {}
53void status_bar(drawing *dr, const char *text) {}
54void document_add_puzzle(document *doc, const game *game, game_params *par,
55 game_ui *ui, game_state *st, game_state *st2) {}
56
57void fatal(const char *fmt, ...)
58{
59 va_list ap;
60
61 fprintf(stderr, "fatal error: ");
62
63 va_start(ap, fmt);
64 vfprintf(stderr, fmt, ap);
65 va_end(ap);
66
67 fprintf(stderr, "\n");
68 exit(1);
69}
70
71#ifdef DEBUGGING
72void debug_printf(const char *fmt, ...)
73{
74 va_list ap;
75 va_start(ap, fmt);
76 vfprintf(stdout, fmt, ap);
77 va_end(ap);
78}
79#endif
diff --git a/apps/plugins/puzzles/src/nullgame.c b/apps/plugins/puzzles/src/nullgame.c
deleted file mode 100644
index c1c2ed18fd..0000000000
--- a/apps/plugins/puzzles/src/nullgame.c
+++ /dev/null
@@ -1,263 +0,0 @@
1/*
2 * nullgame.c [FIXME]: Template defining the null game (in which no
3 * moves are permitted and nothing is ever drawn). This file exists
4 * solely as a basis for constructing new game definitions - it
5 * helps to have something which will compile from the word go and
6 * merely doesn't _do_ very much yet.
7 *
8 * Parts labelled FIXME actually want _removing_ (e.g. the dummy
9 * field in each of the required data structures, and this entire
10 * comment itself) when converting this source file into one
11 * describing a real game.
12 */
13
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <assert.h>
18#include <ctype.h>
19#ifdef NO_TGMATH_H
20# include <math.h>
21#else
22# include <tgmath.h>
23#endif
24
25#include "puzzles.h"
26
27enum {
28 COL_BACKGROUND,
29 NCOLOURS
30};
31
32struct game_params {
33 int FIXME;
34};
35
36struct game_state {
37 int FIXME;
38};
39
40static game_params *default_params(void)
41{
42 game_params *ret = snew(game_params);
43
44 ret->FIXME = 0;
45
46 return ret;
47}
48
49static bool game_fetch_preset(int i, char **name, game_params **params)
50{
51 return false;
52}
53
54static void free_params(game_params *params)
55{
56 sfree(params);
57}
58
59static game_params *dup_params(const game_params *params)
60{
61 game_params *ret = snew(game_params);
62 *ret = *params; /* structure copy */
63 return ret;
64}
65
66static void decode_params(game_params *params, char const *string)
67{
68}
69
70static char *encode_params(const game_params *params, bool full)
71{
72 return dupstr("FIXME");
73}
74
75static const char *validate_params(const game_params *params, bool full)
76{
77 return NULL;
78}
79
80static char *new_game_desc(const game_params *params, random_state *rs,
81 char **aux, bool interactive)
82{
83 return dupstr("FIXME");
84}
85
86static const char *validate_desc(const game_params *params, const char *desc)
87{
88 return NULL;
89}
90
91static game_state *new_game(midend *me, const game_params *params,
92 const char *desc)
93{
94 game_state *state = snew(game_state);
95
96 state->FIXME = 0;
97
98 return state;
99}
100
101static game_state *dup_game(const game_state *state)
102{
103 game_state *ret = snew(game_state);
104
105 ret->FIXME = state->FIXME;
106
107 return ret;
108}
109
110static void free_game(game_state *state)
111{
112 sfree(state);
113}
114
115static game_ui *new_ui(const game_state *state)
116{
117 return NULL;
118}
119
120static void free_ui(game_ui *ui)
121{
122}
123
124static void game_changed_state(game_ui *ui, const game_state *oldstate,
125 const game_state *newstate)
126{
127}
128
129struct game_drawstate {
130 int tilesize;
131 int FIXME;
132};
133
134static char *interpret_move(const game_state *state, game_ui *ui,
135 const game_drawstate *ds,
136 int x, int y, int button)
137{
138 return NULL;
139}
140
141static game_state *execute_move(const game_state *state, const char *move)
142{
143 return NULL;
144}
145
146/* ----------------------------------------------------------------------
147 * Drawing routines.
148 */
149
150static void game_compute_size(const game_params *params, int tilesize,
151 const game_ui *ui, int *x, int *y)
152{
153 *x = *y = 10 * tilesize; /* FIXME */
154}
155
156static void game_set_size(drawing *dr, game_drawstate *ds,
157 const game_params *params, int tilesize)
158{
159 ds->tilesize = tilesize;
160}
161
162static float *game_colours(frontend *fe, int *ncolours)
163{
164 float *ret = snewn(3 * NCOLOURS, float);
165
166 frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
167
168 *ncolours = NCOLOURS;
169 return ret;
170}
171
172static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
173{
174 struct game_drawstate *ds = snew(struct game_drawstate);
175
176 ds->tilesize = 0;
177 ds->FIXME = 0;
178
179 return ds;
180}
181
182static void game_free_drawstate(drawing *dr, game_drawstate *ds)
183{
184 sfree(ds);
185}
186
187static void game_redraw(drawing *dr, game_drawstate *ds,
188 const game_state *oldstate, const game_state *state,
189 int dir, const game_ui *ui,
190 float animtime, float flashtime)
191{
192}
193
194static float game_anim_length(const game_state *oldstate,
195 const game_state *newstate, int dir, game_ui *ui)
196{
197 return 0.0F;
198}
199
200static float game_flash_length(const game_state *oldstate,
201 const game_state *newstate, int dir, game_ui *ui)
202{
203 return 0.0F;
204}
205
206static void game_get_cursor_location(const game_ui *ui,
207 const game_drawstate *ds,
208 const game_state *state,
209 const game_params *params,
210 int *x, int *y, int *w, int *h)
211{
212}
213
214static int game_status(const game_state *state)
215{
216 return 0;
217}
218
219#ifdef COMBINED
220#define thegame nullgame
221#endif
222
223const struct game thegame = {
224 "Null Game", NULL, NULL,
225 default_params,
226 game_fetch_preset, NULL,
227 decode_params,
228 encode_params,
229 free_params,
230 dup_params,
231 false, NULL, NULL, /* configure, custom_params */
232 validate_params,
233 new_game_desc,
234 validate_desc,
235 new_game,
236 dup_game,
237 free_game,
238 false, NULL, /* solve */
239 false, NULL, NULL, /* can_format_as_text_now, text_format */
240 NULL, NULL, /* get_prefs, set_prefs */
241 new_ui,
242 free_ui,
243 NULL, /* encode_ui */
244 NULL, /* decode_ui */
245 NULL, /* game_request_keys */
246 game_changed_state,
247 NULL, /* current_key_label */
248 interpret_move,
249 execute_move,
250 20 /* FIXME */, game_compute_size, game_set_size,
251 game_colours,
252 game_new_drawstate,
253 game_free_drawstate,
254 game_redraw,
255 game_anim_length,
256 game_flash_length,
257 game_get_cursor_location,
258 game_status,
259 false, false, NULL, NULL, /* print_size, print */
260 false, /* wants_statusbar */
261 false, NULL, /* timing_state */
262 0, /* flags */
263};
diff --git a/apps/plugins/puzzles/src/osx-help.but b/apps/plugins/puzzles/src/osx-help.but
deleted file mode 100644
index fa45996aee..0000000000
--- a/apps/plugins/puzzles/src/osx-help.but
+++ /dev/null
@@ -1,14 +0,0 @@
1\# Additional Halibut fragment to set up the HTML output
2\# appropriately for MacOS online help.
3
4\cfg{html-head-end}{
5<style type="text/css">
6body \{ font-family: "Lucida Grande", Helvetica, Arial; font-size: 9pt \}
7h1 \{ font-size: 12pt \}
8h2 \{ font-size: 10pt \}
9h3 \{ font-size: 9pt \}
10h4 \{ font-size: 9pt \}
11h5 \{ font-size: 9pt \}
12h6 \{ font-size: 9pt \}
13</style>
14}
diff --git a/apps/plugins/puzzles/src/ps.c b/apps/plugins/puzzles/src/ps.c
deleted file mode 100644
index d0ea0ff2b5..0000000000
--- a/apps/plugins/puzzles/src/ps.c
+++ /dev/null
@@ -1,432 +0,0 @@
1/*
2 * ps.c: PostScript printing functions.
3 */
4
5#include <stdio.h>
6#include <stdarg.h>
7#include <string.h>
8#include <assert.h>
9
10#include "puzzles.h"
11
12struct psdata {
13 FILE *fp;
14 bool colour;
15 int ytop;
16 bool clipped;
17 float hatchthick, hatchspace;
18 int gamewidth, gameheight;
19 drawing *drawing;
20};
21
22static void ps_printf(psdata *ps, const char *fmt, ...)
23{
24 va_list ap;
25
26 va_start(ap, fmt);
27 vfprintf(ps->fp, fmt, ap);
28 va_end(ap);
29}
30
31static void ps_fill(psdata *ps, int colour)
32{
33 int hatch;
34 float r, g, b;
35
36 print_get_colour(ps->drawing, colour, ps->colour, &hatch, &r, &g, &b);
37
38 if (hatch < 0) {
39 if (ps->colour)
40 ps_printf(ps, "%g %g %g setrgbcolor fill\n", r, g, b);
41 else
42 ps_printf(ps, "%g setgray fill\n", r);
43 } else {
44 /* Clip to the region. */
45 ps_printf(ps, "gsave clip\n");
46 /* Hatch the entire game printing area. */
47 ps_printf(ps, "newpath\n");
48 if (hatch == HATCH_VERT || hatch == HATCH_PLUS)
49 ps_printf(ps, "0 %g %d {\n"
50 " 0 moveto 0 %d rlineto\n"
51 "} for\n", ps->hatchspace, ps->gamewidth,
52 ps->gameheight);
53 if (hatch == HATCH_HORIZ || hatch == HATCH_PLUS)
54 ps_printf(ps, "0 %g %d {\n"
55 " 0 exch moveto %d 0 rlineto\n"
56 "} for\n", ps->hatchspace, ps->gameheight,
57 ps->gamewidth);
58 if (hatch == HATCH_SLASH || hatch == HATCH_X)
59 ps_printf(ps, "%d %g %d {\n"
60 " 0 moveto %d dup rlineto\n"
61 "} for\n", -ps->gameheight, ps->hatchspace * ROOT2,
62 ps->gamewidth, max(ps->gamewidth, ps->gameheight));
63 if (hatch == HATCH_BACKSLASH || hatch == HATCH_X)
64 ps_printf(ps, "0 %g %d {\n"
65 " 0 moveto %d neg dup neg rlineto\n"
66 "} for\n", ps->hatchspace * ROOT2,
67 ps->gamewidth+ps->gameheight,
68 max(ps->gamewidth, ps->gameheight));
69 ps_printf(ps, "0 setgray %g setlinewidth stroke grestore\n",
70 ps->hatchthick);
71 }
72}
73
74static void ps_setcolour_internal(psdata *ps, int colour, const char *suffix)
75{
76 int hatch;
77 float r, g, b;
78
79 print_get_colour(ps->drawing, colour, ps->colour, &hatch, &r, &g, &b);
80
81 /*
82 * Stroking in hatched colours is not permitted.
83 */
84 assert(hatch < 0);
85
86 if (ps->colour)
87 ps_printf(ps, "%g %g %g setrgbcolor%s\n", r, g, b, suffix);
88 else
89 ps_printf(ps, "%g setgray%s\n", r, suffix);
90}
91
92static void ps_setcolour(psdata *ps, int colour)
93{
94 ps_setcolour_internal(ps, colour, "");
95}
96
97static void ps_stroke(psdata *ps, int colour)
98{
99 ps_setcolour_internal(ps, colour, " stroke");
100}
101
102static void ps_draw_text(void *handle, int x, int y, int fonttype,
103 int fontsize, int align, int colour,
104 const char *text)
105{
106 psdata *ps = (psdata *)handle;
107
108 y = ps->ytop - y;
109 ps_setcolour(ps, colour);
110 ps_printf(ps, "/%s findfont %d scalefont setfont\n",
111 fonttype == FONT_FIXED ? "Courier-L1" : "Helvetica-L1",
112 fontsize);
113 if (align & ALIGN_VCENTRE) {
114 ps_printf(ps, "newpath 0 0 moveto (X) true charpath flattenpath"
115 " pathbbox\n"
116 "3 -1 roll add 2 div %d exch sub %d exch moveto pop pop\n",
117 y, x);
118 } else {
119 ps_printf(ps, "%d %d moveto\n", x, y);
120 }
121 ps_printf(ps, "(");
122 while (*text) {
123 if (*text == '\\' || *text == '(' || *text == ')')
124 ps_printf(ps, "\\");
125 ps_printf(ps, "%c", *text);
126 text++;
127 }
128 ps_printf(ps, ") ");
129 if (align & (ALIGN_HCENTRE | ALIGN_HRIGHT))
130 ps_printf(ps, "dup stringwidth pop %sneg 0 rmoveto show\n",
131 (align & ALIGN_HCENTRE) ? "2 div " : "");
132 else
133 ps_printf(ps, "show\n");
134}
135
136static void ps_draw_rect(void *handle, int x, int y, int w, int h, int colour)
137{
138 psdata *ps = (psdata *)handle;
139
140 y = ps->ytop - y;
141 /*
142 * Offset by half a pixel for the exactness requirement.
143 */
144 ps_printf(ps, "newpath %g %g moveto %d 0 rlineto 0 %d rlineto"
145 " %d 0 rlineto closepath\n", x - 0.5, y + 0.5, w, -h, -w);
146 ps_fill(ps, colour);
147}
148
149static void ps_draw_line(void *handle, int x1, int y1, int x2, int y2,
150 int colour)
151{
152 psdata *ps = (psdata *)handle;
153
154 y1 = ps->ytop - y1;
155 y2 = ps->ytop - y2;
156 ps_printf(ps, "newpath %d %d moveto %d %d lineto\n", x1, y1, x2, y2);
157 ps_stroke(ps, colour);
158}
159
160static void ps_draw_polygon(void *handle, const int *coords, int npoints,
161 int fillcolour, int outlinecolour)
162{
163 psdata *ps = (psdata *)handle;
164
165 int i;
166
167 ps_printf(ps, "newpath %d %d moveto\n", coords[0], ps->ytop - coords[1]);
168
169 for (i = 1; i < npoints; i++)
170 ps_printf(ps, "%d %d lineto\n", coords[i*2], ps->ytop - coords[i*2+1]);
171
172 ps_printf(ps, "closepath\n");
173
174 if (fillcolour >= 0) {
175 ps_printf(ps, "gsave\n");
176 ps_fill(ps, fillcolour);
177 ps_printf(ps, "grestore\n");
178 }
179 ps_stroke(ps, outlinecolour);
180}
181
182static void ps_draw_circle(void *handle, int cx, int cy, int radius,
183 int fillcolour, int outlinecolour)
184{
185 psdata *ps = (psdata *)handle;
186
187 cy = ps->ytop - cy;
188
189 ps_printf(ps, "newpath %d %d %d 0 360 arc closepath\n", cx, cy, radius);
190
191 if (fillcolour >= 0) {
192 ps_printf(ps, "gsave\n");
193 ps_fill(ps, fillcolour);
194 ps_printf(ps, "grestore\n");
195 }
196 ps_stroke(ps, outlinecolour);
197}
198
199static void ps_unclip(void *handle)
200{
201 psdata *ps = (psdata *)handle;
202
203 assert(ps->clipped);
204 ps_printf(ps, "grestore\n");
205 ps->clipped = false;
206}
207
208static void ps_clip(void *handle, int x, int y, int w, int h)
209{
210 psdata *ps = (psdata *)handle;
211
212 if (ps->clipped)
213 ps_unclip(ps);
214
215 y = ps->ytop - y;
216 /*
217 * Offset by half a pixel for the exactness requirement.
218 */
219 ps_printf(ps, "gsave\n");
220 ps_printf(ps, "newpath %g %g moveto %d 0 rlineto 0 %d rlineto"
221 " %d 0 rlineto closepath\n", x - 0.5, y + 0.5, w, -h, -w);
222 ps_printf(ps, "clip\n");
223 ps->clipped = true;
224}
225
226static void ps_line_width(void *handle, float width)
227{
228 psdata *ps = (psdata *)handle;
229
230 ps_printf(ps, "%g setlinewidth\n", width);
231}
232
233static void ps_line_dotted(void *handle, bool dotted)
234{
235 psdata *ps = (psdata *)handle;
236
237 if (dotted) {
238 ps_printf(ps, "[ currentlinewidth 3 mul ] 0 setdash\n");
239 } else {
240 ps_printf(ps, "[ ] 0 setdash\n");
241 }
242}
243
244static char *ps_text_fallback(void *handle, const char *const *strings,
245 int nstrings)
246{
247 /*
248 * We can handle anything in ISO 8859-1, and we'll manually
249 * translate it out of UTF-8 for the purpose.
250 */
251 int i, maxlen;
252 char *ret;
253
254 maxlen = 0;
255 for (i = 0; i < nstrings; i++) {
256 int len = strlen(strings[i]);
257 if (maxlen < len) maxlen = len;
258 }
259
260 ret = snewn(maxlen + 1, char);
261
262 for (i = 0; i < nstrings; i++) {
263 const char *p = strings[i];
264 char *q = ret;
265
266 while (*p) {
267 int c = (unsigned char)*p++;
268 if (c < 0x80) {
269 *q++ = c; /* ASCII */
270 } else if ((c == 0xC2 || c == 0xC3) && (*p & 0xC0) == 0x80) {
271 *q++ = (c << 6) | (*p++ & 0x3F); /* top half of 8859-1 */
272 } else {
273 break;
274 }
275 }
276
277 if (!*p) {
278 *q = '\0';
279 return ret;
280 }
281 }
282
283 assert(!"Should never reach here");
284 return NULL;
285}
286
287static void ps_begin_doc(void *handle, int pages)
288{
289 psdata *ps = (psdata *)handle;
290
291 fputs("%!PS-Adobe-3.0\n", ps->fp);
292 fputs("%%Creator: Simon Tatham's Portable Puzzle Collection\n", ps->fp);
293 fputs("%%DocumentData: Clean7Bit\n", ps->fp);
294 fputs("%%LanguageLevel: 1\n", ps->fp);
295 fprintf(ps->fp, "%%%%Pages: %d\n", pages);
296 fputs("%%DocumentNeededResources:\n", ps->fp);
297 fputs("%%+ font Helvetica\n", ps->fp);
298 fputs("%%+ font Courier\n", ps->fp);
299 fputs("%%EndComments\n", ps->fp);
300 fputs("%%BeginSetup\n", ps->fp);
301 fputs("%%IncludeResource: font Helvetica\n", ps->fp);
302 fputs("%%IncludeResource: font Courier\n", ps->fp);
303 fputs("%%EndSetup\n", ps->fp);
304 fputs("%%BeginProlog\n", ps->fp);
305 /*
306 * Re-encode Helvetica and Courier into ISO-8859-1, which gives
307 * us times and divide signs - and also (according to the
308 * Language Reference Manual) a bonus in that the ASCII '-' code
309 * point now points to a minus sign instead of a hyphen.
310 */
311 fputs("/Helvetica findfont " /* get the font dictionary */
312 "dup maxlength dict dup begin " /* create and open a new dict */
313 "exch " /* move the original font to top of stack */
314 "{1 index /FID ne {def} {pop pop} ifelse} forall "
315 /* copy everything except FID */
316 "/Encoding ISOLatin1Encoding def "
317 /* set the thing we actually wanted to change */
318 "/FontName /Helvetica-L1 def " /* set a new font name */
319 "FontName end exch definefont" /* and define the font */
320 "\n", ps->fp);
321 fputs("/Courier findfont " /* get the font dictionary */
322 "dup maxlength dict dup begin " /* create and open a new dict */
323 "exch " /* move the original font to top of stack */
324 "{1 index /FID ne {def} {pop pop} ifelse} forall "
325 /* copy everything except FID */
326 "/Encoding ISOLatin1Encoding def "
327 /* set the thing we actually wanted to change */
328 "/FontName /Courier-L1 def " /* set a new font name */
329 "FontName end exch definefont" /* and define the font */
330 "\n", ps->fp);
331 fputs("%%EndProlog\n", ps->fp);
332}
333
334static void ps_begin_page(void *handle, int number)
335{
336 psdata *ps = (psdata *)handle;
337
338 fprintf(ps->fp, "%%%%Page: %d %d\ngsave save\n%g dup scale\n",
339 number, number, 72.0 / 25.4);
340}
341
342static void ps_begin_puzzle(void *handle, float xm, float xc,
343 float ym, float yc, int pw, int ph, float wmm)
344{
345 psdata *ps = (psdata *)handle;
346
347 fprintf(ps->fp, "gsave\n"
348 "clippath flattenpath pathbbox pop pop translate\n"
349 "clippath flattenpath pathbbox 4 2 roll pop pop\n"
350 "exch %g mul %g add exch dup %g mul %g add sub translate\n"
351 "%g dup scale\n"
352 "0 -%d translate\n", xm, xc, ym, yc, wmm/pw, ph);
353 ps->ytop = ph;
354 ps->clipped = false;
355 ps->gamewidth = pw;
356 ps->gameheight = ph;
357 ps->hatchthick = 0.2 * pw / wmm;
358 ps->hatchspace = 1.0 * pw / wmm;
359}
360
361static void ps_end_puzzle(void *handle)
362{
363 psdata *ps = (psdata *)handle;
364
365 fputs("grestore\n", ps->fp);
366}
367
368static void ps_end_page(void *handle, int number)
369{
370 psdata *ps = (psdata *)handle;
371
372 fputs("restore grestore showpage\n", ps->fp);
373}
374
375static void ps_end_doc(void *handle)
376{
377 psdata *ps = (psdata *)handle;
378
379 fputs("%%EOF\n", ps->fp);
380}
381
382static const struct drawing_api ps_drawing = {
383 ps_draw_text,
384 ps_draw_rect,
385 ps_draw_line,
386 ps_draw_polygon,
387 ps_draw_circle,
388 NULL /* draw_update */,
389 ps_clip,
390 ps_unclip,
391 NULL /* start_draw */,
392 NULL /* end_draw */,
393 NULL /* status_bar */,
394 NULL /* blitter_new */,
395 NULL /* blitter_free */,
396 NULL /* blitter_save */,
397 NULL /* blitter_load */,
398 ps_begin_doc,
399 ps_begin_page,
400 ps_begin_puzzle,
401 ps_end_puzzle,
402 ps_end_page,
403 ps_end_doc,
404 ps_line_width,
405 ps_line_dotted,
406 ps_text_fallback,
407};
408
409psdata *ps_init(FILE *outfile, bool colour)
410{
411 psdata *ps = snew(psdata);
412
413 ps->fp = outfile;
414 ps->colour = colour;
415 ps->ytop = 0;
416 ps->clipped = false;
417 ps->hatchthick = ps->hatchspace = ps->gamewidth = ps->gameheight = 0;
418 ps->drawing = drawing_new(&ps_drawing, NULL, ps);
419
420 return ps;
421}
422
423void ps_free(psdata *ps)
424{
425 drawing_free(ps->drawing);
426 sfree(ps);
427}
428
429drawing *ps_drawing_api(psdata *ps)
430{
431 return ps->drawing;
432}
diff --git a/apps/plugins/puzzles/src/windows.c b/apps/plugins/puzzles/src/windows.c
deleted file mode 100644
index 5273e17842..0000000000
--- a/apps/plugins/puzzles/src/windows.c
+++ /dev/null
@@ -1,3458 +0,0 @@
1/*
2 * windows.c: Windows front end for my puzzle collection.
3 */
4
5#include <windows.h>
6#include <commctrl.h>
7#ifndef NO_HTMLHELP
8#include <htmlhelp.h>
9#endif /* NO_HTMLHELP */
10#include <io.h>
11
12#include <stdio.h>
13#include <assert.h>
14#include <ctype.h>
15#include <stdarg.h>
16#include <stdlib.h>
17#include <limits.h>
18#include <time.h>
19
20#include "puzzles.h"
21
22#define IDM_NEW 0x0010
23#define IDM_RESTART 0x0020
24#define IDM_UNDO 0x0030
25#define IDM_REDO 0x0040
26#define IDM_COPY 0x0050
27#define IDM_SOLVE 0x0060
28#define IDM_QUIT 0x0070
29#define IDM_CONFIG 0x0080
30#define IDM_DESC 0x0090
31#define IDM_SEED 0x00A0
32#define IDM_HELPC 0x00B0
33#define IDM_GAMEHELP 0x00C0
34#define IDM_ABOUT 0x00D0
35#define IDM_SAVE 0x00E0
36#define IDM_LOAD 0x00F0
37#define IDM_PRINT 0x0100
38#define IDM_PREFS 0x0110
39
40/* Menu items for preset game_params go up from IDM_PRESET_BASE in
41 * steps of MENUITEM_STEP = 0x20. Menu items for selecting different
42 * games (in -DCOMBINED mode) go up from IDM_GAME_BASE similarly. */
43#define IDM_PRESET_BASE 0x0120
44#define IDM_GAME_BASE 0x0130
45#define MENUITEM_STEP 0x0020
46
47#define HELP_FILE_NAME "puzzles.hlp"
48#define HELP_CNT_NAME "puzzles.cnt"
49#ifndef NO_HTMLHELP
50#define CHM_FILE_NAME "puzzles.chm"
51#endif /* NO_HTMLHELP */
52
53#ifndef NO_HTMLHELP
54typedef HWND (CALLBACK *htmlhelp_t)(HWND, LPCSTR, UINT, DWORD);
55static htmlhelp_t htmlhelp;
56static HINSTANCE hh_dll;
57#endif /* NO_HTMLHELP */
58enum { NONE, HLP, CHM } help_type;
59char *help_path;
60bool help_has_contents;
61
62#ifndef FILENAME_MAX
63#define FILENAME_MAX (260)
64#endif
65
66#ifndef HGDI_ERROR
67#define HGDI_ERROR ((HANDLE)GDI_ERROR)
68#endif
69
70#ifdef COMBINED
71#define CLASSNAME "Puzzles"
72#else
73#define CLASSNAME thegame.name
74#endif
75
76#ifdef DEBUGGING
77static FILE *debug_fp = NULL;
78static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
79static int debug_got_console = 0;
80
81static void dputs(char *buf)
82{
83 /*DWORD dw;
84
85 if (!debug_got_console) {
86 if (AllocConsole()) {
87 debug_got_console = 1;
88 debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
89 }
90 }
91 if (!debug_fp) {
92 debug_fp = fopen("debug.log", "w");
93 }
94
95 if (debug_hdl != INVALID_HANDLE_VALUE) {
96 WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
97 }
98 if (debug_fp) {
99 fputs(buf, debug_fp);
100 fflush(debug_fp);
101 }*/
102 OutputDebugString(buf);
103}
104
105void debug_printf(const char *fmt, ...)
106{
107 char buf[4096];
108 va_list ap;
109 static int debugging = -1;
110
111 if (debugging == -1)
112 debugging = getenv_bool("DEBUG_PUZZLES", false);
113
114 if (debugging) {
115 va_start(ap, fmt);
116 _vsnprintf(buf, 4095, fmt, ap);
117 dputs(buf);
118 va_end(ap);
119 }
120}
121#endif
122
123#define WINFLAGS (WS_OVERLAPPEDWINDOW &~ \
124 (WS_MAXIMIZEBOX | WS_OVERLAPPED))
125
126static void new_game_size(frontend *fe, float scale);
127static void load_prefs(midend *me);
128static char *save_prefs(midend *me);
129
130struct font {
131 HFONT font;
132 int type;
133 int size;
134};
135
136struct cfg_aux {
137 int ctlid;
138};
139
140struct blitter {
141 HBITMAP bitmap;
142 frontend *fe;
143 int x, y, w, h;
144};
145
146enum { CFG_PRINT = CFG_FRONTEND_SPECIFIC };
147
148struct preset_menuitemref {
149 HMENU which_menu;
150 int item_index;
151};
152
153struct frontend {
154 const game *game;
155 midend *me;
156 HWND hwnd, statusbar, cfgbox;
157 HINSTANCE inst;
158 HBITMAP bitmap, prevbm;
159 RECT bitmapPosition; /* game bitmap position within game window */
160 HDC hdc;
161 COLORREF *colours;
162 HBRUSH *brushes;
163 HPEN *pens;
164 HRGN clip;
165 HMENU gamemenu, typemenu;
166 UINT timer;
167 DWORD timer_last_tickcount;
168 struct preset_menu *preset_menu;
169 struct preset_menuitemref *preset_menuitems;
170 int n_preset_menuitems;
171 struct font *fonts;
172 int nfonts, fontsize;
173 config_item *cfg;
174 struct cfg_aux *cfgaux;
175 int cfg_which, dlg_done;
176 HFONT cfgfont;
177 HBRUSH oldbr;
178 HPEN oldpen;
179 bool help_running;
180 enum { DRAWING, PRINTING, NOTHING } drawstatus;
181 DOCINFO di;
182 int printcount, printw, printh;
183 bool printsolns, printcurr, printcolour;
184 float printscale;
185 int printoffsetx, printoffsety;
186 float printpixelscale;
187 int fontstart;
188 int linewidth;
189 bool linedotted;
190 drawing *dr;
191 int xmin, ymin;
192 float puzz_scale;
193};
194
195void frontend_free(frontend *fe)
196{
197 midend_free(fe->me);
198
199 sfree(fe->colours);
200 sfree(fe->brushes);
201 sfree(fe->pens);
202 sfree(fe->fonts);
203
204 sfree(fe);
205}
206
207static void update_type_menu_tick(frontend *fe);
208static void update_copy_menu_greying(frontend *fe);
209
210void fatal(const char *fmt, ...)
211{
212 char buf[2048];
213 va_list ap;
214
215 va_start(ap, fmt);
216 vsprintf(buf, fmt, ap);
217 va_end(ap);
218
219 MessageBox(NULL, buf, "Fatal error", MB_ICONEXCLAMATION | MB_OK);
220
221 exit(1);
222}
223
224char *geterrstr(void)
225{
226 LPVOID lpMsgBuf;
227 DWORD dw = GetLastError();
228 char *ret;
229
230 FormatMessage(
231 FORMAT_MESSAGE_ALLOCATE_BUFFER |
232 FORMAT_MESSAGE_FROM_SYSTEM,
233 NULL,
234 dw,
235 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
236 (LPTSTR) &lpMsgBuf,
237 0, NULL );
238
239 ret = dupstr(lpMsgBuf);
240
241 LocalFree(lpMsgBuf);
242
243 return ret;
244}
245
246void get_random_seed(void **randseed, int *randseedsize)
247{
248 SYSTEMTIME *st = snew(SYSTEMTIME);
249
250 GetLocalTime(st);
251
252 *randseed = (void *)st;
253 *randseedsize = sizeof(SYSTEMTIME);
254}
255
256static void win_status_bar(void *handle, const char *text)
257{
258 frontend *fe = (frontend *)handle;
259
260 SetWindowText(fe->statusbar, text);
261}
262
263static blitter *win_blitter_new(void *handle, int w, int h)
264{
265 blitter *bl = snew(blitter);
266
267 memset(bl, 0, sizeof(blitter));
268 bl->w = w;
269 bl->h = h;
270 bl->bitmap = 0;
271
272 return bl;
273}
274
275static void win_blitter_free(void *handle, blitter *bl)
276{
277 if (bl->bitmap) DeleteObject(bl->bitmap);
278 sfree(bl);
279}
280
281static void blitter_mkbitmap(frontend *fe, blitter *bl)
282{
283 HDC hdc = GetDC(fe->hwnd);
284 bl->bitmap = CreateCompatibleBitmap(hdc, bl->w, bl->h);
285 ReleaseDC(fe->hwnd, hdc);
286}
287
288/* BitBlt(dstDC, dstX, dstY, dstW, dstH, srcDC, srcX, srcY, dType) */
289
290static void win_blitter_save(void *handle, blitter *bl, int x, int y)
291{
292 frontend *fe = (frontend *)handle;
293 HDC hdc_win, hdc_blit;
294 HBITMAP prev_blit;
295
296 assert(fe->drawstatus == DRAWING);
297
298 if (!bl->bitmap) blitter_mkbitmap(fe, bl);
299
300 bl->x = x; bl->y = y;
301
302 hdc_win = GetDC(fe->hwnd);
303 hdc_blit = CreateCompatibleDC(hdc_win);
304 if (!hdc_blit) fatal("hdc_blit failed: 0x%x", GetLastError());
305
306 prev_blit = SelectObject(hdc_blit, bl->bitmap);
307 if (prev_blit == NULL || prev_blit == HGDI_ERROR)
308 fatal("SelectObject for hdc_main failed: 0x%x", GetLastError());
309
310 if (!BitBlt(hdc_blit, 0, 0, bl->w, bl->h,
311 fe->hdc, x, y, SRCCOPY))
312 fatal("BitBlt failed: 0x%x", GetLastError());
313
314 SelectObject(hdc_blit, prev_blit);
315 DeleteDC(hdc_blit);
316 ReleaseDC(fe->hwnd, hdc_win);
317}
318
319static void win_blitter_load(void *handle, blitter *bl, int x, int y)
320{
321 frontend *fe = (frontend *)handle;
322 HDC hdc_win, hdc_blit;
323 HBITMAP prev_blit;
324
325 assert(fe->drawstatus == DRAWING);
326
327 assert(bl->bitmap); /* we should always have saved before loading */
328
329 if (x == BLITTER_FROMSAVED) x = bl->x;
330 if (y == BLITTER_FROMSAVED) y = bl->y;
331
332 hdc_win = GetDC(fe->hwnd);
333 hdc_blit = CreateCompatibleDC(hdc_win);
334
335 prev_blit = SelectObject(hdc_blit, bl->bitmap);
336
337 BitBlt(fe->hdc, x, y, bl->w, bl->h,
338 hdc_blit, 0, 0, SRCCOPY);
339
340 SelectObject(hdc_blit, prev_blit);
341 DeleteDC(hdc_blit);
342 ReleaseDC(fe->hwnd, hdc_win);
343}
344
345void frontend_default_colour(frontend *fe, float *output)
346{
347 DWORD c = GetSysColor(COLOR_MENU); /* ick */
348
349 output[0] = (float)(GetRValue(c) / 255.0);
350 output[1] = (float)(GetGValue(c) / 255.0);
351 output[2] = (float)(GetBValue(c) / 255.0);
352}
353
354static POINT win_transform_point(frontend *fe, int x, int y)
355{
356 POINT ret;
357
358 assert(fe->drawstatus != NOTHING);
359
360 if (fe->drawstatus == PRINTING) {
361 ret.x = (int)(fe->printoffsetx + fe->printpixelscale * x);
362 ret.y = (int)(fe->printoffsety + fe->printpixelscale * y);
363 } else {
364 ret.x = x;
365 ret.y = y;
366 }
367
368 return ret;
369}
370
371static void win_text_colour(frontend *fe, int colour)
372{
373 assert(fe->drawstatus != NOTHING);
374
375 if (fe->drawstatus == PRINTING) {
376 int hatch;
377 float r, g, b;
378 print_get_colour(fe->dr, colour, fe->printcolour, &hatch, &r, &g, &b);
379
380 /*
381 * Displaying text in hatched colours is not permitted.
382 */
383 assert(hatch < 0);
384
385 SetTextColor(fe->hdc, RGB(r * 255, g * 255, b * 255));
386 } else {
387 SetTextColor(fe->hdc, fe->colours[colour]);
388 }
389}
390
391static void win_set_brush(frontend *fe, int colour)
392{
393 HBRUSH br;
394 assert(fe->drawstatus != NOTHING);
395
396 if (fe->drawstatus == PRINTING) {
397 int hatch;
398 float r, g, b;
399 print_get_colour(fe->dr, colour, fe->printcolour, &hatch, &r, &g, &b);
400
401 if (hatch < 0) {
402 br = CreateSolidBrush(RGB(r * 255, g * 255, b * 255));
403 } else {
404 br = CreateHatchBrush(hatch == HATCH_BACKSLASH ? HS_FDIAGONAL :
405 hatch == HATCH_SLASH ? HS_BDIAGONAL :
406 hatch == HATCH_HORIZ ? HS_HORIZONTAL :
407 hatch == HATCH_VERT ? HS_VERTICAL :
408 hatch == HATCH_PLUS ? HS_CROSS :
409 /* hatch == HATCH_X ? */ HS_DIAGCROSS,
410 RGB(0,0,0));
411 }
412 } else {
413 br = fe->brushes[colour];
414 }
415 fe->oldbr = SelectObject(fe->hdc, br);
416}
417
418static void win_reset_brush(frontend *fe)
419{
420 HBRUSH br;
421
422 assert(fe->drawstatus != NOTHING);
423
424 br = SelectObject(fe->hdc, fe->oldbr);
425 if (fe->drawstatus == PRINTING)
426 DeleteObject(br);
427}
428
429static void win_set_pen(frontend *fe, int colour, bool thin)
430{
431 HPEN pen;
432 assert(fe->drawstatus != NOTHING);
433
434 if (fe->drawstatus == PRINTING) {
435 int hatch;
436 float r, g, b;
437 int width = thin ? 0 : fe->linewidth;
438
439 if (fe->linedotted)
440 width = 0;
441
442 print_get_colour(fe->dr, colour, fe->printcolour, &hatch, &r, &g, &b);
443 /*
444 * Stroking in hatched colours is not permitted.
445 */
446 assert(hatch < 0);
447 pen = CreatePen(fe->linedotted ? PS_DOT : PS_SOLID,
448 width, RGB(r * 255, g * 255, b * 255));
449 } else {
450 pen = fe->pens[colour];
451 }
452 fe->oldpen = SelectObject(fe->hdc, pen);
453}
454
455static void win_reset_pen(frontend *fe)
456{
457 HPEN pen;
458
459 assert(fe->drawstatus != NOTHING);
460
461 pen = SelectObject(fe->hdc, fe->oldpen);
462 if (fe->drawstatus == PRINTING)
463 DeleteObject(pen);
464}
465
466static void win_clip(void *handle, int x, int y, int w, int h)
467{
468 frontend *fe = (frontend *)handle;
469 POINT p, q;
470
471 if (fe->drawstatus == NOTHING)
472 return;
473
474 p = win_transform_point(fe, x, y);
475 q = win_transform_point(fe, x+w, y+h);
476 IntersectClipRect(fe->hdc, p.x, p.y, q.x, q.y);
477}
478
479static void win_unclip(void *handle)
480{
481 frontend *fe = (frontend *)handle;
482
483 if (fe->drawstatus == NOTHING)
484 return;
485
486 SelectClipRgn(fe->hdc, NULL);
487}
488
489static void win_draw_text(void *handle, int x, int y, int fonttype,
490 int fontsize, int align, int colour,
491 const char *text)
492{
493 frontend *fe = (frontend *)handle;
494 POINT xy;
495 int i;
496 LOGFONT lf;
497
498 if (fe->drawstatus == NOTHING)
499 return;
500
501 if (fe->drawstatus == PRINTING)
502 fontsize = (int)(fontsize * fe->printpixelscale);
503
504 xy = win_transform_point(fe, x, y);
505
506 /*
507 * Find or create the font.
508 */
509 for (i = fe->fontstart; i < fe->nfonts; i++)
510 if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
511 break;
512
513 if (i == fe->nfonts) {
514 if (fe->fontsize <= fe->nfonts) {
515 fe->fontsize = fe->nfonts + 10;
516 fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
517 }
518
519 fe->nfonts++;
520
521 fe->fonts[i].type = fonttype;
522 fe->fonts[i].size = fontsize;
523
524 memset (&lf, 0, sizeof(LOGFONT));
525 lf.lfHeight = -fontsize;
526 lf.lfWeight = (fe->drawstatus == PRINTING ? 0 : FW_BOLD);
527 lf.lfCharSet = DEFAULT_CHARSET;
528 lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
529 lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
530 lf.lfQuality = DEFAULT_QUALITY;
531 lf.lfPitchAndFamily = (fonttype == FONT_FIXED ?
532 FIXED_PITCH | FF_DONTCARE :
533 VARIABLE_PITCH | FF_SWISS);
534
535 fe->fonts[i].font = CreateFontIndirect(&lf);
536 }
537
538 /*
539 * Position and draw the text.
540 */
541 {
542 HFONT oldfont;
543 TEXTMETRIC tm;
544 SIZE size;
545 WCHAR wText[256];
546 MultiByteToWideChar (CP_UTF8, 0, text, -1, wText, 256);
547
548 oldfont = SelectObject(fe->hdc, fe->fonts[i].font);
549 if (GetTextMetrics(fe->hdc, &tm)) {
550 if (align & ALIGN_VCENTRE)
551 xy.y -= (tm.tmAscent+tm.tmDescent)/2;
552 else
553 xy.y -= tm.tmAscent;
554 }
555 if (GetTextExtentPoint32W(fe->hdc, wText, wcslen(wText), &size))
556 {
557 if (align & ALIGN_HCENTRE)
558 xy.x -= size.cx / 2;
559 else if (align & ALIGN_HRIGHT)
560 xy.x -= size.cx;
561 }
562 SetBkMode(fe->hdc, TRANSPARENT);
563 win_text_colour(fe, colour);
564 ExtTextOutW(fe->hdc, xy.x, xy.y, 0, NULL, wText, wcslen(wText), NULL);
565 SelectObject(fe->hdc, oldfont);
566 }
567}
568
569static void win_draw_rect(void *handle, int x, int y, int w, int h, int colour)
570{
571 frontend *fe = (frontend *)handle;
572 POINT p, q;
573
574 if (fe->drawstatus == NOTHING)
575 return;
576
577 if (fe->drawstatus == DRAWING && w == 1 && h == 1) {
578 /*
579 * Rectangle() appears to get uppity if asked to draw a 1x1
580 * rectangle, presumably on the grounds that that's beneath
581 * its dignity and you ought to be using SetPixel instead.
582 * So I will.
583 */
584 SetPixel(fe->hdc, x, y, fe->colours[colour]);
585 } else {
586 win_set_brush(fe, colour);
587 win_set_pen(fe, colour, true);
588 p = win_transform_point(fe, x, y);
589 q = win_transform_point(fe, x+w, y+h);
590 Rectangle(fe->hdc, p.x, p.y, q.x, q.y);
591 win_reset_brush(fe);
592 win_reset_pen(fe);
593 }
594}
595
596static void win_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
597{
598 frontend *fe = (frontend *)handle;
599 POINT pp[2];
600
601 if (fe->drawstatus == NOTHING)
602 return;
603
604 win_set_pen(fe, colour, false);
605 pp[0] = win_transform_point(fe, x1, y1);
606 pp[1] = win_transform_point(fe, x2, y2);
607 Polyline(fe->hdc, pp, 2);
608 if (fe->drawstatus == DRAWING)
609 SetPixel(fe->hdc, pp[1].x, pp[1].y, fe->colours[colour]);
610 win_reset_pen(fe);
611}
612
613static void win_draw_circle(void *handle, int cx, int cy, int radius,
614 int fillcolour, int outlinecolour)
615{
616 frontend *fe = (frontend *)handle;
617 POINT p, q;
618
619 assert(outlinecolour >= 0);
620
621 if (fe->drawstatus == NOTHING)
622 return;
623
624 if (fillcolour >= 0)
625 win_set_brush(fe, fillcolour);
626 else
627 fe->oldbr = SelectObject(fe->hdc, GetStockObject(NULL_BRUSH));
628
629 win_set_pen(fe, outlinecolour, false);
630 p = win_transform_point(fe, cx - radius, cy - radius);
631 q = win_transform_point(fe, cx + radius, cy + radius);
632 Ellipse(fe->hdc, p.x, p.y, q.x+1, q.y+1);
633 win_reset_brush(fe);
634 win_reset_pen(fe);
635}
636
637static void win_draw_polygon(void *handle, const int *coords, int npoints,
638 int fillcolour, int outlinecolour)
639{
640 frontend *fe = (frontend *)handle;
641 POINT *pts;
642 int i;
643
644 if (fe->drawstatus == NOTHING)
645 return;
646
647 pts = snewn(npoints+1, POINT);
648
649 for (i = 0; i <= npoints; i++) {
650 int j = (i < npoints ? i : 0);
651 pts[i] = win_transform_point(fe, coords[j*2], coords[j*2+1]);
652 }
653
654 assert(outlinecolour >= 0);
655
656 if (fillcolour >= 0) {
657 win_set_brush(fe, fillcolour);
658 win_set_pen(fe, outlinecolour, false);
659 Polygon(fe->hdc, pts, npoints);
660 win_reset_brush(fe);
661 win_reset_pen(fe);
662 } else {
663 win_set_pen(fe, outlinecolour, false);
664 Polyline(fe->hdc, pts, npoints+1);
665 win_reset_pen(fe);
666 }
667
668 sfree(pts);
669}
670
671static void win_start_draw(void *handle)
672{
673 frontend *fe = (frontend *)handle;
674 HDC hdc_win;
675
676 assert(fe->drawstatus == NOTHING);
677
678 hdc_win = GetDC(fe->hwnd);
679 fe->hdc = CreateCompatibleDC(hdc_win);
680 fe->prevbm = SelectObject(fe->hdc, fe->bitmap);
681 ReleaseDC(fe->hwnd, hdc_win);
682 fe->clip = NULL;
683 SetMapMode(fe->hdc, MM_TEXT);
684 fe->drawstatus = DRAWING;
685}
686
687static void win_draw_update(void *handle, int x, int y, int w, int h)
688{
689 frontend *fe = (frontend *)handle;
690 RECT r;
691
692 if (fe->drawstatus != DRAWING)
693 return;
694
695 r.left = x;
696 r.top = y;
697 r.right = x + w;
698 r.bottom = y + h;
699
700 OffsetRect(&r, fe->bitmapPosition.left, fe->bitmapPosition.top);
701 InvalidateRect(fe->hwnd, &r, false);
702}
703
704static void win_end_draw(void *handle)
705{
706 frontend *fe = (frontend *)handle;
707 assert(fe->drawstatus == DRAWING);
708 SelectObject(fe->hdc, fe->prevbm);
709 DeleteDC(fe->hdc);
710 if (fe->clip) {
711 DeleteObject(fe->clip);
712 fe->clip = NULL;
713 }
714 fe->drawstatus = NOTHING;
715}
716
717static void win_line_width(void *handle, float width)
718{
719 frontend *fe = (frontend *)handle;
720
721 assert(fe->drawstatus != DRAWING);
722 if (fe->drawstatus == NOTHING)
723 return;
724
725 fe->linewidth = (int)(width * fe->printpixelscale);
726}
727
728static void win_line_dotted(void *handle, bool dotted)
729{
730 frontend *fe = (frontend *)handle;
731
732 assert(fe->drawstatus != DRAWING);
733 if (fe->drawstatus == NOTHING)
734 return;
735
736 fe->linedotted = dotted;
737}
738
739static void win_begin_doc(void *handle, int pages)
740{
741 frontend *fe = (frontend *)handle;
742
743 assert(fe->drawstatus != DRAWING);
744 if (fe->drawstatus == NOTHING)
745 return;
746
747 if (StartDoc(fe->hdc, &fe->di) <= 0) {
748 char *e = geterrstr();
749 MessageBox(fe->hwnd, e, "Error starting to print",
750 MB_ICONERROR | MB_OK);
751 sfree(e);
752 fe->drawstatus = NOTHING;
753 }
754
755 /*
756 * Push a marker on the font stack so that we won't use the
757 * same fonts for printing and drawing. (This is because
758 * drawing seems to look generally better in bold, but printing
759 * is better not in bold.)
760 */
761 fe->fontstart = fe->nfonts;
762}
763
764static void win_begin_page(void *handle, int number)
765{
766 frontend *fe = (frontend *)handle;
767
768 assert(fe->drawstatus != DRAWING);
769 if (fe->drawstatus == NOTHING)
770 return;
771
772 if (StartPage(fe->hdc) <= 0) {
773 char *e = geterrstr();
774 MessageBox(fe->hwnd, e, "Error starting a page",
775 MB_ICONERROR | MB_OK);
776 sfree(e);
777 fe->drawstatus = NOTHING;
778 }
779}
780
781static void win_begin_puzzle(void *handle, float xm, float xc,
782 float ym, float yc, int pw, int ph, float wmm)
783{
784 frontend *fe = (frontend *)handle;
785 int ppw, pph, pox, poy;
786 float mmpw, mmph, mmox, mmoy;
787 float scale;
788
789 assert(fe->drawstatus != DRAWING);
790 if (fe->drawstatus == NOTHING)
791 return;
792
793 ppw = GetDeviceCaps(fe->hdc, HORZRES);
794 pph = GetDeviceCaps(fe->hdc, VERTRES);
795 mmpw = (float)GetDeviceCaps(fe->hdc, HORZSIZE);
796 mmph = (float)GetDeviceCaps(fe->hdc, VERTSIZE);
797
798 /*
799 * Compute the puzzle's position on the logical page.
800 */
801 mmox = xm * mmpw + xc;
802 mmoy = ym * mmph + yc;
803
804 /*
805 * Work out what that comes to in pixels.
806 */
807 pox = (int)(mmox * (float)ppw / mmpw);
808 poy = (int)(mmoy * (float)pph / mmph);
809
810 /*
811 * And determine the scale.
812 *
813 * I need a scale such that the maximum puzzle-coordinate
814 * extent of the rectangle (pw * scale) is equal to the pixel
815 * equivalent of the puzzle's millimetre width (wmm * ppw /
816 * mmpw).
817 */
818 scale = (wmm * ppw) / (mmpw * pw);
819
820 /*
821 * Now store pox, poy and scale for use in the main drawing
822 * functions.
823 */
824 fe->printoffsetx = pox;
825 fe->printoffsety = poy;
826 fe->printpixelscale = scale;
827
828 fe->linewidth = 1;
829 fe->linedotted = false;
830}
831
832static void win_end_puzzle(void *handle)
833{
834 /* Nothing needs to be done here. */
835}
836
837static void win_end_page(void *handle, int number)
838{
839 frontend *fe = (frontend *)handle;
840
841 assert(fe->drawstatus != DRAWING);
842
843 if (fe->drawstatus == NOTHING)
844 return;
845
846 if (EndPage(fe->hdc) <= 0) {
847 char *e = geterrstr();
848 MessageBox(fe->hwnd, e, "Error finishing a page",
849 MB_ICONERROR | MB_OK);
850 sfree(e);
851 fe->drawstatus = NOTHING;
852 }
853}
854
855static void win_end_doc(void *handle)
856{
857 frontend *fe = (frontend *)handle;
858
859 assert(fe->drawstatus != DRAWING);
860
861 /*
862 * Free all the fonts created since we began printing.
863 */
864 while (fe->nfonts > fe->fontstart) {
865 fe->nfonts--;
866 DeleteObject(fe->fonts[fe->nfonts].font);
867 }
868 fe->fontstart = 0;
869
870 /*
871 * The MSDN web site sample code doesn't bother to call EndDoc
872 * if an error occurs half way through printing. I expect doing
873 * so would cause the erroneous document to actually be
874 * printed, or something equally undesirable.
875 */
876 if (fe->drawstatus == NOTHING)
877 return;
878
879 if (EndDoc(fe->hdc) <= 0) {
880 char *e = geterrstr();
881 MessageBox(fe->hwnd, e, "Error finishing printing",
882 MB_ICONERROR | MB_OK);
883 sfree(e);
884 fe->drawstatus = NOTHING;
885 }
886}
887
888char *win_text_fallback(void *handle, const char *const *strings, int nstrings)
889{
890 /*
891 * We assume Windows can cope with any UTF-8 likely to be
892 * emitted by a puzzle.
893 */
894 return dupstr(strings[0]);
895}
896
897const struct drawing_api win_drawing = {
898 win_draw_text,
899 win_draw_rect,
900 win_draw_line,
901 win_draw_polygon,
902 win_draw_circle,
903 win_draw_update,
904 win_clip,
905 win_unclip,
906 win_start_draw,
907 win_end_draw,
908 win_status_bar,
909 win_blitter_new,
910 win_blitter_free,
911 win_blitter_save,
912 win_blitter_load,
913 win_begin_doc,
914 win_begin_page,
915 win_begin_puzzle,
916 win_end_puzzle,
917 win_end_page,
918 win_end_doc,
919 win_line_width,
920 win_line_dotted,
921 win_text_fallback,
922};
923
924void print(frontend *fe)
925{
926 PRINTDLG pd;
927 char doctitle[256];
928 document *doc;
929 midend *nme = NULL; /* non-interactive midend for bulk puzzle generation */
930 int i;
931 const char *err = NULL;
932
933 /*
934 * Create our document structure and fill it up with puzzles.
935 */
936 doc = document_new(fe->printw, fe->printh, fe->printscale / 100.0F);
937 for (i = 0; i < fe->printcount; i++) {
938 if (i == 0 && fe->printcurr) {
939 err = midend_print_puzzle(fe->me, doc, fe->printsolns);
940 } else {
941 if (!nme) {
942 game_params *params;
943
944 nme = midend_new(NULL, fe->game, NULL, NULL);
945 load_prefs(nme);
946
947 /*
948 * Set the non-interactive mid-end to have the same
949 * parameters as the standard one.
950 */
951 params = midend_get_params(fe->me);
952 midend_set_params(nme, params);
953 fe->game->free_params(params);
954 }
955
956 midend_new_game(nme);
957 err = midend_print_puzzle(nme, doc, fe->printsolns);
958 }
959 if (err)
960 break;
961 }
962 if (nme)
963 midend_free(nme);
964
965 if (err) {
966 MessageBox(fe->hwnd, err, "Error preparing puzzles for printing",
967 MB_ICONERROR | MB_OK);
968 document_free(doc);
969 return;
970 }
971
972 memset(&pd, 0, sizeof(pd));
973 pd.lStructSize = sizeof(pd);
974 pd.hwndOwner = fe->hwnd;
975 pd.hDevMode = NULL;
976 pd.hDevNames = NULL;
977 pd.Flags = PD_USEDEVMODECOPIESANDCOLLATE | PD_RETURNDC |
978 PD_NOPAGENUMS | PD_NOSELECTION;
979 pd.nCopies = 1;
980 pd.nFromPage = pd.nToPage = 0xFFFF;
981 pd.nMinPage = pd.nMaxPage = 1;
982
983 if (!PrintDlg(&pd)) {
984 document_free(doc);
985 return;
986 }
987
988 /*
989 * Now pd.hDC is a device context for the printer.
990 */
991
992 /*
993 * FIXME: IWBNI we put up an Abort box here.
994 */
995
996 memset(&fe->di, 0, sizeof(fe->di));
997 fe->di.cbSize = sizeof(fe->di);
998 sprintf(doctitle, "Printed puzzles from %s (from Simon Tatham's"
999 " Portable Puzzle Collection)", fe->game->name);
1000 fe->di.lpszDocName = doctitle;
1001 fe->di.lpszOutput = NULL;
1002 fe->di.lpszDatatype = NULL;
1003 fe->di.fwType = 0;
1004
1005 fe->drawstatus = PRINTING;
1006 fe->hdc = pd.hDC;
1007
1008 fe->dr = drawing_new(&win_drawing, NULL, fe);
1009 document_print(doc, fe->dr);
1010 drawing_free(fe->dr);
1011 fe->dr = NULL;
1012
1013 fe->drawstatus = NOTHING;
1014
1015 DeleteDC(pd.hDC);
1016 document_free(doc);
1017}
1018
1019void deactivate_timer(frontend *fe)
1020{
1021 if (!fe)
1022 return; /* for non-interactive midend */
1023 if (fe->hwnd) KillTimer(fe->hwnd, fe->timer);
1024 fe->timer = 0;
1025}
1026
1027void activate_timer(frontend *fe)
1028{
1029 if (!fe)
1030 return; /* for non-interactive midend */
1031 if (!fe->timer) {
1032 fe->timer = SetTimer(fe->hwnd, 1, 20, NULL);
1033 fe->timer_last_tickcount = GetTickCount();
1034 }
1035}
1036
1037void write_clip(HWND hwnd, char *data)
1038{
1039 HGLOBAL clipdata;
1040 int len, i, j;
1041 char *data2;
1042 void *lock;
1043
1044 /*
1045 * Windows expects CRLF in the clipboard, so we must convert
1046 * any \n that has come out of the puzzle backend.
1047 */
1048 len = 0;
1049 for (i = 0; data[i]; i++) {
1050 if (data[i] == '\n')
1051 len++;
1052 len++;
1053 }
1054 data2 = snewn(len+1, char);
1055 j = 0;
1056 for (i = 0; data[i]; i++) {
1057 if (data[i] == '\n')
1058 data2[j++] = '\r';
1059 data2[j++] = data[i];
1060 }
1061 assert(j == len);
1062 data2[j] = '\0';
1063
1064 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
1065 if (!clipdata) {
1066 sfree(data2);
1067 return;
1068 }
1069 lock = GlobalLock(clipdata);
1070 if (!lock) {
1071 GlobalFree(clipdata);
1072 sfree(data2);
1073 return;
1074 }
1075 memcpy(lock, data2, len);
1076 ((unsigned char *) lock)[len] = 0;
1077 GlobalUnlock(clipdata);
1078
1079 if (OpenClipboard(hwnd)) {
1080 EmptyClipboard();
1081 SetClipboardData(CF_TEXT, clipdata);
1082 CloseClipboard();
1083 } else
1084 GlobalFree(clipdata);
1085
1086 sfree(data2);
1087}
1088
1089/*
1090 * Set up Help and see if we can find a help file.
1091 */
1092static void init_help(void)
1093{
1094 char b[2048], *p, *q, *r;
1095 FILE *fp;
1096
1097 /*
1098 * Find the executable file path, so we can look alongside
1099 * it for help files. Trim the filename off the end.
1100 */
1101 GetModuleFileName(NULL, b, sizeof(b) - 1);
1102 r = b;
1103 p = strrchr(b, '\\');
1104 if (p && p >= r) r = p+1;
1105 q = strrchr(b, ':');
1106 if (q && q >= r) r = q+1;
1107
1108#ifndef NO_HTMLHELP
1109 /*
1110 * Try HTML Help first.
1111 */
1112 strcpy(r, CHM_FILE_NAME);
1113 if ( (fp = fopen(b, "r")) != NULL) {
1114 fclose(fp);
1115
1116 /*
1117 * We have a .CHM. See if we can use it.
1118 */
1119 hh_dll = LoadLibrary("hhctrl.ocx");
1120 if (hh_dll) {
1121 htmlhelp = (htmlhelp_t)GetProcAddress(hh_dll, "HtmlHelpA");
1122 if (!htmlhelp)
1123 FreeLibrary(hh_dll);
1124 }
1125 if (htmlhelp) {
1126 help_path = dupstr(b);
1127 help_type = CHM;
1128 return;
1129 }
1130 }
1131#endif /* NO_HTMLHELP */
1132
1133 /*
1134 * Now try old-style .HLP.
1135 */
1136 strcpy(r, HELP_FILE_NAME);
1137 if ( (fp = fopen(b, "r")) != NULL) {
1138 fclose(fp);
1139
1140 help_path = dupstr(b);
1141 help_type = HLP;
1142
1143 /*
1144 * See if there's a .CNT file alongside it.
1145 */
1146 strcpy(r, HELP_CNT_NAME);
1147 if ( (fp = fopen(b, "r")) != NULL) {
1148 fclose(fp);
1149 help_has_contents = true;
1150 } else
1151 help_has_contents = false;
1152
1153 return;
1154 }
1155
1156 help_type = NONE; /* didn't find any */
1157}
1158
1159/*
1160 * Start Help.
1161 */
1162static void start_help(frontend *fe, const char *topic)
1163{
1164 char *str = NULL;
1165 int cmd;
1166
1167 switch (help_type) {
1168 case HLP:
1169 assert(help_path);
1170 if (topic) {
1171 str = snewn(10+strlen(topic), char);
1172 sprintf(str, "JI(`',`%s')", topic);
1173 cmd = HELP_COMMAND;
1174 } else if (help_has_contents) {
1175 cmd = HELP_FINDER;
1176 } else {
1177 cmd = HELP_CONTENTS;
1178 }
1179 WinHelp(fe->hwnd, help_path, cmd, (ULONG_PTR)str);
1180 fe->help_running = true;
1181 break;
1182 case CHM:
1183#ifndef NO_HTMLHELP
1184 assert(help_path);
1185 assert(htmlhelp);
1186 if (topic) {
1187 str = snewn(20 + strlen(topic) + strlen(help_path), char);
1188 sprintf(str, "%s::/%s.html>main", help_path, topic);
1189 } else {
1190 str = dupstr(help_path);
1191 }
1192 htmlhelp(fe->hwnd, str, HH_DISPLAY_TOPIC, 0);
1193 fe->help_running = true;
1194 break;
1195#endif /* NO_HTMLHELP */
1196 case NONE:
1197 assert(!"This shouldn't happen");
1198 break;
1199 }
1200
1201 sfree(str);
1202}
1203
1204/*
1205 * Stop Help on window cleanup.
1206 */
1207static void stop_help(frontend *fe)
1208{
1209 if (fe->help_running) {
1210 switch (help_type) {
1211 case HLP:
1212 WinHelp(fe->hwnd, help_path, HELP_QUIT, 0);
1213 break;
1214 case CHM:
1215#ifndef NO_HTMLHELP
1216 assert(htmlhelp);
1217 htmlhelp(NULL, NULL, HH_CLOSE_ALL, 0);
1218 break;
1219#endif /* NO_HTMLHELP */
1220 case NONE:
1221 assert(!"This shouldn't happen");
1222 break;
1223 }
1224 fe->help_running = false;
1225 }
1226}
1227
1228/*
1229 * Terminate Help on process exit.
1230 */
1231static void cleanup_help(void)
1232{
1233 /* Nothing to do currently.
1234 * (If we were running HTML Help single-threaded, this is where we'd
1235 * call HH_UNINITIALIZE.) */
1236}
1237
1238static int get_statusbar_height(frontend *fe)
1239{
1240 int sy;
1241 if (fe->statusbar) {
1242 RECT sr;
1243 GetWindowRect(fe->statusbar, &sr);
1244 sy = sr.bottom - sr.top;
1245 } else {
1246 sy = 0;
1247 }
1248 return sy;
1249}
1250
1251static void adjust_statusbar(frontend *fe, RECT *r)
1252{
1253 int sy;
1254
1255 if (!fe->statusbar) return;
1256
1257 sy = get_statusbar_height(fe);
1258 SetWindowPos(fe->statusbar, NULL, 0, r->bottom-r->top-sy, r->right-r->left,
1259 sy, SWP_NOZORDER);
1260}
1261
1262static void get_menu_size(HWND wh, RECT *r)
1263{
1264 HMENU bar = GetMenu(wh);
1265 RECT rect;
1266 int i;
1267
1268 SetRect(r, 0, 0, 0, 0);
1269 for (i = 0; i < GetMenuItemCount(bar); i++) {
1270 GetMenuItemRect(wh, bar, i, &rect);
1271 UnionRect(r, r, &rect);
1272 }
1273}
1274
1275/*
1276 * Given a proposed new puzzle size (cx,cy), work out the actual
1277 * puzzle size that would be (px,py) and the window size including
1278 * furniture (wx,wy).
1279 */
1280
1281static bool check_window_resize(frontend *fe, int cx, int cy,
1282 int *px, int *py, int *wx, int *wy)
1283{
1284 RECT r;
1285 int x, y, sy = get_statusbar_height(fe);
1286 bool changed = false;
1287
1288 /* disallow making window thinner than menu bar */
1289 x = max(cx, fe->xmin);
1290 y = max(cy - sy, fe->ymin);
1291
1292 /*
1293 * See if we actually got the window size we wanted, and adjust
1294 * the puzzle size if not.
1295 */
1296 midend_size(fe->me, &x, &y, true, 1.0);
1297 if (x != cx || y != cy) {
1298 /*
1299 * Resize the window, now we know what size we _really_
1300 * want it to be.
1301 */
1302 r.left = r.top = 0;
1303 r.right = x;
1304 r.bottom = y + sy;
1305 AdjustWindowRectEx(&r, WINFLAGS, true, 0);
1306 *wx = r.right - r.left;
1307 *wy = r.bottom - r.top;
1308 changed = true;
1309 }
1310
1311 *px = x;
1312 *py = y;
1313
1314 fe->puzz_scale =
1315 (float)midend_tilesize(fe->me) / (float)fe->game->preferred_tilesize;
1316
1317 return changed;
1318}
1319
1320/*
1321 * Given the current window size, make sure it's sane for the
1322 * current puzzle and resize if necessary.
1323 */
1324
1325static void check_window_size(frontend *fe, int *px, int *py)
1326{
1327 RECT r;
1328 int wx, wy, cx, cy;
1329
1330 GetClientRect(fe->hwnd, &r);
1331 cx = r.right - r.left;
1332 cy = r.bottom - r.top;
1333
1334 if (check_window_resize(fe, cx, cy, px, py, &wx, &wy))
1335 SetWindowPos(fe->hwnd, NULL, 0, 0, wx, wy, SWP_NOMOVE | SWP_NOZORDER);
1336
1337 GetClientRect(fe->hwnd, &r);
1338 adjust_statusbar(fe, &r);
1339}
1340
1341static void get_max_puzzle_size(frontend *fe, int *x, int *y)
1342{
1343 RECT r, sr;
1344
1345 if (SystemParametersInfo(SPI_GETWORKAREA, 0, &sr, false)) {
1346 *x = sr.right - sr.left;
1347 *y = sr.bottom - sr.top;
1348 r.left = 100;
1349 r.right = 200;
1350 r.top = 100;
1351 r.bottom = 200;
1352 AdjustWindowRectEx(&r, WINFLAGS, true, 0);
1353 *x -= r.right - r.left - 100;
1354 *y -= r.bottom - r.top - 100;
1355 } else {
1356 *x = *y = INT_MAX;
1357 }
1358
1359 if (fe->statusbar != NULL) {
1360 GetWindowRect(fe->statusbar, &sr);
1361 *y -= sr.bottom - sr.top;
1362 }
1363}
1364
1365/*
1366 * Allocate a new frontend structure and create its main window.
1367 */
1368static frontend *frontend_new(HINSTANCE inst)
1369{
1370 frontend *fe;
1371 const char *nogame = "Puzzles (no game selected)";
1372
1373 fe = snew(frontend);
1374
1375 fe->inst = inst;
1376
1377 fe->game = NULL;
1378 fe->me = NULL;
1379
1380 fe->timer = 0;
1381 fe->hwnd = NULL;
1382
1383 fe->help_running = false;
1384
1385 fe->drawstatus = NOTHING;
1386 fe->dr = NULL;
1387 fe->fontstart = 0;
1388
1389 fe->fonts = NULL;
1390 fe->nfonts = fe->fontsize = 0;
1391
1392 fe->colours = NULL;
1393 fe->brushes = NULL;
1394 fe->pens = NULL;
1395
1396 fe->puzz_scale = 1.0;
1397
1398 fe->hwnd = CreateWindowEx(0, CLASSNAME, nogame,
1399 WS_OVERLAPPEDWINDOW &~
1400 (WS_MAXIMIZEBOX),
1401 CW_USEDEFAULT, CW_USEDEFAULT,
1402 CW_USEDEFAULT, CW_USEDEFAULT,
1403 NULL, NULL, inst, NULL);
1404 if (!fe->hwnd) {
1405 DWORD lerr = GetLastError();
1406 printf("no window: 0x%x\n", (unsigned)lerr);
1407 }
1408
1409 fe->gamemenu = NULL;
1410 fe->preset_menu = NULL;
1411
1412 fe->statusbar = NULL;
1413 fe->bitmap = NULL;
1414
1415 SetWindowLongPtr(fe->hwnd, GWLP_USERDATA, (LONG_PTR)fe);
1416
1417 return fe;
1418}
1419
1420static void savefile_write(void *wctx, const void *buf, int len)
1421{
1422 FILE *fp = (FILE *)wctx;
1423 fwrite(buf, 1, len, fp);
1424}
1425
1426static bool savefile_read(void *wctx, void *buf, int len)
1427{
1428 FILE *fp = (FILE *)wctx;
1429 int ret;
1430
1431 ret = fread(buf, 1, len, fp);
1432 return (ret == len);
1433}
1434
1435/*
1436 * Create an appropriate midend structure to go in a puzzle window,
1437 * given a game type and/or a command-line argument.
1438 *
1439 * 'arg' can be either a game ID string (descriptive, random, or a
1440 * plain set of parameters) or the filename of a save file. The two
1441 * boolean flag arguments indicate which possibilities are
1442 * permissible.
1443 */
1444static midend *midend_for_new_game(frontend *fe, const game *cgame,
1445 char *arg, bool maybe_game_id,
1446 bool maybe_save_file, char **error)
1447{
1448 midend *me = NULL;
1449
1450 if (!arg) {
1451 if (me) midend_free(me);
1452 me = midend_new(fe, cgame, &win_drawing, fe);
1453 load_prefs(me);
1454 midend_new_game(me);
1455 } else {
1456 FILE *fp;
1457 const char *err_param, *err_load;
1458
1459 /*
1460 * See if arg is a valid filename of a save game file.
1461 */
1462 err_load = NULL;
1463 if (maybe_save_file && (fp = fopen(arg, "r")) != NULL) {
1464 const game *loadgame;
1465
1466#ifdef COMBINED
1467 /*
1468 * Find out what kind of game is stored in the save
1469 * file; if we're going to end up loading that, it
1470 * will have to override our caller's judgment as to
1471 * what game to initialise our midend with.
1472 */
1473 char *id_name;
1474 err_load = identify_game(&id_name, savefile_read, fp);
1475 if (!err_load) {
1476 int i;
1477 for (i = 0; i < gamecount; i++)
1478 if (!strcmp(id_name, gamelist[i]->name))
1479 break;
1480 if (i == gamecount) {
1481 err_load = "Save file is for a game not supported by"
1482 " this program";
1483 } else {
1484 loadgame = gamelist[i];
1485 rewind(fp); /* go back to the start for actual load */
1486 }
1487 }
1488#else
1489 loadgame = cgame;
1490#endif
1491 if (!err_load) {
1492 if (me) midend_free(me);
1493 me = midend_new(fe, loadgame, &win_drawing, fe);
1494 load_prefs(me);
1495 err_load = midend_deserialise(me, savefile_read, fp);
1496 }
1497 } else {
1498 err_load = "Unable to open file";
1499 }
1500
1501 if (maybe_game_id && (!maybe_save_file || err_load)) {
1502 /*
1503 * See if arg is a game description.
1504 */
1505 if (me) midend_free(me);
1506 me = midend_new(fe, cgame, &win_drawing, fe);
1507 load_prefs(me);
1508 err_param = midend_game_id(me, arg);
1509 if (!err_param) {
1510 midend_new_game(me);
1511 } else {
1512 if (maybe_save_file) {
1513 *error = snewn(256 + strlen(arg) + strlen(err_param) +
1514 strlen(err_load), char);
1515 sprintf(*error, "Supplied argument \"%s\" is neither a"
1516 " game ID (%s) nor a save file (%s)",
1517 arg, err_param, err_load);
1518 } else {
1519 *error = dupstr(err_param);
1520 }
1521 midend_free(me);
1522 sfree(fe);
1523 return NULL;
1524 }
1525 } else if (err_load) {
1526 *error = dupstr(err_load);
1527 midend_free(me);
1528 sfree(fe);
1529 return NULL;
1530 }
1531 }
1532
1533 return me;
1534}
1535
1536static void populate_preset_menu(frontend *fe,
1537 struct preset_menu *menu, HMENU winmenu)
1538{
1539 int i;
1540 for (i = 0; i < menu->n_entries; i++) {
1541 struct preset_menu_entry *entry = &menu->entries[i];
1542 UINT_PTR id_or_sub;
1543 UINT flags = MF_ENABLED;
1544
1545 if (entry->params) {
1546 id_or_sub = (UINT_PTR)(
1547 IDM_PRESET_BASE + MENUITEM_STEP * entry->id);
1548
1549 fe->preset_menuitems[entry->id].which_menu = winmenu;
1550 fe->preset_menuitems[entry->id].item_index =
1551 GetMenuItemCount(winmenu);
1552 } else {
1553 HMENU winsubmenu = CreateMenu();
1554 id_or_sub = (UINT_PTR)winsubmenu;
1555 flags |= MF_POPUP;
1556
1557 populate_preset_menu(fe, entry->submenu, winsubmenu);
1558 }
1559
1560 /*
1561 * FIXME: we ought to go through and do something with ampersands
1562 * here.
1563 */
1564
1565 AppendMenu(winmenu, flags, id_or_sub, entry->title);
1566 }
1567}
1568
1569/*
1570 * Populate a frontend structure with a new midend structure, and
1571 * create any window furniture that it needs.
1572 *
1573 * Previously-allocated memory and window furniture will be freed by
1574 * this function.
1575 *
1576 */
1577static int fe_set_midend(frontend *fe, midend *me)
1578{
1579 int x, y;
1580 RECT r;
1581
1582 if (fe->me) {
1583 midend_free(fe->me);
1584 fe->preset_menu = NULL;
1585 sfree(fe->preset_menuitems);
1586 }
1587 fe->me = me;
1588 fe->game = midend_which_game(fe->me);
1589
1590 {
1591 int i, ncolours;
1592 float *colours;
1593
1594 colours = midend_colours(fe->me, &ncolours);
1595
1596 if (fe->colours) sfree(fe->colours);
1597 if (fe->brushes) sfree(fe->brushes);
1598 if (fe->pens) sfree(fe->pens);
1599
1600 fe->colours = snewn(ncolours, COLORREF);
1601 fe->brushes = snewn(ncolours, HBRUSH);
1602 fe->pens = snewn(ncolours, HPEN);
1603
1604 for (i = 0; i < ncolours; i++) {
1605 fe->colours[i] = RGB(255 * colours[i*3+0],
1606 255 * colours[i*3+1],
1607 255 * colours[i*3+2]);
1608 fe->brushes[i] = CreateSolidBrush(fe->colours[i]);
1609 fe->pens[i] = CreatePen(PS_SOLID, 1, fe->colours[i]);
1610 }
1611 sfree(colours);
1612 }
1613
1614 if (fe->statusbar)
1615 DestroyWindow(fe->statusbar);
1616 if (midend_wants_statusbar(fe->me)) {
1617 fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME,
1618 TEXT(DEFAULT_STATUSBAR_TEXT),
1619 WS_CHILD | WS_VISIBLE,
1620 0, 0, 0, 0, /* status bar does these */
1621 NULL, NULL, fe->inst, NULL);
1622 } else
1623 fe->statusbar = NULL;
1624
1625 get_max_puzzle_size(fe, &x, &y);
1626 midend_size(fe->me, &x, &y, false, 1.0);
1627
1628 r.left = r.top = 0;
1629 r.right = x;
1630 r.bottom = y;
1631 AdjustWindowRectEx(&r, WINFLAGS, true, 0);
1632
1633 SetWindowText(fe->hwnd, fe->game->name);
1634
1635 if (fe->statusbar)
1636 DestroyWindow(fe->statusbar);
1637 if (midend_wants_statusbar(fe->me)) {
1638 RECT sr;
1639 fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME, TEXT("ooh"),
1640 WS_CHILD | WS_VISIBLE,
1641 0, 0, 0, 0, /* status bar does these */
1642 fe->hwnd, NULL, fe->inst, NULL);
1643
1644 /*
1645 * Now resize the window to take account of the status bar.
1646 */
1647 GetWindowRect(fe->statusbar, &sr);
1648 GetWindowRect(fe->hwnd, &r);
1649 SetWindowPos(fe->hwnd, NULL, 0, 0, r.right - r.left,
1650 r.bottom - r.top + sr.bottom - sr.top,
1651 SWP_NOMOVE | SWP_NOZORDER);
1652 } else {
1653 fe->statusbar = NULL;
1654 }
1655
1656 {
1657 HMENU oldmenu = GetMenu(fe->hwnd);
1658
1659 HMENU bar = CreateMenu();
1660 HMENU menu = CreateMenu();
1661 RECT menusize;
1662
1663 AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT_PTR)menu, "&Game");
1664 fe->gamemenu = menu;
1665 AppendMenu(menu, MF_ENABLED, IDM_NEW, TEXT("&New"));
1666 AppendMenu(menu, MF_ENABLED, IDM_RESTART, TEXT("&Restart"));
1667 /* ...here I run out of sensible accelerator characters. */
1668 AppendMenu(menu, MF_ENABLED, IDM_DESC, TEXT("Speci&fic..."));
1669 AppendMenu(menu, MF_ENABLED, IDM_SEED, TEXT("Rando&m Seed..."));
1670
1671 assert(!fe->preset_menu);
1672
1673 fe->preset_menu = midend_get_presets(
1674 fe->me, &fe->n_preset_menuitems);
1675 fe->preset_menuitems = snewn(fe->n_preset_menuitems,
1676 struct preset_menuitemref);
1677 {
1678 int i;
1679 for (i = 0; i < fe->n_preset_menuitems; i++)
1680 fe->preset_menuitems[i].which_menu = NULL;
1681 }
1682 if (fe->preset_menu->n_entries > 0 || fe->game->can_configure) {
1683 HMENU sub = CreateMenu();
1684
1685 AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT_PTR)sub, "&Type");
1686
1687 populate_preset_menu(fe, fe->preset_menu, sub);
1688
1689 if (fe->game->can_configure) {
1690 AppendMenu(sub, MF_ENABLED, IDM_CONFIG, TEXT("&Custom..."));
1691 }
1692
1693 fe->typemenu = sub;
1694 } else {
1695 fe->typemenu = INVALID_HANDLE_VALUE;
1696 }
1697
1698#ifdef COMBINED
1699 {
1700 HMENU games = CreateMenu();
1701 int i;
1702
1703 AppendMenu(menu, MF_SEPARATOR, 0, 0);
1704 AppendMenu(menu, MF_ENABLED|MF_POPUP, (UINT_PTR)games, "&Other");
1705 for (i = 0; i < gamecount; i++) {
1706 if (strcmp(gamelist[i]->name, fe->game->name) != 0) {
1707 /* only include those games that aren't the same as the
1708 * game we're currently playing. */
1709 AppendMenu(games, MF_ENABLED,
1710 IDM_GAME_BASE + MENUITEM_STEP * i,
1711 gamelist[i]->name);
1712 }
1713 }
1714 }
1715#endif
1716
1717 AppendMenu(menu, MF_SEPARATOR, 0, 0);
1718 AppendMenu(menu, MF_ENABLED, IDM_LOAD, TEXT("&Load..."));
1719 AppendMenu(menu, MF_ENABLED, IDM_SAVE, TEXT("&Save..."));
1720 AppendMenu(menu, MF_SEPARATOR, 0, 0);
1721 if (fe->game->can_print) {
1722 AppendMenu(menu, MF_ENABLED, IDM_PRINT, TEXT("&Print..."));
1723 AppendMenu(menu, MF_SEPARATOR, 0, 0);
1724 }
1725 AppendMenu(menu, MF_ENABLED, IDM_UNDO, TEXT("Undo"));
1726 AppendMenu(menu, MF_ENABLED, IDM_REDO, TEXT("Redo"));
1727 if (fe->game->can_format_as_text_ever) {
1728 AppendMenu(menu, MF_SEPARATOR, 0, 0);
1729 AppendMenu(menu, MF_ENABLED, IDM_COPY, TEXT("&Copy"));
1730 }
1731 if (fe->game->can_solve) {
1732 AppendMenu(menu, MF_SEPARATOR, 0, 0);
1733 AppendMenu(menu, MF_ENABLED, IDM_SOLVE, TEXT("Sol&ve"));
1734 }
1735 AppendMenu(menu, MF_SEPARATOR, 0, 0);
1736 AppendMenu(menu, MF_ENABLED, IDM_PREFS, TEXT("Pre&ferences"));
1737 AppendMenu(menu, MF_SEPARATOR, 0, 0);
1738 AppendMenu(menu, MF_ENABLED, IDM_QUIT, TEXT("E&xit"));
1739 menu = CreateMenu();
1740 AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT_PTR)menu, TEXT("&Help"));
1741 AppendMenu(menu, MF_ENABLED, IDM_ABOUT, TEXT("&About"));
1742 if (help_type != NONE) {
1743 char *item;
1744 AppendMenu(menu, MF_SEPARATOR, 0, 0);
1745 AppendMenu(menu, MF_ENABLED, IDM_HELPC, TEXT("&Contents"));
1746 assert(fe->game->name);
1747 item = snewn(10+strlen(fe->game->name), char); /*ick*/
1748 sprintf(item, "&Help on %s", fe->game->name);
1749 AppendMenu(menu, MF_ENABLED, IDM_GAMEHELP, item);
1750 sfree(item);
1751 }
1752 DestroyMenu(oldmenu);
1753 SetMenu(fe->hwnd, bar);
1754 get_menu_size(fe->hwnd, &menusize);
1755 fe->xmin = (menusize.right - menusize.left) + 25;
1756 }
1757
1758 if (fe->bitmap) DeleteObject(fe->bitmap);
1759 fe->bitmap = NULL;
1760 new_game_size(fe, fe->puzz_scale); /* initialises fe->bitmap */
1761
1762 return 0;
1763}
1764
1765static void show_window(frontend *fe)
1766{
1767 ShowWindow(fe->hwnd, SW_SHOWNORMAL);
1768 SetForegroundWindow(fe->hwnd);
1769
1770 update_type_menu_tick(fe);
1771 update_copy_menu_greying(fe);
1772
1773 midend_redraw(fe->me);
1774}
1775
1776static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg,
1777 WPARAM wParam, LPARAM lParam)
1778{
1779 frontend *fe = (frontend *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
1780
1781 switch (msg) {
1782 case WM_INITDIALOG:
1783 return 1;
1784
1785 case WM_COMMAND:
1786 if (LOWORD(wParam) == IDOK)
1787 fe->dlg_done = 1;
1788 return 0;
1789
1790 case WM_CLOSE:
1791 fe->dlg_done = 1;
1792 return 0;
1793 }
1794
1795 return 0;
1796}
1797
1798/*
1799 * Wrappers on midend_{get,set}_config, which extend the CFG_*
1800 * enumeration to add CFG_PRINT.
1801 */
1802static config_item *frontend_get_config(frontend *fe, int which,
1803 char **wintitle)
1804{
1805 if (which < CFG_FRONTEND_SPECIFIC) {
1806 return midend_get_config(fe->me, which, wintitle);
1807 } else if (which == CFG_PRINT) {
1808 config_item *ret;
1809 int i;
1810
1811 *wintitle = snewn(40 + strlen(fe->game->name), char);
1812 sprintf(*wintitle, "%s print setup", fe->game->name);
1813
1814 ret = snewn(8, config_item);
1815
1816 i = 0;
1817
1818 ret[i].name = "Number of puzzles to print";
1819 ret[i].type = C_STRING;
1820 ret[i].u.string.sval = dupstr("1");
1821 i++;
1822
1823 ret[i].name = "Number of puzzles across the page";
1824 ret[i].type = C_STRING;
1825 ret[i].u.string.sval = dupstr("1");
1826 i++;
1827
1828 ret[i].name = "Number of puzzles down the page";
1829 ret[i].type = C_STRING;
1830 ret[i].u.string.sval = dupstr("1");
1831 i++;
1832
1833 ret[i].name = "Percentage of standard size";
1834 ret[i].type = C_STRING;
1835 ret[i].u.string.sval = dupstr("100.0");
1836 i++;
1837
1838 ret[i].name = "Include currently shown puzzle";
1839 ret[i].type = C_BOOLEAN;
1840 ret[i].u.boolean.bval = true;
1841 i++;
1842
1843 ret[i].name = "Print solutions";
1844 ret[i].type = C_BOOLEAN;
1845 ret[i].u.boolean.bval = false;
1846 i++;
1847
1848 if (fe->game->can_print_in_colour) {
1849 ret[i].name = "Print in colour";
1850 ret[i].type = C_BOOLEAN;
1851 ret[i].u.boolean.bval = false;
1852 i++;
1853 }
1854
1855 ret[i].name = NULL;
1856 ret[i].type = C_END;
1857 i++;
1858
1859 return ret;
1860 } else {
1861 assert(!"We should never get here");
1862 return NULL;
1863 }
1864}
1865
1866static const char *frontend_set_config(
1867 frontend *fe, int which, config_item *cfg)
1868{
1869 if (which < CFG_FRONTEND_SPECIFIC) {
1870 return midend_set_config(fe->me, which, cfg);
1871 } else if (which == CFG_PRINT) {
1872 if ((fe->printcount = atoi(cfg[0].u.string.sval)) <= 0)
1873 return "Number of puzzles to print should be at least one";
1874 if ((fe->printw = atoi(cfg[1].u.string.sval)) <= 0)
1875 return "Number of puzzles across the page should be at least one";
1876 if ((fe->printh = atoi(cfg[2].u.string.sval)) <= 0)
1877 return "Number of puzzles down the page should be at least one";
1878 if ((fe->printscale = (float)atof(cfg[3].u.string.sval)) <= 0)
1879 return "Print size should be positive";
1880 fe->printcurr = cfg[4].u.boolean.bval;
1881 fe->printsolns = cfg[5].u.boolean.bval;
1882 fe->printcolour = fe->game->can_print_in_colour &&
1883 cfg[6].u.boolean.bval;
1884 return NULL;
1885 } else {
1886 assert(!"We should never get here");
1887 return "Internal error";
1888 }
1889}
1890
1891static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
1892 WPARAM wParam, LPARAM lParam)
1893{
1894 frontend *fe = (frontend *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
1895 config_item *i;
1896 struct cfg_aux *j;
1897
1898 switch (msg) {
1899 case WM_INITDIALOG:
1900 return 1;
1901
1902 case WM_COMMAND:
1903 /*
1904 * OK and Cancel are special cases.
1905 */
1906 if ((LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) {
1907 if (LOWORD(wParam) == IDOK) {
1908 const char *err = frontend_set_config(
1909 fe, fe->cfg_which, fe->cfg);
1910
1911 if (err) {
1912 MessageBox(hwnd, err, "Validation error",
1913 MB_ICONERROR | MB_OK);
1914 } else {
1915 fe->dlg_done = 2;
1916 }
1917 } else {
1918 fe->dlg_done = 1;
1919 }
1920 return 0;
1921 }
1922
1923 /*
1924 * First find the control whose id this is.
1925 */
1926 for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
1927 if (j->ctlid == LOWORD(wParam))
1928 break;
1929 }
1930 if (i->type == C_END)
1931 return 0; /* not our problem */
1932
1933 if (i->type == C_STRING && HIWORD(wParam) == EN_CHANGE) {
1934 char buffer[4096];
1935 GetDlgItemText(fe->cfgbox, j->ctlid, buffer, lenof(buffer));
1936 buffer[lenof(buffer)-1] = '\0';
1937 sfree(i->u.string.sval);
1938 i->u.string.sval = dupstr(buffer);
1939 } else if (i->type == C_BOOLEAN &&
1940 (HIWORD(wParam) == BN_CLICKED ||
1941 HIWORD(wParam) == BN_DBLCLK)) {
1942 i->u.boolean.bval = IsDlgButtonChecked(fe->cfgbox, j->ctlid);
1943 } else if (i->type == C_CHOICES &&
1944 HIWORD(wParam) == CBN_SELCHANGE) {
1945 i->u.choices.selected = SendDlgItemMessage(fe->cfgbox, j->ctlid,
1946 CB_GETCURSEL, 0, 0);
1947 }
1948
1949 return 0;
1950
1951 case WM_CLOSE:
1952 fe->dlg_done = 1;
1953 return 0;
1954 }
1955
1956 return 0;
1957}
1958
1959HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2,
1960 char *wclass, int wstyle,
1961 int exstyle, const char *wtext, INT_PTR wid)
1962{
1963 HWND ret;
1964 ret = CreateWindowEx(exstyle, wclass, wtext,
1965 wstyle | WS_CHILD | WS_VISIBLE, x1, y1, x2-x1, y2-y1,
1966 fe->cfgbox, (HMENU) wid, fe->inst, NULL);
1967 SendMessage(ret, WM_SETFONT, (WPARAM)fe->cfgfont, MAKELPARAM(true, 0));
1968 return ret;
1969}
1970
1971static void about(frontend *fe)
1972{
1973 int i;
1974 WNDCLASS wc;
1975 MSG msg;
1976 TEXTMETRIC tm;
1977 HDC hdc;
1978 HFONT oldfont;
1979 SIZE size;
1980 int gm, id;
1981 int winwidth, winheight, y;
1982 int height, width, maxwid;
1983 const char *strings[16];
1984 int lengths[16];
1985 int nstrings = 0;
1986 char titlebuf[512];
1987
1988 sprintf(titlebuf, "About %.250s", fe->game->name);
1989
1990 strings[nstrings++] = fe->game->name;
1991 strings[nstrings++] = "from Simon Tatham's Portable Puzzle Collection";
1992 strings[nstrings++] = ver;
1993
1994 wc.style = CS_DBLCLKS | CS_SAVEBITS;
1995 wc.lpfnWndProc = DefDlgProc;
1996 wc.cbClsExtra = 0;
1997 wc.cbWndExtra = DLGWINDOWEXTRA + 8;
1998 wc.hInstance = fe->inst;
1999 wc.hIcon = NULL;
2000 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
2001 wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
2002 wc.lpszMenuName = NULL;
2003 wc.lpszClassName = "GameAboutBox";
2004 RegisterClass(&wc);
2005
2006 hdc = GetDC(fe->hwnd);
2007 SetMapMode(hdc, MM_TEXT);
2008
2009 fe->dlg_done = 0;
2010
2011 fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72),
2012 0, 0, 0, 0,
2013 false, false, false, DEFAULT_CHARSET,
2014 OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
2015 DEFAULT_QUALITY,
2016 FF_SWISS,
2017 "MS Shell Dlg");
2018
2019 oldfont = SelectObject(hdc, fe->cfgfont);
2020 if (GetTextMetrics(hdc, &tm)) {
2021 height = tm.tmAscent + tm.tmDescent;
2022 width = tm.tmAveCharWidth;
2023 } else {
2024 height = width = 30;
2025 }
2026
2027 /*
2028 * Figure out the layout of the About box by measuring the
2029 * length of each piece of text.
2030 */
2031 maxwid = 0;
2032 winheight = height/2;
2033
2034 for (i = 0; i < nstrings; i++) {
2035 if (GetTextExtentPoint32(hdc, strings[i], strlen(strings[i]), &size))
2036 lengths[i] = size.cx;
2037 else
2038 lengths[i] = 0; /* *shrug* */
2039 if (maxwid < lengths[i])
2040 maxwid = lengths[i];
2041 winheight += height * 3 / 2 + (height / 2);
2042 }
2043
2044 winheight += height + height * 7 / 4; /* OK button */
2045 winwidth = maxwid + 4*width;
2046
2047 SelectObject(hdc, oldfont);
2048 ReleaseDC(fe->hwnd, hdc);
2049
2050 /*
2051 * Create the dialog, now that we know its size.
2052 */
2053 {
2054 RECT r, r2;
2055
2056 r.left = r.top = 0;
2057 r.right = winwidth;
2058 r.bottom = winheight;
2059
2060 AdjustWindowRectEx(&r, (WS_OVERLAPPEDWINDOW /*|
2061 DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
2062 WS_CAPTION | WS_SYSMENU*/) &~
2063 (WS_MAXIMIZEBOX | WS_OVERLAPPED),
2064 false, 0);
2065
2066 /*
2067 * Centre the dialog on its parent window.
2068 */
2069 r.right -= r.left;
2070 r.bottom -= r.top;
2071 GetWindowRect(fe->hwnd, &r2);
2072 r.left = (r2.left + r2.right - r.right) / 2;
2073 r.top = (r2.top + r2.bottom - r.bottom) / 2;
2074 r.right += r.left;
2075 r.bottom += r.top;
2076
2077 fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, titlebuf,
2078 DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
2079 WS_CAPTION | WS_SYSMENU,
2080 r.left, r.top,
2081 r.right-r.left, r.bottom-r.top,
2082 fe->hwnd, NULL, fe->inst, NULL);
2083 }
2084
2085 SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, false);
2086
2087 SetWindowLongPtr(fe->cfgbox, GWLP_USERDATA, (LONG_PTR)fe);
2088 SetWindowLongPtr(fe->cfgbox, DWLP_DLGPROC, (LONG_PTR)AboutDlgProc);
2089
2090 id = 1000;
2091 y = height/2;
2092 for (i = 0; i < nstrings; i++) {
2093 int border = width*2 + (maxwid - lengths[i]) / 2;
2094 mkctrl(fe, border, border+lengths[i], y+height*1/8, y+height*9/8,
2095 "Static", 0, 0, strings[i], id++);
2096 y += height*3/2;
2097
2098 assert(y < winheight);
2099 y += height/2;
2100 }
2101
2102 y += height/2; /* extra space before OK */
2103 mkctrl(fe, width*2, maxwid+width*2, y, y+height*7/4, "BUTTON",
2104 BS_PUSHBUTTON | WS_TABSTOP | BS_DEFPUSHBUTTON, 0,
2105 "OK", IDOK);
2106
2107 SendMessage(fe->cfgbox, WM_INITDIALOG, 0, 0);
2108
2109 EnableWindow(fe->hwnd, false);
2110 ShowWindow(fe->cfgbox, SW_SHOWNORMAL);
2111 while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
2112 if (!IsDialogMessage(fe->cfgbox, &msg))
2113 DispatchMessage(&msg);
2114 if (fe->dlg_done)
2115 break;
2116 }
2117 EnableWindow(fe->hwnd, true);
2118 SetForegroundWindow(fe->hwnd);
2119 DestroyWindow(fe->cfgbox);
2120 DeleteObject(fe->cfgfont);
2121}
2122
2123static bool get_config(frontend *fe, int which)
2124{
2125 config_item *i;
2126 struct cfg_aux *j;
2127 char *title;
2128 WNDCLASS wc;
2129 MSG msg;
2130 TEXTMETRIC tm;
2131 HDC hdc;
2132 HFONT oldfont;
2133 SIZE size;
2134 HWND ctl;
2135 int gm, id, nctrls;
2136 int winwidth, winheight, col1l, col1r, col2l, col2r, y;
2137 int height, width, maxlabel, maxcheckbox;
2138
2139 wc.style = CS_DBLCLKS | CS_SAVEBITS;
2140 wc.lpfnWndProc = DefDlgProc;
2141 wc.cbClsExtra = 0;
2142 wc.cbWndExtra = DLGWINDOWEXTRA + 8;
2143 wc.hInstance = fe->inst;
2144 wc.hIcon = NULL;
2145 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
2146 wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
2147 wc.lpszMenuName = NULL;
2148 wc.lpszClassName = "GameConfigBox";
2149 RegisterClass(&wc);
2150
2151 hdc = GetDC(fe->hwnd);
2152 SetMapMode(hdc, MM_TEXT);
2153
2154 fe->dlg_done = 0;
2155
2156 fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72),
2157 0, 0, 0, 0,
2158 false, false, false, DEFAULT_CHARSET,
2159 OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
2160 DEFAULT_QUALITY,
2161 FF_SWISS,
2162 "MS Shell Dlg");
2163
2164 oldfont = SelectObject(hdc, fe->cfgfont);
2165 if (GetTextMetrics(hdc, &tm)) {
2166 height = tm.tmAscent + tm.tmDescent;
2167 width = tm.tmAveCharWidth;
2168 } else {
2169 height = width = 30;
2170 }
2171
2172 fe->cfg = frontend_get_config(fe, which, &title);
2173 fe->cfg_which = which;
2174
2175 /*
2176 * Figure out the layout of the config box by measuring the
2177 * length of each piece of text.
2178 */
2179 maxlabel = maxcheckbox = 0;
2180 winheight = height/2;
2181
2182 for (i = fe->cfg; i->type != C_END; i++) {
2183 switch (i->type) {
2184 case C_STRING:
2185 case C_CHOICES:
2186 /*
2187 * Both these control types have a label filling only
2188 * the left-hand column of the box.
2189 */
2190 if (GetTextExtentPoint32(hdc, i->name, strlen(i->name), &size) &&
2191 maxlabel < size.cx)
2192 maxlabel = size.cx;
2193 winheight += height * 3 / 2 + (height / 2);
2194 break;
2195
2196 case C_BOOLEAN:
2197 /*
2198 * Checkboxes take up the whole of the box width.
2199 */
2200 if (GetTextExtentPoint32(hdc, i->name, strlen(i->name), &size) &&
2201 maxcheckbox < size.cx)
2202 maxcheckbox = size.cx;
2203 winheight += height + (height / 2);
2204 break;
2205 }
2206 }
2207
2208 winheight += height + height * 7 / 4; /* OK / Cancel buttons */
2209
2210 col1l = 2*width;
2211 col1r = col1l + maxlabel;
2212 col2l = col1r + 2*width;
2213 col2r = col2l + 30*width;
2214 if (col2r < col1l+2*height+maxcheckbox)
2215 col2r = col1l+2*height+maxcheckbox;
2216 winwidth = col2r + 2*width;
2217
2218 SelectObject(hdc, oldfont);
2219 ReleaseDC(fe->hwnd, hdc);
2220
2221 /*
2222 * Create the dialog, now that we know its size.
2223 */
2224 {
2225 RECT r, r2;
2226
2227 r.left = r.top = 0;
2228 r.right = winwidth;
2229 r.bottom = winheight;
2230
2231 AdjustWindowRectEx(&r, (WS_OVERLAPPEDWINDOW /*|
2232 DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
2233 WS_CAPTION | WS_SYSMENU*/) &~
2234 (WS_MAXIMIZEBOX | WS_OVERLAPPED),
2235 false, 0);
2236
2237 /*
2238 * Centre the dialog on its parent window.
2239 */
2240 r.right -= r.left;
2241 r.bottom -= r.top;
2242 GetWindowRect(fe->hwnd, &r2);
2243 r.left = (r2.left + r2.right - r.right) / 2;
2244 r.top = (r2.top + r2.bottom - r.bottom) / 2;
2245 r.right += r.left;
2246 r.bottom += r.top;
2247
2248 fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, title,
2249 DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
2250 WS_CAPTION | WS_SYSMENU,
2251 r.left, r.top,
2252 r.right-r.left, r.bottom-r.top,
2253 fe->hwnd, NULL, fe->inst, NULL);
2254 sfree(title);
2255 }
2256
2257 SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, false);
2258
2259 SetWindowLongPtr(fe->cfgbox, GWLP_USERDATA, (LONG_PTR)fe);
2260 SetWindowLongPtr(fe->cfgbox, DWLP_DLGPROC, (LONG_PTR)ConfigDlgProc);
2261
2262 /*
2263 * Count the controls so we can allocate cfgaux.
2264 */
2265 for (nctrls = 0, i = fe->cfg; i->type != C_END; i++)
2266 nctrls++;
2267 fe->cfgaux = snewn(nctrls, struct cfg_aux);
2268
2269 id = 1000;
2270 y = height/2;
2271 for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
2272 switch (i->type) {
2273 case C_STRING:
2274 /*
2275 * Edit box with a label beside it.
2276 */
2277 mkctrl(fe, col1l, col1r, y+height*1/8, y+height*9/8,
2278 "Static", 0, 0, i->name, id++);
2279 ctl = mkctrl(fe, col2l, col2r, y, y+height*3/2,
2280 "EDIT", WS_TABSTOP | ES_AUTOHSCROLL,
2281 WS_EX_CLIENTEDGE, "", (j->ctlid = id++));
2282 SetWindowText(ctl, i->u.string.sval);
2283 y += height*3/2;
2284 break;
2285
2286 case C_BOOLEAN:
2287 /*
2288 * Simple checkbox.
2289 */
2290 mkctrl(fe, col1l, col2r, y, y+height, "BUTTON",
2291 BS_NOTIFY | BS_AUTOCHECKBOX | WS_TABSTOP,
2292 0, i->name, (j->ctlid = id++));
2293 CheckDlgButton(fe->cfgbox, j->ctlid, i->u.boolean.bval);
2294 y += height;
2295 break;
2296
2297 case C_CHOICES:
2298 /*
2299 * Drop-down list with a label beside it.
2300 */
2301 mkctrl(fe, col1l, col1r, y+height*1/8, y+height*9/8,
2302 "STATIC", 0, 0, i->name, id++);
2303 ctl = mkctrl(fe, col2l, col2r, y, y+height*41/2,
2304 "COMBOBOX", WS_TABSTOP |
2305 CBS_DROPDOWNLIST | CBS_HASSTRINGS,
2306 WS_EX_CLIENTEDGE, "", (j->ctlid = id++));
2307 {
2308 char c;
2309 const char *p, *q;
2310 char *str;
2311
2312 SendMessage(ctl, CB_RESETCONTENT, 0, 0);
2313 p = i->u.choices.choicenames;
2314 c = *p++;
2315 while (*p) {
2316 q = p;
2317 while (*q && *q != c) q++;
2318 str = snewn(q-p+1, char);
2319 strncpy(str, p, q-p);
2320 str[q-p] = '\0';
2321 SendMessage(ctl, CB_ADDSTRING, 0, (LPARAM)str);
2322 sfree(str);
2323 if (*q) q++;
2324 p = q;
2325 }
2326 }
2327
2328 SendMessage(ctl, CB_SETCURSEL, i->u.choices.selected, 0);
2329
2330 y += height*3/2;
2331 break;
2332 }
2333
2334 assert(y < winheight);
2335 y += height/2;
2336 }
2337
2338 y += height/2; /* extra space before OK and Cancel */
2339 mkctrl(fe, col1l, (col1l+col2r)/2-width, y, y+height*7/4, "BUTTON",
2340 BS_PUSHBUTTON | WS_TABSTOP | BS_DEFPUSHBUTTON, 0,
2341 "OK", IDOK);
2342 mkctrl(fe, (col1l+col2r)/2+width, col2r, y, y+height*7/4, "BUTTON",
2343 BS_PUSHBUTTON | WS_TABSTOP, 0, "Cancel", IDCANCEL);
2344
2345 SendMessage(fe->cfgbox, WM_INITDIALOG, 0, 0);
2346
2347 EnableWindow(fe->hwnd, false);
2348 ShowWindow(fe->cfgbox, SW_SHOWNORMAL);
2349 while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
2350 if (!IsDialogMessage(fe->cfgbox, &msg))
2351 DispatchMessage(&msg);
2352 if (fe->dlg_done)
2353 break;
2354 }
2355 EnableWindow(fe->hwnd, true);
2356 SetForegroundWindow(fe->hwnd);
2357 DestroyWindow(fe->cfgbox);
2358 DeleteObject(fe->cfgfont);
2359
2360 free_cfg(fe->cfg);
2361 sfree(fe->cfgaux);
2362
2363 return (fe->dlg_done == 2);
2364}
2365
2366static void calculate_bitmap_position(frontend *fe, int x, int y)
2367{
2368 /* Plain Windows - position the game in the upper-left corner */
2369 fe->bitmapPosition.left = 0;
2370 fe->bitmapPosition.top = 0;
2371 fe->bitmapPosition.right = fe->bitmapPosition.left + x;
2372 fe->bitmapPosition.bottom = fe->bitmapPosition.top + y;
2373}
2374
2375static void new_bitmap(frontend *fe, int x, int y)
2376{
2377 HDC hdc;
2378
2379 if (fe->bitmap) DeleteObject(fe->bitmap);
2380
2381 hdc = GetDC(fe->hwnd);
2382 fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
2383 calculate_bitmap_position(fe, x, y);
2384 ReleaseDC(fe->hwnd, hdc);
2385}
2386
2387static void new_game_size(frontend *fe, float scale)
2388{
2389 RECT r, sr;
2390 int x, y;
2391
2392 get_max_puzzle_size(fe, &x, &y);
2393 midend_size(fe->me, &x, &y, false, 1.0);
2394
2395 if (scale != 1.0) {
2396 x = (int)((float)x * fe->puzz_scale);
2397 y = (int)((float)y * fe->puzz_scale);
2398 midend_size(fe->me, &x, &y, true, 1.0);
2399 }
2400 fe->ymin = (fe->xmin * y) / x;
2401
2402 r.left = r.top = 0;
2403 r.right = x;
2404 r.bottom = y;
2405 AdjustWindowRectEx(&r, WINFLAGS, true, 0);
2406
2407 if (fe->statusbar != NULL) {
2408 GetWindowRect(fe->statusbar, &sr);
2409 } else {
2410 sr.left = sr.right = sr.top = sr.bottom = 0;
2411 }
2412 SetWindowPos(fe->hwnd, NULL, 0, 0,
2413 r.right - r.left,
2414 r.bottom - r.top + sr.bottom - sr.top,
2415 SWP_NOMOVE | SWP_NOZORDER);
2416
2417 check_window_size(fe, &x, &y);
2418
2419 if (fe->statusbar != NULL)
2420 SetWindowPos(fe->statusbar, NULL, 0, y, x,
2421 sr.bottom - sr.top, SWP_NOZORDER);
2422
2423 new_bitmap(fe, x, y);
2424
2425 midend_redraw(fe->me);
2426}
2427
2428/*
2429 * Given a proposed new window rect, work out the resulting
2430 * difference in client size (from current), and use to try
2431 * and resize the puzzle, returning (wx,wy) as the actual
2432 * new window size.
2433 */
2434
2435static void adjust_game_size(frontend *fe, RECT *proposed, bool isedge,
2436 int *wx_r, int *wy_r)
2437{
2438 RECT cr, wr;
2439 int nx, ny, xdiff, ydiff, wx, wy;
2440
2441 /* Work out the current window sizing, and thus the
2442 * difference in size we're asking for. */
2443 GetClientRect(fe->hwnd, &cr);
2444 wr = cr;
2445 AdjustWindowRectEx(&wr, WINFLAGS, true, 0);
2446
2447 xdiff = (proposed->right - proposed->left) - (wr.right - wr.left);
2448 ydiff = (proposed->bottom - proposed->top) - (wr.bottom - wr.top);
2449
2450 if (isedge) {
2451 /* These next four lines work around the fact that midend_size
2452 * is happy to shrink _but not grow_ if you change one dimension
2453 * but not the other. */
2454 if (xdiff > 0 && ydiff == 0)
2455 ydiff = (xdiff * (wr.right - wr.left)) / (wr.bottom - wr.top);
2456 if (xdiff == 0 && ydiff > 0)
2457 xdiff = (ydiff * (wr.bottom - wr.top)) / (wr.right - wr.left);
2458 }
2459
2460 if (check_window_resize(fe,
2461 (cr.right - cr.left) + xdiff,
2462 (cr.bottom - cr.top) + ydiff,
2463 &nx, &ny, &wx, &wy)) {
2464 new_bitmap(fe, nx, ny);
2465 midend_force_redraw(fe->me);
2466 } else {
2467 /* reset size to current window size */
2468 wx = wr.right - wr.left;
2469 wy = wr.bottom - wr.top;
2470 }
2471 /* Re-fetch rectangle; size limits mean we might not have
2472 * taken it quite to the mouse drag positions. */
2473 GetClientRect(fe->hwnd, &cr);
2474 adjust_statusbar(fe, &cr);
2475
2476 *wx_r = wx; *wy_r = wy;
2477}
2478
2479static void update_type_menu_tick(frontend *fe)
2480{
2481 int total, n, i;
2482
2483 if (fe->typemenu == INVALID_HANDLE_VALUE)
2484 return;
2485
2486 n = midend_which_preset(fe->me);
2487
2488 for (i = 0; i < fe->n_preset_menuitems; i++) {
2489 if (fe->preset_menuitems[i].which_menu) {
2490 int flag = (i == n ? MF_CHECKED : MF_UNCHECKED);
2491 CheckMenuItem(fe->preset_menuitems[i].which_menu,
2492 fe->preset_menuitems[i].item_index,
2493 MF_BYPOSITION | flag);
2494 }
2495 }
2496
2497 if (fe->game->can_configure) {
2498 int flag = (n < 0 ? MF_CHECKED : MF_UNCHECKED);
2499 /* "Custom" menu item is at the bottom of the top-level Type menu */
2500 total = GetMenuItemCount(fe->typemenu);
2501 CheckMenuItem(fe->typemenu, total - 1, MF_BYPOSITION | flag);
2502 }
2503
2504 DrawMenuBar(fe->hwnd);
2505}
2506
2507static char *prefs_dir(void)
2508{
2509 const char *var;
2510 if ((var = getenv("APPDATA")) != NULL) {
2511 size_t size = strlen(var) + 80;
2512 char *dir = snewn(size, char);
2513 sprintf(dir, "%s\\Simon Tatham's Portable Puzzle Collection", var);
2514 return dir;
2515 }
2516 return NULL;
2517}
2518
2519static char *prefs_path_general(const game *game, const char *suffix)
2520{
2521 char *dir, *path;
2522
2523 dir = prefs_dir();
2524 if (!dir)
2525 return NULL;
2526
2527 path = make_prefs_path(dir, "\\", game, suffix);
2528
2529 sfree(dir);
2530 return path;
2531}
2532
2533static char *prefs_path(const game *game)
2534{
2535 return prefs_path_general(game, ".conf");
2536}
2537
2538static char *prefs_tmp_path(const game *game)
2539{
2540 return prefs_path_general(game, ".tmp");
2541}
2542
2543static void load_prefs(midend *me)
2544{
2545 const game *game = midend_which_game(me);
2546 char *path = prefs_path(game);
2547 if (!path)
2548 return;
2549 FILE *fp = fopen(path, "r");
2550 if (!fp)
2551 return;
2552 const char *err = midend_load_prefs(me, savefile_read, fp);
2553 fclose(fp);
2554 if (err)
2555 fprintf(stderr, "Unable to load preferences file %s:\n%s\n",
2556 path, err);
2557 sfree(path);
2558}
2559
2560static char *save_prefs(midend *me)
2561{
2562 const game *game = midend_which_game(me);
2563 char *dir_path = prefs_dir();
2564 char *file_path = prefs_path(game);
2565 char *tmp_path = prefs_tmp_path(game);
2566 HANDLE fh;
2567 FILE *fp;
2568 bool cleanup_dir = false, cleanup_tmpfile = false;
2569 char *err = NULL;
2570
2571 if (!dir_path || !file_path || !tmp_path) {
2572 sprintf(err = snewn(256, char),
2573 "Unable to save preferences:\n"
2574 "Could not determine pathname for configuration files");
2575 goto out;
2576 }
2577
2578 if (!CreateDirectory(dir_path, NULL)) {
2579 /* Ignore errors while trying to make the directory. It may
2580 * well already exist, and even if we got some error code
2581 * other than EEXIST, it's still worth at least _trying_ to
2582 * make the file inside it, and see if that goes wrong. */
2583 } else {
2584 cleanup_dir = true;
2585 }
2586
2587 fh = CreateFile(tmp_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
2588 FILE_ATTRIBUTE_NORMAL, NULL);
2589 if (fh == INVALID_HANDLE_VALUE) {
2590 char *os_err = geterrstr();
2591 sprintf(err = snewn(256 + strlen(tmp_path) + strlen(os_err), char),
2592 "Unable to save preferences:\n"
2593 "Unable to create file '%s':\n%s", tmp_path, os_err);
2594 sfree(os_err);
2595 goto out;
2596 } else {
2597 cleanup_tmpfile = true;
2598 }
2599
2600 fp = _fdopen(_open_osfhandle((intptr_t)fh, 0), "w");
2601 SetLastError(0);
2602 midend_save_prefs(me, savefile_write, fp);
2603 fclose(fp);
2604 if (GetLastError()) {
2605 char *os_err = geterrstr();
2606 sprintf(err = snewn(80 + strlen(tmp_path) + strlen(os_err), char),
2607 "Unable to write file '%s':\n%s", tmp_path, os_err);
2608 sfree(os_err);
2609 goto out;
2610 }
2611
2612 if (MoveFileEx(tmp_path, file_path, MOVEFILE_REPLACE_EXISTING) < 0) {
2613 char *os_err = geterrstr();
2614 sprintf(err = snewn(256 + strlen(tmp_path) + strlen(file_path) +
2615 strlen(os_err), char),
2616 "Unable to save preferences:\n"
2617 "Unable to rename '%s' to '%s':\n%s", tmp_path, file_path,
2618 os_err);
2619 sfree(os_err);
2620 goto out;
2621 } else {
2622 cleanup_dir = false;
2623 cleanup_tmpfile = false;
2624 }
2625
2626 out:
2627 if (cleanup_tmpfile) {
2628 if (!DeleteFile(tmp_path)) { /* can't do anything about this */ }
2629 }
2630 if (cleanup_dir) {
2631 if (!RemoveDirectory(dir_path)) { /* can't do anything about this */ }
2632 }
2633 sfree(dir_path);
2634 sfree(file_path);
2635 sfree(tmp_path);
2636 return err;
2637}
2638
2639static void update_copy_menu_greying(frontend *fe)
2640{
2641 UINT enable = (midend_can_format_as_text_now(fe->me) ?
2642 MF_ENABLED : MF_GRAYED);
2643 EnableMenuItem(fe->gamemenu, IDM_COPY, MF_BYCOMMAND | enable);
2644}
2645
2646static void new_game_type(frontend *fe)
2647{
2648 midend_new_game(fe->me);
2649 new_game_size(fe, 1.0);
2650 update_type_menu_tick(fe);
2651 update_copy_menu_greying(fe);
2652}
2653
2654static bool is_alt_pressed(void)
2655{
2656 BYTE keystate[256];
2657 int r = GetKeyboardState(keystate);
2658 if (!r)
2659 return false;
2660 if (keystate[VK_MENU] & 0x80)
2661 return true;
2662 if (keystate[VK_RMENU] & 0x80)
2663 return true;
2664 return false;
2665}
2666
2667static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
2668 WPARAM wParam, LPARAM lParam)
2669{
2670 frontend *fe = (frontend *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
2671 int cmd;
2672
2673 switch (message) {
2674 case WM_CLOSE:
2675 DestroyWindow(hwnd);
2676 return 0;
2677 case WM_COMMAND:
2678 cmd = wParam & ~0xF; /* low 4 bits reserved to Windows */
2679 switch (cmd) {
2680 case IDM_NEW:
2681 if (midend_process_key(fe->me, 0, 0, UI_NEWGAME) == PKR_QUIT)
2682 PostQuitMessage(0);
2683 break;
2684 case IDM_RESTART:
2685 midend_restart_game(fe->me);
2686 break;
2687 case IDM_UNDO:
2688 if (midend_process_key(fe->me, 0, 0, UI_UNDO) == PKR_QUIT)
2689 PostQuitMessage(0);
2690 break;
2691 case IDM_REDO:
2692 if (midend_process_key(fe->me, 0, 0, UI_REDO) == PKR_QUIT)
2693 PostQuitMessage(0);
2694 break;
2695 case IDM_COPY:
2696 {
2697 char *text = midend_text_format(fe->me);
2698 if (text)
2699 write_clip(hwnd, text);
2700 else
2701 MessageBeep(MB_ICONWARNING);
2702 sfree(text);
2703 }
2704 break;
2705 case IDM_SOLVE:
2706 {
2707 const char *msg = midend_solve(fe->me);
2708 if (msg)
2709 MessageBox(hwnd, msg, "Unable to solve",
2710 MB_ICONERROR | MB_OK);
2711 }
2712 break;
2713 case IDM_QUIT:
2714 if (midend_process_key(fe->me, 0, 0, UI_QUIT) == PKR_QUIT)
2715 PostQuitMessage(0);
2716 break;
2717 case IDM_CONFIG:
2718 if (get_config(fe, CFG_SETTINGS))
2719 new_game_type(fe);
2720 break;
2721 case IDM_SEED:
2722 if (get_config(fe, CFG_SEED))
2723 new_game_type(fe);
2724 break;
2725 case IDM_DESC:
2726 if (get_config(fe, CFG_DESC))
2727 new_game_type(fe);
2728 break;
2729 case IDM_PRINT:
2730 if (get_config(fe, CFG_PRINT))
2731 print(fe);
2732 break;
2733 case IDM_PREFS:
2734 if (get_config(fe, CFG_PREFS)) {
2735 char *prefs_err = save_prefs(fe->me);
2736 if (prefs_err) {
2737 MessageBox(fe->hwnd, prefs_err, "Error saving preferences",
2738 MB_ICONERROR | MB_OK);
2739 sfree(prefs_err);
2740 }
2741 }
2742 break;
2743 case IDM_ABOUT:
2744 about(fe);
2745 break;
2746 case IDM_LOAD:
2747 case IDM_SAVE:
2748 {
2749 OPENFILENAME of;
2750 char filename[FILENAME_MAX];
2751 int ret;
2752
2753 memset(&of, 0, sizeof(of));
2754 of.hwndOwner = hwnd;
2755 of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
2756 of.lpstrCustomFilter = NULL;
2757 of.nFilterIndex = 1;
2758 of.lpstrFile = filename;
2759 filename[0] = '\0';
2760 of.nMaxFile = lenof(filename);
2761 of.lpstrFileTitle = NULL;
2762 of.lpstrTitle = (cmd == IDM_SAVE ?
2763 "Enter name of game file to save" :
2764 "Enter name of saved game file to load");
2765 of.Flags = 0;
2766#ifdef OPENFILENAME_SIZE_VERSION_400
2767 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
2768#else
2769 of.lStructSize = sizeof(of);
2770#endif
2771 of.lpstrInitialDir = NULL;
2772
2773 if (cmd == IDM_SAVE)
2774 ret = GetSaveFileName(&of);
2775 else
2776 ret = GetOpenFileName(&of);
2777
2778 if (ret) {
2779 if (cmd == IDM_SAVE) {
2780 FILE *fp;
2781
2782 if ((fp = fopen(filename, "r")) != NULL) {
2783 char buf[256 + FILENAME_MAX];
2784 fclose(fp);
2785 /* file exists */
2786
2787 sprintf(buf, "Are you sure you want to overwrite"
2788 " the file \"%.*s\"?",
2789 FILENAME_MAX, filename);
2790 if (MessageBox(hwnd, buf, "Question",
2791 MB_YESNO | MB_ICONQUESTION)
2792 != IDYES)
2793 break;
2794 }
2795
2796 fp = fopen(filename, "w");
2797
2798 if (!fp) {
2799 MessageBox(hwnd, "Unable to open save file",
2800 "Error", MB_ICONERROR | MB_OK);
2801 break;
2802 }
2803
2804 midend_serialise(fe->me, savefile_write, fp);
2805
2806 fclose(fp);
2807 } else {
2808 FILE *fp = fopen(filename, "r");
2809 const char *err = NULL;
2810 char *err_w = NULL;
2811 midend *me = fe->me;
2812#ifdef COMBINED
2813 char *id_name;
2814#endif
2815
2816 if (!fp) {
2817 MessageBox(hwnd, "Unable to open saved game file",
2818 "Error", MB_ICONERROR | MB_OK);
2819 break;
2820 }
2821
2822#ifdef COMBINED
2823 /*
2824 * This save file might be from a different
2825 * game.
2826 */
2827 err = identify_game(&id_name, savefile_read, fp);
2828 if (!err) {
2829 int i;
2830 for (i = 0; i < gamecount; i++)
2831 if (!strcmp(id_name, gamelist[i]->name))
2832 break;
2833 if (i == gamecount) {
2834 err = "Save file is for a game not "
2835 "supported by this program";
2836 } else {
2837 me = midend_for_new_game(fe, gamelist[i], NULL,
2838 false, false, &err_w);
2839 err = err_w;
2840 rewind(fp); /* for the actual load */
2841 }
2842 sfree(id_name);
2843 }
2844#endif
2845 if (!err)
2846 err = midend_deserialise(me, savefile_read, fp);
2847
2848 fclose(fp);
2849
2850 if (err) {
2851 MessageBox(hwnd, err, "Error", MB_ICONERROR|MB_OK);
2852 sfree(err_w);
2853 break;
2854 }
2855
2856 if (fe->me != me)
2857 fe_set_midend(fe, me);
2858 new_game_size(fe, 1.0);
2859 }
2860 }
2861 }
2862
2863 break;
2864 case IDM_HELPC:
2865 start_help(fe, NULL);
2866 break;
2867 case IDM_GAMEHELP:
2868 assert(help_type != NONE);
2869 start_help(fe, help_type == CHM ?
2870 fe->game->htmlhelp_topic : fe->game->winhelp_topic);
2871 break;
2872 default: {
2873 unsigned n;
2874
2875#ifdef COMBINED
2876 n = (wParam - IDM_GAME_BASE) / MENUITEM_STEP;
2877 if (n < gamecount && wParam == IDM_GAME_BASE + MENUITEM_STEP * n) {
2878 char *error = NULL;
2879 fe_set_midend(fe, midend_for_new_game(fe, gamelist[n], NULL,
2880 false, false, &error));
2881 sfree(error);
2882 break;
2883 }
2884#endif
2885
2886 n = (wParam - IDM_PRESET_BASE) / MENUITEM_STEP;
2887 if (wParam == IDM_PRESET_BASE + MENUITEM_STEP * n) {
2888 game_params *preset = preset_menu_lookup_by_id(
2889 fe->preset_menu, n);
2890
2891 if (preset) {
2892 midend_set_params(fe->me, preset);
2893 new_game_type(fe);
2894 break;
2895 }
2896 }
2897
2898 break;
2899 }
2900 }
2901 break;
2902 case WM_DESTROY:
2903 stop_help(fe);
2904 frontend_free(fe);
2905 PostQuitMessage(0);
2906 return 0;
2907 case WM_PAINT:
2908 {
2909 PAINTSTRUCT p;
2910 HDC hdc, hdc2;
2911 HBITMAP prevbm;
2912 RECT rcDest;
2913
2914 hdc = BeginPaint(hwnd, &p);
2915 hdc2 = CreateCompatibleDC(hdc);
2916 prevbm = SelectObject(hdc2, fe->bitmap);
2917 IntersectRect(&rcDest, &(fe->bitmapPosition), &(p.rcPaint));
2918 BitBlt(hdc,
2919 rcDest.left, rcDest.top,
2920 rcDest.right - rcDest.left,
2921 rcDest.bottom - rcDest.top,
2922 hdc2,
2923 rcDest.left - fe->bitmapPosition.left,
2924 rcDest.top - fe->bitmapPosition.top,
2925 SRCCOPY);
2926 SelectObject(hdc2, prevbm);
2927 DeleteDC(hdc2);
2928 EndPaint(hwnd, &p);
2929 }
2930 return 0;
2931 case WM_KEYDOWN:
2932 {
2933 int key = -1;
2934 BYTE keystate[256];
2935 int r = GetKeyboardState(keystate);
2936 int shift = (r && (keystate[VK_SHIFT] & 0x80)) ? MOD_SHFT : 0;
2937 int ctrl = (r && (keystate[VK_CONTROL] & 0x80)) ? MOD_CTRL : 0;
2938
2939 switch (wParam) {
2940 case VK_LEFT:
2941 if (!(lParam & 0x01000000))
2942 key = MOD_NUM_KEYPAD | '4';
2943 else
2944 key = shift | ctrl | CURSOR_LEFT;
2945 break;
2946 case VK_RIGHT:
2947 if (!(lParam & 0x01000000))
2948 key = MOD_NUM_KEYPAD | '6';
2949 else
2950 key = shift | ctrl | CURSOR_RIGHT;
2951 break;
2952 case VK_UP:
2953 if (!(lParam & 0x01000000))
2954 key = MOD_NUM_KEYPAD | '8';
2955 else
2956 key = shift | ctrl | CURSOR_UP;
2957 break;
2958 case VK_DOWN:
2959 if (!(lParam & 0x01000000))
2960 key = MOD_NUM_KEYPAD | '2';
2961 else
2962 key = shift | ctrl | CURSOR_DOWN;
2963 break;
2964 /*
2965 * Diagonal keys on the numeric keypad.
2966 */
2967 case VK_PRIOR:
2968 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '9';
2969 break;
2970 case VK_NEXT:
2971 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '3';
2972 break;
2973 case VK_HOME:
2974 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '7';
2975 break;
2976 case VK_END:
2977 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '1';
2978 break;
2979 case VK_INSERT:
2980 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '0';
2981 break;
2982 case VK_CLEAR:
2983 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '5';
2984 break;
2985 /*
2986 * Numeric keypad keys with Num Lock on.
2987 */
2988 case VK_NUMPAD4: key = MOD_NUM_KEYPAD | '4'; break;
2989 case VK_NUMPAD6: key = MOD_NUM_KEYPAD | '6'; break;
2990 case VK_NUMPAD8: key = MOD_NUM_KEYPAD | '8'; break;
2991 case VK_NUMPAD2: key = MOD_NUM_KEYPAD | '2'; break;
2992 case VK_NUMPAD5: key = MOD_NUM_KEYPAD | '5'; break;
2993 case VK_NUMPAD9: key = MOD_NUM_KEYPAD | '9'; break;
2994 case VK_NUMPAD3: key = MOD_NUM_KEYPAD | '3'; break;
2995 case VK_NUMPAD7: key = MOD_NUM_KEYPAD | '7'; break;
2996 case VK_NUMPAD1: key = MOD_NUM_KEYPAD | '1'; break;
2997 case VK_NUMPAD0: key = MOD_NUM_KEYPAD | '0'; break;
2998 }
2999
3000 if (key != -1) {
3001 if (midend_process_key(fe->me, 0, 0, key) == PKR_QUIT)
3002 PostQuitMessage(0);
3003 } else {
3004 MSG m;
3005 m.hwnd = hwnd;
3006 m.message = WM_KEYDOWN;
3007 m.wParam = wParam;
3008 m.lParam = lParam & 0xdfff;
3009 TranslateMessage(&m);
3010 }
3011 }
3012 break;
3013 case WM_LBUTTONDOWN:
3014 case WM_RBUTTONDOWN:
3015 case WM_MBUTTONDOWN:
3016 {
3017 int button;
3018
3019 /*
3020 * Shift-clicks count as middle-clicks, since otherwise
3021 * two-button Windows users won't have any kind of
3022 * middle click to use.
3023 */
3024 if (message == WM_MBUTTONDOWN || (wParam & MK_SHIFT))
3025 button = MIDDLE_BUTTON;
3026 else if (message == WM_RBUTTONDOWN || is_alt_pressed())
3027 button = RIGHT_BUTTON;
3028 else
3029 button = LEFT_BUTTON;
3030
3031 if (midend_process_key(fe->me,
3032 (signed short)LOWORD(lParam) - fe->bitmapPosition.left,
3033 (signed short)HIWORD(lParam) - fe->bitmapPosition.top,
3034 button) == PKR_QUIT)
3035 PostQuitMessage(0);
3036
3037 SetCapture(hwnd);
3038 }
3039 break;
3040 case WM_LBUTTONUP:
3041 case WM_RBUTTONUP:
3042 case WM_MBUTTONUP:
3043 {
3044 int button;
3045
3046 /*
3047 * Shift-clicks count as middle-clicks, since otherwise
3048 * two-button Windows users won't have any kind of
3049 * middle click to use.
3050 */
3051 if (message == WM_MBUTTONUP || (wParam & MK_SHIFT))
3052 button = MIDDLE_RELEASE;
3053 else if (message == WM_RBUTTONUP || is_alt_pressed())
3054 button = RIGHT_RELEASE;
3055 else
3056 button = LEFT_RELEASE;
3057
3058 if (midend_process_key(fe->me,
3059 (signed short)LOWORD(lParam) - fe->bitmapPosition.left,
3060 (signed short)HIWORD(lParam) - fe->bitmapPosition.top,
3061 button) == PKR_QUIT)
3062 PostQuitMessage(0);
3063
3064 ReleaseCapture();
3065 }
3066 break;
3067 case WM_MOUSEMOVE:
3068 {
3069 int button;
3070
3071 if (wParam & (MK_MBUTTON | MK_SHIFT))
3072 button = MIDDLE_DRAG;
3073 else if (wParam & MK_RBUTTON || is_alt_pressed())
3074 button = RIGHT_DRAG;
3075 else
3076 button = LEFT_DRAG;
3077
3078 if (midend_process_key(fe->me,
3079 (signed short)LOWORD(lParam) - fe->bitmapPosition.left,
3080 (signed short)HIWORD(lParam) - fe->bitmapPosition.top,
3081 button) == PKR_QUIT)
3082 PostQuitMessage(0);
3083 }
3084 break;
3085 case WM_CHAR:
3086 {
3087 int key = (unsigned char)wParam;
3088 if (key == '\x1A') {
3089 BYTE keystate[256];
3090 if (GetKeyboardState(keystate) &&
3091 (keystate[VK_SHIFT] & 0x80) &&
3092 (keystate[VK_CONTROL] & 0x80))
3093 key = UI_REDO;
3094 }
3095 if (midend_process_key(fe->me, 0, 0, key) == PKR_QUIT)
3096 PostQuitMessage(0);
3097 }
3098 return 0;
3099 case WM_TIMER:
3100 if (fe->timer) {
3101 DWORD now = GetTickCount();
3102 float elapsed = (float) (now - fe->timer_last_tickcount) * 0.001F;
3103 midend_timer(fe->me, elapsed);
3104 fe->timer_last_tickcount = now;
3105 }
3106 return 0;
3107 case WM_SIZING:
3108 {
3109 RECT *sr = (RECT *)lParam;
3110 int wx, wy;
3111 bool isedge = false;
3112
3113 if (wParam == WMSZ_TOP ||
3114 wParam == WMSZ_RIGHT ||
3115 wParam == WMSZ_BOTTOM ||
3116 wParam == WMSZ_LEFT) isedge = true;
3117 adjust_game_size(fe, sr, isedge, &wx, &wy);
3118
3119 /* Given the window size the puzzles constrain
3120 * us to, work out which edge we should be moving. */
3121 if (wParam == WMSZ_TOP ||
3122 wParam == WMSZ_TOPLEFT ||
3123 wParam == WMSZ_TOPRIGHT) {
3124 sr->top = sr->bottom - wy;
3125 } else {
3126 sr->bottom = sr->top + wy;
3127 }
3128 if (wParam == WMSZ_LEFT ||
3129 wParam == WMSZ_TOPLEFT ||
3130 wParam == WMSZ_BOTTOMLEFT) {
3131 sr->left = sr->right - wx;
3132 } else {
3133 sr->right = sr->left + wx;
3134 }
3135 return true;
3136 }
3137 break;
3138 }
3139
3140 return DefWindowProc(hwnd, message, wParam, lParam);
3141}
3142
3143/*
3144 * Split a complete command line into argc/argv, attempting to do it
3145 * exactly the same way the Visual Studio C library would do it (so
3146 * that our console utilities, which receive argc and argv already
3147 * broken apart by the C library, will have their command lines
3148 * processed in the same way as the GUI utilities which get a whole
3149 * command line and must call this function).
3150 *
3151 * Does not modify the input command line.
3152 *
3153 * The final parameter (argstart) is used to return a second array
3154 * of char * pointers, the same length as argv, each one pointing
3155 * at the start of the corresponding element of argv in the
3156 * original command line. So if you get half way through processing
3157 * your command line in argc/argv form and then decide you want to
3158 * treat the rest as a raw string, you can. If you don't want to,
3159 * `argstart' can be safely left NULL.
3160 */
3161void split_into_argv(char *cmdline, int *argc, char ***argv,
3162 char ***argstart)
3163{
3164 char *p;
3165 char *outputline, *q;
3166 char **outputargv, **outputargstart;
3167 int outputargc;
3168
3169 /*
3170 * These argument-breaking rules apply to Visual Studio 7, which
3171 * is currently the compiler expected to be used for the Windows
3172 * port of my puzzles. Visual Studio 10 has different rules,
3173 * lacking the curious mod 3 behaviour of consecutive quotes
3174 * described below; I presume they fixed a bug. As and when we
3175 * migrate to a newer compiler, we'll have to adjust this to
3176 * match; however, for the moment we faithfully imitate in our GUI
3177 * utilities what our CLI utilities can't be prevented from doing.
3178 *
3179 * When I investigated this, at first glance the rules appeared to
3180 * be:
3181 *
3182 * - Single quotes are not special characters.
3183 *
3184 * - Double quotes are removed, but within them spaces cease
3185 * to be special.
3186 *
3187 * - Backslashes are _only_ special when a sequence of them
3188 * appear just before a double quote. In this situation,
3189 * they are treated like C backslashes: so \" just gives a
3190 * literal quote, \\" gives a literal backslash and then
3191 * opens or closes a double-quoted segment, \\\" gives a
3192 * literal backslash and then a literal quote, \\\\" gives
3193 * two literal backslashes and then opens/closes a
3194 * double-quoted segment, and so forth. Note that this
3195 * behaviour is identical inside and outside double quotes.
3196 *
3197 * - Two successive double quotes become one literal double
3198 * quote, but only _inside_ a double-quoted segment.
3199 * Outside, they just form an empty double-quoted segment
3200 * (which may cause an empty argument word).
3201 *
3202 * - That only leaves the interesting question of what happens
3203 * when one or more backslashes precedes two or more double
3204 * quotes, starting inside a double-quoted string. And the
3205 * answer to that appears somewhat bizarre. Here I tabulate
3206 * number of backslashes (across the top) against number of
3207 * quotes (down the left), and indicate how many backslashes
3208 * are output, how many quotes are output, and whether a
3209 * quoted segment is open at the end of the sequence:
3210 *
3211 * backslashes
3212 *
3213 * 0 1 2 3 4
3214 *
3215 * 0 0,0,y | 1,0,y 2,0,y 3,0,y 4,0,y
3216 * --------+-----------------------------
3217 * 1 0,0,n | 0,1,y 1,0,n 1,1,y 2,0,n
3218 * q 2 0,1,n | 0,1,n 1,1,n 1,1,n 2,1,n
3219 * u 3 0,1,y | 0,2,n 1,1,y 1,2,n 2,1,y
3220 * o 4 0,1,n | 0,2,y 1,1,n 1,2,y 2,1,n
3221 * t 5 0,2,n | 0,2,n 1,2,n 1,2,n 2,2,n
3222 * e 6 0,2,y | 0,3,n 1,2,y 1,3,n 2,2,y
3223 * s 7 0,2,n | 0,3,y 1,2,n 1,3,y 2,2,n
3224 * 8 0,3,n | 0,3,n 1,3,n 1,3,n 2,3,n
3225 * 9 0,3,y | 0,4,n 1,3,y 1,4,n 2,3,y
3226 * 10 0,3,n | 0,4,y 1,3,n 1,4,y 2,3,n
3227 * 11 0,4,n | 0,4,n 1,4,n 1,4,n 2,4,n
3228 *
3229 *
3230 * [Test fragment was of the form "a\\\"""b c" d.]
3231 *
3232 * There is very weird mod-3 behaviour going on here in the
3233 * number of quotes, and it even applies when there aren't any
3234 * backslashes! How ghastly.
3235 *
3236 * With a bit of thought, this extremely odd diagram suddenly
3237 * coalesced itself into a coherent, if still ghastly, model of
3238 * how things work:
3239 *
3240 * - As before, backslashes are only special when one or more
3241 * of them appear contiguously before at least one double
3242 * quote. In this situation the backslashes do exactly what
3243 * you'd expect: each one quotes the next thing in front of
3244 * it, so you end up with n/2 literal backslashes (if n is
3245 * even) or (n-1)/2 literal backslashes and a literal quote
3246 * (if n is odd). In the latter case the double quote
3247 * character right after the backslashes is used up.
3248 *
3249 * - After that, any remaining double quotes are processed. A
3250 * string of contiguous unescaped double quotes has a mod-3
3251 * behaviour:
3252 *
3253 * * inside a quoted segment, a quote ends the segment.
3254 * * _immediately_ after ending a quoted segment, a quote
3255 * simply produces a literal quote.
3256 * * otherwise, outside a quoted segment, a quote begins a
3257 * quoted segment.
3258 *
3259 * So, for example, if we started inside a quoted segment
3260 * then two contiguous quotes would close the segment and
3261 * produce a literal quote; three would close the segment,
3262 * produce a literal quote, and open a new segment. If we
3263 * started outside a quoted segment, then two contiguous
3264 * quotes would open and then close a segment, producing no
3265 * output (but potentially creating a zero-length argument);
3266 * but three quotes would open and close a segment and then
3267 * produce a literal quote.
3268 */
3269
3270 /*
3271 * First deal with the simplest of all special cases: if there
3272 * aren't any arguments, return 0,NULL,NULL.
3273 */
3274 while (*cmdline && isspace(*cmdline)) cmdline++;
3275 if (!*cmdline) {
3276 if (argc) *argc = 0;
3277 if (argv) *argv = NULL;
3278 if (argstart) *argstart = NULL;
3279 return;
3280 }
3281
3282 /*
3283 * This will guaranteeably be big enough; we can realloc it
3284 * down later.
3285 */
3286 outputline = snewn(1+strlen(cmdline), char);
3287 outputargv = snewn(strlen(cmdline)+1 / 2, char *);
3288 outputargstart = snewn(strlen(cmdline)+1 / 2, char *);
3289
3290 p = cmdline; q = outputline; outputargc = 0;
3291
3292 while (*p) {
3293 bool quote;
3294
3295 /* Skip whitespace searching for start of argument. */
3296 while (*p && isspace(*p)) p++;
3297 if (!*p) break;
3298
3299 /* We have an argument; start it. */
3300 outputargv[outputargc] = q;
3301 outputargstart[outputargc] = p;
3302 outputargc++;
3303 quote = false;
3304
3305 /* Copy data into the argument until it's finished. */
3306 while (*p) {
3307 if (!quote && isspace(*p))
3308 break; /* argument is finished */
3309
3310 if (*p == '"' || *p == '\\') {
3311 /*
3312 * We have a sequence of zero or more backslashes
3313 * followed by a sequence of zero or more quotes.
3314 * Count up how many of each, and then deal with
3315 * them as appropriate.
3316 */
3317 int i, slashes = 0, quotes = 0;
3318 while (*p == '\\') slashes++, p++;
3319 while (*p == '"') quotes++, p++;
3320
3321 if (!quotes) {
3322 /*
3323 * Special case: if there are no quotes,
3324 * slashes are not special at all, so just copy
3325 * n slashes to the output string.
3326 */
3327 while (slashes--) *q++ = '\\';
3328 } else {
3329 /* Slashes annihilate in pairs. */
3330 while (slashes >= 2) slashes -= 2, *q++ = '\\';
3331
3332 /* One remaining slash takes out the first quote. */
3333 if (slashes) quotes--, *q++ = '"';
3334
3335 if (quotes > 0) {
3336 /* Outside a quote segment, a quote starts one. */
3337 if (!quote) quotes--, quote = true;
3338
3339 /* Now we produce (n+1)/3 literal quotes... */
3340 for (i = 3; i <= quotes+1; i += 3) *q++ = '"';
3341
3342 /* ... and end in a quote segment iff 3 divides n. */
3343 quote = (quotes % 3 == 0);
3344 }
3345 }
3346 } else {
3347 *q++ = *p++;
3348 }
3349 }
3350
3351 /* At the end of an argument, just append a trailing NUL. */
3352 *q++ = '\0';
3353 }
3354
3355 outputargv = sresize(outputargv, outputargc, char *);
3356 outputargstart = sresize(outputargstart, outputargc, char *);
3357
3358 if (argc) *argc = outputargc;
3359 if (argv) *argv = outputargv; else sfree(outputargv);
3360 if (argstart) *argstart = outputargstart; else sfree(outputargstart);
3361}
3362
3363int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
3364{
3365 MSG msg;
3366 char *error = NULL;
3367 const game *gg;
3368 frontend *fe;
3369 midend *me;
3370 int argc;
3371 char **argv;
3372
3373 split_into_argv(cmdline, &argc, &argv, NULL);
3374
3375 InitCommonControls();
3376
3377 if (!prev) {
3378 WNDCLASS wndclass;
3379
3380 wndclass.style = 0;
3381 wndclass.lpfnWndProc = WndProc;
3382 wndclass.cbClsExtra = 0;
3383 wndclass.cbWndExtra = 0;
3384 wndclass.hInstance = inst;
3385 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(200));
3386 if (!wndclass.hIcon) /* in case resource file is absent */
3387 wndclass.hIcon = LoadIcon(inst, IDI_APPLICATION);
3388 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
3389 wndclass.hbrBackground = NULL;
3390 wndclass.lpszMenuName = NULL;
3391 wndclass.lpszClassName = CLASSNAME;
3392
3393 RegisterClass(&wndclass);
3394 }
3395
3396 while (*cmdline && isspace((unsigned char)*cmdline))
3397 cmdline++;
3398
3399 init_help();
3400
3401#ifdef COMBINED
3402 gg = gamelist[0];
3403 if (argc > 0) {
3404 int i;
3405 for (i = 0; i < gamecount; i++) {
3406 const char *p = gamelist[i]->name;
3407 char *q = argv[0];
3408 while (*p && *q) {
3409 if (isspace((unsigned char)*p)) {
3410 while (*q && isspace((unsigned char)*q))
3411 q++;
3412 } else {
3413 if (tolower((unsigned char)*p) !=
3414 tolower((unsigned char)*q))
3415 break;
3416 q++;
3417 }
3418 p++;
3419 }
3420 if (!*p) {
3421 gg = gamelist[i];
3422 --argc;
3423 ++argv;
3424 break;
3425 }
3426 }
3427 }
3428#else
3429 gg = &thegame;
3430#endif
3431
3432 fe = frontend_new(inst);
3433 me = midend_for_new_game(fe, gg, argc > 0 ? argv[0] : NULL,
3434 true, true, &error);
3435 if (!me) {
3436 char buf[128];
3437#ifdef COMBINED
3438 sprintf(buf, "Puzzles Error");
3439#else
3440 sprintf(buf, "%.100s Error", gg->name);
3441#endif
3442 MessageBox(NULL, error, buf, MB_OK|MB_ICONERROR);
3443 sfree(error);
3444 return 1;
3445 }
3446 fe_set_midend(fe, me);
3447 show_window(fe);
3448
3449 while (GetMessage(&msg, NULL, 0, 0)) {
3450 DispatchMessage(&msg);
3451 }
3452
3453 DestroyWindow(fe->hwnd);
3454 cleanup_help();
3455
3456 return msg.wParam;
3457}
3458/* vim: set shiftwidth=4 tabstop=8: */