diff options
Diffstat (limited to 'apps')
-rw-r--r-- | apps/plugins/puzzles/SOURCES | 16 | ||||
-rw-r--r-- | apps/plugins/puzzles/SOURCES.games | 2 | ||||
-rw-r--r-- | apps/plugins/puzzles/SOURCES.rockbox | 4 | ||||
-rwxr-xr-x | apps/plugins/puzzles/resync.sh | 45 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/devel.but | 6122 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/emcc.c | 1149 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/emcccopy.but | 128 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/fuzzpuzz.c | 250 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/gtk.c | 4399 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/list.c | 17 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/malloc.c | 64 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/nestedvm.c | 486 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/no-icon.c | 10 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/nullfe.c | 79 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/nullgame.c | 263 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/osx-help.but | 14 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/ps.c | 432 | ||||
-rw-r--r-- | apps/plugins/puzzles/src/windows.c | 3458 |
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 */ | ||
1 | rockbox.c | 2 | rockbox.c |
2 | rbwrappers.c | 3 | rbwrappers.c |
3 | rbmalloc.c | 4 | rbmalloc.c |
4 | lz4tiny.c | 5 | lz4tiny.c |
5 | 6 | ||
7 | /* puzzles core sources */ | ||
6 | src/combi.c | 8 | src/combi.c |
7 | src/divvy.c | 9 | src/divvy.c |
8 | src/drawing.c | 10 | src/drawing.c |
9 | src/dsf.c | 11 | src/dsf.c |
10 | src/findloop.c | 12 | src/findloop.c |
11 | src/grid.c | 13 | src/grid.c |
14 | src/hat.c | ||
12 | src/latin.c | 15 | src/latin.c |
13 | src/laydomino.c | 16 | src/laydomino.c |
14 | src/loopgen.c | 17 | src/loopgen.c |
15 | /*src/malloc.c*/ /* we have our own */ | ||
16 | src/matching.c | 18 | src/matching.c |
17 | src/midend.c | 19 | src/midend.c |
18 | src/misc.c | 20 | src/misc.c |
19 | src/penrose.c | ||
20 | src/penrose-legacy.c | 21 | src/penrose-legacy.c |
21 | src/printing.c | 22 | src/penrose.c |
22 | src/random.c | 23 | src/random.c |
23 | src/sort.c | 24 | src/sort.c |
25 | src/spectre.c | ||
24 | src/tdq.c | 26 | src/tdq.c |
25 | src/tree234.c | 27 | src/tree234.c |
26 | src/version.c | 28 | src/version.c |
27 | 29 | src/printing.c | |
28 | src/hat.c | ||
29 | src/spectre.c | ||
30 | |||
31 | #ifdef COMBINED | ||
32 | src/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 | |||
3 | src/blackbox.c | 1 | src/blackbox.c |
4 | src/bridges.c | 2 | src/bridges.c |
5 | src/cube.c | 3 | src/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 @@ | |||
1 | rockbox.c | ||
2 | rbwrappers.c | ||
3 | rbmalloc.c | ||
4 | lz4tiny.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 | |||
25 | if [ "YES" == $ans ] | 25 | if [ "YES" == $ans ] |
26 | then | 26 | then |
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 | ||
63 | src/loopy.c | ||
64 | src/pearl.c | ||
65 | src/solo.c | ||
66 | #endif | ||
67 | EOF | ||
68 | |||
69 | cat <<EOF > SOURCES | ||
70 | /* Auto-generated by resync.sh */ | ||
71 | EOF | ||
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 | |||
30 | This is a guide to the internal structure of Simon Tatham's Portable | ||
31 | Puzzle Collection (henceforth referred to simply as \q{Puzzles}), | ||
32 | for use by anyone attempting to implement a new puzzle or port to a | ||
33 | new platform. | ||
34 | |||
35 | This guide is believed correct as of \cw{git} commit | ||
36 | \cw{a2212e82aa2f4b9a4ee22783d6fed2761c213432}. Hopefully it will be | ||
37 | updated along with the code in future, but if not, I've at least left | ||
38 | this version number in here so you can figure out what's changed by | ||
39 | tracking commit comments from there onwards. | ||
40 | |||
41 | \C{intro} Introduction | ||
42 | |||
43 | The Puzzles code base is divided into four parts: a set of | ||
44 | interchangeable front ends, a set of interchangeable back ends, a | ||
45 | universal \q{middle end} which acts as a buffer between the two, and | ||
46 | a bunch of miscellaneous utility functions. In the following | ||
47 | sections I give some general discussion of each of these parts. | ||
48 | |||
49 | \H{intro-frontend} Front end | ||
50 | |||
51 | The front end is the non-portable part of the code: it's the bit | ||
52 | that you replace completely when you port to a different platform. | ||
53 | So it's responsible for all system calls, all GUI interaction, and | ||
54 | anything else platform-specific. | ||
55 | |||
56 | The front end contains \cw{main()} or the local platform's | ||
57 | equivalent. Top-level control over the application's execution flow | ||
58 | belongs to the front end (it isn't, for example, a set of functions | ||
59 | called by a universal \cw{main()} somewhere else). | ||
60 | |||
61 | The front end has complete freedom to design the GUI for any given | ||
62 | port of Puzzles. There is no centralised mechanism for maintaining the | ||
63 | menu 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 | ||
65 | edit N pieces of code in parallel every time I make a change), but the | ||
66 | advantage is that local GUI conventions can be conformed to and local | ||
67 | constraints adapted to. For example, MacOS has strict human interface | ||
68 | guidelines which specify a different menu layout from the one I've | ||
69 | used on Windows and GTK; there's nothing stopping the MacOS front end | ||
70 | from providing a menu layout consistent with those guidelines. | ||
71 | |||
72 | Although the front end is mostly caller rather than the callee in | ||
73 | its interactions with other parts of the code, it is required to | ||
74 | implement a small API for other modules to call, mostly of drawing | ||
75 | functions for games to use when drawing their graphics. The drawing | ||
76 | API is documented in \k{drawing}; the other miscellaneous front end | ||
77 | API functions are documented in \k{frontend-api}. | ||
78 | |||
79 | \H{intro-backend} Back end | ||
80 | |||
81 | A \q{back end}, in this collection, is synonymous with a \q{puzzle}. | ||
82 | Each back end implements a different game. | ||
83 | |||
84 | At the top level, a back end is simply a data structure, containing | ||
85 | a few constants (flag words, preferred pixel size) and a large | ||
86 | number of function pointers. Back ends are almost invariably callee | ||
87 | rather than caller, which means there's a limitation on what a back | ||
88 | end can do on its own initiative. | ||
89 | |||
90 | The persistent state in a back end is divided into a number of data | ||
91 | structures, which are used for different purposes and therefore | ||
92 | likely to be switched around, changed without notice, and otherwise | ||
93 | updated by the rest of the code. It is important when designing a | ||
94 | back end to put the right pieces of data into the right structures, | ||
95 | or standard midend-provided features (such as Undo) may fail to | ||
96 | work. | ||
97 | |||
98 | The functions and variables provided in the back end data structure | ||
99 | are documented in \k{backend}. | ||
100 | |||
101 | \H{intro-midend} Middle end | ||
102 | |||
103 | Puzzles has a single and universal \q{middle end}. This code is | ||
104 | common to all platforms and all games; it sits in between the front | ||
105 | end and the back end and provides standard functionality everywhere. | ||
106 | |||
107 | People adding new back ends or new front ends should generally not | ||
108 | need to edit the middle end. On rare occasions there might be a | ||
109 | change that can be made to the middle end to permit a new game to do | ||
110 | something not currently anticipated by the middle end's present | ||
111 | design; however, this is terribly easy to get wrong and should | ||
112 | probably not be undertaken without consulting the primary maintainer | ||
113 | (me). Patch submissions containing unannounced mid-end changes will | ||
114 | be treated on their merits like any other patch; this is just a | ||
115 | friendly warning that mid-end changes will need quite a lot of | ||
116 | merits to make them acceptable. | ||
117 | |||
118 | Functionality provided by the mid-end includes: | ||
119 | |||
120 | \b Maintaining a list of game state structures and moving back and | ||
121 | forth along that list to provide Undo and Redo. | ||
122 | |||
123 | \b Handling timers (for move animations, flashes on completion, and | ||
124 | in some cases actually timing the game). | ||
125 | |||
126 | \b Handling the container format of game IDs: receiving them, | ||
127 | picking them apart into parameters, description and/or random seed, | ||
128 | and so on. The game back end need only handle the individual parts | ||
129 | of a game ID (encoded parameters and encoded game description); | ||
130 | everything else is handled centrally by the mid-end. | ||
131 | |||
132 | \b Handling standard keystrokes and menu commands, such as \q{New | ||
133 | Game}, \q{Restart Game} and \q{Quit}. | ||
134 | |||
135 | \b Pre-processing mouse events so that the game back ends can rely | ||
136 | on them arriving in a sensible order (no missing button-release | ||
137 | events, no sudden changes of which button is currently pressed, | ||
138 | etc). | ||
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 | ||
143 | half-finished game to a disk file; for handling application shutdown | ||
144 | and restart on platforms such as PalmOS where state is expected to be | ||
145 | saved; for storing the previous game in order to undo and redo across | ||
146 | a New Game event). | ||
147 | |||
148 | Thus, there's a lot of work done once by the mid-end so that | ||
149 | individual back ends don't have to worry about it. All the back end | ||
150 | has to do is cooperate in ensuring the mid-end can do its work | ||
151 | properly. | ||
152 | |||
153 | The API of functions provided by the mid-end to be called by the | ||
154 | front end is documented in \k{midend}. | ||
155 | |||
156 | \H{intro-utils} Miscellaneous utilities | ||
157 | |||
158 | In addition to these three major structural components, the Puzzles | ||
159 | code also contains a variety of utility modules usable by all of the | ||
160 | above components. There is a set of functions to provide | ||
161 | platform-independent random number generation; functions to make | ||
162 | memory allocation easier; functions which implement a balanced tree | ||
163 | structure to be used as necessary in complex algorithms; and a few | ||
164 | other miscellaneous functions. All of these are documented in | ||
165 | \k{utils}. | ||
166 | |||
167 | \H{intro-structure} Structure of this guide | ||
168 | |||
169 | There are a number of function call interfaces within Puzzles, and | ||
170 | this guide will discuss each one in a chapter of its own. After | ||
171 | that, \k{writing} discusses how to design new games, with some | ||
172 | general design thoughts and tips. | ||
173 | |||
174 | \C{backend} Interface to the back end | ||
175 | |||
176 | This chapter gives a detailed discussion of the interface that each | ||
177 | back end must implement. | ||
178 | |||
179 | At the top level, each back end source file exports a single global | ||
180 | symbol, which is a \c{const struct game} containing a large number | ||
181 | of function pointers and a small amount of constant data. This | ||
182 | structure is called by different names depending on what kind of | ||
183 | platform the puzzle set is being compiled on: | ||
184 | |||
185 | \b On platforms such as Windows and GTK, which build a separate | ||
186 | binary for each puzzle, the game structure in every back end has the | ||
187 | same name, \cq{thegame}; the front end refers directly to this name, | ||
188 | so that compiling the same front end module against a different back | ||
189 | end module builds a different puzzle. | ||
190 | |||
191 | \b On platforms such as MacOS X and PalmOS, which build all the | ||
192 | puzzles into a single monolithic binary, the game structure in each | ||
193 | back 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 | ||
195 | from a header file generated by CMake. | ||
196 | |||
197 | On the latter type of platform, source files may assume that the | ||
198 | preprocessor symbol \c{COMBINED} has been defined. Thus, the usual | ||
199 | code 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 | |||
211 | Game back ends must also internally define a number of data | ||
212 | structures, for storing their various persistent state. This chapter | ||
213 | will first discuss the nature and use of those structures, and then | ||
214 | go on to give details of every element of the game structure. | ||
215 | |||
216 | \H{backend-structs} Data structures | ||
217 | |||
218 | Each game is required to define four separate data structures. This | ||
219 | section discusses each one and suggests what sorts of things need to | ||
220 | be put in it. | ||
221 | |||
222 | \S{backend-game-params} \c{game_params} | ||
223 | |||
224 | The \c{game_params} structure contains anything which affects the | ||
225 | automatic generation of new puzzles. So if puzzle generation is | ||
226 | parametrised in any way, those parameters need to be stored in | ||
227 | \c{game_params}. | ||
228 | |||
229 | Most puzzles currently in this collection are played on a grid of | ||
230 | squares, meaning that the most obvious parameter is the grid size. | ||
231 | Many puzzles have additional parameters; for example, Mines allows | ||
232 | you to control the number of mines in the grid independently of its | ||
233 | size, Net can be wrapping or non-wrapping, Solo has difficulty | ||
234 | levels and symmetry settings, and so on. | ||
235 | |||
236 | A 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 | ||
238 | data item from either the preset-game-types menu or the \q{Custom} | ||
239 | game type configuration? If so, it's part of \c{game_params}. | ||
240 | |||
241 | \c{game_params} structures are permitted to contain pointers to | ||
242 | subsidiary data if they need to. The back end is required to provide | ||
243 | functions to create and destroy \c{game_params}, and those functions | ||
244 | can allocate and free additional memory if necessary. (It has not | ||
245 | yet been necessary to do this in any puzzle so far, but the | ||
246 | capability 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 | ||
250 | of the game which affects the size of the window it needs to be drawn | ||
251 | in (other than the magnification level) must be stored in | ||
252 | \c{game_params}. In particular, this imposes the fundamental | ||
253 | limitation that random game generation may not have a random effect on | ||
254 | the window size: game generation algorithms are constrained to work by | ||
255 | starting from the grid size rather than generating it as an emergent | ||
256 | phenomenon. (Although this is a restriction in theory, it has not yet | ||
257 | seemed to be a problem.) | ||
258 | |||
259 | \S{backend-game-state} \c{game_state} | ||
260 | |||
261 | While the user is actually playing a puzzle, the \c{game_state} | ||
262 | structure stores all the data corresponding to the current state of | ||
263 | play. | ||
264 | |||
265 | The mid-end keeps \c{game_state}s in a list, and adds to the list | ||
266 | every time the player makes a move; the Undo and Redo functions step | ||
267 | back and forth through that list. | ||
268 | |||
269 | Therefore, 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 | ||
271 | on undo? If so, put it in \c{game_state}, and this will automatically | ||
272 | happen without you having to lift a finger. If not, then you might | ||
273 | have found a data item that needs to go in \c{game_ui} instead. | ||
274 | |||
275 | Two quite different examples of this: | ||
276 | |||
277 | \b if the game provides an interface for making moves by moving a | ||
278 | cursor around the grid with the keyboard and pressing some other key | ||
279 | when you get to a square you want to change, then the location of that | ||
280 | cursor belongs in \c{game_ui}, because the player will want to undo | ||
281 | one \e{square change} at a time, not one \e{cursor movement} at a | ||
282 | time. | ||
283 | |||
284 | \b Mines tracks the number of times you opened a mine square and died. | ||
285 | Every time you do that, you can only continue the game by pressing | ||
286 | Undo. So the deaths counter belongs in \c{game_ui}, because otherwise, | ||
287 | it would revert to 0 every time you undid your mistaken move. | ||
288 | |||
289 | During play, \c{game_state}s are often passed around without an | ||
290 | accompanying \c{game_params} structure. Therefore, any information | ||
291 | in \c{game_params} which is important during play (such as the grid | ||
292 | size) must be duplicated within the \c{game_state}. One simple | ||
293 | method of doing this is to have the \c{game_state} structure | ||
294 | \e{contain} a \c{game_params} structure as one of its members, | ||
295 | although 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 | ||
300 | graphical contents of the puzzle window. The same \c{game_drawstate} | ||
301 | is passed to every call to the game redraw function, so that it can | ||
302 | remember what it has already drawn and what needs redrawing. | ||
303 | |||
304 | A typical use for a \c{game_drawstate} is to have an array mirroring | ||
305 | the array of grid squares in the \c{game_state}, but describing what | ||
306 | was drawn in the window on the most recent redraw. This is used to | ||
307 | identify the squares that need redrawing next time, by deciding what | ||
308 | the new value in that array should be, and comparing it to what was | ||
309 | drawn last time. See \k{writing-howto-redraw} for more on this | ||
310 | subject. | ||
311 | |||
312 | \c{game_drawstate} is occasionally completely torn down and | ||
313 | reconstructed by the mid-end, if the user somehow forces a full | ||
314 | redraw. Therefore, no data should be stored in \c{game_drawstate} | ||
315 | which is \e{not} related to the state of the puzzle window, because | ||
316 | it might be unexpectedly destroyed. | ||
317 | |||
318 | The back end provides functions to create and destroy | ||
319 | \c{game_drawstate}, which means it can contain pointers to | ||
320 | subsidiary allocated data if it needs to. A common thing to want to | ||
321 | allocate 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 | ||
327 | structures! | ||
328 | |||
329 | A new \c{game_ui} is created when the user begins playing a new | ||
330 | instance of a puzzle (i.e. during \q{New Game} or after entering a | ||
331 | game ID etc). It persists until the user finishes playing that game | ||
332 | and begins another one (or closes the window); in particular, | ||
333 | \q{Restart Game} does \e{not} destroy the \c{game_ui}. | ||
334 | |||
335 | There are various things that you might store in \c{game_ui}, which | ||
336 | are conceptually different from each other, but I haven't yet found a | ||
337 | need to split them out into smaller sub-structures for different | ||
338 | purposes: | ||
339 | |||
340 | \dt Transient UI state: | ||
341 | |||
342 | \dd Storing a piece of UI state in \c{game_state} means that you can | ||
343 | only update it by appending a move to the undo chain. Some UI state | ||
344 | shouldn't really be treated this way. For example, if your puzzle has | ||
345 | a keyboard-controlled cursor, you probably don't want every cursor | ||
346 | movement to be an undoable action, because the history of where the | ||
347 | cursor went just isn't interesting. More likely the cursor should just | ||
348 | move freely, and the only undoable actions are the ones where you | ||
349 | modify the element under the cursor. So you'd store the cursor | ||
350 | position 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. | ||
354 | If there's an undoable action involved, it will probably occur when | ||
355 | the drag is released. In between, you still need to store state that | ||
356 | the redraw function will use to update the display \dash and that can | ||
357 | live in \c{game_ui}. See \k{writing-howto-dragging} for more details | ||
358 | of this. } | ||
359 | |||
360 | \dt Persistent UI state: | ||
361 | |||
362 | \dd An example of this is the counter of deaths in Mines or Inertia. | ||
363 | This shouldn't be reverted by pressing Undo, for the opposite reason | ||
364 | to the cursor position: the cursor position is too boring to store the | ||
365 | history 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 | ||
370 | requested \q{flash} should be a white flash for victory or a red flash | ||
371 | for 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 | |||
381 | In this section I begin to discuss each individual element in the | ||
382 | back end structure. To begin with, here are some simple | ||
383 | self-contained data elements. | ||
384 | |||
385 | \S{backend-name} \c{name} | ||
386 | |||
387 | \c const char *name; | ||
388 | |||
389 | This is a simple ASCII string giving the name of the puzzle. This | ||
390 | name will be used in window titles, in game selection menus on | ||
391 | monolithic platforms, and anywhere else that the front end needs to | ||
392 | know 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 | |||
398 | These members are used on Windows only, to provide online help. | ||
399 | Although the Windows front end provides a separate binary for each | ||
400 | puzzle, 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 | ||
402 | jump to the chapter describing that particular puzzle. | ||
403 | |||
404 | This code base still supports the legacy \cw{.HLP} Windows Help format | ||
405 | as well as the less old \cw{.CHM} HTML Help format. The two use | ||
406 | different methods of identifying topics, so you have to specify both. | ||
407 | |||
408 | Each chapter about a puzzle in \c{puzzles.but} is labelled with a | ||
409 | \e{help topic} name for Windows Help, which typically appears just | ||
410 | after 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 | |||
416 | But HTML Help is able to use the Halibut identifier for the chapter | ||
417 | itself, i.e. the keyword that appears in braces immediatey after the | ||
418 | \cw{\\C}. | ||
419 | |||
420 | So the corresponding game back end encodes the \c{winhelp-topic} | ||
421 | string (here \cq{games.net}) in the \c{winhelp_topic} element of the | ||
422 | game 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 | |||
432 | In 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 | |||
439 | This function allocates a new \c{game_params} structure, fills it | ||
440 | with 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 | |||
446 | This function is one of the two APIs a back end can provide to | ||
447 | populate the \q{Type} menu, which provides a list of conveniently | ||
448 | accessible preset parameters for most games. | ||
449 | |||
450 | The function is called with \c{i} equal to the index of the preset | ||
451 | required (numbering from zero). It returns \cw{false} if that preset | ||
452 | does not exist (if \c{i} is less than zero or greater than the | ||
453 | largest preset index). Otherwise, it sets \c{*params} to point at a | ||
454 | newly allocated \c{game_params} structure containing the preset | ||
455 | information, sets \c{*name} to point at a newly allocated C string | ||
456 | containing the preset title (to go on the \q{Type} menu), and | ||
457 | returns \cw{true}. | ||
458 | |||
459 | If the game does not wish to support any presets at all, this | ||
460 | function is permitted to return \cw{false} always. | ||
461 | |||
462 | If the game wants to return presets in the form of a hierarchical menu | ||
463 | instead of a flat list (and, indeed, even if it doesn't), then it may | ||
464 | set this function pointer to \cw{NULL}, and instead fill in the | ||
465 | alternative 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 | |||
472 | This function is the more flexible of the two APIs by which a back end | ||
473 | can define a collection of preset game parameters. | ||
474 | |||
475 | This function simply returns a complete menu hierarchy, in the form of | ||
476 | a \c{struct preset_menu} (see \k{midend-get-presets}) and further | ||
477 | submenus (if it wishes) dangling off it. There are utility functions | ||
478 | described in \k{utils-presets} to make it easy for the back end to | ||
479 | construct this menu. | ||
480 | |||
481 | If the game has no need to return a hierarchy of menus, it may instead | ||
482 | opt to implement the \cw{fetch_preset()} function (see | ||
483 | \k{backend-fetch-preset}). | ||
484 | |||
485 | The game need not fill in the \c{id} fields in the preset menu | ||
486 | structures. The mid-end will do that after it receives the structure | ||
487 | from 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 | |||
493 | The job of this function is to take a \c{game_params}, and encode it | ||
494 | in a printable ASCII string form for use in game IDs. The return value must | ||
495 | be 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 | ||
497 | section in a game ID). | ||
498 | |||
499 | Ideally, it should also not contain any other potentially | ||
500 | controversial punctuation; bear in mind when designing a string | ||
501 | parameter format that it will probably be used on both Windows and | ||
502 | Unix command lines under a variety of exciting shell quoting and | ||
503 | metacharacter rules. Sticking entirely to alphanumerics is the | ||
504 | safest thing; if you really need punctuation, you can probably get | ||
505 | away with commas, periods or underscores without causing anybody any | ||
506 | major inconvenience. If you venture far beyond that, you're likely | ||
507 | to irritate \e{somebody}. | ||
508 | |||
509 | (At the time of writing this, most existing games have purely | ||
510 | alphanumeric string parameter formats. Usually these involve a | ||
511 | letter denoting a parameter, followed optionally by a number giving | ||
512 | the value of that parameter, with a few mandatory parts at the | ||
513 | beginning such as numeric width and height separated by \cq{x}.) | ||
514 | |||
515 | If the \c{full} parameter is \cw{true}, this function should encode | ||
516 | absolutely everything in the \c{game_params}, such that a subsequent | ||
517 | call to \cw{decode_params()} (\k{backend-decode-params}) will yield | ||
518 | an identical structure. If \c{full} is \cw{false}, however, you | ||
519 | should leave out anything which is not necessary to describe a | ||
520 | \e{specific puzzle instance}, i.e. anything which only takes effect | ||
521 | when a new puzzle is \e{generated}. | ||
522 | |||
523 | For example, the Solo \c{game_params} includes a difficulty rating | ||
524 | used when constructing new puzzles; but a Solo game ID need not | ||
525 | explicitly include the difficulty, since to describe a puzzle once | ||
526 | generated it's sufficient to give the grid dimensions and the location | ||
527 | and contents of the clue squares. (Indeed, one might very easily type | ||
528 | in a puzzle out of a newspaper without \e{knowing} what its difficulty | ||
529 | level is in Solo's terminology.) Therefore, Solo's | ||
530 | \cw{encode_params()} only encodes the difficulty level if \c{full} is | ||
531 | set. | ||
532 | |||
533 | \S{backend-decode-params} \cw{decode_params()} | ||
534 | |||
535 | \c void (*decode_params)(game_params *params, char const *string); | ||
536 | |||
537 | This function is the inverse of \cw{encode_params()} | ||
538 | (\k{backend-encode-params}). It parses the supplied string and fills | ||
539 | in the supplied \c{game_params} structure. Note that the structure | ||
540 | will \e{already} have been allocated: this function is not expected | ||
541 | to create a \e{new} \c{game_params}, but to modify an existing one. | ||
542 | |||
543 | This function can receive a string which only encodes a subset of | ||
544 | the parameters. The most obvious way in which this can happen is if | ||
545 | the string was constructed by \cw{encode_params()} with its \c{full} | ||
546 | parameter set to \cw{false}; however, it could also happen if the | ||
547 | user typed in a parameter set manually and missed something out. Be | ||
548 | prepared to deal with a wide range of possibilities. | ||
549 | |||
550 | When dealing with a parameter which is not specified in the input | ||
551 | string, what to do requires a judgment call on the part of the | ||
552 | programmer. Sometimes it makes sense to adjust other parameters to | ||
553 | bring them into line with the new ones. In Mines, for example, you | ||
554 | would probably not want to keep the same mine count if the user | ||
555 | dropped the grid size and didn't specify one, since you might easily | ||
556 | end up with more mines than would actually fit in the grid! On the | ||
557 | other hand, sometimes it makes sense to leave the parameter alone: a | ||
558 | Solo player might reasonably expect to be able to configure size and | ||
559 | difficulty independently of one another. | ||
560 | |||
561 | This function currently has no direct means of returning an error if | ||
562 | the 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 | ||
565 | really want to signal parse errors, you could always have a \c{char | ||
566 | *} in your parameters structure which stored an error message, and | ||
567 | have \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 | |||
573 | This function frees a \c{game_params} structure, and any subsidiary | ||
574 | allocations contained within it. | ||
575 | |||
576 | \S{backend-dup-params} \cw{dup_params()} | ||
577 | |||
578 | \c game_params *(*dup_params)(const game_params *params); | ||
579 | |||
580 | This function allocates a new \c{game_params} structure and | ||
581 | initialises it with an exact copy of the information in the one | ||
582 | provided 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 | |||
588 | This data element is set to \cw{true} if the back end supports custom | ||
589 | parameter configuration via a dialog box. If it is \cw{true}, then the | ||
590 | functions \cw{configure()} and \cw{custom_params()} are expected to | ||
591 | work. See \k{backend-configure} and \k{backend-custom-params} for more | ||
592 | details. | ||
593 | |||
594 | \S{backend-configure} \cw{configure()} | ||
595 | |||
596 | \c config_item *(*configure)(const game_params *params); | ||
597 | |||
598 | This function is called when the user requests a dialog box for | ||
599 | custom parameter configuration. It returns a newly allocated array | ||
600 | of \cw{config_item} structures, describing the GUI elements required | ||
601 | in the dialog box. The array should have one more element than the | ||
602 | number of controls, since it is terminated with a \cw{C_END} marker | ||
603 | (see below). Each array element describes the control together with | ||
604 | its initial value; the front end will modify the value fields and | ||
605 | return the updated array to \cw{custom_params()} (see | ||
606 | \k{backend-custom-params}). | ||
607 | |||
608 | The \cw{config_item} structure contains the following elements used by | ||
609 | this 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 | ||
617 | control. It is \e{not} expected to be dynamically allocated. | ||
618 | |||
619 | \c{type} contains one of a small number of \c{enum} values defining | ||
620 | what type of control is being described. The usable member of the | ||
621 | union 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 | ||
626 | input. The back end does not bother informing the front end that the | ||
627 | box is numeric rather than textual; some front ends do have the | ||
628 | capacity to take this into account, but I decided it wasn't worth | ||
629 | the extra complexity in the interface.) | ||
630 | |||
631 | \lcont{ | ||
632 | |||
633 | For controls of this type, \c{u.string} contains a single field | ||
634 | |||
635 | \c char *sval; | ||
636 | |||
637 | which stores a dynamically allocated string representing the contents | ||
638 | of the input box. | ||
639 | |||
640 | } | ||
641 | |||
642 | \dt \c{C_BOOLEAN} | ||
643 | |||
644 | \dd Describes a simple checkbox. | ||
645 | |||
646 | \lcont{ | ||
647 | |||
648 | For 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 | ||
657 | fixed choices. | ||
658 | |||
659 | \lcont{ | ||
660 | |||
661 | For 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 | ||
667 | very first character of \c{sval} is used as a delimiter when | ||
668 | processing the rest (so that the strings \cq{:zero:one:two}, | ||
669 | \cq{!zero!one!two} and \cq{xzeroxonextwo} all define a three-element | ||
670 | list containing \cq{zero}, \cq{one} and \cq{two}). | ||
671 | |||
672 | \c{selected} contains the index of the currently selected element, | ||
673 | numbering from zero (so that in the above example, 0 would mean | ||
674 | \cq{zero} and 2 would mean \cq{two}). | ||
675 | |||
676 | Note that \c{u.choices.choicenames} is \e{not} dynamically allocated, | ||
677 | unlike \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 | ||
684 | associated member of the union field \c{u} for this type. | ||
685 | |||
686 | The array returned from this function is expected to have filled in | ||
687 | the initial values of all the controls according to the input | ||
688 | \c{game_params} structure. | ||
689 | |||
690 | If the game's \c{can_configure} flag is set to \cw{false}, this | ||
691 | function 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 | |||
697 | This 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()}, | ||
700 | but in which the control values have since been changed in | ||
701 | accordance with user input. Its function is to read the new values | ||
702 | out of the controls and return a newly allocated \c{game_params} | ||
703 | structure representing the user's chosen parameter set. | ||
704 | |||
705 | (The front end will have modified the controls' \e{values}, but | ||
706 | there will still always be the same set of controls, in the same | ||
707 | order, as provided by \cw{configure()}. It is not necessary to check | ||
708 | the \c{name} and \c{type} fields, although you could use | ||
709 | \cw{assert()} if you were feeling energetic.) | ||
710 | |||
711 | This function is not expected to (and indeed \e{must not}) free the | ||
712 | input \c{config_item} array. (If the parameters fail to validate, | ||
713 | the dialog box will stay open.) | ||
714 | |||
715 | If the game's \c{can_configure} flag is set to \cw{false}, this | ||
716 | function 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 | |||
722 | This function works very like \cw{configure()}, but instead of | ||
723 | receiving a \c{game_params} and returning GUI elements describing the | ||
724 | data in it, this function receives a \c{game_ui} and returns GUI | ||
725 | elements describing any user preferences stored in that. | ||
726 | |||
727 | This function should only deal with fields of \c{game_ui} that are | ||
728 | user-settable preferences. In-game state like cursor position and | ||
729 | mouse drags, or per-game state like death counters, are nothing to do | ||
730 | with this function. | ||
731 | |||
732 | If there are no user preferences, you can set both this function | ||
733 | pointer and \c{set_prefs} to \cw{NULL}. | ||
734 | |||
735 | If you implement these functions, you must also ensure that your | ||
736 | game's \cw{new_ui()} function can be called with a null \c{game_state} | ||
737 | pointer. (See \k{backend-new-ui}.) | ||
738 | |||
739 | In every \c{config_item} returned from this function, you must set an | ||
740 | additional field beyond the ones described in \k{backend-configure}: | ||
741 | |||
742 | \c const char *kw; | ||
743 | |||
744 | This should be an identifying keyword for the user preference in | ||
745 | question, suitable for use in configuration files. That means it | ||
746 | should remain stable, even if the user-facing wording in the \c{name} | ||
747 | field is reworded for clarity. If it doesn't stay stable, old | ||
748 | configuration files will not be read correctly. | ||
749 | |||
750 | For \c{config_item}s of type \cw{C_CHOICES}, you must also set an | ||
751 | extra field in \c{u.choices}: | ||
752 | |||
753 | \c const char *choicekws; | ||
754 | |||
755 | This has the same structure as the \c{choicenames} field (a list of | ||
756 | values delimited by the first character in the whole string), and it | ||
757 | provides an identifying keyword for each individual choice in the | ||
758 | list, 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 | |||
764 | This 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()}, | ||
767 | with the controls' values updated from user input, and it should | ||
768 | transcribe the new settings into the provided \c{game_ui}. | ||
769 | |||
770 | If there are no user preferences, you can set both this function | ||
771 | pointer 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 | |||
778 | This function takes a \c{game_params} structure as input, and checks | ||
779 | that the parameters described in it fall within sensible limits. (At | ||
780 | the very least, grid dimensions should almost certainly be strictly | ||
781 | positive, for example.) | ||
782 | |||
783 | Return value is \cw{NULL} if no problems were found, or | ||
784 | alternatively a (non-dynamically-allocated) ASCII string describing | ||
785 | the error in human-readable form. | ||
786 | |||
787 | If the \c{full} parameter is set, full validation should be | ||
788 | performed: any set of parameters which would not permit generation | ||
789 | of a sensible puzzle should be faulted. If \c{full} is \e{not} set, | ||
790 | the implication is that these parameters are not going to be used | ||
791 | for \e{generating} a puzzle; so parameters which can't even sensibly | ||
792 | \e{describe} a valid puzzle should still be faulted, but parameters | ||
793 | which only affect puzzle generation should not be. | ||
794 | |||
795 | (The \c{full} option makes a difference when parameter combinations | ||
796 | are non-orthogonal. For example, Net has a boolean option | ||
797 | controlling whether it enforces a unique solution; it turns out that | ||
798 | it's impossible to generate a uniquely soluble puzzle with wrapping | ||
799 | walls and width 2, so \cw{validate_params()} will complain if you | ||
800 | ask for one. However, if the user had just been playing a unique | ||
801 | wrapping puzzle of a more sensible width, and then pastes in a game | ||
802 | ID acquired from somebody else which happens to describe a | ||
803 | \e{non}-unique wrapping width-2 puzzle, then \cw{validate_params()} | ||
804 | will be passed a \c{game_params} containing the width and wrapping | ||
805 | settings from the new game ID and the uniqueness setting from the | ||
806 | old 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 | ||
808 | inconsistency. The resulting \c{game_params} is never subsequently | ||
809 | used to generate a puzzle; this is a promise made by the mid-end | ||
810 | when it asks for a non-full validation.) | ||
811 | |||
812 | \H{backend-descs} Handling game descriptions | ||
813 | |||
814 | In this section I present the functions that deal with a textual | ||
815 | description of a puzzle, i.e. the part that comes after the colon in | ||
816 | a 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 | |||
823 | This function is where all the really hard work gets done. This is | ||
824 | the function whose job is to randomly generate a new puzzle, | ||
825 | ensuring solubility and uniqueness as appropriate. | ||
826 | |||
827 | As 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 | ||
829 | puzzle instance, encode it in printable ASCII string form, and | ||
830 | return a dynamically allocated C string containing that encoding. | ||
831 | |||
832 | Additionally, it may return a second dynamically allocated string in | ||
833 | \c{*aux}. (If it doesn't want to, then it can leave that parameter | ||
834 | completely alone; it isn't required to set it to \cw{NULL}, although | ||
835 | doing so is harmless.) That string, if present, will be passed to | ||
836 | \cw{solve()} (\k{backend-solve}) later on; so if the puzzle is | ||
837 | generated in such a way that a solution is known, then information | ||
838 | about that solution can be saved in \c{*aux} for \cw{solve()} to | ||
839 | use. | ||
840 | |||
841 | The \c{interactive} parameter should be ignored by almost all | ||
842 | puzzles. Its purpose is to distinguish between generating a puzzle | ||
843 | within a GUI context for immediate play, and generating a puzzle in | ||
844 | a command-line context for saving to be played later. The only | ||
845 | puzzle that currently uses this distinction (and, I fervently hope, | ||
846 | the only one which will \e{ever} need to use it) is Mines, which | ||
847 | chooses a random first-click location when generating puzzles | ||
848 | non-interactively, but which waits for the user to place the first | ||
849 | click when interactive. If you think you have come up with another | ||
850 | puzzle which needs to make use of this parameter, please think for | ||
851 | at least ten minutes about whether there is \e{any} alternative! | ||
852 | |||
853 | Note that game description strings are not required to contain an | ||
854 | encoding of parameters such as grid size; a game description is | ||
855 | never separated from the \c{game_params} it was generated with, so | ||
856 | any information contained in that structure need not be encoded | ||
857 | again 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 | |||
864 | This function is given a game description, and its job is to | ||
865 | validate that it describes a puzzle which makes sense. | ||
866 | |||
867 | To some extent it's up to the user exactly how far they take the | ||
868 | phrase \q{makes sense}; there are no particularly strict rules about | ||
869 | how hard the user is permitted to shoot themself in the foot when | ||
870 | typing in a bogus game description by hand. (For example, Rectangles | ||
871 | will not verify that the sum of all the numbers in the grid equals | ||
872 | the grid's area. So a user could enter a puzzle which was provably | ||
873 | not soluble, and the program wouldn't complain; there just wouldn't | ||
874 | happen to be any sequence of moves which solved it.) | ||
875 | |||
876 | The one non-negotiable criterion is that any game description which | ||
877 | makes it through \cw{validate_desc()} \e{must not} subsequently | ||
878 | cause a crash or an assertion failure when fed to \cw{new_game()} | ||
879 | and thence to the rest of the back end. | ||
880 | |||
881 | The return value is \cw{NULL} on success, or a | ||
882 | non-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 | |||
889 | This function takes a game description as input, together with its | ||
890 | accompanying \c{game_params}, and constructs a \c{game_state} | ||
891 | describing the initial state of the puzzle. It returns a newly | ||
892 | allocated \c{game_state} structure. | ||
893 | |||
894 | Almost all puzzles should ignore the \c{me} parameter. It is | ||
895 | required by Mines, which needs it for later passing to | ||
896 | \cw{midend_supersede_game_desc()} (see \k{backend-supersede}) once | ||
897 | the user has placed the first click. I fervently hope that no other | ||
898 | puzzle will be awkward enough to require it, so everybody else | ||
899 | should ignore it. As with the \c{interactive} parameter in | ||
900 | \cw{new_desc()} (\k{backend-new-desc}), if you think you have a | ||
901 | reason to need this parameter, please try very hard to think of an | ||
902 | alternative approach! | ||
903 | |||
904 | \H{backend-states} Handling game states | ||
905 | |||
906 | This 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} | ||
910 | instead of under here; but it deals with game descriptions \e{and} | ||
911 | game 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 | |||
917 | This function allocates a new \c{game_state} structure and | ||
918 | initialises it with an exact copy of the information in the one | ||
919 | provided 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 | |||
925 | This function frees a \c{game_state} structure, and any subsidiary | ||
926 | allocations 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 | |||
934 | This function allocates and returns a new \c{game_ui} structure for | ||
935 | playing a particular puzzle. | ||
936 | |||
937 | Usually, 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 | ||
939 | initial values for the new game. | ||
940 | |||
941 | However, if the puzzle defines \c{get_prefs()} and \c{set_prefs()} | ||
942 | functions, 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()}, | ||
945 | although 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 | |||
951 | This function frees a \c{game_ui} structure, and any subsidiary | ||
952 | allocations contained within it. | ||
953 | |||
954 | \S{backend-encode-ui} \cw{encode_ui()} | ||
955 | |||
956 | \c char *(*encode_ui)(const game_ui *ui); | ||
957 | |||
958 | This function encodes any \e{important} data in a \c{game_ui} | ||
959 | structure in printable ASCII string form. It is only called when | ||
960 | saving a half-finished game to a file. | ||
961 | |||
962 | It should be used sparingly. Almost all data in a \c{game_ui} is not | ||
963 | important enough to save. The location of the keyboard-controlled | ||
964 | cursor, for example, can be reset to a default position on reloading | ||
965 | the game without impacting the user experience. If the user should | ||
966 | somehow manage to save a game while a mouse drag was in progress, | ||
967 | then discarding that mouse drag would be an outright \e{feature}. | ||
968 | |||
969 | A typical thing that \e{would} be worth encoding in this function is | ||
970 | the 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 | ||
972 | revert it by using Undo, and therefore it's also too important to | ||
973 | allow the user to revert it by saving and reloading. (Of course, the | ||
974 | user could edit the save file by hand... But if the user is \e{that} | ||
975 | determined to cheat, they could just as easily modify the game's | ||
976 | source.) | ||
977 | |||
978 | The \cw{encode_ui()} function is optional. If a back-end doesn't need | ||
979 | this 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 | |||
986 | This function parses a string previously output by \cw{encode_ui()}, | ||
987 | and writes the decoded data back into the freshly-created \c{game_ui} | ||
988 | structure provided. If the string is invalid, the function should do | ||
989 | the best it can, which might just mean not changing the \c{game_ui} | ||
990 | structure at all. This might happen if a save file is corrupted, or | ||
991 | simply from a newer version that encodes more \c{game_ui} data. The | ||
992 | current \c{game_state} is provided in case the function needs to | ||
993 | refer to it for validation. | ||
994 | |||
995 | Like \cw{encode_ui()}, \cw{decode_ui()} is optional. If a back-end | ||
996 | doesn'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 | |||
1003 | This function is called by the mid-end whenever the current game | ||
1004 | state 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 | ||
1013 | the Undo and Redo operations | ||
1014 | |||
1015 | \b the user selecting Restart to go back to the initial game state. | ||
1016 | |||
1017 | The job of \cw{changed_state()} is to update the \c{game_ui} for | ||
1018 | consistency with the new game state, if any update is necessary. For | ||
1019 | example, Same Game stores data about the currently selected tile | ||
1020 | group in its \c{game_ui}, and this data is intrinsically related to | ||
1021 | the game state it was derived from. So it's very likely to become | ||
1022 | invalid when the game state changes; thus, Same Game's | ||
1023 | \cw{changed_state()} function clears the current selection whenever | ||
1024 | it is called. | ||
1025 | |||
1026 | When \cw{anim_length()} or \cw{flash_length()} are called, you can | ||
1027 | be sure that there has been a previous call to \cw{changed_state()}. | ||
1028 | So \cw{changed_state()} can set up data in the \c{game_ui} which will | ||
1029 | be read by \cw{anim_length()} and \cw{flash_length()}, and those | ||
1030 | functions will not have to worry about being called without the data | ||
1031 | having been initialised. | ||
1032 | |||
1033 | \H{backend-moves} Making moves | ||
1034 | |||
1035 | This section describes the functions which actually make moves in | ||
1036 | the game: that is, the functions which process user input and end up | ||
1037 | producing 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 | |||
1045 | This function receives user input and processes it. Its input | ||
1046 | parameters are the current \c{game_state}, the current \c{game_ui} | ||
1047 | and 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) | ||
1049 | indicating 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 | ||
1051 | coordinates of the mouse pointer relative to the top left of the | ||
1052 | puzzle'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 | ||
1056 | pointer will be to read the game's tile size parameter in order to | ||
1057 | divide 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 | ||
1062 | action whatsoever occurred in response to the input event; the puzzle | ||
1063 | was 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 | ||
1065 | the event uninteresting, while \cw{MOVE_NO_EFFECT} means that the | ||
1066 | event is intrinsically uninteresting. For example, a mouse click on | ||
1067 | an already-revealed square in Mines might return \cw{MOVE_NO_EFFECT} | ||
1068 | while 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 | ||
1071 | event has resulted in a change being made to the \c{game_ui} which | ||
1072 | will require a redraw of the game window, but that no actual \e{move} | ||
1073 | was 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 | ||
1076 | new \c{game_state} must be created. However, instead of actually | ||
1077 | constructing a new \c{game_state} itself, this function is required | ||
1078 | to return a printable ASCII string description of the details of the | ||
1079 | move. 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 | ||
1082 | the mid-end can keep the strings as well as the game states, and the | ||
1083 | strings can be written to disk when saving the game and fed to | ||
1084 | \cw{execute_move()} again on reloading.) | ||
1085 | |||
1086 | The return value from \cw{interpret_move()} is expected to be | ||
1087 | dynamically 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 | |||
1091 | After this function is called, the back end is permitted to rely on | ||
1092 | some subsequent operations happening in sequence: | ||
1093 | |||
1094 | \b \cw{execute_move()} will be called to convert this move | ||
1095 | description into a new \c{game_state} | ||
1096 | |||
1097 | \b \cw{changed_state()} will be called with the new \c{game_state}. | ||
1098 | |||
1099 | This 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 | ||
1105 | other circumstances. It is only \cw{interpret_move()} which can rely | ||
1106 | on a subsequent call to \cw{changed_state()}.) | ||
1107 | |||
1108 | The 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 | ||
1117 | was still down. The mid-end guarantees that when one of these events | ||
1118 | is received, it will always have been preceded by a button-down | ||
1119 | event (and possibly other drag events) for the same mouse button, | ||
1120 | and no event involving another mouse button will have appeared in | ||
1121 | between. | ||
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 | ||
1126 | guarantees that when one of these events is received, it will always | ||
1127 | have been preceded by a button-down event (and possibly some drag | ||
1128 | events) for the same mouse button, and no event involving another | ||
1129 | mouse 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 | ||
1139 | alongside their cursor keys, indicates that one of those buttons was | ||
1140 | pressed. On other platforms, these represent the Enter (or Return) | ||
1141 | and Space keys respectively. | ||
1142 | |||
1143 | In addition, there are some modifiers which can be bitwise-ORed into | ||
1144 | the \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 | ||
1149 | the key. They only apply to the cursor keys and the ASCII horizontal | ||
1150 | tab 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 | ||
1155 | code was input via the numeric keypad rather than the main keyboard. | ||
1156 | Some puzzles may wish to treat this differently (for example, a | ||
1157 | puzzle might want to use the numeric keypad as an eight-way | ||
1158 | directional pad), whereas others might not (a game involving numeric | ||
1159 | input 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 | ||
1164 | can bitwise-AND with \cw{~MOD_MASK} to strip all the modifiers off any | ||
1165 | input 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 | |||
1173 | This function takes an input \c{game_state} and a move string as | ||
1174 | output from \cw{interpret_move()}. It returns a newly allocated | ||
1175 | \c{game_state} which contains the result of applying the specified | ||
1176 | move to the input game state. | ||
1177 | |||
1178 | This function may return \cw{NULL} if it cannot parse the move | ||
1179 | string (and this is definitely preferable to crashing or failing an | ||
1180 | assertion, since one way this can happen is if loading a corrupt | ||
1181 | save file). However, it must not return \cw{NULL} for any move | ||
1182 | string that really was output from \cw{interpret_move()}: this is | ||
1183 | punishable by assertion failure in the mid-end. | ||
1184 | |||
1185 | \S{backend-can-solve} \c{can_solve} | ||
1186 | |||
1187 | \c bool can_solve; | ||
1188 | |||
1189 | This field is set to \cw{true} if the game's \cw{solve()} function | ||
1190 | does something. If it's set to \cw{false}, the game will not even | ||
1191 | offer 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 | |||
1198 | This function is called when the user selects the \q{Solve} option | ||
1199 | from the menu. If \cw{can_solve} is \cw{false} then it will never | ||
1200 | be called and can be \cw{NULL}. | ||
1201 | |||
1202 | It is passed two input game states: \c{orig} is the game state from | ||
1203 | the 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 | ||
1205 | is also passed the \c{aux} string saved by \cw{new_desc()} | ||
1206 | (\k{backend-new-desc}), in case that encodes important information | ||
1207 | needed to provide the solution. | ||
1208 | |||
1209 | If this function is unable to produce a solution (perhaps, for | ||
1210 | example, the game has no in-built solver so it can only solve | ||
1211 | puzzles it invented internally and has an \c{aux} string for) then | ||
1212 | it 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 | ||
1215 | expected to be dynamically allocated. | ||
1216 | |||
1217 | If this function \e{does} produce a solution, it returns a printable | ||
1218 | ASCII 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 | ||
1221 | allocated. | ||
1222 | |||
1223 | \H{backend-drawing} Drawing the game graphics | ||
1224 | |||
1225 | This section discusses the back end functions that deal with | ||
1226 | drawing. | ||
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 | |||
1233 | This function allocates and returns a new \c{game_drawstate} | ||
1234 | structure for drawing a particular puzzle. It is passed a pointer to | ||
1235 | a \c{game_state}, in case it needs to refer to that when setting up | ||
1236 | any initial data. | ||
1237 | |||
1238 | This function may not rely on the puzzle having been newly started; | ||
1239 | a new draw state can be constructed at any time if the front end | ||
1240 | requests a forced redraw. For games like Pattern, in which initial | ||
1241 | game states are much simpler than general ones, this might be | ||
1242 | important to keep in mind. | ||
1243 | |||
1244 | The parameter \c{dr} is a drawing object (see \k{drawing}) which the | ||
1245 | function might need to use to allocate blitters. (However, this | ||
1246 | isn't recommended; it's usually more sensible to wait to allocate a | ||
1247 | blitter until \cw{set_size()} is called, because that way you can | ||
1248 | tailor 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 | |||
1254 | This function frees a \c{game_drawstate} structure, and any | ||
1255 | subsidiary allocations contained within it. | ||
1256 | |||
1257 | The parameter \c{dr} is a drawing object (see \k{drawing}), which | ||
1258 | might be required if you are freeing a blitter. | ||
1259 | |||
1260 | \S{backend-preferred-tilesize} \c{preferred_tilesize} | ||
1261 | |||
1262 | \c int preferred_tilesize; | ||
1263 | |||
1264 | Each game is required to define a single integer parameter which | ||
1265 | expresses, in some sense, the scale at which it is drawn. This is | ||
1266 | described in the APIs as \cq{tilesize}, since most puzzles are on a | ||
1267 | square (or possibly triangular or hexagonal) grid and hence a | ||
1268 | sensible interpretation of this parameter is to define it as the | ||
1269 | size of one grid tile in pixels; however, there's no actual | ||
1270 | requirement that the \q{tile size} be proportional to the game | ||
1271 | window size. Window size is required to increase monotonically with | ||
1272 | \q{tile size}, however. | ||
1273 | |||
1274 | The data element \c{preferred_tilesize} indicates the tile size which | ||
1275 | should be used in the absence of a good reason to do otherwise (such | ||
1276 | as the screen being too small to fit the whole puzzle, or the user | ||
1277 | explicitly 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 | |||
1284 | This function is passed a \c{game_params} structure and a tile size. | ||
1285 | It returns, in \c{*x} and \c{*y}, the size in pixels of the drawing | ||
1286 | area that would be required to render a puzzle with those parameters | ||
1287 | at 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 | |||
1294 | This function is responsible for setting up a \c{game_drawstate} to | ||
1295 | draw at a given tile size. Typically this will simply involve | ||
1296 | copying the supplied \c{tilesize} parameter into a \c{tilesize} | ||
1297 | field inside the draw state; for some more complex games it might | ||
1298 | also involve setting up other dimension fields, or possibly | ||
1299 | allocating a blitter (see \k{drawing-blitter}). | ||
1300 | |||
1301 | The parameter \c{dr} is a drawing object (see \k{drawing}), which is | ||
1302 | required if a blitter needs to be allocated. | ||
1303 | |||
1304 | Back ends may assume (and may enforce by assertion) that this | ||
1305 | function will be called at most once for any \c{game_drawstate}. If | ||
1306 | a puzzle needs to be redrawn at a different size, the mid-end will | ||
1307 | create a fresh drawstate. | ||
1308 | |||
1309 | \S{backend-colours} \cw{colours()} | ||
1310 | |||
1311 | \c float *(*colours)(frontend *fe, int *ncolours); | ||
1312 | |||
1313 | This function is responsible for telling the front end what colours | ||
1314 | the puzzle will need to draw itself. | ||
1315 | |||
1316 | It returns the number of colours required in \c{*ncolours}, and the | ||
1317 | return value from the function itself is a dynamically allocated | ||
1318 | array of three times that many \c{float}s, containing the red, green | ||
1319 | and blue components of each colour respectively as numbers in the | ||
1320 | range [0,1]. | ||
1321 | |||
1322 | The second parameter passed to this function is a front end handle. | ||
1323 | The only things it is permitted to do with this handle are to call | ||
1324 | the 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 | ||
1327 | latter is a wrapper on the former, so front end implementors only | ||
1328 | need to provide \cw{frontend_default_colour()}.) This allows | ||
1329 | \cw{colours()} to take local configuration into account when | ||
1330 | deciding on its own colour allocations. Most games use the front | ||
1331 | end's default colour as their background, apart from a few which | ||
1332 | depend on drawing relief highlights so they adjust the background | ||
1333 | colour if it's too light for highlights to show up against it. | ||
1334 | |||
1335 | The first colour in the list is slightly special. The mid-end fills | ||
1336 | the 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 | ||
1338 | puzzle window outside the puzzle. This means that it is usually | ||
1339 | sensible to make colour 0 the background colour for the puzzle. | ||
1340 | |||
1341 | Note that the colours returned from this function are for | ||
1342 | \e{drawing}, not for printing. Printing has an entirely different | ||
1343 | colour 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 | |||
1351 | This function is called when a move is made, undone or redone. It is | ||
1352 | given the old and the new \c{game_state}, and its job is to decide | ||
1353 | whether the transition between the two needs to be animated or can | ||
1354 | be 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} | ||
1358 | specifies the chronological order of those states: if it is | ||
1359 | positive, then the transition is the result of a move or a redo (and | ||
1360 | so \c{newstate} is the later of the two moves), whereas if it is | ||
1361 | negative then the transition is the result of an undo (so that | ||
1362 | \c{newstate} is the \e{earlier} move). | ||
1363 | |||
1364 | If this function decides the transition should be animated, it | ||
1365 | returns the desired length of the animation in seconds. If not, it | ||
1366 | returns zero. | ||
1367 | |||
1368 | State changes as a result of a Restart operation are never animated; | ||
1369 | the mid-end will handle them internally and never consult this | ||
1370 | function at all. State changes as a result of Solve operations are | ||
1371 | also not animated by default, although you can change this for a | ||
1372 | particular game by setting a flag in \c{flags} (\k{backend-flags}). | ||
1373 | |||
1374 | The function is also passed a pointer to the local \c{game_ui}. It | ||
1375 | may 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 | ||
1378 | be read later by \cw{redraw()}. | ||
1379 | |||
1380 | When this function is called, it may rely on \cw{changed_state()} | ||
1381 | having been called previously, so if \cw{anim_length()} needs to | ||
1382 | refer to information in the \c{game_ui}, then \cw{changed_state()} | ||
1383 | is a reliable place to have set that information up. | ||
1384 | |||
1385 | Move animations do not inhibit further input events. If the user | ||
1386 | continues playing before a move animation is complete, the animation | ||
1387 | will be abandoned and the display will jump straight to the final | ||
1388 | state. | ||
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 | |||
1396 | This function is called when a move is completed. (\q{Completed} | ||
1397 | means that not only has the move been made, but any animation which | ||
1398 | accompanied it has finished.) It decides whether the transition from | ||
1399 | \c{oldstate} to \c{newstate} merits a \q{flash}. | ||
1400 | |||
1401 | A flash is much like a move animation, but it is \e{not} interrupted | ||
1402 | by further user interface activity; it runs to completion in | ||
1403 | parallel with whatever else might be going on on the display. The | ||
1404 | only thing which will rush a flash to completion is another flash. | ||
1405 | |||
1406 | The purpose of flashes is to indicate that the game has been | ||
1407 | completed. They were introduced as a separate concept from move | ||
1408 | animations because of Net: the habit of most Net players (and | ||
1409 | certainly me) is to rotate a tile into place and immediately lock | ||
1410 | it, then move on to another tile. When you make your last move, at | ||
1411 | the instant the final tile is rotated into place the screen starts | ||
1412 | to flash to indicate victory \dash but if you then press the lock | ||
1413 | button out of habit, then the move animation is cancelled, and the | ||
1414 | victory flash does not complete. (And if you \e{don't} press the | ||
1415 | lock button, the completed grid will look untidy because there will | ||
1416 | be one unlocked square.) Therefore, I introduced a specific concept | ||
1417 | of a \q{flash} which is separate from a move animation and can | ||
1418 | proceed in parallel with move animations and any other display | ||
1419 | activity, so that the victory flash in Net is not cancelled by that | ||
1420 | final locking move. | ||
1421 | |||
1422 | The input parameters to \cw{flash_length()} are exactly the same as | ||
1423 | the ones to \cw{anim_length()}: see \k{backend-anim-length}. | ||
1424 | |||
1425 | Just like \cw{anim_length()}, when this function is called, it may | ||
1426 | rely on \cw{changed_state()} having been called previously, so if it | ||
1427 | needs to refer to information in the \c{game_ui} then | ||
1428 | \cw{changed_state()} is a reliable place to have set that | ||
1429 | information up. | ||
1430 | |||
1431 | (Some games use flashes to indicate defeat as well as victory; | ||
1432 | Mines, for example, flashes in a different colour when you tread on | ||
1433 | a mine from the colour it uses when you complete the game. In order | ||
1434 | to achieve this, its \cw{flash_length()} function has to store a | ||
1435 | flag 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 | |||
1446 | This function queries the backend for the rectangular region | ||
1447 | containing the cursor (in games that have one), or other region of | ||
1448 | interest. | ||
1449 | |||
1450 | This function is called by only | ||
1451 | \cw{midend_get_cursor_location()} (\k{midend-get-cursor-location}). Its | ||
1452 | purpose is to allow front ends to query the location of the backend's | ||
1453 | cursor. With knowledge of this location, a front end can, for example, | ||
1454 | ensure that the region of interest remains visible if the puzzle is | ||
1455 | too big to fit on the screen at once. | ||
1456 | |||
1457 | On returning, \cw{*x}, \cw{*y} should be set to the X and Y | ||
1458 | coordinates of the upper-left corner of the rectangular region of | ||
1459 | interest, and \cw{*w} and \cw{*h} should be the width and height of | ||
1460 | that region, respectively. In the event that a cursor is not visible | ||
1461 | on screen, this function should return and leave the return parameters | ||
1462 | untouched \dash the midend will notice this. The backend need not | ||
1463 | bother checking that \cw{x}, \cw{y}, \cw{w} and \cw{h} are | ||
1464 | non-\cw{NULL} \dash the midend guarantees that they will not be. | ||
1465 | |||
1466 | Defining what constitutes a \q{region of interest} is left up to the | ||
1467 | backend. If a game provides a conventional cursor \dash such as Mines, | ||
1468 | Solo, or any of the other grid-based games \dash the most logical | ||
1469 | choice is of course the location of the cursor itself. However, in | ||
1470 | other cases such as Cube or Inertia, there is no \q{cursor} in the | ||
1471 | conventional sense \dash the player instead controls an object moving | ||
1472 | around the screen. In these cases, it makes sense to define the region | ||
1473 | of interest as the bounding box of the player object or another | ||
1474 | sensible region \dash such as the grid square the player is sitting on | ||
1475 | in Cube. | ||
1476 | |||
1477 | If a backend does not provide a cursor mechanism at all, the backend | ||
1478 | is free to provide an empty implementation of this function, or a | ||
1479 | \cw{NULL} pointer in the \cw{game} structure \dash the midend will | ||
1480 | notice either of these cases and behave appropriately. | ||
1481 | |||
1482 | \S{backend-status} \cw{status()} | ||
1483 | |||
1484 | \c int (*status)(const game_state *state); | ||
1485 | |||
1486 | This function returns a status value indicating whether the current | ||
1487 | game is still in play, or has been won, or has been conclusively lost. | ||
1488 | The mid-end uses this to implement \cw{midend_status()} | ||
1489 | (\k{midend-status}). | ||
1490 | |||
1491 | The return value should be +1 if the game has been successfully | ||
1492 | solved. If the game has been lost in a situation where further play is | ||
1493 | unlikely, the return value should be -1. If neither is true (so play | ||
1494 | is still ongoing), return zero. | ||
1495 | |||
1496 | Front ends may wish to use a non-zero status as a cue to proactively | ||
1497 | offer the option of starting a new game. Therefore, back ends should | ||
1498 | not return -1 if the game has been \e{technically} lost but undoing | ||
1499 | and continuing is still a realistic possibility. | ||
1500 | |||
1501 | (For instance, games with hidden information such as Guess or Mines | ||
1502 | might well return a non-zero status whenever they reveal the solution, | ||
1503 | whether or not the player guessed it correctly, on the grounds that a | ||
1504 | player would be unlikely to hide the solution and continue playing | ||
1505 | after the answer was spoiled. On the other hand, games where you can | ||
1506 | merely get into a dead end such as Same Game or Inertia might choose | ||
1507 | to return 0 in that situation, on the grounds that the player would | ||
1508 | quite 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 | |||
1518 | This function is responsible for actually drawing the contents of | ||
1519 | the game window, and for redrawing every time the game state or the | ||
1520 | \c{game_ui} changes. | ||
1521 | |||
1522 | The parameter \c{dr} is a drawing object which may be passed to the | ||
1523 | drawing API functions (see \k{drawing} for documentation of the | ||
1524 | drawing API). This function may not save \c{dr} and use it | ||
1525 | elsewhere; it must only use it for calling back to the drawing API | ||
1526 | functions within its own lifetime. | ||
1527 | |||
1528 | \c{ds} is the local \c{game_drawstate}, of course, and \c{ui} is the | ||
1529 | local \c{game_ui}. | ||
1530 | |||
1531 | \c{newstate} is the semantically-current game state, and is always | ||
1532 | non-\cw{NULL}. If \c{oldstate} is also non-\cw{NULL}, it means that | ||
1533 | a move has recently been made and the game is still in the process | ||
1534 | of displaying an animation linking the old and new states; in this | ||
1535 | situation, \c{anim_time} will give the length of time (in seconds) | ||
1536 | that the animation has already been running. If \c{oldstate} is | ||
1537 | \cw{NULL}, then \c{anim_time} is unused (and will hopefully be set | ||
1538 | to zero to avoid confusion). | ||
1539 | |||
1540 | \c{dir} specifies the chronological order of those states: if it is | ||
1541 | positive, then the transition is the result of a move or a redo (and | ||
1542 | so \c{newstate} is the later of the two moves), whereas if it is | ||
1543 | negative then the transition is the result of an undo (so that | ||
1544 | \c{newstate} is the \e{earlier} move). This allows move animations | ||
1545 | that are not time-symmetric (such as Inertia, where gems are consumed | ||
1546 | during 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 | ||
1549 | the middle of a flash, and gives the time since the start of the | ||
1550 | flash. See \k{backend-flash-length} for general discussion of | ||
1551 | flashes. | ||
1552 | |||
1553 | The very first time this function is called for a new | ||
1554 | \c{game_drawstate}, it is expected to redraw the \e{entire} drawing | ||
1555 | area. Since this often involves drawing visual furniture which is | ||
1556 | never subsequently altered, it is often simplest to arrange this by | ||
1557 | having a special \q{first time} flag in the draw state, and | ||
1558 | resetting it after the first redraw. This function can assume that | ||
1559 | the mid-end has filled the drawing area with colour 0 before the first | ||
1560 | call. | ||
1561 | |||
1562 | When this function (or any subfunction) calls the drawing API, it is | ||
1563 | expected to pass colour indices which were previously defined by the | ||
1564 | \cw{colours()} function. | ||
1565 | |||
1566 | \H{backend-printing} Printing functions | ||
1567 | |||
1568 | This section discusses the back end functions that deal with | ||
1569 | printing puzzles out on paper. | ||
1570 | |||
1571 | \S{backend-can-print} \c{can_print} | ||
1572 | |||
1573 | \c bool can_print; | ||
1574 | |||
1575 | This flag is set to \cw{true} if the puzzle is capable of printing | ||
1576 | itself on paper. (This makes sense for some puzzles, such as Solo, | ||
1577 | which can be filled in with a pencil. Other puzzles, such as | ||
1578 | Twiddle, inherently involve moving things around and so would not | ||
1579 | make sense to print.) | ||
1580 | |||
1581 | If this flag is \cw{false}, then the functions \cw{print_size()} | ||
1582 | and \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 | |||
1588 | This flag is set to \cw{true} if the puzzle is capable of printing | ||
1589 | itself differently when colour is available. For example, Map can | ||
1590 | actually print coloured regions in different \e{colours} rather than | ||
1591 | resorting to cross-hatching. | ||
1592 | |||
1593 | If the \c{can_print} flag is \cw{false}, then this flag will be | ||
1594 | ignored. | ||
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 | |||
1601 | This function is passed a \c{game_params} structure and a tile size. | ||
1602 | It 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 | |||
1605 | If the \c{can_print} flag is \cw{false}, this function will never be | ||
1606 | called. | ||
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 | |||
1613 | This function is called when a puzzle is to be printed out on paper. | ||
1614 | It should use the drawing API functions (see \k{drawing}) to print | ||
1615 | itself. | ||
1616 | |||
1617 | This function is separate from \cw{redraw()} because it is often | ||
1618 | very different: | ||
1619 | |||
1620 | \b The printing function may not depend on pixel accuracy, since | ||
1621 | printer resolution is variable. Draw as if your canvas had infinite | ||
1622 | resolution. | ||
1623 | |||
1624 | \b The printing function sometimes needs to display things in a | ||
1625 | completely different style. Net, for example, is very different as | ||
1626 | an on-screen puzzle and as a printed one. | ||
1627 | |||
1628 | \b The printing function is often much simpler since it has no need | ||
1629 | to deal with repeated partial redraws. | ||
1630 | |||
1631 | However, there's no reason the printing and redraw functions can't | ||
1632 | share some code if they want to. | ||
1633 | |||
1634 | When this function (or any subfunction) calls the drawing API, the | ||
1635 | colour indices it passes should be colours which have been allocated | ||
1636 | by the \cw{print_*_colour()} functions within this execution of | ||
1637 | \cw{print()}. This is very different from the fixed small number of | ||
1638 | colours used in \cw{redraw()}, because printers do not have a | ||
1639 | limitation on the total number of colours that may be used. Some | ||
1640 | puzzles' printing functions might wish to allocate only one \q{ink} | ||
1641 | colour and use it for all drawing; others might wish to allocate | ||
1642 | \e{more} colours than are used on screen. | ||
1643 | |||
1644 | One possible colour policy worth mentioning specifically is that a | ||
1645 | puzzle's printing function might want to allocate the \e{same} | ||
1646 | colour indices as are used by the redraw function, so that code | ||
1647 | shared between drawing and printing does not have to keep switching | ||
1648 | its colour indices. In order to do this, the simplest thing is to | ||
1649 | make use of the fact that colour indices returned from | ||
1650 | \cw{print_*_colour()} are guaranteed to be in increasing order from | ||
1651 | zero. 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 | ||
1653 | write | ||
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 | |||
1660 | If the \c{can_print} flag is \cw{false}, this function will never be | ||
1661 | called. | ||
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 | |||
1669 | This field is \cw{true} if the game supports formatting a | ||
1670 | game state as ASCII text (typically ASCII art) for copying to the | ||
1671 | clipboard and pasting into other applications. If it is \cw{false}, | ||
1672 | front ends will not offer the \q{Copy} command at all. | ||
1673 | |||
1674 | If this field is \cw{true}, the game does not necessarily have to | ||
1675 | support text formatting for \e{all} games: e.g. a game which can be | ||
1676 | played on a square grid or a triangular one might only support copy | ||
1677 | and paste for the former, because triangular grids in ASCII art are | ||
1678 | just too difficult. | ||
1679 | |||
1680 | If this field is \cw{false}, the functions | ||
1681 | \cw{can_format_as_text_now()} (\k{backend-can-format-as-text-now}) | ||
1682 | and \cw{text_format()} (\k{backend-text-format}) are never called | ||
1683 | and 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 | |||
1689 | This function is passed a \c{game_params}, and returns \cw{true} if | ||
1690 | the game can support ASCII text output for this particular game type. | ||
1691 | If it returns \cw{false}, front ends will grey out or otherwise | ||
1692 | disable the \q{Copy} command. | ||
1693 | |||
1694 | Games may enable and disable the copy-and-paste function for | ||
1695 | different game \e{parameters}, but are currently constrained to | ||
1696 | return the same answer from this function for all game \e{states} | ||
1697 | sharing the same parameters. In other words, the \q{Copy} function | ||
1698 | may enable or disable itself when the player changes game preset, | ||
1699 | but will never change during play of a single game or when another | ||
1700 | game of exactly the same type is generated. | ||
1701 | |||
1702 | This function should not take into account aspects of the game | ||
1703 | parameters 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 | ||
1706 | call to this function and a subsequent call to \cw{text_format()} | ||
1707 | itself. (For instance, game \e{difficulty} should not affect whether | ||
1708 | the 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 | |||
1715 | This function is passed a \c{game_state}, and returns a newly | ||
1716 | allocated C string containing an ASCII representation of that game | ||
1717 | state. It is used to implement the \q{Copy} operation in many front | ||
1718 | ends. | ||
1719 | |||
1720 | This 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 | ||
1724 | currently selected game parameters. | ||
1725 | |||
1726 | The returned string may contain line endings (and will probably want | ||
1727 | to), using the normal C internal \cq{\\n} convention. For | ||
1728 | consistency between puzzles, all multi-line textual puzzle | ||
1729 | representations should \e{end} with a newline as well as containing | ||
1730 | them internally. (There are currently no puzzles which have a | ||
1731 | one-line ASCII representation, so there's no precedent yet for | ||
1732 | whether that should come with a newline or not.) | ||
1733 | |||
1734 | \S{backend-wants-statusbar} \cw{wants_statusbar} | ||
1735 | |||
1736 | \c bool wants_statusbar; | ||
1737 | |||
1738 | This field is set to \cw{true} if the puzzle has a use for a textual | ||
1739 | status line (to display score, completion status, currently active | ||
1740 | tiles, etc). If the \c{redraw()} function ever intends to call | ||
1741 | \c{status_bar()} in the drawing API (\k{drawing-status-bar}), then it | ||
1742 | should set this flag to \c{true}. | ||
1743 | |||
1744 | \S{backend-is-timed} \c{is_timed} | ||
1745 | |||
1746 | \c bool is_timed; | ||
1747 | |||
1748 | This field is \cw{true} if the puzzle is time-critical. If | ||
1749 | so, the mid-end will maintain a game timer while the user plays. | ||
1750 | |||
1751 | If this field is \cw{false}, then \cw{timing_state()} will never be | ||
1752 | called 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 | |||
1758 | This 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 | ||
1760 | be running. | ||
1761 | |||
1762 | A typical use for the \c{game_ui} in this function is to note when | ||
1763 | the game was first completed (by setting a flag in | ||
1764 | \cw{changed_state()} \dash see \k{backend-changed-state}), and | ||
1765 | freeze the timer thereafter so that the user can undo back through | ||
1766 | their 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 | |||
1772 | This function returns a dynamically allocated array of \cw{key_label} | ||
1773 | items containing the buttons the back end deems absolutely | ||
1774 | \e{necessary} for gameplay, not an exhaustive list of every button the | ||
1775 | back end could accept. For example, Keen only returns the digits up to | ||
1776 | the 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 | ||
1778 | play 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 | |||
1785 | The \cw{label} field of this structure can (and often will) be set by | ||
1786 | the backend to \cw{NULL}, in which case the midend will instead call | ||
1787 | \c{button2label()} (\k{utils-button2label}) and fill in a generic | ||
1788 | label. The \cw{button} field is the associated code that can be passed | ||
1789 | to the midend when the frontend deems appropriate. | ||
1790 | |||
1791 | If \cw{label} is not \cw{NULL}, then it's a dynamically allocated | ||
1792 | string. Therefore, freeing an array of these structures needs more | ||
1793 | than 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 | ||
1795 | structures conveniently. | ||
1796 | |||
1797 | The backend should set \cw{*nkeys} to the number of elements in the | ||
1798 | returned array. | ||
1799 | |||
1800 | The field for this function pointer in the \cw{game} structure might | ||
1801 | be set to \cw{NULL} (and indeed it is for the majority of the games) | ||
1802 | to indicate that no additional buttons (apart from the cursor keys) | ||
1803 | are required to play the game. | ||
1804 | |||
1805 | This function should not be called directly by frontends. Instead, | ||
1806 | frontends 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 | |||
1815 | This function is called to ask the back-end how certain keys should be | ||
1816 | labelled on platforms (such a feature phones) where this is | ||
1817 | conventional. | ||
1818 | These labels are expected to reflect what the keys will do right now, | ||
1819 | so they can change depending on the game and UI state. | ||
1820 | |||
1821 | The \c{ui} and \c{state} arguments describe the state of the game for | ||
1822 | which key labels are required. | ||
1823 | The \c{button} argument is the same as the one passed to | ||
1824 | \cw{interpret_move()}. | ||
1825 | At 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}. | ||
1827 | The return value is a short string describing what the requested key | ||
1828 | will do if pressed. | ||
1829 | Usually the string should be a static string constant. | ||
1830 | If it's really necessary to use a dynamically-allocated string, it | ||
1831 | should 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 | ||
1833 | the \cw{game_ui} and freed at the next one of those calls). | ||
1834 | |||
1835 | There's no fixed upper limit on the length of string that this | ||
1836 | function can return, but more than about 12 characters is likely to | ||
1837 | cause problems for front-ends. If two buttons have the same effect, | ||
1838 | their labels should be identical so that the front end can detect | ||
1839 | this. Similarly, keys that do different things should have different | ||
1840 | labels. The label should be an empty string (\cw{""}) if the key does | ||
1841 | nothing. | ||
1842 | |||
1843 | Like \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 | ||
1845 | mid-end will treat it as though it always returned \cw{""}. | ||
1846 | |||
1847 | \S{backend-flags} \c{flags} | ||
1848 | |||
1849 | \c int flags; | ||
1850 | |||
1851 | This field contains miscellaneous per-backend flags. It consists of | ||
1852 | the 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 | ||
1858 | bit flag which indicates that when buttons \cw{x} and \cw{y} are | ||
1859 | both pressed simultaneously, the mid-end should consider \cw{x} to | ||
1860 | have priority. (In the absence of any such flags, the mid-end will | ||
1861 | always 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 | ||
1867 | move. For most games, solve moves should not be animated, so the | ||
1868 | mid-end doesn't even bother calling \cw{anim_length()} | ||
1869 | (\k{backend-anim-length}), thus saving some special-case code in | ||
1870 | each game. On the rare occasion that animated solve moves are | ||
1871 | actually 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 | ||
1876 | without the use of mouse buttons other than the left one. On some | ||
1877 | PDA platforms, this flag is used by the front end to enable | ||
1878 | right-button emulation through an appropriate gesture. Note that a | ||
1879 | puzzle is not required to set this just because it \e{uses} the | ||
1880 | right button, but only if its use of the right button is critical to | ||
1881 | playing the game. (Slant, for example, uses the right button to | ||
1882 | cycle through the three square states in the opposite order from the | ||
1883 | left 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 | ||
1888 | without the use of number-key input. On some PDA platforms it causes | ||
1889 | an emulated number pad to appear on the screen. Similarly to | ||
1890 | \cw{REQUIRE_RBUTTON}, a puzzle need not specify this simply if its | ||
1891 | use of the number keys is not critical. | ||
1892 | |||
1893 | \H{backend-initiative} Things a back end may do on its own initiative | ||
1894 | |||
1895 | This section describes a couple of things that a back end may choose | ||
1896 | to do by calling functions elsewhere in the program, which would not | ||
1897 | otherwise be obvious. | ||
1898 | |||
1899 | \S{backend-newrs} Create a random state | ||
1900 | |||
1901 | If a back end needs random numbers at some point during normal play, | ||
1902 | it can create a fresh \c{random_state} by first calling | ||
1903 | \c{get_random_seed} (\k{frontend-get-random-seed}) and then passing | ||
1904 | the returned seed data to \cw{random_new()}. | ||
1905 | |||
1906 | This is likely not to be what you want. If a puzzle needs randomness | ||
1907 | in the middle of play, it's likely to be more sensible to store some | ||
1908 | sort of random state within the \c{game_state}, so that the random | ||
1909 | numbers are tied to the particular game state and hence the player | ||
1910 | can't simply keep undoing their move until they get numbers they | ||
1911 | like better. | ||
1912 | |||
1913 | This facility is currently used only in Net, to implement the | ||
1914 | \q{jumble} command, which sets every unlocked tile to a new random | ||
1915 | orientation. This randomness \e{is} a reasonable use of the feature, | ||
1916 | because it's non-adversarial \dash there's no advantage to the user | ||
1917 | in getting different random numbers. | ||
1918 | |||
1919 | \S{backend-supersede} Supersede its own game description | ||
1920 | |||
1921 | In 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 | |||
1927 | When the user selects \q{New Game}, the mid-end calls | ||
1928 | \cw{new_desc()} (\k{backend-new-desc}) to get a new game | ||
1929 | description, and (as well as using that to generate an initial game | ||
1930 | state) stores it for the save file and for telling to the user. The | ||
1931 | function above overwrites that game description, and also splits it | ||
1932 | in two. \c{desc} becomes the new game description which is provided | ||
1933 | to the user on request, and is also the one used to construct a new | ||
1934 | initial game state if the user selects \q{Restart}. \c{privdesc} is | ||
1935 | a \q{private} game description, used to reconstruct the game's | ||
1936 | initial state when reloading. | ||
1937 | |||
1938 | The distinction between the two, as well as the need for this | ||
1939 | function at all, comes from Mines. Mines begins with a blank grid | ||
1940 | and no idea of where the mines actually are; \cw{new_desc()} does | ||
1941 | almost no work in interactive mode, and simply returns a string | ||
1942 | encoding the \c{random_state}. When the user first clicks to open a | ||
1943 | tile, \e{then} Mines generates the mine positions, in such a way | ||
1944 | that the game is soluble from that starting point. Then it uses this | ||
1945 | function to supersede the random-state game description with a | ||
1946 | proper one. But it needs two: one containing the initial click | ||
1947 | location (because that's what you want to happen if you restart the | ||
1948 | game, 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 | ||
1950 | location (because when you save and reload the game, you expect to | ||
1951 | see the same blank initial state as you had before saving). | ||
1952 | |||
1953 | I should stress again that this function is a horrid hack. Nobody | ||
1954 | should use it if they're not Mines; if you think you need to use it, | ||
1955 | think again repeatedly in the hope of finding a better way to do | ||
1956 | whatever it was you needed to do. | ||
1957 | |||
1958 | \C{drawing} The drawing API | ||
1959 | |||
1960 | The back end function \cw{redraw()} (\k{backend-redraw}) is required | ||
1961 | to draw the puzzle's graphics on the window's drawing area. The back | ||
1962 | end function \cw{print()} similarly draws the puzzle on paper, if the | ||
1963 | puzzle is printable. To do this portably, the back end is provided | ||
1964 | with a drawing API allowing it to talk directly to the front end. In | ||
1965 | this chapter I document that API, both for the benefit of back end | ||
1966 | authors trying to use it and for front end authors trying to implement | ||
1967 | it. | ||
1968 | |||
1969 | The drawing API as seen by the back end is a collection of global | ||
1970 | functions, each of which takes a pointer to a \c{drawing} structure | ||
1971 | (a \q{drawing object}). These objects are supplied as parameters to | ||
1972 | the back end's \cw{redraw()} and \cw{print()} functions. | ||
1973 | |||
1974 | In fact these global functions are not implemented directly by the | ||
1975 | front end; instead, they are implemented centrally in \c{drawing.c} | ||
1976 | and form a small piece of middleware. The drawing API as supplied by | ||
1977 | the front end is a structure containing a set of function pointers, | ||
1978 | plus a \cq{void *} handle which is passed to each of those | ||
1979 | functions. This enables a single front end to switch between | ||
1980 | multiple implementations of the drawing API if necessary. For | ||
1981 | example, the Windows API supplies a printing mechanism integrated | ||
1982 | into the same GDI which deals with drawing in windows, and therefore | ||
1983 | the same API implementation can handle both drawing and printing; | ||
1984 | but on Unix, the most common way for applications to print is by | ||
1985 | producing PostScript output directly, and although it would be | ||
1986 | \e{possible} to write a single (say) \cw{draw_rect()} function which | ||
1987 | checked a global flag to decide whether to do GTK drawing operations | ||
1988 | or output PostScript to a file, it's much nicer to have two separate | ||
1989 | functions and switch between them as appropriate. | ||
1990 | |||
1991 | When drawing, the puzzle window is indexed by pixel coordinates, | ||
1992 | with the top left pixel defined as \cw{(0,0)} and the bottom right | ||
1993 | pixel \cw{(w-1,h-1)}, where \c{w} and \c{h} are the width and height | ||
1994 | values returned by the back end function \cw{compute_size()} | ||
1995 | (\k{backend-compute-size}). | ||
1996 | |||
1997 | When printing, the puzzle's print area is indexed in exactly the | ||
1998 | same way (with an arbitrary tile size provided by the printing | ||
1999 | module \c{printing.c}), to facilitate sharing of code between the | ||
2000 | drawing and printing routines. However, when printing, puzzles may | ||
2001 | no longer assume that the coordinate unit has any relationship to a | ||
2002 | pixel; the printer's actual resolution might very well not even be | ||
2003 | known at print time, so the coordinate unit might be smaller or | ||
2004 | larger than a pixel. Puzzles' print functions should restrict | ||
2005 | themselves to drawing geometric shapes rather than fiddly pixel | ||
2006 | manipulation. | ||
2007 | |||
2008 | \e{Puzzles' redraw functions may assume that the surface they draw | ||
2009 | on is persistent}. It is the responsibility of every front end to | ||
2010 | preserve the puzzle's window contents in the face of GUI window | ||
2011 | expose issues and similar. It is not permissible to request that the | ||
2012 | back end redraw any part of a window that it has already drawn, | ||
2013 | unless something has actually changed as a result of making moves in | ||
2014 | the puzzle. | ||
2015 | |||
2016 | Most front ends accomplish this by having the drawing routines draw | ||
2017 | on a stored bitmap rather than directly on the window, and copying | ||
2018 | the bitmap to the window every time a part of the window needs to be | ||
2019 | redrawn. Therefore, it is vitally important that whenever the back | ||
2020 | end does any drawing it informs the front end of which parts of the | ||
2021 | window it has accessed, and hence which parts need repainting. This | ||
2022 | is done by calling \cw{draw_update()} (\k{drawing-draw-update}). | ||
2023 | |||
2024 | Persistence of old drawing is convenient. However, a puzzle should | ||
2025 | be very careful about how it updates its drawing area. The problem | ||
2026 | is that some front ends do anti-aliased drawing: rather than simply | ||
2027 | choosing between leaving each pixel untouched or painting it a | ||
2028 | specified colour, an antialiased drawing function will \e{blend} the | ||
2029 | original and new colours in pixels at a figure's boundary according | ||
2030 | to the proportion of the pixel occupied by the figure (probably | ||
2031 | modified by some heuristic fudge factors). All of this produces a | ||
2032 | smoother appearance for curves and diagonal lines. | ||
2033 | |||
2034 | An unfortunate effect of drawing an anti-aliased figure repeatedly | ||
2035 | is that the pixels around the figure's boundary come steadily more | ||
2036 | saturated with \q{ink} and the boundary appears to \q{spread out}. | ||
2037 | Worse, redrawing a figure in a different colour won't fully paint | ||
2038 | over the old boundary pixels, so the end result is a rather ugly | ||
2039 | smudge. | ||
2040 | |||
2041 | A good strategy to avoid unpleasant anti-aliasing artifacts is to | ||
2042 | identify a number of rectangular areas which need to be redrawn, | ||
2043 | clear them to the background colour, and then redraw their contents | ||
2044 | from scratch, being careful all the while not to stray beyond the | ||
2045 | boundaries of the original rectangles. The \cw{clip()} function | ||
2046 | (\k{drawing-clip}) comes in very handy here. Games based on a square | ||
2047 | grid can often do this fairly easily. Other games may need to be | ||
2048 | somewhat more careful. For example, Loopy's redraw function first | ||
2049 | identifies portions of the display which need to be updated. Then, | ||
2050 | if the changes are fairly well localised, it clears and redraws a | ||
2051 | rectangle containing each changed area. Otherwise, it gives up and | ||
2052 | redraws the entire grid from scratch. | ||
2053 | |||
2054 | It is possible to avoid clearing to background and redrawing from | ||
2055 | scratch if one is very careful about which drawing functions one | ||
2056 | uses: if a function is documented as not anti-aliasing under some | ||
2057 | circumstances, you can rely on each pixel in a drawing either being | ||
2058 | left entirely alone or being set to the requested colour, with no | ||
2059 | blending being performed. | ||
2060 | |||
2061 | In the following sections I first discuss the drawing API as seen by | ||
2062 | the back end, and then the \e{almost} identical function-pointer | ||
2063 | form seen by the front end. | ||
2064 | |||
2065 | \H{drawing-backend} Drawing API as seen by the back end | ||
2066 | |||
2067 | This section documents the back-end drawing API, in the form of | ||
2068 | functions 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 | |||
2075 | Draws a filled rectangle in the puzzle window. | ||
2076 | |||
2077 | \c{x} and \c{y} give the coordinates of the top left pixel of the | ||
2078 | rectangle. \c{w} and \c{h} give its width and height. Thus, the | ||
2079 | horizontal extent of the rectangle runs from \c{x} to \c{x+w-1} | ||
2080 | inclusive, and the vertical extent from \c{y} to \c{y+h-1} | ||
2081 | inclusive. | ||
2082 | |||
2083 | \c{colour} is an integer index into the colours array returned by | ||
2084 | the back end function \cw{colours()} (\k{backend-colours}). | ||
2085 | |||
2086 | There is no separate pixel-plotting function. If you want to plot a | ||
2087 | single pixel, the approved method is to use \cw{draw_rect()} with | ||
2088 | width and height set to 1. | ||
2089 | |||
2090 | Unlike many of the other drawing functions, this function is | ||
2091 | guaranteed to be pixel-perfect: the rectangle will be sharply | ||
2092 | defined and not anti-aliased or anything like that. | ||
2093 | |||
2094 | This 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 | |||
2101 | Draws an outline rectangle in the puzzle window. | ||
2102 | |||
2103 | \c{x} and \c{y} give the coordinates of the top left pixel of the | ||
2104 | rectangle. \c{w} and \c{h} give its width and height. Thus, the | ||
2105 | horizontal extent of the rectangle runs from \c{x} to \c{x+w-1} | ||
2106 | inclusive, and the vertical extent from \c{y} to \c{y+h-1} | ||
2107 | inclusive. | ||
2108 | |||
2109 | \c{colour} is an integer index into the colours array returned by | ||
2110 | the back end function \cw{colours()} (\k{backend-colours}). | ||
2111 | |||
2112 | From a back end perspective, this function may be considered to be | ||
2113 | part of the drawing API. However, front ends are not required to | ||
2114 | implement it, since it is actually implemented centrally (in | ||
2115 | \cw{misc.c}) as a wrapper on \cw{draw_polygon()}. | ||
2116 | |||
2117 | This 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 | |||
2123 | Draws four L-shapes at the corners of a square, in the manner of a | ||
2124 | target reticule. This is a convenience function for back ends to use | ||
2125 | to 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 | ||
2129 | at \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 | ||
2133 | the 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 | |||
2140 | Draws 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 | ||
2144 | drawn includes both those points. | ||
2145 | |||
2146 | \c{colour} is an integer index into the colours array returned by | ||
2147 | the back end function \cw{colours()} (\k{backend-colours}). | ||
2148 | |||
2149 | Some platforms may perform anti-aliasing on this function. | ||
2150 | Therefore, do not assume that you can erase a line by drawing the | ||
2151 | same line over it in the background colour; anti-aliasing might lead | ||
2152 | to perceptible ghost artefacts around the vanished line. Horizontal | ||
2153 | and vertical lines, however, are pixel-perfect and not anti-aliased. | ||
2154 | |||
2155 | This 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 | |||
2162 | Draws 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 | ||
2168 | colours array returned by the back end function \cw{colours()} | ||
2169 | (\k{backend-colours}). \c{fillcolour} may also be \cw{-1} to | ||
2170 | indicate that the polygon should be outlined only. | ||
2171 | |||
2172 | The polygon defined by the specified list of vertices is first | ||
2173 | filled 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 | ||
2178 | because different platforms disagree on whether a filled polygon | ||
2179 | should include its boundary line or not, so drawing \e{only} a | ||
2180 | filled polygon would have non-portable effects. If you want your | ||
2181 | filled polygon not to have a visible outline, you must set | ||
2182 | \c{outlinecolour} to the same as \c{fillcolour}. | ||
2183 | |||
2184 | Some platforms may perform anti-aliasing on this function. | ||
2185 | Therefore, do not assume that you can erase a polygon by drawing the | ||
2186 | same polygon over it in the background colour. Also, be prepared for | ||
2187 | the polygon to extend a pixel beyond its obvious bounding box as a | ||
2188 | result of this; if you really need it not to do this to avoid | ||
2189 | interfering with other delicate graphics, you should probably use | ||
2190 | \cw{clip()} (\k{drawing-clip}). You can rely on horizontal and | ||
2191 | vertical lines not being anti-aliased. | ||
2192 | |||
2193 | This 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 | |||
2200 | Draws 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 | ||
2204 | the circle is from \c{cx-radius+1} to \c{cx+radius-1} inclusive, and | ||
2205 | the vertical extent similarly around \c{cy}. | ||
2206 | |||
2207 | \c{fillcolour} and \c{outlinecolour} are integer indices into the | ||
2208 | colours array returned by the back end function \cw{colours()} | ||
2209 | (\k{backend-colours}). \c{fillcolour} may also be \cw{-1} to | ||
2210 | indicate that the circle should be outlined only. | ||
2211 | |||
2212 | The circle is first filled in \c{fillcolour}, if specified, and then | ||
2213 | outlined 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 | ||
2217 | because different platforms disagree on whether a filled circle | ||
2218 | should include its boundary line or not, so drawing \e{only} a | ||
2219 | filled circle would have non-portable effects. If you want your | ||
2220 | filled circle not to have a visible outline, you must set | ||
2221 | \c{outlinecolour} to the same as \c{fillcolour}. | ||
2222 | |||
2223 | Some platforms may perform anti-aliasing on this function. | ||
2224 | Therefore, do not assume that you can erase a circle by drawing the | ||
2225 | same circle over it in the background colour. Also, be prepared for | ||
2226 | the circle to extend a pixel beyond its obvious bounding box as a | ||
2227 | result of this; if you really need it not to do this to avoid | ||
2228 | interfering with other delicate graphics, you should probably use | ||
2229 | \cw{clip()} (\k{drawing-clip}). | ||
2230 | |||
2231 | This 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 | |||
2239 | Draws a line in the puzzle window, giving control over the line's | ||
2240 | thickness. | ||
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 | |||
2246 | Note that the coordinates and thickness are floating-point: the | ||
2247 | continuous coordinate system is in effect here. It's important to | ||
2248 | be able to address points with better-than-pixel precision in this | ||
2249 | case, because one can't otherwise properly express the endpoints of | ||
2250 | lines with both odd and even thicknesses. | ||
2251 | |||
2252 | Some platforms may perform anti-aliasing on this function. The | ||
2253 | precise pixels affected by a thick-line drawing operation may vary | ||
2254 | between platforms, and no particular guarantees are provided. | ||
2255 | Indeed, even horizontal or vertical lines may be anti-aliased. | ||
2256 | |||
2257 | This function may be used for both drawing and printing. | ||
2258 | |||
2259 | If the specified thickness is less than 1.0, 1.0 is used. | ||
2260 | This 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 | |||
2268 | Draws text in the puzzle window. | ||
2269 | |||
2270 | \c{x} and \c{y} give the coordinates of a point. The relation of | ||
2271 | this point to the location of the text is specified by \c{align}, | ||
2272 | which 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 | ||
2281 | text. (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 | ||
2284 | baselines to be aligned with one another, even if one is an ascender | ||
2285 | and 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 | ||
2290 | text. | ||
2291 | |||
2292 | \dt \cw{ALIGN_HCENTRE} | ||
2293 | |||
2294 | \dd Indicates that \c{x} is aligned with the horizontal centre of | ||
2295 | the text. | ||
2296 | |||
2297 | \dt \cw{ALIGN_HRIGHT} | ||
2298 | |||
2299 | \dd Indicates that \c{x} is aligned with the right-hand end of the | ||
2300 | text. | ||
2301 | |||
2302 | \c{fonttype} is either \cw{FONT_FIXED} or \cw{FONT_VARIABLE}, for a | ||
2303 | monospaced or proportional font respectively. (No more detail than | ||
2304 | that may be specified; it would only lead to portability issues | ||
2305 | between different platforms.) | ||
2306 | |||
2307 | \c{fontsize} is the desired size, in pixels, of the text. This size | ||
2308 | corresponds to the overall point size of the text, not to any | ||
2309 | internal dimension such as the cap-height. | ||
2310 | |||
2311 | \c{colour} is an integer index into the colours array returned by | ||
2312 | the back end function \cw{colours()} (\k{backend-colours}). | ||
2313 | |||
2314 | This function may be used for both drawing and printing. | ||
2315 | |||
2316 | The character set used to encode the text passed to this function is | ||
2317 | specified \e{by the drawing object}, although it must be a superset | ||
2318 | of ASCII. If a puzzle wants to display text that is not contained in | ||
2319 | ASCII, it should use the \cw{text_fallback()} function | ||
2320 | (\k{drawing-text-fallback}) to query the drawing object for an | ||
2321 | appropriate 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 | |||
2328 | This function is used to request a translation of UTF-8 text into | ||
2329 | whatever character encoding is expected by the drawing object's | ||
2330 | implementation of \cw{draw_text()}. | ||
2331 | |||
2332 | The input is a list of strings encoded in UTF-8: \cw{nstrings} gives | ||
2333 | the number of strings in the list, and \cw{strings[0]}, | ||
2334 | \cw{strings[1]}, ..., \cw{strings[nstrings-1]} are the strings | ||
2335 | themselves. | ||
2336 | |||
2337 | The returned string (which is dynamically allocated and must be | ||
2338 | freed when finished with) is derived from the first string in the | ||
2339 | list that the drawing object expects to be able to display reliably; | ||
2340 | it will consist of that string translated into the character set | ||
2341 | expected by \cw{draw_text()}. | ||
2342 | |||
2343 | Drawing implementations are not required to handle anything outside | ||
2344 | ASCII, but are permitted to assume that \e{some} string will be | ||
2345 | successfully translated. So every call to this function must include | ||
2346 | a string somewhere in the list (presumably the last element) which | ||
2347 | consists of nothing but ASCII, to be used by any front end which | ||
2348 | cannot handle anything else. | ||
2349 | |||
2350 | For example, if a puzzle wished to display a string including a | ||
2351 | multiplication sign (U+00D7 in Unicode, represented by the bytes C3 | ||
2352 | 97 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 | |||
2361 | which would draw a string with a times sign in the middle on | ||
2362 | platforms that support it, and fall back to a simple ASCII \cq{x} | ||
2363 | where 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 | |||
2369 | Establishes a clipping rectangle in the puzzle window. | ||
2370 | |||
2371 | \c{x} and \c{y} give the coordinates of the top left pixel of the | ||
2372 | clipping rectangle. \c{w} and \c{h} give its width and height. Thus, | ||
2373 | the horizontal extent of the rectangle runs from \c{x} to \c{x+w-1} | ||
2374 | inclusive, and the vertical extent from \c{y} to \c{y+h-1} | ||
2375 | inclusive. (These are exactly the same semantics as | ||
2376 | \cw{draw_rect()}.) | ||
2377 | |||
2378 | After this call, no drawing operation will affect anything outside | ||
2379 | the specified rectangle. The effect can be reversed by calling | ||
2380 | \cw{unclip()} (\k{drawing-unclip}). The clipping rectangle is | ||
2381 | pixel-perfect: pixels within the rectangle are affected as usual by | ||
2382 | drawing functions; pixels outside are completely untouched. | ||
2383 | |||
2384 | Back ends should not assume that a clipping rectangle will be | ||
2385 | automatically cleared up by the front end if it's left lying around; | ||
2386 | that might work on current front ends, but shouldn't be relied upon. | ||
2387 | Always explicitly call \cw{unclip()}. | ||
2388 | |||
2389 | This function may be used for both drawing and printing. | ||
2390 | |||
2391 | \S{drawing-unclip} \cw{unclip()} | ||
2392 | |||
2393 | \c void unclip(drawing *dr); | ||
2394 | |||
2395 | Reverts the effect of a previous call to \cw{clip()}. After this | ||
2396 | call, all drawing operations will be able to affect the entire | ||
2397 | puzzle window again. | ||
2398 | |||
2399 | This 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 | |||
2405 | Informs the front end that a rectangular portion of the puzzle | ||
2406 | window 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 | ||
2409 | update rectangle. \c{w} and \c{h} give its width and height. Thus, | ||
2410 | the horizontal extent of the rectangle runs from \c{x} to \c{x+w-1} | ||
2411 | inclusive, and the vertical extent from \c{y} to \c{y+h-1} | ||
2412 | inclusive. (These are exactly the same semantics as | ||
2413 | \cw{draw_rect()}.) | ||
2414 | |||
2415 | The back end redraw function \e{must} call this function to report | ||
2416 | any changes it has made to the window. Otherwise, those changes may | ||
2417 | not become immediately visible, and may then appear at an | ||
2418 | unpredictable subsequent time such as the next time the window is | ||
2419 | covered and re-exposed. | ||
2420 | |||
2421 | This function is only important when drawing. It may be called when | ||
2422 | printing 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 | ||
2424 | printing 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 | |||
2430 | Sets the text in the game's status bar to \c{text}. The text is copied | ||
2431 | from the supplied buffer, so the caller is free to deallocate or | ||
2432 | modify the buffer after use. | ||
2433 | |||
2434 | (This function is not exactly a \e{drawing} function, but it shares | ||
2435 | with the drawing API the property that it may only be called from | ||
2436 | within the back end redraw function. And it's implemented by front | ||
2437 | ends via the \c{drawing_api} function pointer table. So this is the | ||
2438 | best place to document it.) | ||
2439 | |||
2440 | The supplied text is filtered through the mid-end for optional | ||
2441 | rewriting before being passed on to the front end; the mid-end will | ||
2442 | prepend the current game time if the game is timed (and may in | ||
2443 | future perform other rewriting if it seems like a good idea). | ||
2444 | |||
2445 | This function is for drawing only; it must never be called during | ||
2446 | printing. | ||
2447 | |||
2448 | \S{drawing-blitter} Blitter functions | ||
2449 | |||
2450 | This section describes a group of related functions which save and | ||
2451 | restore a section of the puzzle window. This is most commonly used | ||
2452 | to implement user interfaces involving dragging a puzzle element | ||
2453 | around the window: at the end of each call to \cw{redraw()}, if an | ||
2454 | object is currently being dragged, the back end saves the window | ||
2455 | contents under that location and then draws the dragged object, and | ||
2456 | at the start of the next \cw{redraw()} the first thing it does is to | ||
2457 | restore the background. | ||
2458 | |||
2459 | The front end defines an opaque type called a \c{blitter}, which is | ||
2460 | capable of storing a rectangular area of a specified size. | ||
2461 | |||
2462 | Blitter functions are for drawing only; they must never be called | ||
2463 | during printing. | ||
2464 | |||
2465 | \S2{drawing-blitter-new} \cw{blitter_new()} | ||
2466 | |||
2467 | \c blitter *blitter_new(drawing *dr, int w, int h); | ||
2468 | |||
2469 | Creates a new blitter object which stores a rectangle of size \c{w} | ||
2470 | by \c{h} pixels. Returns a pointer to the blitter object. | ||
2471 | |||
2472 | Blitter objects are best stored in the \c{game_drawstate}. A good | ||
2473 | time to create them is in the \cw{set_size()} function | ||
2474 | (\k{backend-set-size}), since it is at this point that you first | ||
2475 | know 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 | |||
2481 | Disposes of a blitter object. Best called in \cw{free_drawstate()}. | ||
2482 | (However, check that the blitter object is not \cw{NULL} before | ||
2483 | attempting to free it; it is possible that a draw state might be | ||
2484 | created and freed without ever having \cw{set_size()} called on it | ||
2485 | in 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 | |||
2491 | This is a true drawing API function, in that it may only be called | ||
2492 | from within the game redraw routine. It saves a rectangular portion | ||
2493 | of 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 | ||
2496 | saved rectangle. The rectangle's width and height are the ones | ||
2497 | specified when the blitter object was created. | ||
2498 | |||
2499 | This function is required to cope and do the right thing if \c{x} | ||
2500 | and \c{y} are out of range. (The right thing probably means saving | ||
2501 | whatever part of the blitter rectangle overlaps with the visible | ||
2502 | area 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 | |||
2508 | This is a true drawing API function, in that it may only be called | ||
2509 | from within the game redraw routine. It restores a rectangular | ||
2510 | portion 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 | ||
2513 | rectangle to be restored. The rectangle's width and height are the | ||
2514 | ones specified when the blitter object was created. | ||
2515 | |||
2516 | Alternatively, you can specify both \c{x} and \c{y} as the special | ||
2517 | value \cw{BLITTER_FROMSAVED}, in which case the rectangle will be | ||
2518 | restored to exactly where it was saved from. (This is probably what | ||
2519 | you want to do almost all the time, if you're using blitters to | ||
2520 | implement draggable puzzle elements.) | ||
2521 | |||
2522 | This function is required to cope and do the right thing if \c{x} | ||
2523 | and \c{y} (or the equivalent ones saved in the blitter) are out of | ||
2524 | range. (The right thing probably means restoring whatever part of | ||
2525 | the blitter rectangle overlaps with the visible area of the puzzle | ||
2526 | window.) | ||
2527 | |||
2528 | If this function is called on a blitter which had previously been | ||
2529 | saved from a partially out-of-range rectangle, then the parts of the | ||
2530 | saved bitmap which were not visible at save time are undefined. If | ||
2531 | the blitter is restored to a different position so as to make those | ||
2532 | parts 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 | |||
2538 | This function allocates a colour index for a simple monochrome | ||
2539 | colour during printing. | ||
2540 | |||
2541 | \c{grey} must be 0 or 1. If \c{grey} is 0, the colour returned is | ||
2542 | black; 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 | |||
2548 | This function allocates a colour index for a grey-scale colour | ||
2549 | during printing. | ||
2550 | |||
2551 | \c{grey} may be any number between 0 (black) and 1 (white); for | ||
2552 | example, 0.5 indicates a medium grey. | ||
2553 | |||
2554 | The chosen colour will be rendered to the limits of the printer's | ||
2555 | halftoning capability. | ||
2556 | |||
2557 | \S{print-hatched-colour} \cw{print_hatched_colour()} | ||
2558 | |||
2559 | \c int print_hatched_colour(drawing *dr, int hatch); | ||
2560 | |||
2561 | This function allocates a colour index which does not represent a | ||
2562 | literal \e{colour}. Instead, regions shaded in this colour will be | ||
2563 | hatched with parallel lines. The \c{hatch} parameter defines what | ||
2564 | type 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 | ||
2569 | degrees. | ||
2570 | |||
2571 | \dt \cw{HATCH_BACKSLASH} | ||
2572 | |||
2573 | \dd This colour will be hatched by lines slanting to the left at 45 | ||
2574 | degrees. | ||
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 | ||
2587 | vertical lines. | ||
2588 | |||
2589 | \dt \cw{HATCH_X} | ||
2590 | |||
2591 | \dd This colour will be hatched by criss-crossing diagonal lines. | ||
2592 | |||
2593 | Colours defined to use hatching may not be used for drawing lines or | ||
2594 | text; they may only be used for filling areas. That is, they may be | ||
2595 | used 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} | ||
2598 | parameter 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 | |||
2606 | This function allocates a colour index for a fully specified RGB | ||
2607 | colour during printing. | ||
2608 | |||
2609 | \c{r}, \c{g} and \c{b} may each be anywhere in the range from 0 to 1. | ||
2610 | |||
2611 | If printing in black and white only, these values will be ignored, | ||
2612 | and either pure black or pure white will be used instead, according | ||
2613 | to the \q{grey} parameter. (The fallback colour is the same as the | ||
2614 | one 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 | |||
2621 | This function allocates a colour index for a fully specified RGB | ||
2622 | colour during printing. | ||
2623 | |||
2624 | \c{r}, \c{g} and \c{b} may each be anywhere in the range from 0 to 1. | ||
2625 | |||
2626 | If printing in black and white only, these values will be ignored, | ||
2627 | and a shade of grey given by the \c{grey} parameter will be used | ||
2628 | instead. (The fallback colour is the same as the one which would be | ||
2629 | allocated 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 | |||
2636 | This function allocates a colour index for a fully specified RGB | ||
2637 | colour during printing. | ||
2638 | |||
2639 | \c{r}, \c{g} and \c{b} may each be anywhere in the range from 0 to 1. | ||
2640 | |||
2641 | If printing in black and white only, these values will be ignored, | ||
2642 | and a form of cross-hatching given by the \c{hatch} parameter will | ||
2643 | be used instead; see \k{print-hatched-colour} for the possible | ||
2644 | values of this parameter. (The fallback colour is the same as the | ||
2645 | one 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 | |||
2651 | This function is called to set the thickness of lines drawn during | ||
2652 | printing. It is meaningless in drawing: all lines drawn by | ||
2653 | \cw{draw_line()}, \cw{draw_circle} and \cw{draw_polygon()} are one | ||
2654 | pixel in thickness. However, in printing there is no clear | ||
2655 | definition of a pixel and so line widths must be explicitly | ||
2656 | specified. | ||
2657 | |||
2658 | The line width is specified in the usual coordinate system. Note, | ||
2659 | however, that it is a hint only: the central printing system may | ||
2660 | choose to vary line thicknesses at user request or due to printer | ||
2661 | capabilities. | ||
2662 | |||
2663 | \S{print-line-dotted} \cw{print_line_dotted()} | ||
2664 | |||
2665 | \c void print_line_dotted(drawing *dr, bool dotted); | ||
2666 | |||
2667 | This function is called to toggle the drawing of dotted lines during | ||
2668 | printing. It is not supported during drawing. | ||
2669 | |||
2670 | Setting \cq{dotted} to \cw{true} means that future lines drawn by | ||
2671 | \cw{draw_line()}, \cw{draw_circle} and \cw{draw_polygon()} will be | ||
2672 | dotted. Setting it to \cw{false} means that they will be solid. | ||
2673 | |||
2674 | Some front ends may impose restrictions on the width of dotted | ||
2675 | lines. Asking for a dotted line via this front end will override any | ||
2676 | line width request if the front end requires it. | ||
2677 | |||
2678 | \H{drawing-frontend} The drawing API as implemented by the front end | ||
2679 | |||
2680 | This section describes the drawing API in the function-pointer form | ||
2681 | in which it is implemented by a front end. | ||
2682 | |||
2683 | (It isn't only platform-specific front ends which implement this | ||
2684 | API; the platform-independent module \c{ps.c} also provides an | ||
2685 | implementation of it which outputs PostScript. Thus, any platform | ||
2686 | which wants to do PS printing can do so with minimum fuss.) | ||
2687 | |||
2688 | The following entries all describe function pointer fields in a | ||
2689 | structure called \c{drawing_api}. Each of the functions takes a | ||
2690 | \cq{void *} context pointer, which it should internally cast back to | ||
2691 | a more useful type. Thus, a drawing \e{object} (\c{drawing *)} | ||
2692 | suitable for passing to the back end redraw or printing functions | ||
2693 | is constructed by passing a \c{drawing_api} and a \cq{void *} to the | ||
2694 | function \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 | |||
2702 | This function behaves exactly like the back end \cw{draw_text()} | ||
2703 | function; 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 | |||
2710 | This function behaves exactly like the back end \cw{draw_rect()} | ||
2711 | function; 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 | |||
2718 | This function behaves exactly like the back end \cw{draw_line()} | ||
2719 | function; 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 | |||
2726 | This function behaves exactly like the back end \cw{draw_polygon()} | ||
2727 | function; 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 | |||
2734 | This function behaves exactly like the back end \cw{draw_circle()} | ||
2735 | function; 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 | |||
2743 | This function behaves exactly like the back end | ||
2744 | \cw{draw_thick_line()} function; see \k{drawing-draw-thick-line}. | ||
2745 | |||
2746 | An implementation of this API which doesn't provide high-quality | ||
2747 | rendering of thick lines is permitted to define this function | ||
2748 | pointer to be \cw{NULL}. The middleware in \cw{drawing.c} will notice | ||
2749 | and 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 | |||
2755 | This function behaves exactly like the back end \cw{draw_update()} | ||
2756 | function; see \k{drawing-draw-update}. | ||
2757 | |||
2758 | An implementation of this API which only supports printing is | ||
2759 | permitted to define this function pointer to be \cw{NULL} rather | ||
2760 | than 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 | |||
2767 | This function behaves exactly like the back end \cw{clip()} | ||
2768 | function; see \k{drawing-clip}. | ||
2769 | |||
2770 | \S{drawingapi-unclip} \cw{unclip()} | ||
2771 | |||
2772 | \c void (*unclip)(void *handle); | ||
2773 | |||
2774 | This function behaves exactly like the back end \cw{unclip()} | ||
2775 | function; see \k{drawing-unclip}. | ||
2776 | |||
2777 | \S{drawingapi-start-draw} \cw{start_draw()} | ||
2778 | |||
2779 | \c void (*start_draw)(void *handle); | ||
2780 | |||
2781 | This function is called at the start of drawing. It allows the front | ||
2782 | end to initialise any temporary data required to draw with, such as | ||
2783 | device contexts. | ||
2784 | |||
2785 | Implementations of this API which do not provide drawing services | ||
2786 | may define this function pointer to be \cw{NULL}; it will never be | ||
2787 | called unless drawing is attempted. | ||
2788 | |||
2789 | \S{drawingapi-end-draw} \cw{end_draw()} | ||
2790 | |||
2791 | \c void (*end_draw)(void *handle); | ||
2792 | |||
2793 | This function is called at the end of drawing. It allows the front | ||
2794 | end to do cleanup tasks such as deallocating device contexts and | ||
2795 | scheduling appropriate GUI redraw events. | ||
2796 | |||
2797 | Implementations of this API which do not provide drawing services | ||
2798 | may define this function pointer to be \cw{NULL}; it will never be | ||
2799 | called 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 | |||
2805 | This function behaves exactly like the back end \cw{status_bar()} | ||
2806 | function; see \k{drawing-status-bar}. | ||
2807 | |||
2808 | Front ends implementing this function need not worry about it being | ||
2809 | called repeatedly with the same text; the middleware code in | ||
2810 | \cw{status_bar()} will take care of this. | ||
2811 | |||
2812 | Implementations of this API which do not provide drawing services | ||
2813 | may define this function pointer to be \cw{NULL}; it will never be | ||
2814 | called 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 | |||
2820 | This function behaves exactly like the back end \cw{blitter_new()} | ||
2821 | function; see \k{drawing-blitter-new}. | ||
2822 | |||
2823 | Implementations of this API which do not provide drawing services | ||
2824 | may define this function pointer to be \cw{NULL}; it will never be | ||
2825 | called unless drawing is attempted. | ||
2826 | |||
2827 | \S{drawingapi-blitter-free} \cw{blitter_free()} | ||
2828 | |||
2829 | \c void (*blitter_free)(void *handle, blitter *bl); | ||
2830 | |||
2831 | This function behaves exactly like the back end \cw{blitter_free()} | ||
2832 | function; see \k{drawing-blitter-free}. | ||
2833 | |||
2834 | Implementations of this API which do not provide drawing services | ||
2835 | may define this function pointer to be \cw{NULL}; it will never be | ||
2836 | called 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 | |||
2842 | This function behaves exactly like the back end \cw{blitter_save()} | ||
2843 | function; see \k{drawing-blitter-save}. | ||
2844 | |||
2845 | Implementations of this API which do not provide drawing services | ||
2846 | may define this function pointer to be \cw{NULL}; it will never be | ||
2847 | called 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 | |||
2853 | This function behaves exactly like the back end \cw{blitter_load()} | ||
2854 | function; see \k{drawing-blitter-load}. | ||
2855 | |||
2856 | Implementations of this API which do not provide drawing services | ||
2857 | may define this function pointer to be \cw{NULL}; it will never be | ||
2858 | called unless drawing is attempted. | ||
2859 | |||
2860 | \S{drawingapi-begin-doc} \cw{begin_doc()} | ||
2861 | |||
2862 | \c void (*begin_doc)(void *handle, int pages); | ||
2863 | |||
2864 | This function is called at the beginning of a printing run. It gives | ||
2865 | the front end an opportunity to initialise any required printing | ||
2866 | subsystem. It also provides the number of pages in advance. | ||
2867 | |||
2868 | Implementations of this API which do not provide printing services | ||
2869 | may define this function pointer to be \cw{NULL}; it will never be | ||
2870 | called unless printing is attempted. | ||
2871 | |||
2872 | \S{drawingapi-begin-page} \cw{begin_page()} | ||
2873 | |||
2874 | \c void (*begin_page)(void *handle, int number); | ||
2875 | |||
2876 | This function is called during printing, at the beginning of each | ||
2877 | page. It gives the page number (numbered from 1 rather than 0, so | ||
2878 | suitable for use in user-visible contexts). | ||
2879 | |||
2880 | Implementations of this API which do not provide printing services | ||
2881 | may define this function pointer to be \cw{NULL}; it will never be | ||
2882 | called 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 | |||
2889 | This function is called during printing, just before printing a | ||
2890 | single puzzle on a page. It specifies the size and location of the | ||
2891 | puzzle on the page. | ||
2892 | |||
2893 | \c{xm} and \c{xc} specify the horizontal position of the puzzle on | ||
2894 | the page, as a linear function of the page width. The front end is | ||
2895 | expected to multiply the page width by \c{xm}, add \c{xc} (measured | ||
2896 | in millimetres), and use the resulting x-coordinate as the left edge | ||
2897 | of the puzzle. | ||
2898 | |||
2899 | Similarly, \c{ym} and \c{yc} specify the vertical position of the | ||
2900 | puzzle as a function of the page height: the page height times | ||
2901 | \c{ym}, plus \c{yc} millimetres, equals the desired distance from | ||
2902 | the top of the page to the top of the puzzle. | ||
2903 | |||
2904 | (This unwieldy mechanism is required because not all printing | ||
2905 | systems can communicate the page size back to the software. The | ||
2906 | PostScript back end, for example, writes out PS which determines the | ||
2907 | page size at print time by means of calling \cq{clippath}, and | ||
2908 | centres the puzzles within that. Thus, exactly the same PS file | ||
2909 | works on A4 or on US Letter paper without needing local | ||
2910 | configuration, which simplifies matters.) | ||
2911 | |||
2912 | \cw{pw} and \cw{ph} give the size of the puzzle in drawing API | ||
2913 | coordinates. The printing system will subsequently call the puzzle's | ||
2914 | own print function, which will in turn call drawing API functions in | ||
2915 | the expectation that an area \cw{pw} by \cw{ph} units is available | ||
2916 | to draw the puzzle on. | ||
2917 | |||
2918 | Finally, \cw{wmm} gives the desired width of the puzzle in | ||
2919 | millimetres. (The aspect ratio is expected to be preserved, so if | ||
2920 | the desired puzzle height is also needed then it can be computed as | ||
2921 | \cw{wmm*ph/pw}.) | ||
2922 | |||
2923 | Implementations of this API which do not provide printing services | ||
2924 | may define this function pointer to be \cw{NULL}; it will never be | ||
2925 | called unless printing is attempted. | ||
2926 | |||
2927 | \S{drawingapi-end-puzzle} \cw{end_puzzle()} | ||
2928 | |||
2929 | \c void (*end_puzzle)(void *handle); | ||
2930 | |||
2931 | This function is called after the printing of a specific puzzle is | ||
2932 | complete. | ||
2933 | |||
2934 | Implementations of this API which do not provide printing services | ||
2935 | may define this function pointer to be \cw{NULL}; it will never be | ||
2936 | called unless printing is attempted. | ||
2937 | |||
2938 | \S{drawingapi-end-page} \cw{end_page()} | ||
2939 | |||
2940 | \c void (*end_page)(void *handle, int number); | ||
2941 | |||
2942 | This function is called after the printing of a page is finished. | ||
2943 | |||
2944 | Implementations of this API which do not provide printing services | ||
2945 | may define this function pointer to be \cw{NULL}; it will never be | ||
2946 | called unless printing is attempted. | ||
2947 | |||
2948 | \S{drawingapi-end-doc} \cw{end_doc()} | ||
2949 | |||
2950 | \c void (*end_doc)(void *handle); | ||
2951 | |||
2952 | This function is called after the printing of the entire document is | ||
2953 | finished. This is the moment to close files, send things to the | ||
2954 | print spooler, or whatever the local convention is. | ||
2955 | |||
2956 | Implementations of this API which do not provide printing services | ||
2957 | may define this function pointer to be \cw{NULL}; it will never be | ||
2958 | called unless printing is attempted. | ||
2959 | |||
2960 | \S{drawingapi-line-width} \cw{line_width()} | ||
2961 | |||
2962 | \c void (*line_width)(void *handle, float width); | ||
2963 | |||
2964 | This function is called to set the line thickness, during printing | ||
2965 | only. 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 | ||
2967 | have scaled it on the way past. | ||
2968 | |||
2969 | However, the width is still specified in the same coordinate system | ||
2970 | as the rest of the drawing. | ||
2971 | |||
2972 | Implementations of this API which do not provide printing services | ||
2973 | may define this function pointer to be \cw{NULL}; it will never be | ||
2974 | called unless printing is attempted. | ||
2975 | |||
2976 | \S{drawingapi-line-dotted} \cw{line_dotted()} | ||
2977 | |||
2978 | \c void (*line_dotted)(void *handle, bool dotted); | ||
2979 | |||
2980 | This function is called to toggle drawing of dotted lines, during | ||
2981 | printing only. | ||
2982 | |||
2983 | Implementations of this API which do not provide printing services | ||
2984 | may define this function pointer to be \cw{NULL}; it will never be | ||
2985 | called 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 | |||
2992 | This function behaves exactly like the back end \cw{text_fallback()} | ||
2993 | function; see \k{drawing-text-fallback}. | ||
2994 | |||
2995 | Implementations of this API which do not support any characters | ||
2996 | outside ASCII may define this function pointer to be \cw{NULL}, in | ||
2997 | which case the central code in \cw{drawing.c} will provide a default | ||
2998 | implementation. | ||
2999 | |||
3000 | \H{drawingapi-frontend} The drawing API as called by the front end | ||
3001 | |||
3002 | There are a small number of functions provided in \cw{drawing.c} | ||
3003 | which the front end needs to \e{call}, rather than helping to | ||
3004 | implement. 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 | |||
3011 | This function creates a drawing object. It is passed a | ||
3012 | \c{drawing_api}, which is a structure containing nothing but | ||
3013 | function pointers; and also a \cq{void *} handle. The handle is | ||
3014 | passed back to each function pointer when it is called. | ||
3015 | |||
3016 | The \c{midend} parameter is used for rewriting the status bar | ||
3017 | contents: \cw{status_bar()} (see \k{drawing-status-bar}) has to call | ||
3018 | a function in the mid-end which might rewrite the status bar text. | ||
3019 | If the drawing object is to be used only for printing, or if the | ||
3020 | game 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 | |||
3027 | This function frees a drawing object. Note that the \cq{void *} | ||
3028 | handle is not freed; if that needs cleaning up it must be done by | ||
3029 | the 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 | |||
3037 | This function is called by the implementations of the drawing API | ||
3038 | functions when they are called in a printing context. It takes a | ||
3039 | colour index as input, and returns the description of the colour as | ||
3040 | requested by the back end. | ||
3041 | |||
3042 | \c{printing_in_colour} is \cw{true} iff the implementation is printing | ||
3043 | in colour. This will alter the results returned if the colour in | ||
3044 | question was specified with a black-and-white fallback value. | ||
3045 | |||
3046 | If the colour should be rendered by hatching, \c{*hatch} is filled | ||
3047 | with the type of hatching desired. See \k{print-grey-colour} for | ||
3048 | details of the values this integer can take. | ||
3049 | |||
3050 | If the colour should be rendered as solid colour, \c{*hatch} is | ||
3051 | given a negative value, and \c{*r}, \c{*g} and \c{*b} are filled | ||
3052 | with the RGB values of the desired colour (if printing in colour), | ||
3053 | or all filled with the grey-scale value (if printing in black and | ||
3054 | white). | ||
3055 | |||
3056 | \C{midend} The API provided by the mid-end | ||
3057 | |||
3058 | This chapter documents the API provided by the mid-end to be called | ||
3059 | by the front end. You probably only need to read this if you are a | ||
3060 | front end implementor, i.e. you are porting Puzzles to a new | ||
3061 | platform. If you're only interested in writing new puzzles, you can | ||
3062 | safely skip this chapter. | ||
3063 | |||
3064 | All the persistent state in the mid-end is encapsulated within a | ||
3065 | \c{midend} structure, to facilitate having multiple mid-ends in any | ||
3066 | port which supports multiple puzzle windows open simultaneously. | ||
3067 | Each \c{midend} is intended to handle the contents of a single | ||
3068 | puzzle 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 | |||
3075 | Allocates and returns a new mid-end structure. | ||
3076 | |||
3077 | The \c{fe} argument is stored in the mid-end. It will be used when | ||
3078 | calling back to functions such as \cw{activate_timer()} | ||
3079 | (\k{frontend-activate-timer}), and will be passed on to the back end | ||
3080 | function \cw{colours()} (\k{backend-colours}). | ||
3081 | |||
3082 | The parameters \c{drapi} and \c{drhandle} are passed to | ||
3083 | \cw{drawing_new()} (\k{drawing-new}) to construct a drawing object | ||
3084 | which will be passed to the back end function \cw{redraw()} | ||
3085 | (\k{backend-redraw}). Hence, all drawing-related function pointers | ||
3086 | defined in \c{drapi} can expect to be called with \c{drhandle} as | ||
3087 | their first argument. | ||
3088 | |||
3089 | The \c{ourgame} argument points to a container structure describing | ||
3090 | a game back end. The mid-end thus created will only be capable of | ||
3091 | handling that one game. (So even in a monolithic front end | ||
3092 | containing all the games, this imposes the constraint that any | ||
3093 | individual puzzle window is tied to a single game. Unless, of | ||
3094 | course, you feel brave enough to change the mid-end for the window | ||
3095 | without closing the window...) | ||
3096 | |||
3097 | \H{midend-free} \cw{midend_free()} | ||
3098 | |||
3099 | \c void midend_free(midend *me); | ||
3100 | |||
3101 | Frees 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 | |||
3107 | Returns the \cq{tilesize} parameter being used to display the | ||
3108 | current 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 | |||
3114 | Sets the current game parameters for a mid-end. Subsequent games | ||
3115 | generated by \cw{midend_new_game()} (\k{midend-new-game}) will use | ||
3116 | these parameters until further notice. | ||
3117 | |||
3118 | The 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 | ||
3120 | previously got it from \cw{midend_get_presets()} | ||
3121 | (\k{midend-get-presets}). Thus, this function is usually called in | ||
3122 | response 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 | |||
3128 | Returns the current game parameters stored in this mid-end. | ||
3129 | |||
3130 | The returned value is dynamically allocated, and should be freed | ||
3131 | when 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 | |||
3139 | Tells the mid-end to figure out its window size. | ||
3140 | |||
3141 | On input, \c{*x} and \c{*y} should contain the maximum or requested | ||
3142 | size for the window. (Typically this will be the size of the screen | ||
3143 | that the window has to fit on, or similar.) The mid-end will | ||
3144 | repeatedly call the back end function \cw{compute_size()} | ||
3145 | (\k{backend-compute-size}), searching for a tile size that best | ||
3146 | satisfies the requirements. On exit, \c{*x} and \c{*y} will contain | ||
3147 | the size needed for the puzzle window's drawing area. (It is of | ||
3148 | course up to the front end to adjust this for any additional window | ||
3149 | furniture such as menu bars and window borders, if necessary. The | ||
3150 | status bar is also not included in this size.) | ||
3151 | |||
3152 | Use \c{user_size} to indicate whether \c{*x} and \c{*y} are a | ||
3153 | requested size, or just a maximum size. | ||
3154 | |||
3155 | If \c{user_size} is set to \cw{true}, the mid-end will treat the | ||
3156 | input size as a request, and will pick a tile size which | ||
3157 | approximates it \e{as closely as possible}, going over the game's | ||
3158 | preferred tile size if necessary to achieve this. The mid-end will | ||
3159 | also use the resulting tile size as its preferred one until further | ||
3160 | notice, on the assumption that this size was explicitly requested | ||
3161 | by the user. Use this option if you want your front end to support | ||
3162 | dynamic resizing of the puzzle window with automatic scaling of the | ||
3163 | puzzle to fit. | ||
3164 | |||
3165 | If \c{user_size} is set to \cw{false}, then the game's tile size | ||
3166 | will never go over its preferred one, although it may go under in | ||
3167 | order to fit within the maximum bounds specified by \c{*x} and | ||
3168 | \c{*y}. This is the recommended approach when opening a new window | ||
3169 | at default size: the game will use its preferred size unless it has | ||
3170 | to use a smaller one to fit on the screen. If the tile size is | ||
3171 | shrunk for this reason, the change will not persist; if a smaller | ||
3172 | grid is subsequently chosen, the tile size will recover. | ||
3173 | |||
3174 | The mid-end will try as hard as it can to return a size which is | ||
3175 | less than or equal to the input size, in both dimensions. In extreme | ||
3176 | circumstances it may fail (if even the lowest possible tile size | ||
3177 | gives window dimensions greater than the input), in which case it | ||
3178 | will return a size greater than the input size. Front ends should be | ||
3179 | prepared for this to happen (i.e. don't crash or fail an assertion), | ||
3180 | but may handle it in any way they see fit: by rejecting the game | ||
3181 | parameters which caused the problem, by opening a window larger than | ||
3182 | the screen regardless of inconvenience, by introducing scroll bars | ||
3183 | on the window, by drawing on a large bitmap and scaling it into a | ||
3184 | smaller window, or by any other means you can think of. It is likely | ||
3185 | that when the tile size is that small the game will be unplayable | ||
3186 | anyway, so don't put \e{too} much effort into handling it | ||
3187 | creatively. | ||
3188 | |||
3189 | If your platform has no limit on window size (or if you're planning | ||
3190 | to 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 | ||
3192 | that \e{and} set the \c{user_size} flag, though! | ||
3193 | |||
3194 | The \cw{device_pixel_ratio} allows the front end to specify that its | ||
3195 | pixels are unusually large or small (or should be treated as such). | ||
3196 | The mid-end uses this to adjust the tile size, both at startup (if the | ||
3197 | ratio is not 1) and if the ratio changes. | ||
3198 | |||
3199 | A \cw{device_pixel_ratio} of 1 indicates normal-sized pixels. | ||
3200 | \q{Normal} is not precisely defined, but it's about 4 pixels per | ||
3201 | millimetre on a screen designed to be viewed from a metre away, or a | ||
3202 | size such that text 15 pixels high is comfortably readable. Some | ||
3203 | platforms have a concept of a logical pixel that this can be mapped | ||
3204 | onto. For instance, Cascading Style Sheets (CSS) has a unit called | ||
3205 | \cq{px} that only matches physical pixels at a \cw{device_pixel_ratio} | ||
3206 | of 1. | ||
3207 | |||
3208 | The \cw{device_pixel_ratio} indicates the number of physical pixels in | ||
3209 | a normal-sized pixel, so values less than 1 indicate unusually large | ||
3210 | pixels and values greater than 1 indicate unusually small pixels. | ||
3211 | |||
3212 | The 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 | |||
3219 | This function resets the midend's preferred tile size to that of the | ||
3220 | standard puzzle. | ||
3221 | |||
3222 | As discussed in \k{midend-size}, puzzle resizes are typically | ||
3223 | 'sticky', in that once the user has dragged the puzzle to a different | ||
3224 | window size, the resulting tile size will be remembered and used when | ||
3225 | the puzzle configuration changes. If you \e{don't} want that, e.g. if | ||
3226 | you want to provide a command to explicitly reset the puzzle size back | ||
3227 | to 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 | |||
3235 | Causes the mid-end to begin a new game. Normally the game will be a | ||
3236 | new randomly generated puzzle. However, if you have previously | ||
3237 | called \cw{midend_game_id()} or \cw{midend_set_config()}, the game | ||
3238 | generated might be dictated by the results of those functions. (In | ||
3239 | particular, you \e{must} call \cw{midend_new_game()} after calling | ||
3240 | either of those functions, or else no immediate effect will be | ||
3241 | visible.) | ||
3242 | |||
3243 | You will probably need to call \cw{midend_size()} after calling this | ||
3244 | function, because if the game parameters have been changed since the | ||
3245 | last new game then the window size might need to change. (If you | ||
3246 | know the parameters \e{haven't} changed, you don't need to do this.) | ||
3247 | |||
3248 | This function will create a new \c{game_drawstate}, but does not | ||
3249 | actually perform a redraw (since you often need to call | ||
3250 | \cw{midend_size()} before the redraw can be done). So after calling | ||
3251 | this function and after calling \cw{midend_size()}, you should then | ||
3252 | call \cw{midend_redraw()}. (It is not necessary to call | ||
3253 | \cw{midend_force_redraw()}; that will discard the draw state and | ||
3254 | create a fresh one, which is unnecessary in this case since there's | ||
3255 | a 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 | |||
3261 | This function causes the current game to be restarted. This is done | ||
3262 | by placing a new copy of the original game state on the end of the | ||
3263 | undo list (so that an accidental restart can be undone). | ||
3264 | |||
3265 | This function automatically causes a redraw, i.e. the front end can | ||
3266 | expect its drawing API to be called from \e{within} a call to this | ||
3267 | function. 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 | |||
3274 | Forces a complete redraw of the puzzle window, by means of | ||
3275 | discarding the current \c{game_drawstate} and creating a new one | ||
3276 | from scratch before calling the game's \cw{redraw()} function. | ||
3277 | |||
3278 | The front end can expect its drawing API to be called from within a | ||
3279 | call 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 | |||
3286 | Causes a partial redraw of the puzzle window, by means of simply | ||
3287 | calling the game's \cw{redraw()} function. (That is, the only things | ||
3288 | redrawn will be things that have changed since the last redraw.) | ||
3289 | |||
3290 | The front end can expect its drawing API to be called from within a | ||
3291 | call 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 | |||
3298 | The front end calls this function to report a mouse or keyboard event. | ||
3299 | The parameters \c{x} and \c{y} are identical to the ones passed to the | ||
3300 | back 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 | ||
3304 | values passed to in, and some additional special button values | ||
3305 | are defined for the front end to pass to the midend (see below). | ||
3306 | |||
3307 | Also, the front end is \e{not} required to provide guarantees about | ||
3308 | mouse event ordering. The mid-end will sort out multiple simultaneous | ||
3309 | button presses and changes of button; the front end's responsibility | ||
3310 | is simply to pass on the mouse events it receives as accurately as | ||
3311 | possible. | ||
3312 | |||
3313 | (Some platforms may need to emulate absent mouse buttons by means of | ||
3314 | using a modifier key such as Shift with another mouse button. This | ||
3315 | tends to mean that if Shift is pressed or released in the middle of | ||
3316 | a mouse drag, the mid-end will suddenly stop receiving, say, | ||
3317 | \cw{LEFT_DRAG} events and start receiving \cw{RIGHT_DRAG}s, with no | ||
3318 | intervening button release or press events. This too is something | ||
3319 | which the mid-end will sort out for you; the front end has no | ||
3320 | obligation to maintain sanity in this area.) | ||
3321 | |||
3322 | The front end \e{should}, however, always eventually send some kind | ||
3323 | of button release. On some platforms this requires special effort: | ||
3324 | Windows, for example, requires a call to the system API function | ||
3325 | \cw{SetCapture()} in order to ensure that your window receives a | ||
3326 | mouse-up event even if the pointer has left the window by the time | ||
3327 | the mouse button is released. On any platform that requires this | ||
3328 | sort of thing, the front end \e{is} responsible for doing it. | ||
3329 | |||
3330 | Calling this function is very likely to result in calls back to the | ||
3331 | front end's drawing API and/or \cw{activate_timer()} | ||
3332 | (\k{frontend-activate-timer}). | ||
3333 | |||
3334 | The return value from \cw{midend_process_key()} is one of the | ||
3335 | following constants: | ||
3336 | |||
3337 | \dt \cw{PKR_QUIT} | ||
3338 | |||
3339 | \dd Means that the effect of the keypress was to request termination | ||
3340 | of the program. A front end should shut down the puzzle in response | ||
3341 | to 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 | ||
3346 | the puzzle itself. | ||
3347 | |||
3348 | \dt \cw{PKR_NO_EFFECT} | ||
3349 | |||
3350 | \dd The keypress had no effect, but might have had an effect in | ||
3351 | slightly different circumstances. For instance it requested a move | ||
3352 | that 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 | ||
3357 | use for at all. | ||
3358 | |||
3359 | A front end might respond to the last value by passing the key on to | ||
3360 | something else that might be interested in it. | ||
3361 | |||
3362 | The following additional values of \c{button} are permitted to be | ||
3363 | passed to this function by the front end, but are never passed on to | ||
3364 | the back end. They indicate front-end specific UI operations, such as | ||
3365 | selecting an option from a drop-down menu. (Otherwise the front end | ||
3366 | would have to translate the \q{New Game} menu item into an \cq{n} | ||
3367 | keypress, 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 | ||
3389 | front end might perfectly well handle this on its own. But including | ||
3390 | it in this enumeration allows the front end to treat all these menu | ||
3391 | items the same, by translating each of them into a button code passed | ||
3392 | to the midend, and handle quitting by noticing the \c{false} return | ||
3393 | value from \cw{midend_process_key()}.) | ||
3394 | |||
3395 | The midend tolerates any modifier being set on any key and removes | ||
3396 | them as necessary before passing the key on to the backend. It will | ||
3397 | also 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 | |||
3404 | This function behaves similarly to the backend's \cw{request_keys()} | ||
3405 | function (\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 | ||
3408 | labels (i.e. the \cw{key_label} items that have their \cw{label} | ||
3409 | fields 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 | |||
3416 | This is a thin wrapper around the backend's \cw{current_key_label()} | ||
3417 | function (\k{backend-current-key-label}). Front ends that need to | ||
3418 | label \cw{CURSOR_SELECT} or \cw{CURSOR_SELECT2} should call this | ||
3419 | function after each move (at least after each call to | ||
3420 | \cw{midend_process_key()}) to get the current labels. The front end | ||
3421 | should arrange to copy the returned string somewhere before the next | ||
3422 | call to the mid-end, just in case it's dynamically allocated. If the | ||
3423 | button supplied does nothing, the label returned will be an empty | ||
3424 | string. | ||
3425 | |||
3426 | \H{midend-colours} \cw{midend_colours()} | ||
3427 | |||
3428 | \c float *midend_colours(midend *me, int *ncolours); | ||
3429 | |||
3430 | Returns an array of the colours required by the game, in exactly the | ||
3431 | same format as that returned by the back end function \cw{colours()} | ||
3432 | (\k{backend-colours}). Front ends should call this function rather | ||
3433 | than calling the back end's version directly, since the mid-end adds | ||
3434 | standard customisation facilities. (At the time of writing, those | ||
3435 | customisation facilities are implemented hackily by means of | ||
3436 | environment variables, but it's not impossible that they may become | ||
3437 | more full and formal in future.) | ||
3438 | |||
3439 | \H{midend-timer} \cw{midend_timer()} | ||
3440 | |||
3441 | \c void midend_timer(midend *me, float tplus); | ||
3442 | |||
3443 | If the mid-end has called \cw{activate_timer()} | ||
3444 | (\k{frontend-activate-timer}) to request regular callbacks for | ||
3445 | purposes of animation or timing, this is the function the front end | ||
3446 | should call on a regular basis. The argument \c{tplus} gives the | ||
3447 | time, in seconds, since the last time either this function was | ||
3448 | called or \cw{activate_timer()} was invoked. | ||
3449 | |||
3450 | One of the major purposes of timing in the mid-end is to perform | ||
3451 | move animation. Therefore, calling this function is very likely to | ||
3452 | result 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 | |||
3458 | Returns a data structure describing this game's collection of preset | ||
3459 | game parameters, organised into a hierarchical structure of menus and | ||
3460 | submenus. | ||
3461 | |||
3462 | The return value is a pointer to a data structure containing the | ||
3463 | following fields (among others, which are not intended for front end | ||
3464 | use): | ||
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 | |||
3473 | Those fields describe the intended contents of one particular menu in | ||
3474 | the hierarchy. \cq{entries} points to an array of \cq{n_entries} | ||
3475 | items, 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 | |||
3484 | Of these fields, \cq{title} and \cq{id} are present in every entry, | ||
3485 | giving (respectively) the textual name of the menu item and an integer | ||
3486 | identifier for it. The integer id will correspond to the one returned | ||
3487 | by \c{midend_which_preset} (\k{midend-which-preset}), when that preset | ||
3488 | is the one selected. | ||
3489 | |||
3490 | The other two fields are mutually exclusive. Each \c{struct | ||
3491 | preset_menu_entry} will have one of those fields \cw{NULL} and the | ||
3492 | other 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 | ||
3494 | name; if it's a submenu, then \cq{submenu} instead will be non-null, | ||
3495 | and will point at a subsidiary \c{struct preset_menu}. | ||
3496 | |||
3497 | The complete hierarchy of these structures is owned by the mid-end, | ||
3498 | and will be freed when the mid-end is freed. The front end should not | ||
3499 | attempt to free any of it. | ||
3500 | |||
3501 | The integer identifiers will be allocated densely from 0 upwards, so | ||
3502 | that it's reasonable for the front end to allocate an array which uses | ||
3503 | them as indices, if it needs to store information per preset menu | ||
3504 | item. 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} | ||
3506 | variable, into which \cw{midend_get_presets} will write an integer one | ||
3507 | larger than the largest id number actually used (i.e. the number of | ||
3508 | elements the front end would need in the array). | ||
3509 | |||
3510 | Submenu-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 | |||
3516 | Returns the numeric index of the preset game parameter structure | ||
3517 | which matches the current game parameters, or a negative number if | ||
3518 | no preset matches. Front ends could use this to maintain a tick | ||
3519 | beside one of the items in the menu (or tick the \q{Custom} option | ||
3520 | if the return value is less than zero). | ||
3521 | |||
3522 | The returned index value (if non-negative) will match the \c{id} field | ||
3523 | of 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 | |||
3530 | This function returns \cw{true} if the puzzle has a use for a | ||
3531 | textual status line (to display score, completion status, currently | ||
3532 | active tiles, time, or anything else). | ||
3533 | |||
3534 | Front ends should call this function rather than talking directly to | ||
3535 | the 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 | |||
3542 | Returns a dialog box description for user configuration. | ||
3543 | |||
3544 | On input, \cw{which} should be set to one of three values, which | ||
3545 | select 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 | ||
3550 | puzzle itself. This should be used when the user selects \q{Custom} | ||
3551 | from the game types menu (or equivalent). The mid-end passes this | ||
3552 | request 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 | ||
3558 | viewing the existing one). The mid-end generates this dialog box | ||
3559 | description 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 | ||
3565 | viewing the existing one). The mid-end generates this dialog box | ||
3566 | description 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 | ||
3574 | enumeration, so that frontends can extend it for their own internal | ||
3575 | use. For example, you might wrap this function with a | ||
3576 | \cw{frontend_get_config} which handles some values of \c{which} itself | ||
3577 | and hands others on to the midend, depending on whether \cw{which < | ||
3578 | CFG_FRONTEND_SPECIFIC}.) | ||
3579 | |||
3580 | The returned value is an array of \cw{config_item}s, exactly as | ||
3581 | described in \k{backend-configure}. Another returned value is an | ||
3582 | ASCII string giving a suitable title for the configuration window, | ||
3583 | in \c{*wintitle}. | ||
3584 | |||
3585 | Both returned values are dynamically allocated and will need to be | ||
3586 | freed. The window title can be freed in the obvious way; the | ||
3587 | \cw{config_item} array is a slightly complex structure, so a utility | ||
3588 | function \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} | ||
3592 | array until the dialog box is dismissed, because before then you | ||
3593 | will 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 | |||
3600 | Passes 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 | ||
3604 | contain the results of the user's editing operations. | ||
3605 | |||
3606 | This function returns \cw{NULL} on success, or otherwise (if the | ||
3607 | configuration data was in some way invalid) an ASCII string | ||
3608 | containing an error message suitable for showing to the user. | ||
3609 | |||
3610 | If the function succeeds, it is likely that the game parameters will | ||
3611 | have been changed and it is certain that a new game will be | ||
3612 | requested. The front end should therefore call | ||
3613 | \cw{midend_new_game()}, and probably also re-think the window size | ||
3614 | using \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 | |||
3621 | Passes 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 | ||
3623 | mid-end will process and use for the next generated game. | ||
3624 | |||
3625 | This function returns \cw{NULL} on success, or otherwise (if the | ||
3626 | configuration data was in some way invalid) an ASCII string | ||
3627 | containing an error message (not dynamically allocated) suitable for | ||
3628 | showing to the user. In the event of an error, the mid-end's | ||
3629 | internal state will be left exactly as it was before the call. | ||
3630 | |||
3631 | If the function succeeds, it is likely that the game parameters will | ||
3632 | have been changed and it is certain that a new game will be | ||
3633 | requested. The front end should therefore call | ||
3634 | \cw{midend_new_game()}, and probably also re-think the window size | ||
3635 | using \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 | |||
3642 | Returns a descriptive game ID (i.e. one in the form | ||
3643 | \cq{params:description}) describing the game currently active in the | ||
3644 | mid-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 | |||
3650 | Returns a random game ID (i.e. one in the form \cq{params#seedstring}) | ||
3651 | describing the game currently active in the mid-end, if there is one. | ||
3652 | If the game was created by entering a description, no random seed will | ||
3653 | currently exist and this function will return \cw{NULL}. | ||
3654 | |||
3655 | The returned string, if it is non-\cw{NULL}, is dynamically allocated. | ||
3656 | |||
3657 | Unlike the descriptive game ID, the random seed can contain characters | ||
3658 | outside 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 | |||
3664 | Returns \cw{true} if the game code is capable of formatting puzzles | ||
3665 | of the currently selected game type as ASCII. | ||
3666 | |||
3667 | If 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 | |||
3674 | Formats the current game's current state as ASCII text suitable for | ||
3675 | copying to the clipboard. The returned string is dynamically | ||
3676 | allocated. | ||
3677 | |||
3678 | If the game's \c{can_format_as_text_ever} flag is \cw{false}, or if | ||
3679 | its \cw{can_format_as_text_now()} function returns \cw{false}, then | ||
3680 | this function will return \cw{NULL}. | ||
3681 | |||
3682 | If the returned string contains multiple lines (which is likely), it | ||
3683 | will use the normal C line ending convention (\cw{\\n} only). On | ||
3684 | platforms which use a different line ending convention for data in | ||
3685 | the clipboard, it is the front end's responsibility to perform the | ||
3686 | conversion. | ||
3687 | |||
3688 | \H{midend-solve} \cw{midend_solve()} | ||
3689 | |||
3690 | \c const char *midend_solve(midend *me); | ||
3691 | |||
3692 | Requests the mid-end to perform a Solve operation. | ||
3693 | |||
3694 | On success, \cw{NULL} is returned. On failure, an error message (not | ||
3695 | dynamically allocated) is returned, suitable for showing to the | ||
3696 | user. | ||
3697 | |||
3698 | The front end can expect its drawing API and/or | ||
3699 | \cw{activate_timer()} to be called from within a call to this | ||
3700 | function. 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 | |||
3709 | This function requests the location of the back end's on-screen cursor | ||
3710 | or other region of interest. | ||
3711 | |||
3712 | What exactly this region contains is up to the backend, but in general | ||
3713 | the region will be an area that the player is controlling with the | ||
3714 | cursor keys \dash such as the player location in Cube and Inertia, or | ||
3715 | the cursor in any of the conventional grid-based games. With knowledge | ||
3716 | of this location, a front end can, for example, ensure that the region | ||
3717 | of interest remains visible even if the entire puzzle is too big to | ||
3718 | fit on the screen. | ||
3719 | |||
3720 | On success, this function returns \cw{true}, and the locations pointed | ||
3721 | to by \cw{x}, \cw{y}, \cw{w} and \cw{h} are updated to describe the | ||
3722 | cursor region, which has an upper-left corner located at \cw{(*x,*y)} | ||
3723 | and a size of \cw{*w} pixels wide by \cw{*h} pixels tall. The caller | ||
3724 | may pass \cw{NULL} for any number of these pointers, which will be | ||
3725 | ignored. | ||
3726 | |||
3727 | On failure, this function returns \cw{false}. Failure can occur if | ||
3728 | there is currently no active cursor region, or if the back end lacks | ||
3729 | cursor support. | ||
3730 | |||
3731 | \H{midend-status} \cw{midend_status()} | ||
3732 | |||
3733 | \c int midend_status(midend *me); | ||
3734 | |||
3735 | This function returns +1 if the midend is currently displaying a game | ||
3736 | in a solved state, -1 if the game is in a permanently lost state, or 0 | ||
3737 | otherwise. This function just calls the back end's \cw{status()} | ||
3738 | function. Front ends may wish to use this as a cue to proactively | ||
3739 | offer 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 | ||
3743 | status code.) | ||
3744 | |||
3745 | \H{midend-can-undo} \cw{midend_can_undo()} | ||
3746 | |||
3747 | \c bool midend_can_undo(midend *me); | ||
3748 | |||
3749 | Returns \cw{true} if the midend is currently in a state where the undo | ||
3750 | operation is meaningful (i.e. at least one position exists on the undo | ||
3751 | chain before the present one). Front ends may wish to use this to | ||
3752 | visually 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 | |||
3758 | Returns \cw{true} if the midend is currently in a state where the redo | ||
3759 | operation is meaningful (i.e. at least one position exists on the redo | ||
3760 | chain after the present one). Front ends may wish to use this to | ||
3761 | visually 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 | |||
3768 | Calling this function causes the mid-end to convert its entire | ||
3769 | internal state into a long ASCII text string, and to pass that | ||
3770 | string (piece by piece) to the supplied \c{write} function. | ||
3771 | The string will consist of printable ASCII characters and line | ||
3772 | feeds. | ||
3773 | |||
3774 | Desktop implementations can use this function to save a game in any | ||
3775 | state (including half-finished) to a disk file, by supplying a | ||
3776 | \c{write} function which is a wrapper on \cw{fwrite()} (or local | ||
3777 | equivalent). Other implementations may find other uses for it, such | ||
3778 | as compressing the large and sprawling mid-end state into a | ||
3779 | manageable amount of memory when a palmtop application is suspended | ||
3780 | so that another one can run; in this case \cw{write} might want to | ||
3781 | write to a memory buffer rather than a file. There may be other uses | ||
3782 | for it as well. | ||
3783 | |||
3784 | This function will call back to the supplied \c{write} function a | ||
3785 | number of times, with the first parameter (\c{ctx}) equal to | ||
3786 | \c{wctx}, and the other two parameters pointing at a piece of the | ||
3787 | output 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 | |||
3794 | This function is the counterpart to \cw{midend_serialise()}. It | ||
3795 | calls the supplied \cw{read} function repeatedly to read a quantity | ||
3796 | of data, and attempts to interpret that data as a serialised mid-end | ||
3797 | as output by \cw{midend_serialise()}. | ||
3798 | |||
3799 | The \cw{read} function is called with the first parameter (\c{ctx}) | ||
3800 | equal to \c{rctx}, and should attempt to read \c{len} bytes of data | ||
3801 | into the buffer pointed to by \c{buf}. It should return \cw{false} | ||
3802 | on failure or \cw{true} on success. It should not report success | ||
3803 | unless it has filled the entire buffer; on platforms which might be | ||
3804 | reading from a pipe or other blocking data source, \c{read} is | ||
3805 | responsible for looping until the whole buffer has been filled. | ||
3806 | |||
3807 | If the de-serialisation operation is successful, the mid-end's | ||
3808 | internal data structures will be replaced by the results of the | ||
3809 | load, and \cw{NULL} will be returned. Otherwise, the mid-end's state | ||
3810 | will be completely unchanged and an error message (typically some | ||
3811 | variation on \q{save file is corrupt}) will be returned. As usual, | ||
3812 | the error message string is not dynamically allocated. | ||
3813 | |||
3814 | If this function succeeds, it is likely that the game parameters | ||
3815 | will have been changed. The front end should therefore probably | ||
3816 | re-think the window size using \cw{midend_size()}, and probably | ||
3817 | cause a refresh using \cw{midend_redraw()}. | ||
3818 | |||
3819 | Because each mid-end is tied to a specific game back end, this | ||
3820 | function will fail if you attempt to read in a save file generated by | ||
3821 | a different game from the one configured in this mid-end, even if your | ||
3822 | application is a monolithic one containing all the puzzles. See | ||
3823 | \k{identify-game} for a helper function which will allow you to | ||
3824 | identify a save file before you instantiate your mid-end in the first | ||
3825 | place. | ||
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 | |||
3833 | Calling this function causes the mid-end to write out the states of | ||
3834 | all user-settable preference options, including its own cross-platform | ||
3835 | preferences 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 | ||
3838 | writing into a configuration file on disk. | ||
3839 | |||
3840 | The \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 | |||
3849 | This function is used to load a configuration file in the same format | ||
3850 | emitted by \cw{midend_save_prefs()}, and import all the preferences | ||
3851 | described 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 | |||
3858 | This function examines a serialised midend stream, of the same kind | ||
3859 | used by \cw{midend_serialise()} and \cw{midend_deserialise()}, and | ||
3860 | returns the \cw{name} field of the game back end from which it was | ||
3861 | saved. | ||
3862 | |||
3863 | You might want this if your front end was a monolithic one containing | ||
3864 | all the puzzles, and you wanted to be able to load an arbitrary save | ||
3865 | file and automatically switch to the right game. Probably your next | ||
3866 | step would be to iterate through \cw{gamelist} (\k{frontend-backend}) | ||
3867 | looking for a game structure whose \cw{name} field matched the | ||
3868 | returned string, and give an error if you didn't find one. | ||
3869 | |||
3870 | On success, the return value of this function is \cw{NULL}, and the | ||
3871 | game name string is written into \cw{*name}. The caller should free | ||
3872 | that string after using it. | ||
3873 | |||
3874 | On failure, \cw{*name} is \cw{NULL}, and the return value is an error | ||
3875 | message (which does not need freeing at all). | ||
3876 | |||
3877 | (This isn't strictly speaking a midend function, since it doesn't | ||
3878 | accept 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 | |||
3886 | This function is called by the front end to request notification by | ||
3887 | the mid-end when the current game IDs (either descriptive or | ||
3888 | random-seed) change. This can occur as a result of keypresses ('n' for | ||
3889 | New Game, for example) or when a puzzle supersedes its game | ||
3890 | description (see \k{backend-supersede}). After this function is | ||
3891 | called, any change of the game ids will cause the mid-end to call | ||
3892 | \cw{notify(ctx)} after the change. | ||
3893 | |||
3894 | This is for use by puzzles which want to present the game description | ||
3895 | to the user constantly (e.g. as an HTML hyperlink) instead of only | ||
3896 | showing it when the user explicitly requests it. | ||
3897 | |||
3898 | This is a function I anticipate few front ends needing to implement, | ||
3899 | so I make it a callback rather than a static function in order to | ||
3900 | relieve most front ends of the need to provide an empty | ||
3901 | implementation. | ||
3902 | |||
3903 | \H{midend-which-game} \cw{midend_which_game()} | ||
3904 | |||
3905 | \c const game *midend_which_preset(midend *me); | ||
3906 | |||
3907 | This function returns the \c{game} structure for the puzzle type this | ||
3908 | midend is committed to. | ||
3909 | |||
3910 | \H{frontend-backend} Direct reference to the back end structure by | ||
3911 | the front end | ||
3912 | |||
3913 | Although \e{most} things the front end needs done should be done by | ||
3914 | calling the mid-end, there are a few situations in which the front | ||
3915 | end needs to refer directly to the game back end structure. | ||
3916 | |||
3917 | The most obvious of these is | ||
3918 | |||
3919 | \b passing the game back end as a parameter to \cw{midend_new()}. | ||
3920 | |||
3921 | There are a few other back end features which are not wrapped by the | ||
3922 | mid-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 | ||
3928 | items 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 | ||
3933 | which directly calls the back end to do most of its work. This is | ||
3934 | not really part of the main front end code, though, and I'm not sure | ||
3935 | it counts. | ||
3936 | |||
3937 | In order to find the game back end structure, the front end does one | ||
3938 | of two things: | ||
3939 | |||
3940 | \b If the particular front end is compiling a separate binary per | ||
3941 | game, then the back end structure is a global variable with the | ||
3942 | standard 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 | ||
3951 | containing all the puzzles together (in which case the preprocessor | ||
3952 | symbol \cw{COMBINED} must be defined when compiling most of the code | ||
3953 | base), 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, | ||
3961 | declared in the automatically constructed source module \c{list.c}. | ||
3962 | The application should search that array for the game it wants, | ||
3963 | probably 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 | |||
3970 | This section describes the small number of functions which a front | ||
3971 | end must provide to be called by the mid-end or other standard | ||
3972 | utility modules. | ||
3973 | |||
3974 | \H{frontend-get-random-seed} \cw{get_random_seed()} | ||
3975 | |||
3976 | \c void get_random_seed(void **randseed, int *randseedsize); | ||
3977 | |||
3978 | This function is called by a new mid-end, and also occasionally by | ||
3979 | game back ends. Its job is to return a piece of data suitable for | ||
3980 | using as a seed for initialisation of a new \c{random_state}. | ||
3981 | |||
3982 | On exit, \c{*randseed} should be set to point at a newly allocated | ||
3983 | piece of memory containing some seed data, and \c{*randseedsize} | ||
3984 | should be set to the length of that data. | ||
3985 | |||
3986 | A simple and entirely adequate implementation is to return a piece | ||
3987 | of data containing the current system time at the highest | ||
3988 | conveniently available resolution. | ||
3989 | |||
3990 | \H{frontend-activate-timer} \cw{activate_timer()} | ||
3991 | |||
3992 | \c void activate_timer(frontend *fe); | ||
3993 | |||
3994 | This is called by the mid-end to request that the front end begin | ||
3995 | calling it back at regular intervals. | ||
3996 | |||
3997 | The timeout interval is left up to the front end; the finer it is, | ||
3998 | the smoother move animations will be, but the more CPU time will be | ||
3999 | used. Current front ends use values around 20ms (i.e. 50Hz). | ||
4000 | |||
4001 | After this function is called, the mid-end will expect to receive | ||
4002 | calls 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 | |||
4008 | This is called by the mid-end to request that the front end stop | ||
4009 | calling \cw{midend_timer()}. | ||
4010 | |||
4011 | \H{frontend-fatal} \cw{fatal()} | ||
4012 | |||
4013 | \c void fatal(const char *fmt, ...); | ||
4014 | |||
4015 | This is called by some utility functions if they encounter a | ||
4016 | genuinely fatal error such as running out of memory. It is a | ||
4017 | variadic function in the style of \cw{printf()}, and is expected to | ||
4018 | show the formatted error message to the user any way it can and then | ||
4019 | terminate 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 | |||
4025 | This function expects to be passed a pointer to an array of three | ||
4026 | \cw{float}s. It returns the platform's local preferred background | ||
4027 | colour in those three floats, as red, green and blue values (in that | ||
4028 | order) ranging from \cw{0.0} to \cw{1.0}. | ||
4029 | |||
4030 | This 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 | ||
4033 | anywhere else particularly good to put it. Sorry.) | ||
4034 | |||
4035 | \C{utils} Utility APIs | ||
4036 | |||
4037 | This chapter documents a variety of utility APIs provided for the | ||
4038 | general use of the rest of the Puzzles code. | ||
4039 | |||
4040 | \H{utils-random} Random number generation | ||
4041 | |||
4042 | Platforms' local random number generators vary widely in quality and | ||
4043 | seed size. Puzzles therefore supplies its own high-quality random | ||
4044 | number generator, with the additional advantage of giving the same | ||
4045 | results if fed the same seed data on different platforms. This | ||
4046 | allows game random seeds to be exchanged between different ports of | ||
4047 | Puzzles and still generate the same games. | ||
4048 | |||
4049 | Unlike the ANSI C \cw{rand()} function, the Puzzles random number | ||
4050 | generator has an \e{explicit} state object called a | ||
4051 | \c{random_state}. One of these is managed by each mid-end, for | ||
4052 | example, 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 | |||
4058 | Allocates, initialises and returns a new \c{random_state}. The input | ||
4059 | data is used as the seed for the random number stream (i.e. using | ||
4060 | the same seed at a later time will generate the same stream). | ||
4061 | |||
4062 | The seed data can be any data at all; there is no requirement to use | ||
4063 | printable 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 | |||
4069 | Allocates a new \c{random_state}, copies the contents of another | ||
4070 | \c{random_state} into it, and returns the new state. If exactly the | ||
4071 | same sequence of functions is subsequently called on both the copy and | ||
4072 | the original, the results will be identical. This may be useful for | ||
4073 | speculatively performing some operation using a given random state, | ||
4074 | and later replaying that operation precisely. | ||
4075 | |||
4076 | \S{utils-random-free} \cw{random_free()} | ||
4077 | |||
4078 | \c void random_free(random_state *state); | ||
4079 | |||
4080 | Frees 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 | |||
4086 | Returns a random number from 0 to \cw{2^bits-1} inclusive. \c{bits} | ||
4087 | should 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 | |||
4093 | Returns a random number from 0 to \cw{limit-1} inclusive. \c{limit} | ||
4094 | may 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 | |||
4100 | Encodes the entire contents of a \c{random_state} in printable | ||
4101 | ASCII. Returns a dynamically allocated string containing that | ||
4102 | encoding. 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 | |||
4109 | Decodes a string generated by \cw{random_state_encode()} and | ||
4110 | reconstructs an equivalent \c{random_state} to the one encoded, i.e. | ||
4111 | it should produce the same stream of random numbers. | ||
4112 | |||
4113 | This function has no error reporting; if you pass it an invalid | ||
4114 | string it will simply generate an arbitrary random state, which may | ||
4115 | turn 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 | |||
4121 | Shuffles an array into a random order. The interface is much like | ||
4122 | ANSI C \cw{qsort()}, except that there's no need for a compare | ||
4123 | function. | ||
4124 | |||
4125 | \c{array} is a pointer to the first element of the array. \c{nelts} | ||
4126 | is the number of elements in the array; \c{eltsize} is the size of a | ||
4127 | single element (typically measured using \c{sizeof}). \c{rs} is a | ||
4128 | \c{random_state} used to generate all the random numbers for the | ||
4129 | shuffling process. | ||
4130 | |||
4131 | \H{utils-presets} Presets menu management | ||
4132 | |||
4133 | The function \c{midend_get_presets()} (\k{midend-get-presets}) returns | ||
4134 | a data structure describing a menu hierarchy. Back ends can also | ||
4135 | choose to provide such a structure to the mid-end, if they want to | ||
4136 | group their presets hierarchically. To make this easy, there are a few | ||
4137 | utility functions to construct preset menu structures, and also one | ||
4138 | intended 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 | |||
4144 | Allocates a new \c{struct preset_menu}, and initialises it to hold no | ||
4145 | menu 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 | |||
4152 | Adds a new submenu to the end of an existing preset menu, and returns | ||
4153 | a pointer to a newly allocated \c{struct preset_menu} describing the | ||
4154 | submenu. | ||
4155 | |||
4156 | The string parameter \cq{title} must be dynamically allocated by the | ||
4157 | caller. The preset-menu structure will take ownership of it, so the | ||
4158 | caller 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 | |||
4165 | Adds a preset game configuration to the end of a preset menu. | ||
4166 | |||
4167 | Both the string parameter \cq{title} and the game parameter structure | ||
4168 | \cq{params} itself must be dynamically allocated by the caller. The | ||
4169 | preset-menu structure will take ownership of it, so the caller must | ||
4170 | not 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 | |||
4177 | Given a numeric index, searches recursively through a preset menu | ||
4178 | hierarchy to find the corresponding menu entry, and returns a pointer | ||
4179 | to its existing \c{game_params} structure. | ||
4180 | |||
4181 | This function is intended for front end use (but front ends need not | ||
4182 | use it if they prefer to do things another way). If a front end finds | ||
4183 | it inconvenient to store anything more than a numeric index alongside | ||
4184 | each menu item, then this function provides an easy way for the front | ||
4185 | end to get back the actual game parameters corresponding to a menu | ||
4186 | item that the user has selected. | ||
4187 | |||
4188 | \H{utils-alloc} Memory allocation | ||
4189 | |||
4190 | Puzzles has some central wrappers on the standard memory allocation | ||
4191 | functions, which provide compile-time type checking, and run-time | ||
4192 | error checking by means of quitting the application if it runs out | ||
4193 | of memory. This doesn't provide the best possible recovery from | ||
4194 | memory shortage, but on the other hand it greatly simplifies the | ||
4195 | rest 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 | |||
4203 | This macro takes a single argument which is a \e{type name}. It | ||
4204 | allocates space for one object of that type. If allocation fails it | ||
4205 | will call \cw{fatal()} and not return; so if it does return, you can | ||
4206 | be confident that its return value is non-\cw{NULL}. | ||
4207 | |||
4208 | The return value is cast to the specified type, so that the compiler | ||
4209 | will type-check it against the variable you assign it into. Thus, | ||
4210 | this ensures you don't accidentally allocate memory the size of the | ||
4211 | wrong type and assign it into a variable of the right one (or vice | ||
4212 | versa!). | ||
4213 | |||
4214 | \S{utils-snewn} \cw{snewn()} | ||
4215 | |||
4216 | \c var = snewn(n, type); | ||
4217 | \e iii i iiii | ||
4218 | |||
4219 | This macro is the array form of \cw{snew()}. It takes two arguments; | ||
4220 | the first is a number, and the second is a type name. It allocates | ||
4221 | space for that many objects of that type, and returns a type-checked | ||
4222 | non-\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 | |||
4229 | This macro is a type-checked form of \cw{realloc()}. It takes three | ||
4230 | arguments: an input memory block, a new size in elements, and a | ||
4231 | type. It re-sizes the input memory block to a size sufficient to | ||
4232 | contain that many elements of that type. It returns a type-checked | ||
4233 | non-\cw{NULL} pointer, like \cw{snew()} and \cw{snewn()}. | ||
4234 | |||
4235 | The input memory block can be \cw{NULL}, in which case this function | ||
4236 | will behave exactly like \cw{snewn()}. (In principle any | ||
4237 | ANSI-compliant \cw{realloc()} implementation ought to cope with | ||
4238 | this, 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 | |||
4244 | This function is pretty much equivalent to \cw{free()}. It is | ||
4245 | provided with a dynamically allocated block, and frees it. | ||
4246 | |||
4247 | The input memory block can be \cw{NULL}, in which case this function | ||
4248 | will do nothing. (In principle any ANSI-compliant \cw{free()} | ||
4249 | implementation ought to cope with this, but I've never quite trusted | ||
4250 | it to work everywhere.) | ||
4251 | |||
4252 | \S{utils-dupstr} \cw{dupstr()} | ||
4253 | |||
4254 | \c char *dupstr(const char *s); | ||
4255 | |||
4256 | This function dynamically allocates a duplicate of a C string. Like | ||
4257 | the \cw{snew()} functions, it guarantees to return non-\cw{NULL} or | ||
4258 | not return at all. | ||
4259 | |||
4260 | (Many platforms provide the function \cw{strdup()}. As well as | ||
4261 | guaranteeing never to return \cw{NULL}, my version has the advantage | ||
4262 | of being defined \e{everywhere}, rather than inconveniently not | ||
4263 | quite everywhere.) | ||
4264 | |||
4265 | \S{utils-free-cfg} \cw{free_cfg()} | ||
4266 | |||
4267 | \c void free_cfg(config_item *cfg); | ||
4268 | |||
4269 | This function correctly frees an array of \c{config_item}s, including | ||
4270 | walking the array until it gets to the end and freeing any subsidiary | ||
4271 | data items in each \c{u} sub-union which are expected to be | ||
4272 | dynamically allocated. | ||
4273 | |||
4274 | (See \k{backend-configure} for details of the \c{config_item} | ||
4275 | structure.) | ||
4276 | |||
4277 | \S{utils-free-keys} \cw{free_keys()} | ||
4278 | |||
4279 | \c void free_keys(key_label *keys, int nkeys); | ||
4280 | |||
4281 | This function correctly frees an array of \c{key_label}s, including | ||
4282 | the dynamically allocated label string for each key. | ||
4283 | |||
4284 | (See \k{backend-request-keys} for details of the \c{key_label} | ||
4285 | structure.) | ||
4286 | |||
4287 | \H{utils-tree234} Sorted and counted tree functions | ||
4288 | |||
4289 | Many games require complex algorithms for generating random puzzles, | ||
4290 | and some require moderately complex algorithms even during play. A | ||
4291 | common requirement during these algorithms is for a means of | ||
4292 | maintaining sorted or unsorted lists of items, such that items can | ||
4293 | be removed and added conveniently. | ||
4294 | |||
4295 | For general use, Puzzles provides the following set of functions | ||
4296 | which maintain 2-3-4 trees in memory. (A 2-3-4 tree is a balanced | ||
4297 | tree structure, with the property that all lookups, insertions, | ||
4298 | deletions, splits and joins can be done in \cw{O(log N)} time.) | ||
4299 | |||
4300 | All these functions expect you to be storing a tree of \c{void *} | ||
4301 | pointers. You can put anything you like in those pointers. | ||
4302 | |||
4303 | By the use of per-node element counts, these tree structures have | ||
4304 | the slightly unusual ability to look elements up by their numeric | ||
4305 | index within the list represented by the tree. This means that they | ||
4306 | can be used to store an unsorted list (in which case, every time you | ||
4307 | insert a new element, you must explicitly specify the position where | ||
4308 | you wish to insert it). They can also do numeric lookups in a sorted | ||
4309 | tree, which might be useful for (for example) tracking the median of | ||
4310 | a changing data set. | ||
4311 | |||
4312 | As well as storing sorted lists, these functions can be used for | ||
4313 | storing \q{maps} (associative arrays), by defining each element of a | ||
4314 | tree to be a (key, value) pair. | ||
4315 | |||
4316 | \S{utils-newtree234} \cw{newtree234()} | ||
4317 | |||
4318 | \c tree234 *newtree234(cmpfn234 cmp); | ||
4319 | |||
4320 | Creates a new empty tree, and returns a pointer to it. | ||
4321 | |||
4322 | The parameter \c{cmp} determines the sorting criterion on the tree. | ||
4323 | Its prototype is | ||
4324 | |||
4325 | \c typedef int (*cmpfn234)(void *, void *); | ||
4326 | |||
4327 | If you want a sorted tree, you should provide a function matching | ||
4328 | this prototype, which returns like \cw{strcmp()} does (negative if | ||
4329 | the first argument is smaller than the second, positive if it is | ||
4330 | bigger, zero if they compare equal). In this case, the function | ||
4331 | \cw{addpos234()} will not be usable on your tree (because all | ||
4332 | insertions must respect the sorting order). | ||
4333 | |||
4334 | If you want an unsorted tree, pass \cw{NULL}. In this case you will | ||
4335 | not be able to use either \cw{add234()} or \cw{del234()}, or any | ||
4336 | other function such as \cw{find234()} which depends on a sorting | ||
4337 | order. Your tree will become something more like an array, except | ||
4338 | that it will efficiently support insertion and deletion as well as | ||
4339 | lookups by numeric index. | ||
4340 | |||
4341 | \S{utils-freetree234} \cw{freetree234()} | ||
4342 | |||
4343 | \c void freetree234(tree234 *t); | ||
4344 | |||
4345 | Frees a tree. This function will not free the \e{elements} of the | ||
4346 | tree (because they might not be dynamically allocated, or you might | ||
4347 | be storing the same set of elements in more than one tree); it will | ||
4348 | just free the tree structure itself. If you want to free all the | ||
4349 | elements 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 | |||
4360 | Inserts a new element \c{e} into the tree \c{t}. This function | ||
4361 | expects the tree to be sorted; the new element is inserted according | ||
4362 | to the sort order. | ||
4363 | |||
4364 | If an element comparing equal to \c{e} is already in the tree, then | ||
4365 | the insertion will fail, and the return value will be the existing | ||
4366 | element. 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 | |||
4372 | Inserts a new element into an unsorted tree. Since there is no | ||
4373 | sorting order to dictate where the new element goes, you must | ||
4374 | specify where you want it to go. Setting \c{index} to zero puts the | ||
4375 | new element right at the start of the list; setting \c{index} to the | ||
4376 | current number of elements in the tree puts the new element at the | ||
4377 | end. | ||
4378 | |||
4379 | Return value is \c{e}, in line with \cw{add234()} (although this | ||
4380 | function cannot fail except by running out of memory, in which case | ||
4381 | it 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 | |||
4387 | Returns 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 | ||
4389 | numbered from zero. | ||
4390 | |||
4391 | \S{utils-find234} \cw{find234()} | ||
4392 | |||
4393 | \c void *find234(tree234 *t, void *e, cmpfn234 cmp); | ||
4394 | |||
4395 | Searches for an element comparing equal to \c{e} in a sorted tree. | ||
4396 | |||
4397 | If \c{cmp} is \cw{NULL}, the tree's ordinary comparison function | ||
4398 | will be used to perform the search. However, sometimes you don't | ||
4399 | want that; suppose, for example, each of your elements is a big | ||
4400 | structure containing a \c{char *} name field, and you want to find | ||
4401 | the element with a given name. You \e{could} achieve this by | ||
4402 | constructing a fake element structure, setting its name field | ||
4403 | appropriately, and passing it to \cw{find234()}, but you might find | ||
4404 | it more convenient to pass \e{just} a name string to \cw{find234()}, | ||
4405 | supplying an alternative comparison function which expects one of | ||
4406 | its arguments to be a bare name and the other to be a large | ||
4407 | structure containing a name field. | ||
4408 | |||
4409 | Therefore, if \c{cmp} is not \cw{NULL}, then it will be used to | ||
4410 | compare \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 | ||
4412 | tree. | ||
4413 | |||
4414 | (See \k{utils-newtree234} for the definition of the \c{cmpfn234} | ||
4415 | function pointer type.) | ||
4416 | |||
4417 | The returned value is the element found, or \cw{NULL} if the search | ||
4418 | is unsuccessful. | ||
4419 | |||
4420 | \S{utils-findrel234} \cw{findrel234()} | ||
4421 | |||
4422 | \c void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation); | ||
4423 | |||
4424 | This function is like \cw{find234()}, but has the additional ability | ||
4425 | to do a \e{relative} search. The additional parameter \c{relation} | ||
4426 | can 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 | ||
4431 | exactly 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 | ||
4437 | element 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 | ||
4444 | possible, 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 | ||
4450 | element 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 | ||
4457 | possible, but failing that settle for something just bigger than | ||
4458 | it.) | ||
4459 | |||
4460 | Return value, as before, is the element found or \cw{NULL} if no | ||
4461 | element 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 | |||
4467 | This function is like \cw{find234()}, but has the additional feature | ||
4468 | of returning the index of the element found in the tree; that index | ||
4469 | is written to \c{*index} in the event of a successful search (a | ||
4470 | non-\cw{NULL} return value). | ||
4471 | |||
4472 | \c{index} may be \cw{NULL}, in which case this function behaves | ||
4473 | exactly 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 | |||
4480 | This 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 | |||
4487 | Finds an element comparing equal to \c{e} in the tree, deletes it, | ||
4488 | and returns it. | ||
4489 | |||
4490 | The input tree must be sorted. | ||
4491 | |||
4492 | The element found might be \c{e} itself, or might merely compare | ||
4493 | equal to it. | ||
4494 | |||
4495 | Return 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 | |||
4501 | Deletes the element at position \c{index} in the tree, and returns | ||
4502 | it. | ||
4503 | |||
4504 | Return 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 | |||
4510 | Returns 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 | |||
4516 | Splits the input tree into two pieces at a given position, and | ||
4517 | creates a new tree containing all the elements on one side of that | ||
4518 | position. | ||
4519 | |||
4520 | If \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 | ||
4522 | point are returned in the new tree. Otherwise, the reverse happens: | ||
4523 | all the items at or after \c{index} are moved into the new tree, and | ||
4524 | those before that point are left in the old one. | ||
4525 | |||
4526 | If \c{index} is equal to 0 or to the number of elements in the input | ||
4527 | tree, then one of the two trees will end up empty (and this is not | ||
4528 | an error condition). If \c{index} is further out of range in either | ||
4529 | direction, the operation will fail completely and return \cw{NULL}. | ||
4530 | |||
4531 | This operation completes in \cw{O(log N)} time, no matter how large | ||
4532 | the 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 | |||
4538 | Splits 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 | ||
4542 | elements having that relation to \c{e} will be transferred into the | ||
4543 | new tree; the rest will be left in the old one. | ||
4544 | |||
4545 | The 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 | ||
4547 | the tree's own comparison function when comparing elements to \c{e}, | ||
4548 | in such a way that \c{e} itself is always the first of its two | ||
4549 | operands. | ||
4550 | |||
4551 | Again, this operation completes in \cw{O(log N)} time, no matter how | ||
4552 | large 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 | |||
4558 | Joins two trees together by concatenating the lists they represent. | ||
4559 | All the elements of \c{t2} are moved into \c{t1}, in such a way that | ||
4560 | they appear \e{after} the elements of \c{t1}. The tree \c{t2} is | ||
4561 | freed; the return value is \c{t1}. | ||
4562 | |||
4563 | If you apply this function to a sorted tree and it violates the sort | ||
4564 | order (i.e. the smallest element in \c{t2} is smaller than or equal | ||
4565 | to the largest element in \c{t1}), the operation will fail and | ||
4566 | return \cw{NULL}. | ||
4567 | |||
4568 | This operation completes in \cw{O(log N)} time, no matter how large | ||
4569 | the trees being joined together. | ||
4570 | |||
4571 | \S{utils-join234r} \cw{join234r()} | ||
4572 | |||
4573 | \c tree234 *join234r(tree234 *t1, tree234 *t2); | ||
4574 | |||
4575 | Joins two trees together in exactly the same way as \cw{join234()}, | ||
4576 | but this time the combined tree is returned in \c{t2}, and \c{t1} is | ||
4577 | destroyed. The elements in \c{t1} still appear before those in | ||
4578 | \c{t2}. | ||
4579 | |||
4580 | Again, this operation completes in \cw{O(log N)} time, no matter how | ||
4581 | large 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 | |||
4588 | Makes a copy of an entire tree. | ||
4589 | |||
4590 | If \c{copyfn} is \cw{NULL}, the tree will be copied but the elements | ||
4591 | will not be; i.e. the new tree will contain pointers to exactly the | ||
4592 | same physical elements as the old one. | ||
4593 | |||
4594 | If you want to copy each actual element during the operation, you | ||
4595 | can instead pass a function in \c{copyfn} which makes a copy of each | ||
4596 | element. That function has the prototype | ||
4597 | |||
4598 | \c typedef void *(*copyfn234)(void *state, void *element); | ||
4599 | |||
4600 | and every time it is called, the \c{state} parameter will be set to | ||
4601 | the value you passed in as \c{copyfnstate}. | ||
4602 | |||
4603 | \H{utils-dsf} Disjoint set forests | ||
4604 | |||
4605 | This section describes a set of functions implementing the data | ||
4606 | structure variously known as \q{union-find} or \q{Tarjan's disjoint | ||
4607 | set forest}. In this code base, it's universally abbreviated as a | ||
4608 | \q{dsf}. | ||
4609 | |||
4610 | A dsf represents a collection of elements partitioned into | ||
4611 | \q{equivalence classes}, in circumstances where equivalences are added | ||
4612 | incrementally. That is, all elements start off considered to be | ||
4613 | different, and you gradually declare more and more of them to be equal | ||
4614 | via the \cw{dsf_merge()} operation, which says that two particular | ||
4615 | elements should be regarded as equal from now on. | ||
4616 | |||
4617 | For example, if I start off with A,B,U,V all distinct, and I merge A | ||
4618 | with B and merge U with V, then the structure will tell me that A and | ||
4619 | U are not equivalent. But if I then merge B with V, then after that, | ||
4620 | the structure will tell me that A and U \e{are} equivalent, by | ||
4621 | following the transitive chain of equivalences it knows about. | ||
4622 | |||
4623 | The dsf data structure is therefore ideal for tracking incremental | ||
4624 | connectivity in an undirected graph (again, \q{incremental} meaning | ||
4625 | that you only ever add edges, never delete them), and other | ||
4626 | applications in which you gradually acquire knowledge you didn't | ||
4627 | previously have about what things are the same as each other. It's | ||
4628 | used extensively in puzzle solver and generator algorithms, and | ||
4629 | sometimes during gameplay as well. | ||
4630 | |||
4631 | The time complexity of dsf operations is not \e{quite} constant time, | ||
4632 | in theory, but it's so close to it as to make no difference in | ||
4633 | practice. In particular, any time a dsf has to do non-trivial work, it | ||
4634 | updates the structure so that that work won't be needed a second time. | ||
4635 | Use dsf operations without worrying about how long they take! | ||
4636 | |||
4637 | For some puzzle-game applications, it's useful to augment this data | ||
4638 | structure with extra information about how the elements of an | ||
4639 | equivalence class relate to each other. There's more than one way you | ||
4640 | might do this; the one supported here is useful in cases where the | ||
4641 | objects you're tracking are going to end up in one of two states (say, | ||
4642 | black/white, or on/off), and for any two objects you either know that | ||
4643 | they're in the same one of those states, or you know they're in | ||
4644 | opposite 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 | ||
4646 | are flipped relative to each other. | ||
4647 | |||
4648 | As well as querying whether two elements are equivalent, this dsf | ||
4649 | implementation also allows you to ask for the number of elements in a | ||
4650 | given equivalence class, and the smallest element in the class. (The | ||
4651 | latter is used, for example, to decide which square to print the clue | ||
4652 | in 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 | |||
4660 | Each of these functions allocates space for a dsf describing \c{size} | ||
4661 | elements, and initialises it so that every element is in an | ||
4662 | equivalence class by itself. | ||
4663 | |||
4664 | The 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 | ||
4668 | track whether objects in the same equivalence class are flipped | ||
4669 | relative to each other. | ||
4670 | |||
4671 | \cw{dsf_new_min()} will create a dsf which has the extra ability to | ||
4672 | track the smallest element of each equivalence class. | ||
4673 | |||
4674 | The 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 | |||
4681 | Frees 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 | |||
4687 | Reinitialises an existing dsf to the state in which all elements are | ||
4688 | distinct, 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 | |||
4694 | Copies the contents of one dsf over the top of another. Everything | ||
4695 | previously stored in \c{to} is overwritten. | ||
4696 | |||
4697 | The two dsfs must have been created with the same size, and the | ||
4698 | destination dsf may not have any extra information that the source dsf | ||
4699 | does not have. | ||
4700 | |||
4701 | \S{utils-dsf-merge} \cw{dsf_merge()} | ||
4702 | |||
4703 | \c void dsf_merge(DSF *dsf, int v1, int v2); | ||
4704 | |||
4705 | Updates a dsf so that elements \c{v1} and \c{v2} will now be | ||
4706 | considered to be in the same equivalence class. If they were already | ||
4707 | in the same class, this function will safely do nothing. | ||
4708 | |||
4709 | This function may not be called on a flip dsf. Use \cw{dsf_merge_flip} | ||
4710 | instead. | ||
4711 | |||
4712 | \S{utils-dsf-canonify} \cw{dsf_canonify()} | ||
4713 | |||
4714 | \c int dsf_canonify(DSF *dsf, int val); | ||
4715 | |||
4716 | Returns the \q{canonical} element of the equivalence class in the dsf | ||
4717 | containing \c{val}. This will be some element of the same equivalence | ||
4718 | class. So in order to determine whether two elements are in the same | ||
4719 | equivalence class, you can call \cw{dsf_canonify} on both of them, and | ||
4720 | compare the results. | ||
4721 | |||
4722 | Canonical elements don't necessarily stay the same if the dsf is | ||
4723 | mutated via \c{dsf_merge}. But between two calls to \c{dsf_merge}, | ||
4724 | they stay the same. | ||
4725 | |||
4726 | \S{utils-dsf-size} \cw{dsf_size()} | ||
4727 | |||
4728 | \c int dsf_size(DSF *dsf, int val); | ||
4729 | |||
4730 | Returns the number of elements currently in the equivalence class | ||
4731 | containing \c{val}. | ||
4732 | |||
4733 | \c{val} itself counts, so in a newly created dsf, the return value | ||
4734 | will 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 | |||
4740 | Updates a flip dsf so that elements \c{v1} and \c{v2} are in the same | ||
4741 | equivalence class. If \c{flip} is \cw{false}, they will be regarded as | ||
4742 | in the same state as each other; if \c{flip} is \cw{true} then they | ||
4743 | will be regarded as being in opposite states. | ||
4744 | |||
4745 | If \c{v1} and \c{v2} were already in the same equivalence class, then | ||
4746 | the new value of \c{flip} will be checked against what the edsf | ||
4747 | previously believed, and an assertion failure will occur if you | ||
4748 | contradict that. | ||
4749 | |||
4750 | For 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 | |||
4755 | then it will create a dsf in which elements 0,1,2 are all in the same | ||
4756 | class, with 0,1 in the same state as each other and 2 in the opposite | ||
4757 | state from both. And then this call will do nothing, because it agrees | ||
4758 | with what the dsf already knew: | ||
4759 | |||
4760 | \c dsf_merge_flip(dsf, 0, 2, true); | ||
4761 | |||
4762 | But 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 | |||
4770 | Like \c{dsf_canonify()}, this returns the canonical element of the | ||
4771 | equivalence class of a dsf containing \c{val}. | ||
4772 | |||
4773 | However, 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 | ||
4775 | element are in opposite states: \cw{true} if they are in opposite | ||
4776 | states, or \cw{false} if they're in the same state. | ||
4777 | |||
4778 | So if you want to know the relationship between \c{v1} and \c{v2}, you | ||
4779 | can 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 | |||
4796 | Returns the smallest element of the equivalence class in the dsf | ||
4797 | containing \c{val}. | ||
4798 | |||
4799 | For 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 | |||
4804 | This section describes a set of functions implementing a \q{to-do | ||
4805 | queue}, a simple de-duplicating to-do list mechanism. The code calls | ||
4806 | this a \q{tdq}. | ||
4807 | |||
4808 | A tdq can store integers up to a given size (specified at creation | ||
4809 | time). But it can't store the same integer more than once. So you can | ||
4810 | quickly \e{make sure} an integer is in the queue (which will do | ||
4811 | nothing if it's already there), and you can quickly pop an integer | ||
4812 | from the queue and return it, both in constant time. | ||
4813 | |||
4814 | The idea is that you might use this in a game solver, in the kind of | ||
4815 | game where updating your knowledge about one square of a grid means | ||
4816 | there's a specific other set of squares (such as its neighbours) where | ||
4817 | it's now worth attempting further deductions. So you keep a tdq of all | ||
4818 | the grid squares you plan to look at next, and every time you make a | ||
4819 | deduction in one square, you add the neighbouring squares to the tdq | ||
4820 | to make sure they get looked at again after that. | ||
4821 | |||
4822 | In solvers where deductions are mostly localised, this avoids the | ||
4823 | slowdown of having to find the next thing to do every time by looping | ||
4824 | over the whole grid: instead, you can keep checking the tdq for | ||
4825 | \e{specific} squares to look at, until you run out. | ||
4826 | |||
4827 | However, it's common to have games in which \e{most} deductions are | ||
4828 | localised, but not all. In that situation, when your tdq is empty, you | ||
4829 | can re-fill it with every square in the grid using \cw{tdq_fill()}, | ||
4830 | which will force an iteration over everything again. And then if the | ||
4831 | tdq becomes empty \e{again} without you having made any progress, give | ||
4832 | up. | ||
4833 | |||
4834 | \S{utils-tdq-new} \cw{tdq_new()} | ||
4835 | |||
4836 | \c tdq *tdq_new(int n); | ||
4837 | |||
4838 | Allocates space for a tdq that tracks items from \cw{0} to \cw{size-1} | ||
4839 | inclusive. | ||
4840 | |||
4841 | \S{utils-tdq-free} \cw{tdq_free()} | ||
4842 | |||
4843 | \c void tdq_free(tdq *tdq); | ||
4844 | |||
4845 | Frees a tdq. | ||
4846 | |||
4847 | \S{utils-tdq-add} \cw{tdq_add()} | ||
4848 | |||
4849 | \c void tdq_add(tdq *tdq, int k); | ||
4850 | |||
4851 | Adds the value \c{k} to a tdq. If \c{k} was already in the to-do list, | ||
4852 | does nothing. | ||
4853 | |||
4854 | \S{utils-tdq-remove} \cw{tdq_remove()} | ||
4855 | |||
4856 | \c int tdq_remove(tdq *tdq); | ||
4857 | |||
4858 | Removes one item from the tdq, and returns it. If the tdq is empty, | ||
4859 | returns \cw{-1}. | ||
4860 | |||
4861 | \S{utils-tdq-fill} \cw{tdq_fill()} | ||
4862 | |||
4863 | \c void tdq_fill(tdq *tdq); | ||
4864 | |||
4865 | Fills a tdq with every element it can possibly keep track of. | ||
4866 | |||
4867 | \H{utils-findloop} Finding loops in graphs and grids | ||
4868 | |||
4869 | Many puzzles played on grids or graphs have a common gameplay element | ||
4870 | of connecting things together into paths in such a way that you need | ||
4871 | to avoid making loops (or, perhaps, making the \e{wrong} kind of | ||
4872 | loop). | ||
4873 | |||
4874 | Just determining \e{whether} a loop exists in a graph is easy, using a | ||
4875 | dsf tracking connectivity between the vertices. Simply iterate over | ||
4876 | each edge of the graph, merging the two vertices at each end of the | ||
4877 | edge \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 | ||
4879 | the new edge is about to complete a loop. | ||
4880 | |||
4881 | But if you also want to identify \e{exactly} the set of edges that are | ||
4882 | part of any loop, e.g. to highlight the whole loop red during | ||
4883 | gameplay, then that's a harder problem. This API is provided here for | ||
4884 | all 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 | |||
4890 | Allocates a new state structure for the findloop algorithm, capable of | ||
4891 | handling a graph with up to \c{nvertices} vertices. The vertices will | ||
4892 | be 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 | |||
4898 | Frees 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 | |||
4905 | Runs the loop-finding algorithm, which will explore the graph and | ||
4906 | identify whether each edge is or is not part of any loop. | ||
4907 | |||
4908 | The algorithm will call the provided function \c{neighbour} to list | ||
4909 | the neighbouring vertices of each vertex. It should have this | ||
4910 | prototype: | ||
4911 | |||
4912 | \c int neighbour(int vertex, void *ctx); | ||
4913 | |||
4914 | In this callback, \c{vertex} will be the index of a vertex when the | ||
4915 | algorithm \e{first} calls it for a given vertex. The function should | ||
4916 | return the index of one of that vertex's neighbours, or a negative | ||
4917 | number if there are none. | ||
4918 | |||
4919 | If the function returned a vertex, the algorithm will then call | ||
4920 | \c{neighbour} again with a \e{negative} number as the \c{vertex} | ||
4921 | parameter, which means \q{please give me another neighbour of the same | ||
4922 | vertex as last time}. Again, the function should return a vertex | ||
4923 | index, or a negative number to indicate that there are no more | ||
4924 | vertices. | ||
4925 | |||
4926 | The \c{ctx} parameter passed to \cw{findloop_run()} is passed on | ||
4927 | unchanged to \c{neighbour}, so you can point that at your game state | ||
4928 | or solver state or whatever. | ||
4929 | |||
4930 | The return value is \cw{true} if at least one loop exists in the | ||
4931 | graph, and \cw{false} if no loop exists. Also, the algorithm state | ||
4932 | will have been filled in with information that the following query | ||
4933 | functions 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 | |||
4940 | Queries whether the graph edge between vertices \c{u} and \c{v} is | ||
4941 | part 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 | |||
4949 | Queries 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) | ||
4951 | disconnected components if it were removed. | ||
4952 | |||
4953 | This is the exact inverse of the \q{loop edge} criterion: a vertex | ||
4954 | returns \cw{true} from \cw{findloop_is_loop_edge()} if and only if it | ||
4955 | returns \cw{false} from \cw{findloop_is_bridge()}, and vice versa. | ||
4956 | |||
4957 | However, \cw{findloop_is_bridge()} returns more information. If it | ||
4958 | returns \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 | |||
4962 | For example, if you have three vertices A,B,C all connected to each | ||
4963 | other, and four vertices U,V,W,X all connected to each other, and a | ||
4964 | single edge between A and V, then calling \cw{findloop_is_bridge()} on | ||
4965 | the pair A,V will return true (removing that edge would separate the | ||
4966 | two sets from each other), and will report that there are three | ||
4967 | vertices on the A side and four on the V side. | ||
4968 | |||
4969 | \H{utils-combi} Choosing r things out of n | ||
4970 | |||
4971 | This section describes a small API for iterating over all combinations | ||
4972 | of r things out of n. | ||
4973 | |||
4974 | For example, if you asked for all combinations of 3 things out of 5, | ||
4975 | you'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 | |||
4978 | These functions use a structure called a \c{combi_ctx}, which contains | ||
4979 | an element \c{int *a} holding each returned combination, plus other | ||
4980 | fields 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 | |||
4986 | Allocates a new \c{combi_ctx} structure for enumerating r things out | ||
4987 | of n. | ||
4988 | |||
4989 | \S{utils-combi-free} \cw{free_combi()} | ||
4990 | |||
4991 | \c void free_combi(combi_ctx *combi); | ||
4992 | |||
4993 | Frees a \c{combi_ctx} structure. | ||
4994 | |||
4995 | \S{utils-combi-reset} \cw{reset_combi()} | ||
4996 | |||
4997 | \c void reset_combi(combi_ctx *combi); | ||
4998 | |||
4999 | Resets an existing \c{combi_ctx} structure to the start of its | ||
5000 | iteration | ||
5001 | |||
5002 | \S{utils-combi-next} \cw{next_combi()} | ||
5003 | |||
5004 | \c combi_ctx *next_combi(combi_ctx *combi); | ||
5005 | |||
5006 | Requests a combination from a \c{combi_ctx}. | ||
5007 | |||
5008 | If there are none left to return, the return value is \cw{NULL}. | ||
5009 | Otherwise, it returns the input structure \c{combi}, indicating that | ||
5010 | it has filled in \cw{combi->a[0]}, \cw{combi->a[1]}, ..., | ||
5011 | \cw{combi->a[r-1]} with an increasing sequence of distinct integers | ||
5012 | from \cw{0} to \cw{n-1} inclusive. | ||
5013 | |||
5014 | \H{utils-misc} Miscellaneous utility functions and macros | ||
5015 | |||
5016 | This section contains all the utility functions which didn't | ||
5017 | sensibly fit anywhere else. | ||
5018 | |||
5019 | \S{utils-maxmin} \cw{max()} and \cw{min()} | ||
5020 | |||
5021 | The main Puzzles header file defines the pretty standard macros | ||
5022 | \cw{max()} and \cw{min()}, each of which is given two arguments and | ||
5023 | returns the one which compares greater or less respectively. | ||
5024 | |||
5025 | These macros may evaluate their arguments multiple times. Avoid side | ||
5026 | effects. | ||
5027 | |||
5028 | \S{utils-max-digits} \cw{MAX_DIGITS()} | ||
5029 | |||
5030 | The \cw{MAX_DIGITS()} macro, defined in the main Puzzles header file, | ||
5031 | takes a type (or a variable of that type) and expands to an integer | ||
5032 | constant representing a reasonable upper bound on the number of | ||
5033 | characters that a number of that type could expand to when formatted | ||
5034 | as a decimal number using the \c{%u} or \c{%d} format of | ||
5035 | \cw{printf()}. This is useful for allocating a fixed-size buffer | ||
5036 | that's guaranteed to be big enough to \cw{sprintf()} a value into. | ||
5037 | Don't forget to add one for the trailing \cw{'\\0'}! | ||
5038 | |||
5039 | \S{utils-pi} \cw{PI} | ||
5040 | |||
5041 | The main Puzzles header file defines a macro \cw{PI} which expands | ||
5042 | to a floating-point constant representing pi. | ||
5043 | |||
5044 | (I've never understood why ANSI's \cw{<math.h>} doesn't define this. | ||
5045 | It'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 | |||
5051 | This function obscures the contents of a piece of data, by | ||
5052 | cryptographic methods. It is useful for games of hidden information | ||
5053 | (such as Mines, Guess or Black Box), in which the game ID | ||
5054 | theoretically reveals all the information the player is supposed to | ||
5055 | be trying to guess. So in order that players should be able to send | ||
5056 | game IDs to one another without accidentally spoiling the resulting | ||
5057 | game by looking at them, these games obfuscate their game IDs using | ||
5058 | this function. | ||
5059 | |||
5060 | Although the obfuscation function is cryptographic, it cannot | ||
5061 | properly be called encryption because it has no key. Therefore, | ||
5062 | anybody motivated enough can re-implement it, or hack it out of the | ||
5063 | Puzzles source, and strip the obfuscation off one of these game IDs | ||
5064 | to see what lies beneath. (Indeed, they could usually do it much | ||
5065 | more easily than that, by entering the game ID into their own copy | ||
5066 | of the puzzle and hitting Solve.) The aim is not to protect against | ||
5067 | a determined attacker; the aim is simply to protect people who | ||
5068 | wanted to play the game honestly from \e{accidentally} spoiling | ||
5069 | their own fun. | ||
5070 | |||
5071 | The input argument \c{bmp} points at a piece of memory to be | ||
5072 | obfuscated. \c{bits} gives the length of the data. Note that that | ||
5073 | length is in \e{bits} rather than bytes: if you ask for obfuscation | ||
5074 | of a partial number of bytes, then you will get it. Bytes are | ||
5075 | considered 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 | ||
5077 | two} bits of \cw{bmp[1]}. The remainder of a partially used byte is | ||
5078 | undefined (i.e. it may be corrupted by the function). | ||
5079 | |||
5080 | The parameter \c{decode} is \cw{false} for an encoding operation, | ||
5081 | and \cw{true} for a decoding operation. Each is the inverse of the | ||
5082 | other. (There's no particular reason you shouldn't obfuscate by | ||
5083 | decoding and restore cleartext by encoding, if you really wanted to; | ||
5084 | it should still work.) | ||
5085 | |||
5086 | The 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 | |||
5092 | This function takes an input byte array and converts it into an | ||
5093 | ASCII string encoding those bytes in (lower-case) hex. It returns a | ||
5094 | dynamically allocated string containing that encoding. | ||
5095 | |||
5096 | This 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 | |||
5103 | This function takes an ASCII string containing hex digits, and | ||
5104 | converts it back into a byte array of length \c{outlen}. If there | ||
5105 | aren't enough hex digits in the string, the contents of the | ||
5106 | resulting array will be undefined. | ||
5107 | |||
5108 | This function is the inverse of \cw{bin2hex()}. | ||
5109 | |||
5110 | \S{utils-fgetline} \cw{fgetline()} | ||
5111 | |||
5112 | \c char *fgetline(FILE *fp); | ||
5113 | |||
5114 | This function reads a single line of text from a standard C input | ||
5115 | stream, and returns it as a dynamically allocated string. The returned | ||
5116 | string still has a newline on the end. | ||
5117 | |||
5118 | \S{utils-arraysort} \cw{arraysort()} | ||
5119 | |||
5120 | Sorts an array, with slightly more flexibility than the standard C | ||
5121 | \cw{qsort()}. | ||
5122 | |||
5123 | This function is really implemented as a macro, so it doesn't have a | ||
5124 | prototype as such. But you could imagine it having a prototype like | ||
5125 | this: | ||
5126 | |||
5127 | \c void arraysort(element_t *array, size_t nmemb, | ||
5128 | \c arraysort_cmpfn_t cmp, void *ctx); | ||
5129 | |||
5130 | in which \c{element_t} is an unspecified type. | ||
5131 | |||
5132 | (Really, there's an underlying function that takes an extra parameter | ||
5133 | giving the size of each array element. But callers are encouraged to | ||
5134 | use this macro version, which fills that in automatically using | ||
5135 | \c{sizeof}.) | ||
5136 | |||
5137 | This function behaves essentially like \cw{qsort()}: it expects | ||
5138 | \c{array} to point to an array of \c{nmemb} elements, and it will sort | ||
5139 | them in place into the order specified by the comparison function | ||
5140 | \c{cmp}. | ||
5141 | |||
5142 | The comparison function should have this prototype: | ||
5143 | |||
5144 | \c int cmp(const void *a, const void *b, void *ctx); | ||
5145 | |||
5146 | in which \c{a} and \c{b} point at the two elements to be compared, and | ||
5147 | the return value is negative if \cw{a<b} (that is, \c{a} should appear | ||
5148 | before \c{b} in the output array), positive if \cw{a>b}, or zero if | ||
5149 | \c{a=b}. | ||
5150 | |||
5151 | The \c{ctx} parameter to \cw{arraysort()} is passed directly to the | ||
5152 | comparison function. This is the feature that makes \cw{arraysort()} | ||
5153 | more flexible than standard \cw{qsort()}: it lets you vary the sorting | ||
5154 | criterion in a dynamic manner without having to write global variables | ||
5155 | in 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 | |||
5162 | This function mixes the colours \c{src1} and \c{src2} in specified | ||
5163 | proportions, producing \c{dst}. \c{p} is the proportion of \c{src2} | ||
5164 | in 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 | ||
5168 | outside that range will produce extrapolated colours, which may be | ||
5169 | useful 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 | |||
5176 | It's reasonably common for a puzzle game's graphics to use | ||
5177 | highlights and lowlights to indicate \q{raised} or \q{lowered} | ||
5178 | sections. Fifteen, Sixteen and Twiddle are good examples of this. | ||
5179 | |||
5180 | Puzzles using this graphical style are running a risk if they just | ||
5181 | use whatever background colour is supplied to them by the front end, | ||
5182 | because that background colour might be too light or dark to see any | ||
5183 | highlights on at all. (In particular, it's not unheard of for the | ||
5184 | front end to specify a default background colour of white.) | ||
5185 | |||
5186 | Therefore, such puzzles can call this utility function from their | ||
5187 | \cw{colours()} routine (\k{backend-colours}). You pass it your front | ||
5188 | end handle, a pointer to the start of your return array, and three | ||
5189 | colour indices. It will: | ||
5190 | |||
5191 | \b call \cw{frontend_default_colour()} (\k{frontend-default-colour}) | ||
5192 | to 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 | ||
5197 | highlights and lowlights | ||
5198 | |||
5199 | \b write those results into the relevant positions in the \c{ret} | ||
5200 | array. | ||
5201 | |||
5202 | Thus, \cw{ret[background*3]} to \cw{ret[background*3+2]} will be set | ||
5203 | to RGB values defining a sensible background colour, and similary | ||
5204 | \c{highlight} and \c{lowlight} will be set to sensible colours. | ||
5205 | |||
5206 | Either \c{highlight} or \c{lowlight} may be passed in as \cw{-1} to | ||
5207 | indicate that the back-end does not require a highlight or lowlight | ||
5208 | colour, 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 | |||
5215 | This function behaves exactly like \cw{game_mkhighlight()}, except | ||
5216 | that 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 | ||
5219 | brighter and darker versions of that. | ||
5220 | |||
5221 | This is useful if you want to show relief sections of a puzzle in more | ||
5222 | than one base colour. | ||
5223 | |||
5224 | \S{utils-button2label} \cw{button2label()} | ||
5225 | |||
5226 | \c char *button2label(int button); | ||
5227 | |||
5228 | This function generates a descriptive text label for \cw{button}, | ||
5229 | which should be a button code that can be passed to the midend. For | ||
5230 | example, calling this function with \cw{CURSOR_UP} will result in the | ||
5231 | string \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 | ||
5235 | passed to this function to obtain an appropriate label. If, however, | ||
5236 | the field is not \cw{NULL}, this function should not be called with | ||
5237 | the corresponding \cw{button} field. | ||
5238 | |||
5239 | The 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 | |||
5247 | This function can be called by \cw{interpret_move()} to implement the | ||
5248 | default keyboard API for moving a cursor around a grid. | ||
5249 | |||
5250 | \c{button} is the same value passed in to \cw{interpret_move()}. If | ||
5251 | it'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 | ||
5255 | location of a cursor in a square grid. \c{w} and \c{h} give the | ||
5256 | dimensions of the grid. On return, \c{x} and \c{y} are updated to give | ||
5257 | the cursor's new position according to which arrow key was pressed. | ||
5258 | |||
5259 | This 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} | ||
5261 | inclusive (top to bottom). | ||
5262 | |||
5263 | If \c{wrap} is \cw{true}, then trying to move the cursor off any edge | ||
5264 | of the grid will result in it wrapping round to the corresponding | ||
5265 | square on the opposite edge. If \c{wrap} is \cw{false}, such a move | ||
5266 | will have no effect. | ||
5267 | |||
5268 | If \c{visible} is not \cw{NULL}, it points to a flag indicating | ||
5269 | whether the cursor is visible. This will be set to \cw{true} if | ||
5270 | \c{button} represents a cursor-movement key. | ||
5271 | |||
5272 | The function returns one of the special constants that can be returned | ||
5273 | by \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 | |||
5281 | Invents a random division of a rectangle into same-sized polyominoes, | ||
5282 | such as is found in the block layout of a Solo puzzle in jigsaw mode, | ||
5283 | or the solution to a Palisade puzzle. | ||
5284 | |||
5285 | \c{w} and \c{h} are the dimensions of the rectangle. \c{k} is the size | ||
5286 | of 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 | ||
5289 | select a random division of the rectangle. | ||
5290 | |||
5291 | The return value is a dsf (see \k{utils-dsf}) whose equivalence | ||
5292 | classes correspond to the polyominoes that the rectangle is divided | ||
5293 | into. The indices of the dsf are of the form \c{y*w+x}, for the cell | ||
5294 | with 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 | |||
5300 | Invents 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 | ||
5303 | odd, then one square will be left untiled. | ||
5304 | |||
5305 | \c{rs} is a \cw{random_state} used to supply the random numbers to | ||
5306 | select a random division of the rectangle. | ||
5307 | |||
5308 | The return value is an array in which element \c{y*w+x} represents the | ||
5309 | cell with coordinates \cw{x,y}. Each element of the array gives the | ||
5310 | index (in the same representation) of the other end of its domino. If | ||
5311 | there'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 | |||
5318 | Just like \cw{domino_layout()}, but does no memory allocation. You can | ||
5319 | use this to save allocator overhead if you expect to need to generate | ||
5320 | many 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 | |||
5325 | The returned array is delivered in \c{grid}. | ||
5326 | |||
5327 | \S{utils-strip-button-modifiers} \cw{STRIP_BUTTON_MODIFIERS()} | ||
5328 | |||
5329 | This macro, defined in the main Puzzles header file, strips the | ||
5330 | modifier flags from the key code passed as an argument. It is | ||
5331 | equivalent 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 | |||
5337 | Swap two regions of memory of \cw{size} bytes. The two regions must | ||
5338 | not overlap. | ||
5339 | |||
5340 | \C{writing} How to write a new puzzle | ||
5341 | |||
5342 | This chapter gives a guide to how to actually write a new puzzle: | ||
5343 | where to start, what to do first, how to solve common problems. | ||
5344 | |||
5345 | The previous chapters have been largely composed of facts. This one | ||
5346 | is mostly advice. | ||
5347 | |||
5348 | \H{writing-editorial} Choosing a puzzle | ||
5349 | |||
5350 | Before you start writing a puzzle, you have to choose one. Your | ||
5351 | taste in puzzle games is up to you, of course; and, in fact, you're | ||
5352 | probably reading this guide because you've \e{already} thought of a | ||
5353 | game you want to write. But if you want to get it accepted into the | ||
5354 | official Puzzles distribution, then there's a criterion it has to | ||
5355 | meet. | ||
5356 | |||
5357 | The 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 | ||
5359 | through demonstrable lack of skill \dash that is, such that a better | ||
5360 | player presented with the same game state would have \e{known} to do | ||
5361 | something different. | ||
5362 | |||
5363 | For 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 | ||
5365 | is actually impossible is not acceptable. | ||
5366 | |||
5367 | (An exception to this: if the user has selected some non-default | ||
5368 | option which is clearly labelled as potentially unfair, \e{then} | ||
5369 | you're allowed to generate possibly insoluble puzzles, because the | ||
5370 | user isn't unsuspecting any more. Same Game and Mines both have | ||
5371 | options of this type.) | ||
5372 | |||
5373 | Secondly, if the game includes hidden information, then it must be | ||
5374 | possible to deduce a correct move at every stage from the currently | ||
5375 | available information. It's not enough that there should exist some | ||
5376 | sequence of moves which will get from the start state to the solved | ||
5377 | state, if the player doesn't necessarily have enough information to | ||
5378 | \e{find} that solution. For example, in the card solitaire game | ||
5379 | Klondike, it's possible to reach a dead end because you had an | ||
5380 | arbitrary choice to make on no information, and made it the wrong way, | ||
5381 | which violates the fairness criterion, because a better player | ||
5382 | couldn't have known they needed to make the other choice. | ||
5383 | |||
5384 | (Of course, games in this collection always have an Undo function, so | ||
5385 | if you did take the wrong route through a Klondike game, you could use | ||
5386 | Undo to back up and try a different choice. This doesn't count. In a | ||
5387 | fair game, you should be able to determine a correct move from the | ||
5388 | information visible \e{now}, without having to make moves to get more | ||
5389 | information that you can then back up and use.) | ||
5390 | |||
5391 | Sometimes you can adjust the rules of an unfair puzzle to make it meet | ||
5392 | this definition of fairness. For example, more than one implementation | ||
5393 | of solitaire-style games (including card solitaires and Mahjong | ||
5394 | Solitaire) include a UI action to shuffle the remaining cards or tiles | ||
5395 | without changing their position; this action might be available at any | ||
5396 | time with a time or points penalty, or it might be illegal to use | ||
5397 | unless you have no other possible move. Adding an option like this | ||
5398 | would make a game \e{technically} fair, but it's better to avoid even | ||
5399 | that if you can. | ||
5400 | |||
5401 | Providing a \e{unique} solution is a little more negotiable; it | ||
5402 | depends on the puzzle. Solo would have been of unacceptably low | ||
5403 | quality if it didn't always have a unique solution, whereas Twiddle | ||
5404 | inherently has multiple solutions by its very nature and it would | ||
5405 | have been meaningless to even \e{suggest} making it uniquely | ||
5406 | soluble. Somewhere in between, Flip could reasonably be made to have | ||
5407 | unique solutions (by enforcing a zero-dimension kernel in every | ||
5408 | generated matrix) but it doesn't seem like a serious quality problem | ||
5409 | that it doesn't. | ||
5410 | |||
5411 | Of course, you don't \e{have} to care about all this. There's | ||
5412 | nothing stopping you implementing any puzzle you want to if you're | ||
5413 | happy to maintain your puzzle yourself, distribute it from your own | ||
5414 | web site, fork the Puzzles code completely, or anything like that. | ||
5415 | It's free software; you can do what you like with it. But any game | ||
5416 | that you want to be accepted into \e{my} Puzzles code base has to | ||
5417 | satisfy the fairness criterion, which means all randomly generated | ||
5418 | puzzles must have a solution (unless the user has deliberately | ||
5419 | chosen otherwise) and it must be possible \e{in theory} to find that | ||
5420 | solution without having to guess. | ||
5421 | |||
5422 | \H{writing-gs} Getting started | ||
5423 | |||
5424 | The simplest way to start writing a new puzzle is to copy | ||
5425 | \c{nullgame.c}. This is a template puzzle source file which does | ||
5426 | almost nothing, but which contains all the back end function | ||
5427 | prototypes and declares the back end data structure correctly. It is | ||
5428 | built every time the rest of Puzzles is built, to ensure that it | ||
5429 | doesn't get out of sync with the code and remains buildable. | ||
5430 | |||
5431 | So start by copying \c{nullgame.c} into your new source file. Then | ||
5432 | you'll gradually add functionality until the very boring Null Game | ||
5433 | turns into your real game. | ||
5434 | |||
5435 | Next you'll need to add your puzzle to the build scripts, in order to | ||
5436 | compile it conveniently. Puzzles is a CMake project, so you do this by | ||
5437 | adding a \cw{puzzle()} statement to CMakeLists.txt. Look at the | ||
5438 | existing ones to see what those look like, and add one that looks | ||
5439 | similar. | ||
5440 | |||
5441 | Once your source file is building, you can move on to the fun bit. | ||
5442 | |||
5443 | \S{writing-generation} Puzzle generation | ||
5444 | |||
5445 | Randomly generating instances of your puzzle is almost certain to be | ||
5446 | the most difficult part of the code, and also the task with the | ||
5447 | highest chance of turning out to be completely infeasible. Therefore | ||
5448 | I strongly recommend doing it \e{first}, so that if it all goes | ||
5449 | horribly wrong you haven't wasted any more time than you absolutely | ||
5450 | had to. What I usually do is to take an unmodified \c{nullgame.c}, | ||
5451 | and start adding code to \cw{new_game_desc()} which tries to | ||
5452 | generate a puzzle instance and print it out using \cw{printf()}. | ||
5453 | Once that's working, \e{then} I start connecting it up to the return | ||
5454 | value of \cw{new_game_desc()}, populating other structures like | ||
5455 | \c{game_params}, and generally writing the rest of the source file. | ||
5456 | |||
5457 | There are many ways to generate a puzzle which is known to be | ||
5458 | soluble. In this section I list all the methods I currently know of, | ||
5459 | in case any of them can be applied to your puzzle. (Not all of these | ||
5460 | methods will work, or in some cases even make sense, for all | ||
5461 | puzzles.) | ||
5462 | |||
5463 | Some puzzles are mathematically tractable, meaning you can work out | ||
5464 | in advance which instances are soluble. Sixteen, for example, has a | ||
5465 | parity constraint in some settings which renders exactly half the | ||
5466 | game space unreachable, but it can be mathematically proved that any | ||
5467 | position not in that half \e{is} reachable. Therefore, Sixteen's | ||
5468 | grid generation simply consists of selecting at random from a well | ||
5469 | defined subset of the game space. Cube in its default state is even | ||
5470 | easier: \e{every} possible arrangement of the blue squares and the | ||
5471 | cube's starting position is soluble! | ||
5472 | |||
5473 | Another option is to redefine what you mean by \q{soluble}. Black | ||
5474 | Box takes this approach. There are layouts of balls in the box which | ||
5475 | are completely indistinguishable from one another no matter how many | ||
5476 | beams you fire into the box from which angles, which would normally | ||
5477 | be grounds for declaring those layouts unfair; but fortunately, | ||
5478 | detecting that indistinguishability is computationally easy. So | ||
5479 | Black Box doesn't demand that your ball placements match its own; it | ||
5480 | merely demands that your ball placements be \e{indistinguishable} | ||
5481 | from the ones it was thinking of. If you have an ambiguous puzzle, | ||
5482 | then any of the possible answers is considered to be a solution. | ||
5483 | Having redefined the rules in that way, any puzzle is soluble again. | ||
5484 | |||
5485 | Those are the simple techniques. If they don't work, you have to get | ||
5486 | cleverer. | ||
5487 | |||
5488 | One way to generate a soluble puzzle is to start from the solved | ||
5489 | state and make inverse moves until you reach a starting state. Then | ||
5490 | you know there's a solution, because you can just list the inverse | ||
5491 | moves you made and make them in the opposite order to return to the | ||
5492 | solved state. | ||
5493 | |||
5494 | This method can be simple and effective for puzzles where you get to | ||
5495 | decide what's a starting state and what's not. In Pegs, for example, | ||
5496 | the generator begins with one peg in the centre of the board and | ||
5497 | makes inverse moves until it gets bored; in this puzzle, valid | ||
5498 | inverse moves are easy to detect, and \e{any} state that's reachable | ||
5499 | from the solved state by inverse moves is a reasonable starting | ||
5500 | position. So Pegs just continues making inverse moves until the | ||
5501 | board satisfies some criteria about extent and density, and then | ||
5502 | stops and declares itself done. | ||
5503 | |||
5504 | For other puzzles, it can be a lot more difficult. Same Game uses | ||
5505 | this strategy too, and it's lucky to get away with it at all: valid | ||
5506 | inverse moves aren't easy to find (because although it's easy to | ||
5507 | insert additional squares in a Same Game position, it's difficult to | ||
5508 | arrange that \e{after} the insertion they aren't adjacent to any | ||
5509 | other squares of the same colour), so you're constantly at risk of | ||
5510 | running out of options and having to backtrack or start again. Also, | ||
5511 | Same Game grids never start off half-empty, which means you can't | ||
5512 | just stop when you run out of moves \dash you have to find a way to | ||
5513 | fill the grid up \e{completely}. | ||
5514 | |||
5515 | The other way to generate a puzzle that's soluble is to start from | ||
5516 | the other end, and actually write a \e{solver}. This tends to ensure | ||
5517 | that a puzzle has a \e{unique} solution over and above having a | ||
5518 | solution at all, so it's a good technique to apply to puzzles for | ||
5519 | which that's important. | ||
5520 | |||
5521 | One theoretical drawback of generating soluble puzzles by using a | ||
5522 | solver is that your puzzles are restricted in difficulty to those | ||
5523 | which the solver can handle. (Most solvers are not fully general: | ||
5524 | many sets of puzzle rules are NP-complete or otherwise nasty, so | ||
5525 | most solvers can only handle a subset of the theoretically soluble | ||
5526 | puzzles.) It's been my experience in practice, however, that this | ||
5527 | usually isn't a problem; computers are good at very different things | ||
5528 | from humans, and what the computer thinks is nice and easy might | ||
5529 | still be pleasantly challenging for a human. For example, when | ||
5530 | solving Dominosa puzzles I frequently find myself using a variety of | ||
5531 | reasoning techniques that my solver doesn't know about; in | ||
5532 | principle, therefore, I should be able to solve the puzzle using | ||
5533 | only those techniques it \e{does} know about, but this would involve | ||
5534 | repeatedly searching the entire grid for the one simple deduction I | ||
5535 | can make. Computers are good at this sort of exhaustive search, but | ||
5536 | it's been my experience that human solvers prefer to do more complex | ||
5537 | deductions than to spend ages searching for simple ones. So in many | ||
5538 | cases I don't find my own playing experience to be limited by the | ||
5539 | restrictions on the solver. | ||
5540 | |||
5541 | (This isn't \e{always} the case. Solo is a counter-example; | ||
5542 | generating Solo puzzles using a simple solver does lead to | ||
5543 | qualitatively easier puzzles. Therefore I had to make the Solo | ||
5544 | solver rather more advanced than most of them.) | ||
5545 | |||
5546 | There are several different ways to apply a solver to the problem of | ||
5547 | generating a soluble puzzle. I list a few of them below. | ||
5548 | |||
5549 | The simplest approach is brute force: randomly generate a puzzle, | ||
5550 | use the solver to see if it's soluble, and if not, throw it away and | ||
5551 | try again until you get lucky. This is often a viable technique if | ||
5552 | all else fails, but it tends not to scale well: for many puzzle | ||
5553 | types, the probability of finding a uniquely soluble instance | ||
5554 | decreases sharply as puzzle size goes up, so this technique might | ||
5555 | work reasonably fast for small puzzles but take (almost) forever at | ||
5556 | larger sizes. Still, if there's no other alternative it can be | ||
5557 | usable: Pattern and Dominosa both use this technique. (However, | ||
5558 | Dominosa has a means of tweaking the randomly generated grids to | ||
5559 | increase the \e{probability} of them being soluble, by ruling out | ||
5560 | one of the most common ambiguous cases. This improved generation | ||
5561 | speed by over a factor of 10 on the highest preset!) | ||
5562 | |||
5563 | An approach which can be more scalable involves generating a grid | ||
5564 | and then tweaking it to make it soluble. This is the technique used | ||
5565 | by Mines and also by Net: first a random puzzle is generated, and | ||
5566 | then the solver is run to see how far it gets. Sometimes the solver | ||
5567 | will get stuck; when that happens, examine the area it's having | ||
5568 | trouble with, and make a small random change in that area to allow | ||
5569 | it to make more progress. Continue solving (possibly even without | ||
5570 | restarting the solver), tweaking as necessary, until the solver | ||
5571 | finishes. Then restart the solver from the beginning to ensure that | ||
5572 | the tweaks haven't caused new problems in the process of solving old | ||
5573 | ones (which can sometimes happen). | ||
5574 | |||
5575 | This strategy works well in situations where the usual solver | ||
5576 | failure mode is to get stuck in an easily localised spot. Thus it | ||
5577 | works well for Net and Mines, whose most common failure mode tends | ||
5578 | to be that most of the grid is fine but there are a few widely | ||
5579 | separated ambiguous sections; but it would work less well for | ||
5580 | Dominosa, in which the way you get stuck is to have scoured the | ||
5581 | whole grid and not found anything you can deduce \e{anywhere}. Also, | ||
5582 | it relies on there being a low probability that tweaking the grid | ||
5583 | introduces a new problem at the same time as solving the old one; | ||
5584 | Mines and Net also have the property that most of their deductions | ||
5585 | are local, so that it's very unlikely for a tweak to affect | ||
5586 | something half way across the grid from the location where it was | ||
5587 | applied. In Dominosa, by contrast, a lot of deductions use | ||
5588 | information about half the grid (\q{out of all the sixes, only one | ||
5589 | is next to a three}, which can depend on the values of up to 32 of | ||
5590 | the 56 squares in the default setting!), so this tweaking strategy | ||
5591 | would be rather less likely to work well. | ||
5592 | |||
5593 | A more specialised strategy is that used in Solo and Slant. These | ||
5594 | puzzles have the property that they derive their difficulty from not | ||
5595 | presenting all the available clues. (In Solo's case, if all the | ||
5596 | possible clues were provided then the puzzle would already be | ||
5597 | solved; in Slant it would still require user action to fill in the | ||
5598 | lines, but it would present no challenge at all). Therefore, a | ||
5599 | simple generation technique is to leave the decision of which clues | ||
5600 | to provide until the last minute. In other words, first generate a | ||
5601 | random \e{filled} grid with all possible clues present, and then | ||
5602 | gradually remove clues for as long as the solver reports that it's | ||
5603 | still soluble. Unlike the methods described above, this technique | ||
5604 | \e{cannot} fail \dash once you've got a filled grid, nothing can | ||
5605 | stop you from being able to convert it into a viable puzzle. | ||
5606 | However, 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 | ||
5608 | to affect the set of clues is by altering the solution. | ||
5609 | |||
5610 | (Unfortunately, Solo is complicated by the need to provide puzzles | ||
5611 | at varying difficulty levels. It's easy enough to generate a puzzle | ||
5612 | of \e{at most} a given level of difficulty; you just have a solver | ||
5613 | with configurable intelligence, and you set it to a given level and | ||
5614 | apply the above technique, thus guaranteeing that the resulting grid | ||
5615 | is solvable by someone with at most that much intelligence. However, | ||
5616 | generating a puzzle of \e{at least} a given level of difficulty is | ||
5617 | rather harder; if you go for \e{at most} Intermediate level, you're | ||
5618 | likely to find that you've accidentally generated a Trivial grid a | ||
5619 | lot of the time, because removing just one number is sufficient to | ||
5620 | take the puzzle from Trivial straight to Ambiguous. In that | ||
5621 | situation Solo has no remaining options but to throw the puzzle away | ||
5622 | and start again.) | ||
5623 | |||
5624 | A final strategy is to use the solver \e{during} puzzle | ||
5625 | construction: lay out a bit of the grid, run the solver to see what | ||
5626 | it allows you to deduce, and then lay out a bit more to allow the | ||
5627 | solver to make more progress. There are articles on the web that | ||
5628 | recommend constructing Sudoku puzzles by this method (which is | ||
5629 | completely the opposite way round to how Solo does it); for Sudoku | ||
5630 | it has the advantage that you get to specify your clue squares in | ||
5631 | advance (so you can have them make pretty patterns). | ||
5632 | |||
5633 | Rectangles uses a strategy along these lines. First it generates a | ||
5634 | grid by placing the actual rectangles; then it has to decide where | ||
5635 | in each rectangle to place a number. It uses a solver to help it | ||
5636 | place the numbers in such a way as to ensure a unique solution. It | ||
5637 | does 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 | ||
5639 | solver must be capable of coping with uncertainty about exactly | ||
5640 | where the numbers are! It runs the solver as far as it can until it | ||
5641 | gets stuck; then it narrows down the possible positions of a number | ||
5642 | in order to allow the solver to make more progress, and so on. Most | ||
5643 | of the time this process terminates with the grid fully solved, at | ||
5644 | which point any remaining number-placement decisions can be made at | ||
5645 | random from the options not so far ruled out. Note that unlike the | ||
5646 | Net/Mines tweaking strategy described above, this algorithm does not | ||
5647 | require a checking run after it completes: if it finishes | ||
5648 | successfully at all, then it has definitely produced a uniquely | ||
5649 | soluble puzzle. | ||
5650 | |||
5651 | Most of the strategies described above are not 100% reliable. Each | ||
5652 | one has a failure rate: every so often it has to throw out the whole | ||
5653 | grid and generate a fresh one from scratch. (Solo's strategy would | ||
5654 | be the exception, if it weren't for the need to provide configurable | ||
5655 | difficulty levels.) Occasional failures are not a fundamental | ||
5656 | problem in this sort of work, however: it's just a question of | ||
5657 | dividing the grid generation time by the success rate (if it takes | ||
5658 | 10ms to generate a candidate grid and 1/5 of them work, then it will | ||
5659 | take 50ms on average to generate a viable one), and seeing whether | ||
5660 | the expected time taken to \e{successfully} generate a puzzle is | ||
5661 | unacceptably 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 | ||
5663 | think \e{that's} bad then go and look at the source code and find | ||
5664 | the comment showing what the figures were before the generation-time | ||
5665 | tweaks!), but the generator itself is very fast so this doesn't | ||
5666 | matter. Rectangles has a slower generator, but fails well under 50% | ||
5667 | of the time. | ||
5668 | |||
5669 | So don't be discouraged if you have an algorithm that doesn't always | ||
5670 | work: if it \e{nearly} always works, that's probably good enough. | ||
5671 | The one place where reliability is important is that your algorithm | ||
5672 | must never produce false positives: it must not claim a puzzle is | ||
5673 | soluble when it isn't. It can produce false negatives (failing to | ||
5674 | notice that a puzzle is soluble), and it can fail to generate a | ||
5675 | puzzle at all, provided it doesn't do either so often as to become | ||
5676 | slow. | ||
5677 | |||
5678 | One last piece of advice: for grid-based puzzles, when writing and | ||
5679 | testing 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 | ||
5682 | you mistakenly write \c{h} instead of \c{w} (or vice versa) | ||
5683 | somewhere in the code. Use a rectangular grid for testing, and any | ||
5684 | size of grid will be likely to work after that. | ||
5685 | |||
5686 | \S{writing-textformats} Designing textual description formats | ||
5687 | |||
5688 | Another aspect of writing a puzzle which is worth putting some | ||
5689 | thought into is the design of the various text description formats: | ||
5690 | the format of the game parameter encoding, the game description | ||
5691 | encoding, and the move encoding. | ||
5692 | |||
5693 | The first two of these should be reasonably intuitive for a user to | ||
5694 | type in; so provide some flexibility where possible. Suppose, for | ||
5695 | example, your parameter format consists of two numbers separated by | ||
5696 | an \c{x} to specify the grid dimensions (\c{10x10} or \c{20x15}), | ||
5697 | and then has some suffixes to specify other aspects of the game | ||
5698 | type. It's almost always a good idea in this situation to arrange | ||
5699 | that \cw{decode_params()} can handle the suffixes appearing in any | ||
5700 | order, even if \cw{encode_params()} only ever generates them in one | ||
5701 | order. | ||
5702 | |||
5703 | These formats will also be expected to be reasonably stable: users | ||
5704 | will expect to be able to exchange game IDs with other users who | ||
5705 | aren't running exactly the same version of your game. So make them | ||
5706 | robust and stable: don't build too many assumptions into the game ID | ||
5707 | format which will have to be changed every time something subtle | ||
5708 | changes in the puzzle code. | ||
5709 | |||
5710 | \H{writing-howto} Common how-to questions | ||
5711 | |||
5712 | This section lists some common things people want to do when writing | ||
5713 | a puzzle, and describes how to achieve them within the Puzzles | ||
5714 | framework. | ||
5715 | |||
5716 | \S{writing-howto-redraw} Redrawing just the changed parts of the window | ||
5717 | |||
5718 | Redrawing the entire window on every move is wasteful. If the user | ||
5719 | makes a move changing only one square of a grid, it's better to redraw | ||
5720 | just that square. | ||
5721 | |||
5722 | (Yes, computers are fast these days, but these puzzles still try to be | ||
5723 | portable to devices at the less fast end of the spectrum, so it's | ||
5724 | still worth saving effort where it's easy. On the other hand, some | ||
5725 | puzzles just \e{can't} do this easily \dash Untangle is an example | ||
5726 | that really does have no better option than to redraw everything.) | ||
5727 | |||
5728 | For a typical grid-oriented puzzle, a robust way to do this is: | ||
5729 | |||
5730 | \b Invent a data representation that describes everything about the | ||
5731 | appearance of a grid cell in the puzzle window. | ||
5732 | |||
5733 | \b Have \c{game_drawstate} contain an array of those, describing the | ||
5734 | current 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 | ||
5737 | appearance 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 | |||
5741 | Where possible, I generally make my data representation an integer | ||
5742 | full of bit flags, to save space, and to make it easy to compare the | ||
5743 | old and new versions. If yours needs to be bigger than that, you may | ||
5744 | have to define a small \cw{struct} and write an equality-checking | ||
5745 | function. | ||
5746 | |||
5747 | The data representation of the \e{appearance} of a square in | ||
5748 | \c{game_drawstate} will not generally be identical to the | ||
5749 | representation of the \e{logical state} of a square in \c{game_state}, | ||
5750 | because many things contribute to a square's appearance other than its | ||
5751 | logical state. For example: | ||
5752 | |||
5753 | \b Extra information overlaid on the square by the user interface, | ||
5754 | such as a keyboard-controlled cursor, or highlighting of squares | ||
5755 | currently 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 | ||
5760 | squares. For example, if you draw thick lines along the edges between | ||
5761 | grid squares, then the corners of those lines will be visible in | ||
5762 | logically unrelated squares. An entry in the \c{game_drawstate} array | ||
5763 | should describe a specific \e{rectangular area of the screen}, so that | ||
5764 | those areas can be erased and redrawn independently \dash so it must | ||
5765 | represent anything that appears in that area, even if it's sticking | ||
5766 | out from a graphic that logically lives in some other square. | ||
5767 | |||
5768 | \b Temporary changes to the appearance of a square because of an | ||
5769 | ongoing completion flash. | ||
5770 | |||
5771 | \b The current display mode, if a game provides more than one. (For | ||
5772 | example, the optional letters distinguishing the different coloured | ||
5773 | pegs in Guess.) | ||
5774 | |||
5775 | All of this must be included in the \c{game_drawstate} representation, | ||
5776 | but should not be in the \c{game_state} at all. \cw{redraw()} will | ||
5777 | pull it all together from the \c{game_state}, the \c{game_ui}, and the | ||
5778 | animation and flash parameters. | ||
5779 | |||
5780 | To make sure that \e{everything} affecting a square's appearance is | ||
5781 | included in this representation, it's a good idea to have a separate | ||
5782 | function for drawing a grid square, and deliberately \e{not} pass it a | ||
5783 | copy of the \c{game_state} or the \c{game_ui} at all. That way, if you | ||
5784 | want that function to draw anything differently, you \e{have} to do it | ||
5785 | by including that information in the representation of a square's | ||
5786 | appearance. | ||
5787 | |||
5788 | But of course there are a couple of exceptions to this rule. A few | ||
5789 | things \e{don't} have to go in the \c{game_drawstate} array, and can | ||
5790 | safely be passed separately to the redraw-square function: | ||
5791 | |||
5792 | \b Anything that remains completely fixed throughout the whole of a | ||
5793 | game, 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 | ||
5795 | press New Game, a new \c{game_drawstate} will always be created from | ||
5796 | scratch. So the \c{game_drawstate} only needs to describe everything | ||
5797 | that might \e{change} during gameplay. If you have a sub-\cw{struct} | ||
5798 | in your \c{game_state} that describes immutable properties of the | ||
5799 | current game, as suggested in \k{writing-ref-counting}, then it's safe | ||
5800 | to pass \e{that substructure} to the redraw-square function, and have | ||
5801 | it 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 | ||
5805 | much easier to just assume that any square involved in the animation | ||
5806 | will \e{always} need redrawing. So \c{anim_length} can safely be | ||
5807 | passed separately to the redraw-square function \dash but you also | ||
5808 | have to remember to redraw a square if \e{either} its appearance is | ||
5809 | different 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 | |||
5813 | A 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. | ||
5815 | A cursor \dash probably specified in the \c{game_ui} \dash is a good | ||
5816 | example. | ||
5817 | |||
5818 | In the \c{game_ui}, it would \e{obviously} be silly to have an array | ||
5819 | covering the whole game grid with a boolean flag stating whether the | ||
5820 | cursor was at each position. Doing that would waste space, would | ||
5821 | make it difficult to find the cursor in order to do anything with | ||
5822 | it, and would introduce the potential for synchronisation bugs in | ||
5823 | which you ended up with two cursors or none. The obviously sensible | ||
5824 | way to store a cursor in the \c{game_ui} is to have fields directly | ||
5825 | encoding the cursor's coordinates. | ||
5826 | |||
5827 | However, 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 | ||
5829 | draw state, the redraw code will get very complicated. In the draw | ||
5830 | state, in fact, it \e{is} probably the right thing to have a cursor | ||
5831 | flag for every position in the grid, and make it part of the | ||
5832 | representation 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} | ||
5835 | flag in the representation of the square's appearance, if its | ||
5836 | coordinates match the cursor coordinates stored in the \c{game_ui}. | ||
5837 | This will automatically ensure that when the cursor moves, the redraw | ||
5838 | loop will redraw the square that \e{previously} contained the cursor | ||
5839 | and doesn't any more, and the one that now contains the cursor. | ||
5840 | |||
5841 | \S{writing-keyboard-cursor} Implementing a keyboard-controlled cursor | ||
5842 | |||
5843 | It is often useful to provide a keyboard control method in a | ||
5844 | basically mouse-controlled game. A keyboard-controlled cursor is | ||
5845 | best implemented by storing its location in the \c{game_ui} (since | ||
5846 | if it were in the \c{game_state} then the user would have to | ||
5847 | separately undo every cursor move operation). So the procedure would | ||
5848 | be: | ||
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 | ||
5853 | cursor 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 | ||
5857 | by actually performing a move based on the current cursor location. | ||
5858 | |||
5859 | \b You might want an additional \c{game_ui} field stating whether | ||
5860 | the cursor is currently visible, and having it disappear when a | ||
5861 | mouse action occurs (so that it doesn't clutter the display when not | ||
5862 | actually 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 | ||
5866 | which there is no move to make (which is the case in some types of | ||
5867 | completed 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 | |||
5874 | Some games have a user interface which involves dragging some sort | ||
5875 | of game element around using the mouse. If you need to show a | ||
5876 | graphic moving smoothly over the top of other graphics, use a | ||
5877 | blitter (see \k{drawing-blitter} for the blitter API) to save the | ||
5878 | background 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 | ||
5884 | piece of saved background needs to be. | ||
5885 | |||
5886 | \b In the game's \cw{set_size()} function, once you know the size of | ||
5887 | the object you'll be dragging around the display and hence the | ||
5888 | required 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 | ||
5893 | events by updating some fields in the \cw{game_ui} which indicate | ||
5894 | that a drag is in progress. | ||
5895 | |||
5896 | \b At the \e{very end} of \cw{redraw()}, after all other drawing has | ||
5897 | been done, draw the moving object if there is one. First save the | ||
5898 | background under the object in the blitter; then set a clip | ||
5899 | rectangle covering precisely the area you just saved (just in case | ||
5900 | anti-aliasing or some other error causes your drawing to go beyond | ||
5901 | the area you saved). Then draw the object, and call \cw{unclip()}. | ||
5902 | Finally, set a flag in the \cw{game_drawstate} that indicates that | ||
5903 | the blitter needs restoring. | ||
5904 | |||
5905 | \b At the very start of \cw{redraw()}, before doing anything else at | ||
5906 | all, check the flag in the \cw{game_drawstate}, and if it says the | ||
5907 | blitter needs restoring then restore it. (Then clear the flag, so | ||
5908 | that this won't happen again in the next redraw if no moving object | ||
5909 | is drawn this time.) | ||
5910 | |||
5911 | This way, you will be able to write the rest of the redraw function | ||
5912 | completely ignoring the dragged object, as if it were floating above | ||
5913 | your bitmap and being completely separate. | ||
5914 | |||
5915 | \S{writing-ref-counting} Sharing large invariant data between all | ||
5916 | game states | ||
5917 | |||
5918 | In some puzzles, there is a large amount of data which never changes | ||
5919 | between game states. The array of numbers in Dominosa is a good | ||
5920 | example. | ||
5921 | |||
5922 | You \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 | ||
5924 | every new \c{game_state}; but it would waste memory and time. A | ||
5925 | more efficient way is to use a reference-counted structure. | ||
5926 | |||
5927 | \b Define a structure type containing the data in question, and also | ||
5928 | containing an integer reference count. | ||
5929 | |||
5930 | \b Have a field in \c{game_state} which is a pointer to this | ||
5931 | structure. | ||
5932 | |||
5933 | \b In \cw{new_game()}, when creating a fresh game state at the start | ||
5934 | of a new game, create an instance of this structure, initialise it | ||
5935 | with 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 | ||
5938 | for the new game state, simply set the new game state to point at | ||
5939 | the same copy of the structure, and increment its reference count. | ||
5940 | |||
5941 | \b In \cw{free_game()}, decrement the reference count in the | ||
5942 | structure pointed to by the game state; if the count reaches zero, | ||
5943 | free the structure. | ||
5944 | |||
5945 | This way, the invariant data will persist for only as long as it's | ||
5946 | genuinely needed; \e{as soon} as the last game state for a | ||
5947 | particular puzzle instance is freed, the invariant data for that | ||
5948 | puzzle will vanish as well. Reference counting is a very efficient | ||
5949 | form of garbage collection, when it works at all. (Which it does in | ||
5950 | this instance, of course, because there's no possibility of circular | ||
5951 | references.) | ||
5952 | |||
5953 | \S{writing-flash-types} Implementing multiple types of flash | ||
5954 | |||
5955 | In some games you need to flash in more than one different way. | ||
5956 | Mines, for example, flashes white when you win, and flashes red when | ||
5957 | you tread on a mine and die. | ||
5958 | |||
5959 | The 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 | ||
5964 | decide whether a flash is required and what type. Write the type of | ||
5965 | flash 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 | ||
5968 | non-zero, examine the field in \c{game_ui} to decide which type of | ||
5969 | flash to draw. | ||
5970 | |||
5971 | \cw{redraw()} will never be called with \c{flash_time} non-zero | ||
5972 | unless \cw{flash_length()} was first called to tell the mid-end that | ||
5973 | a 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 | |||
5979 | A number of puzzle types benefit from a quick animation of each move | ||
5980 | you make. | ||
5981 | |||
5982 | For some games, such as Fifteen, this is particularly easy. Whenever | ||
5983 | \cw{redraw()} is called with \c{oldstate} non-\cw{NULL}, Fifteen | ||
5984 | simply compares the position of each tile in the two game states, | ||
5985 | and if the tile is not in the same place then it draws it some | ||
5986 | fraction of the way from its old position to its new position. This | ||
5987 | method copes automatically with undo. | ||
5988 | |||
5989 | Other games are less obvious. In Sixteen, for example, you can't | ||
5990 | just draw each tile a fraction of the way from its old to its new | ||
5991 | position: if you did that, the end tile would zip very rapidly past | ||
5992 | all 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 | ||
5994 | going one way and on the bottom going the other way.) | ||
5995 | |||
5996 | A useful trick here is to define a field or two in the game state | ||
5997 | that indicates what the last move was. | ||
5998 | |||
5999 | \b Add a \q{last move} field to the \c{game_state} (or two or more | ||
6000 | fields 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 | ||
6003 | game state. | ||
6004 | |||
6005 | \b \cw{execute_move()} sets up the field to reflect the move it just | ||
6006 | performed. | ||
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 | ||
6010 | looking at the last-move field in \c{newstate}; but if \c{dir} is | ||
6011 | negative, it has to look at the last-move field in \c{oldstate}, and | ||
6012 | invert whatever move it finds there. | ||
6013 | |||
6014 | Note also that Sixteen needs to store the \e{direction} of the move, | ||
6015 | because you can't quite determine it by examining the row or column | ||
6016 | in question. You can in almost all cases, but when the row is | ||
6017 | precisely two squares long it doesn't work since a move in either | ||
6018 | direction looks the same. (You could argue that since moving a | ||
6019 | 2-element row left and right has the same effect, it doesn't matter | ||
6020 | which one you animate; but in fact it's very disorienting to click | ||
6021 | the arrow left and find the row moving right, and almost as bad to | ||
6022 | undo a move to the right and find the game animating \e{another} | ||
6023 | move to the right.) | ||
6024 | |||
6025 | \S{writing-conditional-anim} Animating drag operations | ||
6026 | |||
6027 | In Untangle, moves are made by dragging a node from an old position | ||
6028 | to a new position. Therefore, at the time when the move is initially | ||
6029 | made, it should not be animated, because the node has already been | ||
6030 | dragged to the right place and doesn't need moving there. However, | ||
6031 | it's nice to animate the same move if it's later undone or redone. | ||
6032 | This requires a bit of fiddling. | ||
6033 | |||
6034 | The obvious approach is to have a flag in the \c{game_ui} which | ||
6035 | inhibits move animation, and to set that flag in | ||
6036 | \cw{interpret_move()}. The question is, when would the flag be reset | ||
6037 | again? The obvious place to do so is \cw{changed_state()}, which | ||
6038 | will 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()} | ||
6040 | will never see the flag set at all. | ||
6041 | |||
6042 | The 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 | ||
6060 | to 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 | ||
6064 | only if the \q{current} flag is \e{not} set. | ||
6065 | |||
6066 | \S{writing-cheating} Inhibiting the victory flash when Solve is used | ||
6067 | |||
6068 | Many games flash when you complete them, as a visual congratulation | ||
6069 | for having got to the end of the puzzle. It often seems like a good | ||
6070 | idea to disable that flash when the puzzle is brought to a solved | ||
6071 | state by means of the Solve operation. | ||
6072 | |||
6073 | This 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 | ||
6080 | identifies the move as a solve operation. | ||
6081 | |||
6082 | \b Have \cw{execute_move()} respond to that clear identification by | ||
6083 | setting the \q{cheated} flag in the returned \c{game_state}. The | ||
6084 | flag will then be propagated to all subsequent game states, even if | ||
6085 | the 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 | ||
6088 | completed 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 | |||
6093 | Puzzle implementations written in this framework are self-testing as | ||
6094 | far as I could make them. | ||
6095 | |||
6096 | Textual game and move descriptions, for example, are generated and | ||
6097 | parsed as part of the normal process of play. Therefore, if you can | ||
6098 | make moves in the game \e{at all} you can be reasonably confident | ||
6099 | that the mid-end serialisation interface will function correctly and | ||
6100 | you will be able to save your game. (By contrast, if I'd stuck with | ||
6101 | a single \cw{make_move()} function performing the jobs of both | ||
6102 | \cw{interpret_move()} and \cw{execute_move()}, and had separate | ||
6103 | functions to encode and decode a game state in string form, then | ||
6104 | those functions would not be used during normal play; so they could | ||
6105 | have been completely broken, and you'd never know it until you tried | ||
6106 | to save the game \dash which would have meant you'd have to test | ||
6107 | game saving \e{extensively} and make sure to test every possible | ||
6108 | type of game state. As an added bonus, doing it the way I did leads | ||
6109 | to smaller save files.) | ||
6110 | |||
6111 | There 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 | ||
6114 | decode functions; but if there is anything in there, you do need to | ||
6115 | test game loading and saving to ensure those functions work | ||
6116 | properly. | ||
6117 | |||
6118 | It's also worth testing undo and redo of all operations, to ensure | ||
6119 | that the redraw and the animations (if any) work properly. Failing | ||
6120 | to animate undo properly seems to be a common error. | ||
6121 | |||
6122 | Other 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 | */ | ||
45 | extern void js_init_puzzle(void); | ||
46 | extern void js_post_init(void); | ||
47 | extern void js_debug(const char *); | ||
48 | extern void js_error_box(const char *message); | ||
49 | extern void js_remove_type_dropdown(void); | ||
50 | extern void js_remove_solve_button(void); | ||
51 | extern void js_add_preset(int menuid, const char *name, int value); | ||
52 | extern int js_add_preset_submenu(int menuid, const char *name); | ||
53 | extern int js_get_selected_preset(void); | ||
54 | extern void js_select_preset(int n); | ||
55 | extern void js_default_colour(float *output); | ||
56 | extern void js_set_colour(int colour_number, const char *colour_string); | ||
57 | extern void js_get_date_64(unsigned *p); | ||
58 | extern void js_update_permalinks(const char *desc, const char *seed); | ||
59 | extern void js_enable_undo_redo(bool undo, bool redo); | ||
60 | extern void js_update_key_labels(const char *lsk, const char *csk); | ||
61 | extern void js_activate_timer(void); | ||
62 | extern void js_deactivate_timer(void); | ||
63 | extern void js_canvas_start_draw(void); | ||
64 | extern void js_canvas_draw_update(int x, int y, int w, int h); | ||
65 | extern void js_canvas_end_draw(void); | ||
66 | extern void js_canvas_draw_rect(int x, int y, int w, int h, int colour); | ||
67 | extern void js_canvas_clip_rect(int x, int y, int w, int h); | ||
68 | extern void js_canvas_unclip(void); | ||
69 | extern void js_canvas_draw_line(float x1, float y1, float x2, float y2, | ||
70 | int width, int colour); | ||
71 | extern void js_canvas_draw_poly(const int *points, int npoints, | ||
72 | int fillcolour, int outlinecolour); | ||
73 | extern void js_canvas_draw_circle(int x, int y, int r, | ||
74 | int fillcolour, int outlinecolour); | ||
75 | extern int js_canvas_find_font_midpoint(int height, bool monospaced); | ||
76 | extern void js_canvas_draw_text(int x, int y, int halign, | ||
77 | int colour, int height, | ||
78 | bool monospaced, const char *text); | ||
79 | extern int js_canvas_new_blitter(int w, int h); | ||
80 | extern void js_canvas_free_blitter(int id); | ||
81 | extern void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h); | ||
82 | extern void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h); | ||
83 | extern void js_canvas_remove_statusbar(void); | ||
84 | extern void js_canvas_set_statusbar(const char *text); | ||
85 | extern bool js_canvas_get_preferred_size(int *wp, int *hp); | ||
86 | extern void js_canvas_set_size(int w, int h); | ||
87 | extern double js_get_device_pixel_ratio(void); | ||
88 | |||
89 | extern void js_dialog_init(const char *title); | ||
90 | extern void js_dialog_string(int i, const char *title, const char *initvalue); | ||
91 | extern void js_dialog_choices(int i, const char *title, const char *choicelist, | ||
92 | int initvalue); | ||
93 | extern void js_dialog_boolean(int i, const char *title, bool initvalue); | ||
94 | extern void js_dialog_launch(void); | ||
95 | extern void js_dialog_cleanup(void); | ||
96 | extern void js_focus_canvas(void); | ||
97 | |||
98 | extern bool js_savefile_read(void *buf, int len); | ||
99 | |||
100 | extern void js_save_prefs(const char *); | ||
101 | extern 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 | */ | ||
107 | bool mouseup(int x, int y, int button); | ||
108 | bool mousedown(int x, int y, int button); | ||
109 | bool mousemove(int x, int y, int buttons); | ||
110 | bool key(int keycode, const char *key, const char *chr, int location, | ||
111 | bool shift, bool ctrl); | ||
112 | void timer_callback(double tplus); | ||
113 | void command(int n); | ||
114 | char *get_text_format(void); | ||
115 | void free_save_file(char *buffer); | ||
116 | char *get_save_file(void); | ||
117 | void free_save_file(char *buffer); | ||
118 | void load_game(void); | ||
119 | void dlg_return_sval(int index, const char *val); | ||
120 | void dlg_return_ival(int index, int val); | ||
121 | void resize_puzzle(int w, int h); | ||
122 | void restore_puzzle_size(int w, int h); | ||
123 | void rescale_puzzle(void); | ||
124 | |||
125 | /* | ||
126 | * Internal forward references. | ||
127 | */ | ||
128 | static 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 | */ | ||
134 | void 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 | */ | ||
146 | void 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 | ||
161 | void 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 | */ | ||
176 | static 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 | */ | ||
186 | static midend *me; | ||
187 | |||
188 | /* ---------------------------------------------------------------------- | ||
189 | * Timing functions. | ||
190 | */ | ||
191 | static bool timer_active = false; | ||
192 | void deactivate_timer(frontend *fe) | ||
193 | { | ||
194 | js_deactivate_timer(); | ||
195 | timer_active = false; | ||
196 | } | ||
197 | void activate_timer(frontend *fe) | ||
198 | { | ||
199 | if (!timer_active) { | ||
200 | js_activate_timer(); | ||
201 | timer_active = true; | ||
202 | } | ||
203 | } | ||
204 | void 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 | */ | ||
214 | static 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 | */ | ||
220 | static 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 */ | ||
233 | void rescale_puzzle(void) | ||
234 | { | ||
235 | resize(); | ||
236 | midend_force_redraw(me); | ||
237 | } | ||
238 | |||
239 | /* Called from JS when the user uses the resize handle */ | ||
240 | void 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 */ | ||
252 | void 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 | */ | ||
263 | void 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 | */ | ||
274 | static 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 | */ | ||
284 | bool 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 | |||
295 | bool 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 | |||
306 | bool 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 | */ | ||
321 | bool 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 | */ | ||
435 | static 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 | */ | ||
449 | static 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 | */ | ||
459 | static void js_start_draw(void *handle) | ||
460 | { | ||
461 | js_canvas_start_draw(); | ||
462 | } | ||
463 | |||
464 | static 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 | |||
469 | static void js_unclip(void *handle) | ||
470 | { | ||
471 | js_canvas_unclip(); | ||
472 | } | ||
473 | |||
474 | static 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 | |||
494 | static 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 | |||
499 | static 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 | |||
505 | static 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 | |||
512 | static 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 | |||
518 | static 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 | |||
524 | struct blitter { | ||
525 | int id; /* allocated on the js side */ | ||
526 | int w, h; /* easier to retain here */ | ||
527 | }; | ||
528 | |||
529 | static 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 | |||
538 | static void js_blitter_free(void *handle, blitter *bl) | ||
539 | { | ||
540 | js_canvas_free_blitter(bl->id); | ||
541 | sfree(bl); | ||
542 | } | ||
543 | |||
544 | static 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 | |||
572 | static 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 | |||
580 | static 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 | |||
588 | static 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 | |||
595 | static void js_end_draw(void *handle) | ||
596 | { | ||
597 | js_canvas_end_draw(); | ||
598 | } | ||
599 | |||
600 | static void js_status_bar(void *handle, const char *text) | ||
601 | { | ||
602 | js_canvas_set_statusbar(text); | ||
603 | } | ||
604 | |||
605 | static 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 | |||
611 | static 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 | */ | ||
636 | static game_params **presets; | ||
637 | static int npresets; | ||
638 | static bool have_presets_dropdown; | ||
639 | |||
640 | static 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 | |||
655 | static 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 | |||
663 | static config_item *cfg = NULL; | ||
664 | static 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 | */ | ||
670 | static 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 | */ | ||
703 | void 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 | } | ||
715 | void 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 | */ | ||
735 | static 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 | */ | ||
793 | void 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 | |||
871 | char *get_text_format(void) | ||
872 | { | ||
873 | return midend_text_format(me); | ||
874 | } | ||
875 | |||
876 | void 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 | |||
886 | struct savefile_write_ctx { | ||
887 | char *buffer; | ||
888 | size_t pos; | ||
889 | }; | ||
890 | |||
891 | static 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 | |||
899 | char *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 | |||
923 | void free_save_file(char *buffer) | ||
924 | { | ||
925 | sfree(buffer); | ||
926 | } | ||
927 | |||
928 | static bool savefile_read(void *vctx, void *buf, int len) | ||
929 | { | ||
930 | return js_savefile_read(buf, len); | ||
931 | } | ||
932 | |||
933 | void 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 | |||
959 | static 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 | |||
983 | struct prefs_read_ctx { | ||
984 | const char *buffer; | ||
985 | size_t pos, len; | ||
986 | }; | ||
987 | |||
988 | static 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 | |||
1001 | void 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 | */ | ||
1020 | int 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 | ||
4 | included in the Emscripten builds of Puzzles. To get a list of | ||
5 | relevant source files, you can build Puzzles with "-gsource-map" and | ||
6 | then do something like: | ||
7 | |||
8 | \# jq -r '.sources[]' *.map | sort -u | ||
9 | |||
10 | \# This file is based on a build of Git commit | ||
11 | 2e48ce132e011e83517a9fc4905edcc8f9a5ef58 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 | ||
17 | LLVM 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 | |||
23 | The JavaScript and KaiOS versions of Puzzles incorporate some third | ||
24 | party 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 | ||
38 | Emscripten LICENSE file or make no mention of a licence. LICENSE | ||
39 | allows use under the MIT licence and specifies this copyright notice: | ||
40 | |||
41 | Copyright (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 | ||
75 | MIT licence. These parts don't carry any licence notice themselves. | ||
76 | This is the copyright notice from musl's COPYRIGHT file, modified to | ||
77 | allow for non-Unicode targets: | ||
78 | |||
79 | Copyright \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 | ||
84 | licence in its source file. | ||
85 | |||
86 | Copyright (C) 2011 by Valentin Ochs | ||
87 | |||
88 | } | ||
89 | |||
90 | Other 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 | |||
106 | Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. | ||
107 | |||
108 | Developed at SunPro, a Sun Microsystems, Inc. business. | ||
109 | Permission to use, copy, modify, and distribute this | ||
110 | software is freely granted, provided that this notice | ||
111 | is 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 | |||
121 | Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. | ||
122 | |||
123 | Developed at SunSoft, a Sun Microsystems, Inc. business. | ||
124 | Permission to use, copy, modify, and distribute this | ||
125 | software is freely granted, provided that this notice | ||
126 | is 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 | ||
52 | extern int HF_ITER(unsigned char **, size_t *); | ||
53 | #endif | ||
54 | |||
55 | /* This function is expected by libFuzzer. */ | ||
56 | |||
57 | int LLVMFuzzerTestOneInput(unsigned char *data, size_t size); | ||
58 | |||
59 | static 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) | ||
99 | static 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 | |||
107 | struct memread { | ||
108 | const unsigned char *buf; | ||
109 | size_t pos; | ||
110 | size_t len; | ||
111 | }; | ||
112 | |||
113 | static 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 | |||
123 | static void mem_rewind(void *wctx) | ||
124 | { | ||
125 | struct memread *ctx = wctx; | ||
126 | |||
127 | ctx->pos = 0; | ||
128 | } | ||
129 | |||
130 | static void null_write(void *wctx, const void *buf, int len) | ||
131 | { | ||
132 | } | ||
133 | |||
134 | int 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) | ||
145 | static 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 | */ | ||
168 | int 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 | */ | ||
195 | int 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 | */ | ||
212 | static 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 | |||
221 | static void savefile_rewind(void *wctx) | ||
222 | { | ||
223 | FILE *fp = (FILE *)wctx; | ||
224 | |||
225 | rewind(fp); | ||
226 | } | ||
227 | |||
228 | int 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 | ||
96 | static FILE *debug_fp = NULL; | ||
97 | |||
98 | static 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 | |||
112 | void 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 | |||
128 | void 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 | |||
146 | static void changed_preset(frontend *fe); | ||
147 | static void load_prefs(frontend *fe); | ||
148 | static char *save_prefs(frontend *fe); | ||
149 | |||
150 | struct 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 | */ | ||
164 | struct 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 | */ | ||
179 | struct 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 | |||
285 | struct 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 | |||
294 | void 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 | |||
302 | void 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 | |||
332 | static 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 | |||
351 | static 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 | |||
361 | static 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 | |||
381 | static void snaffle_colours(frontend *fe) | ||
382 | { | ||
383 | fe->colours = midend_colours(fe->me, &fe->ncolours); | ||
384 | } | ||
385 | |||
386 | static 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 | |||
394 | static 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 | |||
405 | static 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 | |||
474 | static PangoLayout *make_pango_layout(frontend *fe) | ||
475 | { | ||
476 | return (pango_cairo_create_layout(fe->cr)); | ||
477 | } | ||
478 | |||
479 | static 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 | |||
486 | static void save_screenshot_png(frontend *fe, const char *screenshot_file) | ||
487 | { | ||
488 | cairo_surface_write_to_png(fe->image, screenshot_file); | ||
489 | } | ||
490 | |||
491 | static 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 | |||
537 | static void do_draw_fill(frontend *fe) | ||
538 | { | ||
539 | cairo_fill(fe->cr); | ||
540 | } | ||
541 | |||
542 | static void do_draw_fill_preserve(frontend *fe) | ||
543 | { | ||
544 | cairo_fill_preserve(fe->cr); | ||
545 | } | ||
546 | |||
547 | static 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 | |||
555 | static 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 | |||
567 | static 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 | |||
574 | static void do_unclip(frontend *fe) | ||
575 | { | ||
576 | cairo_reset_clip(fe->cr); | ||
577 | } | ||
578 | |||
579 | static 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 | |||
589 | static 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 | |||
597 | static 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 | |||
609 | static 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 | |||
627 | static 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 | |||
642 | static void setup_blitter(blitter *bl, int w, int h) | ||
643 | { | ||
644 | bl->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h); | ||
645 | } | ||
646 | |||
647 | static void teardown_blitter(blitter *bl) | ||
648 | { | ||
649 | cairo_surface_destroy(bl->image); | ||
650 | } | ||
651 | |||
652 | static 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 | |||
661 | static 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 | |||
667 | static void clear_backing_store(frontend *fe) | ||
668 | { | ||
669 | fe->image = NULL; | ||
670 | } | ||
671 | |||
672 | static 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 | |||
681 | static 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 | |||
721 | static bool backing_store_ok(frontend *fe) | ||
722 | { | ||
723 | return fe->image != NULL; | ||
724 | } | ||
725 | |||
726 | static 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 | |||
743 | static void setup_drawing(frontend *fe) | ||
744 | { | ||
745 | fe->gc = gdk_gc_new(fe->area->window); | ||
746 | } | ||
747 | |||
748 | static void teardown_drawing(frontend *fe) | ||
749 | { | ||
750 | gdk_gc_unref(fe->gc); | ||
751 | fe->gc = NULL; | ||
752 | } | ||
753 | |||
754 | static 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 | |||
782 | static 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 | |||
789 | static 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 | ||
795 | static PangoLayout *make_pango_layout(frontend *fe) | ||
796 | { | ||
797 | return (pango_layout_new(gtk_widget_get_pango_context(fe->area))); | ||
798 | } | ||
799 | |||
800 | static 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 | |||
807 | static 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 | |||
819 | static 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 | |||
830 | static 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 | |||
841 | static 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 | |||
846 | static 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 | |||
851 | static 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 | |||
870 | static 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 | |||
902 | static 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 | |||
919 | static 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 | |||
929 | static void teardown_blitter(blitter *bl) | ||
930 | { | ||
931 | if (bl->pixmap) | ||
932 | gdk_pixmap_unref(bl->pixmap); | ||
933 | } | ||
934 | |||
935 | static 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 | |||
945 | static 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 | |||
954 | static void clear_backing_store(frontend *fe) | ||
955 | { | ||
956 | fe->pixmap = NULL; | ||
957 | } | ||
958 | |||
959 | static 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 | |||
977 | static int backing_store_ok(frontend *fe) | ||
978 | { | ||
979 | return (!!fe->pixmap); | ||
980 | } | ||
981 | |||
982 | static 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 | ||
991 | static 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 | |||
1034 | static 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 | |||
1075 | static 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 | |||
1114 | static 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 | |||
1125 | static 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 | |||
1167 | static 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 | |||
1177 | static 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 | |||
1183 | static void gtk_unclip(void *handle) | ||
1184 | { | ||
1185 | frontend *fe = (frontend *)handle; | ||
1186 | do_unclip(fe); | ||
1187 | } | ||
1188 | |||
1189 | static 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 | |||
1223 | static 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 | |||
1230 | static 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 | |||
1238 | static 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 | |||
1247 | static 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 | |||
1254 | static 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 | |||
1261 | static 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 | |||
1270 | static void gtk_blitter_free(void *handle, blitter *bl) | ||
1271 | { | ||
1272 | teardown_blitter(bl); | ||
1273 | sfree(bl); | ||
1274 | } | ||
1275 | |||
1276 | static 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 | |||
1284 | static 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 | |||
1294 | static 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 | |||
1303 | static 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 | ||
1327 | static 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 | ||
1339 | static 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 | |||
1345 | static void gtk_begin_page(void *handle, int number) | ||
1346 | { | ||
1347 | } | ||
1348 | |||
1349 | static 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 | |||
1388 | static void gtk_end_puzzle(void *handle) | ||
1389 | { | ||
1390 | frontend *fe = (frontend *)handle; | ||
1391 | cairo_restore(fe->cr); | ||
1392 | } | ||
1393 | |||
1394 | static void gtk_end_page(void *handle, int number) | ||
1395 | { | ||
1396 | } | ||
1397 | |||
1398 | static void gtk_end_doc(void *handle) | ||
1399 | { | ||
1400 | } | ||
1401 | |||
1402 | static void gtk_line_width(void *handle, float width) | ||
1403 | { | ||
1404 | frontend *fe = (frontend *)handle; | ||
1405 | cairo_set_line_width(fe->cr, width); | ||
1406 | } | ||
1407 | |||
1408 | static 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 | |||
1421 | static 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 | ||
1430 | static const struct internal_drawing_api internal_printing = { | ||
1431 | print_set_colour, | ||
1432 | do_print_fill, | ||
1433 | do_print_fill_preserve, | ||
1434 | }; | ||
1435 | #endif | ||
1436 | |||
1437 | static 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 | |||
1478 | static 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 | |||
1486 | static 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 | |||
1561 | static 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 | |||
1596 | static 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) | ||
1627 | static 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 | ||
1651 | static 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 | |||
1674 | static 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 | |||
1689 | static 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 | |||
1717 | static 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) | ||
1730 | static 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 | |||
1743 | static 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 | |||
1760 | void 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 | |||
1769 | void 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 | |||
1780 | static void window_destroy(GtkWidget *widget, gpointer data) | ||
1781 | { | ||
1782 | gtk_main_quit(); | ||
1783 | } | ||
1784 | |||
1785 | static 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 | |||
1800 | enum { MB_OK, MB_YESNO }; | ||
1801 | |||
1802 | static 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) | ||
1820 | static 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) */ | ||
1838 | static 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 | |||
1850 | bool 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 | |||
1915 | static void error_box(GtkWidget *parent, const char *msg) | ||
1916 | { | ||
1917 | message_box(parent, "Error", msg, false, MB_OK); | ||
1918 | } | ||
1919 | |||
1920 | static 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 | |||
1944 | static void config_cancel_button_clicked(GtkButton *button, gpointer data) | ||
1945 | { | ||
1946 | frontend *fe = (frontend *)data; | ||
1947 | |||
1948 | gtk_widget_destroy(fe->cfgbox); | ||
1949 | } | ||
1950 | |||
1951 | static 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 | |||
1973 | static 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 | |||
1982 | static 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 | |||
1990 | static 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 | |||
1998 | static 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 | |||
2213 | static 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 | |||
2222 | static 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 | */ | ||
2254 | static 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) | ||
2293 | static 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 | |||
2317 | static 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 | |||
2343 | static 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) | ||
2360 | static 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 | |||
2377 | static 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 | |||
2399 | static 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 | |||
2417 | static GdkAtom compound_text_atom, utf8_string_atom; | ||
2418 | static bool paste_initialised = false; | ||
2419 | |||
2420 | static 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 | |||
2444 | static 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 | |||
2456 | static 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 | |||
2464 | static 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 | |||
2476 | static 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 | |||
2492 | static 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 | |||
2504 | static 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 | |||
2535 | static 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 | ||
2563 | static 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 | |||
2704 | static 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 | |||
2728 | static 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 | |||
2782 | static 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 | |||
2790 | static 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 | |||
2801 | static 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 | |||
2875 | struct savefile_write_ctx { | ||
2876 | FILE *fp; | ||
2877 | int error; | ||
2878 | }; | ||
2879 | |||
2880 | static 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 | |||
2887 | static 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 | |||
2896 | static 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 | |||
2944 | static 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 | |||
2976 | static 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 | |||
2996 | static 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 | |||
3010 | static char *prefs_path(const game *game) | ||
3011 | { | ||
3012 | return prefs_path_general(game, ".conf"); | ||
3013 | } | ||
3014 | |||
3015 | static char *prefs_tmp_path(const game *game) | ||
3016 | { | ||
3017 | return prefs_path_general(game, ".conf.tmp"); | ||
3018 | } | ||
3019 | |||
3020 | static 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 | |||
3037 | static 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 | |||
3112 | static 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 | ||
3177 | static void menu_print_event(GtkMenuItem *menuitem, gpointer data) | ||
3178 | { | ||
3179 | frontend *fe = (frontend *)data; | ||
3180 | |||
3181 | print_dialog(fe); | ||
3182 | } | ||
3183 | #endif | ||
3184 | |||
3185 | static 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 | |||
3196 | static void menu_restart_event(GtkMenuItem *menuitem, gpointer data) | ||
3197 | { | ||
3198 | frontend *fe = (frontend *)data; | ||
3199 | |||
3200 | midend_restart_game(fe->me); | ||
3201 | } | ||
3202 | |||
3203 | static 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 | |||
3228 | static 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 | |||
3237 | static 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 | |||
3286 | static void menu_help_contents_event(GtkMenuItem *menuitem, gpointer data) | ||
3287 | { | ||
3288 | show_help((frontend *)data, "index"); | ||
3289 | } | ||
3290 | |||
3291 | static void menu_help_specific_event(GtkMenuItem *menuitem, gpointer data) | ||
3292 | { | ||
3293 | show_help((frontend *)data, thegame.htmlhelp_topic); | ||
3294 | } | ||
3295 | |||
3296 | static 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 | |||
3337 | static 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 | |||
3369 | static 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 | |||
3376 | static 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 | |||
3406 | enum { ARG_EITHER, ARG_SAVE, ARG_ID }; /* for argtype */ | ||
3407 | |||
3408 | static 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, >k_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(>k_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 | |||
3915 | static 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 | |||
3931 | int 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, | ||
12 | const game *gamelist[] = { | ||
13 | #include "generated-games.h" | ||
14 | }; | ||
15 | #undef GAME | ||
16 | |||
17 | const 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 | */ | ||
16 | void *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 | */ | ||
31 | void sfree(void *p) { | ||
32 | if (p) { | ||
33 | free(p); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | /* | ||
38 | * srealloc should guaranteeably be able to realloc NULL | ||
39 | */ | ||
40 | void *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 | */ | ||
60 | char *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 | |||
17 | extern void _pause(); | ||
18 | extern int _call_java(int cmd, int arg1, int arg2, int arg3); | ||
19 | |||
20 | void 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 | |||
31 | struct 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 | |||
42 | static frontend *_fe; | ||
43 | |||
44 | void 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 | |||
52 | void frontend_default_colour(frontend *fe, float *output) | ||
53 | { | ||
54 | output[0] = output[1]= output[2] = 0.8f; | ||
55 | } | ||
56 | |||
57 | void nestedvm_status_bar(void *handle, const char *text) | ||
58 | { | ||
59 | _call_java(4,0,(int)text,0); | ||
60 | } | ||
61 | |||
62 | void 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 | |||
69 | void 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 | |||
76 | void nestedvm_unclip(void *handle) | ||
77 | { | ||
78 | frontend *fe = (frontend *)handle; | ||
79 | _call_java(4, 4, fe->ox, fe->oy); | ||
80 | } | ||
81 | |||
82 | void 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 | |||
91 | void 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 | |||
98 | void 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 | |||
106 | void 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 | |||
118 | void 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 | |||
126 | struct blitter { | ||
127 | int handle, w, h, x, y; | ||
128 | }; | ||
129 | |||
130 | blitter *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 | |||
139 | void 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 | |||
146 | void 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 | |||
156 | void 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 | |||
167 | void nestedvm_end_draw(void *handle) | ||
168 | { | ||
169 | _call_java(4,2,0,0); | ||
170 | } | ||
171 | |||
172 | char *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 | |||
182 | const 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 | |||
203 | int 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 | |||
214 | int 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 | |||
229 | int 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 | |||
244 | void deactivate_timer(frontend *fe) | ||
245 | { | ||
246 | if (fe->timer_active) | ||
247 | _call_java(4, 13, 0, 0); | ||
248 | fe->timer_active = false; | ||
249 | } | ||
250 | |||
251 | void 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 | |||
260 | void 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 | |||
274 | void 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 | |||
283 | void 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 | |||
289 | void 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 | |||
295 | static 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 | |||
323 | int 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 | |||
331 | int 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 | |||
339 | int 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 | |||
347 | int 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 | |||
355 | static 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 | |||
365 | int 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 | |||
378 | int 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 | |||
390 | int jcallback_restart_event() | ||
391 | { | ||
392 | frontend *fe = (frontend *)_fe; | ||
393 | |||
394 | midend_restart_game(fe->me); | ||
395 | return 0; | ||
396 | } | ||
397 | |||
398 | int 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 | |||
410 | int 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 | |||
424 | void 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 | |||
441 | int 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 | |||
9 | const char *const *const xpm_icons[] = { 0 }; | ||
10 | const 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 | |||
11 | void frontend_default_colour(frontend *fe, float *output) {} | ||
12 | void get_random_seed(void **randseed, int *randseedsize) | ||
13 | { char *c = snewn(1, char); *c = 0; *randseed = c; *randseedsize = 1; } | ||
14 | void deactivate_timer(frontend *fe) {} | ||
15 | void activate_timer(frontend *fe) {} | ||
16 | struct drawing { char dummy; }; | ||
17 | drawing *drawing_new(const drawing_api *api, midend *me, void *handle) | ||
18 | { return snew(drawing); } | ||
19 | void drawing_free(drawing *dr) { sfree(dr); } | ||
20 | void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize, | ||
21 | int align, int colour, const char *text) {} | ||
22 | void draw_rect(drawing *dr, int x, int y, int w, int h, int colour) {} | ||
23 | void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour) {} | ||
24 | void draw_thick_line(drawing *dr, float thickness, | ||
25 | float x1, float y1, float x2, float y2, int colour) {} | ||
26 | void draw_polygon(drawing *dr, const int *coords, int npoints, | ||
27 | int fillcolour, int outlinecolour) {} | ||
28 | void draw_circle(drawing *dr, int cx, int cy, int radius, | ||
29 | int fillcolour, int outlinecolour) {} | ||
30 | char *text_fallback(drawing *dr, const char *const *strings, int nstrings) | ||
31 | { return dupstr(strings[0]); } | ||
32 | void clip(drawing *dr, int x, int y, int w, int h) {} | ||
33 | void unclip(drawing *dr) {} | ||
34 | void start_draw(drawing *dr) {} | ||
35 | void draw_update(drawing *dr, int x, int y, int w, int h) {} | ||
36 | void end_draw(drawing *dr) {} | ||
37 | struct blitter { char dummy; }; | ||
38 | blitter *blitter_new(drawing *dr, int w, int h) { return snew(blitter); } | ||
39 | void blitter_free(drawing *dr, blitter *bl) { sfree(bl); } | ||
40 | void blitter_save(drawing *dr, blitter *bl, int x, int y) {} | ||
41 | void blitter_load(drawing *dr, blitter *bl, int x, int y) {} | ||
42 | int print_mono_colour(drawing *dr, int grey) { return 0; } | ||
43 | int print_grey_colour(drawing *dr, float grey) { return 0; } | ||
44 | int print_hatched_colour(drawing *dr, int hatch) { return 0; } | ||
45 | int print_rgb_mono_colour(drawing *dr, float r, float g, float b, int grey) | ||
46 | { return 0; } | ||
47 | int print_rgb_grey_colour(drawing *dr, float r, float g, float b, float grey) | ||
48 | { return 0; } | ||
49 | int print_rgb_hatched_colour(drawing *dr, float r, float g, float b, int hatch) | ||
50 | { return 0; } | ||
51 | void print_line_width(drawing *dr, int width) {} | ||
52 | void print_line_dotted(drawing *dr, bool dotted) {} | ||
53 | void status_bar(drawing *dr, const char *text) {} | ||
54 | void document_add_puzzle(document *doc, const game *game, game_params *par, | ||
55 | game_ui *ui, game_state *st, game_state *st2) {} | ||
56 | |||
57 | void 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 | ||
72 | void 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 | |||
27 | enum { | ||
28 | COL_BACKGROUND, | ||
29 | NCOLOURS | ||
30 | }; | ||
31 | |||
32 | struct game_params { | ||
33 | int FIXME; | ||
34 | }; | ||
35 | |||
36 | struct game_state { | ||
37 | int FIXME; | ||
38 | }; | ||
39 | |||
40 | static game_params *default_params(void) | ||
41 | { | ||
42 | game_params *ret = snew(game_params); | ||
43 | |||
44 | ret->FIXME = 0; | ||
45 | |||
46 | return ret; | ||
47 | } | ||
48 | |||
49 | static bool game_fetch_preset(int i, char **name, game_params **params) | ||
50 | { | ||
51 | return false; | ||
52 | } | ||
53 | |||
54 | static void free_params(game_params *params) | ||
55 | { | ||
56 | sfree(params); | ||
57 | } | ||
58 | |||
59 | static 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 | |||
66 | static void decode_params(game_params *params, char const *string) | ||
67 | { | ||
68 | } | ||
69 | |||
70 | static char *encode_params(const game_params *params, bool full) | ||
71 | { | ||
72 | return dupstr("FIXME"); | ||
73 | } | ||
74 | |||
75 | static const char *validate_params(const game_params *params, bool full) | ||
76 | { | ||
77 | return NULL; | ||
78 | } | ||
79 | |||
80 | static char *new_game_desc(const game_params *params, random_state *rs, | ||
81 | char **aux, bool interactive) | ||
82 | { | ||
83 | return dupstr("FIXME"); | ||
84 | } | ||
85 | |||
86 | static const char *validate_desc(const game_params *params, const char *desc) | ||
87 | { | ||
88 | return NULL; | ||
89 | } | ||
90 | |||
91 | static 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 | |||
101 | static 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 | |||
110 | static void free_game(game_state *state) | ||
111 | { | ||
112 | sfree(state); | ||
113 | } | ||
114 | |||
115 | static game_ui *new_ui(const game_state *state) | ||
116 | { | ||
117 | return NULL; | ||
118 | } | ||
119 | |||
120 | static void free_ui(game_ui *ui) | ||
121 | { | ||
122 | } | ||
123 | |||
124 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | ||
125 | const game_state *newstate) | ||
126 | { | ||
127 | } | ||
128 | |||
129 | struct game_drawstate { | ||
130 | int tilesize; | ||
131 | int FIXME; | ||
132 | }; | ||
133 | |||
134 | static 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 | |||
141 | static game_state *execute_move(const game_state *state, const char *move) | ||
142 | { | ||
143 | return NULL; | ||
144 | } | ||
145 | |||
146 | /* ---------------------------------------------------------------------- | ||
147 | * Drawing routines. | ||
148 | */ | ||
149 | |||
150 | static 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 | |||
156 | static void game_set_size(drawing *dr, game_drawstate *ds, | ||
157 | const game_params *params, int tilesize) | ||
158 | { | ||
159 | ds->tilesize = tilesize; | ||
160 | } | ||
161 | |||
162 | static 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 | |||
172 | static 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 | |||
182 | static void game_free_drawstate(drawing *dr, game_drawstate *ds) | ||
183 | { | ||
184 | sfree(ds); | ||
185 | } | ||
186 | |||
187 | static 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 | |||
194 | static 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 | |||
200 | static 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 | |||
206 | static 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 | |||
214 | static int game_status(const game_state *state) | ||
215 | { | ||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | #ifdef COMBINED | ||
220 | #define thegame nullgame | ||
221 | #endif | ||
222 | |||
223 | const 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"> | ||
6 | body \{ font-family: "Lucida Grande", Helvetica, Arial; font-size: 9pt \} | ||
7 | h1 \{ font-size: 12pt \} | ||
8 | h2 \{ font-size: 10pt \} | ||
9 | h3 \{ font-size: 9pt \} | ||
10 | h4 \{ font-size: 9pt \} | ||
11 | h5 \{ font-size: 9pt \} | ||
12 | h6 \{ 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 | |||
12 | struct 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 | |||
22 | static 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 | |||
31 | static 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 | |||
74 | static 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 | |||
92 | static void ps_setcolour(psdata *ps, int colour) | ||
93 | { | ||
94 | ps_setcolour_internal(ps, colour, ""); | ||
95 | } | ||
96 | |||
97 | static void ps_stroke(psdata *ps, int colour) | ||
98 | { | ||
99 | ps_setcolour_internal(ps, colour, " stroke"); | ||
100 | } | ||
101 | |||
102 | static 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 | |||
136 | static 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 | |||
149 | static 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 | |||
160 | static 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 | |||
182 | static 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 | |||
199 | static 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 | |||
208 | static 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 | |||
226 | static 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 | |||
233 | static 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 | |||
244 | static 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 | |||
287 | static 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 | |||
334 | static 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 | |||
342 | static 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 | |||
361 | static void ps_end_puzzle(void *handle) | ||
362 | { | ||
363 | psdata *ps = (psdata *)handle; | ||
364 | |||
365 | fputs("grestore\n", ps->fp); | ||
366 | } | ||
367 | |||
368 | static 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 | |||
375 | static void ps_end_doc(void *handle) | ||
376 | { | ||
377 | psdata *ps = (psdata *)handle; | ||
378 | |||
379 | fputs("%%EOF\n", ps->fp); | ||
380 | } | ||
381 | |||
382 | static 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 | |||
409 | psdata *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 | |||
423 | void ps_free(psdata *ps) | ||
424 | { | ||
425 | drawing_free(ps->drawing); | ||
426 | sfree(ps); | ||
427 | } | ||
428 | |||
429 | drawing *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 | ||
54 | typedef HWND (CALLBACK *htmlhelp_t)(HWND, LPCSTR, UINT, DWORD); | ||
55 | static htmlhelp_t htmlhelp; | ||
56 | static HINSTANCE hh_dll; | ||
57 | #endif /* NO_HTMLHELP */ | ||
58 | enum { NONE, HLP, CHM } help_type; | ||
59 | char *help_path; | ||
60 | bool 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 | ||
77 | static FILE *debug_fp = NULL; | ||
78 | static HANDLE debug_hdl = INVALID_HANDLE_VALUE; | ||
79 | static int debug_got_console = 0; | ||
80 | |||
81 | static 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 | |||
105 | void 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 | |||
126 | static void new_game_size(frontend *fe, float scale); | ||
127 | static void load_prefs(midend *me); | ||
128 | static char *save_prefs(midend *me); | ||
129 | |||
130 | struct font { | ||
131 | HFONT font; | ||
132 | int type; | ||
133 | int size; | ||
134 | }; | ||
135 | |||
136 | struct cfg_aux { | ||
137 | int ctlid; | ||
138 | }; | ||
139 | |||
140 | struct blitter { | ||
141 | HBITMAP bitmap; | ||
142 | frontend *fe; | ||
143 | int x, y, w, h; | ||
144 | }; | ||
145 | |||
146 | enum { CFG_PRINT = CFG_FRONTEND_SPECIFIC }; | ||
147 | |||
148 | struct preset_menuitemref { | ||
149 | HMENU which_menu; | ||
150 | int item_index; | ||
151 | }; | ||
152 | |||
153 | struct 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 | |||
195 | void 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 | |||
207 | static void update_type_menu_tick(frontend *fe); | ||
208 | static void update_copy_menu_greying(frontend *fe); | ||
209 | |||
210 | void 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 | |||
224 | char *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 | |||
246 | void 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 | |||
256 | static void win_status_bar(void *handle, const char *text) | ||
257 | { | ||
258 | frontend *fe = (frontend *)handle; | ||
259 | |||
260 | SetWindowText(fe->statusbar, text); | ||
261 | } | ||
262 | |||
263 | static 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 | |||
275 | static void win_blitter_free(void *handle, blitter *bl) | ||
276 | { | ||
277 | if (bl->bitmap) DeleteObject(bl->bitmap); | ||
278 | sfree(bl); | ||
279 | } | ||
280 | |||
281 | static 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 | |||
290 | static 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 | |||
319 | static 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 | |||
345 | void 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 | |||
354 | static 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 | |||
371 | static 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 | |||
391 | static 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 | |||
418 | static 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 | |||
429 | static 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 | |||
455 | static 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 | |||
466 | static 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 | |||
479 | static 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 | |||
489 | static 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 | |||
569 | static 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 | |||
596 | static 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 | |||
613 | static 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 | |||
637 | static 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 | |||
671 | static 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 | |||
687 | static 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 | |||
704 | static 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 | |||
717 | static 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 | |||
728 | static 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 | |||
739 | static 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 | |||
764 | static 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 | |||
781 | static 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 | |||
832 | static void win_end_puzzle(void *handle) | ||
833 | { | ||
834 | /* Nothing needs to be done here. */ | ||
835 | } | ||
836 | |||
837 | static 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 | |||
855 | static 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 | |||
888 | char *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 | |||
897 | const 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 | |||
924 | void 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 | |||
1019 | void 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 | |||
1027 | void 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 | |||
1037 | void 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 | */ | ||
1092 | static 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 | */ | ||
1162 | static 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 | */ | ||
1207 | static 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 | */ | ||
1231 | static 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 | |||
1238 | static 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 | |||
1251 | static 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 | |||
1262 | static 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 | |||
1281 | static 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 | |||
1325 | static 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 | |||
1341 | static 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 | */ | ||
1368 | static 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 | |||
1420 | static void savefile_write(void *wctx, const void *buf, int len) | ||
1421 | { | ||
1422 | FILE *fp = (FILE *)wctx; | ||
1423 | fwrite(buf, 1, len, fp); | ||
1424 | } | ||
1425 | |||
1426 | static 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 | */ | ||
1444 | static 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 | |||
1536 | static 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 | */ | ||
1577 | static 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 | |||
1765 | static 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 | |||
1776 | static 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 | */ | ||
1802 | static 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 | |||
1866 | static 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 | |||
1891 | static 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 | |||
1959 | HWND 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 | |||
1971 | static 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 | |||
2123 | static 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 | |||
2366 | static 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 | |||
2375 | static 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 | |||
2387 | static 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 | |||
2435 | static 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 | |||
2479 | static 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 | |||
2507 | static 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 | |||
2519 | static 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 | |||
2533 | static char *prefs_path(const game *game) | ||
2534 | { | ||
2535 | return prefs_path_general(game, ".conf"); | ||
2536 | } | ||
2537 | |||
2538 | static char *prefs_tmp_path(const game *game) | ||
2539 | { | ||
2540 | return prefs_path_general(game, ".tmp"); | ||
2541 | } | ||
2542 | |||
2543 | static 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 | |||
2560 | static 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 | |||
2639 | static 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 | |||
2646 | static 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 | |||
2654 | static 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 | |||
2667 | static 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 | */ | ||
3161 | void 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 | |||
3363 | int 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: */ | ||