added support for amoeba settings in BD engine to level editor
[rocksndiamonds.git] / src / screens.c
1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // screens.c
10 // ============================================================================
11
12 #include "libgame/libgame.h"
13
14 #include "screens.h"
15 #include "events.h"
16 #include "game.h"
17 #include "tools.h"
18 #include "editor.h"
19 #include "files.h"
20 #include "tape.h"
21 #include "anim.h"
22 #include "network.h"
23 #include "init.h"
24 #include "config.h"
25 #include "api.h"
26
27
28 #define DEBUG_JOYSTICKS                         0
29
30
31 // screens on the info screen
32 #define INFO_MODE_MAIN                          0
33 #define INFO_MODE_TITLE                         1
34 #define INFO_MODE_ELEMENTS                      2
35 #define INFO_MODE_MUSIC                         3
36 #define INFO_MODE_CREDITS                       4
37 #define INFO_MODE_PROGRAM                       5
38 #define INFO_MODE_VERSION                       6
39 #define INFO_MODE_LEVELSET                      7
40
41 #define MAX_INFO_MODES                          8
42
43 // screens on the setup screen
44 // (must match GFX_SPECIAL_ARG_SETUP_* values as defined in src/main.h)
45 // (should also match corresponding entries in src/conf_gfx.c)
46 #define SETUP_MODE_MAIN                         0
47 #define SETUP_MODE_GAME                         1
48 #define SETUP_MODE_ENGINES                      2
49 #define SETUP_MODE_EDITOR                       3
50 #define SETUP_MODE_GRAPHICS                     4
51 #define SETUP_MODE_SOUND                        5
52 #define SETUP_MODE_ARTWORK                      6
53 #define SETUP_MODE_INPUT                        7
54 #define SETUP_MODE_TOUCH                        8
55 #define SETUP_MODE_SHORTCUTS                    9
56 #define SETUP_MODE_SHORTCUTS_1                  10
57 #define SETUP_MODE_SHORTCUTS_2                  11
58 #define SETUP_MODE_SHORTCUTS_3                  12
59 #define SETUP_MODE_SHORTCUTS_4                  13
60 #define SETUP_MODE_SHORTCUTS_5                  14
61
62 // sub-screens on the setup screen (generic)
63 #define SETUP_MODE_CHOOSE_ARTWORK               15
64 #define SETUP_MODE_CHOOSE_OTHER                 16
65
66 // sub-screens on the setup screen (specific)
67 #define SETUP_MODE_CHOOSE_SCORES_TYPE           17
68 #define SETUP_MODE_CHOOSE_GAME_SPEED            18
69 #define SETUP_MODE_CHOOSE_SCROLL_DELAY          19
70 #define SETUP_MODE_CHOOSE_SNAPSHOT_MODE         20
71 #define SETUP_MODE_CHOOSE_WINDOW_SIZE           21
72 #define SETUP_MODE_CHOOSE_SCALING_TYPE          22
73 #define SETUP_MODE_CHOOSE_RENDERING             23
74 #define SETUP_MODE_CHOOSE_VSYNC                 24
75 #define SETUP_MODE_CHOOSE_GRAPHICS              25
76 #define SETUP_MODE_CHOOSE_SOUNDS                26
77 #define SETUP_MODE_CHOOSE_MUSIC                 27
78 #define SETUP_MODE_CHOOSE_VOLUME_SIMPLE         28
79 #define SETUP_MODE_CHOOSE_VOLUME_LOOPS          29
80 #define SETUP_MODE_CHOOSE_VOLUME_MUSIC          30
81 #define SETUP_MODE_CHOOSE_TOUCH_CONTROL         31
82 #define SETUP_MODE_CHOOSE_MOVE_DISTANCE         32
83 #define SETUP_MODE_CHOOSE_DROP_DISTANCE         33
84 #define SETUP_MODE_CHOOSE_TRANSPARENCY          34
85 #define SETUP_MODE_CHOOSE_GRID_XSIZE_0          35
86 #define SETUP_MODE_CHOOSE_GRID_YSIZE_0          36
87 #define SETUP_MODE_CHOOSE_GRID_XSIZE_1          37
88 #define SETUP_MODE_CHOOSE_GRID_YSIZE_1          38
89 #define SETUP_MODE_CONFIG_VIRT_BUTTONS          39
90
91 #define MAX_SETUP_MODES                         40
92
93 #define MAX_MENU_MODES                          MAX(MAX_INFO_MODES, MAX_SETUP_MODES)
94
95 // info screen titles
96 #define STR_INFO_MAIN                           "Info Screen"
97 #define STR_INFO_TITLE                          "Title Screen"
98 #define STR_INFO_ELEMENTS                       "Game Elements"
99 #define STR_INFO_MUSIC                          "Music Info"
100 #define STR_INFO_CREDITS                        "Credits"
101 #define STR_INFO_PROGRAM                        "Program Info"
102 #define STR_INFO_VERSION                        "Version Info"
103 #define STR_INFO_LEVELSET                       "Level Set Info"
104 #define STR_INFO_EXIT                           "Exit"
105
106 // setup screen titles
107 #define STR_SETUP_MAIN                          "Setup"
108 #define STR_SETUP_GAME                          "Game & Menu"
109 #define STR_SETUP_ENGINES                       "Game Engines"
110 #define STR_SETUP_EDITOR                        "Editor"
111 #define STR_SETUP_GRAPHICS                      "Graphics"
112 #define STR_SETUP_SOUND                         "Sound & Music"
113 #define STR_SETUP_ARTWORK                       "Custom Artwork"
114 #define STR_SETUP_INPUT                         "Input Devices"
115 #define STR_SETUP_TOUCH                         "Touch Controls"
116 #define STR_SETUP_SHORTCUTS                     "Key Shortcuts"
117 #define STR_SETUP_EXIT                          "Exit"
118 #define STR_SETUP_SAVE_AND_EXIT                 "Save and Exit"
119
120 #define STR_SETUP_CHOOSE_SCORES_TYPE            "Scores Type"
121 #define STR_SETUP_CHOOSE_GAME_SPEED             "Game Speed"
122 #define STR_SETUP_CHOOSE_SCROLL_DELAY           "Scroll Delay"
123 #define STR_SETUP_CHOOSE_SNAPSHOT_MODE          "Snapshot Mode"
124 #define STR_SETUP_CHOOSE_WINDOW_SIZE            "Window Scaling"
125 #define STR_SETUP_CHOOSE_SCALING_TYPE           "Anti-Aliasing"
126 #define STR_SETUP_CHOOSE_RENDERING              "Rendering Mode"
127 #define STR_SETUP_CHOOSE_VSYNC                  "VSync Mode"
128 #define STR_SETUP_CHOOSE_VOLUME_SIMPLE          "Sound Volume"
129 #define STR_SETUP_CHOOSE_VOLUME_LOOPS           "Loops Volume"
130 #define STR_SETUP_CHOOSE_VOLUME_MUSIC           "Music Volume"
131 #define STR_SETUP_CHOOSE_TOUCH_CONTROL          "Control Type"
132 #define STR_SETUP_CHOOSE_MOVE_DISTANCE          "Move Distance"
133 #define STR_SETUP_CHOOSE_DROP_DISTANCE          "Drop Distance"
134 #define STR_SETUP_CHOOSE_TRANSPARENCY           "Transparency"
135 #define STR_SETUP_CHOOSE_GRID_XSIZE_0           "Horiz. Buttons"
136 #define STR_SETUP_CHOOSE_GRID_YSIZE_0           "Vert. Buttons"
137 #define STR_SETUP_CHOOSE_GRID_XSIZE_1           "Horiz. Buttons"
138 #define STR_SETUP_CHOOSE_GRID_YSIZE_1           "Vert. Buttons"
139
140 // other screen text constants
141 #define STR_CHOOSE_TREE_EDIT                    "Edit"
142 #define MENU_CHOOSE_TREE_FONT(x)                (FONT_TEXT_1 + (x))
143 #define MENU_CHOOSE_TREE_COLOR(ti, a)           TREE_COLOR(ti, a)
144
145 #define TEXT_MAIN_MENU                          "Press any key or button for main menu"
146 #define TEXT_INFO_MENU                          "Press any key or button for info menu"
147 #define TEXT_NEXT_PAGE                          "Press any key or button for next page"
148 #define TEXT_NEXT_MENU                          (info_screens_from_main ?               \
149                                                  TEXT_MAIN_MENU : TEXT_INFO_MENU)
150
151 // for input setup functions
152 #define SETUPINPUT_SCREEN_POS_START             0
153 #define SETUPINPUT_SCREEN_POS_EMPTY1            3
154 #define SETUPINPUT_SCREEN_POS_EMPTY2            12
155 #define SETUPINPUT_SCREEN_POS_END               13
156
157 #define MENU_SETUP_FONT_TITLE                   FONT_TEXT_1
158 #define MENU_SETUP_FONT_TEXT                    FONT_TITLE_2
159
160 #define MAX_SETUP_TEXT_INPUT_LEN                28
161
162 // for various menu stuff
163 #define MENU_SCREEN_START_XPOS                  1
164 #define MENU_SCREEN_START_YPOS                  2
165 #define MENU_SCREEN_VALUE_XPOS                  (SCR_FIELDX - 3)
166 #define MENU_SCREEN_TEXT2_XPOS                  (SCR_FIELDX - 2)
167 #define MENU_SCREEN_MAX_XPOS                    (SCR_FIELDX - 1)
168 #define MENU_TITLE1_YPOS                        8
169 #define MENU_TITLE2_YPOS                        46
170 #define MENU_INFO_FONT_TITLE                    FONT_TEXT_1
171 #define MENU_INFO_FONT_HEAD                     FONT_TEXT_2
172 #define MENU_INFO_FONT_TEXT                     FONT_TEXT_3
173 #define MENU_INFO_FONT_FOOT                     FONT_TEXT_4
174 #define MENU_INFO_SPACE_HEAD                    (menu.headline2_spacing_info[info_mode])
175 #define MENU_SCREEN_INFO_SPACE_LEFT             (menu.left_spacing_info[info_mode])
176 #define MENU_SCREEN_INFO_SPACE_MIDDLE           (menu.middle_spacing_info[info_mode])
177 #define MENU_SCREEN_INFO_SPACE_RIGHT            (menu.right_spacing_info[info_mode])
178 #define MENU_SCREEN_INFO_SPACE_TOP              (menu.top_spacing_info[info_mode])
179 #define MENU_SCREEN_INFO_SPACE_BOTTOM           (menu.bottom_spacing_info[info_mode])
180 #define MENU_SCREEN_INFO_SPACE_LINE             (menu.line_spacing_info[info_mode])
181 #define MENU_SCREEN_INFO_SPACE_EXTRA            (menu.extra_spacing_info[info_mode])
182 #define MENU_SCREEN_INFO_TILE_SIZE_RAW          (menu.tile_size_info[info_mode])
183 #define MENU_SCREEN_INFO_TILE_SIZE              (MENU_SCREEN_INFO_TILE_SIZE_RAW > 0 ?           \
184                                                  MENU_SCREEN_INFO_TILE_SIZE_RAW : TILEY)
185 #define MENU_SCREEN_INFO_ENTRY_SIZE_RAW         (menu.list_entry_size_info[info_mode])
186 #define MENU_SCREEN_INFO_ENTRY_SIZE             (MAX(MENU_SCREEN_INFO_ENTRY_SIZE_RAW,           \
187                                                      MENU_SCREEN_INFO_TILE_SIZE))
188 #define MENU_SCREEN_INFO_YSTART                 MENU_SCREEN_INFO_SPACE_TOP
189 #define MENU_SCREEN_INFO_YSTEP                  (MENU_SCREEN_INFO_ENTRY_SIZE +                  \
190                                                  MENU_SCREEN_INFO_SPACE_EXTRA)
191 #define MENU_SCREEN_INFO_YBOTTOM                (SYSIZE - MENU_SCREEN_INFO_SPACE_BOTTOM)
192 #define MENU_SCREEN_INFO_YSIZE                  (MENU_SCREEN_INFO_YBOTTOM -                     \
193                                                  MENU_SCREEN_INFO_YSTART - TILEY / 2)
194 #define MAX_INFO_ELEMENTS_IN_ARRAY              128
195 #define MAX_INFO_ELEMENTS_ON_SCREEN             (SYSIZE / TILEY)
196 #define MAX_INFO_ELEMENTS                       MIN(MAX_INFO_ELEMENTS_IN_ARRAY,                 \
197                                                     MAX_INFO_ELEMENTS_ON_SCREEN)
198 #define STD_INFO_ELEMENTS_ON_SCREEN             10
199 #define DYN_INFO_ELEMENTS_ON_SCREEN             (MENU_SCREEN_INFO_YSIZE / MENU_SCREEN_INFO_YSTEP)
200 #define DEFAULT_INFO_ELEMENTS                   MIN(STD_INFO_ELEMENTS_ON_SCREEN,                \
201                                                     DYN_INFO_ELEMENTS_ON_SCREEN)
202 #define NUM_INFO_ELEMENTS_FROM_CONF                                     \
203                                 (menu.list_size_info[GFX_SPECIAL_ARG_INFO_ELEMENTS] > 0 ?       \
204                                  menu.list_size_info[GFX_SPECIAL_ARG_INFO_ELEMENTS] :           \
205                                  DEFAULT_INFO_ELEMENTS)
206 #define NUM_INFO_ELEMENTS_ON_SCREEN             MIN(NUM_INFO_ELEMENTS_FROM_CONF, MAX_INFO_ELEMENTS)
207 #define MAX_MENU_ENTRIES_ON_SCREEN              (SCR_FIELDY - MENU_SCREEN_START_YPOS)
208 #define MAX_MENU_TEXT_LENGTH_BIG                13
209 #define MAX_MENU_TEXT_LENGTH_MEDIUM             (MAX_MENU_TEXT_LENGTH_BIG * 2)
210
211 // screen gadget identifiers
212 #define SCREEN_CTRL_ID_PREV_LEVEL               0
213 #define SCREEN_CTRL_ID_NEXT_LEVEL               1
214 #define SCREEN_CTRL_ID_PREV_LEVEL2              2
215 #define SCREEN_CTRL_ID_NEXT_LEVEL2              3
216 #define SCREEN_CTRL_ID_PREV_SCORE               4
217 #define SCREEN_CTRL_ID_NEXT_SCORE               5
218 #define SCREEN_CTRL_ID_PLAY_TAPE                6
219 #define SCREEN_CTRL_ID_FIRST_LEVEL              7
220 #define SCREEN_CTRL_ID_LAST_LEVEL               8
221 #define SCREEN_CTRL_ID_LEVEL_NUMBER             9
222 #define SCREEN_CTRL_ID_PREV_PLAYER              10
223 #define SCREEN_CTRL_ID_NEXT_PLAYER              11
224 #define SCREEN_CTRL_ID_INSERT_SOLUTION          12
225 #define SCREEN_CTRL_ID_PLAY_SOLUTION            13
226 #define SCREEN_CTRL_ID_LEVELSET_INFO            14
227 #define SCREEN_CTRL_ID_SWITCH_ECS_AGA           15
228 #define SCREEN_CTRL_ID_TOUCH_PREV_PAGE          16
229 #define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE          17
230 #define SCREEN_CTRL_ID_TOUCH_PREV_PAGE2         18
231 #define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2         19
232
233 #define NUM_SCREEN_MENUBUTTONS                  20
234
235 #define SCREEN_CTRL_ID_SCROLL_UP                20
236 #define SCREEN_CTRL_ID_SCROLL_DOWN              21
237 #define SCREEN_CTRL_ID_SCROLL_VERTICAL          22
238 #define SCREEN_CTRL_ID_NETWORK_SERVER           23
239
240 #define NUM_SCREEN_GADGETS                      24
241
242 #define NUM_SCREEN_SCROLLBUTTONS                2
243 #define NUM_SCREEN_SCROLLBARS                   1
244 #define NUM_SCREEN_TEXTINPUT                    1
245
246 #define SCREEN_MASK_MAIN                        (1 << 0)
247 #define SCREEN_MASK_MAIN_HAS_SOLUTION           (1 << 1)
248 #define SCREEN_MASK_MAIN_HAS_SET_INFO           (1 << 2)
249 #define SCREEN_MASK_INPUT                       (1 << 3)
250 #define SCREEN_MASK_TOUCH                       (1 << 4)
251 #define SCREEN_MASK_TOUCH2                      (1 << 5)
252 #define SCREEN_MASK_SCORES                      (1 << 6)
253 #define SCREEN_MASK_SCORES_INFO                 (1 << 7)
254
255 // graphic position and size values for buttons and scrollbars
256 #define SC_MENUBUTTON_XSIZE                     TILEX
257 #define SC_MENUBUTTON_YSIZE                     TILEY
258
259 #define SC_SCROLLBUTTON_XSIZE                   TILEX
260 #define SC_SCROLLBUTTON_YSIZE                   TILEY
261
262 #define SC_SCROLLBAR_XPOS                       (SXSIZE - SC_SCROLLBUTTON_XSIZE)
263
264 #define SC_SCROLL_VERTICAL_XSIZE                SC_SCROLLBUTTON_XSIZE
265 #define SC_SCROLL_VERTICAL_YSIZE                ((MAX_MENU_ENTRIES_ON_SCREEN - 2) * \
266                                                  SC_SCROLLBUTTON_YSIZE)
267
268 #define SC_SCROLL_UP_XPOS                       SC_SCROLLBAR_XPOS
269 #define SC_SCROLL_UP_YPOS                       (2 * SC_SCROLLBUTTON_YSIZE)
270
271 #define SC_SCROLL_VERTICAL_XPOS                 SC_SCROLLBAR_XPOS
272 #define SC_SCROLL_VERTICAL_YPOS                 (SC_SCROLL_UP_YPOS + \
273                                                  SC_SCROLLBUTTON_YSIZE)
274
275 #define SC_SCROLL_DOWN_XPOS                     SC_SCROLLBAR_XPOS
276 #define SC_SCROLL_DOWN_YPOS                     (SC_SCROLL_VERTICAL_YPOS + \
277                                                  SC_SCROLL_VERTICAL_YSIZE)
278
279 #define SC_BORDER_SIZE                          14
280
281
282 // forward declarations of internal functions
283 static void HandleScreenGadgets(struct GadgetInfo *);
284 static void HandleSetupScreen_Generic(int, int, int, int, int);
285 static void HandleSetupScreen_Input(int, int, int, int, int);
286 static void CustomizeKeyboard(int);
287 static void ConfigureJoystick(int);
288 static void ConfigureVirtualButtons(void);
289 static void execSetupGame(void);
290 static void execSetupEngines(void);
291 static void execSetupGraphics(void);
292 static void execSetupSound(void);
293 static void execSetupTouch(void);
294 static void execSetupArtwork(void);
295 static void HandleChooseTree(int, int, int, int, int, TreeInfo **);
296
297 static void DrawChoosePlayerName(void);
298 static void DrawChooseLevelSet(void);
299 static void DrawChooseLevelNr(void);
300 static void DrawScoreInfo(int);
301 static void DrawScoreInfo_Content(int);
302 static void DrawInfoScreen(void);
303 static void DrawSetupScreen(void);
304 static void DrawTypeName(void);
305
306 static void DrawInfoScreen_NotAvailable(char *, char *);
307 static void DrawInfoScreen_HelpAnim(int, int, boolean);
308 static void DrawInfoScreen_HelpText(int, int, int, int);
309 static void HandleInfoScreen_Main(int, int, int, int, int);
310 static void HandleInfoScreen_TitleScreen(int, int, int);
311 static void HandleInfoScreen_Elements(int, int, int);
312 static void HandleInfoScreen_Music(int, int, int);
313 static void HandleInfoScreen_Version(int);
314 static void HandleInfoScreen_Generic(int, int, int);
315
316 static void ModifyGameSpeedIfNeeded(void);
317 static void DisableVsyncIfNeeded(void);
318
319 static void RedrawScreenMenuGadgets(int);
320 static void MapScreenMenuGadgets(int);
321 static void UnmapScreenMenuGadgets(int);
322 static void MapScreenGadgets(int);
323 static void UnmapScreenGadgets(void);
324 static void MapScreenTreeGadgets(TreeInfo *);
325 static void UnmapScreenTreeGadgets(void);
326
327 static void UpdateScreenMenuGadgets(int, boolean);
328 static void AdjustScoreInfoButtons_SelectScore(int, int, int);
329 static void AdjustScoreInfoButtons_PlayTape(int, int, boolean);
330
331 static boolean OfferUploadTapes(void);
332 static void execOfferUploadTapes(void);
333
334 static void DrawHallOfFame_setScoreEntries(void);
335 static void HandleHallOfFame_SelectLevel(int, int);
336 static char *getHallOfFameRankText(int, int);
337 static char *getHallOfFameScoreText(int, int);
338 static char *getInfoScreenTitle_Generic(void);
339 static int getInfoScreenBackgroundImage_Generic(void);
340 static int getInfoScreenBackgroundSound_Generic(void);
341 static int getInfoScreenBackgroundMusic_Generic(void);
342
343 static struct TokenInfo *getSetupInfoFinal(struct TokenInfo *);
344
345 static struct GadgetInfo *screen_gadget[NUM_SCREEN_GADGETS];
346
347 static int info_mode = INFO_MODE_MAIN;
348 static int setup_mode = SETUP_MODE_MAIN;
349
350 static boolean info_screens_from_main = FALSE;
351
352 static TreeInfo *window_sizes = NULL;
353 static TreeInfo *window_size_current = NULL;
354
355 static TreeInfo *scaling_types = NULL;
356 static TreeInfo *scaling_type_current = NULL;
357
358 static TreeInfo *rendering_modes = NULL;
359 static TreeInfo *rendering_mode_current = NULL;
360
361 static TreeInfo *vsync_modes = NULL;
362 static TreeInfo *vsync_mode_current = NULL;
363
364 static TreeInfo *scroll_delays = NULL;
365 static TreeInfo *scroll_delay_current = NULL;
366
367 static TreeInfo *snapshot_modes = NULL;
368 static TreeInfo *snapshot_mode_current = NULL;
369
370 static TreeInfo *scores_types = NULL;
371 static TreeInfo *scores_type_current = NULL;
372
373 static TreeInfo *game_speeds_normal = NULL;
374 static TreeInfo *game_speeds_extended = NULL;
375 static TreeInfo *game_speeds = NULL;
376 static TreeInfo *game_speed_current = NULL;
377
378 static TreeInfo *volumes_simple = NULL;
379 static TreeInfo *volume_simple_current = NULL;
380
381 static TreeInfo *volumes_loops = NULL;
382 static TreeInfo *volume_loops_current = NULL;
383
384 static TreeInfo *volumes_music = NULL;
385 static TreeInfo *volume_music_current = NULL;
386
387 static TreeInfo *touch_controls = NULL;
388 static TreeInfo *touch_control_current = NULL;
389
390 static TreeInfo *move_distances = NULL;
391 static TreeInfo *move_distance_current = NULL;
392
393 static TreeInfo *drop_distances = NULL;
394 static TreeInfo *drop_distance_current = NULL;
395
396 static TreeInfo *transparencies = NULL;
397 static TreeInfo *transparency_current = NULL;
398
399 static TreeInfo *grid_sizes[2][2] = { { NULL, NULL }, { NULL, NULL } };
400 static TreeInfo *grid_size_current[2][2] = { { NULL, NULL }, { NULL, NULL } };
401
402 static TreeInfo *player_name = NULL;
403 static TreeInfo *player_name_current = NULL;
404
405 static TreeInfo *level_number = NULL;
406 static TreeInfo *level_number_current = NULL;
407
408 static TreeInfo *score_entries = NULL;
409 static TreeInfo *score_entry_current = NULL;
410
411 static struct ValueTextInfo window_sizes_list[] =
412 {
413   {     50,     "50 %"                          },
414   {     80,     "80 %"                          },
415   {     90,     "90 %"                          },
416   {     100,    "100 % (Default)"               },
417   {     110,    "110 %"                         },
418   {     120,    "120 %"                         },
419   {     130,    "130 %"                         },
420   {     140,    "140 %"                         },
421   {     150,    "150 %"                         },
422   {     200,    "200 %"                         },
423   {     250,    "250 %"                         },
424   {     300,    "300 %"                         },
425
426   {     -1,     NULL                            },
427 };
428
429 static struct StringValueTextInfo scaling_types_list[] =
430 {
431   {     SCALING_QUALITY_NEAREST, "Off"          },
432   {     SCALING_QUALITY_LINEAR,  "Linear"       },
433   {     SCALING_QUALITY_BEST,    "Anisotropic"  },
434
435   {     NULL,                    NULL           },
436 };
437
438 static struct StringValueTextInfo rendering_modes_list[] =
439 {
440   {     STR_SPECIAL_RENDERING_OFF,      "Off (May show artifacts, fast)"        },
441   {     STR_SPECIAL_RENDERING_BITMAP,   "Bitmap/Texture mode (slower)"          },
442 #if DEBUG
443   // this mode may work under certain conditions, but does not work on Windows
444   {     STR_SPECIAL_RENDERING_TARGET,   "Target Texture mode (slower)"          },
445 #endif
446   {     STR_SPECIAL_RENDERING_DOUBLE,   "Double Texture mode (slower)"          },
447
448   {     NULL,                            NULL                                   },
449 };
450
451 static struct StringValueTextInfo vsync_modes_list[] =
452 {
453   {     STR_VSYNC_MODE_OFF,             "Off"           },
454   {     STR_VSYNC_MODE_NORMAL,          "Normal"        },
455   {     STR_VSYNC_MODE_ADAPTIVE,        "Adaptive"      },
456
457   {     NULL,                            NULL           },
458 };
459
460 static struct StringValueTextInfo scores_types_list[] =
461 {
462   {     STR_SCORES_TYPE_LOCAL_ONLY,         "Local scores only"         },
463   {     STR_SCORES_TYPE_SERVER_ONLY,        "Server scores only"        },
464   {     STR_SCORES_TYPE_LOCAL_AND_SERVER,   "Local and server scores"   },
465
466   {     NULL,                           NULL            },
467 };
468
469 static struct ValueTextInfo game_speeds_list_normal[] =
470 {
471   {     30,     "Very Slow"                     },
472   {     25,     "Slow"                          },
473   {     20,     "Normal"                        },
474   {     15,     "Fast"                          },
475   {     10,     "Very Fast"                     },
476
477   {     -1,     NULL                            },
478 };
479
480 static struct ValueTextInfo game_speeds_list_extended[] =
481 {
482   {     1000,   "1 fps (Extremely Slow)"        },
483   {     500,    "2 fps"                         },
484   {     200,    "5 fps"                         },
485   {     100,    "10 fps"                        },
486   {     50,     "20 fps"                        },
487   {     29,     "35 fps (Original Supaplex)"    },
488   {     25,     "40 fps"                        },
489   {     20,     "50 fps (=== Normal Speed ===)" },
490   {     16,     "60 fps (60 Hz VSync Speed)"    },
491   {     14,     "70 fps (Maximum Supaplex)"     },
492   {     10,     "100 fps"                       },
493   {     5,      "200 fps"                       },
494   {     2,      "500 fps"                       },
495   {     1,      "1000 fps (Extremely Fast)"     },
496
497   {     -1,     NULL                            },
498 };
499
500 static struct ValueTextInfo *game_speeds_list;
501
502 static struct ValueTextInfo scroll_delays_list[] =
503 {
504   {     0,      "0 Tiles (No Scroll Delay)"     },
505   {     1,      "1 Tile"                        },
506   {     2,      "2 Tiles"                       },
507   {     3,      "3 Tiles (Default)"             },
508   {     4,      "4 Tiles"                       },
509   {     5,      "5 Tiles"                       },
510   {     6,      "6 Tiles"                       },
511   {     7,      "7 Tiles"                       },
512   {     8,      "8 Tiles (Maximum Scroll Delay)"},
513
514   {     -1,     NULL                            },
515 };
516
517 static struct StringValueTextInfo snapshot_modes_list[] =
518 {
519   {     STR_SNAPSHOT_MODE_OFF,                  "Off"           },
520   {     STR_SNAPSHOT_MODE_EVERY_STEP,           "Every Step"    },
521   {     STR_SNAPSHOT_MODE_EVERY_MOVE,           "Every Move"    },
522   {     STR_SNAPSHOT_MODE_EVERY_COLLECT,        "Every Collect" },
523
524   {     NULL,                                   NULL            },
525 };
526
527 static struct ValueTextInfo volumes_list[] =
528 {
529   {     0,      "0 %"                           },
530   {     1,      "1 %"                           },
531   {     2,      "2 %"                           },
532   {     5,      "5 %"                           },
533   {     10,     "10 %"                          },
534   {     20,     "20 %"                          },
535   {     30,     "30 %"                          },
536   {     40,     "40 %"                          },
537   {     50,     "50 %"                          },
538   {     60,     "60 %"                          },
539   {     70,     "70 %"                          },
540   {     80,     "80 %"                          },
541   {     90,     "90 %"                          },
542   {     100,    "100 %"                         },
543
544   {     -1,     NULL                            },
545 };
546
547 static struct StringValueTextInfo touch_controls_list[] =
548 {
549   {     TOUCH_CONTROL_OFF,              "Off"                   },
550   {     TOUCH_CONTROL_VIRTUAL_BUTTONS,  "Virtual Buttons"       },
551   {     TOUCH_CONTROL_WIPE_GESTURES,    "Wipe Gestures"         },
552   {     TOUCH_CONTROL_FOLLOW_FINGER,    "Follow Finger"         },
553
554   {     NULL,                           NULL                    },
555 };
556
557 static struct ValueTextInfo distances_list[] =
558 {
559   {     1,      "1 %"                           },
560   {     2,      "2 %"                           },
561   {     3,      "3 %"                           },
562   {     4,      "4 %"                           },
563   {     5,      "5 %"                           },
564   {     10,     "10 %"                          },
565   {     15,     "15 %"                          },
566   {     20,     "20 %"                          },
567   {     25,     "25 %"                          },
568
569   {     -1,     NULL                            },
570 };
571
572 static struct ValueTextInfo transparencies_list[] =
573 {
574   {     0,      "0 % (Opaque)"                  },
575   {     10,     "10 %"                          },
576   {     20,     "20 %"                          },
577   {     30,     "30 %"                          },
578   {     40,     "40 %"                          },
579   {     50,     "50 %"                          },
580   {     60,     "60 %"                          },
581   {     70,     "70 %"                          },
582   {     80,     "80 %"                          },
583   {     90,     "90 %"                          },
584   {     100,    "100 % (Invisible)"             },
585
586   {     -1,     NULL                            },
587 };
588
589 static struct ValueTextInfo grid_sizes_list[] =
590 {
591   {     3,      "3"                             },
592   {     4,      "4"                             },
593   {     5,      "5"                             },
594   {     6,      "6"                             },
595   {     7,      "7"                             },
596   {     8,      "8"                             },
597   {     9,      "9"                             },
598   {     10,     "10"                            },
599   {     11,     "11"                            },
600   {     12,     "12"                            },
601   {     13,     "13"                            },
602   {     14,     "14"                            },
603   {     15,     "15"                            },
604   {     16,     "16"                            },
605   {     17,     "17"                            },
606   {     18,     "18"                            },
607   {     19,     "19"                            },
608   {     20,     "20"                            },
609   {     21,     "21"                            },
610   {     22,     "22"                            },
611   {     23,     "23"                            },
612   {     24,     "24"                            },
613   {     25,     "25"                            },
614   {     26,     "26"                            },
615   {     27,     "27"                            },
616   {     28,     "28"                            },
617   {     29,     "29"                            },
618   {     30,     "30"                            },
619   {     31,     "31"                            },
620   {     32,     "32"                            },
621
622   {     -1,     NULL                            },
623 };
624
625 static int align_xoffset = 0;
626 static int align_yoffset = 0;
627
628 #define DRAW_MODE(s)            ((s) >= GAME_MODE_MAIN &&                       \
629                                  (s) <= GAME_MODE_SETUP ? (s) :                 \
630                                  (s) == GAME_MODE_PSEUDO_TYPENAME ?             \
631                                  GAME_MODE_MAIN :                               \
632                                  (s) == GAME_MODE_PSEUDO_TYPENAMES ?            \
633                                  GAME_MODE_NAMES : GAME_MODE_DEFAULT)
634
635 // (there are no draw offset definitions needed for INFO_MODE_TITLE)
636 #define DRAW_MODE_INFO(i)       ((i) >= INFO_MODE_TITLE &&                      \
637                                  (i) <= INFO_MODE_LEVELSET ? (i) :              \
638                                  INFO_MODE_MAIN)
639
640 #define DRAW_MODE_SETUP(i)      ((i) >= SETUP_MODE_MAIN &&                      \
641                                  (i) <= SETUP_MODE_SHORTCUTS_5 ? (i) :          \
642                                  (i) >= SETUP_MODE_CHOOSE_GRAPHICS &&           \
643                                  (i) <= SETUP_MODE_CHOOSE_MUSIC ?               \
644                                  SETUP_MODE_CHOOSE_ARTWORK :                    \
645                                  SETUP_MODE_CHOOSE_OTHER)
646
647 #define DRAW_XOFFSET_INFO(i)    (DRAW_MODE_INFO(i) == INFO_MODE_MAIN ?          \
648                                  menu.draw_xoffset[GAME_MODE_INFO] :            \
649                                  menu.draw_xoffset_info[DRAW_MODE_INFO(i)])
650 #define DRAW_YOFFSET_INFO(i)    (DRAW_MODE_INFO(i) == INFO_MODE_MAIN ?          \
651                                  menu.draw_yoffset[GAME_MODE_INFO] :            \
652                                  menu.draw_yoffset_info[DRAW_MODE_INFO(i)])
653 #define EXTRA_SPACING_INFO(i)   (DRAW_MODE_INFO(i) == INFO_MODE_MAIN ?          \
654                                  menu.extra_spacing[GAME_MODE_INFO] :           \
655                                  menu.extra_spacing_info[DRAW_MODE_INFO(i)])
656
657 #define DRAW_XOFFSET_SETUP(i)   (DRAW_MODE_SETUP(i) == SETUP_MODE_MAIN ?        \
658                                  menu.draw_xoffset[GAME_MODE_SETUP] :           \
659                                  menu.draw_xoffset_setup[DRAW_MODE_SETUP(i)])
660 #define DRAW_YOFFSET_SETUP(i)   (DRAW_MODE_SETUP(i) == SETUP_MODE_MAIN ?        \
661                                  menu.draw_yoffset[GAME_MODE_SETUP] :           \
662                                  menu.draw_yoffset_setup[DRAW_MODE_SETUP(i)])
663 #define EXTRA_SPACING_SETUP(i)  (DRAW_MODE_SETUP(i) == SETUP_MODE_MAIN ?        \
664                                  menu.extra_spacing[GAME_MODE_SETUP] :          \
665                                  menu.extra_spacing_setup[DRAW_MODE_SETUP(i)])
666
667 #define EXTRA_SPACING_SCORES(i) (EXTRA_SPACING_INFO(i))
668
669 #define EXTRA_SPACING_SCOREINFO(i) (menu.extra_spacing[GAME_MODE_SCOREINFO])
670
671 #define DRAW_XOFFSET(s)         ((s) == GAME_MODE_INFO  ? DRAW_XOFFSET_INFO(info_mode) :        \
672                                  (s) == GAME_MODE_SETUP ? DRAW_XOFFSET_SETUP(setup_mode) :      \
673                                  menu.draw_xoffset[DRAW_MODE(s)])
674
675 #define DRAW_YOFFSET(s)         ((s) == GAME_MODE_INFO  ? DRAW_YOFFSET_INFO(info_mode) :        \
676                                  (s) == GAME_MODE_SETUP ? DRAW_YOFFSET_SETUP(setup_mode) :      \
677                                  menu.draw_yoffset[DRAW_MODE(s)])
678
679 #define EXTRA_SPACING(s)        ((s) == GAME_MODE_INFO   ? EXTRA_SPACING_INFO(info_mode) :      \
680                                  (s) == GAME_MODE_SETUP  ? EXTRA_SPACING_SETUP(setup_mode) :    \
681                                  (s) == GAME_MODE_SCORES ? EXTRA_SPACING_SCORES(info_mode) :    \
682                                  menu.extra_spacing[DRAW_MODE(s)])
683
684 #define mSX                     (SX + DRAW_XOFFSET(game_status))
685 #define mSY                     (SY + DRAW_YOFFSET(game_status))
686
687 #define amSX                    (mSX + align_xoffset)
688 #define amSY                    (mSY + align_yoffset)
689
690 #define NUM_MENU_ENTRIES_ON_SCREEN (menu.list_size[game_status] > 2 ?   \
691                                     menu.list_size[game_status] :       \
692                                     MAX_MENU_ENTRIES_ON_SCREEN)
693
694 #define IN_VIS_MENU(x, y)       IN_FIELD(x, y, SCR_FIELDX, NUM_MENU_ENTRIES_ON_SCREEN)
695
696
697 // title display and control definitions
698
699 #define MAX_NUM_TITLE_SCREENS   (2 * MAX_NUM_TITLE_IMAGES + 2 * MAX_NUM_TITLE_MESSAGES)
700
701 #define NO_DIRECT_LEVEL_SELECT  (-1)
702
703
704 static int num_title_screens = 0;
705
706 struct TitleControlInfo
707 {
708   boolean is_image;
709   boolean initial;
710   boolean first;
711   int local_nr;
712   int sort_priority;
713 };
714
715 struct TitleControlInfo title_controls[MAX_NUM_TITLE_SCREENS];
716
717
718 // main menu display and control definitions
719
720 #define MAIN_CONTROL_NAME                       0
721 #define MAIN_CONTROL_LEVELS                     1
722 #define MAIN_CONTROL_SCORES                     2
723 #define MAIN_CONTROL_EDITOR                     3
724 #define MAIN_CONTROL_INFO                       4
725 #define MAIN_CONTROL_GAME                       5
726 #define MAIN_CONTROL_SETUP                      6
727 #define MAIN_CONTROL_QUIT                       7
728 #define MAIN_CONTROL_PREV_LEVEL                 8
729 #define MAIN_CONTROL_NEXT_LEVEL                 9
730 #define MAIN_CONTROL_FIRST_LEVEL                10
731 #define MAIN_CONTROL_LAST_LEVEL                 11
732 #define MAIN_CONTROL_LEVEL_NUMBER               12
733 #define MAIN_CONTROL_LEVEL_INFO_1               13
734 #define MAIN_CONTROL_LEVEL_INFO_2               14
735 #define MAIN_CONTROL_LEVEL_NAME                 15
736 #define MAIN_CONTROL_LEVEL_AUTHOR               16
737 #define MAIN_CONTROL_LEVEL_YEAR                 17
738 #define MAIN_CONTROL_LEVEL_IMPORTED_FROM        18
739 #define MAIN_CONTROL_LEVEL_IMPORTED_BY          19
740 #define MAIN_CONTROL_LEVEL_TESTED_BY            20
741 #define MAIN_CONTROL_TITLE_1                    21
742 #define MAIN_CONTROL_TITLE_2                    22
743 #define MAIN_CONTROL_TITLE_3                    23
744
745 static char str_main_text_name[10];
746 static char str_main_text_first_level[10];
747 static char str_main_text_last_level[10];
748 static char str_main_text_level_number[10];
749
750 static char network_server_hostname[MAX_SETUP_TEXT_INPUT_LEN + 1];
751
752 static char *main_text_name                     = str_main_text_name;
753 static char *main_text_first_level              = str_main_text_first_level;
754 static char *main_text_last_level               = str_main_text_last_level;
755 static char *main_text_level_number             = str_main_text_level_number;
756 static char *main_text_levels                   = "Levelset";
757 static char *main_text_scores                   = "Hall Of Fame";
758 static char *main_text_editor                   = "Level Creator";
759 static char *main_text_info                     = "Info Screen";
760 static char *main_text_game                     = "Start Game";
761 static char *main_text_setup                    = "Setup";
762 static char *main_text_quit                     = "Quit";
763 static char *main_text_level_name               = level.name;
764 static char *main_text_level_author             = level.author;
765 static char *main_text_level_year               = NULL;
766 static char *main_text_level_imported_from      = NULL;
767 static char *main_text_level_imported_by        = NULL;
768 static char *main_text_level_tested_by          = NULL;
769 static char *main_text_title_1                  = NULL;
770 static char *main_text_title_2                  = NULL;
771 static char *main_text_title_3                  = NULL;
772
773 extern char debug_xsn_mode[];
774
775 struct MainControlInfo
776 {
777   int nr;
778
779   struct MenuPosInfo *pos_button;
780   int button_graphic;
781
782   struct TextPosInfo *pos_text;
783   char **text;
784
785   struct TextPosInfo *pos_input;
786   char **input;
787 };
788
789 static struct MainControlInfo main_controls[] =
790 {
791   {
792     MAIN_CONTROL_NAME,
793     &menu.main.button.name,             IMG_MENU_BUTTON_NAME,
794     &menu.main.text.name,               &main_text_name,
795     &menu.main.input.name,              &setup.player_name,
796   },
797   {
798     MAIN_CONTROL_LEVELS,
799     &menu.main.button.levels,           IMG_MENU_BUTTON_LEVELS,
800     &menu.main.text.levels,             &main_text_levels,
801     NULL,                               NULL,
802   },
803   {
804     MAIN_CONTROL_SCORES,
805     &menu.main.button.scores,           IMG_MENU_BUTTON_SCORES,
806     &menu.main.text.scores,             &main_text_scores,
807     NULL,                               NULL,
808   },
809   {
810     MAIN_CONTROL_EDITOR,
811     &menu.main.button.editor,           IMG_MENU_BUTTON_EDITOR,
812     &menu.main.text.editor,             &main_text_editor,
813     NULL,                               NULL,
814   },
815   {
816     MAIN_CONTROL_INFO,
817     &menu.main.button.info,             IMG_MENU_BUTTON_INFO,
818     &menu.main.text.info,               &main_text_info,
819     NULL,                               NULL,
820   },
821   {
822     MAIN_CONTROL_GAME,
823     &menu.main.button.game,             IMG_MENU_BUTTON_GAME,
824     &menu.main.text.game,               &main_text_game,
825     NULL,                               NULL,
826   },
827   {
828     MAIN_CONTROL_SETUP,
829     &menu.main.button.setup,            IMG_MENU_BUTTON_SETUP,
830     &menu.main.text.setup,              &main_text_setup,
831     NULL,                               NULL,
832   },
833   {
834     MAIN_CONTROL_QUIT,
835     &menu.main.button.quit,             IMG_MENU_BUTTON_QUIT,
836     &menu.main.text.quit,               &main_text_quit,
837     NULL,                               NULL,
838   },
839   {
840     MAIN_CONTROL_PREV_LEVEL,
841     NULL,                               -1,
842     NULL,                               NULL,
843     NULL,                               NULL,
844   },
845   {
846     MAIN_CONTROL_NEXT_LEVEL,
847     NULL,                               -1,
848     NULL,                               NULL,
849     NULL,                               NULL,
850   },
851   {
852     MAIN_CONTROL_FIRST_LEVEL,
853     NULL,                               -1,
854     &menu.main.text.first_level,        &main_text_first_level,
855     NULL,                               NULL,
856   },
857   {
858     MAIN_CONTROL_LAST_LEVEL,
859     NULL,                               -1,
860     &menu.main.text.last_level,         &main_text_last_level,
861     NULL,                               NULL,
862   },
863   {
864     MAIN_CONTROL_LEVEL_NUMBER,
865     NULL,                               -1,
866     &menu.main.text.level_number,       &main_text_level_number,
867     NULL,                               NULL,
868   },
869   {
870     MAIN_CONTROL_LEVEL_INFO_1,
871     NULL,                               -1,
872     &menu.main.text.level_info_1,       NULL,
873     NULL,                               NULL,
874   },
875   {
876     MAIN_CONTROL_LEVEL_INFO_2,
877     NULL,                               -1,
878     &menu.main.text.level_info_2,       NULL,
879     NULL,                               NULL,
880   },
881   {
882     MAIN_CONTROL_LEVEL_NAME,
883     NULL,                               -1,
884     &menu.main.text.level_name,         &main_text_level_name,
885     NULL,                               NULL,
886   },
887   {
888     MAIN_CONTROL_LEVEL_AUTHOR,
889     NULL,                               -1,
890     &menu.main.text.level_author,       &main_text_level_author,
891     NULL,                               NULL,
892   },
893   {
894     MAIN_CONTROL_LEVEL_YEAR,
895     NULL,                               -1,
896     &menu.main.text.level_year,         &main_text_level_year,
897     NULL,                               NULL,
898   },
899   {
900     MAIN_CONTROL_LEVEL_IMPORTED_FROM,
901     NULL,                               -1,
902     &menu.main.text.level_imported_from, &main_text_level_imported_from,
903     NULL,                               NULL,
904   },
905   {
906     MAIN_CONTROL_LEVEL_IMPORTED_BY,
907     NULL,                               -1,
908     &menu.main.text.level_imported_by,  &main_text_level_imported_by,
909     NULL,                               NULL,
910   },
911   {
912     MAIN_CONTROL_LEVEL_TESTED_BY,
913     NULL,                               -1,
914     &menu.main.text.level_tested_by,    &main_text_level_tested_by,
915     NULL,                               NULL,
916   },
917   {
918     MAIN_CONTROL_TITLE_1,
919     NULL,                               -1,
920     &menu.main.text.title_1,            &main_text_title_1,
921     NULL,                               NULL,
922   },
923   {
924     MAIN_CONTROL_TITLE_2,
925     NULL,                               -1,
926     &menu.main.text.title_2,            &main_text_title_2,
927     NULL,                               NULL,
928   },
929   {
930     MAIN_CONTROL_TITLE_3,
931     NULL,                               -1,
932     &menu.main.text.title_3,            &main_text_title_3,
933     NULL,                               NULL,
934   },
935
936   {
937     -1,
938     NULL,                               -1,
939     NULL,                               NULL,
940     NULL,                               NULL,
941   }
942 };
943
944
945 static boolean hasLevelSetInfo(void)
946 {
947   return (getLevelSetInfoFilename(0) != NULL);
948 }
949
950 static int getTitleScreenGraphic(int nr, boolean initial)
951 {
952   return (initial ? IMG_TITLESCREEN_INITIAL_1 : IMG_TITLESCREEN_1) + nr;
953 }
954
955 static struct TitleMessageInfo *getTitleMessageInfo(int nr, boolean initial)
956 {
957   return (initial ? &titlemessage_initial[nr] : &titlemessage[nr]);
958 }
959
960 #if 0
961 static int getTitleScreenGameMode(boolean initial)
962 {
963   return (initial ? GAME_MODE_TITLE_INITIAL : GAME_MODE_TITLE);
964 }
965 #endif
966
967 static int getTitleMessageGameMode(boolean initial)
968 {
969   return (initial ? GAME_MODE_TITLE_INITIAL : GAME_MODE_TITLE);
970 }
971
972 static int getTitleAnimMode(struct TitleControlInfo *tci)
973 {
974   int base = (tci->initial ? GAME_MODE_TITLE_INITIAL_1 : GAME_MODE_TITLE_1);
975
976   return base + tci->local_nr;
977 }
978
979 #if 0
980 static int getTitleScreenBackground(boolean initial)
981 {
982   return (initial ? IMG_BACKGROUND_TITLE_INITIAL : IMG_BACKGROUND_TITLE);
983 }
984 #endif
985
986 #if 0
987 static int getTitleMessageBackground(int nr, boolean initial)
988 {
989   return (initial ? IMG_BACKGROUND_TITLE_INITIAL : IMG_BACKGROUND_TITLE);
990 }
991 #endif
992
993 static int getTitleBackground(int nr, boolean initial, boolean is_image)
994 {
995   int base = (is_image ?
996               (initial ? IMG_BACKGROUND_TITLESCREEN_INITIAL_1 :
997                          IMG_BACKGROUND_TITLESCREEN_1) :
998               (initial ? IMG_BACKGROUND_TITLEMESSAGE_INITIAL_1 :
999                          IMG_BACKGROUND_TITLEMESSAGE_1));
1000   int graphic_global = (initial ? IMG_BACKGROUND_TITLE_INITIAL :
1001                                   IMG_BACKGROUND_TITLE);
1002   int graphic_local = base + nr;
1003
1004   if (graphic_info[graphic_local].bitmap != NULL)
1005     return graphic_local;
1006
1007   if (graphic_info[graphic_global].bitmap != NULL)
1008     return graphic_global;
1009
1010   return IMG_UNDEFINED;
1011 }
1012
1013 static int getTitleSound(struct TitleControlInfo *tci)
1014 {
1015   boolean is_image = tci->is_image;
1016   int initial = tci->initial;
1017   int nr = tci->local_nr;
1018   int mode = (initial ? GAME_MODE_TITLE_INITIAL : GAME_MODE_TITLE);
1019   int base = (is_image ?
1020               (initial ? SND_BACKGROUND_TITLESCREEN_INITIAL_1 :
1021                          SND_BACKGROUND_TITLESCREEN_1) :
1022               (initial ? SND_BACKGROUND_TITLEMESSAGE_INITIAL_1 :
1023                          SND_BACKGROUND_TITLEMESSAGE_1));
1024   int sound_global = menu.sound[mode];
1025   int sound_local = base + nr;
1026
1027 #if 0
1028   Debug("screens:getTitleSound", "%d, %d, %d: %d ['%s'], %d ['%s']",
1029         nr, initial, is_image,
1030         sound_global, getSoundListEntry(sound_global)->filename,
1031         sound_local, getSoundListEntry(sound_local)->filename);
1032 #endif
1033
1034   if (!strEqual(getSoundListEntry(sound_local)->filename, UNDEFINED_FILENAME))
1035     return sound_local;
1036
1037   if (!strEqual(getSoundListEntry(sound_global)->filename, UNDEFINED_FILENAME))
1038     return sound_global;
1039
1040   return SND_UNDEFINED;
1041 }
1042
1043 static int getTitleMusic(struct TitleControlInfo *tci)
1044 {
1045   boolean is_image = tci->is_image;
1046   int initial = tci->initial;
1047   int nr = tci->local_nr;
1048   int mode = (initial ? GAME_MODE_TITLE_INITIAL : GAME_MODE_TITLE);
1049   int base = (is_image ?
1050               (initial ? MUS_BACKGROUND_TITLESCREEN_INITIAL_1 :
1051                          MUS_BACKGROUND_TITLESCREEN_1) :
1052               (initial ? MUS_BACKGROUND_TITLEMESSAGE_INITIAL_1 :
1053                          MUS_BACKGROUND_TITLEMESSAGE_1));
1054   int music_global = menu.music[mode];
1055   int music_local = base + nr;
1056
1057 #if 0
1058   Debug("screens:getTitleMusic", "%d, %d, %d: %d ['%s'], %d ['%s']",
1059         nr, initial, is_image,
1060         music_global, getMusicListEntry(music_global)->filename,
1061         music_local, getMusicListEntry(music_local)->filename);
1062 #endif
1063
1064   if (!strEqual(getMusicListEntry(music_local)->filename, UNDEFINED_FILENAME))
1065     return music_local;
1066
1067   if (!strEqual(getMusicListEntry(music_global)->filename, UNDEFINED_FILENAME))
1068     return music_global;
1069
1070   return MUS_UNDEFINED;
1071 }
1072
1073 static struct TitleFadingInfo getTitleFading(struct TitleControlInfo *tci)
1074 {
1075   boolean is_image = tci->is_image;
1076   boolean initial = tci->initial;
1077   boolean first = tci->first;
1078   int nr = tci->local_nr;
1079   struct TitleMessageInfo tmi;
1080   struct TitleFadingInfo ti;
1081
1082   tmi = (is_image ? (initial ? (first ?
1083                                 titlescreen_initial_first[nr] :
1084                                 titlescreen_initial[nr])
1085                              : (first ?
1086                                 titlescreen_first[nr] :
1087                                 titlescreen[nr]))
1088                   : (initial ? (first ?
1089                                 titlemessage_initial_first[nr] :
1090                                 titlemessage_initial[nr])
1091                              : (first ?
1092                                 titlemessage_first[nr] :
1093                                 titlemessage[nr])));
1094
1095   ti.fade_mode  = tmi.fade_mode;
1096   ti.fade_delay = tmi.fade_delay;
1097   ti.post_delay = tmi.post_delay;
1098   ti.auto_delay = tmi.auto_delay;
1099   ti.auto_delay_unit = tmi.auto_delay_unit;
1100
1101   return ti;
1102 }
1103
1104 static int compareTitleControlInfo(const void *object1, const void *object2)
1105 {
1106   const struct TitleControlInfo *tci1 = (struct TitleControlInfo *)object1;
1107   const struct TitleControlInfo *tci2 = (struct TitleControlInfo *)object2;
1108   int compare_result;
1109
1110   if (tci1->initial != tci2->initial)
1111     compare_result = (tci1->initial ? -1 : +1);
1112   else if (tci1->sort_priority != tci2->sort_priority)
1113     compare_result = tci1->sort_priority - tci2->sort_priority;
1114   else if (tci1->is_image != tci2->is_image)
1115     compare_result = (tci1->is_image ? -1 : +1);
1116   else
1117     compare_result = tci1->local_nr - tci2->local_nr;
1118
1119   return compare_result;
1120 }
1121
1122 static void InitializeTitleControlsExt_AddTitleInfo(boolean is_image,
1123                                                     boolean initial,
1124                                                     int nr, int sort_priority)
1125 {
1126   title_controls[num_title_screens].is_image = is_image;
1127   title_controls[num_title_screens].initial = initial;
1128   title_controls[num_title_screens].local_nr = nr;
1129   title_controls[num_title_screens].sort_priority = sort_priority;
1130
1131   title_controls[num_title_screens].first = FALSE;      // will be set later
1132
1133   num_title_screens++;
1134 }
1135
1136 static void InitializeTitleControls_CheckTitleInfo(boolean initial)
1137 {
1138   int i;
1139
1140   for (i = 0; i < MAX_NUM_TITLE_IMAGES; i++)
1141   {
1142     int graphic = getTitleScreenGraphic(i, initial);
1143     Bitmap *bitmap = graphic_info[graphic].bitmap;
1144     int sort_priority = graphic_info[graphic].sort_priority;
1145
1146     if (bitmap != NULL)
1147       InitializeTitleControlsExt_AddTitleInfo(TRUE, initial, i, sort_priority);
1148   }
1149
1150   for (i = 0; i < MAX_NUM_TITLE_MESSAGES; i++)
1151   {
1152     struct TitleMessageInfo *tmi = getTitleMessageInfo(i, initial);
1153     char *filename = getLevelSetTitleMessageFilename(i, initial);
1154     int sort_priority = tmi->sort_priority;
1155
1156     if (filename != NULL)
1157       InitializeTitleControlsExt_AddTitleInfo(FALSE, initial, i, sort_priority);
1158   }
1159 }
1160
1161 static void InitializeTitleControls(boolean show_title_initial)
1162 {
1163   num_title_screens = 0;
1164
1165   // 1st step: initialize title screens for game start (only when starting)
1166   if (show_title_initial)
1167     InitializeTitleControls_CheckTitleInfo(TRUE);
1168
1169   // 2nd step: initialize title screens for current level set
1170   InitializeTitleControls_CheckTitleInfo(FALSE);
1171
1172   // sort title screens according to sort_priority and title number
1173   qsort(title_controls, num_title_screens, sizeof(struct TitleControlInfo),
1174         compareTitleControlInfo);
1175
1176   // mark first title screen
1177   title_controls[0].first = TRUE;
1178 }
1179
1180 static boolean visibleMenuPos(struct MenuPosInfo *pos)
1181 {
1182   return (pos != NULL && pos->x != -1 && pos->y != -1);
1183 }
1184
1185 static boolean visibleTextPos(struct TextPosInfo *pos)
1186 {
1187   return (pos != NULL && pos->x != -1 && pos->y != -1);
1188 }
1189
1190 static void InitializeMainControls(void)
1191 {
1192   boolean local_team_mode = (!network.enabled && setup.team_mode);
1193   int i;
1194
1195   // set main control text values to dynamically determined values
1196   sprintf(main_text_name,         "%s",   local_team_mode ? "Team:" : "Name:");
1197
1198   strcpy(main_text_first_level,  int2str(leveldir_current->first_level,
1199                                          menu.main.text.first_level.size));
1200   strcpy(main_text_last_level,   int2str(leveldir_current->last_level,
1201                                          menu.main.text.last_level.size));
1202   strcpy(main_text_level_number, int2str(level_nr,
1203                                          menu.main.text.level_number.size));
1204
1205   main_text_level_year          = leveldir_current->year;
1206   main_text_level_imported_from = leveldir_current->imported_from;
1207   main_text_level_imported_by   = leveldir_current->imported_by;
1208   main_text_level_tested_by     = leveldir_current->tested_by;
1209
1210   main_text_title_1 = getConfigProgramTitleString();
1211   main_text_title_2 = getConfigProgramCopyrightString();
1212   main_text_title_3 = getConfigProgramCompanyString();
1213
1214   // set main control screen positions to dynamically determined values
1215   for (i = 0; main_controls[i].nr != -1; i++)
1216   {
1217     struct MainControlInfo *mci = &main_controls[i];
1218     int nr                         = mci->nr;
1219     struct MenuPosInfo *pos_button = mci->pos_button;
1220     struct TextPosInfo *pos_text   = mci->pos_text;
1221     struct TextPosInfo *pos_input  = mci->pos_input;
1222     char *text                     = (mci->text  ? *mci->text  : NULL);
1223     char *input                    = (mci->input ? *mci->input : NULL);
1224     int button_graphic             = mci->button_graphic;
1225     int font_text                  = (pos_text  ? pos_text->font  : -1);
1226     int font_input                 = (pos_input ? pos_input->font : -1);
1227
1228     int font_text_width   = (font_text  != -1 ? getFontWidth(font_text)   : 0);
1229     int font_text_height  = (font_text  != -1 ? getFontHeight(font_text)  : 0);
1230     int font_input_width  = (font_input != -1 ? getFontWidth(font_input)  : 0);
1231     int font_input_height = (font_input != -1 ? getFontHeight(font_input) : 0);
1232     int text_chars  = (text  != NULL ? strlen(text)  : 0);
1233     int input_chars = (input != NULL ? strlen(input) : 0);
1234
1235     int button_width =
1236       (button_graphic != -1 ? graphic_info[button_graphic].width  : 0);
1237     int button_height =
1238       (button_graphic != -1 ? graphic_info[button_graphic].height : 0);
1239     int text_width   = font_text_width * text_chars;
1240     int text_height  = font_text_height;
1241     int input_width  = font_input_width * input_chars;
1242     int input_height = font_input_height;
1243
1244     if (nr == MAIN_CONTROL_NAME)
1245     {
1246       menu.main.input.name.width  = input_width;
1247       menu.main.input.name.height = input_height;
1248     }
1249
1250     if (pos_button != NULL)             // (x/y may be -1/-1 here)
1251     {
1252       pos_button->width  = button_width;
1253       pos_button->height = button_height;
1254     }
1255
1256     if (pos_text != NULL)               // (x/y may be -1/-1 here)
1257     {
1258       // calculate text size -- needed for text alignment
1259       boolean calculate_text_size = (text != NULL);
1260
1261       if (pos_text->width == -1 || calculate_text_size)
1262         pos_text->width = text_width;
1263       if (pos_text->height == -1 || calculate_text_size)
1264         pos_text->height = text_height;
1265
1266       if (visibleMenuPos(pos_button))
1267       {
1268         if (pos_text->x == -1)
1269           pos_text->x = pos_button->x + pos_button->width;
1270         if (pos_text->y == -1)
1271           pos_text->y =
1272             pos_button->y + (pos_button->height - pos_text->height) / 2;
1273       }
1274     }
1275
1276     if (pos_input != NULL)              // (x/y may be -1/-1 here)
1277     {
1278       if (visibleTextPos(pos_text))
1279       {
1280         if (pos_input->x == -1)
1281           pos_input->x = pos_text->x + pos_text->width;
1282         if (pos_input->y == -1)
1283           pos_input->y = pos_text->y;
1284       }
1285
1286       if (pos_input->width == -1)
1287         pos_input->width = input_width;
1288       if (pos_input->height == -1)
1289         pos_input->height = input_height;
1290     }
1291   }
1292 }
1293
1294 static void DrawPressedGraphicThruMask(int dst_x, int dst_y,
1295                                        int graphic, boolean pressed)
1296 {
1297   struct GraphicInfo *g = &graphic_info[graphic];
1298   Bitmap *src_bitmap;
1299   int src_x, src_y;
1300   int xoffset = (pressed ? g->pressed_xoffset : 0);
1301   int yoffset = (pressed ? g->pressed_yoffset : 0);
1302
1303   getFixedGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y);
1304
1305   BlitBitmapMasked(src_bitmap, drawto, src_x + xoffset, src_y + yoffset,
1306                    g->width, g->height, dst_x, dst_y);
1307 }
1308
1309 static void DrawCursorAndText_Main_Ext(int nr, boolean active_text,
1310                                        boolean active_input,
1311                                        boolean pressed_button)
1312 {
1313   int i;
1314
1315   for (i = 0; main_controls[i].nr != -1; i++)
1316   {
1317     struct MainControlInfo *mci = &main_controls[i];
1318
1319     if (mci->nr == nr || nr == -1)
1320     {
1321       struct MenuPosInfo *pos_button = mci->pos_button;
1322       struct TextPosInfo *pos_text   = mci->pos_text;
1323       struct TextPosInfo *pos_input  = mci->pos_input;
1324       char *text                     = (mci->text  ? *mci->text  : NULL);
1325       char *input                    = (mci->input ? *mci->input : NULL);
1326       int button_graphic             = mci->button_graphic;
1327       int font_text                  = (pos_text  ? pos_text->font  : -1);
1328       int font_input                 = (pos_input ? pos_input->font : -1);
1329
1330       if (active_text)
1331       {
1332         button_graphic = BUTTON_ACTIVE(button_graphic);
1333         font_text = FONT_ACTIVE(font_text);
1334       }
1335
1336       if (active_input)
1337       {
1338         font_input = FONT_ACTIVE(font_input);
1339       }
1340
1341       if (visibleMenuPos(pos_button))
1342       {
1343         struct MenuPosInfo *pos = pos_button;
1344         int x = mSX + pos->x;
1345         int y = mSY + pos->y;
1346
1347         DrawBackgroundForGraphic(x, y, pos->width, pos->height, button_graphic);
1348         DrawPressedGraphicThruMask(x, y, button_graphic, pressed_button);
1349       }
1350
1351       if (visibleTextPos(pos_text) && text != NULL)
1352       {
1353         struct TextPosInfo *pos = pos_text;
1354         int x = mSX + ALIGNED_TEXT_XPOS(pos);
1355         int y = mSY + ALIGNED_TEXT_YPOS(pos);
1356
1357 #if 1
1358         // (check why/if this is needed)
1359         DrawBackgroundForFont(x, y, pos->width, pos->height, font_text);
1360 #endif
1361         DrawText(x, y, text, font_text);
1362       }
1363
1364       if (visibleTextPos(pos_input) && input != NULL)
1365       {
1366         struct TextPosInfo *pos = pos_input;
1367         int x = mSX + ALIGNED_TEXT_XPOS(pos);
1368         int y = mSY + ALIGNED_TEXT_YPOS(pos);
1369
1370 #if 1
1371         // (check why/if this is needed)
1372         DrawBackgroundForFont(x, y, pos->width, pos->height, font_input);
1373 #endif
1374         DrawText(x, y, input, font_input);
1375       }
1376     }
1377   }
1378 }
1379
1380 static void DrawCursorAndText_Main(int nr, boolean active_text,
1381                                    boolean pressed_button)
1382 {
1383   DrawCursorAndText_Main_Ext(nr, active_text, FALSE, pressed_button);
1384 }
1385
1386 #if 0
1387 static void DrawCursorAndText_Main_Input(int nr, boolean active_text,
1388                                          boolean pressed_button)
1389 {
1390   DrawCursorAndText_Main_Ext(nr, active_text, TRUE, pressed_button);
1391 }
1392 #endif
1393
1394 static struct MainControlInfo *getMainControlInfo(int nr)
1395 {
1396   int i;
1397
1398   for (i = 0; main_controls[i].nr != -1; i++)
1399     if (main_controls[i].nr == nr)
1400       return &main_controls[i];
1401
1402   return NULL;
1403 }
1404
1405 static boolean insideMenuPosRect(struct MenuPosInfo *rect, int x, int y)
1406 {
1407   if (rect == NULL)
1408     return FALSE;
1409
1410   int rect_x = ALIGNED_TEXT_XPOS(rect);
1411   int rect_y = ALIGNED_TEXT_YPOS(rect);
1412
1413   return (x >= rect_x && x < rect_x + rect->width &&
1414           y >= rect_y && y < rect_y + rect->height);
1415 }
1416
1417 static boolean insideTextPosRect(struct TextPosInfo *rect, int x, int y)
1418 {
1419   if (rect == NULL)
1420     return FALSE;
1421
1422   int rect_x = ALIGNED_TEXT_XPOS(rect);
1423   int rect_y = ALIGNED_TEXT_YPOS(rect);
1424
1425 #if 0
1426   Debug("screens:insideTextPosRect",
1427         "(%d, %d), (%d, %d) [%d, %d] (%d, %d) => %d",
1428         x, y, rect_x, rect_y, rect->x, rect->y, rect->width, rect->height,
1429         (x >= rect_x && x < rect_x + rect->width &&
1430          y >= rect_y && y < rect_y + rect->height));
1431 #endif
1432
1433   return (x >= rect_x && x < rect_x + rect->width &&
1434           y >= rect_y && y < rect_y + rect->height);
1435 }
1436
1437 static boolean insidePreviewRect(struct PreviewInfo *preview, int x, int y)
1438 {
1439   int rect_width  = preview->xsize * preview->tile_size;
1440   int rect_height = preview->ysize * preview->tile_size;
1441   int rect_x = ALIGNED_XPOS(preview->x, rect_width,  preview->align);
1442   int rect_y = ALIGNED_YPOS(preview->y, rect_height, preview->valign);
1443
1444   return (x >= rect_x && x < rect_x + rect_width &&
1445           y >= rect_y && y < rect_y + rect_height);
1446 }
1447
1448 static void AdjustScrollbar(int id, int items_max, int items_visible,
1449                             int item_position)
1450 {
1451   struct GadgetInfo *gi = screen_gadget[id];
1452
1453   if (item_position > items_max - items_visible)
1454     item_position = items_max - items_visible;
1455
1456   ModifyGadget(gi, GDI_SCROLLBAR_ITEMS_MAX, items_max,
1457                GDI_SCROLLBAR_ITEMS_VISIBLE, items_visible,
1458                GDI_SCROLLBAR_ITEM_POSITION, item_position, GDI_END);
1459 }
1460
1461 static void AdjustChooseTreeScrollbar(TreeInfo *ti, int id)
1462 {
1463   AdjustScrollbar(id, numTreeInfoInGroup(ti), NUM_MENU_ENTRIES_ON_SCREEN,
1464                   ti->cl_first);
1465 }
1466
1467 static void clearMenuListArea(void)
1468 {
1469   int scrollbar_xpos = mSX + SC_SCROLLBAR_XPOS + menu.scrollbar_xoffset;
1470
1471   // correct scrollbar position if placed outside menu (playfield) area
1472   if (scrollbar_xpos > SX + SC_SCROLLBAR_XPOS)
1473     scrollbar_xpos = SX + SC_SCROLLBAR_XPOS;
1474
1475   // clear menu list area, but not title or scrollbar
1476   DrawBackground(mSX, mSY + MENU_SCREEN_START_YPOS * 32,
1477                  scrollbar_xpos - mSX, NUM_MENU_ENTRIES_ON_SCREEN * 32);
1478
1479   // special compatibility handling for "Snake Bite" graphics set
1480   if (strPrefix(leveldir_current->identifier, "snake_bite"))
1481     ClearRectangle(drawto, mSX, mSY + MENU_SCREEN_START_YPOS * 32,
1482                    scrollbar_xpos - mSX, NUM_MENU_ENTRIES_ON_SCREEN * 32);
1483 }
1484
1485 static void drawCursorExt(int xpos, int ypos, boolean active, int graphic)
1486 {
1487   static int cursor_array[MAX_LEV_FIELDY];
1488   int x = amSX + TILEX * xpos;
1489   int y = amSY + TILEY * (MENU_SCREEN_START_YPOS + ypos);
1490
1491   if (xpos == 0)
1492   {
1493     if (graphic != -1)
1494       cursor_array[ypos] = graphic;
1495     else
1496       graphic = cursor_array[ypos];
1497   }
1498
1499   if (active)
1500     graphic = BUTTON_ACTIVE(graphic);
1501
1502   DrawBackgroundForGraphic(x, y, TILEX, TILEY, graphic);
1503   DrawFixedGraphicThruMaskExt(drawto, x, y, graphic, 0);
1504 }
1505
1506 static void initCursor(int ypos, int graphic)
1507 {
1508   drawCursorExt(0, ypos, FALSE, graphic);
1509 }
1510
1511 static void drawCursor(int ypos, boolean active)
1512 {
1513   drawCursorExt(0, ypos, active, -1);
1514 }
1515
1516 static void drawCursorXY(int xpos, int ypos, int graphic)
1517 {
1518   drawCursorExt(xpos, ypos, FALSE, graphic);
1519 }
1520
1521 static void drawChooseTreeCursor(int ypos, boolean active)
1522 {
1523   drawCursorExt(0, ypos, active, -1);
1524 }
1525
1526 static int getChooseTreeEditFont(boolean active)
1527 {
1528   return (active ? FONT_MENU_2_ACTIVE : FONT_MENU_2);
1529 }
1530
1531 static int getChooseTreeEditXPos(int pos)
1532 {
1533   boolean has_scrollbar = screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->mapped;
1534   int xoffset = (has_scrollbar ? -1 : 0);
1535   int xpos = MENU_SCREEN_TEXT2_XPOS + xoffset;
1536   int sx = amSX + xpos * TILEX;
1537   int font_nr = getChooseTreeEditFont(FALSE);
1538   int width = getTextWidth(STR_CHOOSE_TREE_EDIT, font_nr);
1539
1540   return (pos == POS_RIGHT ? sx + width - 1 : sx);
1541 }
1542
1543 static int getChooseTreeEditYPos(int ypos_raw)
1544 {
1545   int ypos = MENU_SCREEN_START_YPOS + ypos_raw;
1546   int sy = amSY + ypos * TILEY;
1547
1548   return sy;
1549 }
1550
1551 static int getChooseTreeEditXPosReal(int pos)
1552 {
1553   int xpos = getChooseTreeEditXPos(pos);
1554   int font_nr = getChooseTreeEditFont(FALSE);
1555   int font_xoffset = getFontDrawOffsetX(font_nr);
1556
1557   return xpos + font_xoffset;
1558 }
1559
1560 static void drawChooseTreeEdit(int ypos_raw, boolean active)
1561 {
1562   int sx = getChooseTreeEditXPos(POS_LEFT);
1563   int sy = getChooseTreeEditYPos(ypos_raw);
1564   int font_nr = getChooseTreeEditFont(active);
1565
1566   DrawText(sx, sy, STR_CHOOSE_TREE_EDIT, font_nr);
1567 }
1568
1569 static void DrawInfoScreen_Headline(int screen_nr, int num_screens,
1570                                     int use_global_screens)
1571 {
1572   char *info_text_title_1 = getInfoScreenTitle_Generic();
1573   char info_text_title_2[MAX_LINE_LEN + 1];
1574
1575   if (num_screens > 1)
1576   {
1577     sprintf(info_text_title_2, "Page %d of %d", screen_nr + 1, num_screens);
1578   }
1579   else
1580   {
1581     char *text_format = (use_global_screens ? "for %s" : "for \"%s\"");
1582     int text_format_len = strlen(text_format) - strlen("%s");
1583     int max_text_len = SXSIZE / getFontWidth(FONT_TITLE_2);
1584     int max_name_len = max_text_len - text_format_len;
1585     char name_cut[max_name_len];
1586     char *name_full = (use_global_screens ? getProgramTitleString() :
1587                        leveldir_current->name);
1588
1589     snprintf(name_cut, max_name_len, "%s", name_full);
1590     snprintf(info_text_title_2, max_text_len, text_format, name_cut);
1591   }
1592
1593   DrawTextSCentered(MENU_TITLE1_YPOS, FONT_TITLE_1, info_text_title_1);
1594   DrawTextSCentered(MENU_TITLE2_YPOS, FONT_TITLE_2, info_text_title_2);
1595 }
1596
1597 static void DrawTitleScreenImage(int nr, boolean initial)
1598 {
1599   int graphic = getTitleScreenGraphic(nr, initial);
1600   Bitmap *bitmap = graphic_info[graphic].bitmap;
1601   int draw_masked = graphic_info[graphic].draw_masked;
1602   int width  = graphic_info[graphic].width;
1603   int height = graphic_info[graphic].height;
1604   int src_x = graphic_info[graphic].src_x;
1605   int src_y = graphic_info[graphic].src_y;
1606   int dst_x, dst_y;
1607
1608   if (bitmap == NULL)
1609     return;
1610
1611   if (width > WIN_XSIZE)
1612   {
1613     // image width too large for window => center image horizontally
1614     src_x = (width - WIN_XSIZE) / 2;
1615     width = WIN_XSIZE;
1616   }
1617
1618   if (height > WIN_YSIZE)
1619   {
1620     // image height too large for window => center image vertically
1621     src_y = (height - WIN_YSIZE) / 2;
1622     height = WIN_YSIZE;
1623   }
1624
1625   // always display title screens centered
1626   dst_x = (WIN_XSIZE - width) / 2;
1627   dst_y = (WIN_YSIZE - height) / 2;
1628
1629   SetDrawBackgroundMask(REDRAW_ALL);
1630   SetWindowBackgroundImage(getTitleBackground(nr, initial, TRUE));
1631
1632   ClearRectangleOnBackground(drawto, 0, 0, WIN_XSIZE, WIN_YSIZE);
1633
1634   if (DrawingOnBackground(dst_x, dst_y) && draw_masked)
1635     BlitBitmapMasked(bitmap, drawto, src_x, src_y, width, height, dst_x, dst_y);
1636   else
1637     BlitBitmap(bitmap, drawto, src_x, src_y, width, height, dst_x, dst_y);
1638
1639   redraw_mask = REDRAW_ALL;
1640 }
1641
1642 static void DrawTitleScreenMessage(int nr, boolean initial)
1643 {
1644   char *filename = getLevelSetTitleMessageFilename(nr, initial);
1645   struct TitleMessageInfo *tmi = getTitleMessageInfo(nr, initial);
1646
1647   if (filename == NULL)
1648     return;
1649
1650   // force TITLE font on title message screen
1651   SetFontStatus(getTitleMessageGameMode(initial));
1652
1653   // if chars *and* width set to "-1", automatically determine width
1654   if (tmi->chars == -1 && tmi->width == -1)
1655     tmi->width = viewport.window[game_status].width;
1656
1657   // if lines *and* height set to "-1", automatically determine height
1658   if (tmi->lines == -1 && tmi->height == -1)
1659     tmi->height = viewport.window[game_status].height;
1660
1661   // if chars set to "-1", automatically determine by text and font width
1662   if (tmi->chars == -1)
1663     tmi->chars = tmi->width / getFontWidth(tmi->font);
1664   else
1665     tmi->width = tmi->chars * getFontWidth(tmi->font);
1666
1667   // if lines set to "-1", automatically determine by text and font height
1668   if (tmi->lines == -1)
1669     tmi->lines = tmi->height / getFontHeight(tmi->font);
1670   else
1671     tmi->height = tmi->lines * getFontHeight(tmi->font);
1672
1673   // if x set to "-1", automatically determine by width and alignment
1674   if (tmi->x == -1)
1675     tmi->x = -1 * ALIGNED_XPOS(0, tmi->width, tmi->align);
1676
1677   // if y set to "-1", automatically determine by height and alignment
1678   if (tmi->y == -1)
1679     tmi->y = -1 * ALIGNED_YPOS(0, tmi->height, tmi->valign);
1680
1681   SetDrawBackgroundMask(REDRAW_ALL);
1682   SetWindowBackgroundImage(getTitleBackground(nr, initial, FALSE));
1683
1684   ClearRectangleOnBackground(drawto, 0, 0, WIN_XSIZE, WIN_YSIZE);
1685
1686   DrawTextFile(ALIGNED_TEXT_XPOS(tmi), ALIGNED_TEXT_YPOS(tmi),
1687                filename, tmi->font, tmi->chars, -1, tmi->lines, 0, -1,
1688                tmi->autowrap, tmi->centered, tmi->parse_comments);
1689
1690   ResetFontStatus();
1691 }
1692
1693 static void DrawTitleScreen(void)
1694 {
1695   KeyboardAutoRepeatOff();
1696
1697   HandleTitleScreen(0, 0, 0, 0, MB_MENU_INITIALIZE);
1698 }
1699
1700 static boolean CheckTitleScreen(boolean levelset_has_changed)
1701 {
1702   static boolean show_title_initial = TRUE;
1703   boolean show_titlescreen = FALSE;
1704
1705   // needed to be able to skip title screen, if no image or message defined
1706   InitializeTitleControls(show_title_initial);
1707
1708   if (setup.show_titlescreen && (show_title_initial || levelset_has_changed))
1709     show_titlescreen = TRUE;
1710
1711   // show initial title images and messages only once at program start
1712   show_title_initial = FALSE;
1713
1714   return (show_titlescreen && num_title_screens > 0);
1715 }
1716
1717 void DrawMainMenu(void)
1718 {
1719   static LevelDirTree *leveldir_last_valid = NULL;
1720   boolean levelset_has_changed = FALSE;
1721   int fade_mask = REDRAW_FIELD;
1722
1723   LimitScreenUpdates(FALSE);
1724
1725   FadeSetLeaveScreen();
1726
1727   // do not fade out here -- function may continue and fade on editor screen
1728
1729   UnmapAllGadgets();
1730   FadeMenuSoundsAndMusic();
1731
1732   ExpireSoundLoops(FALSE);
1733
1734   KeyboardAutoRepeatOn();
1735
1736   audio.sound_deactivated = FALSE;
1737
1738   GetPlayerConfig();
1739
1740   // needed if last screen was the playing screen, invoked from level editor
1741   if (level_editor_test_game)
1742   {
1743     CloseDoor(DOOR_CLOSE_ALL);
1744
1745     SetGameStatus(GAME_MODE_EDITOR);
1746
1747     DrawLevelEd();
1748
1749     return;
1750   }
1751
1752   // needed if last screen was the playing screen, invoked from hall of fame
1753   if (score_info_tape_play)
1754   {
1755     CloseDoor(DOOR_CLOSE_ALL);
1756
1757     SetGameStatus(GAME_MODE_SCOREINFO);
1758
1759     DrawScoreInfo(scores.last_entry_nr);
1760
1761     return;
1762   }
1763
1764   // reset flag to continue playing next level from hall of fame
1765   scores.continue_playing = FALSE;
1766
1767   // leveldir_current may be invalid (level group, parent link, node copy)
1768   leveldir_current = getValidLevelSeries(leveldir_current, leveldir_last_valid);
1769
1770   if (leveldir_current != leveldir_last_valid)
1771   {
1772     // level setup config may have been loaded to "last played" tree node copy,
1773     // but "leveldir_current" now points to the "original" level set tree node,
1774     // in which case "handicap_level" may still default to the first level
1775     LoadLevelSetup_SeriesInfo();
1776
1777     UpdateLastPlayedLevels_TreeInfo();
1778
1779     levelset_has_changed = TRUE;
1780   }
1781
1782   // store valid level series information
1783   leveldir_last_valid = leveldir_current;
1784
1785   // store first level of this level set for "level_nr" style animations
1786   SetAnimationFirstLevel(leveldir_current->first_level);
1787
1788   // needed if last screen (level choice) changed graphics, sounds or music
1789   ReloadCustomArtwork(0);
1790
1791   if (CheckTitleScreen(levelset_has_changed))
1792   {
1793     SetGameStatus(GAME_MODE_TITLE);
1794
1795     DrawTitleScreen();
1796
1797     return;
1798   }
1799
1800   if (redraw_mask & REDRAW_ALL)
1801     fade_mask = REDRAW_ALL;
1802
1803   if (CheckFadeAll())
1804     fade_mask = REDRAW_ALL;
1805
1806   FadeOut(fade_mask);
1807
1808   // needed if different viewport properties defined for menues
1809   ChangeViewportPropertiesIfNeeded();
1810
1811   SetDrawtoField(DRAW_TO_BACKBUFFER);
1812
1813   // level_nr may have been set to value over handicap with level editor
1814   if (setup.handicap && level_nr > leveldir_current->handicap_level)
1815     level_nr = leveldir_current->handicap_level;
1816
1817   LoadLevel(level_nr);
1818   LoadScore(level_nr);
1819
1820   SaveLevelSetup_SeriesInfo();
1821
1822   // set this after "ChangeViewportPropertiesIfNeeded()" (which may reset it)
1823   SetDrawDeactivationMask(REDRAW_NONE);
1824   SetDrawBackgroundMask(REDRAW_FIELD);
1825
1826   SetMainBackgroundImage(IMG_BACKGROUND_MAIN);
1827
1828 #if 0
1829   if (fade_mask == REDRAW_ALL)
1830     RedrawGlobalBorder();
1831 #endif
1832
1833   ClearField();
1834
1835   InitializeMainControls();
1836
1837   DrawCursorAndText_Main(-1, FALSE, FALSE);
1838   DrawPreviewLevelInitial();
1839   DrawNetworkPlayers();
1840
1841   HandleMainMenu(0, 0, 0, 0, MB_MENU_INITIALIZE);
1842
1843   TapeStop();
1844   if (TAPE_IS_EMPTY(tape))
1845     LoadTape(level_nr);
1846   DrawCompleteVideoDisplay();
1847
1848   PlayMenuSoundsAndMusic();
1849
1850   // create gadgets for main menu screen
1851   FreeScreenGadgets();
1852   CreateScreenGadgets();
1853
1854   // may be required if audio buttons shown on tape and changed in setup menu
1855   FreeGameButtons();
1856   CreateGameButtons();
1857
1858   // map gadgets for main menu screen
1859   MapTapeButtons();
1860   MapScreenMenuGadgets(SCREEN_MASK_MAIN);
1861   UpdateScreenMenuGadgets(SCREEN_MASK_MAIN_HAS_SOLUTION, hasSolutionTape());
1862   UpdateScreenMenuGadgets(SCREEN_MASK_MAIN_HAS_SET_INFO, hasLevelSetInfo());
1863
1864   // copy actual game door content to door double buffer for OpenDoor()
1865   BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
1866   BlitBitmap(drawto, bitmap_db_door_2, VX, VY, VXSIZE, VYSIZE, 0, 0);
1867
1868   OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
1869
1870   DrawMaskedBorder(fade_mask);
1871
1872   FadeIn(fade_mask);
1873   FadeSetEnterMenu();
1874
1875   // update screen area with special editor door
1876   redraw_mask |= REDRAW_ALL;
1877   BackToFront();
1878
1879   SetMouseCursor(CURSOR_DEFAULT);
1880
1881   OpenDoor(DOOR_CLOSE_1 | DOOR_OPEN_2);
1882
1883   SyncEmscriptenFilesystem();
1884
1885   // needed once after program start or after user change
1886   CheckApiServerTasks();
1887 }
1888
1889 static void gotoTopLevelDir(void)
1890 {
1891   // move upwards until inside (but not above) top level directory
1892   while (leveldir_current->node_parent &&
1893          !strEqual(leveldir_current->node_parent->subdir, STRING_TOP_DIRECTORY))
1894   {
1895     // write a "path" into level tree for easy navigation to last level
1896     if (leveldir_current->node_parent->node_group->cl_first == -1)
1897     {
1898       int num_leveldirs = numTreeInfoInGroup(leveldir_current);
1899       int leveldir_pos = getPosFromTreeInfo(leveldir_current);
1900       int num_page_entries = MIN(num_leveldirs, NUM_MENU_ENTRIES_ON_SCREEN);
1901       int cl_first, cl_cursor;
1902
1903       cl_first = MAX(0, leveldir_pos - num_page_entries + 1);
1904       cl_cursor = leveldir_pos - cl_first;
1905
1906       leveldir_current->node_parent->node_group->cl_first = cl_first;
1907       leveldir_current->node_parent->node_group->cl_cursor = cl_cursor;
1908     }
1909
1910     leveldir_current = leveldir_current->node_parent;
1911   }
1912 }
1913
1914 static unsigned int getAutoDelayCounter(struct TitleFadingInfo *fi)
1915 {
1916   boolean use_frame_counter = (fi->auto_delay_unit == AUTO_DELAY_UNIT_FRAMES);
1917
1918   return (use_frame_counter ? video.frame_counter : Counter());
1919 }
1920
1921 static boolean TitleAutoDelayReached(unsigned int *counter_var,
1922                                      struct TitleFadingInfo *fi)
1923 {
1924   return DelayReachedExt2(counter_var, fi->auto_delay, getAutoDelayCounter(fi));
1925 }
1926
1927 static void ResetTitleAutoDelay(unsigned int *counter_var,
1928                                 struct TitleFadingInfo *fi)
1929 {
1930   *counter_var = getAutoDelayCounter(fi);
1931 }
1932
1933 void HandleTitleScreen(int mx, int my, int dx, int dy, int button)
1934 {
1935   static unsigned int title_delay = 0;
1936   static int title_screen_nr = 0;
1937   static int last_sound = -1, last_music = -1;
1938   boolean return_to_main_menu = FALSE;
1939   struct TitleControlInfo *tci;
1940   int sound, music;
1941
1942   if (button == MB_MENU_INITIALIZE)
1943   {
1944     title_delay = 0;
1945     title_screen_nr = 0;
1946     tci = &title_controls[title_screen_nr];
1947
1948     SetAnimStatus(getTitleAnimMode(tci));
1949
1950     last_sound = SND_UNDEFINED;
1951     last_music = MUS_UNDEFINED;
1952
1953     if (num_title_screens != 0)
1954     {
1955       FadeSetEnterScreen();
1956
1957       // use individual title fading instead of global "enter screen" fading
1958       fading = getTitleFading(tci);
1959     }
1960
1961     if (game_status_last_screen == GAME_MODE_INFO)
1962     {
1963       if (num_title_screens == 0)
1964       {
1965         // switch game mode from title screen mode back to info screen mode
1966         SetGameStatus(GAME_MODE_INFO);
1967
1968         // store that last screen was info screen, not main menu screen
1969         game_status_last_screen = GAME_MODE_INFO;
1970
1971         DrawInfoScreen_NotAvailable("Title screen information:",
1972                                     "No title screen for this level set.");
1973         return;
1974       }
1975     }
1976
1977     FadeMenuSoundsAndMusic();
1978
1979     FadeOut(REDRAW_ALL);
1980
1981     // title screens may have different window size
1982     ChangeViewportPropertiesIfNeeded();
1983
1984     // only required to update logic for redrawing global border
1985     ClearField();
1986
1987     if (tci->is_image)
1988       DrawTitleScreenImage(tci->local_nr, tci->initial);
1989     else
1990       DrawTitleScreenMessage(tci->local_nr, tci->initial);
1991
1992     sound = getTitleSound(tci);
1993     music = getTitleMusic(tci);
1994
1995     if (sound != last_sound)
1996       PlayMenuSoundExt(sound);
1997     if (music != last_music)
1998       PlayMenuMusicExt(music);
1999
2000     last_sound = sound;
2001     last_music = music;
2002
2003     SetMouseCursor(CURSOR_NONE);
2004
2005     FadeIn(REDRAW_ALL);
2006
2007     ResetTitleAutoDelay(&title_delay, &fading);
2008
2009     return;
2010   }
2011
2012   if (fading.auto_delay > 0 && TitleAutoDelayReached(&title_delay, &fading))
2013     button = MB_MENU_CHOICE;
2014
2015   if (button == MB_MENU_LEAVE)
2016   {
2017     return_to_main_menu = TRUE;
2018   }
2019   else if (button == MB_MENU_CHOICE || dx)
2020   {
2021     if (game_status_last_screen == GAME_MODE_INFO && num_title_screens == 0)
2022     {
2023       SetGameStatus(GAME_MODE_INFO);
2024
2025       info_mode = INFO_MODE_MAIN;
2026
2027       DrawInfoScreen();
2028
2029       return;
2030     }
2031
2032     title_screen_nr +=
2033       (game_status_last_screen == GAME_MODE_INFO && dx < 0 ? -1 : +1);
2034
2035     if (title_screen_nr >= 0 && title_screen_nr < num_title_screens)
2036     {
2037       tci = &title_controls[title_screen_nr];
2038
2039       SetAnimStatus(getTitleAnimMode(tci));
2040
2041       sound = getTitleSound(tci);
2042       music = getTitleMusic(tci);
2043
2044       if (last_sound != SND_UNDEFINED && sound != last_sound)
2045         FadeSound(last_sound);
2046       if (last_music != MUS_UNDEFINED && music != last_music)
2047         FadeMusic();
2048
2049       fading = getTitleFading(tci);
2050
2051       FadeOut(REDRAW_ALL);
2052
2053       if (tci->is_image)
2054         DrawTitleScreenImage(tci->local_nr, tci->initial);
2055       else
2056         DrawTitleScreenMessage(tci->local_nr, tci->initial);
2057
2058       sound = getTitleSound(tci);
2059       music = getTitleMusic(tci);
2060
2061       if (sound != last_sound)
2062         PlayMenuSoundExt(sound);
2063       if (music != last_music)
2064         PlayMenuMusicExt(music);
2065
2066       last_sound = sound;
2067       last_music = music;
2068
2069       FadeIn(REDRAW_ALL);
2070
2071       ResetTitleAutoDelay(&title_delay, &fading);
2072     }
2073     else
2074     {
2075       FadeMenuSoundsAndMusic();
2076
2077       return_to_main_menu = TRUE;
2078     }
2079   }
2080
2081   if (return_to_main_menu)
2082   {
2083     SetMouseCursor(CURSOR_DEFAULT);
2084
2085     // force full menu screen redraw after displaying title screens
2086     redraw_mask = REDRAW_ALL;
2087
2088     if (game_status_last_screen == GAME_MODE_INFO)
2089     {
2090       SetGameStatus(GAME_MODE_INFO);
2091
2092       info_mode = INFO_MODE_MAIN;
2093
2094       DrawInfoScreen();
2095     }
2096     else        // default: return to main menu
2097     {
2098       SetGameStatus(GAME_MODE_MAIN);
2099
2100       DrawMainMenu();
2101     }
2102   }
2103 }
2104
2105 static void HandleMainMenu_ToggleTeamMode(void)
2106 {
2107   setup.team_mode = !setup.team_mode;
2108
2109   InitializeMainControls();
2110   DrawCursorAndText_Main(MAIN_CONTROL_NAME, TRUE, FALSE);
2111
2112   DrawPreviewPlayers();
2113 }
2114
2115 static void HandleMainMenu_SelectLevel(int step, int direction,
2116                                        int selected_level_nr)
2117 {
2118   int old_level_nr = level_nr;
2119   int new_level_nr;
2120
2121   if (selected_level_nr != NO_DIRECT_LEVEL_SELECT)
2122     new_level_nr = selected_level_nr;
2123   else
2124     new_level_nr = old_level_nr + step * direction;
2125
2126   if (new_level_nr < leveldir_current->first_level)
2127     new_level_nr = leveldir_current->first_level;
2128   if (new_level_nr > leveldir_current->last_level)
2129     new_level_nr = leveldir_current->last_level;
2130
2131   if (setup.handicap && new_level_nr > leveldir_current->handicap_level)
2132   {
2133     // skipping levels is only allowed when trying to skip single level
2134     // (also, skipping BD style intermission levels is always possible)
2135     if (new_level_nr == old_level_nr + 1 &&
2136         (level.bd_intermission ||
2137          (setup.skip_levels && Request("Level still unsolved! Skip it anyway?", REQ_ASK))))
2138     {
2139       leveldir_current->handicap_level++;
2140       SaveLevelSetup_SeriesInfo();
2141     }
2142
2143     new_level_nr = leveldir_current->handicap_level;
2144   }
2145
2146   if (new_level_nr != old_level_nr)
2147   {
2148     struct MainControlInfo *mci = getMainControlInfo(MAIN_CONTROL_LEVEL_NUMBER);
2149
2150     PlaySound(SND_MENU_ITEM_SELECTING);
2151
2152     level_nr = new_level_nr;
2153
2154     DrawText(mSX + mci->pos_text->x, mSY + mci->pos_text->y,
2155              int2str(level_nr, menu.main.text.level_number.size),
2156              mci->pos_text->font);
2157
2158     LoadLevel(level_nr);
2159     DrawPreviewLevelInitial();
2160
2161     TapeErase();
2162     LoadTape(level_nr);
2163     DrawCompleteVideoDisplay();
2164
2165     SaveLevelSetup_SeriesInfo();
2166
2167     UpdateScreenMenuGadgets(SCREEN_MASK_MAIN_HAS_SOLUTION, hasSolutionTape());
2168
2169     // force redraw of playfield area (may be reset at this point)
2170     redraw_mask |= REDRAW_FIELD;
2171   }
2172 }
2173
2174 void HandleMainMenu(int mx, int my, int dx, int dy, int button)
2175 {
2176   static int choice = MAIN_CONTROL_GAME;
2177   static boolean button_pressed_last = FALSE;
2178   boolean button_pressed = FALSE;
2179   int pos = choice;
2180   int i = 0;    // needed to prevent compiler warning due to bad code below
2181
2182   if (button == MB_MENU_INITIALIZE)
2183   {
2184     DrawCursorAndText_Main(choice, TRUE, FALSE);
2185
2186     return;
2187   }
2188
2189   if (mx || my)         // mouse input
2190   {
2191     pos = -1;
2192
2193     for (i = 0; main_controls[i].nr != -1; i++)
2194     {
2195       if (insideMenuPosRect(main_controls[i].pos_button, mx - mSX, my - mSY) ||
2196           insideTextPosRect(main_controls[i].pos_text,   mx - mSX, my - mSY) ||
2197           insideTextPosRect(main_controls[i].pos_input,  mx - mSX, my - mSY))
2198       {
2199         pos = main_controls[i].nr;
2200
2201         break;
2202       }
2203     }
2204
2205     // check if level preview was clicked
2206     if (insidePreviewRect(&preview, mx - SX, my - SY))
2207       pos = MAIN_CONTROL_GAME;
2208
2209     // handle pressed/unpressed state for active/inactive menu buttons
2210     // (if pos != -1, "i" contains index position corresponding to "pos")
2211     if (button &&
2212         pos >= MAIN_CONTROL_NAME && pos <= MAIN_CONTROL_QUIT &&
2213         insideMenuPosRect(main_controls[i].pos_button, mx - mSX, my - mSY))
2214       button_pressed = TRUE;
2215
2216     if (button_pressed != button_pressed_last)
2217     {
2218       DrawCursorAndText_Main(choice, TRUE, button_pressed);
2219
2220       if (button_pressed)
2221         PlaySound(SND_MENU_BUTTON_PRESSING);
2222       else
2223         PlaySound(SND_MENU_BUTTON_RELEASING);
2224     }
2225   }
2226   else if (dx || dy)    // keyboard input
2227   {
2228     if (dx > 0 && (choice == MAIN_CONTROL_INFO ||
2229                    choice == MAIN_CONTROL_SETUP))
2230       button = MB_MENU_CHOICE;
2231     else if (dy)
2232       pos = choice + dy;
2233   }
2234
2235   if (pos == MAIN_CONTROL_FIRST_LEVEL && !button)
2236   {
2237     HandleMainMenu_SelectLevel(MAX_LEVELS, -1, NO_DIRECT_LEVEL_SELECT);
2238   }
2239   else if (pos == MAIN_CONTROL_LAST_LEVEL && !button)
2240   {
2241     HandleMainMenu_SelectLevel(MAX_LEVELS, +1, NO_DIRECT_LEVEL_SELECT);
2242   }
2243   else if (pos == MAIN_CONTROL_LEVEL_NUMBER && !button)
2244   {
2245     CloseDoor(DOOR_CLOSE_2);
2246
2247     SetGameStatus(GAME_MODE_LEVELNR);
2248
2249     DrawChooseLevelNr();
2250   }
2251   else if (pos >= MAIN_CONTROL_NAME && pos <= MAIN_CONTROL_QUIT)
2252   {
2253     if (button)
2254     {
2255       if (pos != choice)
2256       {
2257         PlaySound(SND_MENU_ITEM_ACTIVATING);
2258
2259         DrawCursorAndText_Main(choice, FALSE, FALSE);
2260         DrawCursorAndText_Main(pos, TRUE, button_pressed);
2261
2262         choice = pos;
2263       }
2264       else if (dx != 0)
2265       {
2266         if (choice == MAIN_CONTROL_NAME)
2267         {
2268           // special case: cursor left or right pressed -- toggle team mode
2269           HandleMainMenu_ToggleTeamMode();
2270         }
2271         else if (choice != MAIN_CONTROL_INFO &&
2272                  choice != MAIN_CONTROL_SETUP)
2273         {
2274           HandleMainMenu_SelectLevel(1, dx, NO_DIRECT_LEVEL_SELECT);
2275         }
2276       }
2277     }
2278     else
2279     {
2280       PlaySound(SND_MENU_ITEM_SELECTING);
2281
2282       if (pos == MAIN_CONTROL_NAME)
2283       {
2284         if ((mx || my) &&
2285             insideTextPosRect(main_controls[i].pos_text, mx - mSX, my - mSY))
2286         {
2287           // special case: menu text "name/team" clicked -- toggle team mode
2288           HandleMainMenu_ToggleTeamMode();
2289         }
2290         else
2291         {
2292           if (setup.multiple_users)
2293           {
2294             CloseDoor(DOOR_CLOSE_2);
2295
2296             SetGameStatus(GAME_MODE_NAMES);
2297
2298             DrawChoosePlayerName();
2299           }
2300           else
2301           {
2302             SetGameStatus(GAME_MODE_PSEUDO_TYPENAME);
2303
2304             DrawTypeName();
2305           }
2306         }
2307       }
2308       else if (pos == MAIN_CONTROL_LEVELS)
2309       {
2310         if (leveldir_first)
2311         {
2312           CloseDoor(DOOR_CLOSE_2);
2313
2314           SetGameStatus(GAME_MODE_LEVELS);
2315
2316           SaveLevelSetup_LastSeries();
2317           SaveLevelSetup_SeriesInfo();
2318
2319           // restore level set if chosen from "last played level set" menu
2320           RestoreLastPlayedLevels(&leveldir_current);
2321
2322           if (setup.internal.choose_from_top_leveldir)
2323             gotoTopLevelDir();
2324
2325           DrawChooseLevelSet();
2326         }
2327       }
2328       else if (pos == MAIN_CONTROL_SCORES)
2329       {
2330         CloseDoor(DOOR_CLOSE_2);
2331
2332         SetGameStatus(GAME_MODE_SCORES);
2333
2334         DrawHallOfFame(level_nr);
2335       }
2336       else if (pos == MAIN_CONTROL_EDITOR)
2337       {
2338         if (leveldir_current->readonly &&
2339             setup.editor.show_read_only_warning)
2340           Request("This level is read-only!", REQ_CONFIRM | REQ_STAY_OPEN);
2341
2342         CloseDoor(DOOR_CLOSE_ALL);
2343
2344         SetGameStatus(GAME_MODE_EDITOR);
2345
2346         FadeSetEnterScreen();
2347
2348         DrawLevelEd();
2349       }
2350       else if (pos == MAIN_CONTROL_INFO)
2351       {
2352         CloseDoor(DOOR_CLOSE_2);
2353
2354         SetGameStatus(GAME_MODE_INFO);
2355
2356         info_mode = INFO_MODE_MAIN;
2357
2358         DrawInfoScreen();
2359       }
2360       else if (pos == MAIN_CONTROL_GAME)
2361       {
2362         StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2363       }
2364       else if (pos == MAIN_CONTROL_SETUP)
2365       {
2366         CloseDoor(DOOR_CLOSE_2);
2367
2368         SetGameStatus(GAME_MODE_SETUP);
2369
2370         setup_mode = SETUP_MODE_MAIN;
2371
2372         DrawSetupScreen();
2373       }
2374       else if (pos == MAIN_CONTROL_QUIT)
2375       {
2376         SaveLevelSetup_LastSeries();
2377         SaveLevelSetup_SeriesInfo();
2378
2379 #if defined(PLATFORM_EMSCRIPTEN)
2380         Request("Close the browser window to quit!", REQ_CONFIRM);
2381 #else
2382         if (!setup.ask_on_quit_program ||
2383             Request("Do you really want to quit?", REQ_ASK | REQ_STAY_CLOSED))
2384           SetGameStatus(GAME_MODE_QUIT);
2385 #endif
2386       }
2387     }
2388   }
2389
2390   button_pressed_last = button_pressed;
2391 }
2392
2393
2394 // ============================================================================
2395 // info screen functions
2396 // ============================================================================
2397
2398 static struct TokenInfo *info_info;
2399 static int num_info_info;       // number of info entries shown on screen
2400 static int max_info_info;       // total number of info entries in list
2401
2402 static void execInfoTitleScreen(void)
2403 {
2404   info_mode = INFO_MODE_TITLE;
2405
2406   DrawInfoScreen();
2407 }
2408
2409 static void execInfoElements(void)
2410 {
2411   info_mode = INFO_MODE_ELEMENTS;
2412
2413   DrawInfoScreen();
2414 }
2415
2416 static void execInfoMusic(void)
2417 {
2418   info_mode = INFO_MODE_MUSIC;
2419
2420   DrawInfoScreen();
2421 }
2422
2423 static void execInfoCredits(void)
2424 {
2425   info_mode = INFO_MODE_CREDITS;
2426
2427   DrawInfoScreen();
2428 }
2429
2430 static void execInfoProgram(void)
2431 {
2432   info_mode = INFO_MODE_PROGRAM;
2433
2434   DrawInfoScreen();
2435 }
2436
2437 static void execInfoVersion(void)
2438 {
2439   info_mode = INFO_MODE_VERSION;
2440
2441   DrawInfoScreen();
2442 }
2443
2444 static void execInfoLevelSet(void)
2445 {
2446   info_mode = INFO_MODE_LEVELSET;
2447
2448   DrawInfoScreen();
2449 }
2450
2451 static void execExitInfo(void)
2452 {
2453   SetGameStatus(GAME_MODE_MAIN);
2454
2455   DrawMainMenu();
2456 }
2457
2458 static struct TokenInfo info_info_main[] =
2459 {
2460   { TYPE_ENTER_SCREEN,  execInfoTitleScreen,    STR_INFO_TITLE          },
2461   { TYPE_ENTER_SCREEN,  execInfoElements,       STR_INFO_ELEMENTS       },
2462   { TYPE_ENTER_SCREEN,  execInfoMusic,          STR_INFO_MUSIC          },
2463   { TYPE_ENTER_SCREEN,  execInfoCredits,        STR_INFO_CREDITS        },
2464   { TYPE_ENTER_SCREEN,  execInfoProgram,        STR_INFO_PROGRAM        },
2465   { TYPE_ENTER_SCREEN,  execInfoVersion,        STR_INFO_VERSION        },
2466   { TYPE_ENTER_SCREEN,  execInfoLevelSet,       STR_INFO_LEVELSET       },
2467   { TYPE_EMPTY,         NULL,                   ""                      },
2468   { TYPE_LEAVE_MENU,    execExitInfo,           STR_INFO_EXIT           },
2469
2470   { 0,                  NULL,                   NULL                    }
2471 };
2472
2473 static int getMenuTextFont(int type)
2474 {
2475   if (type & (TYPE_SWITCH       |
2476               TYPE_YES_NO       |
2477               TYPE_YES_NO_AUTO  |
2478               TYPE_STRING       |
2479               TYPE_PLAYER       |
2480               TYPE_ECS_AGA      |
2481               TYPE_KEYTEXT      |
2482               TYPE_ENTER_LIST   |
2483               TYPE_TEXT_INPUT))
2484     return FONT_MENU_2;
2485   else
2486     return FONT_MENU_1;
2487 }
2488
2489 static struct TokenInfo *setup_info;
2490 static struct TokenInfo setup_info_input[];
2491
2492 static struct TokenInfo *menu_info;
2493
2494 static void PlayInfoSound(void)
2495 {
2496   int info_sound = getInfoScreenBackgroundSound_Generic();
2497   char *next_sound = getSoundInfoEntryFilename(info_sound);
2498
2499   if (next_sound != NULL)
2500     PlayMenuSoundExt(info_sound);
2501   else
2502     PlayMenuSound();
2503 }
2504
2505 static void PlayInfoSoundIfLoop(void)
2506 {
2507   int info_sound = getInfoScreenBackgroundSound_Generic();
2508   char *next_sound = getSoundInfoEntryFilename(info_sound);
2509
2510   if (next_sound != NULL)
2511     PlayMenuSoundIfLoopExt(info_sound);
2512   else
2513     PlayMenuSoundIfLoop();
2514 }
2515
2516 static void PlayInfoMusic(void)
2517 {
2518   int info_music = getInfoScreenBackgroundMusic_Generic();
2519   char *curr_music = getCurrentlyPlayingMusicFilename();
2520   char *next_music = getMusicInfoEntryFilename(info_music);
2521
2522   if (next_music != NULL)
2523   {
2524     // play music if info screen music differs from current music
2525     if (!strEqual(curr_music, next_music))
2526       PlayMenuMusicExt(info_music);
2527   }
2528   else
2529   {
2530     // only needed if info screen was directly invoked from main menu
2531     PlayMenuMusic();
2532   }
2533 }
2534
2535 static void PlayInfoSoundsAndMusic(void)
2536 {
2537   PlayInfoSound();
2538   PlayInfoMusic();
2539 }
2540
2541 static void FadeInfoSounds(void)
2542 {
2543   FadeSounds();
2544 }
2545
2546 static void FadeInfoMusic(void)
2547 {
2548   int info_music = getInfoScreenBackgroundMusic_Generic();
2549   char *curr_music = getCurrentlyPlayingMusicFilename();
2550   char *next_music = getMusicInfoEntryFilename(info_music);
2551
2552   if (next_music != NULL)
2553   {
2554     // fade music if info screen music differs from current music
2555     if (!strEqual(curr_music, next_music))
2556       FadeMusic();
2557   }
2558 }
2559
2560 static void FadeInfoSoundsAndMusic(void)
2561 {
2562   FadeInfoSounds();
2563   FadeInfoMusic();
2564 }
2565
2566 static void DrawCursorAndText_Menu_Ext(struct TokenInfo *token_info,
2567                                        int screen_pos, int menu_info_pos_raw,
2568                                        boolean active)
2569 {
2570   int pos = (menu_info_pos_raw < 0 ? screen_pos : menu_info_pos_raw);
2571   struct TokenInfo *ti = &token_info[pos];
2572   int xpos = MENU_SCREEN_START_XPOS;
2573   int ypos = MENU_SCREEN_START_YPOS + screen_pos;
2574   int font_nr = getMenuTextFont(ti->type);
2575
2576   if (setup_mode == SETUP_MODE_INPUT)
2577     font_nr = FONT_MENU_1;
2578
2579   if (active)
2580     font_nr = FONT_ACTIVE(font_nr);
2581
2582   DrawText(mSX + xpos * 32, mSY + ypos * 32, ti->text, font_nr);
2583
2584   if (ti->type & ~TYPE_SKIP_ENTRY)
2585     drawCursor(screen_pos, active);
2586 }
2587
2588 static void DrawCursorAndText_Menu(int screen_pos, int menu_info_pos_raw,
2589                                    boolean active)
2590 {
2591   DrawCursorAndText_Menu_Ext(menu_info, screen_pos, menu_info_pos_raw, active);
2592 }
2593
2594 static void DrawCursorAndText_Setup(int screen_pos, int menu_info_pos_raw,
2595                                     boolean active)
2596 {
2597   DrawCursorAndText_Menu_Ext(setup_info, screen_pos, menu_info_pos_raw, active);
2598 }
2599
2600 static char *window_size_text;
2601 static char *scaling_type_text;
2602 static char *network_server_text;
2603
2604 static void drawSetupValue(int, int);
2605
2606 static void drawMenuInfoList(int first_entry, int num_page_entries,
2607                              int max_page_entries)
2608 {
2609   int i;
2610
2611   if (first_entry + num_page_entries > max_page_entries)
2612     first_entry = 0;
2613
2614   clearMenuListArea();
2615
2616   for (i = 0; i < num_page_entries; i++)
2617   {
2618     int menu_info_pos = first_entry + i;
2619     struct TokenInfo *si = &menu_info[menu_info_pos];
2620     void *value_ptr = si->value;
2621
2622     // set some entries to "unchangeable" according to other variables
2623     if ((value_ptr == &setup.sound_simple && !audio.sound_available) ||
2624         (value_ptr == &setup.sound_loops  && !audio.loops_available) ||
2625         (value_ptr == &setup.sound_music  && !audio.music_available) ||
2626         (value_ptr == &setup.fullscreen   && !video.fullscreen_available) ||
2627         (value_ptr == &window_size_text   && !video.window_scaling_available) ||
2628         (value_ptr == &scaling_type_text  && !video.window_scaling_available))
2629       si->type |= TYPE_GHOSTED;
2630
2631     if (si->type & (TYPE_ENTER_MENU|TYPE_ENTER_LIST))
2632       initCursor(i, IMG_MENU_BUTTON_ENTER_MENU);
2633     else if (si->type & (TYPE_LEAVE_MENU|TYPE_LEAVE_LIST))
2634       initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU);
2635     else if (si->type & ~TYPE_SKIP_ENTRY)
2636       initCursor(i, IMG_MENU_BUTTON);
2637
2638     DrawCursorAndText_Menu(i, menu_info_pos, FALSE);
2639
2640     if (si->type & TYPE_STRING)
2641     {
2642       int gadget_id = -1;
2643
2644       if (value_ptr == &network_server_text)
2645         gadget_id = SCREEN_CTRL_ID_NETWORK_SERVER;
2646
2647       if (gadget_id != -1)
2648       {
2649         struct GadgetInfo *gi = screen_gadget[gadget_id];
2650         int xpos = MENU_SCREEN_START_XPOS;
2651         int ypos = MENU_SCREEN_START_YPOS + i;
2652         int x = mSX + xpos * 32;
2653         int y = mSY + ypos * 32;
2654
2655         ModifyGadget(gi, GDI_X, x, GDI_Y, y, GDI_END);
2656       }
2657     }
2658
2659     if (si->type & TYPE_VALUE &&
2660         menu_info == setup_info)
2661       drawSetupValue(i, menu_info_pos);
2662   }
2663 }
2664
2665 static void DrawInfoScreen_Main(void)
2666 {
2667   int fade_mask = REDRAW_FIELD;
2668   int i;
2669
2670   // (needed after displaying info sub-screens directly from main menu)
2671   if (info_screens_from_main)
2672   {
2673     info_screens_from_main = FALSE;
2674
2675     SetGameStatus(GAME_MODE_MAIN);
2676
2677     DrawMainMenu();
2678
2679     return;
2680   }
2681
2682   if (redraw_mask & REDRAW_ALL)
2683     fade_mask = REDRAW_ALL;
2684
2685   if (CheckFadeAll())
2686     fade_mask = REDRAW_ALL;
2687
2688   UnmapAllGadgets();
2689   FadeMenuSoundsAndMusic();
2690
2691   FreeScreenGadgets();
2692   CreateScreenGadgets();
2693
2694   // (needed after displaying title screens which disable auto repeat)
2695   KeyboardAutoRepeatOn();
2696
2697   FadeSetLeaveScreen();
2698
2699   FadeOut(fade_mask);
2700
2701   // needed if different viewport properties defined for info screen
2702   ChangeViewportPropertiesIfNeeded();
2703
2704   SetMainBackgroundImage(IMG_BACKGROUND_INFO);
2705
2706   ClearField();
2707
2708   OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
2709
2710   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, STR_INFO_MAIN);
2711
2712   info_info = info_info_main;
2713
2714   // use modified info screen info without info screen entries marked as hidden
2715   info_info = getSetupInfoFinal(info_info);
2716
2717   // determine maximal number of info entries that can be displayed on screen
2718   num_info_info = 0;
2719   for (i = 0; info_info[i].type != 0 && i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
2720     num_info_info++;
2721
2722   // determine maximal number of info entries available for menu of info screen
2723   max_info_info = 0;
2724   for (i = 0; info_info[i].type != 0; i++)
2725     max_info_info++;
2726
2727   HandleInfoScreen_Main(0, 0, 0, 0, MB_MENU_INITIALIZE);
2728
2729   MapScreenGadgets(max_info_info);
2730
2731   PlayMenuSoundsAndMusic();
2732
2733   DrawMaskedBorder(fade_mask);
2734
2735   FadeIn(fade_mask);
2736 }
2737
2738 static void changeSetupValue(int, int, int);
2739
2740 static void HandleMenuScreen(int mx, int my, int dx, int dy, int button,
2741                              int mode, int num_page_entries,
2742                              int max_page_entries)
2743 {
2744   static int num_page_entries_all_last[NUM_SPECIAL_GFX_ARGS][MAX_MENU_MODES];
2745   static int choice_stores[NUM_SPECIAL_GFX_ARGS][MAX_MENU_MODES];
2746   static int first_entry_stores[NUM_SPECIAL_GFX_ARGS][MAX_MENU_MODES];
2747   boolean has_scrollbar = screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->mapped;
2748   int mx_scrollbar = screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->x;
2749   int mx_right_border = (has_scrollbar ? mx_scrollbar : SX + SXSIZE);
2750   int *num_page_entries_last = num_page_entries_all_last[game_status];
2751   int *choice_store = choice_stores[game_status];
2752   int *first_entry_store = first_entry_stores[game_status];
2753   int choice = choice_store[mode];              // starts with 0
2754   int first_entry = first_entry_store[mode];    // starts with 0
2755   int x = 0;
2756   int y = choice - first_entry;
2757   int y_old = y;
2758   boolean position_set_by_scrollbar = (dx == 999);
2759   int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
2760   int i;
2761
2762   if (button == MB_MENU_INITIALIZE)
2763   {
2764     // check if number of menu page entries has changed (may happen by change
2765     // of custom artwork definition value for 'list_size' for this menu screen)
2766     // (in this case, the last menu position most probably has to be corrected)
2767     if (num_page_entries != num_page_entries_last[mode])
2768     {
2769       choice_store[mode] = first_entry_store[mode] = 0;
2770
2771       choice = first_entry = 0;
2772       y = y_old = 0;
2773
2774       num_page_entries_last[mode] = num_page_entries;
2775     }
2776
2777     // advance to first valid menu entry
2778     while (choice < num_page_entries &&
2779            menu_info[choice].type & TYPE_SKIP_ENTRY)
2780       choice++;
2781
2782     if (position_set_by_scrollbar)
2783       first_entry = first_entry_store[mode] = dy;
2784     else
2785       AdjustScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL, max_page_entries,
2786                       NUM_MENU_ENTRIES_ON_SCREEN, first_entry);
2787
2788     drawMenuInfoList(first_entry, num_page_entries, max_page_entries);
2789
2790     if (choice < first_entry)
2791     {
2792       choice = first_entry;
2793
2794       if (menu_info[choice].type & TYPE_SKIP_ENTRY)
2795         choice++;
2796     }
2797     else if (choice > first_entry + num_page_entries - 1)
2798     {
2799       choice = first_entry + num_page_entries - 1;
2800
2801       if (menu_info[choice].type & TYPE_SKIP_ENTRY)
2802         choice--;
2803     }
2804
2805     choice_store[mode] = choice;
2806
2807     DrawCursorAndText_Menu(choice - first_entry, choice, TRUE);
2808
2809     return;
2810   }
2811   else if (button == MB_MENU_LEAVE)
2812   {
2813     PlaySound(SND_MENU_ITEM_SELECTING);
2814
2815     for (i = 0; i < max_page_entries; i++)
2816     {
2817       if (menu_info[i].type & TYPE_LEAVE_MENU)
2818       {
2819         void (*menu_callback_function)(void) = menu_info[i].value;
2820
2821         FadeSetLeaveMenu();
2822
2823         menu_callback_function();
2824
2825         break;  // absolutely needed because function changes 'menu_info'!
2826       }
2827     }
2828
2829     return;
2830   }
2831
2832   if (mx || my)         // mouse input
2833   {
2834     x = (mx - mSX) / 32;
2835     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
2836   }
2837   else if (dx || dy)    // keyboard or scrollbar/scrollbutton input
2838   {
2839     // move cursor instead of scrolling when already at start/end of list
2840     if (dy == -1 * SCROLL_LINE && first_entry == 0)
2841       dy = -1;
2842     else if (dy == +1 * SCROLL_LINE &&
2843              first_entry + num_page_entries == max_page_entries)
2844       dy = 1;
2845
2846     // handle scrolling screen one line or page
2847     if (y + dy < 0 ||
2848         y + dy > num_page_entries - 1)
2849     {
2850       boolean redraw = FALSE;
2851
2852       if (ABS(dy) == SCROLL_PAGE)
2853         step = num_page_entries - 1;
2854
2855       if (dy < 0 && first_entry > 0)
2856       {
2857         // scroll page/line up
2858
2859         first_entry -= step;
2860         if (first_entry < 0)
2861           first_entry = 0;
2862
2863         redraw = TRUE;
2864       }
2865       else if (dy > 0 && first_entry + num_page_entries < max_page_entries)
2866       {
2867         // scroll page/line down
2868
2869         first_entry += step;
2870         if (first_entry + num_page_entries > max_page_entries)
2871           first_entry = MAX(0, max_page_entries - num_page_entries);
2872
2873         redraw = TRUE;
2874       }
2875
2876       if (redraw)
2877       {
2878         choice += first_entry - first_entry_store[mode];
2879
2880         if (choice < first_entry)
2881         {
2882           choice = first_entry;
2883
2884           if (menu_info[choice].type & TYPE_SKIP_ENTRY)
2885             choice++;
2886         }
2887         else if (choice > first_entry + num_page_entries - 1)
2888         {
2889           choice = first_entry + num_page_entries - 1;
2890
2891           if (menu_info[choice].type & TYPE_SKIP_ENTRY)
2892             choice--;
2893         }
2894         else if (menu_info[choice].type & TYPE_SKIP_ENTRY)
2895         {
2896           choice += SIGN(dy);
2897
2898           if (choice < first_entry ||
2899               choice > first_entry + num_page_entries - 1)
2900           first_entry += SIGN(dy);
2901         }
2902
2903         first_entry_store[mode] = first_entry;
2904         choice_store[mode] = choice;
2905
2906         drawMenuInfoList(first_entry, num_page_entries, max_page_entries);
2907
2908         DrawCursorAndText_Menu(choice - first_entry, choice, TRUE);
2909
2910         AdjustScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL, max_page_entries,
2911                         NUM_MENU_ENTRIES_ON_SCREEN, first_entry);
2912       }
2913
2914       return;
2915     }
2916
2917     if (dx)
2918     {
2919       int menu_navigation_type = (dx < 0 ? TYPE_LEAVE : TYPE_ENTER);
2920
2921       if (menu_info[choice].type & menu_navigation_type ||
2922           menu_info[choice].type & TYPE_BOOLEAN_STYLE ||
2923           menu_info[choice].type & TYPE_YES_NO_AUTO ||
2924           menu_info[choice].type & TYPE_PLAYER)
2925         button = MB_MENU_CHOICE;
2926     }
2927     else if (dy)
2928       y += dy;
2929
2930     // jump to next non-empty menu entry (up or down)
2931     while (first_entry + y > 0 &&
2932            first_entry + y < max_page_entries - 1 &&
2933            menu_info[first_entry + y].type & TYPE_SKIP_ENTRY)
2934       y += dy;
2935
2936     if (!IN_VIS_MENU(x, y))
2937     {
2938       choice += y - y_old;
2939
2940       if (choice < first_entry)
2941         first_entry = choice;
2942       else if (choice > first_entry + num_page_entries - 1)
2943         first_entry = choice - num_page_entries + 1;
2944
2945       if (first_entry >= 0 &&
2946           first_entry + num_page_entries <= max_page_entries)
2947       {
2948         first_entry_store[mode] = first_entry;
2949
2950         if (choice < first_entry)
2951           choice = first_entry;
2952         else if (choice > first_entry + num_page_entries - 1)
2953           choice = first_entry + num_page_entries - 1;
2954
2955         choice_store[mode] = choice;
2956
2957         drawMenuInfoList(first_entry, num_page_entries, max_page_entries);
2958
2959         DrawCursorAndText_Menu(choice - first_entry, choice, TRUE);
2960
2961         AdjustScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL, max_page_entries,
2962                         NUM_MENU_ENTRIES_ON_SCREEN, first_entry);
2963       }
2964
2965       return;
2966     }
2967   }
2968
2969   if (!anyScrollbarGadgetActive() &&
2970       IN_VIS_MENU(x, y) &&
2971       mx < mx_right_border &&
2972       y >= 0 && y < num_page_entries)
2973   {
2974     if (button)
2975     {
2976       if (first_entry + y != choice &&
2977           menu_info[first_entry + y].type & ~TYPE_SKIP_ENTRY)
2978       {
2979         PlaySound(SND_MENU_ITEM_ACTIVATING);
2980
2981         DrawCursorAndText_Menu(choice - first_entry, choice, FALSE);
2982         DrawCursorAndText_Menu(y, first_entry + y, TRUE);
2983
2984         choice = choice_store[mode] = first_entry + y;
2985       }
2986       else if (dx < 0)
2987       {
2988         PlaySound(SND_MENU_ITEM_SELECTING);
2989
2990         for (i = 0; menu_info[i].type != 0; i++)
2991         {
2992           if (menu_info[i].type & TYPE_LEAVE_MENU)
2993           {
2994             void (*menu_callback_function)(void) = menu_info[i].value;
2995
2996             FadeSetLeaveMenu();
2997
2998             menu_callback_function();
2999
3000             // absolutely needed because function changes 'menu_info'!
3001             break;
3002           }
3003         }
3004
3005         return;
3006       }
3007     }
3008     else if (!(menu_info[first_entry + y].type & TYPE_GHOSTED))
3009     {
3010       PlaySound(SND_MENU_ITEM_SELECTING);
3011
3012       // when selecting key headline, execute function for key value change
3013       if (menu_info[first_entry + y].type & TYPE_KEYTEXT &&
3014           menu_info[first_entry + y + 1].type & TYPE_KEY)
3015         y++;
3016
3017       // when selecting string value, execute function for list selection
3018       if (menu_info[first_entry + y].type & TYPE_STRING && y > 0 &&
3019           menu_info[first_entry + y - 1].type & TYPE_ENTER_LIST)
3020         y--;
3021
3022       // when selecting string value, execute function for text input gadget
3023       if (menu_info[first_entry + y].type & TYPE_STRING && y > 0 &&
3024           menu_info[first_entry + y - 1].type & TYPE_TEXT_INPUT)
3025         y--;
3026
3027       if (menu_info[first_entry + y].type & TYPE_ENTER_OR_LEAVE)
3028       {
3029         void (*menu_callback_function)(void) =
3030           menu_info[first_entry + y].value;
3031
3032         FadeSetFromType(menu_info[first_entry + y].type);
3033
3034         menu_callback_function();
3035       }
3036       else if (menu_info[first_entry + y].type & TYPE_TEXT_INPUT)
3037       {
3038         void (*gadget_callback_function)(void) =
3039           menu_info[first_entry + y].value;
3040
3041         gadget_callback_function();
3042       }
3043       else if (menu_info[first_entry + y].type & TYPE_VALUE &&
3044                menu_info == setup_info)
3045       {
3046         changeSetupValue(y, first_entry + y, dx);
3047       }
3048     }
3049   }
3050 }
3051
3052 void HandleInfoScreen_Main(int mx, int my, int dx, int dy, int button)
3053 {
3054   menu_info = info_info;
3055
3056   HandleMenuScreen(mx, my, dx, dy, button,
3057                    info_mode, num_info_info, max_info_info);
3058 }
3059
3060 static int getMenuFontSpacing(int spacing_height, int font_nr)
3061 {
3062   int font_spacing = getFontHeight(font_nr) + EXTRA_SPACING(game_status);
3063
3064   return (spacing_height < 0 ? ABS(spacing_height) * font_spacing :
3065           spacing_height);
3066 }
3067
3068 static int getMenuTextSpacing(int spacing_height, int font_nr)
3069 {
3070   return (getMenuFontSpacing(spacing_height, font_nr) +
3071           EXTRA_SPACING(game_status));
3072 }
3073
3074 static int getMenuTextStep(int spacing_height, int font_nr)
3075 {
3076   return getFontHeight(font_nr) + getMenuTextSpacing(spacing_height, font_nr);
3077 }
3078
3079 static int getHeadlineSpacing(void)
3080 {
3081   // special compatibility handling for "R'n'D jue 2022" game editions
3082   int spacing_check = menu.headline1_spacing[GAME_MODE_SCOREINFO];
3083   int spacing = (game_status == GAME_MODE_SCOREINFO ?
3084                  menu.headline1_spacing[GAME_MODE_SCOREINFO] :
3085                  menu.headline1_spacing_info[info_mode]);
3086   int font = MENU_INFO_FONT_TITLE;
3087
3088   return (spacing_check != -2 ? getMenuTextStep(spacing, font) : 0);
3089 }
3090
3091 void DrawInfoScreen_NotAvailable(char *text_title, char *text_error)
3092 {
3093   int font_error = FONT_TEXT_2;
3094   int font_foot  = MENU_INFO_FONT_FOOT;
3095   int ystart  = mSY - SY + MENU_SCREEN_INFO_YSTART + getHeadlineSpacing();
3096   int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM;
3097
3098   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO);
3099
3100   FadeOut(REDRAW_FIELD);
3101
3102   ClearField();
3103
3104   DrawInfoScreen_Headline(0, 1, FALSE);
3105
3106   DrawTextSCentered(ystart, font_error, text_error);
3107   DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_MENU);
3108
3109   FadeIn(REDRAW_FIELD);
3110 }
3111
3112 void DrawInfoScreen_HelpAnim(int start, int max_anims, boolean init)
3113 {
3114   static int infoscreen_step[MAX_INFO_ELEMENTS_IN_ARRAY];
3115   static int infoscreen_frame[MAX_INFO_ELEMENTS_IN_ARRAY];
3116   int font_foot = MENU_INFO_FONT_FOOT;
3117   int xstart = mSX + MENU_SCREEN_INFO_SPACE_LEFT;
3118   int ystart = mSY + MENU_SCREEN_INFO_YSTART + getHeadlineSpacing();
3119   int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM;
3120   int ystep = MENU_SCREEN_INFO_YSTEP;
3121   int row_height = MENU_SCREEN_INFO_ENTRY_SIZE;
3122   int tilesize = MENU_SCREEN_INFO_TILE_SIZE;
3123   int yoffset = (row_height - tilesize) / 2;
3124   int element, action, direction;
3125   int graphic;
3126   int delay;
3127   int sync_frame;
3128   int i, j;
3129
3130   if (init)
3131   {
3132     for (i = 0; i < NUM_INFO_ELEMENTS_ON_SCREEN; i++)
3133       infoscreen_step[i] = infoscreen_frame[i] = 0;
3134
3135     DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_PAGE);
3136
3137     FrameCounter = 0;
3138   }
3139
3140   i = j = 0;
3141   while (helpanim_info[j].element != HELPANIM_LIST_END)
3142   {
3143     if (i >= start + NUM_INFO_ELEMENTS_ON_SCREEN ||
3144         i >= max_anims)
3145       break;
3146     else if (i < start)
3147     {
3148       while (helpanim_info[j].element != HELPANIM_LIST_NEXT)
3149         j++;
3150
3151       j++;
3152       i++;
3153
3154       continue;
3155     }
3156
3157     int ypos = i - start;
3158     int ystart_pos = ystart + ypos * ystep + yoffset;
3159
3160     j += infoscreen_step[ypos];
3161
3162     element = helpanim_info[j].element;
3163     action = helpanim_info[j].action;
3164     direction = helpanim_info[j].direction;
3165
3166     if (element < 0)
3167       element = EL_UNKNOWN;
3168
3169     if (action != -1 && direction != -1)
3170       graphic = el_act_dir2img(element, action, direction);
3171     else if (action != -1)
3172       graphic = el_act2img(element, action);
3173     else if (direction != -1)
3174       graphic = el_dir2img(element, direction);
3175     else
3176       graphic = el2img(element);
3177
3178     delay = helpanim_info[j++].delay;
3179
3180     if (delay == -1)
3181       delay = 1000000;
3182
3183     if (infoscreen_frame[ypos] == 0)
3184     {
3185       sync_frame = 0;
3186       infoscreen_frame[ypos] = delay - 1;
3187     }
3188     else
3189     {
3190       sync_frame = delay - infoscreen_frame[ypos];
3191       infoscreen_frame[ypos]--;
3192     }
3193
3194     if (helpanim_info[j].element == HELPANIM_LIST_NEXT)
3195     {
3196       if (!infoscreen_frame[ypos])
3197         infoscreen_step[ypos] = 0;
3198     }
3199     else
3200     {
3201       if (!infoscreen_frame[ypos])
3202         infoscreen_step[ypos]++;
3203       while (helpanim_info[j].element != HELPANIM_LIST_NEXT)
3204         j++;
3205     }
3206
3207     j++;
3208
3209     ClearRectangleOnBackground(drawto, xstart, ystart_pos, tilesize, tilesize);
3210     DrawSizedGraphicAnimationExt(drawto, xstart, ystart_pos,
3211                                  graphic, sync_frame, tilesize, USE_MASKING);
3212
3213     if (init)
3214       DrawInfoScreen_HelpText(element, action, direction, ypos);
3215
3216     i++;
3217   }
3218
3219   redraw_mask |= REDRAW_FIELD;
3220
3221   FrameCounter++;
3222 }
3223
3224 static char *getHelpText(int element, int action, int direction)
3225 {
3226   char token[MAX_LINE_LEN];
3227
3228   strcpy(token, element_info[element].token_name);
3229
3230   if (action != -1)
3231     strcat(token, element_action_info[action].suffix);
3232
3233   if (direction != -1)
3234     strcat(token, element_direction_info[MV_DIR_TO_BIT(direction)].suffix);
3235
3236   return getHashEntry(helptext_info, token);
3237 }
3238
3239 void DrawInfoScreen_HelpText(int element, int action, int direction, int ypos)
3240 {
3241   int font_nr = FONT_INFO_ELEMENTS;
3242   int font_width = getFontWidth(font_nr);
3243   int font_height = getFontHeight(font_nr);
3244   int line_spacing = MENU_SCREEN_INFO_SPACE_LINE;
3245   int left_spacing = MENU_SCREEN_INFO_SPACE_LEFT;
3246   int middle_spacing = MENU_SCREEN_INFO_SPACE_MIDDLE;
3247   int right_spacing = MENU_SCREEN_INFO_SPACE_RIGHT;
3248   int line_height = font_height + line_spacing;
3249   int row_height = MENU_SCREEN_INFO_ENTRY_SIZE;
3250   int tilesize = MENU_SCREEN_INFO_TILE_SIZE;
3251   int xstart = mSX + left_spacing + tilesize + middle_spacing;
3252   int ystart = mSY + MENU_SCREEN_INFO_YSTART + getHeadlineSpacing();
3253   int ystep = MENU_SCREEN_INFO_YSTEP;
3254   int pad_left = xstart - SX;
3255   int pad_right = right_spacing;
3256   int max_chars_per_line = (SXSIZE - pad_left - pad_right) / font_width;
3257   int max_lines_per_text = (row_height + line_spacing) / line_height;
3258   char *text = NULL;
3259   boolean autowrap = TRUE;
3260   boolean centered = FALSE;
3261   boolean parse_comments = FALSE;
3262
3263   if (action != -1 && direction != -1)          // element.action.direction
3264     text = getHelpText(element, action, direction);
3265
3266   if (text == NULL && action != -1)             // element.action
3267     text = getHelpText(element, action, -1);
3268
3269   if (text == NULL && direction != -1)          // element.direction
3270     text = getHelpText(element, -1, direction);
3271
3272   if (text == NULL)                             // base element
3273     text = getHelpText(element, -1, -1);
3274
3275   if (text == NULL)                             // not found
3276     text = "No description available";
3277
3278   DisableDrawingText();
3279
3280   // first get number of text lines to calculate offset for centering text
3281   int num_lines_printed =
3282     DrawTextBuffer(0, 0, text, font_nr,
3283                    max_chars_per_line, -1, max_lines_per_text, line_spacing, -1,
3284                    autowrap, centered, parse_comments);
3285
3286   EnableDrawingText();
3287
3288   int size_lines_printed = num_lines_printed * line_height - line_spacing;
3289   int yoffset = (row_height - size_lines_printed) / 2;
3290
3291   DrawTextBuffer(xstart, ystart + ypos * ystep + yoffset, text, font_nr,
3292                  max_chars_per_line, -1, max_lines_per_text, line_spacing, -1,
3293                  autowrap, centered, parse_comments);
3294 }
3295
3296 static void DrawInfoScreen_TitleScreen(void)
3297 {
3298   SetGameStatus(GAME_MODE_TITLE);
3299
3300   UnmapAllGadgets();
3301
3302   DrawTitleScreen();
3303 }
3304
3305 void HandleInfoScreen_TitleScreen(int dx, int dy, int button)
3306 {
3307   HandleTitleScreen(0, 0, dx, dy, button);
3308 }
3309
3310 static void DrawInfoScreen_Elements(void)
3311 {
3312   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_ELEMENTS);
3313
3314   UnmapAllGadgets();
3315   FadeInfoSoundsAndMusic();
3316
3317   FadeOut(REDRAW_FIELD);
3318
3319   LoadHelpAnimInfo();
3320   LoadHelpTextInfo();
3321
3322   HandleInfoScreen_Elements(0, 0, MB_MENU_INITIALIZE);
3323
3324   PlayInfoSoundsAndMusic();
3325
3326   FadeIn(REDRAW_FIELD);
3327 }
3328
3329 void HandleInfoScreen_Elements(int dx, int dy, int button)
3330 {
3331   static DelayCounter info_delay = { 0 };
3332   static int num_anims;
3333   static int num_pages;
3334   static int page;
3335   int anims_per_page = NUM_INFO_ELEMENTS_ON_SCREEN;
3336   int i;
3337
3338   info_delay.value = GameFrameDelay;
3339
3340   if (button == MB_MENU_INITIALIZE)
3341   {
3342     boolean new_element = TRUE;
3343
3344     num_anims = 0;
3345
3346     for (i = 0; helpanim_info[i].element != HELPANIM_LIST_END; i++)
3347     {
3348       if (helpanim_info[i].element == HELPANIM_LIST_NEXT)
3349         new_element = TRUE;
3350       else if (new_element)
3351       {
3352         num_anims++;
3353         new_element = FALSE;
3354       }
3355     }
3356
3357     num_pages = (num_anims + anims_per_page - 1) / anims_per_page;
3358     page = 0;
3359   }
3360
3361   if (button == MB_MENU_LEAVE)
3362   {
3363     PlaySound(SND_MENU_ITEM_SELECTING);
3364
3365     info_mode = INFO_MODE_MAIN;
3366     DrawInfoScreen();
3367
3368     return;
3369   }
3370   else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE || dx)
3371   {
3372     if (button != MB_MENU_INITIALIZE)
3373     {
3374       PlaySound(SND_MENU_ITEM_SELECTING);
3375
3376       page += (dx < 0 ? -1 : +1);
3377     }
3378
3379     if (page < 0 || page >= num_pages)
3380     {
3381       FadeInfoSoundsAndMusic();
3382
3383       info_mode = INFO_MODE_MAIN;
3384       DrawInfoScreen();
3385
3386       return;
3387     }
3388
3389     if (button != MB_MENU_INITIALIZE)
3390       FadeSetNextScreen();
3391
3392     if (button != MB_MENU_INITIALIZE)
3393       FadeOut(REDRAW_FIELD);
3394
3395     ClearField();
3396
3397     DrawInfoScreen_Headline(page, num_pages, TRUE);
3398     DrawInfoScreen_HelpAnim(page * anims_per_page, num_anims, TRUE);
3399
3400     if (button != MB_MENU_INITIALIZE)
3401       FadeIn(REDRAW_FIELD);
3402   }
3403   else
3404   {
3405     if (DelayReached(&info_delay))
3406       if (page < num_pages)
3407         DrawInfoScreen_HelpAnim(page * anims_per_page, num_anims, FALSE);
3408
3409     PlayInfoSoundIfLoop();
3410   }
3411 }
3412
3413 static void DrawInfoScreen_Music(void)
3414 {
3415   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_MUSIC);
3416
3417   UnmapAllGadgets();
3418
3419   FadeOut(REDRAW_FIELD);
3420
3421   ClearField();
3422
3423   DrawInfoScreen_Headline(0, 1, TRUE);
3424
3425   LoadMusicInfo();
3426
3427   HandleInfoScreen_Music(0, 0, MB_MENU_INITIALIZE);
3428
3429   FadeIn(REDRAW_FIELD);
3430 }
3431
3432 void HandleInfoScreen_Music(int dx, int dy, int button)
3433 {
3434   static struct MusicFileInfo *list = NULL;
3435   static int num_screens = 0;
3436   static int screen_nr = 0;
3437   int font_title = MENU_INFO_FONT_TITLE;
3438   int font_head  = MENU_INFO_FONT_HEAD;
3439   int font_text  = MENU_INFO_FONT_TEXT;
3440   int font_foot  = MENU_INFO_FONT_FOOT;
3441   int spacing_head = menu.headline2_spacing_info[info_mode];
3442   int ystep_head = getMenuTextStep(spacing_head,  font_head);
3443   int ystart  = mSY - SY + MENU_SCREEN_INFO_YSTART;
3444   int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM;
3445
3446   if (button == MB_MENU_INITIALIZE)
3447   {
3448     struct MusicFileInfo *list_ptr = music_file_info;
3449
3450     num_screens = 0;
3451     screen_nr = 0;
3452
3453     while (list_ptr != NULL)
3454     {
3455       list_ptr = list_ptr->next;
3456       num_screens++;
3457     }
3458
3459     list = music_file_info;
3460
3461     if (list == NULL)
3462     {
3463       FadeMenuSoundsAndMusic();
3464
3465       ClearField();
3466
3467       DrawInfoScreen_Headline(0, 1, TRUE);
3468
3469       DrawTextSCentered(ystart, font_title, "No music info for this level set.");
3470       DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_MENU);
3471
3472       return;
3473     }
3474   }
3475
3476   if (button == MB_MENU_LEAVE)
3477   {
3478     PlaySound(SND_MENU_ITEM_SELECTING);
3479
3480     FadeMenuSoundsAndMusic();
3481
3482     info_mode = INFO_MODE_MAIN;
3483     DrawInfoScreen();
3484
3485     return;
3486   }
3487   else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE || dx)
3488   {
3489     if (button != MB_MENU_INITIALIZE)
3490     {
3491       PlaySound(SND_MENU_ITEM_SELECTING);
3492
3493       if (list != NULL)
3494       {
3495         list = (dx < 0 ? list->prev : list->next);
3496         screen_nr += (dx < 0 ? -1 : +1);
3497       }
3498     }
3499
3500     if (list == NULL)
3501     {
3502       FadeMenuSoundsAndMusic();
3503
3504       info_mode = INFO_MODE_MAIN;
3505       DrawInfoScreen();
3506
3507       return;
3508     }
3509
3510     FadeMenuSoundsAndMusic();
3511
3512     if (list != music_file_info)
3513       FadeSetNextScreen();
3514
3515     if (button != MB_MENU_INITIALIZE)
3516       FadeOut(REDRAW_FIELD);
3517
3518     ClearField();
3519
3520     DrawInfoScreen_Headline(screen_nr, num_screens, TRUE);
3521
3522     if (list->is_sound)
3523     {
3524       int sound = list->music;
3525
3526       if (IS_LOOP_SOUND(sound))
3527         PlaySoundLoop(sound);
3528       else
3529         PlaySound(sound);
3530     }
3531     else
3532     {
3533       int music = list->music;
3534
3535       if (IS_LOOP_MUSIC(music))
3536         PlayMusicLoop(music);
3537       else
3538         PlayMusic(music);
3539     }
3540
3541     if (!strEqual(list->title, UNKNOWN_NAME))
3542     {
3543       if (!strEqual(list->title_header, UNKNOWN_NAME))
3544         DrawTextSCentered(ystart, font_head, list->title_header);
3545       else
3546         DrawTextSCentered(ystart, font_head, "Track");
3547
3548       ystart += ystep_head;
3549
3550       DrawTextFCentered(ystart, font_text, "\"%s\"", list->title);
3551       ystart += ystep_head;
3552     }
3553
3554     if (!strEqual(list->artist, UNKNOWN_NAME))
3555     {
3556       if (!strEqual(list->artist_header, UNKNOWN_NAME))
3557         DrawTextSCentered(ystart, font_head, list->artist_header);
3558       else
3559         DrawTextSCentered(ystart, font_head, "by");
3560
3561       ystart += ystep_head;
3562
3563       DrawTextFCentered(ystart, font_text, "%s", list->artist);
3564       ystart += ystep_head;
3565     }
3566
3567     if (!strEqual(list->album, UNKNOWN_NAME))
3568     {
3569       if (!strEqual(list->album_header, UNKNOWN_NAME))
3570         DrawTextSCentered(ystart, font_head, list->album_header);
3571       else
3572         DrawTextSCentered(ystart, font_head, "from the album");
3573
3574       ystart += ystep_head;
3575
3576       DrawTextFCentered(ystart, font_text, "\"%s\"", list->album);
3577       ystart += ystep_head;
3578     }
3579
3580     if (!strEqual(list->year, UNKNOWN_NAME))
3581     {
3582       if (!strEqual(list->year_header, UNKNOWN_NAME))
3583         DrawTextSCentered(ystart, font_head, list->year_header);
3584       else
3585         DrawTextSCentered(ystart, font_head, "from the year");
3586
3587       ystart += ystep_head;
3588
3589       DrawTextFCentered(ystart, font_text, "%s", list->year);
3590       ystart += ystep_head;
3591     }
3592
3593     if (!strEqual(list->played, UNKNOWN_NAME))
3594     {
3595       if (!strEqual(list->played_header, UNKNOWN_NAME))
3596         DrawTextSCentered(ystart, font_head, list->played_header);
3597       else
3598         DrawTextSCentered(ystart, font_head, "played in");
3599
3600       ystart += ystep_head;
3601
3602       DrawTextFCentered(ystart, font_text, "%s", list->played);
3603       ystart += ystep_head;
3604     }
3605     else if (!list->is_sound)
3606     {
3607       int music_level_nr = -1;
3608       int i;
3609
3610       // check if this music is configured for a certain level
3611       for (i = leveldir_current->first_level;
3612            i <= leveldir_current->last_level; i++)
3613       {
3614         // (special case: "list->music" may be negative for unconfigured music)
3615         if (levelset.music[i] != MUS_UNDEFINED &&
3616             levelset.music[i] == list->music)
3617         {
3618           music_level_nr = i;
3619
3620           break;
3621         }
3622       }
3623
3624       if (music_level_nr != -1)
3625       {
3626         if (!strEqual(list->played_header, UNKNOWN_NAME))
3627           DrawTextSCentered(ystart, font_head, list->played_header);
3628         else
3629           DrawTextSCentered(ystart, font_head, "played in");
3630
3631         ystart += ystep_head;
3632
3633         DrawTextFCentered(ystart, font_text, "level %03d", music_level_nr);
3634         ystart += ystep_head;
3635       }
3636     }
3637
3638     DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_PAGE);
3639
3640     if (button != MB_MENU_INITIALIZE)
3641       FadeIn(REDRAW_FIELD);
3642   }
3643
3644   if (list != NULL && list->is_sound && IS_LOOP_SOUND(list->music))
3645     PlaySoundLoop(list->music);
3646 }
3647
3648 static void DrawInfoScreen_Version(void)
3649 {
3650   int font_head = MENU_INFO_FONT_HEAD;
3651   int font_text = MENU_INFO_FONT_TEXT;
3652   int font_foot = MENU_INFO_FONT_FOOT;
3653   int spacing_head = menu.headline2_spacing_info[info_mode];
3654   int spacing_para = menu.paragraph_spacing_info[info_mode];
3655   int spacing_line = menu.line_spacing_info[info_mode];
3656   int xstep = getFontWidth(font_text);
3657   int ystep_head = getMenuTextStep(spacing_head,  font_head);
3658   int ystep_para = getMenuTextStep(spacing_para,  font_text);
3659   int ystep_line = getMenuTextStep(spacing_line,  font_text);
3660   int ystart  = mSY - SY + MENU_SCREEN_INFO_YSTART + getHeadlineSpacing();
3661   int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM;
3662   int xstart1 = mSX - SX + 2 * xstep;
3663   int xstart2 = mSX - SX + 18 * xstep;
3664   int xstart3 = mSX - SX + 28 * xstep;
3665   SDL_version sdl_version_compiled;
3666   const SDL_version *sdl_version_linked;
3667   int driver_name_len = 10;
3668   SDL_version sdl_version_linked_ext;
3669   const char *driver_name = NULL;
3670
3671   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_VERSION);
3672
3673   UnmapAllGadgets();
3674   FadeInfoSoundsAndMusic();
3675
3676   FadeOut(REDRAW_FIELD);
3677
3678   ClearField();
3679
3680   DrawInfoScreen_Headline(0, 1, TRUE);
3681
3682   DrawTextF(xstart1, ystart, font_head, "Name");
3683   DrawTextF(xstart2, ystart, font_text, getProgramTitleString());
3684   ystart += ystep_line;
3685
3686   if (!strEqual(getProgramVersionString(), getProgramRealVersionString()))
3687   {
3688     DrawTextF(xstart1, ystart, font_head, "Version (fake)");
3689     DrawTextF(xstart2, ystart, font_text, getProgramVersionString());
3690     ystart += ystep_line;
3691
3692     DrawTextF(xstart1, ystart, font_head, "Version (real)");
3693     DrawTextF(xstart2, ystart, font_text, getProgramRealVersionString());
3694     ystart += ystep_line;
3695   }
3696   else
3697   {
3698     DrawTextF(xstart1, ystart, font_head, "Version");
3699     DrawTextF(xstart2, ystart, font_text, getProgramVersionString());
3700     ystart += ystep_line;
3701   }
3702
3703   DrawTextF(xstart1, ystart, font_head, "Platform");
3704   DrawTextF(xstart2, ystart, font_text, "%s (%s)",
3705             PLATFORM_STRING,
3706             PLATFORM_XX_BIT_STRING);
3707   ystart += ystep_line;
3708
3709   DrawTextF(xstart1, ystart, font_head, "Target");
3710   DrawTextF(xstart2, ystart, font_text, TARGET_STRING);
3711   ystart += ystep_line;
3712
3713   DrawTextF(xstart1, ystart, font_head, "Source date");
3714   DrawTextF(xstart2, ystart, font_text, getSourceDateString());
3715   ystart += ystep_line;
3716
3717   DrawTextF(xstart1, ystart, font_head, "Commit hash");
3718   DrawTextF(xstart2, ystart, font_text, getSourceHashString());
3719   ystart += ystep_para;
3720
3721   DrawTextF(xstart1, ystart, font_head, "Library");
3722   DrawTextF(xstart2, ystart, font_head, "compiled");
3723   DrawTextF(xstart3, ystart, font_head, "linked");
3724   ystart += ystep_head;
3725
3726   SDL_VERSION(&sdl_version_compiled);
3727   SDL_GetVersion(&sdl_version_linked_ext);
3728   sdl_version_linked = &sdl_version_linked_ext;
3729
3730   DrawTextF(xstart1, ystart, font_text, "SDL");
3731   DrawTextF(xstart2, ystart, font_text, "%d.%d.%d",
3732             sdl_version_compiled.major,
3733             sdl_version_compiled.minor,
3734             sdl_version_compiled.patch);
3735   DrawTextF(xstart3, ystart, font_text, "%d.%d.%d",
3736             sdl_version_linked->major,
3737             sdl_version_linked->minor,
3738             sdl_version_linked->patch);
3739   ystart += ystep_line;
3740
3741   SDL_IMAGE_VERSION(&sdl_version_compiled);
3742   sdl_version_linked = IMG_Linked_Version();
3743
3744   DrawTextF(xstart1, ystart, font_text, "SDL_image");
3745   DrawTextF(xstart2, ystart, font_text, "%d.%d.%d",
3746             sdl_version_compiled.major,
3747             sdl_version_compiled.minor,
3748             sdl_version_compiled.patch);
3749   DrawTextF(xstart3, ystart, font_text, "%d.%d.%d",
3750             sdl_version_linked->major,
3751             sdl_version_linked->minor,
3752             sdl_version_linked->patch);
3753   ystart += ystep_line;
3754
3755   SDL_MIXER_VERSION(&sdl_version_compiled);
3756   sdl_version_linked = Mix_Linked_Version();
3757
3758   DrawTextF(xstart1, ystart, font_text, "SDL_mixer");
3759   DrawTextF(xstart2, ystart, font_text, "%d.%d.%d",
3760             sdl_version_compiled.major,
3761             sdl_version_compiled.minor,
3762             sdl_version_compiled.patch);
3763   DrawTextF(xstart3, ystart, font_text, "%d.%d.%d",
3764             sdl_version_linked->major,
3765             sdl_version_linked->minor,
3766             sdl_version_linked->patch);
3767   ystart += ystep_line;
3768
3769   SDL_NET_VERSION(&sdl_version_compiled);
3770   sdl_version_linked = SDLNet_Linked_Version();
3771
3772   DrawTextF(xstart1, ystart, font_text, "SDL_net");
3773   DrawTextF(xstart2, ystart, font_text, "%d.%d.%d",
3774             sdl_version_compiled.major,
3775             sdl_version_compiled.minor,
3776             sdl_version_compiled.patch);
3777   DrawTextF(xstart3, ystart, font_text, "%d.%d.%d",
3778             sdl_version_linked->major,
3779             sdl_version_linked->minor,
3780             sdl_version_linked->patch);
3781   ystart += ystep_para;
3782
3783   DrawTextF(xstart1, ystart, font_head, "Driver");
3784   DrawTextF(xstart2, ystart, font_head, "Requested");
3785   DrawTextF(xstart3, ystart, font_head, "Used");
3786   ystart += ystep_head;
3787
3788   driver_name =
3789     getStringCopyNStatic(SDLGetRendererName(), driver_name_len);
3790
3791   DrawTextF(xstart1, ystart, font_text, "Render Driver");
3792   DrawTextF(xstart2, ystart, font_text, "%s", setup.system.sdl_renderdriver);
3793   DrawTextF(xstart3, ystart, font_text, "%s", driver_name);
3794   ystart += ystep_line;
3795
3796   driver_name =
3797     getStringCopyNStatic(SDL_GetCurrentVideoDriver(), driver_name_len);
3798
3799   DrawTextF(xstart1, ystart, font_text, "Video Driver");
3800   DrawTextF(xstart2, ystart, font_text, "%s", setup.system.sdl_videodriver);
3801   DrawTextF(xstart3, ystart, font_text, "%s", driver_name);
3802   ystart += ystep_line;
3803
3804   driver_name =
3805     getStringCopyNStatic(SDL_GetCurrentAudioDriver(), driver_name_len);
3806
3807   DrawTextF(xstart1, ystart, font_text, "Audio Driver");
3808   DrawTextF(xstart2, ystart, font_text, "%s", setup.system.sdl_audiodriver);
3809   DrawTextF(xstart3, ystart, font_text, "%s", driver_name);
3810
3811   DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_MENU);
3812
3813   PlayInfoSoundsAndMusic();
3814
3815   FadeIn(REDRAW_FIELD);
3816 }
3817
3818 void HandleInfoScreen_Version(int button)
3819 {
3820   if (button == MB_MENU_LEAVE)
3821   {
3822     PlaySound(SND_MENU_ITEM_SELECTING);
3823
3824     info_mode = INFO_MODE_MAIN;
3825     DrawInfoScreen();
3826
3827     return;
3828   }
3829   else if (button == MB_MENU_CHOICE)
3830   {
3831     PlaySound(SND_MENU_ITEM_SELECTING);
3832
3833     FadeMenuSoundsAndMusic();
3834
3835     info_mode = INFO_MODE_MAIN;
3836     DrawInfoScreen();
3837   }
3838   else
3839   {
3840     PlayMenuSoundIfLoop();
3841   }
3842 }
3843
3844 static char *getInfoScreenTitle_Generic(void)
3845 {
3846   return (info_mode == INFO_MODE_MAIN     ? STR_INFO_MAIN     :
3847           info_mode == INFO_MODE_TITLE    ? STR_INFO_TITLE    :
3848           info_mode == INFO_MODE_ELEMENTS ? STR_INFO_ELEMENTS :
3849           info_mode == INFO_MODE_MUSIC    ? STR_INFO_MUSIC    :
3850           info_mode == INFO_MODE_CREDITS  ? STR_INFO_CREDITS  :
3851           info_mode == INFO_MODE_PROGRAM  ? STR_INFO_PROGRAM  :
3852           info_mode == INFO_MODE_VERSION  ? STR_INFO_VERSION  :
3853           info_mode == INFO_MODE_LEVELSET ? STR_INFO_LEVELSET :
3854           "");
3855 }
3856
3857 static int getInfoScreenBackgroundImage_Generic(void)
3858 {
3859   return (info_mode == INFO_MODE_ELEMENTS ? IMG_BACKGROUND_INFO_ELEMENTS :
3860           info_mode == INFO_MODE_MUSIC    ? IMG_BACKGROUND_INFO_MUSIC    :
3861           info_mode == INFO_MODE_CREDITS  ? IMG_BACKGROUND_INFO_CREDITS  :
3862           info_mode == INFO_MODE_PROGRAM  ? IMG_BACKGROUND_INFO_PROGRAM  :
3863           info_mode == INFO_MODE_VERSION  ? IMG_BACKGROUND_INFO_VERSION  :
3864           info_mode == INFO_MODE_LEVELSET ? IMG_BACKGROUND_INFO_LEVELSET :
3865           IMG_BACKGROUND_INFO);
3866 }
3867
3868 static int getInfoScreenBackgroundSound_Generic(void)
3869 {
3870   return (info_mode == INFO_MODE_ELEMENTS ? SND_BACKGROUND_INFO_ELEMENTS :
3871           info_mode == INFO_MODE_CREDITS  ? SND_BACKGROUND_INFO_CREDITS  :
3872           info_mode == INFO_MODE_PROGRAM  ? SND_BACKGROUND_INFO_PROGRAM  :
3873           info_mode == INFO_MODE_VERSION  ? SND_BACKGROUND_INFO_VERSION  :
3874           info_mode == INFO_MODE_LEVELSET ? SND_BACKGROUND_INFO_LEVELSET :
3875           SND_BACKGROUND_INFO);
3876 }
3877
3878 static int getInfoScreenBackgroundMusic_Generic(void)
3879 {
3880   return (info_mode == INFO_MODE_ELEMENTS ? MUS_BACKGROUND_INFO_ELEMENTS :
3881           info_mode == INFO_MODE_CREDITS  ? MUS_BACKGROUND_INFO_CREDITS  :
3882           info_mode == INFO_MODE_PROGRAM  ? MUS_BACKGROUND_INFO_PROGRAM  :
3883           info_mode == INFO_MODE_VERSION  ? MUS_BACKGROUND_INFO_VERSION  :
3884           info_mode == INFO_MODE_LEVELSET ? MUS_BACKGROUND_INFO_LEVELSET :
3885           MUS_BACKGROUND_INFO);
3886 }
3887
3888 static char *getInfoScreenFilename_Generic(int nr, boolean global)
3889 {
3890   return (info_mode == INFO_MODE_CREDITS  ? getCreditsFilename(nr, global) :
3891           info_mode == INFO_MODE_PROGRAM  ? getProgramInfoFilename(nr)     :
3892           info_mode == INFO_MODE_LEVELSET ? getLevelSetInfoFilename(nr)    :
3893           NULL);
3894 }
3895
3896 static void DrawInfoScreen_GenericScreen(int screen_nr, int num_screens,
3897                                          int use_global_screens)
3898 {
3899   char *filename = getInfoScreenFilename_Generic(screen_nr, use_global_screens);
3900   int font_text = MENU_INFO_FONT_TEXT;
3901   int font_foot = MENU_INFO_FONT_FOOT;
3902   int spacing_line = menu.line_spacing_info[info_mode];
3903   int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM;
3904
3905   ClearField();
3906
3907   DrawInfoScreen_Headline(screen_nr, num_screens, use_global_screens);
3908
3909   if (info_mode == INFO_MODE_CREDITS ||
3910       info_mode == INFO_MODE_PROGRAM)
3911   {
3912     int width = SXSIZE;
3913     int height = MENU_SCREEN_INFO_YBOTTOM - MENU_SCREEN_INFO_YSTART;
3914     int chars = width / getFontWidth(font_text);
3915     int lines = height / getFontHeight(font_text);
3916     int padx = (width - chars * getFontWidth(font_text)) / 2;
3917     int line_spacing = getMenuTextSpacing(spacing_line, font_text);
3918     int xstart = mSX + padx;
3919     int ystart = mSY + MENU_SCREEN_INFO_YSTART + getHeadlineSpacing();
3920     boolean autowrap = FALSE;
3921     boolean centered = TRUE;
3922     boolean parse_comments = TRUE;
3923
3924     DrawTextFile(xstart, ystart,
3925                  filename, font_text, chars, -1, lines, line_spacing, -1,
3926                  autowrap, centered, parse_comments);
3927   }
3928   else if (info_mode == INFO_MODE_LEVELSET)
3929   {
3930     struct TitleMessageInfo *tmi = &readme;
3931
3932     // if x position set to "-1", automatically determine by playfield width
3933     if (tmi->x == -1)
3934       tmi->x = SXSIZE / 2;
3935
3936     // if y position set to "-1", use static default value
3937     if (tmi->y == -1)
3938       tmi->y = MENU_SCREEN_INFO_YSTART + getHeadlineSpacing();
3939
3940     // if width set to "-1", automatically determine by playfield width
3941     if (tmi->width == -1)
3942       tmi->width = SXSIZE - 2 * TILEX;
3943
3944     // if height set to "-1", automatically determine by playfield height
3945     if (tmi->height == -1)
3946       tmi->height = MENU_SCREEN_INFO_YBOTTOM - tmi->y - 10;
3947
3948     // if chars set to "-1", automatically determine by text and font width
3949     if (tmi->chars == -1)
3950       tmi->chars = tmi->width / getFontWidth(tmi->font);
3951     else
3952       tmi->width = tmi->chars * getFontWidth(tmi->font);
3953
3954     // if lines set to "-1", automatically determine by text and font height
3955     if (tmi->lines == -1)
3956       tmi->lines = tmi->height / getFontHeight(tmi->font);
3957     else
3958       tmi->height = tmi->lines * getFontHeight(tmi->font);
3959
3960     DrawTextFile(mSX + ALIGNED_TEXT_XPOS(tmi), mSY + ALIGNED_TEXT_YPOS(tmi),
3961                  filename, tmi->font, tmi->chars, -1, tmi->lines, 0, -1,
3962                  tmi->autowrap, tmi->centered, tmi->parse_comments);
3963   }
3964
3965   boolean last_screen = (screen_nr == num_screens - 1);
3966   char *text_foot = (last_screen ? TEXT_NEXT_MENU : TEXT_NEXT_PAGE);
3967
3968   DrawTextSCentered(ybottom, font_foot, text_foot);
3969 }
3970
3971 static void DrawInfoScreen_Generic(void)
3972 {
3973   SetMainBackgroundImageIfDefined(getInfoScreenBackgroundImage_Generic());
3974
3975   UnmapAllGadgets();
3976   FadeInfoSoundsAndMusic();
3977
3978   FadeOut(REDRAW_FIELD);
3979
3980   HandleInfoScreen_Generic(0, 0, MB_MENU_INITIALIZE);
3981
3982   PlayInfoSoundsAndMusic();
3983
3984   FadeIn(REDRAW_FIELD);
3985 }
3986
3987 void HandleInfoScreen_Generic(int dx, int dy, int button)
3988 {
3989   static char *text_no_info = "";
3990   static int num_screens = 0;
3991   static int screen_nr = 0;
3992   static boolean use_global_screens = FALSE;
3993
3994   if (button == MB_MENU_INITIALIZE)
3995   {
3996     num_screens = 0;
3997     screen_nr = 0;
3998
3999     if (info_mode == INFO_MODE_CREDITS)
4000     {
4001       int i;
4002
4003       for (i = 0; i < 2; i++)
4004       {
4005         use_global_screens = i;         // check for "FALSE", then "TRUE"
4006
4007         // determine number of (global or level set specific) credits screens
4008         while (getCreditsFilename(num_screens, use_global_screens) != NULL)
4009           num_screens++;
4010
4011         if (num_screens > 0)
4012           break;
4013       }
4014
4015       text_no_info = "No credits available.";
4016     }
4017     else if (info_mode == INFO_MODE_PROGRAM)
4018     {
4019       use_global_screens = TRUE;
4020
4021       // determine number of program info screens
4022       while (getProgramInfoFilename(num_screens) != NULL)
4023         num_screens++;
4024
4025       text_no_info = "No program info available.";
4026     }
4027     else if (info_mode == INFO_MODE_LEVELSET)
4028     {
4029       use_global_screens = FALSE;
4030
4031       // determine number of levelset info screens
4032       while (getLevelSetInfoFilename(num_screens) != NULL)
4033         num_screens++;
4034
4035       text_no_info = "No level set info available.";
4036     }
4037
4038     if (num_screens == 0)
4039     {
4040       int font_title = MENU_INFO_FONT_TITLE;
4041       int font_foot  = MENU_INFO_FONT_FOOT;
4042       int ystart  = mSY - SY + MENU_SCREEN_INFO_YSTART;
4043       int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM;
4044
4045       ClearField();
4046
4047       DrawInfoScreen_Headline(screen_nr, num_screens, use_global_screens);
4048
4049       DrawTextSCentered(ystart, font_title, text_no_info);
4050       DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_MENU);
4051
4052       return;
4053     }
4054
4055     DrawInfoScreen_GenericScreen(screen_nr, num_screens, use_global_screens);
4056   }
4057   else if (button == MB_MENU_LEAVE)
4058   {
4059     PlaySound(SND_MENU_ITEM_SELECTING);
4060
4061     info_mode = INFO_MODE_MAIN;
4062     DrawInfoScreen();
4063   }
4064   else if (button == MB_MENU_CHOICE || dx)
4065   {
4066     PlaySound(SND_MENU_ITEM_SELECTING);
4067
4068     screen_nr += (dx < 0 ? -1 : +1);
4069
4070     if (screen_nr < 0 || screen_nr >= num_screens)
4071     {
4072       FadeInfoSoundsAndMusic();
4073
4074       info_mode = INFO_MODE_MAIN;
4075       DrawInfoScreen();
4076     }
4077     else
4078     {
4079       FadeSetNextScreen();
4080
4081       FadeOut(REDRAW_FIELD);
4082
4083       DrawInfoScreen_GenericScreen(screen_nr, num_screens, use_global_screens);
4084
4085       FadeIn(REDRAW_FIELD);
4086     }
4087   }
4088   else
4089   {
4090     PlayInfoSoundIfLoop();
4091   }
4092 }
4093
4094 static void DrawInfoScreen(void)
4095 {
4096   if (info_mode == INFO_MODE_TITLE)
4097     DrawInfoScreen_TitleScreen();
4098   else if (info_mode == INFO_MODE_ELEMENTS)
4099     DrawInfoScreen_Elements();
4100   else if (info_mode == INFO_MODE_MUSIC)
4101     DrawInfoScreen_Music();
4102   else if (info_mode == INFO_MODE_CREDITS)
4103     DrawInfoScreen_Generic();
4104   else if (info_mode == INFO_MODE_PROGRAM)
4105     DrawInfoScreen_Generic();
4106   else if (info_mode == INFO_MODE_VERSION)
4107     DrawInfoScreen_Version();
4108   else if (info_mode == INFO_MODE_LEVELSET)
4109     DrawInfoScreen_Generic();
4110   else
4111     DrawInfoScreen_Main();
4112 }
4113
4114 void DrawInfoScreen_FromMainMenu(int nr)
4115 {
4116   int fade_mask = REDRAW_FIELD;
4117
4118   if (nr < INFO_MODE_MAIN || nr >= MAX_INFO_MODES)
4119     return;
4120
4121   CloseDoor(DOOR_CLOSE_2);
4122
4123   SetGameStatus(GAME_MODE_INFO);
4124
4125   info_mode = nr;
4126   info_screens_from_main = TRUE;
4127
4128   if (redraw_mask & REDRAW_ALL)
4129     fade_mask = REDRAW_ALL;
4130
4131   if (CheckFadeAll())
4132     fade_mask = REDRAW_ALL;
4133
4134   UnmapAllGadgets();
4135   FadeMenuSoundsAndMusic();
4136
4137   FadeSetEnterScreen();
4138
4139   FadeOut(fade_mask);
4140
4141   FadeSkipNextFadeOut();
4142
4143   // needed if different viewport properties defined for info screen
4144   ChangeViewportPropertiesIfNeeded();
4145
4146   SetMainBackgroundImage(IMG_BACKGROUND_INFO);
4147
4148   DrawInfoScreen();
4149 }
4150
4151 void HandleInfoScreen(int mx, int my, int dx, int dy, int button)
4152 {
4153   if (info_mode == INFO_MODE_TITLE)
4154     HandleInfoScreen_TitleScreen(dx, dy, button);
4155   else if (info_mode == INFO_MODE_ELEMENTS)
4156     HandleInfoScreen_Elements(dx, dy, button);
4157   else if (info_mode == INFO_MODE_MUSIC)
4158     HandleInfoScreen_Music(dx, dy, button);
4159   else if (info_mode == INFO_MODE_CREDITS)
4160     HandleInfoScreen_Generic(dx, dy, button);
4161   else if (info_mode == INFO_MODE_PROGRAM)
4162     HandleInfoScreen_Generic(dx, dy, button);
4163   else if (info_mode == INFO_MODE_VERSION)
4164     HandleInfoScreen_Version(button);
4165   else if (info_mode == INFO_MODE_LEVELSET)
4166     HandleInfoScreen_Generic(dx, dy, button);
4167   else
4168     HandleInfoScreen_Main(mx, my, dx, dy, button);
4169 }
4170
4171
4172 // ============================================================================
4173 // type name functions
4174 // ============================================================================
4175
4176 static TreeInfo *type_name_node = NULL;
4177 static char type_name_last[MAX_PLAYER_NAME_LEN + 1] = { 0 };
4178 static int type_name_nr = 0;
4179
4180 static int getPlayerNameColor(char *name)
4181 {
4182   return (strEqual(name, EMPTY_PLAYER_NAME) ? FC_BLUE : FC_RED);
4183 }
4184
4185 static void drawTypeNameText(char *name, struct TextPosInfo *pos,
4186                              boolean active)
4187 {
4188   char text[MAX_PLAYER_NAME_LEN + 2] = { 0 };
4189   boolean multiple_users = (game_status == GAME_MODE_PSEUDO_TYPENAMES);
4190   int sx = (multiple_users ? amSX + pos->x : mSX + ALIGNED_TEXT_XPOS(pos));
4191   int sy = (multiple_users ? amSY + pos->y : mSY + ALIGNED_TEXT_YPOS(pos));
4192   int font_nr = (active ? FONT_ACTIVE(pos->font) : pos->font);
4193   int font_width = getFontWidth(font_nr);
4194   int font_xoffset = getFontDrawOffsetX(font_nr);
4195   int font_yoffset = getFontDrawOffsetY(font_nr);
4196   int font_sx = sx + font_xoffset;
4197   int font_sy = sy + font_yoffset;
4198
4199   DrawBackgroundForFont(font_sx, font_sy, pos->width, pos->height, font_nr);
4200
4201   sprintf(text, "%s%c", name, (active ? '_' : '\0'));
4202
4203   pos->width = strlen(text) * font_width;
4204   sx = (multiple_users ? amSX + pos->x : mSX + ALIGNED_TEXT_XPOS(pos));
4205
4206   DrawText(sx, sy, text, font_nr);
4207 }
4208
4209 static void getTypeNameValues(char *name, struct TextPosInfo *pos, int *xpos)
4210 {
4211   struct MainControlInfo *mci = getMainControlInfo(MAIN_CONTROL_NAME);
4212
4213   *pos = *mci->pos_input;
4214
4215   if (game_status == GAME_MODE_PSEUDO_TYPENAMES)
4216   {
4217     TreeInfo *ti = player_name_current;
4218     int first_entry = ti->cl_first;
4219     int entry_pos = first_entry + ti->cl_cursor;
4220     TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti);
4221     int xpos = MENU_SCREEN_START_XPOS;
4222     int ypos = MENU_SCREEN_START_YPOS + ti->cl_cursor;
4223
4224     type_name_node = getTreeInfoFromPos(node_first, entry_pos);
4225     type_name_nr = entry_pos;
4226
4227     strcpy(name, type_name_node->name);
4228
4229     pos->x = xpos * 32;
4230     pos->y = ypos * 32;
4231     pos->width = MAX_PLAYER_NAME_LEN * 32;
4232   }
4233   else
4234   {
4235     type_name_nr = user.nr;
4236
4237     strcpy(name, setup.player_name);
4238   }
4239
4240   strcpy(type_name_last, name);
4241
4242   if (strEqual(name, EMPTY_PLAYER_NAME))
4243     strcpy(name, "");
4244
4245   *xpos = strlen(name);
4246 }
4247
4248 static void setTypeNameValues_Name(char *name, struct TextPosInfo *pos)
4249 {
4250   // change name of edited user in global list of user names
4251   setString(&global.user_names[type_name_nr], name);
4252
4253   if (game_status == GAME_MODE_PSEUDO_TYPENAMES)
4254   {
4255     TreeInfo *node = type_name_node;
4256
4257     // change name of edited user in local menu tree structure
4258     setString(&node->name, name);
4259     setString(&node->name_sorting, name);
4260
4261     node->color = getPlayerNameColor(name);
4262     pos->font = MENU_CHOOSE_TREE_FONT(node->color);
4263   }
4264 }
4265
4266 static void setTypeNameValues(char *name, struct TextPosInfo *pos,
4267                               boolean changed)
4268 {
4269   boolean reset_setup = strEqual(name, "");
4270   boolean remove_user = strEqual(name, EMPTY_PLAYER_NAME);
4271   boolean create_user = strEqual(type_name_last, EMPTY_PLAYER_NAME);
4272
4273   if (!changed)
4274     strcpy(name, type_name_last);
4275
4276   if (strEqual(name, ""))
4277     strcpy(name, EMPTY_PLAYER_NAME);
4278
4279   setTypeNameValues_Name(name, pos);
4280
4281   // if player name not changed, no further action required
4282   if (strEqual(name, type_name_last))
4283     return;
4284
4285   // redraw player name before (possibly) opening request dialogs
4286   drawTypeNameText(name, pos, FALSE);
4287
4288   int last_user_nr = user.nr;
4289
4290   if (game_status == GAME_MODE_PSEUDO_TYPENAMES)
4291   {
4292     // save setup of currently active user (may differ from edited user)
4293     SaveSetup();
4294
4295     // temporarily change active user to edited user
4296     user.nr = type_name_nr;
4297
4298     if (create_user &&
4299         Request("Use current setup values for the new player?", REQ_ASK))
4300     {
4301       // use current setup values for new user, but create new player UUID
4302       setup.player_uuid = getStringCopy(getUUID());
4303     }
4304     else
4305     {
4306       // load setup for existing user (or start with defaults for new user)
4307       LoadSetup();
4308     }
4309   }
4310
4311   char *setup_filename = getSetupFilename();
4312   boolean setup_exists = fileExists(setup_filename);
4313
4314   // change name of edited user in setup structure
4315   strcpy(setup.player_name, name);
4316
4317   // save setup of edited user
4318   SaveSetup();
4319
4320   // change name of edited user on score server
4321   ApiRenamePlayerAsThread();
4322
4323   if (game_status == GAME_MODE_PSEUDO_TYPENAMES || reset_setup)
4324   {
4325     if (reset_setup)
4326     {
4327       if (Request("Reset setup values for this player?", REQ_ASK))
4328       {
4329         // remove setup config file
4330         unlink(setup_filename);
4331
4332         // set player name to default player name
4333         LoadSetup();
4334
4335         // update player name used by name typing functions
4336         strcpy(name, setup.player_name);
4337
4338         setTypeNameValues_Name(name, pos);
4339       }
4340     }
4341     else if (remove_user && type_name_nr != 0)
4342     {
4343       if (Request("Remove settings and tapes for deleted player?", REQ_ASK))
4344       {
4345         char *user_dir = getUserGameDataDir();
4346         char *user_dir_removed =
4347           getStringCat3WithSeparator(user_dir, "REMOVED",
4348                                      getCurrentTimestamp(), ".");
4349
4350         if (rename(user_dir, user_dir_removed) != 0)
4351           Request("Removing settings and tapes failed!", REQ_CONFIRM);
4352
4353         checked_free(user_dir_removed);
4354       }
4355     }
4356     else if (create_user && type_name_nr != 0 && !setup_exists)
4357     {
4358       if (Request("Create empty level set for the new player?", REQ_ASK))
4359       {
4360         char *levelset_subdir = getNewUserLevelSubdir();
4361
4362         if (CreateUserLevelSet(levelset_subdir, name, name, 100, FALSE))
4363         {
4364           AddUserLevelSetToLevelInfo(levelset_subdir);
4365
4366           LevelDirTree *leveldir_current_last = leveldir_current;
4367
4368           leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4369                                                        levelset_subdir);
4370
4371           // set level number of newly created level set to default value
4372           LoadLevelSetup_SeriesInfo();
4373
4374           // set newly created level set as current level set for new user
4375           SaveLevelSetup_LastSeries();
4376           SaveLevelSetup_SeriesInfo();
4377
4378           leveldir_current = leveldir_current_last;
4379         }
4380         else
4381         {
4382           Request("Creating new level set failed!", REQ_CONFIRM);
4383         }
4384       }
4385     }
4386
4387     // restore currently active user
4388     user.nr = last_user_nr;
4389
4390     // restore setup of currently active user
4391     LoadSetup();
4392
4393     // restore last level set of currently active user
4394     LoadLevelSetup_LastSeries();
4395     LoadLevelSetup_SeriesInfo();
4396   }
4397 }
4398
4399 static void HandleTypeNameExt(boolean initialize, Key key)
4400 {
4401   static struct TextPosInfo pos_name = { 0 };
4402   static char name[MAX_PLAYER_NAME_LEN + 1] = { 0 };
4403   static int xpos = 0;
4404   struct TextPosInfo *pos = &pos_name;
4405   char key_char = getValidConfigValueChar(getCharFromKey(key));
4406   boolean is_valid_key_char = (key_char != 0 && (key_char != ' ' || xpos > 0));
4407   boolean active = TRUE;
4408
4409   if (initialize)
4410   {
4411     getTypeNameValues(name, pos, &xpos);
4412
4413     int sx = mSX + ALIGNED_TEXT_XPOS(pos);
4414     int sy = mSY + ALIGNED_TEXT_YPOS(pos);
4415
4416     StartTextInput(sx, sy, pos->width, pos->height);
4417   }
4418   else if (is_valid_key_char && xpos < MAX_PLAYER_NAME_LEN)
4419   {
4420     name[xpos] = key_char;
4421     name[xpos + 1] = 0;
4422
4423     xpos++;
4424   }
4425   else if ((key == KSYM_Delete || key == KSYM_BackSpace) && xpos > 0)
4426   {
4427     xpos--;
4428
4429     name[xpos] = 0;
4430   }
4431   else if (key == KSYM_Return || key == KSYM_Escape)
4432   {
4433     boolean changed = (key == KSYM_Return);
4434
4435     StopTextInput();
4436
4437     setTypeNameValues(name, pos, changed);
4438
4439     active = FALSE;
4440   }
4441
4442   drawTypeNameText(name, pos, active);
4443
4444   if (!active)
4445   {
4446     SetGameStatus(game_status_last_screen);
4447
4448     if (game_status == GAME_MODE_MAIN)
4449       InitializeMainControls();
4450   }
4451 }
4452
4453 static void DrawTypeName(void)
4454 {
4455   HandleTypeNameExt(TRUE, 0);
4456 }
4457
4458 void HandleTypeName(Key key)
4459 {
4460   HandleTypeNameExt(FALSE, key);
4461 }
4462
4463
4464 // ============================================================================
4465 // tree menu functions
4466 // ============================================================================
4467
4468 static int getAlignXOffsetFromTreeInfo(TreeInfo *ti)
4469 {
4470   if (game_status != GAME_MODE_SETUP ||
4471       DRAW_MODE_SETUP(setup_mode) != SETUP_MODE_CHOOSE_OTHER)
4472     return 0;
4473
4474   int max_text_size = 0;
4475   TreeInfo *node;
4476
4477   for (node = getTreeInfoFirstGroupEntry(ti); node != NULL; node = node->next)
4478     max_text_size = MAX(max_text_size, strlen(node->name));
4479
4480   int num_entries = numTreeInfoInGroup(ti);
4481   boolean scrollbar_needed = (num_entries > NUM_MENU_ENTRIES_ON_SCREEN);
4482   int font_nr = MENU_CHOOSE_TREE_FONT(FC_RED);
4483   int text_width = max_text_size * getFontWidth(font_nr);
4484   int button_width = SC_MENUBUTTON_XSIZE;
4485   int scrollbar_xpos = SC_SCROLLBAR_XPOS + menu.scrollbar_xoffset;
4486   int screen_width = (scrollbar_needed ? scrollbar_xpos : SXSIZE);
4487   int align = menu.list_setup[SETUP_MODE_CHOOSE_OTHER].align;
4488   int x = ALIGNED_XPOS(0, screen_width, align) * -1;
4489   int align_xoffset_raw = ALIGNED_XPOS(x, button_width + text_width, align);
4490   int align_xoffset = MAX(0, align_xoffset_raw);
4491
4492   return align_xoffset;
4493 }
4494
4495 static int getAlignYOffsetFromTreeInfo(TreeInfo *ti)
4496 {
4497   if (game_status != GAME_MODE_SETUP ||
4498       DRAW_MODE_SETUP(setup_mode) != SETUP_MODE_CHOOSE_OTHER)
4499     return 0;
4500
4501   int num_entries = numTreeInfoInGroup(ti);
4502   int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN);
4503   int font_nr = MENU_CHOOSE_TREE_FONT(FC_RED);
4504   int font_height = getFontHeight(font_nr);
4505   int text_height = font_height * num_page_entries;
4506   int page_height = font_height * NUM_MENU_ENTRIES_ON_SCREEN;
4507   int align = menu.list_setup[SETUP_MODE_CHOOSE_OTHER].valign;
4508   int y = ALIGNED_YPOS(0, page_height, align) * -1;
4509   int align_yoffset_raw = ALIGNED_YPOS(y, text_height, align);
4510   int align_yoffset = MAX(0, align_yoffset_raw);
4511
4512   return align_yoffset;
4513 }
4514
4515 static void StartPlayingFromHallOfFame(void)
4516 {
4517   level_nr = scores.next_level_nr;
4518   LoadLevel(level_nr);
4519
4520   StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4521 }
4522
4523 static void DrawChooseTree(TreeInfo **ti_ptr)
4524 {
4525   int fade_mask = REDRAW_FIELD;
4526   boolean restart_music = (game_status != game_status_last_screen &&
4527                            game_status_last_screen != GAME_MODE_SCOREINFO);
4528
4529   scores.continue_on_return = (game_status == GAME_MODE_SCORES &&
4530                                game_status_last_screen == GAME_MODE_PLAYING);
4531
4532   if (CheckFadeAll())
4533     fade_mask = REDRAW_ALL;
4534
4535   if (*ti_ptr != NULL && strEqual((*ti_ptr)->subdir, STRING_TOP_DIRECTORY))
4536   {
4537     if (game_status == GAME_MODE_SETUP)
4538     {
4539       execSetupArtwork();
4540     }
4541     else if (game_status == GAME_MODE_SCORES && scores.continue_playing)
4542     {
4543       StartPlayingFromHallOfFame();
4544     }
4545     else
4546     {
4547       SetGameStatus(GAME_MODE_MAIN);
4548
4549       DrawMainMenu();
4550     }
4551
4552     return;
4553   }
4554
4555   UnmapAllGadgets();
4556
4557   FreeScreenGadgets();
4558   CreateScreenGadgets();
4559
4560   if (restart_music)
4561     FadeMenuSoundsAndMusic();
4562
4563   FadeOut(fade_mask);
4564
4565   // needed if different viewport properties defined for this screen
4566   ChangeViewportPropertiesIfNeeded();
4567
4568   if (game_status == GAME_MODE_NAMES)
4569     SetMainBackgroundImage(IMG_BACKGROUND_NAMES);
4570   else if (game_status == GAME_MODE_LEVELNR)
4571     SetMainBackgroundImage(IMG_BACKGROUND_LEVELNR);
4572   else if (game_status == GAME_MODE_LEVELS)
4573     SetMainBackgroundImage(IMG_BACKGROUND_LEVELS);
4574   else if (game_status == GAME_MODE_SCORES)
4575     SetMainBackgroundImage(IMG_BACKGROUND_SCORES);
4576
4577   ClearField();
4578
4579   OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
4580
4581   // map gadgets for high score screen
4582   if (game_status == GAME_MODE_SCORES)
4583     MapScreenMenuGadgets(SCREEN_MASK_SCORES);
4584
4585   MapScreenTreeGadgets(*ti_ptr);
4586
4587   HandleChooseTree(0, 0, 0, 0, MB_MENU_INITIALIZE, ti_ptr);
4588
4589   DrawMaskedBorder(fade_mask);
4590
4591   if (restart_music)
4592     PlayMenuSoundsAndMusic();
4593
4594   FadeIn(fade_mask);
4595 }
4596
4597 static int getChooseTreeFont(TreeInfo *node, boolean active)
4598 {
4599   if (game_status == GAME_MODE_SCORES)
4600     return (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1);
4601   else
4602     return MENU_CHOOSE_TREE_FONT(MENU_CHOOSE_TREE_COLOR(node, active));
4603 }
4604
4605 static void drawChooseTreeText(TreeInfo *ti, int y, boolean active)
4606 {
4607   int num_entries = numTreeInfoInGroup(ti);
4608   boolean scrollbar_needed = (num_entries > NUM_MENU_ENTRIES_ON_SCREEN);
4609   int scrollbar_xpos = SC_SCROLLBAR_XPOS + menu.scrollbar_xoffset;
4610   int screen_width = (scrollbar_needed ? scrollbar_xpos : SXSIZE);
4611   int first_entry = ti->cl_first;
4612   int entry_pos = first_entry + y;
4613   TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti);
4614   TreeInfo *node = getTreeInfoFromPos(node_first, entry_pos);
4615   int font_nr = getChooseTreeFont(node, active);
4616   int font_xoffset = getFontDrawOffsetX(font_nr);
4617   int xpos = MENU_SCREEN_START_XPOS;
4618   int ypos = MENU_SCREEN_START_YPOS + y;
4619   int startdx = xpos * 32;
4620   int startdy = ypos * 32;
4621   int startx = amSX + startdx;
4622   int starty = amSY + startdy;
4623   int startx_text = startx + font_xoffset;
4624   int endx_text = amSX + screen_width;
4625   int max_text_size = endx_text - startx_text;
4626   int max_buffer_len = max_text_size / getFontWidth(font_nr);
4627   char buffer[max_buffer_len + 1];
4628
4629   if (game_status == GAME_MODE_SCORES && !node->parent_link)
4630   {
4631     int font_nr1 = (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1);
4632     int font_nr2 = (active ? FONT_TEXT_2_ACTIVE : FONT_TEXT_2);
4633     int font_nr3 = (active ? FONT_TEXT_3_ACTIVE : FONT_TEXT_3);
4634     int font_nr4 = (active ? FONT_TEXT_4_ACTIVE : FONT_TEXT_4);
4635     int font_size_1 = getFontWidth(font_nr1);
4636     int font_size_3 = getFontWidth(font_nr3);
4637     int font_size_4 = getFontWidth(font_nr4);
4638     int text_size_1 = 4 * font_size_1;
4639     int text_size_4 = 5 * font_size_4;
4640     int border = amSX - SX + getFontDrawOffsetX(font_nr1);
4641     int dx1 = 0;
4642     int dx3 = text_size_1;
4643     int dx4 = SXSIZE - 2 * startdx - 2 * border - text_size_4;
4644     int num_dots = (dx4 - dx3) / font_size_3;
4645     int startx1 = startx + dx1;
4646     int startx3 = startx + dx3;
4647     int startx4 = startx + dx4;
4648     int pos = node->pos;
4649     char *pos_text = getHallOfFameRankText(pos, 3);
4650     int i;
4651
4652     DrawText(startx1, starty, pos_text, font_nr1);
4653
4654     for (i = 0; i < num_dots; i++)
4655       DrawText(startx3 + i * font_size_3, starty, ".", font_nr3);
4656
4657     if (!strEqual(scores.entry[pos].name, EMPTY_PLAYER_NAME))
4658       DrawText(startx3, starty, scores.entry[pos].name, font_nr2);
4659
4660     DrawText(startx4, starty, getHallOfFameScoreText(pos, 5), font_nr4);
4661   }
4662   else
4663   {
4664     strncpy(buffer, node->name, max_buffer_len);
4665     buffer[max_buffer_len] = '\0';
4666
4667     DrawText(startx, starty, buffer, font_nr);
4668   }
4669 }
4670
4671 static void drawChooseTreeHeadExt(int type, char *title_string)
4672 {
4673   int yoffset_sets = MENU_TITLE1_YPOS;
4674   int yoffset_setup = 16;
4675   int yoffset = (type == TREE_TYPE_SCORE_ENTRY ||
4676                  type == TREE_TYPE_LEVEL_DIR ||
4677                  type == TREE_TYPE_LEVEL_NR ? yoffset_sets : yoffset_setup);
4678
4679   DrawTextSCentered(mSY - SY + yoffset, FONT_TITLE_1, title_string);
4680 }
4681
4682 static void drawChooseTreeHead(TreeInfo *ti)
4683 {
4684   drawChooseTreeHeadExt(ti->type, ti->infotext);
4685 }
4686
4687 static void drawChooseTreeList(TreeInfo *ti)
4688 {
4689   int first_entry = ti->cl_first;
4690   int num_entries = numTreeInfoInGroup(ti);
4691   int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN);
4692   int i;
4693
4694   clearMenuListArea();
4695
4696   for (i = 0; i < num_page_entries; i++)
4697   {
4698     TreeInfo *node, *node_first;
4699     int entry_pos = first_entry + i;
4700
4701     node_first = getTreeInfoFirstGroupEntry(ti);
4702     node = getTreeInfoFromPos(node_first, entry_pos);
4703
4704     drawChooseTreeText(ti, i, FALSE);
4705
4706     if (node->parent_link)
4707       initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU);
4708     else if (node->level_group)
4709       initCursor(i, IMG_MENU_BUTTON_ENTER_MENU);
4710     else
4711       initCursor(i, IMG_MENU_BUTTON);
4712
4713     if (game_status == GAME_MODE_SCORES && node->pos == scores.last_added)
4714       initCursor(i, IMG_MENU_BUTTON_ENTER_MENU);
4715
4716     if (game_status == GAME_MODE_NAMES)
4717       drawChooseTreeEdit(i, FALSE);
4718   }
4719
4720   redraw_mask |= REDRAW_FIELD;
4721 }
4722
4723 static void drawChooseTreeInfo(TreeInfo *ti)
4724 {
4725   int entry_pos = ti->cl_first + ti->cl_cursor;
4726   int last_redraw_mask = redraw_mask;
4727   int ypos = MENU_TITLE2_YPOS;
4728   int font_nr = FONT_TITLE_2;
4729   int x;
4730
4731   if (ti->type == TREE_TYPE_LEVEL_NR)
4732     DrawTextFCentered(ypos, font_nr, leveldir_current->name);
4733
4734   if (ti->type == TREE_TYPE_SCORE_ENTRY)
4735     DrawTextFCentered(ypos, font_nr, "HighScores of Level %d",
4736                       scores.last_level_nr);
4737
4738   if (ti->type != TREE_TYPE_LEVEL_DIR)
4739     return;
4740
4741   TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti);
4742   TreeInfo *node = getTreeInfoFromPos(node_first, entry_pos);
4743
4744   DrawBackgroundForFont(SX, SY + ypos, SXSIZE, getFontHeight(font_nr), font_nr);
4745
4746   if (node->parent_link)
4747     DrawTextFCentered(ypos, font_nr, "leave \"%s\"",
4748                       node->node_parent->name);
4749   else if (node->level_group)
4750     DrawTextFCentered(ypos, font_nr, "enter \"%s\"",
4751                       node->name);
4752   else if (ti->type == TREE_TYPE_LEVEL_DIR)
4753     DrawTextFCentered(ypos, font_nr, "%3d %s (%s)",
4754                       node->levels, (node->levels > 1 ? "levels" : "level"),
4755                       node->class_desc);
4756
4757   // let BackToFront() redraw only what is needed
4758   redraw_mask = last_redraw_mask;
4759   for (x = 0; x < SCR_FIELDX; x++)
4760     MarkTileDirty(x, 1);
4761 }
4762
4763 static void drawChooseTreeCursorAndText(TreeInfo *ti, boolean active)
4764 {
4765   drawChooseTreeCursor(ti->cl_cursor, active);
4766   drawChooseTreeText(ti, ti->cl_cursor, active);
4767 }
4768
4769 static void drawChooseTreeScreen(TreeInfo *ti)
4770 {
4771   drawChooseTreeHead(ti);
4772   drawChooseTreeList(ti);
4773   drawChooseTreeInfo(ti);
4774   drawChooseTreeCursorAndText(ti, TRUE);
4775
4776   AdjustChooseTreeScrollbar(ti, SCREEN_CTRL_ID_SCROLL_VERTICAL);
4777
4778   // scroll bar and buttons may just have been added after reloading scores
4779   if (game_status == GAME_MODE_SCORES)
4780     MapScreenTreeGadgets(ti);
4781 }
4782
4783 static TreeInfo *setHallOfFameActiveEntry(TreeInfo **ti_ptr)
4784 {
4785   int score_pos = scores.last_added;
4786
4787   if (game_status_last_screen == GAME_MODE_SCOREINFO)
4788     score_pos = scores.last_entry_nr;
4789
4790   // set current tree entry to last added score entry
4791   *ti_ptr = getTreeInfoFromIdentifier(score_entries, i_to_a(score_pos));
4792
4793   // if that fails, set current tree entry to first entry (back link)
4794   if (*ti_ptr == NULL)
4795     *ti_ptr = score_entries->node_group;
4796
4797   int num_entries = numTreeInfoInGroup(*ti_ptr);
4798   int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN);
4799   int pos_score = getPosFromTreeInfo(*ti_ptr);
4800   int pos_first_raw = pos_score - (num_page_entries + 1) / 2 + 1;
4801   int pos_first = MIN(MAX(0, pos_first_raw), num_entries - num_page_entries);
4802
4803   (*ti_ptr)->cl_first = pos_first;
4804   (*ti_ptr)->cl_cursor = pos_score - pos_first;
4805
4806   return *ti_ptr;
4807 }
4808
4809 static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
4810                              TreeInfo **ti_ptr)
4811 {
4812   TreeInfo *ti = *ti_ptr;
4813   boolean has_scrollbar = screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->mapped;
4814   int mx_scrollbar = screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->x;
4815   int mx_right_border = (has_scrollbar ? mx_scrollbar : SX + SXSIZE);
4816   int sx1_edit_name = getChooseTreeEditXPosReal(POS_LEFT);
4817   int sx2_edit_name = getChooseTreeEditXPosReal(POS_RIGHT);
4818   int x = 0;
4819   int y = (ti != NULL ? ti->cl_cursor : 0);
4820   int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
4821   int num_entries = numTreeInfoInGroup(ti);
4822   int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN);
4823   boolean position_set_by_scrollbar = (dx == 999);
4824
4825   if (game_status == GAME_MODE_SCORES)
4826   {
4827     if (server_scores.updated)
4828     {
4829       // reload scores, using updated server score cache file
4830       LoadLocalAndServerScore(scores.last_level_nr, FALSE);
4831
4832       server_scores.updated = FALSE;
4833
4834       DrawHallOfFame_setScoreEntries();
4835
4836       ti = setHallOfFameActiveEntry(ti_ptr);
4837
4838       if (button != MB_MENU_INITIALIZE)
4839         drawChooseTreeScreen(ti);
4840     }
4841   }
4842
4843   if (button == MB_MENU_INITIALIZE)
4844   {
4845     int num_entries = numTreeInfoInGroup(ti);
4846     int entry_pos = getPosFromTreeInfo(ti);
4847
4848     align_xoffset = getAlignXOffsetFromTreeInfo(ti);
4849     align_yoffset = getAlignYOffsetFromTreeInfo(ti);
4850
4851     if (game_status == GAME_MODE_SCORES)
4852     {
4853       ti = setHallOfFameActiveEntry(ti_ptr);
4854     }
4855     else if (ti->cl_first == -1)
4856     {
4857       // only on initialization
4858       ti->cl_first = MAX(0, entry_pos - num_page_entries + 1);
4859       ti->cl_cursor = entry_pos - ti->cl_first;
4860
4861     }
4862     else if (ti->cl_cursor >= num_page_entries ||
4863              (num_entries > num_page_entries &&
4864               num_entries - ti->cl_first < num_page_entries))
4865     {
4866       // only after change of list size (by custom graphic configuration)
4867       ti->cl_first = MAX(0, entry_pos - num_page_entries + 1);
4868       ti->cl_cursor = entry_pos - ti->cl_first;
4869     }
4870
4871     if (position_set_by_scrollbar)
4872       ti->cl_first = dy;
4873
4874     drawChooseTreeScreen(ti);
4875
4876     return;
4877   }
4878   else if (button == MB_MENU_LEAVE)
4879   {
4880     if (game_status != GAME_MODE_SCORES)
4881       FadeSetLeaveMenu();
4882
4883     PlaySound(SND_MENU_ITEM_SELECTING);
4884
4885     if (ti->node_parent)
4886     {
4887       *ti_ptr = ti->node_parent;
4888       DrawChooseTree(ti_ptr);
4889     }
4890     else if (game_status == GAME_MODE_SETUP)
4891     {
4892       if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE ||
4893           setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED ||
4894           setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY ||
4895           setup_mode == SETUP_MODE_CHOOSE_SNAPSHOT_MODE)
4896         execSetupGame();
4897       else if (setup_mode == SETUP_MODE_CHOOSE_WINDOW_SIZE ||
4898                setup_mode == SETUP_MODE_CHOOSE_SCALING_TYPE ||
4899                setup_mode == SETUP_MODE_CHOOSE_RENDERING ||
4900                setup_mode == SETUP_MODE_CHOOSE_VSYNC)
4901         execSetupGraphics();
4902       else if (setup_mode == SETUP_MODE_CHOOSE_VOLUME_SIMPLE ||
4903                setup_mode == SETUP_MODE_CHOOSE_VOLUME_LOOPS ||
4904                setup_mode == SETUP_MODE_CHOOSE_VOLUME_MUSIC)
4905         execSetupSound();
4906       else if (setup_mode == SETUP_MODE_CHOOSE_TOUCH_CONTROL ||
4907                setup_mode == SETUP_MODE_CHOOSE_MOVE_DISTANCE ||
4908                setup_mode == SETUP_MODE_CHOOSE_DROP_DISTANCE ||
4909                setup_mode == SETUP_MODE_CHOOSE_TRANSPARENCY ||
4910                setup_mode == SETUP_MODE_CHOOSE_GRID_XSIZE_0 ||
4911                setup_mode == SETUP_MODE_CHOOSE_GRID_YSIZE_0 ||
4912                setup_mode == SETUP_MODE_CHOOSE_GRID_XSIZE_1 ||
4913                setup_mode == SETUP_MODE_CHOOSE_GRID_YSIZE_1)
4914         execSetupTouch();
4915       else
4916         execSetupArtwork();
4917     }
4918     else
4919     {
4920       if (game_status == GAME_MODE_LEVELNR)
4921       {
4922         int new_level_nr = atoi(level_number_current->identifier);
4923
4924         HandleMainMenu_SelectLevel(0, 0, new_level_nr);
4925       }
4926
4927       SetGameStatus(GAME_MODE_MAIN);
4928
4929       DrawMainMenu();
4930     }
4931
4932     return;
4933   }
4934
4935 #if defined(PLATFORM_ANDROID)
4936   // directly continue when touching the screen after playing
4937   if ((mx || my) && scores.continue_on_return)
4938   {
4939     // ignore touch events until released
4940     mx = my = 0;
4941   }
4942 #endif
4943
4944   // any mouse click or cursor key stops leaving scores by "Return" key
4945   if ((mx || my || dx || dy) && scores.continue_on_return)
4946   {
4947     scores.continue_on_return = FALSE;
4948     level_nr = scores.last_level_nr;
4949     LoadLevel(level_nr);
4950   }
4951
4952   if (mx || my)         // mouse input
4953   {
4954     x = (mx - amSX) / 32;
4955     y = (my - amSY) / 32 - MENU_SCREEN_START_YPOS;
4956
4957     if (game_status == GAME_MODE_NAMES)
4958       drawChooseTreeEdit(ti->cl_cursor, FALSE);
4959   }
4960   else if (dx || dy)    // keyboard or scrollbar/scrollbutton input
4961   {
4962     // move cursor instead of scrolling when already at start/end of list
4963     if (dy == -1 * SCROLL_LINE && ti->cl_first == 0)
4964       dy = -1;
4965     else if (dy == +1 * SCROLL_LINE &&
4966              ti->cl_first + num_page_entries == num_entries)
4967       dy = 1;
4968
4969     // handle scrolling screen one line or page
4970     if (ti->cl_cursor + dy < 0 ||
4971         ti->cl_cursor + dy > num_page_entries - 1)
4972     {
4973       boolean redraw = FALSE;
4974
4975       if (ABS(dy) == SCROLL_PAGE)
4976         step = num_page_entries - 1;
4977
4978       if (dy < 0 && ti->cl_first > 0)
4979       {
4980         // scroll page/line up
4981
4982         ti->cl_first -= step;
4983         if (ti->cl_first < 0)
4984           ti->cl_first = 0;
4985
4986         redraw = TRUE;
4987       }
4988       else if (dy > 0 && ti->cl_first + num_page_entries < num_entries)
4989       {
4990         // scroll page/line down
4991
4992         ti->cl_first += step;
4993         if (ti->cl_first + num_page_entries > num_entries)
4994           ti->cl_first = MAX(0, num_entries - num_page_entries);
4995
4996         redraw = TRUE;
4997       }
4998
4999       if (redraw)
5000         drawChooseTreeScreen(ti);
5001
5002       return;
5003     }
5004
5005     // handle moving cursor one line
5006     y = ti->cl_cursor + dy;
5007   }
5008
5009   if (game_status == GAME_MODE_SCORES && ABS(dx) == 1)
5010   {
5011     HandleHallOfFame_SelectLevel(1, dx);
5012
5013     return;
5014   }
5015   else if (dx == 1)
5016   {
5017     TreeInfo *node_first, *node_cursor;
5018     int entry_pos = ti->cl_first + y;
5019
5020     node_first = getTreeInfoFirstGroupEntry(ti);
5021     node_cursor = getTreeInfoFromPos(node_first, entry_pos);
5022
5023     if (node_cursor->node_group)
5024     {
5025       FadeSetEnterMenu();
5026
5027       PlaySound(SND_MENU_ITEM_SELECTING);
5028
5029       node_cursor->cl_first = ti->cl_first;
5030       node_cursor->cl_cursor = ti->cl_cursor;
5031
5032       *ti_ptr = node_cursor->node_group;
5033       DrawChooseTree(ti_ptr);
5034
5035       return;
5036     }
5037   }
5038   else if ((dx == -1 || button == MB_MENU_CONTINUE) && ti->node_parent)
5039   {
5040     if (game_status != GAME_MODE_SCORES)
5041       FadeSetLeaveMenu();
5042
5043     PlaySound(SND_MENU_ITEM_SELECTING);
5044
5045     *ti_ptr = ti->node_parent;
5046     DrawChooseTree(ti_ptr);
5047
5048     return;
5049   }
5050
5051   if (!anyScrollbarGadgetActive() &&
5052       IN_VIS_MENU(x, y) &&
5053       mx < mx_right_border &&
5054       y >= 0 && y < num_page_entries)
5055   {
5056     if (button)
5057     {
5058       if (game_status == GAME_MODE_NAMES)
5059       {
5060         if (mx >= sx1_edit_name && mx <= sx2_edit_name)
5061           drawChooseTreeEdit(y, TRUE);
5062       }
5063
5064       if (y != ti->cl_cursor)
5065       {
5066         PlaySound(SND_MENU_ITEM_ACTIVATING);
5067
5068         drawChooseTreeCursorAndText(ti, FALSE);
5069
5070         ti->cl_cursor = y;
5071
5072         drawChooseTreeCursorAndText(ti, TRUE);
5073
5074         drawChooseTreeInfo(ti);
5075       }
5076       else if (dx < 0)
5077       {
5078         if (game_status == GAME_MODE_SETUP)
5079         {
5080           if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE ||
5081               setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED ||
5082               setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY ||
5083               setup_mode == SETUP_MODE_CHOOSE_SNAPSHOT_MODE)
5084             execSetupGame();
5085           else if (setup_mode == SETUP_MODE_CHOOSE_WINDOW_SIZE ||
5086                    setup_mode == SETUP_MODE_CHOOSE_SCALING_TYPE ||
5087                    setup_mode == SETUP_MODE_CHOOSE_RENDERING ||
5088                    setup_mode == SETUP_MODE_CHOOSE_VSYNC)
5089             execSetupGraphics();
5090           else if (setup_mode == SETUP_MODE_CHOOSE_VOLUME_SIMPLE ||
5091                    setup_mode == SETUP_MODE_CHOOSE_VOLUME_LOOPS ||
5092                    setup_mode == SETUP_MODE_CHOOSE_VOLUME_MUSIC)
5093             execSetupSound();
5094           else if (setup_mode == SETUP_MODE_CHOOSE_TOUCH_CONTROL ||
5095                    setup_mode == SETUP_MODE_CHOOSE_MOVE_DISTANCE ||
5096                    setup_mode == SETUP_MODE_CHOOSE_DROP_DISTANCE ||
5097                    setup_mode == SETUP_MODE_CHOOSE_TRANSPARENCY ||
5098                    setup_mode == SETUP_MODE_CHOOSE_GRID_XSIZE_0 ||
5099                    setup_mode == SETUP_MODE_CHOOSE_GRID_YSIZE_0 ||
5100                    setup_mode == SETUP_MODE_CHOOSE_GRID_XSIZE_1 ||
5101                    setup_mode == SETUP_MODE_CHOOSE_GRID_YSIZE_1)
5102             execSetupTouch();
5103           else
5104             execSetupArtwork();
5105         }
5106       }
5107     }
5108     else
5109     {
5110       TreeInfo *node_first, *node_cursor;
5111       int entry_pos = ti->cl_first + y;
5112
5113       PlaySound(SND_MENU_ITEM_SELECTING);
5114
5115       node_first = getTreeInfoFirstGroupEntry(ti);
5116       node_cursor = getTreeInfoFromPos(node_first, entry_pos);
5117
5118       if (node_cursor->node_group)
5119       {
5120         FadeSetEnterMenu();
5121
5122         node_cursor->cl_first = ti->cl_first;
5123         node_cursor->cl_cursor = ti->cl_cursor;
5124
5125         *ti_ptr = node_cursor->node_group;
5126         DrawChooseTree(ti_ptr);
5127       }
5128       else if (node_cursor->parent_link)
5129       {
5130         if (game_status != GAME_MODE_SCORES)
5131           FadeSetLeaveMenu();
5132
5133         *ti_ptr = node_cursor->node_parent;
5134         DrawChooseTree(ti_ptr);
5135       }
5136       else
5137       {
5138         if (game_status != GAME_MODE_SCORES)
5139           FadeSetEnterMenu();
5140
5141         node_cursor->cl_first = ti->cl_first;
5142         node_cursor->cl_cursor = ti->cl_cursor;
5143
5144         *ti_ptr = node_cursor;
5145
5146         if (ti->type == TREE_TYPE_LEVEL_DIR)
5147         {
5148           LoadLevelSetup_SeriesInfo();
5149
5150           SaveLevelSetup_LastSeries();
5151           SaveLevelSetup_SeriesInfo();
5152           TapeErase();
5153         }
5154
5155         if (game_status == GAME_MODE_SETUP)
5156         {
5157           if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE ||
5158               setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED ||
5159               setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY ||
5160               setup_mode == SETUP_MODE_CHOOSE_SNAPSHOT_MODE)
5161             execSetupGame();
5162           else if (setup_mode == SETUP_MODE_CHOOSE_WINDOW_SIZE ||
5163                    setup_mode == SETUP_MODE_CHOOSE_SCALING_TYPE ||
5164                    setup_mode == SETUP_MODE_CHOOSE_RENDERING ||
5165                    setup_mode == SETUP_MODE_CHOOSE_VSYNC)
5166             execSetupGraphics();
5167           else if (setup_mode == SETUP_MODE_CHOOSE_VOLUME_SIMPLE ||
5168                    setup_mode == SETUP_MODE_CHOOSE_VOLUME_LOOPS ||
5169                    setup_mode == SETUP_MODE_CHOOSE_VOLUME_MUSIC)
5170             execSetupSound();
5171           else if (setup_mode == SETUP_MODE_CHOOSE_TOUCH_CONTROL ||
5172                    setup_mode == SETUP_MODE_CHOOSE_MOVE_DISTANCE ||
5173                    setup_mode == SETUP_MODE_CHOOSE_DROP_DISTANCE ||
5174                    setup_mode == SETUP_MODE_CHOOSE_TRANSPARENCY ||
5175                    setup_mode == SETUP_MODE_CHOOSE_GRID_XSIZE_0 ||
5176                    setup_mode == SETUP_MODE_CHOOSE_GRID_YSIZE_0 ||
5177                    setup_mode == SETUP_MODE_CHOOSE_GRID_XSIZE_1 ||
5178                    setup_mode == SETUP_MODE_CHOOSE_GRID_YSIZE_1)
5179             execSetupTouch();
5180           else
5181             execSetupArtwork();
5182         }
5183         else
5184         {
5185           if (game_status == GAME_MODE_LEVELNR)
5186           {
5187             int new_level_nr = atoi(level_number_current->identifier);
5188
5189             HandleMainMenu_SelectLevel(0, 0, new_level_nr);
5190           }
5191           else if (game_status == GAME_MODE_LEVELS)
5192           {
5193             // store level set if chosen from "last played level set" menu
5194             StoreLastPlayedLevels(leveldir_current);
5195
5196             // store if level set chosen from "last played level set" menu
5197             SaveLevelSetup_LastSeries();
5198           }
5199           else if (game_status == GAME_MODE_NAMES)
5200           {
5201             if (mx >= sx1_edit_name && mx <= sx2_edit_name)
5202             {
5203               SetGameStatus(GAME_MODE_PSEUDO_TYPENAMES);
5204
5205               DrawTypeName();
5206
5207               return;
5208             }
5209
5210             // change active user to selected user
5211             user.nr = entry_pos;
5212
5213             // save number of new active user
5214             SaveUserSetup();
5215
5216             // load setup of new active user
5217             LoadSetup();
5218
5219             // load last level set of new active user
5220             LoadLevelSetup_LastSeries();
5221             LoadLevelSetup_SeriesInfo();
5222
5223             // update list of last played level sets
5224             UpdateLastPlayedLevels_TreeInfo();
5225
5226             TapeErase();
5227
5228             ToggleFullscreenIfNeeded();
5229             ChangeWindowScalingIfNeeded();
5230
5231             ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
5232             ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
5233             ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
5234           }
5235           else if (game_status == GAME_MODE_SCORES)
5236           {
5237             if (scores.continue_playing && scores.continue_on_return)
5238             {
5239               StartPlayingFromHallOfFame();
5240
5241               return;
5242             }
5243             else if (!scores.continue_on_return)
5244             {
5245               SetGameStatus(GAME_MODE_SCOREINFO);
5246
5247               DrawScoreInfo(node_cursor->pos);
5248
5249               return;
5250             }
5251           }
5252
5253           SetGameStatus(GAME_MODE_MAIN);
5254
5255           DrawMainMenu();
5256         }
5257       }
5258     }
5259   }
5260
5261   if (game_status == GAME_MODE_SCORES)
5262     PlayMenuSoundIfLoop();
5263 }
5264
5265 void DrawChoosePlayerName(void)
5266 {
5267   int i;
5268
5269   if (player_name != NULL)
5270   {
5271     freeTreeInfo(player_name);
5272
5273     player_name = NULL;
5274   }
5275
5276   for (i = 0; i < MAX_PLAYER_NAMES; i++)
5277   {
5278     TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_PLAYER_NAME);
5279     char identifier[32], name[MAX_PLAYER_NAME_LEN + 1];
5280     int value = i;
5281
5282     ti->node_top = &player_name;
5283     ti->sort_priority = 10000 + value;
5284     ti->color = getPlayerNameColor(global.user_names[i]);
5285
5286     snprintf(identifier, sizeof(identifier), "%d", value);
5287     snprintf(name, sizeof(name), "%s", global.user_names[i]);
5288
5289     setString(&ti->identifier, identifier);
5290     setString(&ti->name, name);
5291     setString(&ti->name_sorting, name);
5292
5293     pushTreeInfo(&player_name, ti);
5294   }
5295
5296   // sort player entries by player number
5297   sortTreeInfo(&player_name);
5298
5299   // set current player entry to selected player entry
5300   player_name_current =
5301     getTreeInfoFromIdentifier(player_name, i_to_a(user.nr));
5302
5303   // if that fails, set current player name to first available name
5304   if (player_name_current == NULL)
5305     player_name_current = player_name;
5306
5307   // set text size for main name input (also used on name selection screen)
5308   InitializeMainControls();
5309
5310   DrawChooseTree(&player_name_current);
5311 }
5312
5313 void HandleChoosePlayerName(int mx, int my, int dx, int dy, int button)
5314 {
5315   HandleChooseTree(mx, my, dx, dy, button, &player_name_current);
5316 }
5317
5318 void DrawChooseLevelSet(void)
5319 {
5320   DrawChooseTree(&leveldir_current);
5321 }
5322
5323 void HandleChooseLevelSet(int mx, int my, int dx, int dy, int button)
5324 {
5325   HandleChooseTree(mx, my, dx, dy, button, &leveldir_current);
5326 }
5327
5328 void DrawChooseLevelNr(void)
5329 {
5330   int i;
5331
5332   if (level_number != NULL)
5333   {
5334     freeTreeInfo(level_number);
5335
5336     level_number = NULL;
5337   }
5338
5339   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;i++)
5340   {
5341     TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_LEVEL_NR);
5342     char identifier[32], name[64];
5343     int value = i;
5344
5345     // temporarily load level info to get level name
5346     LoadLevelInfoOnly(i);
5347
5348     ti->node_top = &level_number;
5349     ti->sort_priority = 10000 + value;
5350     ti->color = (level.no_level_file ? FC_BLUE :
5351                  LevelStats_getSolved(i) ? FC_GREEN :
5352                  LevelStats_getPlayed(i) ? FC_YELLOW : FC_RED);
5353
5354     snprintf(identifier, sizeof(identifier), "%d", value);
5355     snprintf(name, sizeof(name), "%03d: %s", value,
5356              (level.no_level_file ? "(no file)" : level.name));
5357
5358     setString(&ti->identifier, identifier);
5359     setString(&ti->name, name);
5360     setString(&ti->name_sorting, name);
5361
5362     pushTreeInfo(&level_number, ti);
5363   }
5364
5365   // sort level number values to start with lowest level number
5366   sortTreeInfo(&level_number);
5367
5368   // set current level number to current level number
5369   level_number_current =
5370     getTreeInfoFromIdentifier(level_number, i_to_a(level_nr));
5371
5372   // if that also fails, set current level number to first available level
5373   if (level_number_current == NULL)
5374     level_number_current = level_number;
5375
5376   DrawChooseTree(&level_number_current);
5377 }
5378
5379 void HandleChooseLevelNr(int mx, int my, int dx, int dy, int button)
5380 {
5381   HandleChooseTree(mx, my, dx, dy, button, &level_number_current);
5382 }
5383
5384 static void DrawHallOfFame_setScoreEntries(void)
5385 {
5386   int max_empty_entries = 10;   // at least show "top ten" list, if empty
5387   int max_visible_entries = NUM_MENU_ENTRIES_ON_SCREEN - 1;   // w/o back link
5388   int min_score_entries = MIN(max_empty_entries, max_visible_entries);
5389   int score_pos = (scores.last_added >= 0 ? scores.last_added : 0);
5390   int i;
5391
5392   if (score_entries != NULL)
5393   {
5394     freeTreeInfo(score_entries);
5395
5396     score_entries = NULL;
5397   }
5398
5399   for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5400   {
5401     // do not add empty score entries if off-screen
5402     if (scores.entry[i].score == 0 &&
5403         scores.entry[i].time == 0 &&
5404         i >= min_score_entries)
5405       break;
5406
5407     TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_SCORE_ENTRY);
5408     char identifier[32], name[64];
5409     int value = i;
5410
5411     ti->node_top = &score_entries;
5412     ti->sort_priority = 10000 + value;
5413     ti->color = FC_YELLOW;
5414     ti->pos = i;
5415
5416     snprintf(identifier, sizeof(identifier), "%d", value);
5417     snprintf(name, sizeof(name), "%03d.", value + 1);
5418
5419     setString(&ti->identifier, identifier);
5420     setString(&ti->name, name);
5421     setString(&ti->name_sorting, name);
5422
5423     pushTreeInfo(&score_entries, ti);
5424   }
5425
5426   // sort score entries to start with highest score entry
5427   sortTreeInfo(&score_entries);
5428
5429   // add top tree node to create back link to main menu
5430   score_entries = addTopTreeInfoNode(score_entries);
5431
5432   // set current score entry to last added or highest score entry
5433   score_entry_current =
5434     getTreeInfoFromIdentifier(score_entries, i_to_a(score_pos));
5435
5436   // if that fails, set current score entry to first valid score entry
5437   if (score_entry_current == NULL)
5438     score_entry_current = getFirstValidTreeInfoEntry(score_entries);
5439
5440   if (score_entries != NULL && scores.continue_playing)
5441     setString(&score_entries->node_group->name, BACKLINK_TEXT_NEXT);
5442 }
5443
5444 void DrawHallOfFame(int nr)
5445 {
5446   scores.last_level_nr = nr;
5447
5448   // (this is needed when called from GameEnd() after winning a game)
5449   KeyboardAutoRepeatOn();
5450
5451   // (this is needed when called from GameEnd() after winning a game)
5452   SetDrawDeactivationMask(REDRAW_NONE);
5453   SetDrawBackgroundMask(REDRAW_FIELD);
5454
5455   LoadLocalAndServerScore(scores.last_level_nr, TRUE);
5456
5457   DrawHallOfFame_setScoreEntries();
5458
5459   if (scores.last_added >= 0)
5460     SetAnimStatus(GAME_MODE_PSEUDO_SCORESNEW);
5461
5462   FadeSetEnterScreen();
5463
5464   DrawChooseTree(&score_entry_current);
5465 }
5466
5467 static char *getHallOfFameRankText(int nr, int size)
5468 {
5469   static char rank_text[10];
5470   boolean forced = (scores.force_last_added && nr == scores.last_added);
5471   char *rank_text_raw = (forced ? "???" : int2str(nr + 1, size));
5472
5473   sprintf(rank_text, "%s%s", rank_text_raw, (size > 0 || !forced ? "." : ""));
5474
5475   return rank_text;
5476 }
5477
5478 static char *getHallOfFameTimeText(int nr)
5479 {
5480   static char score_text[10];
5481   int time_seconds = scores.entry[nr].time / FRAMES_PER_SECOND;
5482   int mm = (time_seconds / 60) % 60;
5483   int ss = (time_seconds % 60);
5484
5485   sprintf(score_text, "%02d:%02d", mm, ss);     // show playing time
5486
5487   return score_text;
5488 }
5489
5490 static char *getHallOfFameScoreText(int nr, int size)
5491 {
5492   if (!level.rate_time_over_score)
5493     return int2str(scores.entry[nr].score, size);       // show normal score
5494   else if (level.use_step_counter)
5495     return int2str(scores.entry[nr].time, size);        // show number of steps
5496   else
5497     return getHallOfFameTimeText(nr);                   // show playing time
5498 }
5499
5500 static char *getHallOfFameTapeDateText(struct ScoreEntry *entry)
5501 {
5502   static char tape_date[MAX_ISO_DATE_LEN + 1];
5503   int i, j;
5504
5505   if (!strEqual(entry->tape_date, UNKNOWN_NAME) ||
5506       strEqual(entry->tape_basename, UNDEFINED_FILENAME))
5507     return entry->tape_date;
5508
5509   for (i = 0, j = 0; i < 8; i++, j++)
5510   {
5511     tape_date[j] = entry->tape_basename[i];
5512
5513     if (i == 3 || i == 5)
5514       tape_date[++j] = '-';
5515   }
5516
5517   tape_date[MAX_ISO_DATE_LEN] = '\0';
5518
5519   return tape_date;
5520 }
5521
5522 static void HandleHallOfFame_SelectLevel(int step, int direction)
5523 {
5524   int old_level_nr = scores.last_level_nr;
5525   int new_level_nr = old_level_nr + step * direction;
5526
5527   if (new_level_nr < leveldir_current->first_level)
5528     new_level_nr = leveldir_current->first_level;
5529   if (new_level_nr > leveldir_current->last_level)
5530     new_level_nr = leveldir_current->last_level;
5531
5532   if (setup.handicap && new_level_nr > leveldir_current->handicap_level)
5533     new_level_nr = leveldir_current->handicap_level;
5534
5535   if (new_level_nr != old_level_nr)
5536   {
5537     PlaySound(SND_MENU_ITEM_SELECTING);
5538
5539     scores.last_level_nr = level_nr = new_level_nr;
5540     scores.last_entry_nr = 0;
5541
5542     LoadLevel(level_nr);
5543     LoadLocalAndServerScore(level_nr, TRUE);
5544
5545     DrawHallOfFame_setScoreEntries();
5546
5547     if (game_status == GAME_MODE_SCORES)
5548     {
5549       // force remapping optional gadgets (especially scroll bar)
5550       UnmapScreenTreeGadgets();
5551
5552       // redraw complete high score screen, as sub-title has changed
5553       ClearField();
5554
5555       // redraw level selection buttons (which have just been erased)
5556       RedrawScreenMenuGadgets(SCREEN_MASK_SCORES);
5557
5558       HandleChooseTree(0, 0, 0, 0, MB_MENU_INITIALIZE, &score_entry_current);
5559     }
5560     else
5561     {
5562       DrawScoreInfo_Content(scores.last_entry_nr);
5563     }
5564
5565     SaveLevelSetup_SeriesInfo();
5566   }
5567 }
5568
5569 void HandleHallOfFame(int mx, int my, int dx, int dy, int button)
5570 {
5571   HandleChooseTree(mx, my, dx, dy, button, &score_entry_current);
5572 }
5573
5574 static void DrawScoreInfo_Content(int entry_nr)
5575 {
5576   struct ScoreEntry *entry = &scores.entry[entry_nr];
5577   char *pos_text = getHallOfFameRankText(entry_nr, 0);
5578   char *tape_date = getHallOfFameTapeDateText(entry);
5579   int font_head = MENU_INFO_FONT_HEAD;
5580   int font_text = MENU_INFO_FONT_TEXT;
5581   int font_foot = MENU_INFO_FONT_FOOT;
5582   int spacing_para = menu.paragraph_spacing[GAME_MODE_SCOREINFO];
5583   int spacing_line = menu.line_spacing[GAME_MODE_SCOREINFO];
5584   int spacing_left = menu.left_spacing[GAME_MODE_SCOREINFO];
5585   int spacing_top  = menu.top_spacing[GAME_MODE_SCOREINFO];
5586   int xstep = getFontWidth(font_text);
5587   int ystep_para = getMenuTextStep(spacing_para,  font_text);
5588   int ystep_line = getMenuTextStep(spacing_line,  font_text);
5589   int xstart  = mSX - SX + spacing_left;
5590   int ystart  = mSY - SY + spacing_top + getHeadlineSpacing();
5591   int ybottom = mSY - SY + SYSIZE - menu.bottom_spacing[GAME_MODE_SCOREINFO];
5592   int xstart1 = xstart + xstep;
5593   int xstart2 = xstart + xstep * 12;
5594   int select_x = SX + xstart1;
5595   int select_y1, select_y2;
5596   int play_x, play_y;
5597   int play_height = screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE]->height;
5598   boolean play_visible = !strEqual(tape_date, UNKNOWN_NAME);
5599   int font_width = getFontWidth(font_text);
5600   int font_height = getFontHeight(font_text);
5601   int tape_date_width = getTextWidth(tape_date, font_text);
5602   int pad_left = xstart2;
5603   int pad_right = menu.right_spacing[GAME_MODE_SCOREINFO];
5604   int max_chars_per_line = (SXSIZE - pad_left - pad_right) / font_width;
5605   int max_lines_per_text = 5;
5606   int lines;
5607
5608   ClearField();
5609
5610   // redraw level selection buttons (which have just been erased)
5611   RedrawScreenMenuGadgets(SCREEN_MASK_SCORES);
5612
5613   drawChooseTreeHead(score_entries);
5614   drawChooseTreeInfo(score_entries);
5615
5616   DrawTextF(xstart1, ystart, font_head, "Level Set");
5617   lines = DrawTextBufferS(xstart2, ystart, leveldir_current->name, font_text,
5618                           max_chars_per_line, -1, max_lines_per_text, 0, -1,
5619                           TRUE, FALSE, FALSE);
5620   ystart += ystep_line + (lines > 0 ? lines - 1 : 0) * font_height;
5621
5622   DrawTextF(xstart1, ystart, font_head, "Level");
5623   lines = DrawTextBufferS(xstart2, ystart, level.name, font_text,
5624                           max_chars_per_line, -1, max_lines_per_text, 0, -1,
5625                           TRUE, FALSE, FALSE);
5626   ystart += ystep_para + (lines > 0 ? lines - 1 : 0) * font_height;
5627
5628   select_y1 = SY + ystart;
5629   ystart += graphic_info[IMG_MENU_BUTTON_PREV_SCORE].height;
5630
5631   DrawTextF(xstart1, ystart, font_head, "Rank");
5632   DrawTextF(xstart2, ystart, font_text, pos_text);
5633   ystart += ystep_line;
5634
5635   DrawTextF(xstart1, ystart, font_head, "Player");
5636   DrawTextF(xstart2, ystart, font_text, entry->name);
5637   ystart += ystep_line;
5638
5639   if (level.use_step_counter)
5640   {
5641     DrawTextF(xstart1, ystart, font_head, "Steps");
5642     DrawTextF(xstart2, ystart, font_text, int2str(entry->time, 5));
5643     ystart += ystep_line;
5644   }
5645   else
5646   {
5647     DrawTextF(xstart1, ystart, font_head, "Time");
5648     DrawTextF(xstart2, ystart, font_text, getHallOfFameTimeText(entry_nr));
5649     ystart += ystep_line;
5650   }
5651
5652   if (!level.rate_time_over_score || entry->score > 0)
5653   {
5654     DrawTextF(xstart1, ystart, font_head, "Score");
5655     DrawTextF(xstart2, ystart, font_text, int2str(entry->score, 5));
5656     ystart += ystep_line;
5657   }
5658
5659   ystart += ystep_line;
5660
5661   play_x = SX + xstart2 + tape_date_width + font_width;
5662   play_y = SY + ystart + (font_height - play_height) / 2;
5663
5664   DrawTextF(xstart1, ystart, font_head, "Tape Date");
5665   DrawTextF(xstart2, ystart, font_text, tape_date);
5666   ystart += ystep_line;
5667
5668   DrawTextF(xstart1, ystart, font_head, "Platform");
5669   DrawTextF(xstart2, ystart, font_text, entry->platform);
5670   ystart += ystep_line;
5671
5672   DrawTextF(xstart1, ystart, font_head, "Version");
5673   DrawTextF(xstart2, ystart, font_text, entry->version);
5674   ystart += ystep_line;
5675
5676   DrawTextF(xstart1, ystart, font_head, "Country");
5677   lines = DrawTextBufferS(xstart2, ystart, entry->country_name, font_text,
5678                           max_chars_per_line, -1, max_lines_per_text, 0, -1,
5679                           TRUE, FALSE, FALSE);
5680   ystart += ystep_line;
5681
5682   select_y2 = SY + ystart;
5683
5684   DrawTextSCentered(ybottom, font_foot, "Press any key or button to go back");
5685
5686   AdjustScoreInfoButtons_SelectScore(select_x, select_y1, select_y2);
5687   AdjustScoreInfoButtons_PlayTape(play_x, play_y, play_visible);
5688 }
5689
5690 static void DrawScoreInfo(int entry_nr)
5691 {
5692   scores.last_entry_nr = entry_nr;
5693   score_info_tape_play = FALSE;
5694
5695   UnmapAllGadgets();
5696
5697   FreeScreenGadgets();
5698   CreateScreenGadgets();
5699
5700   FadeOut(REDRAW_FIELD);
5701
5702   // needed if different viewport properties defined after playing score tape
5703   ChangeViewportPropertiesIfNeeded();
5704
5705   // set this after "ChangeViewportPropertiesIfNeeded()" (which may reset it)
5706   SetDrawDeactivationMask(REDRAW_NONE);
5707   SetDrawBackgroundMask(REDRAW_FIELD);
5708
5709   // needed if different background image defined after playing score tape
5710   SetMainBackgroundImage(IMG_BACKGROUND_SCORES);
5711   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_SCOREINFO);
5712
5713   // special compatibility handling for "Snake Bite" graphics set
5714   if (strPrefix(leveldir_current->identifier, "snake_bite"))
5715     ClearRectangle(gfx.background_bitmap, gfx.real_sx, gfx.real_sy + 64,
5716                    gfx.full_sxsize, gfx.full_sysize - 64);
5717
5718   DrawScoreInfo_Content(entry_nr);
5719
5720   // map gadgets for score info screen
5721   MapScreenMenuGadgets(SCREEN_MASK_SCORES_INFO);
5722
5723   FadeIn(REDRAW_FIELD);
5724 }
5725
5726 static void HandleScoreInfo_SelectScore(int step, int direction)
5727 {
5728   int old_entry_nr = scores.last_entry_nr;
5729   int new_entry_nr = old_entry_nr + step * direction;
5730   int num_nodes = numTreeInfoInGroup(score_entry_current);
5731   int num_entries = num_nodes - 1;      // score nodes only, without back link
5732
5733   if (new_entry_nr < 0)
5734     new_entry_nr = 0;
5735   if (new_entry_nr > num_entries - 1)
5736     new_entry_nr = num_entries - 1;
5737
5738   if (new_entry_nr != old_entry_nr)
5739   {
5740     scores.last_entry_nr = new_entry_nr;
5741
5742     DrawScoreInfo_Content(new_entry_nr);
5743   }
5744 }
5745
5746 static void HandleScoreInfo_PlayTape(void)
5747 {
5748   if (!PlayScoreTape(scores.last_entry_nr))
5749   {
5750     DrawScoreInfo_Content(scores.last_entry_nr);
5751
5752     FadeIn(REDRAW_FIELD);
5753   }
5754 }
5755
5756 void HandleScoreInfo(int mx, int my, int dx, int dy, int button)
5757 {
5758   boolean button_action = (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE);
5759   boolean button_is_valid = (mx >= 0 && my >= 0);
5760   boolean button_screen_clicked = (button_action && button_is_valid);
5761
5762   if (server_scores.updated)
5763   {
5764     // reload scores, using updated server score cache file
5765     LoadLocalAndServerScore(scores.last_level_nr, FALSE);
5766
5767     server_scores.updated = FALSE;
5768
5769     DrawHallOfFame_setScoreEntries();
5770
5771     DrawScoreInfo_Content(scores.last_entry_nr);
5772   }
5773
5774   if (button_screen_clicked)
5775   {
5776     PlaySound(SND_MENU_ITEM_SELECTING);
5777
5778     SetGameStatus(GAME_MODE_SCORES);
5779
5780     DrawHallOfFame(scores.last_level_nr);
5781   }
5782   else if (dx)
5783   {
5784     HandleHallOfFame_SelectLevel(1, SIGN(dx) * (ABS(dx) > 1 ? 10 : 1));
5785   }
5786   else if (dy)
5787   {
5788     HandleScoreInfo_SelectScore(1, SIGN(dy) * (ABS(dy) > 1 ? 10 : 1));
5789   }
5790 }
5791
5792
5793 // ============================================================================
5794 // setup screen functions
5795 // ============================================================================
5796
5797 static struct TokenInfo *setup_info;
5798 static int num_setup_info;      // number of setup entries shown on screen
5799 static int max_setup_info;      // total number of setup entries in list
5800
5801 static char *window_size_text;
5802 static char *scaling_type_text;
5803 static char *rendering_mode_text;
5804 static char *vsync_mode_text;
5805 static char *scroll_delay_text;
5806 static char *snapshot_mode_text;
5807 static char *game_speed_text;
5808 static char *scores_type_text;
5809 static char *network_server_text;
5810 static char *graphics_set_name;
5811 static char *sounds_set_name;
5812 static char *music_set_name;
5813 static char *volume_simple_text;
5814 static char *volume_loops_text;
5815 static char *volume_music_text;
5816 static char *touch_controls_text;
5817 static char *move_distance_text;
5818 static char *drop_distance_text;
5819 static char *transparency_text;
5820 static char *grid_size_text[2][2];
5821
5822 static void execSetupMain(void)
5823 {
5824   setup_mode = SETUP_MODE_MAIN;
5825
5826   DrawSetupScreen();
5827 }
5828
5829 static void execSetupGame_setScoresType(void)
5830 {
5831   if (scores_types == NULL)
5832   {
5833     int i;
5834
5835     for (i = 0; scores_types_list[i].value != NULL; i++)
5836     {
5837       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
5838       char identifier[32], name[32];
5839       char *value = scores_types_list[i].value;
5840       char *text = scores_types_list[i].text;
5841
5842       ti->node_top = &scores_types;
5843       ti->sort_priority = i;
5844
5845       sprintf(identifier, "%s", value);
5846       sprintf(name, "%s", text);
5847
5848       setString(&ti->identifier, identifier);
5849       setString(&ti->name, name);
5850       setString(&ti->name_sorting, name);
5851       setString(&ti->infotext, STR_SETUP_CHOOSE_SCORES_TYPE);
5852
5853       pushTreeInfo(&scores_types, ti);
5854     }
5855
5856     // sort scores type values to start with lowest scores type value
5857     sortTreeInfo(&scores_types);
5858
5859     // set current scores type value to configured scores type value
5860     scores_type_current =
5861       getTreeInfoFromIdentifier(scores_types, setup.scores_in_highscore_list);
5862
5863     // if that fails, set current scores type to reliable default value
5864     if (scores_type_current == NULL)
5865       scores_type_current =
5866         getTreeInfoFromIdentifier(scores_types, STR_SCORES_TYPE_DEFAULT);
5867
5868     // if that also fails, set current scores type to first available value
5869     if (scores_type_current == NULL)
5870       scores_type_current = scores_types;
5871   }
5872
5873   setup.scores_in_highscore_list = scores_type_current->identifier;
5874
5875   // needed for displaying scores type text instead of identifier
5876   scores_type_text = scores_type_current->name;
5877 }
5878
5879 static void execSetupGame_setGameSpeeds(boolean update_value)
5880 {
5881   if (setup.game_speed_extended)
5882   {
5883     game_speeds_list = game_speeds_list_extended;
5884     game_speeds      = game_speeds_extended;
5885   }
5886   else
5887   {
5888     game_speeds_list = game_speeds_list_normal;
5889     game_speeds      = game_speeds_normal;
5890   }
5891
5892   if (game_speeds == NULL)
5893   {
5894     int i;
5895
5896     for (i = 0; game_speeds_list[i].value != -1; i++)
5897     {
5898       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
5899       char identifier[32], name[32];
5900       int value = game_speeds_list[i].value;
5901       char *text = game_speeds_list[i].text;
5902
5903       ti->node_top = &game_speeds;
5904       ti->sort_priority = 10000 - value;
5905
5906       sprintf(identifier, "%d", value);
5907       sprintf(name, "%s", text);
5908
5909       setString(&ti->identifier, identifier);
5910       setString(&ti->name, name);
5911       setString(&ti->name_sorting, name);
5912       setString(&ti->infotext, STR_SETUP_CHOOSE_GAME_SPEED);
5913
5914       pushTreeInfo(&game_speeds, ti);
5915     }
5916
5917     // sort game speed values to start with slowest game speed
5918     sortTreeInfo(&game_speeds);
5919
5920     update_value = TRUE;
5921   }
5922
5923   if (update_value)
5924   {
5925     // set current game speed to configured game speed value
5926     game_speed_current =
5927       getTreeInfoFromIdentifier(game_speeds, i_to_a(setup.game_frame_delay));
5928
5929     // if that fails, set current game speed to reliable default value
5930     if (game_speed_current == NULL)
5931       game_speed_current =
5932         getTreeInfoFromIdentifier(game_speeds, i_to_a(GAME_FRAME_DELAY));
5933
5934     // if that also fails, set current game speed to first available speed
5935     if (game_speed_current == NULL)
5936       game_speed_current = game_speeds;
5937
5938     if (setup.game_speed_extended)
5939       game_speeds_extended = game_speeds;
5940     else
5941       game_speeds_normal = game_speeds;
5942   }
5943
5944   setup.game_frame_delay = atoi(game_speed_current->identifier);
5945
5946   // needed for displaying game speed text instead of identifier
5947   game_speed_text = game_speed_current->name;
5948 }
5949
5950 static void execSetupGame_setScrollDelays(void)
5951 {
5952   if (scroll_delays == NULL)
5953   {
5954     int i;
5955
5956     for (i = 0; scroll_delays_list[i].value != -1; i++)
5957     {
5958       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
5959       char identifier[32], name[32];
5960       int value = scroll_delays_list[i].value;
5961       char *text = scroll_delays_list[i].text;
5962
5963       ti->node_top = &scroll_delays;
5964       ti->sort_priority = value;
5965
5966       sprintf(identifier, "%d", value);
5967       sprintf(name, "%s", text);
5968
5969       setString(&ti->identifier, identifier);
5970       setString(&ti->name, name);
5971       setString(&ti->name_sorting, name);
5972       setString(&ti->infotext, STR_SETUP_CHOOSE_SCROLL_DELAY);
5973
5974       pushTreeInfo(&scroll_delays, ti);
5975     }
5976
5977     // sort scroll delay values to start with lowest scroll delay value
5978     sortTreeInfo(&scroll_delays);
5979
5980     // set current scroll delay value to configured scroll delay value
5981     scroll_delay_current =
5982       getTreeInfoFromIdentifier(scroll_delays, i_to_a(setup.scroll_delay_value));
5983
5984     // if that fails, set current scroll delay to reliable default value
5985     if (scroll_delay_current == NULL)
5986       scroll_delay_current =
5987         getTreeInfoFromIdentifier(scroll_delays, i_to_a(STD_SCROLL_DELAY));
5988
5989     // if that also fails, set current scroll delay to first available value
5990     if (scroll_delay_current == NULL)
5991       scroll_delay_current = scroll_delays;
5992   }
5993
5994   setup.scroll_delay_value = atoi(scroll_delay_current->identifier);
5995
5996   // needed for displaying scroll delay text instead of identifier
5997   scroll_delay_text = scroll_delay_current->name;
5998 }
5999
6000 static void execSetupGame_setSnapshotModes(void)
6001 {
6002   if (snapshot_modes == NULL)
6003   {
6004     int i;
6005
6006     for (i = 0; snapshot_modes_list[i].value != NULL; i++)
6007     {
6008       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6009       char identifier[32], name[32];
6010       char *value = snapshot_modes_list[i].value;
6011       char *text = snapshot_modes_list[i].text;
6012
6013       ti->node_top = &snapshot_modes;
6014       ti->sort_priority = i;
6015
6016       sprintf(identifier, "%s", value);
6017       sprintf(name, "%s", text);
6018
6019       setString(&ti->identifier, identifier);
6020       setString(&ti->name, name);
6021       setString(&ti->name_sorting, name);
6022       setString(&ti->infotext, STR_SETUP_CHOOSE_SNAPSHOT_MODE);
6023
6024       pushTreeInfo(&snapshot_modes, ti);
6025     }
6026
6027     // sort snapshot mode values to start with lowest snapshot mode value
6028     sortTreeInfo(&snapshot_modes);
6029
6030     // set current snapshot mode value to configured snapshot mode value
6031     snapshot_mode_current =
6032       getTreeInfoFromIdentifier(snapshot_modes, setup.engine_snapshot_mode);
6033
6034     // if that fails, set current snapshot mode to reliable default value
6035     if (snapshot_mode_current == NULL)
6036       snapshot_mode_current =
6037         getTreeInfoFromIdentifier(snapshot_modes, STR_SNAPSHOT_MODE_DEFAULT);
6038
6039     // if that also fails, set current snapshot mode to first available value
6040     if (snapshot_mode_current == NULL)
6041       snapshot_mode_current = snapshot_modes;
6042   }
6043
6044   setup.engine_snapshot_mode = snapshot_mode_current->identifier;
6045
6046   // needed for displaying snapshot mode text instead of identifier
6047   snapshot_mode_text = snapshot_mode_current->name;
6048 }
6049
6050 static void execSetupGame_setNetworkServerText(void)
6051 {
6052   if (strEqual(setup.network_server_hostname, STR_NETWORK_AUTO_DETECT))
6053   {
6054     strcpy(network_server_hostname, STR_NETWORK_AUTO_DETECT_SETUP);
6055   }
6056   else
6057   {
6058     strncpy(network_server_hostname, setup.network_server_hostname,
6059             MAX_SETUP_TEXT_INPUT_LEN);
6060     network_server_hostname[MAX_SETUP_TEXT_INPUT_LEN] = '\0';
6061   }
6062
6063   // needed for displaying network server text instead of identifier
6064   network_server_text = network_server_hostname;
6065 }
6066
6067 static void execSetupGame(void)
6068 {
6069   boolean check_vsync_mode = (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED);
6070
6071   execSetupGame_setGameSpeeds(FALSE);
6072   execSetupGame_setScoresType();
6073   execSetupGame_setScrollDelays();
6074   execSetupGame_setSnapshotModes();
6075
6076   execSetupGame_setNetworkServerText();
6077
6078   if (!setup.provide_uploading_tapes)
6079     setHideSetupEntry(execOfferUploadTapes);
6080
6081   setup_mode = SETUP_MODE_GAME;
6082
6083   DrawSetupScreen();
6084
6085   // check if vsync needs to be disabled for this game speed to work
6086   if (check_vsync_mode)
6087     DisableVsyncIfNeeded();
6088 }
6089
6090 static void execSetupChooseScoresType(void)
6091 {
6092   setup_mode = SETUP_MODE_CHOOSE_SCORES_TYPE;
6093
6094   DrawSetupScreen();
6095 }
6096
6097 static void execSetupChooseGameSpeed(void)
6098 {
6099   setup_mode = SETUP_MODE_CHOOSE_GAME_SPEED;
6100
6101   DrawSetupScreen();
6102 }
6103
6104 static void execSetupChooseScrollDelay(void)
6105 {
6106   setup_mode = SETUP_MODE_CHOOSE_SCROLL_DELAY;
6107
6108   DrawSetupScreen();
6109 }
6110
6111 static void execSetupChooseSnapshotMode(void)
6112 {
6113   setup_mode = SETUP_MODE_CHOOSE_SNAPSHOT_MODE;
6114
6115   DrawSetupScreen();
6116 }
6117
6118 static void execSetupEngines(void)
6119 {
6120   setup_mode = SETUP_MODE_ENGINES;
6121
6122   DrawSetupScreen();
6123 }
6124
6125 static void execSetupEditor(void)
6126 {
6127   setup_mode = SETUP_MODE_EDITOR;
6128
6129   DrawSetupScreen();
6130 }
6131
6132 static void execSetupGraphics_setWindowSizes(boolean update_list)
6133 {
6134   if (window_sizes != NULL && update_list)
6135   {
6136     freeTreeInfo(window_sizes);
6137
6138     window_sizes = NULL;
6139   }
6140
6141   if (window_sizes == NULL)
6142   {
6143     boolean current_window_size_found = FALSE;
6144     int i;
6145
6146     for (i = 0; window_sizes_list[i].value != -1; i++)
6147     {
6148       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6149       char identifier[32], name[32];
6150       int value = window_sizes_list[i].value;
6151       char *text = window_sizes_list[i].text;
6152
6153       ti->node_top = &window_sizes;
6154       ti->sort_priority = value;
6155
6156       sprintf(identifier, "%d", value);
6157       sprintf(name, "%s", text);
6158
6159       setString(&ti->identifier, identifier);
6160       setString(&ti->name, name);
6161       setString(&ti->name_sorting, name);
6162       setString(&ti->infotext, STR_SETUP_CHOOSE_WINDOW_SIZE);
6163
6164       pushTreeInfo(&window_sizes, ti);
6165
6166       if (value == setup.window_scaling_percent)
6167         current_window_size_found = TRUE;
6168     }
6169
6170     if (!current_window_size_found)
6171     {
6172       // add entry for non-preset window scaling value
6173
6174       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6175       char identifier[32], name[32];
6176       int value = setup.window_scaling_percent;
6177
6178       ti->node_top = &window_sizes;
6179       ti->sort_priority = value;
6180
6181       sprintf(identifier, "%d", value);
6182       sprintf(name, "%d %% (Current)", value);
6183
6184       setString(&ti->identifier, identifier);
6185       setString(&ti->name, name);
6186       setString(&ti->name_sorting, name);
6187       setString(&ti->infotext, STR_SETUP_CHOOSE_WINDOW_SIZE);
6188
6189       pushTreeInfo(&window_sizes, ti);
6190     }
6191
6192     // sort window size values to start with lowest window size value
6193     sortTreeInfo(&window_sizes);
6194
6195     // set current window size value to configured window size value
6196     window_size_current =
6197       getTreeInfoFromIdentifier(window_sizes,
6198                                 i_to_a(setup.window_scaling_percent));
6199
6200     // if that fails, set current window size to reliable default value
6201     if (window_size_current == NULL)
6202       window_size_current =
6203         getTreeInfoFromIdentifier(window_sizes,
6204                                   i_to_a(STD_WINDOW_SCALING_PERCENT));
6205
6206     // if that also fails, set current window size to first available value
6207     if (window_size_current == NULL)
6208       window_size_current = window_sizes;
6209   }
6210
6211   setup.window_scaling_percent = atoi(window_size_current->identifier);
6212
6213   // needed for displaying window size text instead of identifier
6214   window_size_text = window_size_current->name;
6215 }
6216
6217 static void execSetupGraphics_setScalingTypes(void)
6218 {
6219   if (scaling_types == NULL)
6220   {
6221     int i;
6222
6223     for (i = 0; scaling_types_list[i].value != NULL; i++)
6224     {
6225       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6226       char identifier[32], name[32];
6227       char *value = scaling_types_list[i].value;
6228       char *text = scaling_types_list[i].text;
6229
6230       ti->node_top = &scaling_types;
6231       ti->sort_priority = i;
6232
6233       sprintf(identifier, "%s", value);
6234       sprintf(name, "%s", text);
6235
6236       setString(&ti->identifier, identifier);
6237       setString(&ti->name, name);
6238       setString(&ti->name_sorting, name);
6239       setString(&ti->infotext, STR_SETUP_CHOOSE_SCALING_TYPE);
6240
6241       pushTreeInfo(&scaling_types, ti);
6242     }
6243
6244     // sort scaling type values to start with lowest scaling type value
6245     sortTreeInfo(&scaling_types);
6246
6247     // set current scaling type value to configured scaling type value
6248     scaling_type_current =
6249       getTreeInfoFromIdentifier(scaling_types, setup.window_scaling_quality);
6250
6251     // if that fails, set current scaling type to reliable default value
6252     if (scaling_type_current == NULL)
6253       scaling_type_current =
6254         getTreeInfoFromIdentifier(scaling_types, SCALING_QUALITY_DEFAULT);
6255
6256     // if that also fails, set current scaling type to first available value
6257     if (scaling_type_current == NULL)
6258       scaling_type_current = scaling_types;
6259   }
6260
6261   setup.window_scaling_quality = scaling_type_current->identifier;
6262
6263   // needed for displaying scaling type text instead of identifier
6264   scaling_type_text = scaling_type_current->name;
6265 }
6266
6267 static void execSetupGraphics_setRenderingModes(void)
6268 {
6269   if (rendering_modes == NULL)
6270   {
6271     int i;
6272
6273     for (i = 0; rendering_modes_list[i].value != NULL; i++)
6274     {
6275       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6276       char identifier[32], name[32];
6277       char *value = rendering_modes_list[i].value;
6278       char *text = rendering_modes_list[i].text;
6279
6280       ti->node_top = &rendering_modes;
6281       ti->sort_priority = i;
6282
6283       sprintf(identifier, "%s", value);
6284       sprintf(name, "%s", text);
6285
6286       setString(&ti->identifier, identifier);
6287       setString(&ti->name, name);
6288       setString(&ti->name_sorting, name);
6289       setString(&ti->infotext, STR_SETUP_CHOOSE_RENDERING);
6290
6291       pushTreeInfo(&rendering_modes, ti);
6292     }
6293
6294     // sort rendering mode values to start with lowest rendering mode value
6295     sortTreeInfo(&rendering_modes);
6296
6297     // set current rendering mode value to configured rendering mode value
6298     rendering_mode_current =
6299       getTreeInfoFromIdentifier(rendering_modes, setup.screen_rendering_mode);
6300
6301     // if that fails, set current rendering mode to reliable default value
6302     if (rendering_mode_current == NULL)
6303       rendering_mode_current =
6304         getTreeInfoFromIdentifier(rendering_modes,
6305                                   STR_SPECIAL_RENDERING_DEFAULT);
6306
6307     // if that also fails, set current rendering mode to first available one
6308     if (rendering_mode_current == NULL)
6309       rendering_mode_current = rendering_modes;
6310   }
6311
6312   setup.screen_rendering_mode = rendering_mode_current->identifier;
6313
6314   // needed for displaying rendering mode text instead of identifier
6315   rendering_mode_text = rendering_mode_current->name;
6316 }
6317
6318 static void execSetupGraphics_setVsyncModes(boolean update_value)
6319 {
6320   if (vsync_modes == NULL)
6321   {
6322     int i;
6323
6324     for (i = 0; vsync_modes_list[i].value != NULL; i++)
6325     {
6326       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6327       char identifier[32], name[32];
6328       char *value = vsync_modes_list[i].value;
6329       char *text = vsync_modes_list[i].text;
6330
6331       ti->node_top = &vsync_modes;
6332       ti->sort_priority = i;
6333
6334       sprintf(identifier, "%s", value);
6335       sprintf(name, "%s", text);
6336
6337       setString(&ti->identifier, identifier);
6338       setString(&ti->name, name);
6339       setString(&ti->name_sorting, name);
6340       setString(&ti->infotext, STR_SETUP_CHOOSE_VSYNC);
6341
6342       pushTreeInfo(&vsync_modes, ti);
6343     }
6344
6345     // sort vsync mode values to start with lowest vsync mode value
6346     sortTreeInfo(&vsync_modes);
6347
6348     update_value = TRUE;
6349   }
6350
6351   if (update_value)
6352   {
6353     // set current vsync mode value to configured vsync mode value
6354     vsync_mode_current =
6355       getTreeInfoFromIdentifier(vsync_modes, setup.vsync_mode);
6356
6357     // if that fails, set current vsync mode to reliable default value
6358     if (vsync_mode_current == NULL)
6359       vsync_mode_current =
6360         getTreeInfoFromIdentifier(vsync_modes, STR_VSYNC_MODE_DEFAULT);
6361
6362     // if that also fails, set current vsync mode to first available one
6363     if (vsync_mode_current == NULL)
6364       vsync_mode_current = vsync_modes;
6365   }
6366
6367   setup.vsync_mode = vsync_mode_current->identifier;
6368
6369   // needed for displaying vsync mode text instead of identifier
6370   vsync_mode_text = vsync_mode_current->name;
6371 }
6372
6373 static void execSetupGraphics(void)
6374 {
6375   boolean check_game_speed = (setup_mode == SETUP_MODE_CHOOSE_VSYNC);
6376
6377   // update "setup.window_scaling_percent" from list selection
6378   // (in this case, window scaling was changed on setup screen)
6379   if (setup_mode == SETUP_MODE_CHOOSE_WINDOW_SIZE)
6380     execSetupGraphics_setWindowSizes(FALSE);
6381
6382   // update "setup.vsync_mode" from list selection
6383   // (in this case, vsync mode was changed on setup screen)
6384   if (setup_mode == SETUP_MODE_CHOOSE_VSYNC)
6385     execSetupGraphics_setVsyncModes(FALSE);
6386
6387   // update list selection from "setup.window_scaling_percent"
6388   // (window scaling may have changed by resizing the window)
6389   execSetupGraphics_setWindowSizes(TRUE);
6390
6391   // update list selection from "setup.vsync_mode"
6392   // (vsync_mode may have changed by re-creating the renderer)
6393   execSetupGraphics_setVsyncModes(TRUE);
6394
6395   execSetupGraphics_setScalingTypes();
6396   execSetupGraphics_setRenderingModes();
6397
6398   setup_mode = SETUP_MODE_GRAPHICS;
6399
6400   DrawSetupScreen();
6401
6402   // check if game speed is high enough for 60 Hz vsync to work
6403   if (check_game_speed)
6404     ModifyGameSpeedIfNeeded();
6405
6406   // window scaling may have changed at this point
6407   ChangeWindowScalingIfNeeded();
6408
6409   // window scaling quality may have changed at this point
6410   if (!strEqual(setup.window_scaling_quality, video.window_scaling_quality))
6411     SDLSetWindowScalingQuality(setup.window_scaling_quality);
6412
6413   // screen rendering mode may have changed at this point
6414   SDLSetScreenRenderingMode(setup.screen_rendering_mode);
6415
6416   int setup_vsync_mode = VSYNC_MODE_STR_TO_INT(setup.vsync_mode);
6417   int video_vsync_mode = video.vsync_mode;
6418
6419   // screen vsync mode may have changed at this point
6420   ChangeVsyncModeIfNeeded();
6421
6422   // check if setting vsync mode to selected value failed
6423   if (setup_vsync_mode != video_vsync_mode &&
6424       setup_vsync_mode != video.vsync_mode)
6425   {
6426     // changing vsync mode to selected value failed -- reset displayed value
6427     execSetupGraphics_setVsyncModes(TRUE);
6428
6429     Request("Setting VSync failed!", REQ_CONFIRM);
6430
6431     DrawSetupScreen();
6432   }
6433 }
6434
6435 static void execSetupChooseWindowSize(void)
6436 {
6437   setup_mode = SETUP_MODE_CHOOSE_WINDOW_SIZE;
6438
6439   DrawSetupScreen();
6440 }
6441
6442 static void execSetupChooseScalingType(void)
6443 {
6444   setup_mode = SETUP_MODE_CHOOSE_SCALING_TYPE;
6445
6446   DrawSetupScreen();
6447 }
6448
6449 static void execSetupChooseRenderingMode(void)
6450 {
6451   setup_mode = SETUP_MODE_CHOOSE_RENDERING;
6452
6453   DrawSetupScreen();
6454 }
6455
6456 static void execSetupChooseVsyncMode(void)
6457 {
6458   setup_mode = SETUP_MODE_CHOOSE_VSYNC;
6459
6460   DrawSetupScreen();
6461 }
6462
6463 static void execSetupChooseVolumeSimple(void)
6464 {
6465   setup_mode = SETUP_MODE_CHOOSE_VOLUME_SIMPLE;
6466
6467   DrawSetupScreen();
6468 }
6469
6470 static void execSetupChooseVolumeLoops(void)
6471 {
6472   setup_mode = SETUP_MODE_CHOOSE_VOLUME_LOOPS;
6473
6474   DrawSetupScreen();
6475 }
6476
6477 static void execSetupChooseVolumeMusic(void)
6478 {
6479   setup_mode = SETUP_MODE_CHOOSE_VOLUME_MUSIC;
6480
6481   DrawSetupScreen();
6482 }
6483
6484 static void execSetupSound(void)
6485 {
6486   if (volumes_simple == NULL)
6487   {
6488     boolean current_volume_simple_found = FALSE;
6489     int i;
6490
6491     for (i = 0; volumes_list[i].value != -1; i++)
6492     {
6493       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6494       char identifier[32], name[32];
6495       int value = volumes_list[i].value;
6496       char *text = volumes_list[i].text;
6497
6498       ti->node_top = &volumes_simple;
6499       ti->sort_priority = value;
6500
6501       sprintf(identifier, "%d", value);
6502       sprintf(name, "%s", text);
6503
6504       setString(&ti->identifier, identifier);
6505       setString(&ti->name, name);
6506       setString(&ti->name_sorting, name);
6507       setString(&ti->infotext, STR_SETUP_CHOOSE_VOLUME_SIMPLE);
6508
6509       pushTreeInfo(&volumes_simple, ti);
6510
6511       if (value == setup.volume_simple)
6512         current_volume_simple_found = TRUE;
6513     }
6514
6515     if (!current_volume_simple_found)
6516     {
6517       // add entry for non-preset volume value
6518
6519       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6520       char identifier[32], name[32];
6521       int value = setup.volume_simple;
6522
6523       ti->node_top = &volumes_simple;
6524       ti->sort_priority = value;
6525
6526       sprintf(identifier, "%d", value);
6527       sprintf(name, "%d %% (Current)", value);
6528
6529       setString(&ti->identifier, identifier);
6530       setString(&ti->name, name);
6531       setString(&ti->name_sorting, name);
6532       setString(&ti->infotext, STR_SETUP_CHOOSE_VOLUME_SIMPLE);
6533
6534       pushTreeInfo(&volumes_simple, ti);
6535     }
6536
6537     // sort volume values to start with lowest volume value
6538     sortTreeInfo(&volumes_simple);
6539
6540     // set current volume value to configured volume value
6541     volume_simple_current =
6542       getTreeInfoFromIdentifier(volumes_simple, i_to_a(setup.volume_simple));
6543
6544     // if that fails, set current volume to reliable default value
6545     if (volume_simple_current == NULL)
6546       volume_simple_current =
6547         getTreeInfoFromIdentifier(volumes_simple, i_to_a(100));
6548
6549     // if that also fails, set current volume to first available value
6550     if (volume_simple_current == NULL)
6551       volume_simple_current = volumes_simple;
6552   }
6553
6554   if (volumes_loops == NULL)
6555   {
6556     boolean current_volume_loops_found = FALSE;
6557     int i;
6558
6559     for (i = 0; volumes_list[i].value != -1; i++)
6560     {
6561       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6562       char identifier[32], name[32];
6563       int value = volumes_list[i].value;
6564       char *text = volumes_list[i].text;
6565
6566       ti->node_top = &volumes_loops;
6567       ti->sort_priority = value;
6568
6569       sprintf(identifier, "%d", value);
6570       sprintf(name, "%s", text);
6571
6572       setString(&ti->identifier, identifier);
6573       setString(&ti->name, name);
6574       setString(&ti->name_sorting, name);
6575       setString(&ti->infotext, STR_SETUP_CHOOSE_VOLUME_LOOPS);
6576
6577       pushTreeInfo(&volumes_loops, ti);
6578
6579       if (value == setup.volume_loops)
6580         current_volume_loops_found = TRUE;
6581     }
6582
6583     if (!current_volume_loops_found)
6584     {
6585       // add entry for non-preset volume value
6586
6587       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6588       char identifier[32], name[32];
6589       int value = setup.volume_loops;
6590
6591       ti->node_top = &volumes_loops;
6592       ti->sort_priority = value;
6593
6594       sprintf(identifier, "%d", value);
6595       sprintf(name, "%d %% (Current)", value);
6596
6597       setString(&ti->identifier, identifier);
6598       setString(&ti->name, name);
6599       setString(&ti->name_sorting, name);
6600       setString(&ti->infotext, STR_SETUP_CHOOSE_VOLUME_LOOPS);
6601
6602       pushTreeInfo(&volumes_loops, ti);
6603     }
6604
6605     // sort volume values to start with lowest volume value
6606     sortTreeInfo(&volumes_loops);
6607
6608     // set current volume value to configured volume value
6609     volume_loops_current =
6610       getTreeInfoFromIdentifier(volumes_loops, i_to_a(setup.volume_loops));
6611
6612     // if that fails, set current volume to reliable default value
6613     if (volume_loops_current == NULL)
6614       volume_loops_current =
6615         getTreeInfoFromIdentifier(volumes_loops, i_to_a(100));
6616
6617     // if that also fails, set current volume to first available value
6618     if (volume_loops_current == NULL)
6619       volume_loops_current = volumes_loops;
6620   }
6621
6622   if (volumes_music == NULL)
6623   {
6624     boolean current_volume_music_found = FALSE;
6625     int i;
6626
6627     for (i = 0; volumes_list[i].value != -1; i++)
6628     {
6629       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6630       char identifier[32], name[32];
6631       int value = volumes_list[i].value;
6632       char *text = volumes_list[i].text;
6633
6634       ti->node_top = &volumes_music;
6635       ti->sort_priority = value;
6636
6637       sprintf(identifier, "%d", value);
6638       sprintf(name, "%s", text);
6639
6640       setString(&ti->identifier, identifier);
6641       setString(&ti->name, name);
6642       setString(&ti->name_sorting, name);
6643       setString(&ti->infotext, STR_SETUP_CHOOSE_VOLUME_MUSIC);
6644
6645       pushTreeInfo(&volumes_music, ti);
6646
6647       if (value == setup.volume_music)
6648         current_volume_music_found = TRUE;
6649     }
6650
6651     if (!current_volume_music_found)
6652     {
6653       // add entry for non-preset volume value
6654
6655       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6656       char identifier[32], name[32];
6657       int value = setup.volume_music;
6658
6659       ti->node_top = &volumes_music;
6660       ti->sort_priority = value;
6661
6662       sprintf(identifier, "%d", value);
6663       sprintf(name, "%d %% (Current)", value);
6664
6665       setString(&ti->identifier, identifier);
6666       setString(&ti->name, name);
6667       setString(&ti->name_sorting, name);
6668       setString(&ti->infotext, STR_SETUP_CHOOSE_VOLUME_MUSIC);
6669
6670       pushTreeInfo(&volumes_music, ti);
6671     }
6672
6673     // sort volume values to start with lowest volume value
6674     sortTreeInfo(&volumes_music);
6675
6676     // set current volume value to configured volume value
6677     volume_music_current =
6678       getTreeInfoFromIdentifier(volumes_music, i_to_a(setup.volume_music));
6679
6680     // if that fails, set current volume to reliable default value
6681     if (volume_music_current == NULL)
6682       volume_music_current =
6683         getTreeInfoFromIdentifier(volumes_music, i_to_a(100));
6684
6685     // if that also fails, set current volume to first available value
6686     if (volume_music_current == NULL)
6687       volume_music_current = volumes_music;
6688   }
6689
6690   setup.volume_simple = atoi(volume_simple_current->identifier);
6691   setup.volume_loops  = atoi(volume_loops_current->identifier);
6692   setup.volume_music  = atoi(volume_music_current->identifier);
6693
6694   // needed for displaying volume text instead of identifier
6695   volume_simple_text = volume_simple_current->name;
6696   volume_loops_text = volume_loops_current->name;
6697   volume_music_text = volume_music_current->name;
6698
6699   setup_mode = SETUP_MODE_SOUND;
6700
6701   DrawSetupScreen();
6702 }
6703
6704 static void execSetupChooseTouchControls(void)
6705 {
6706   setup_mode = SETUP_MODE_CHOOSE_TOUCH_CONTROL;
6707
6708   DrawSetupScreen();
6709 }
6710
6711 static void execSetupChooseMoveDistance(void)
6712 {
6713   setup_mode = SETUP_MODE_CHOOSE_MOVE_DISTANCE;
6714
6715   DrawSetupScreen();
6716 }
6717
6718 static void execSetupChooseDropDistance(void)
6719 {
6720   setup_mode = SETUP_MODE_CHOOSE_DROP_DISTANCE;
6721
6722   DrawSetupScreen();
6723 }
6724
6725 static void execSetupChooseTransparency(void)
6726 {
6727   setup_mode = SETUP_MODE_CHOOSE_TRANSPARENCY;
6728
6729   DrawSetupScreen();
6730 }
6731
6732 static void execSetupChooseGridXSize_0(void)
6733 {
6734   setup_mode = SETUP_MODE_CHOOSE_GRID_XSIZE_0;
6735
6736   DrawSetupScreen();
6737 }
6738
6739 static void execSetupChooseGridYSize_0(void)
6740 {
6741   setup_mode = SETUP_MODE_CHOOSE_GRID_YSIZE_0;
6742
6743   DrawSetupScreen();
6744 }
6745
6746 static void execSetupChooseGridXSize_1(void)
6747 {
6748   setup_mode = SETUP_MODE_CHOOSE_GRID_XSIZE_1;
6749
6750   DrawSetupScreen();
6751 }
6752
6753 static void execSetupChooseGridYSize_1(void)
6754 {
6755   setup_mode = SETUP_MODE_CHOOSE_GRID_YSIZE_1;
6756
6757   DrawSetupScreen();
6758 }
6759
6760 static void execSetupConfigureVirtualButtons(void)
6761 {
6762   setup_mode = SETUP_MODE_CONFIG_VIRT_BUTTONS;
6763
6764   ConfigureVirtualButtons();
6765
6766   setup_mode = SETUP_MODE_TOUCH;
6767
6768   DrawSetupScreen();
6769 }
6770
6771 static void execSetupTouch(void)
6772 {
6773   int i, j, k;
6774
6775   if (touch_controls == NULL)
6776   {
6777     for (i = 0; touch_controls_list[i].value != NULL; i++)
6778     {
6779       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6780       char identifier[32], name[32];
6781       char *value = touch_controls_list[i].value;
6782       char *text = touch_controls_list[i].text;
6783
6784       ti->node_top = &touch_controls;
6785       ti->sort_priority = i;
6786
6787       sprintf(identifier, "%s", value);
6788       sprintf(name, "%s", text);
6789
6790       setString(&ti->identifier, identifier);
6791       setString(&ti->name, name);
6792       setString(&ti->name_sorting, name);
6793       setString(&ti->infotext, STR_SETUP_CHOOSE_TOUCH_CONTROL);
6794
6795       pushTreeInfo(&touch_controls, ti);
6796     }
6797
6798     // sort touch control values to start with lowest touch control value
6799     sortTreeInfo(&touch_controls);
6800
6801     // set current touch control value to configured touch control value
6802     touch_control_current =
6803       getTreeInfoFromIdentifier(touch_controls, setup.touch.control_type);
6804
6805     // if that fails, set current touch control to reliable default value
6806     if (touch_control_current == NULL)
6807       touch_control_current =
6808         getTreeInfoFromIdentifier(touch_controls, TOUCH_CONTROL_DEFAULT);
6809
6810     // if that also fails, set current touch control to first available value
6811     if (touch_control_current == NULL)
6812       touch_control_current = touch_controls;
6813   }
6814
6815   if (move_distances == NULL)
6816   {
6817     for (i = 0; distances_list[i].value != -1; i++)
6818     {
6819       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6820       char identifier[32], name[32];
6821       int value = distances_list[i].value;
6822       char *text = distances_list[i].text;
6823
6824       ti->node_top = &move_distances;
6825       ti->sort_priority = value;
6826
6827       sprintf(identifier, "%d", value);
6828       sprintf(name, "%s", text);
6829
6830       setString(&ti->identifier, identifier);
6831       setString(&ti->name, name);
6832       setString(&ti->name_sorting, name);
6833       setString(&ti->infotext, STR_SETUP_CHOOSE_MOVE_DISTANCE);
6834
6835       pushTreeInfo(&move_distances, ti);
6836     }
6837
6838     // sort distance values to start with lowest distance value
6839     sortTreeInfo(&move_distances);
6840
6841     // set current distance value to configured distance value
6842     move_distance_current =
6843       getTreeInfoFromIdentifier(move_distances,
6844                                 i_to_a(setup.touch.move_distance));
6845
6846     // if that fails, set current distance to reliable default value
6847     if (move_distance_current == NULL)
6848       move_distance_current =
6849         getTreeInfoFromIdentifier(move_distances,
6850                                   i_to_a(TOUCH_MOVE_DISTANCE_DEFAULT));
6851
6852     // if that also fails, set current distance to first available value
6853     if (move_distance_current == NULL)
6854       move_distance_current = move_distances;
6855   }
6856
6857   if (drop_distances == NULL)
6858   {
6859     for (i = 0; distances_list[i].value != -1; i++)
6860     {
6861       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6862       char identifier[32], name[32];
6863       int value = distances_list[i].value;
6864       char *text = distances_list[i].text;
6865
6866       ti->node_top = &drop_distances;
6867       ti->sort_priority = value;
6868
6869       sprintf(identifier, "%d", value);
6870       sprintf(name, "%s", text);
6871
6872       setString(&ti->identifier, identifier);
6873       setString(&ti->name, name);
6874       setString(&ti->name_sorting, name);
6875       setString(&ti->infotext, STR_SETUP_CHOOSE_DROP_DISTANCE);
6876
6877       pushTreeInfo(&drop_distances, ti);
6878     }
6879
6880     // sort distance values to start with lowest distance value
6881     sortTreeInfo(&drop_distances);
6882
6883     // set current distance value to configured distance value
6884     drop_distance_current =
6885       getTreeInfoFromIdentifier(drop_distances,
6886                                 i_to_a(setup.touch.drop_distance));
6887
6888     // if that fails, set current distance to reliable default value
6889     if (drop_distance_current == NULL)
6890       drop_distance_current =
6891         getTreeInfoFromIdentifier(drop_distances,
6892                                   i_to_a(TOUCH_DROP_DISTANCE_DEFAULT));
6893
6894     // if that also fails, set current distance to first available value
6895     if (drop_distance_current == NULL)
6896       drop_distance_current = drop_distances;
6897   }
6898
6899   if (transparencies == NULL)
6900   {
6901     for (i = 0; transparencies_list[i].value != -1; i++)
6902     {
6903       TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6904       char identifier[32], name[32];
6905       int value = transparencies_list[i].value;
6906       char *text = transparencies_list[i].text;
6907
6908       ti->node_top = &transparencies;
6909       ti->sort_priority = value;
6910
6911       sprintf(identifier, "%d", value);
6912       sprintf(name, "%s", text);
6913
6914       setString(&ti->identifier, identifier);
6915       setString(&ti->name, name);
6916       setString(&ti->name_sorting, name);
6917       setString(&ti->infotext, STR_SETUP_CHOOSE_TRANSPARENCY);
6918
6919       pushTreeInfo(&transparencies, ti);
6920     }
6921
6922     // sort transparency values to start with lowest transparency value
6923     sortTreeInfo(&transparencies);
6924
6925     // set current transparency value to configured transparency value
6926     transparency_current =
6927       getTreeInfoFromIdentifier(transparencies,
6928                                 i_to_a(setup.touch.transparency));
6929
6930     // if that fails, set current transparency to reliable default value
6931     if (transparency_current == NULL)
6932       transparency_current =
6933         getTreeInfoFromIdentifier(transparencies,
6934                                   i_to_a(TOUCH_TRANSPARENCY_DEFAULT));
6935
6936     // if that also fails, set current transparency to first available value
6937     if (transparency_current == NULL)
6938       transparency_current = transparencies;
6939   }
6940
6941   for (i = 0; i < 2; i++)
6942   {
6943     for (j = 0; j < 2; j++)
6944     {
6945       if (grid_sizes[i][j] == NULL)
6946       {
6947         for (k = 0; grid_sizes_list[k].value != -1; k++)
6948         {
6949           TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED);
6950           char identifier[32], name[32];
6951           int value = grid_sizes_list[k].value;
6952           char *text = grid_sizes_list[k].text;
6953
6954           ti->node_top = &grid_sizes[i][j];
6955           ti->sort_priority = value;
6956
6957           sprintf(identifier, "%d", value);
6958           sprintf(name, "%s", text);
6959
6960           setString(&ti->identifier, identifier);
6961           setString(&ti->name, name);
6962           setString(&ti->name_sorting, name);
6963           setString(&ti->infotext,
6964                     (i == 0 ?
6965                      (j == 0 ?
6966                       STR_SETUP_CHOOSE_GRID_XSIZE_0 :
6967                       STR_SETUP_CHOOSE_GRID_YSIZE_0) :
6968                      (j == 0 ?
6969                       STR_SETUP_CHOOSE_GRID_XSIZE_1 :
6970                       STR_SETUP_CHOOSE_GRID_YSIZE_1)));
6971
6972           pushTreeInfo(&grid_sizes[i][j], ti);
6973         }
6974
6975         // sort grid size values to start with lowest grid size value
6976         sortTreeInfo(&grid_sizes[i][j]);
6977
6978         // set current grid size value to configured grid size value
6979         grid_size_current[i][j] =
6980           getTreeInfoFromIdentifier(grid_sizes[i][j],
6981                                     i_to_a(j == 0 ?
6982                                            setup.touch.grid_xsize[i] :
6983                                            setup.touch.grid_ysize[i]));
6984
6985         // if that fails, set current grid size to reliable default value
6986         if (grid_size_current[i][j] == NULL)
6987           grid_size_current[i][j] =
6988             getTreeInfoFromIdentifier(grid_sizes[i][j],
6989                                       i_to_a(j == 0 ?
6990                                              DEFAULT_GRID_XSIZE(i) :
6991                                              DEFAULT_GRID_YSIZE(i)));
6992
6993         // if that also fails, set current grid size to first available value
6994         if (grid_size_current[i][j] == NULL)
6995           grid_size_current[i][j] = grid_sizes[i][j];
6996       }
6997     }
6998   }
6999
7000   setup.touch.control_type = touch_control_current->identifier;
7001   setup.touch.move_distance = atoi(move_distance_current->identifier);
7002   setup.touch.drop_distance = atoi(drop_distance_current->identifier);
7003   setup.touch.transparency = atoi(transparency_current->identifier);
7004
7005   for (i = 0; i < 2; i++)
7006   {
7007     setup.touch.grid_xsize[i] = atoi(grid_size_current[i][0]->identifier);
7008     setup.touch.grid_ysize[i] = atoi(grid_size_current[i][1]->identifier);
7009
7010     if (i == GRID_ACTIVE_NR())
7011     {
7012       overlay.grid_xsize = setup.touch.grid_xsize[i];
7013       overlay.grid_ysize = setup.touch.grid_ysize[i];
7014     }
7015   }
7016
7017   // needed for displaying value text instead of identifier
7018   touch_controls_text = touch_control_current->name;
7019   move_distance_text = move_distance_current->name;
7020   drop_distance_text = drop_distance_current->name;
7021   transparency_text = transparency_current->name;
7022
7023   for (i = 0; i < 2; i++)
7024     for (j = 0; j < 2; j++)
7025       grid_size_text[i][j] = grid_size_current[i][j]->name;
7026
7027   setup_mode = SETUP_MODE_TOUCH;
7028
7029   DrawSetupScreen();
7030 }
7031
7032 static void execSetupArtwork(void)
7033 {
7034   static ArtworkDirTree *gfx_last_valid = NULL;
7035   static ArtworkDirTree *snd_last_valid = NULL;
7036   static ArtworkDirTree *mus_last_valid = NULL;
7037
7038   // current artwork directory may be invalid (level group, parent link)
7039   if (!validLevelSeries(artwork.gfx_current))
7040     artwork.gfx_current = getFirstValidTreeInfoEntry(gfx_last_valid);
7041   if (!validLevelSeries(artwork.snd_current))
7042     artwork.snd_current = getFirstValidTreeInfoEntry(snd_last_valid);
7043   if (!validLevelSeries(artwork.mus_current))
7044     artwork.mus_current = getFirstValidTreeInfoEntry(mus_last_valid);
7045
7046   // store valid artwork directory information
7047   gfx_last_valid = artwork.gfx_current;
7048   snd_last_valid = artwork.snd_current;
7049   mus_last_valid = artwork.mus_current;
7050
7051 #if 0
7052   Debug("screens:execSetupArtwork", "'%s', '%s', '%s'",
7053         artwork.gfx_current->subdir,
7054         artwork.gfx_current->fullpath,
7055         artwork.gfx_current->basepath);
7056 #endif
7057
7058   setup.graphics_set = artwork.gfx_current->identifier;
7059   setup.sounds_set = artwork.snd_current->identifier;
7060   setup.music_set = artwork.mus_current->identifier;
7061
7062   // needed if last screen (setup choice) changed graphics, sounds or music
7063   ReloadCustomArtwork(0);
7064
7065   // needed for displaying artwork name instead of artwork identifier
7066   graphics_set_name = artwork.gfx_current->name;
7067   sounds_set_name = artwork.snd_current->name;
7068   music_set_name = artwork.mus_current->name;
7069
7070   setup_mode = SETUP_MODE_ARTWORK;
7071
7072   DrawSetupScreen();
7073 }
7074
7075 static void execSetupChooseGraphics(void)
7076 {
7077   setup_mode = SETUP_MODE_CHOOSE_GRAPHICS;
7078
7079   DrawSetupScreen();
7080 }
7081
7082 static void execSetupChooseSounds(void)
7083 {
7084   setup_mode = SETUP_MODE_CHOOSE_SOUNDS;
7085
7086   DrawSetupScreen();
7087 }
7088
7089 static void execSetupChooseMusic(void)
7090 {
7091   setup_mode = SETUP_MODE_CHOOSE_MUSIC;
7092
7093   DrawSetupScreen();
7094 }
7095
7096 static void execSetupInput(void)
7097 {
7098   setup_mode = SETUP_MODE_INPUT;
7099
7100   DrawSetupScreen();
7101 }
7102
7103 static void execSetupShortcuts(void)
7104 {
7105   setup_mode = SETUP_MODE_SHORTCUTS;
7106
7107   DrawSetupScreen();
7108 }
7109
7110 static void execSetupShortcuts1(void)
7111 {
7112   setup_mode = SETUP_MODE_SHORTCUTS_1;
7113
7114   DrawSetupScreen();
7115 }
7116
7117 static void execSetupShortcuts2(void)
7118 {
7119   setup_mode = SETUP_MODE_SHORTCUTS_2;
7120
7121   DrawSetupScreen();
7122 }
7123
7124 static void execSetupShortcuts3(void)
7125 {
7126   setup_mode = SETUP_MODE_SHORTCUTS_3;
7127
7128   DrawSetupScreen();
7129 }
7130
7131 static void execSetupShortcuts4(void)
7132 {
7133   setup_mode = SETUP_MODE_SHORTCUTS_4;
7134
7135   DrawSetupScreen();
7136 }
7137
7138 static void execSetupShortcuts5(void)
7139 {
7140   setup_mode = SETUP_MODE_SHORTCUTS_5;
7141
7142   DrawSetupScreen();
7143 }
7144
7145 static void execExitSetup(void)
7146 {
7147   SetGameStatus(GAME_MODE_MAIN);
7148
7149   DrawMainMenu();
7150 }
7151
7152 static void execSaveAndExitSetup(void)
7153 {
7154   SaveSetup();
7155   execExitSetup();
7156 }
7157
7158 static void execGadgetNetworkServer(void)
7159 {
7160   int gadget_id = SCREEN_CTRL_ID_NETWORK_SERVER;
7161   struct GadgetInfo *gi = screen_gadget[gadget_id];
7162
7163   if (strEqual(setup.network_server_hostname, STR_NETWORK_AUTO_DETECT))
7164     network_server_hostname[0] = '\0';
7165
7166   ModifyGadget(gi, GDI_TEXT_VALUE, network_server_hostname, GDI_END);
7167
7168   MapGadget(gi);
7169
7170   ClickOnGadget(gi, MB_LEFTBUTTON);
7171 }
7172
7173 static void execOfferUploadTapes(void)
7174 {
7175   OfferUploadTapes();
7176 }
7177
7178 static void ToggleNetworkModeIfNeeded(void)
7179 {
7180   int font_title = FONT_TITLE_1;
7181   int font_foot = FC_BLUE;
7182   int ystart  = mSY - SY + 16;
7183   int ybottom = mSY - SY + SYSIZE - 20;
7184   char *text = (setup.network_mode ? "Start Network" : "Stop Network");
7185
7186   if (setup.network_mode == network.enabled)
7187     return;
7188
7189   network.enabled = setup.network_mode;
7190
7191   FadeOut(REDRAW_ALL);
7192
7193   ClearField();
7194
7195   DrawTextSCentered(ystart, font_title, text);
7196
7197   FadeIn(REDRAW_ALL);
7198
7199   if (network.enabled)
7200     InitNetworkServer();
7201   else
7202     DisconnectFromNetworkServer();
7203
7204   DrawTextSCentered(ybottom, font_foot,
7205                     "Press any key or button for setup menu");
7206
7207   WaitForEventToContinue();
7208
7209   DrawSetupScreen();
7210 }
7211
7212 static void ToggleGameSpeedsListIfNeeded(void)
7213 {
7214   boolean using_game_speeds_extended = (game_speeds == game_speeds_extended);
7215
7216   if (setup.game_speed_extended == using_game_speeds_extended)
7217     return;
7218
7219   // try to match similar values when changing game speeds list
7220   if (setup.game_speed_extended)
7221     setup.game_frame_delay = (setup.game_frame_delay == 15 ? 16 :
7222                               setup.game_frame_delay == 30 ? 29 :
7223                               setup.game_frame_delay);
7224   else
7225     setup.game_frame_delay = (setup.game_frame_delay == 14 ? 15 :
7226                               setup.game_frame_delay == 16 ? 15 :
7227                               setup.game_frame_delay >= 29 ? 30 :
7228                               setup.game_frame_delay <= 10 ? 10 :
7229                               setup.game_frame_delay);
7230
7231   execSetupGame_setGameSpeeds(TRUE);
7232
7233   DrawSetupScreen();
7234 }
7235
7236 static void ToggleUseApiServerIfNeeded(void)
7237 {
7238   if (runtime.use_api_server == setup.use_api_server)
7239     return;
7240
7241   runtime.use_api_server = setup.use_api_server;
7242
7243   if (runtime.use_api_server)
7244   {
7245     if (setup.has_remaining_tapes)
7246       setup.ask_for_uploading_tapes = TRUE;
7247
7248     CheckApiServerTasks();
7249   }
7250 }
7251
7252 static void ModifyGameSpeedIfNeeded(void)
7253 {
7254   if (strEqual(setup.vsync_mode, STR_VSYNC_MODE_OFF) ||
7255       setup.game_frame_delay <= MAX_VSYNC_FRAME_DELAY)
7256     return;
7257
7258   char message[100];
7259   char *game_speed_text = "Fast";
7260   int game_speed_value = 15;
7261
7262   if (setup.game_speed_extended)
7263   {
7264     game_speed_text = "60 fps";
7265     game_speed_value = 16;
7266   }
7267
7268   sprintf(message, "Game speed set to %s for VSync to work!", game_speed_text);
7269
7270   // set game speed to existing list value that is fast enough for vsync
7271   setup.game_frame_delay = game_speed_value;
7272
7273   execSetupGame_setGameSpeeds(TRUE);
7274
7275   Request(message, REQ_CONFIRM);
7276 }
7277
7278 static void DisableVsyncIfNeeded(void)
7279 {
7280   if (strEqual(setup.vsync_mode, STR_VSYNC_MODE_OFF) ||
7281       (setup.game_frame_delay >= MIN_VSYNC_FRAME_DELAY &&
7282        setup.game_frame_delay <= MAX_VSYNC_FRAME_DELAY))
7283     return;
7284
7285   // disable vsync for the selected game speed to work
7286   setup.vsync_mode = STR_VSYNC_MODE_OFF;
7287
7288   execSetupGraphics_setVsyncModes(TRUE);
7289
7290   Request("VSync disabled for this game speed to work!", REQ_CONFIRM);
7291 }
7292
7293 static struct
7294 {
7295   void *value;
7296   void *related_value;
7297 } hide_related_entry_list[] =
7298 {
7299   { &setup.network_server_hostname,             execGadgetNetworkServer         },
7300   { &setup.network_server_hostname,             &network_server_text            },
7301
7302   { &setup.scores_in_highscore_list,            execSetupChooseScoresType       },
7303   { &setup.scores_in_highscore_list,            &scores_type_text               },
7304
7305   { &setup.game_frame_delay,                    execSetupChooseGameSpeed        },
7306   { &setup.game_frame_delay,                    &game_speed_text                },
7307
7308   { &setup.scroll_delay_value,                  execSetupChooseScrollDelay      },
7309   { &setup.scroll_delay_value,                  &scroll_delay_text              },
7310
7311   { &setup.engine_snapshot_mode,                execSetupChooseSnapshotMode     },
7312   { &setup.engine_snapshot_mode,                &snapshot_mode_text             },
7313
7314   { &setup.window_scaling_percent,              execSetupChooseWindowSize       },
7315   { &setup.window_scaling_percent,              &window_size_text               },
7316
7317   { &setup.window_scaling_quality,              execSetupChooseScalingType      },
7318   { &setup.window_scaling_quality,              &scaling_type_text              },
7319
7320   { &setup.screen_rendering_mode,               execSetupChooseRenderingMode    },
7321   { &setup.screen_rendering_mode,               &rendering_mode_text            },
7322
7323   { &setup.vsync_mode,                          execSetupChooseVsyncMode        },
7324   { &setup.vsync_mode,                          &vsync_mode_text                },
7325
7326   { &setup.graphics_set,                        execSetupChooseGraphics         },
7327   { &setup.graphics_set,                        &graphics_set_name              },
7328
7329   { &setup.sounds_set,                          execSetupChooseSounds           },
7330   { &setup.sounds_set,                          &sounds_set_name                },
7331
7332   { &setup.music_set,                           execSetupChooseMusic            },
7333   { &setup.music_set,                           &music_set_name                 },
7334
7335   { &setup.volume_simple,                       execSetupChooseVolumeSimple     },
7336   { &setup.volume_simple,                       &volume_simple_text             },
7337
7338   { &setup.volume_loops,                        execSetupChooseVolumeLoops      },
7339   { &setup.volume_loops,                        &volume_loops_text              },
7340
7341   { &setup.volume_music,                        execSetupChooseVolumeMusic      },
7342   { &setup.volume_music,                        &volume_music_text              },
7343
7344   { &setup.touch.control_type,                  execSetupChooseTouchControls    },
7345   { &setup.touch.control_type,                  &touch_controls_text            },
7346
7347   { &setup.touch.move_distance,                 execSetupChooseMoveDistance     },
7348   { &setup.touch.move_distance,                 &move_distance_text             },
7349
7350   { &setup.touch.drop_distance,                 execSetupChooseDropDistance     },
7351   { &setup.touch.drop_distance,                 &drop_distance_text             },
7352
7353   { &setup.touch.transparency,                  execSetupChooseTransparency     },
7354   { &setup.touch.transparency,                  &transparency_text              },
7355
7356   { &setup.touch.grid_xsize[0],                 execSetupChooseGridXSize_0      },
7357   { &setup.touch.grid_xsize[0],                 &grid_size_text[0][0]           },
7358
7359   { &setup.touch.grid_ysize[0],                 execSetupChooseGridYSize_0      },
7360   { &setup.touch.grid_ysize[0],                 &grid_size_text[0][1]           },
7361
7362   { &setup.touch.grid_xsize[1],                 execSetupChooseGridXSize_1      },
7363   { &setup.touch.grid_xsize[1],                 &grid_size_text[1][0]           },
7364
7365   { &setup.touch.grid_ysize[1],                 execSetupChooseGridYSize_1      },
7366   { &setup.touch.grid_ysize[1],                 &grid_size_text[1][1]           },
7367
7368   { &setup.internal.menu_game,                  execSetupGame                   },
7369   { &setup.internal.menu_engines,               execSetupEngines                },
7370   { &setup.internal.menu_editor,                execSetupEditor                 },
7371   { &setup.internal.menu_graphics,              execSetupGraphics               },
7372   { &setup.internal.menu_sound,                 execSetupSound                  },
7373   { &setup.internal.menu_artwork,               execSetupArtwork                },
7374   { &setup.internal.menu_input,                 execSetupInput                  },
7375   { &setup.internal.menu_touch,                 execSetupTouch                  },
7376   { &setup.internal.menu_shortcuts,             execSetupShortcuts              },
7377   { &setup.internal.menu_exit,                  execExitSetup                   },
7378   { &setup.internal.menu_save_and_exit,         execSaveAndExitSetup            },
7379
7380   { &setup.internal.menu_shortcuts_various,     execSetupShortcuts1             },
7381   { &setup.internal.menu_shortcuts_focus,       execSetupShortcuts2             },
7382   { &setup.internal.menu_shortcuts_tape,        execSetupShortcuts3             },
7383   { &setup.internal.menu_shortcuts_sound,       execSetupShortcuts4             },
7384   { &setup.internal.menu_shortcuts_snap,        execSetupShortcuts5             },
7385
7386   { &setup.internal.info_title,                 execInfoTitleScreen             },
7387   { &setup.internal.info_elements,              execInfoElements                },
7388   { &setup.internal.info_music,                 execInfoMusic                   },
7389   { &setup.internal.info_credits,               execInfoCredits                 },
7390   { &setup.internal.info_program,               execInfoProgram                 },
7391   { &setup.internal.info_version,               execInfoVersion                 },
7392   { &setup.internal.info_levelset,              execInfoLevelSet                },
7393   { &setup.internal.info_exit,                  execExitInfo                    },
7394
7395   { NULL,                                       NULL                            }
7396 };
7397
7398 void setHideRelatedSetupEntries(void)
7399 {
7400   int i;
7401
7402   for (i = 0; hide_related_entry_list[i].value != NULL; i++)
7403     if (hideSetupEntry(hide_related_entry_list[i].value))
7404       setHideSetupEntry(hide_related_entry_list[i].related_value);
7405 }
7406
7407 static struct TokenInfo setup_info_main[] =
7408 {
7409   { TYPE_ENTER_MENU,    execSetupGame,                  STR_SETUP_GAME                  },
7410   { TYPE_ENTER_MENU,    execSetupEngines,               STR_SETUP_ENGINES               },
7411   { TYPE_ENTER_MENU,    execSetupEditor,                STR_SETUP_EDITOR                },
7412   { TYPE_ENTER_MENU,    execSetupGraphics,              STR_SETUP_GRAPHICS              },
7413   { TYPE_ENTER_MENU,    execSetupSound,                 STR_SETUP_SOUND                 },
7414   { TYPE_ENTER_MENU,    execSetupArtwork,               STR_SETUP_ARTWORK               },
7415   { TYPE_ENTER_MENU,    execSetupInput,                 STR_SETUP_INPUT                 },
7416   { TYPE_ENTER_MENU,    execSetupTouch,                 STR_SETUP_TOUCH                 },
7417   { TYPE_ENTER_MENU,    execSetupShortcuts,             STR_SETUP_SHORTCUTS             },
7418   { TYPE_EMPTY,         NULL,                           ""                              },
7419   { TYPE_LEAVE_MENU,    execExitSetup,                  STR_SETUP_EXIT                  },
7420   { TYPE_LEAVE_MENU,    execSaveAndExitSetup,           STR_SETUP_SAVE_AND_EXIT         },
7421
7422   { 0,                  NULL,                           NULL                            }
7423 };
7424
7425 static struct TokenInfo setup_info_game[] =
7426 {
7427   { TYPE_SWITCH,        &setup.team_mode,               "Team-Mode (Multi-Player):"     },
7428   { TYPE_SWITCH,        &setup.network_mode,            "Network Multi-Player Mode:"    },
7429   { TYPE_PLAYER,        &setup.network_player_nr,       "Preferred Network Player:"     },
7430   { TYPE_TEXT_INPUT,    execGadgetNetworkServer,        "Network Server Hostname:"      },
7431   { TYPE_STRING,        &network_server_text,           ""                              },
7432   { TYPE_SWITCH,        &setup.use_api_server,          "Use Highscore Server:"         },
7433   { TYPE_ENTER_LIST,    execSetupChooseScoresType,      "Scores in Highscore List:"     },
7434   { TYPE_STRING,        &scores_type_text,              ""                              },
7435   { TYPE_ENTER_LIST,    execOfferUploadTapes,           "Upload Tapes to Server"        },
7436   { TYPE_SWITCH,        &setup.multiple_users,          "Multiple Users/Teams:"         },
7437   { TYPE_YES_NO,        &setup.input_on_focus,          "Only Move Focussed Player:"    },
7438   { TYPE_SWITCH,        &setup.time_limit,              "Time Limit:"                   },
7439   { TYPE_SWITCH,        &setup.handicap,                "Force Solving Levels:"         },
7440   { TYPE_SWITCH,        &setup.skip_levels,             "Allow Skipping Levels:"        },
7441   { TYPE_SWITCH,        &setup.increment_levels,        "Increment Solved Levels:"      },
7442   { TYPE_SWITCH,        &setup.auto_play_next_level,    "Auto-play Next Level:"         },
7443   { TYPE_SWITCH,        &setup.count_score_after_game,  "Count Score After Game:"       },
7444   { TYPE_SWITCH,        &setup.show_scores_after_game,  "Show Scores After Game:"       },
7445   { TYPE_YES_NO,        &setup.ask_on_game_over,        "Ask on Game Over:"             },
7446   { TYPE_YES_NO,        &setup.ask_on_quit_game,        "Ask on Quit Game:"             },
7447   { TYPE_YES_NO,        &setup.ask_on_quit_program,     "Ask on Quit Program:"          },
7448   { TYPE_SWITCH,        &setup.autorecord,              "Auto-Record When Playing:"     },
7449   { TYPE_SWITCH,        &setup.autorecord_after_replay, "Auto-Record After Replay:"     },
7450   { TYPE_SWITCH,        &setup.auto_pause_on_start,     "Start Game in Pause Mode:"     },
7451   { TYPE_ENTER_LIST,    execSetupChooseGameSpeed,       "Game Speed:"                   },
7452   { TYPE_STRING,        &game_speed_text,               ""                              },
7453   { TYPE_SWITCH,        &setup.game_speed_extended,     "Game Speed Extended List:"     },
7454 #if 1
7455   { TYPE_ENTER_LIST,    execSetupChooseScrollDelay,     "Scroll Delay:"                 },
7456   { TYPE_STRING,        &scroll_delay_text,             ""                              },
7457 #endif
7458   { TYPE_ENTER_LIST,    execSetupChooseSnapshotMode,    "Game Engine Snapshot Mode:"    },
7459   { TYPE_STRING,        &snapshot_mode_text,            ""                              },
7460   { TYPE_SWITCH,        &setup.show_load_save_buttons,  "Show Load/Save Buttons:"       },
7461   { TYPE_SWITCH,        &setup.show_undo_redo_buttons,  "Show Undo/Redo Buttons:"       },
7462   { TYPE_EMPTY,         NULL,                           ""                              },
7463   { TYPE_LEAVE_MENU,    execSetupMain,                  "Back"                          },
7464
7465   { 0,                  NULL,                           NULL                            }
7466 };
7467
7468 static struct TokenInfo setup_info_engines[] =
7469 {
7470   { TYPE_HEADLINE,      NULL,                           "Boulder Dash"                  },
7471   { TYPE_SWITCH,        &setup.bd_skip_uncovering,      "Skip (un)covering screen:"     },
7472   { TYPE_SWITCH,        &setup.bd_skip_hatching,        "Skip hatching player:"         },
7473   { TYPE_SWITCH,        &setup.bd_scroll_delay,         "Scroll Delay:"                 },
7474   { TYPE_YES_NO_AUTO,   &setup.bd_smooth_movements,     "Smooth Element Movement:"      },
7475   { TYPE_EMPTY,         NULL,                           ""                              },
7476   { TYPE_HEADLINE,      NULL,                           "Emerald Mine"                  },
7477   { TYPE_SWITCH,        &setup.forced_scroll_delay,     "Scroll Delay:"                 },
7478   { TYPE_ECS_AGA,       &setup.prefer_aga_graphics,     "Amiga Graphics Chipset:"       },
7479   { TYPE_SWITCH,        &setup.prefer_lowpass_sounds,   "Low-Pass Filter Sounds:"       },
7480   { TYPE_SWITCH,        &setup.prefer_extra_panel_items,"Show Dynamite and Keys:"       },
7481   { TYPE_EMPTY,         NULL,                           ""                              },
7482   { TYPE_HEADLINE,      NULL,                           "Supaplex"                      },
7483   { TYPE_SWITCH,        &setup.sp_show_border_elements, "Border Elements:"              },
7484   { TYPE_EMPTY,         NULL,                           ""                              },
7485   { TYPE_LEAVE_MENU,    execSetupMain,                  "Back"                          },
7486
7487   { 0,                  NULL,                           NULL                            }
7488 };
7489
7490 static struct TokenInfo setup_info_editor[] =
7491 {
7492 #if 0
7493   { TYPE_SWITCH,        &setup.editor.el_boulderdash,   "Boulder Dash:"                 },
7494   { TYPE_SWITCH,        &setup.editor.el_boulderdash_native, "Boulder Dash Native:"     },
7495   { TYPE_SWITCH,        &setup.editor.el_emerald_mine,  "Emerald Mine:"                 },
7496   { TYPE_SWITCH,        &setup.editor.el_emerald_mine_club, "Emerald Mine Club:"        },
7497   { TYPE_SWITCH,        &setup.editor.el_more,          "Rocks'n'Diamonds:"             },
7498   { TYPE_SWITCH,        &setup.editor.el_sokoban,       "Sokoban:"                      },
7499   { TYPE_SWITCH,        &setup.editor.el_supaplex,      "Supaplex:"                     },
7500   { TYPE_SWITCH,        &setup.editor.el_diamond_caves, "Diamond Caves II:"             },
7501   { TYPE_SWITCH,        &setup.editor.el_dx_boulderdash,"DX-Boulderdash:"               },
7502   { TYPE_SWITCH,        &setup.editor.el_chars,         "Text Characters:"              },
7503   { TYPE_SWITCH,        &setup.editor.el_steel_chars,   "Text Characters (Steel):"      },
7504 #endif
7505   { TYPE_SWITCH,        &setup.editor.el_classic,       "Classic Elements:"             },
7506   { TYPE_SWITCH,        &setup.editor.el_custom,        "Custom & Group Elements:"      },
7507 #if 0
7508   { TYPE_SWITCH,        &setup.editor.el_headlines,     "Headlines:"                    },
7509 #endif
7510   { TYPE_SWITCH, &setup.editor.el_user_defined,         "User defined element list:"    },
7511   { TYPE_SWITCH,        &setup.editor.el_dynamic,       "Dynamic level elements:"       },
7512   { TYPE_EMPTY,         NULL,                           ""                              },
7513 #if 0
7514   { TYPE_SWITCH,        &setup.editor.el_by_game,       "Show elements by game:"        },
7515   { TYPE_SWITCH,        &setup.editor.el_by_type,       "Show elements by type:"        },
7516   { TYPE_EMPTY,         NULL,                           ""                              },
7517 #endif
7518   { TYPE_SWITCH, &setup.editor.show_element_token,      "Show element token:"           },
7519   { TYPE_EMPTY,         NULL,                           ""                              },
7520   { TYPE_SWITCH, &setup.editor.show_read_only_warning,  "Show read-only warning:"       },
7521   { TYPE_EMPTY,         NULL,                           ""                              },
7522   { TYPE_LEAVE_MENU,    execSetupMain,                  "Back"                          },
7523
7524   { 0,                  NULL,                           NULL                            }
7525 };
7526
7527 static struct TokenInfo setup_info_graphics[] =
7528 {
7529 #if !defined(PLATFORM_ANDROID) && !defined(PLATFORM_EMSCRIPTEN)
7530   { TYPE_SWITCH,        &setup.fullscreen,              "Fullscreen:"                   },
7531   { TYPE_ENTER_LIST,    execSetupChooseWindowSize,      "Window Scaling:"               },
7532   { TYPE_STRING,        &window_size_text,              ""                              },
7533   { TYPE_ENTER_LIST,    execSetupChooseScalingType,     "Anti-Aliasing:"                },
7534   { TYPE_STRING,        &scaling_type_text,             ""                              },
7535   { TYPE_ENTER_LIST,    execSetupChooseRenderingMode,   "Special Rendering:"            },
7536   { TYPE_STRING,        &rendering_mode_text,           ""                              },
7537 #endif
7538 #if 0
7539   { TYPE_ENTER_LIST,    execSetupChooseScrollDelay,     "Scroll Delay:"                 },
7540   { TYPE_STRING,        &scroll_delay_text,             ""                              },
7541 #endif
7542 #if !defined(PLATFORM_EMSCRIPTEN)
7543   { TYPE_ENTER_LIST,    execSetupChooseVsyncMode,       "Vertical Sync (VSync):"        },
7544   { TYPE_STRING,        &vsync_mode_text,               ""                              },
7545 #endif
7546   { TYPE_SWITCH,        &setup.fade_screens,            "Fade Screens:"                 },
7547   { TYPE_SWITCH,        &setup.quick_switch,            "Quick Player Focus Switch:"    },
7548   { TYPE_SWITCH,        &setup.quick_doors,             "Quick Menu Doors:"             },
7549   { TYPE_SWITCH,        &setup.show_titlescreen,        "Show Title Screens:"           },
7550   { TYPE_SWITCH,        &setup.toons,                   "Show Toons:"                   },
7551   { TYPE_SWITCH,        &setup.small_game_graphics,     "Small Game Graphics:"          },
7552   { TYPE_YES_NO_AUTO,   &setup.debug.xsn_mode,          debug_xsn_mode                  },
7553   { TYPE_EMPTY,         NULL,                           ""                              },
7554   { TYPE_LEAVE_MENU,    execSetupMain,                  "Back"                          },
7555
7556   { 0,                  NULL,                           NULL                            }
7557 };
7558
7559 static struct TokenInfo setup_info_sound[] =
7560 {
7561   { TYPE_SWITCH,        &setup.sound_simple,            "Sound Effects (Normal):"       },
7562   { TYPE_SWITCH,        &setup.sound_loops,             "Sound Effects (Looping):"      },
7563   { TYPE_SWITCH,        &setup.sound_music,             "Music:"                        },
7564   { TYPE_EMPTY,         NULL,                           ""                              },
7565   { TYPE_ENTER_LIST,    execSetupChooseVolumeSimple,    "Sound Volume (Normal):"        },
7566   { TYPE_STRING,        &volume_simple_text,            ""                              },
7567   { TYPE_ENTER_LIST,    execSetupChooseVolumeLoops,     "Sound Volume (Looping):"       },
7568   { TYPE_STRING,        &volume_loops_text,             ""                              },
7569   { TYPE_ENTER_LIST,    execSetupChooseVolumeMusic,     "Music Volume:"                 },
7570   { TYPE_STRING,        &volume_music_text,             ""                              },
7571   { TYPE_EMPTY,         NULL,                           ""                              },
7572   { TYPE_LEAVE_MENU,    execSetupMain,                  "Back"                          },
7573
7574   { 0,                  NULL,                           NULL                            }
7575 };
7576
7577 static struct TokenInfo setup_info_artwork[] =
7578 {
7579   { TYPE_ENTER_LIST,    execSetupChooseGraphics,        "Custom Graphics:"              },
7580   { TYPE_STRING,        &graphics_set_name,             ""                              },
7581   { TYPE_ENTER_LIST,    execSetupChooseSounds,          "Custom Sounds:"                },
7582   { TYPE_STRING,        &sounds_set_name,               ""                              },
7583   { TYPE_ENTER_LIST,    execSetupChooseMusic,           "Custom Music:"                 },
7584   { TYPE_STRING,        &music_set_name,                ""                              },
7585   { TYPE_EMPTY,         NULL,                           ""                              },
7586   { TYPE_YES_NO_AUTO,   &setup.override_level_graphics,"Override Level Graphics:"       },
7587   { TYPE_YES_NO_AUTO,   &setup.override_level_sounds,   "Override Level Sounds:"        },
7588   { TYPE_YES_NO_AUTO,   &setup.override_level_music,    "Override Level Music:"         },
7589   { TYPE_EMPTY,         NULL,                           ""                              },
7590   { TYPE_LEAVE_MENU,    execSetupMain,                  "Back"                          },
7591
7592   { 0,                  NULL,                           NULL                            }
7593 };
7594
7595 static struct TokenInfo setup_info_input[] =
7596 {
7597   { TYPE_SWITCH,        NULL,                           "Player:"                       },
7598   { TYPE_SWITCH,        NULL,                           "Device:"                       },
7599   { TYPE_SWITCH,        NULL,                           ""                              },
7600   { TYPE_SKIPPABLE,     NULL,                           ""                              },
7601   { TYPE_EMPTY,         NULL,                           ""                              },
7602   { TYPE_EMPTY,         NULL,                           ""                              },
7603   { TYPE_EMPTY,         NULL,                           ""                              },
7604   { TYPE_EMPTY,         NULL,                           ""                              },
7605   { TYPE_EMPTY,         NULL,                           ""                              },
7606   { TYPE_EMPTY,         NULL,                           ""                              },
7607   { TYPE_EMPTY,         NULL,                           ""                              },
7608   { TYPE_EMPTY,         NULL,                           ""                              },
7609   { TYPE_SKIPPABLE,     NULL,                           ""                              },
7610   { TYPE_LEAVE_MENU,    execSetupMain,                  "Back"                          },
7611
7612   { 0,                  NULL,                           NULL                            }
7613 };
7614
7615 static struct TokenInfo setup_info_touch[] =
7616 {
7617   { TYPE_ENTER_LIST,    execSetupChooseTouchControls,   "Touch Control Type:"           },
7618   { TYPE_STRING,        &touch_controls_text,           ""                              },
7619   { TYPE_EMPTY,         NULL,                           ""                              },
7620   { TYPE_LEAVE_MENU,    execSetupMain,                  "Back"                          },
7621
7622   { 0,                  NULL,                           NULL                            }
7623 };
7624
7625 static struct TokenInfo setup_info_touch_virtual_buttons_0[] =
7626 {
7627   { TYPE_ENTER_LIST,    execSetupChooseTouchControls,   "Touch Control Type:"           },
7628   { TYPE_STRING,        &touch_controls_text,           ""                              },
7629   { TYPE_EMPTY,         NULL,                           ""                              },
7630   { TYPE_ENTER_LIST,    execSetupChooseGridXSize_0,     "Horizontal Buttons (Landscape):" },
7631   { TYPE_STRING,        &grid_size_text[0][0],          ""                              },
7632   { TYPE_ENTER_LIST,    execSetupChooseGridYSize_0,     "Vertical Buttons (Landscape):" },
7633   { TYPE_STRING,        &grid_size_text[0][1],          ""                              },
7634   { TYPE_ENTER_LIST,    execSetupChooseTransparency,    "Button Transparency:"          },
7635   { TYPE_STRING,        &transparency_text,             ""                              },
7636   { TYPE_SWITCH,        &setup.touch.draw_outlined,     "Draw Buttons Outlined:"        },
7637   { TYPE_SWITCH,        &setup.touch.draw_pressed,      "Highlight Pressed Buttons:"    },
7638   { TYPE_EMPTY,         NULL,                           ""                              },
7639   { TYPE_ENTER_LIST,    execSetupConfigureVirtualButtons, "Configure Virtual Buttons"   },
7640   { TYPE_EMPTY,         NULL,                           ""                              },
7641   { TYPE_LEAVE_MENU,    execSetupMain,                  "Back"                          },
7642
7643   { 0,                  NULL,                           NULL                            }
7644 };
7645
7646 static struct TokenInfo setup_info_touch_virtual_buttons_1[] =
7647 {
7648   { TYPE_ENTER_LIST,    execSetupChooseTouchControls,   "Touch Control Type:"           },
7649   { TYPE_STRING,        &touch_controls_text,           ""                              },
7650   { TYPE_EMPTY,         NULL,                           ""                              },
7651   { TYPE_ENTER_LIST,    execSetupChooseGridXSize_1,     "Horizontal Buttons (Portrait):" },
7652   { TYPE_STRING,        &grid_size_text[1][0],          ""                              },
7653   { TYPE_ENTER_LIST,    execSetupChooseGridYSize_1,     "Vertical Buttons (Portrait):"  },
7654   { TYPE_STRING,        &grid_size_text[1][1],          ""                              },
7655   { TYPE_ENTER_LIST,    execSetupChooseTransparency,    "Button Transparency:"          },
7656   { TYPE_STRING,        &transparency_text,             ""                              },
7657   { TYPE_SWITCH,        &setup.touch.draw_outlined,     "Draw Buttons Outlined:"        },
7658   { TYPE_SWITCH,        &setup.touch.draw_pressed,      "Highlight Pressed Buttons:"    },
7659   { TYPE_EMPTY,         NULL,                           ""                              },
7660   { TYPE_ENTER_LIST,    execSetupConfigureVirtualButtons, "Configure Virtual Buttons"   },
7661   { TYPE_EMPTY,         NULL,                           ""                              },
7662   { TYPE_LEAVE_MENU,    execSetupMain,                  "Back"                          },
7663
7664   { 0,                  NULL,                           NULL                            }
7665 };
7666
7667 static struct TokenInfo *setup_info_touch_virtual_buttons[] =
7668 {
7669   setup_info_touch_virtual_buttons_0,
7670   setup_info_touch_virtual_buttons_1
7671 };
7672
7673 static struct TokenInfo setup_info_touch_wipe_gestures[] =
7674 {
7675   { TYPE_ENTER_LIST,    execSetupChooseTouchControls,   "Touch Control Type:"           },
7676   { TYPE_STRING,        &touch_controls_text,           ""                              },
7677   { TYPE_EMPTY,         NULL,                           ""                              },
7678   { TYPE_ENTER_LIST,    execSetupChooseMoveDistance,    "Move Trigger Distance:"        },
7679   { TYPE_STRING,        &move_distance_text,            ""                              },
7680   { TYPE_ENTER_LIST,    execSetupChooseDropDistance,    "Drop Trigger Distance:"        },
7681   { TYPE_STRING,        &drop_distance_text,            ""                              },
7682   { TYPE_EMPTY,         NULL,                           ""                              },
7683   { TYPE_LEAVE_MENU,    execSetupMain,                  "Back"                          },
7684
7685   { 0,                  NULL,                           NULL                            }
7686 };
7687
7688 static struct TokenInfo setup_info_shortcuts[] =
7689 {
7690   { TYPE_ENTER_MENU,    execSetupShortcuts1,            "Various Keys"                  },
7691   { TYPE_ENTER_MENU,    execSetupShortcuts2,            "Player Focus"                  },
7692   { TYPE_ENTER_MENU,    execSetupShortcuts3,            "Tape Buttons"                  },
7693   { TYPE_ENTER_MENU,    execSetupShortcuts4,            "Sound & Music"                 },
7694   { TYPE_ENTER_MENU,    execSetupShortcuts5,            "TAS Snap Keys"                 },
7695   { TYPE_EMPTY,         NULL,                           ""                              },
7696   { TYPE_LEAVE_MENU,    execSetupMain,                  "Back"                          },
7697
7698   { 0,                  NULL,                           NULL                            }
7699 };
7700
7701 static struct TokenInfo setup_info_shortcuts_1[] =
7702 {
7703   { TYPE_KEYTEXT,       NULL,                           "Quick Save Game to Tape:"      },
7704   { TYPE_KEY,           &setup.shortcut.save_game,      ""                              },
7705   { TYPE_KEYTEXT,       NULL,                           "Quick Load Game from Tape:"    },
7706   { TYPE_KEY,           &setup.shortcut.load_game,      ""                              },
7707   { TYPE_KEYTEXT,       NULL,                           "Restart Game:"                 },
7708   { TYPE_KEY,           &setup.shortcut.restart_game,   ""                              },
7709   { TYPE_KEYTEXT,       NULL,                           "Replay & Pause Before End:"    },
7710   { TYPE_KEY,           &setup.shortcut.pause_before_end, ""                            },
7711   { TYPE_KEYTEXT,       NULL,                           "Start Game & Toggle Pause:"    },
7712   { TYPE_KEY,           &setup.shortcut.toggle_pause,   ""                              },
7713   { TYPE_EMPTY,         NULL,                           ""                              },
7714   { TYPE_YES_NO,        &setup.ask_on_escape,           "Ask on 'Esc' Key:"             },
7715   { TYPE_YES_NO, &setup.ask_on_escape_editor,           "Ask on 'Esc' Key (Editor):"    },
7716   { TYPE_EMPTY,         NULL,                           ""                              },
7717   { TYPE_LEAVE_MENU,    execSetupShortcuts,             "Back"                          },
7718
7719   { 0,                  NULL,                           NULL                            }
7720 };
7721
7722 static struct TokenInfo setup_info_shortcuts_2[] =
7723 {
7724   { TYPE_KEYTEXT,       NULL,                           "Set Focus to Player 1:"        },
7725   { TYPE_KEY,           &setup.shortcut.focus_player[0], ""                             },
7726   { TYPE_KEYTEXT,       NULL,                           "Set Focus to Player 2:"        },
7727   { TYPE_KEY,           &setup.shortcut.focus_player[1], ""                             },
7728   { TYPE_KEYTEXT,       NULL,                           "Set Focus to Player 3:"        },
7729   { TYPE_KEY,           &setup.shortcut.focus_player[2], ""                             },
7730   { TYPE_KEYTEXT,       NULL,                           "Set Focus to Player 4:"        },
7731   { TYPE_KEY,           &setup.shortcut.focus_player[3], ""                             },
7732   { TYPE_KEYTEXT,       NULL,                           "Set Focus to All Players:"     },
7733   { TYPE_KEY,           &setup.shortcut.focus_player_all, ""                            },
7734   { TYPE_EMPTY,         NULL,                           ""                              },
7735   { TYPE_LEAVE_MENU,    execSetupShortcuts,             "Back"                          },
7736
7737   { 0,                  NULL,                           NULL                            }
7738 };
7739
7740 static struct TokenInfo setup_info_shortcuts_3[] =
7741 {
7742   { TYPE_KEYTEXT,       NULL,                           "Eject Tape:"                   },
7743   { TYPE_KEY,           &setup.shortcut.tape_eject,     ""                              },
7744   { TYPE_KEYTEXT,       NULL,                           "Warp / Single Step:"           },
7745   { TYPE_KEY,           &setup.shortcut.tape_extra,     ""                              },
7746   { TYPE_KEYTEXT,       NULL,                           "Stop Tape:"                    },
7747   { TYPE_KEY,           &setup.shortcut.tape_stop,      ""                              },
7748   { TYPE_KEYTEXT,       NULL,                           "Pause / Unpause Tape:"         },
7749   { TYPE_KEY,           &setup.shortcut.tape_pause,     ""                              },
7750   { TYPE_KEYTEXT,       NULL,                           "Record Tape:"                  },
7751   { TYPE_KEY,           &setup.shortcut.tape_record,    ""                              },
7752   { TYPE_KEYTEXT,       NULL,                           "Play Tape:"                    },
7753   { TYPE_KEY,           &setup.shortcut.tape_play,      ""                              },
7754   { TYPE_EMPTY,         NULL,                           ""                              },
7755   { TYPE_LEAVE_MENU,    execSetupShortcuts,             "Back"                          },
7756
7757   { 0,                  NULL,                           NULL                            }
7758 };
7759
7760 static struct TokenInfo setup_info_shortcuts_4[] =
7761 {
7762   { TYPE_KEYTEXT,       NULL,                           "Toggle Sound Effects (Normal):" },
7763   { TYPE_KEY,           &setup.shortcut.sound_simple,   ""                              },
7764   { TYPE_KEYTEXT,       NULL,                           "Toggle Sound Effects (Looping):" },
7765   { TYPE_KEY,           &setup.shortcut.sound_loops,    ""                              },
7766   { TYPE_KEYTEXT,       NULL,                           "Toggle Music:"                 },
7767   { TYPE_KEY,           &setup.shortcut.sound_music,    ""                              },
7768   { TYPE_EMPTY,         NULL,                           ""                              },
7769   { TYPE_LEAVE_MENU,    execSetupShortcuts,             "Back"                          },
7770
7771   { 0,                  NULL,                           NULL                            }
7772 };
7773
7774 static struct TokenInfo setup_info_shortcuts_5[] =
7775 {
7776   { TYPE_KEYTEXT,       NULL,                           "Snap Left:"                    },
7777   { TYPE_KEY,           &setup.shortcut.snap_left,      ""                              },
7778   { TYPE_KEYTEXT,       NULL,                           "Snap Right:"                   },
7779   { TYPE_KEY,           &setup.shortcut.snap_right,     ""                              },
7780   { TYPE_KEYTEXT,       NULL,                           "Snap Up:"                      },
7781   { TYPE_KEY,           &setup.shortcut.snap_up,        ""                              },
7782   { TYPE_KEYTEXT,       NULL,                           "Snap Down:"                    },
7783   { TYPE_KEY,           &setup.shortcut.snap_down,      ""                              },
7784   { TYPE_EMPTY,         NULL,                           ""                              },
7785   { TYPE_LEAVE_MENU,    execSetupShortcuts,             "Back"                          },
7786
7787   { 0,                  NULL,                           NULL                            }
7788 };
7789
7790 static Key getSetupKey(void)
7791 {
7792   Key key = KSYM_UNDEFINED;
7793   boolean got_key_event = FALSE;
7794
7795   while (!got_key_event)
7796   {
7797     Event event;
7798
7799     if (NextValidEvent(&event))
7800     {
7801       switch (event.type)
7802       {
7803         case EVENT_KEYPRESS:
7804           {
7805             key = GetEventKey((KeyEvent *)&event);
7806
7807             // press 'Escape' or 'Enter' to keep the existing key binding
7808             if (key == KSYM_Escape || key == KSYM_Return)
7809               key = KSYM_UNDEFINED;     // keep old value
7810
7811             got_key_event = TRUE;
7812           }
7813           break;
7814
7815         case EVENT_KEYRELEASE:
7816           key_joystick_mapping = 0;
7817           break;
7818
7819         default:
7820           HandleOtherEvents(&event);
7821           break;
7822       }
7823     }
7824
7825     BackToFront();
7826   }
7827
7828   return key;
7829 }
7830
7831 static int getSetupValueFont(int type, void *value)
7832 {
7833   if (type & TYPE_GHOSTED)
7834     return FONT_OPTION_OFF;
7835   else if (type & TYPE_KEY)
7836     return (type & TYPE_QUERY ? FONT_INPUT_1_ACTIVE : FONT_VALUE_1);
7837   else if (type & TYPE_STRING)
7838     return FONT_VALUE_2;
7839   else if (type & TYPE_ECS_AGA)
7840     return FONT_VALUE_1;
7841   else if (type & TYPE_BOOLEAN_STYLE)
7842     return (*(boolean *)value ? FONT_OPTION_ON : FONT_OPTION_OFF);
7843   else if (type & TYPE_YES_NO_AUTO)
7844     return (*(int *)value == AUTO  ? FONT_OPTION_ON :
7845             *(int *)value == FALSE ? FONT_OPTION_OFF : FONT_OPTION_ON);
7846   else if (type & TYPE_PLAYER)
7847     return FONT_VALUE_1;
7848   else
7849     return FONT_VALUE_1;
7850 }
7851
7852 static int getSetupValueFontNarrow(int type, int font_nr)
7853 {
7854   return (font_nr == FONT_VALUE_1    ? FONT_VALUE_NARROW :
7855           font_nr == FONT_OPTION_ON  ? FONT_OPTION_ON_NARROW :
7856           font_nr == FONT_OPTION_OFF ? FONT_OPTION_OFF_NARROW :
7857           font_nr);
7858 }
7859
7860 static void drawSetupValue(int screen_pos, int setup_info_pos_raw)
7861 {
7862   int si_pos = (setup_info_pos_raw < 0 ? screen_pos : setup_info_pos_raw);
7863   struct TokenInfo *si = &setup_info[si_pos];
7864   boolean font_draw_xoffset_modified = FALSE;
7865   boolean scrollbar_needed = (num_setup_info < max_setup_info);
7866   int font_draw_xoffset_old = -1;
7867   int xoffset = (scrollbar_needed ? -1 : 0);
7868   int menu_screen_value_xpos = MENU_SCREEN_VALUE_XPOS + xoffset;
7869   int menu_screen_max_xpos = MENU_SCREEN_MAX_XPOS + xoffset;
7870   int xpos = menu_screen_value_xpos;
7871   int ypos = MENU_SCREEN_START_YPOS + screen_pos;
7872   int startx = mSX + xpos * 32;
7873   int starty = mSY + ypos * 32;
7874   int type = si->type;
7875   void *value = si->value;
7876   char *value_string = getSetupValue(type, value);
7877   int font_nr_default = getSetupValueFont(type, value);
7878   int font_width_default = getFontWidth(font_nr_default);
7879   int font_nr = font_nr_default;
7880   int i;
7881
7882   if (value_string == NULL)
7883     return;
7884
7885   if (type & TYPE_KEY)
7886   {
7887     xpos = MENU_SCREEN_START_XPOS;
7888
7889     if (type & TYPE_QUERY)
7890       value_string = "<press key>";
7891   }
7892   else if (type & TYPE_STRING)
7893   {
7894     int max_value_len = (SXSIZE - 2 * TILEX) / font_width_default;
7895
7896     xpos = MENU_SCREEN_START_XPOS;
7897
7898     if (strlen(value_string) > max_value_len)
7899       value_string[max_value_len] = '\0';
7900   }
7901   else if (type & TYPE_YES_NO_AUTO)
7902   {
7903     xpos = menu_screen_value_xpos - 1;
7904   }
7905   else if (type & TYPE_PLAYER)
7906   {
7907     int displayed_player_nr = *(int *)value + 1;
7908
7909     value_string = getSetupValue(TYPE_INTEGER, (void *)&displayed_player_nr);
7910   }
7911
7912   startx = mSX + xpos * 32;
7913   starty = mSY + ypos * 32;
7914
7915   // special check if right-side setup values moved left due to scrollbar
7916   if (scrollbar_needed && xpos > MENU_SCREEN_START_XPOS)
7917   {
7918     int max_menu_text_length = 26;      // maximum text length for classic menu
7919     int font_xoffset = getFontDrawOffsetX(font_nr);
7920     int text_startx = mSX + MENU_SCREEN_START_XPOS * 32;
7921     int text_font_nr = getMenuTextFont(FONT_MENU_2);
7922     int text_font_xoffset = getFontDrawOffsetX(text_font_nr);
7923     int text_width = max_menu_text_length * getFontWidth(text_font_nr);
7924
7925     if (startx + font_xoffset < text_startx + text_width + text_font_xoffset)
7926     {
7927       // when using narrow font, left-shifting text "auto" not needed
7928       if (type & TYPE_YES_NO_AUTO)
7929         xpos += 1;
7930
7931       xpos += 1;
7932       startx = mSX + xpos * 32;
7933
7934       font_nr = getSetupValueFontNarrow(type, font_nr);
7935     }
7936   }
7937
7938   // downward compatibility correction for Juergen Bonhagen's menu settings
7939   if (setup_mode != SETUP_MODE_INPUT)
7940   {
7941     int max_menu_text_length_big = (menu_screen_value_xpos -
7942                                     MENU_SCREEN_START_XPOS);
7943     int max_menu_text_length_medium = max_menu_text_length_big * 2;
7944     int check_font_nr = FONT_OPTION_ON; // known font that needs correction
7945     int font1_xoffset = getFontDrawOffsetX(font_nr);
7946     int font2_xoffset = getFontDrawOffsetX(check_font_nr);
7947     int text_startx = mSX + MENU_SCREEN_START_XPOS * 32;
7948     int text_font_nr = getMenuTextFont(FONT_MENU_2);
7949     int text_font_xoffset = getFontDrawOffsetX(text_font_nr);
7950     int text_width = max_menu_text_length_medium * getFontWidth(text_font_nr);
7951     boolean correct_font_draw_xoffset = FALSE;
7952
7953     if (xpos == MENU_SCREEN_START_XPOS &&
7954         startx + font1_xoffset < text_startx + text_font_xoffset)
7955       correct_font_draw_xoffset = TRUE;
7956
7957     if (xpos == menu_screen_value_xpos &&
7958         startx + font2_xoffset < text_startx + text_width + text_font_xoffset)
7959       correct_font_draw_xoffset = TRUE;
7960
7961     // check if setup value would overlap with setup text when printed
7962     // (this can happen for extreme/wrong values for font draw offset)
7963     if (correct_font_draw_xoffset)
7964     {
7965       font_draw_xoffset_old = getFontDrawOffsetX(font_nr);
7966       font_draw_xoffset_modified = TRUE;
7967
7968       if (type & TYPE_KEY)
7969         getFontBitmapInfo(font_nr)->draw_xoffset += 2 * getFontWidth(font_nr);
7970       else if (!(type & TYPE_STRING))
7971         getFontBitmapInfo(font_nr)->draw_xoffset = text_font_xoffset + 20 -
7972           max_menu_text_length_medium * (16 - getFontWidth(text_font_nr));
7973     }
7974   }
7975
7976   for (i = 0; i <= menu_screen_max_xpos - xpos; i++)
7977     DrawText(startx + i * font_width_default, starty, " ", font_nr_default);
7978
7979   DrawText(startx, starty, value_string, font_nr);
7980
7981   if (type & TYPE_PLAYER)
7982   {
7983     struct FontBitmapInfo *font = getFontBitmapInfo(font_nr);
7984     int player_nr = *(int *)value;
7985     int xoff = font->draw_xoffset + getFontWidth(font_nr);
7986     int yoff = font->draw_yoffset + (getFontHeight(font_nr) - TILEY) / 2;
7987     int startx2 = startx + xoff;
7988     int starty2 = starty + yoff;
7989
7990     if (DrawingOnBackground(startx2, starty2))
7991       ClearRectangleOnBackground(drawto, startx2, starty2, TILEX, TILEY);
7992
7993     DrawFixedGraphicThruMaskExt(drawto, startx2, starty2,
7994                                 PLAYER_NR_GFX(IMG_PLAYER_1, player_nr), 0);
7995   }
7996
7997   if (font_draw_xoffset_modified)
7998     getFontBitmapInfo(font_nr)->draw_xoffset = font_draw_xoffset_old;
7999 }
8000
8001 static void changeSetupValue(int screen_pos, int setup_info_pos_raw, int dx)
8002 {
8003   int si_pos = (setup_info_pos_raw < 0 ? screen_pos : setup_info_pos_raw);
8004   struct TokenInfo *si = &setup_info[si_pos];
8005
8006   if (si->type & TYPE_BOOLEAN_STYLE)
8007   {
8008     *(boolean *)si->value ^= TRUE;
8009   }
8010   else if (si->type & TYPE_YES_NO_AUTO)
8011   {
8012     *(int *)si->value =
8013       (dx == -1 ?
8014        (*(int *)si->value == AUTO ? TRUE :
8015         *(int *)si->value == TRUE ? FALSE : AUTO) :
8016        (*(int *)si->value == TRUE ? AUTO :
8017         *(int *)si->value == AUTO ? FALSE : TRUE));
8018   }
8019   else if (si->type & TYPE_KEY)
8020   {
8021     Key key;
8022
8023     si->type |= TYPE_QUERY;
8024     drawSetupValue(screen_pos, setup_info_pos_raw);
8025     si->type &= ~TYPE_QUERY;
8026
8027     key = getSetupKey();
8028     if (key != KSYM_UNDEFINED)
8029       *(Key *)si->value = key;
8030   }
8031   else if (si->type & TYPE_PLAYER)
8032   {
8033     int player_nr = *(int *)si->value;
8034
8035     if (dx)
8036       player_nr += dx;
8037     else
8038       player_nr = Request("Choose player", REQ_PLAYER) - 1;
8039
8040     *(int *)si->value = MIN(MAX(0, player_nr), MAX_PLAYERS - 1);
8041   }
8042
8043   drawSetupValue(screen_pos, setup_info_pos_raw);
8044
8045   // fullscreen state may have changed at this point
8046   if (si->value == &setup.fullscreen)
8047     ToggleFullscreenIfNeeded();
8048
8049   // network mode may have changed at this point
8050   if (si->value == &setup.network_mode)
8051     ToggleNetworkModeIfNeeded();
8052
8053   // API server mode may have changed at this point
8054   if (si->value == &setup.use_api_server)
8055     ToggleUseApiServerIfNeeded();
8056
8057   // game speed list may have changed at this point
8058   if (si->value == &setup.game_speed_extended)
8059     ToggleGameSpeedsListIfNeeded();
8060 }
8061
8062 static struct TokenInfo *getSetupInfoFinal(struct TokenInfo *setup_info_orig)
8063 {
8064   static struct TokenInfo *setup_info_final = NULL;
8065   int list_size = 0;
8066   int list_pos = 0;
8067   int i;
8068
8069   // determine maximum list size of target list
8070   while (setup_info_orig[list_size++].type != 0);
8071
8072   // free, allocate and clear memory for target list
8073   checked_free(setup_info_final);
8074   setup_info_final = checked_calloc(list_size * sizeof(struct TokenInfo));
8075
8076   // copy setup info list without setup entries marked as hidden
8077   for (i = 0; setup_info_orig[i].type != 0; i++)
8078   {
8079     // skip setup entries configured to be hidden
8080     if (hideSetupEntry(setup_info_orig[i].value))
8081       continue;
8082
8083     // skip skippable setup entries if screen is lower than usual
8084     if (SCR_FIELDY < SCR_FIELDY_DEFAULT &&
8085         setup_info_orig[i].type == TYPE_SKIPPABLE)
8086       continue;
8087
8088     setup_info_final[list_pos++] = setup_info_orig[i];
8089   }
8090
8091   return setup_info_final;
8092 }
8093
8094 static void DrawSetupScreen_Generic(void)
8095 {
8096   int fade_mask = REDRAW_FIELD;
8097   boolean redraw_all = FALSE;
8098   char *title_string = NULL;
8099   int i;
8100
8101   if (CheckFadeAll())
8102     fade_mask = REDRAW_ALL;
8103
8104   UnmapAllGadgets();
8105   FadeMenuSoundsAndMusic();
8106
8107   FreeScreenGadgets();
8108   CreateScreenGadgets();
8109
8110   if (redraw_mask & REDRAW_ALL)
8111     redraw_all = TRUE;
8112
8113   FadeOut(fade_mask);
8114
8115   // needed if different viewport properties defined for setup screen
8116   ChangeViewportPropertiesIfNeeded();
8117
8118   SetMainBackgroundImage(IMG_BACKGROUND_SETUP);
8119
8120   ClearField();
8121
8122   OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
8123
8124   if (setup_mode == SETUP_MODE_MAIN)
8125   {
8126     setup_info = setup_info_main;
8127     title_string = STR_SETUP_MAIN;
8128   }
8129   else if (setup_mode == SETUP_MODE_GAME)
8130   {
8131     setup_info = setup_info_game;
8132     title_string = STR_SETUP_GAME;
8133   }
8134   else if (setup_mode == SETUP_MODE_ENGINES)
8135   {
8136     setup_info = setup_info_engines;
8137     title_string = STR_SETUP_ENGINES;
8138   }
8139   else if (setup_mode == SETUP_MODE_EDITOR)
8140   {
8141     setup_info = setup_info_editor;
8142     title_string = STR_SETUP_EDITOR;
8143   }
8144   else if (setup_mode == SETUP_MODE_GRAPHICS)
8145   {
8146     setup_info = setup_info_graphics;
8147     title_string = STR_SETUP_GRAPHICS;
8148   }
8149   else if (setup_mode == SETUP_MODE_SOUND)
8150   {
8151     setup_info = setup_info_sound;
8152     title_string = STR_SETUP_SOUND;
8153   }
8154   else if (setup_mode == SETUP_MODE_ARTWORK)
8155   {
8156     setup_info = setup_info_artwork;
8157     title_string = STR_SETUP_ARTWORK;
8158   }
8159   else if (setup_mode == SETUP_MODE_TOUCH)
8160   {
8161     setup_info = setup_info_touch;
8162     title_string = STR_SETUP_TOUCH;
8163
8164     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
8165       setup_info = setup_info_touch_virtual_buttons[GRID_ACTIVE_NR()];
8166     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
8167       setup_info = setup_info_touch_wipe_gestures;
8168   }
8169   else if (setup_mode == SETUP_MODE_SHORTCUTS)
8170   {
8171     setup_info = setup_info_shortcuts;
8172     title_string = STR_SETUP_SHORTCUTS;
8173   }
8174   else if (setup_mode == SETUP_MODE_SHORTCUTS_1)
8175   {
8176     setup_info = setup_info_shortcuts_1;
8177     title_string = STR_SETUP_SHORTCUTS;
8178   }
8179   else if (setup_mode == SETUP_MODE_SHORTCUTS_2)
8180   {
8181     setup_info = setup_info_shortcuts_2;
8182     title_string = STR_SETUP_SHORTCUTS;
8183   }
8184   else if (setup_mode == SETUP_MODE_SHORTCUTS_3)
8185   {
8186     setup_info = setup_info_shortcuts_3;
8187     title_string = STR_SETUP_SHORTCUTS;
8188   }
8189   else if (setup_mode == SETUP_MODE_SHORTCUTS_4)
8190   {
8191     setup_info = setup_info_shortcuts_4;
8192     title_string = STR_SETUP_SHORTCUTS;
8193   }
8194   else if (setup_mode == SETUP_MODE_SHORTCUTS_5)
8195   {
8196     setup_info = setup_info_shortcuts_5;
8197     title_string = STR_SETUP_SHORTCUTS;
8198   }
8199
8200   // use modified setup info without setup entries marked as hidden
8201   setup_info = getSetupInfoFinal(setup_info);
8202
8203   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, title_string);
8204
8205   // determine maximal number of setup entries that can be displayed on screen
8206   num_setup_info = 0;
8207   for (i = 0; setup_info[i].type != 0 && i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
8208     num_setup_info++;
8209
8210   // determine maximal number of setup entries available for this setup screen
8211   max_setup_info = 0;
8212   for (i = 0; setup_info[i].type != 0; i++)
8213     max_setup_info++;
8214
8215   HandleSetupScreen_Generic(0, 0, 0, 0, MB_MENU_INITIALIZE);
8216
8217   MapScreenGadgets(max_setup_info);
8218
8219   if (redraw_all)
8220     redraw_mask = fade_mask = REDRAW_ALL;
8221
8222   DrawMaskedBorder(fade_mask);
8223
8224   FadeIn(fade_mask);
8225 }
8226
8227 void HandleSetupScreen_Generic(int mx, int my, int dx, int dy, int button)
8228 {
8229   menu_info = setup_info;
8230
8231   HandleMenuScreen(mx, my, dx, dy, button,
8232                    setup_mode, num_setup_info, max_setup_info);
8233 }
8234
8235 static void DrawSetupScreen_Input(void)
8236 {
8237   int i;
8238
8239   FadeOut(REDRAW_FIELD);
8240
8241   ClearField();
8242
8243   setup_info = getSetupInfoFinal(setup_info_input);
8244
8245   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, STR_SETUP_INPUT);
8246
8247   for (i = 0; setup_info[i].type != 0; i++)
8248   {
8249     if (setup_info[i].type & (TYPE_ENTER_MENU|TYPE_ENTER_LIST))
8250       initCursor(i, IMG_MENU_BUTTON_ENTER_MENU);
8251     else if (setup_info[i].type & (TYPE_LEAVE_MENU|TYPE_LEAVE_LIST))
8252       initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU);
8253     else if (setup_info[i].type & ~TYPE_SKIP_ENTRY)
8254       initCursor(i, IMG_MENU_BUTTON);
8255
8256     DrawCursorAndText_Setup(i, -1, FALSE);
8257   }
8258
8259   // create gadgets for setup input menu screen
8260   FreeScreenGadgets();
8261   CreateScreenGadgets();
8262
8263   // map gadgets for setup input menu screen
8264   MapScreenMenuGadgets(SCREEN_MASK_INPUT);
8265
8266   HandleSetupScreen_Input(0, 0, 0, 0, MB_MENU_INITIALIZE);
8267
8268   FadeIn(REDRAW_FIELD);
8269 }
8270
8271 static void setJoystickDeviceToNr(char *device_name, int device_nr)
8272 {
8273   if (device_name == NULL)
8274     return;
8275
8276   if (device_nr < 0 || device_nr >= MAX_PLAYERS)
8277     device_nr = 0;
8278
8279   if (strlen(device_name) > 1)
8280   {
8281     char c1 = device_name[strlen(device_name) - 1];
8282     char c2 = device_name[strlen(device_name) - 2];
8283
8284     if (c1 >= '0' && c1 <= '9' && !(c2 >= '0' && c2 <= '9'))
8285       device_name[strlen(device_name) - 1] = '0' + (char)(device_nr % 10);
8286   }
8287   else
8288     strncpy(device_name, getDeviceNameFromJoystickNr(device_nr),
8289             strlen(device_name));
8290 }
8291
8292 static void drawPlayerSetupInputInfo(int player_nr, boolean active)
8293 {
8294   int i;
8295   static struct SetupKeyboardInfo custom_key;
8296   static struct
8297   {
8298     Key *key;
8299     char *text;
8300   } custom[] =
8301   {
8302     { &custom_key.left,  "Axis/Pad Left"  },
8303     { &custom_key.right, "Axis/Pad Right" },
8304     { &custom_key.up,    "Axis/Pad Up"    },
8305     { &custom_key.down,  "Axis/Pad Down"  },
8306     { &custom_key.snap,  "Button 1/A/X"   },
8307     { &custom_key.drop,  "Button 2/B/Y"   }
8308   };
8309   static char *joystick_name[MAX_PLAYERS] =
8310   {
8311     "Joystick1",
8312     "Joystick2",
8313     "Joystick3",
8314     "Joystick4"
8315   };
8316   int font_nr_menu = (active ? FONT_MENU_1_ACTIVE : FONT_MENU_1);
8317   int font_nr_info = FONT_MENU_1;
8318   int font_nr_name = FONT_VALUE_OLD;
8319   int font_nr_on   = FONT_VALUE_1;
8320   int font_nr_off  = FONT_VALUE_OLD;
8321   int pos = 4;
8322
8323   if (SCR_FIELDX < SCR_FIELDX_DEFAULT)
8324   {
8325     font_nr_info = FONT_MENU_2;
8326     font_nr_on   = FONT_VALUE_NARROW;
8327     font_nr_off  = FONT_VALUE_OLD_NARROW;
8328   }
8329
8330   custom_key = setup.input[player_nr].key;
8331
8332   DrawText(mSX + 11 * 32, mSY + 2 * 32, int2str(player_nr + 1, 1),
8333            FONT_INPUT_1_ACTIVE);
8334
8335   ClearRectangleOnBackground(drawto, mSX + 8 * TILEX, mSY + 2 * TILEY,
8336                              TILEX, TILEY);
8337   DrawFixedGraphicThruMaskExt(drawto, mSX + 8 * TILEX, mSY + 2 * TILEY,
8338                               PLAYER_NR_GFX(IMG_PLAYER_1, player_nr), 0);
8339
8340   if (setup.input[player_nr].use_joystick)
8341   {
8342     char *device_name = setup.input[player_nr].joy.device_name;
8343     int joystick_nr = getJoystickNrFromDeviceName(device_name);
8344     boolean joystick_active = CheckJoystickOpened(joystick_nr);
8345     char *text = joystick_name[joystick_nr];
8346     int font_nr = (joystick_active ? font_nr_on : font_nr_off);
8347
8348     DrawText(mSX + 8 * 32, mSY + 3 * 32, text, font_nr);
8349     DrawText(mSX + 32, mSY + 4 * 32, "Configure", font_nr_menu);
8350   }
8351   else
8352   {
8353     DrawText(mSX + 8 * 32, mSY + 3 * 32, "Keyboard ", font_nr_on);
8354     DrawText(mSX + 1 * 32, mSY + 4 * 32, "Customize", font_nr_menu);
8355   }
8356
8357   if (SCR_FIELDY >= SCR_FIELDY_DEFAULT)
8358     DrawText(mSX + 32, mSY + 5 * 32, "Actual Settings:", font_nr_info);
8359   else
8360     pos = 3;
8361
8362   drawCursorXY(1, pos + 0, IMG_MENU_BUTTON_LEFT);
8363   drawCursorXY(1, pos + 1, IMG_MENU_BUTTON_RIGHT);
8364   drawCursorXY(1, pos + 2, IMG_MENU_BUTTON_UP);
8365   drawCursorXY(1, pos + 3, IMG_MENU_BUTTON_DOWN);
8366
8367   DrawText(mSX + 2 * 32, mSY + (pos + 2) * 32, ":", font_nr_name);
8368   DrawText(mSX + 2 * 32, mSY + (pos + 3) * 32, ":", font_nr_name);
8369   DrawText(mSX + 2 * 32, mSY + (pos + 4) * 32, ":", font_nr_name);
8370   DrawText(mSX + 2 * 32, mSY + (pos + 5) * 32, ":", font_nr_name);
8371   DrawText(mSX + 1 * 32, mSY + (pos + 6) * 32, "Snap Field:", font_nr_name);
8372   DrawText(mSX + 1 * 32, mSY + (pos + 8) * 32, "Drop Element:", font_nr_name);
8373
8374   for (i = 0; i < 6; i++)
8375   {
8376     int ypos = (pos + 2) + i + (i > 3 ? i - 3 : 0);
8377
8378     DrawText(mSX + 3 * 32, mSY + ypos * 32,
8379              "              ", font_nr_on);
8380     DrawText(mSX + 3 * 32, mSY + ypos * 32,
8381              (setup.input[player_nr].use_joystick ?
8382               custom[i].text :
8383               getKeyNameFromKey(*custom[i].key)), font_nr_on);
8384   }
8385 }
8386
8387 static int input_player_nr = 0;
8388
8389 static void HandleSetupScreen_Input_Player(int step, int direction)
8390 {
8391   int old_player_nr = input_player_nr;
8392   int new_player_nr;
8393
8394   new_player_nr = old_player_nr + step * direction;
8395   if (new_player_nr < 0)
8396     new_player_nr = 0;
8397   if (new_player_nr > MAX_PLAYERS - 1)
8398     new_player_nr = MAX_PLAYERS - 1;
8399
8400   if (new_player_nr != old_player_nr)
8401   {
8402     input_player_nr = new_player_nr;
8403
8404     drawPlayerSetupInputInfo(input_player_nr, FALSE);
8405   }
8406 }
8407
8408 void HandleSetupScreen_Input(int mx, int my, int dx, int dy, int button)
8409 {
8410   static int choice = 0;
8411   int x = 0;
8412   int y = choice;
8413   int pos_start  = SETUPINPUT_SCREEN_POS_START;
8414   int pos_empty1 = SETUPINPUT_SCREEN_POS_EMPTY1;
8415   int pos_empty2 = SETUPINPUT_SCREEN_POS_EMPTY2;
8416   int pos_end    = SETUPINPUT_SCREEN_POS_END;
8417
8418   if (SCR_FIELDY < SCR_FIELDY_DEFAULT)
8419   {
8420     int i;
8421
8422     for (i = 0; setup_info_input[i].type != 0; i++)
8423     {
8424       // adjust menu structure according to skipped setup entries
8425       if (setup_info_input[i].type == TYPE_SKIPPABLE)
8426       {
8427         pos_empty2--;
8428         pos_end--;
8429       }
8430     }
8431   }
8432
8433   if (button == MB_MENU_INITIALIZE)
8434   {
8435     // input setup menu may have changed size due to graphics configuration
8436     if (choice >= pos_empty1)
8437       choice = pos_end;
8438
8439     drawPlayerSetupInputInfo(input_player_nr, (choice == 2));
8440
8441     DrawCursorAndText_Setup(choice, -1, TRUE);
8442
8443     return;
8444   }
8445   else if (button == MB_MENU_LEAVE)
8446   {
8447     setup_mode = SETUP_MODE_MAIN;
8448     DrawSetupScreen();
8449     InitJoysticks();
8450
8451     return;
8452   }
8453
8454   if (mx || my)         // mouse input
8455   {
8456     x = (mx - mSX) / 32;
8457     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
8458   }
8459   else if (dx || dy)    // keyboard input
8460   {
8461     if (dx && choice == 0)
8462       x = (dx < 0 ? 10 : 12);
8463     else if ((dx && choice == 1) ||
8464              (dx == -1 && choice == pos_end))
8465       button = MB_MENU_CHOICE;
8466     else if (dy)
8467       y = choice + dy;
8468
8469     if (y >= pos_empty1 && y <= pos_empty2)
8470       y = (dy > 0 ? pos_empty2 + 1 : pos_empty1 - 1);
8471   }
8472
8473   if (y == 0 && dx != 0 && button)
8474   {
8475     HandleSetupScreen_Input_Player(1, dx < 0 ? -1 : +1);
8476   }
8477   else if (IN_VIS_FIELD(x, y) &&        // (does not use "IN_VIS_MENU()" yet)
8478            y >= pos_start && y <= pos_end &&
8479            !(y >= pos_empty1 && y <= pos_empty2))
8480   {
8481     if (button)
8482     {
8483       if (y != choice)
8484       {
8485         DrawCursorAndText_Setup(choice, -1, FALSE);
8486         DrawCursorAndText_Setup(y, -1, TRUE);
8487
8488         drawPlayerSetupInputInfo(input_player_nr, (y == 2));
8489
8490         choice = y;
8491       }
8492     }
8493     else
8494     {
8495       if (y == 1)
8496       {
8497         char *device_name = setup.input[input_player_nr].joy.device_name;
8498
8499         if (!setup.input[input_player_nr].use_joystick)
8500         {
8501           int new_device_nr = (dx >= 0 ? 0 : MAX_PLAYERS - 1);
8502
8503           setJoystickDeviceToNr(device_name, new_device_nr);
8504           setup.input[input_player_nr].use_joystick = TRUE;
8505         }
8506         else
8507         {
8508           int device_nr = getJoystickNrFromDeviceName(device_name);
8509           int new_device_nr = device_nr + (dx >= 0 ? +1 : -1);
8510
8511           if (new_device_nr < 0 || new_device_nr >= MAX_PLAYERS)
8512             setup.input[input_player_nr].use_joystick = FALSE;
8513           else
8514             setJoystickDeviceToNr(device_name, new_device_nr);
8515         }
8516
8517         drawPlayerSetupInputInfo(input_player_nr, FALSE);
8518       }
8519       else if (y == 2)
8520       {
8521         if (setup.input[input_player_nr].use_joystick)
8522           ConfigureJoystick(input_player_nr);
8523         else
8524           CustomizeKeyboard(input_player_nr);
8525       }
8526       else if (y == pos_end)
8527       {
8528         InitJoysticks();
8529
8530         FadeSetLeaveMenu();
8531
8532         setup_mode = SETUP_MODE_MAIN;
8533         DrawSetupScreen();
8534       }
8535     }
8536   }
8537 }
8538
8539 static boolean CustomizeKeyboardMain(int player_nr)
8540 {
8541   int i;
8542   int step_nr;
8543   boolean finished = FALSE;
8544   static struct SetupKeyboardInfo custom_key;
8545   static struct
8546   {
8547     Key *key;
8548     char *text;
8549   } customize_step[] =
8550   {
8551     { &custom_key.left,  "Move Left"    },
8552     { &custom_key.right, "Move Right"   },
8553     { &custom_key.up,    "Move Up"      },
8554     { &custom_key.down,  "Move Down"    },
8555     { &custom_key.snap,  "Snap Field"   },
8556     { &custom_key.drop,  "Drop Element" }
8557   };
8558   int font_nr_old = FONT_VALUE_OLD;
8559   int font_nr_new = FONT_VALUE_1;
8560   boolean success = FALSE;
8561
8562   if (SCR_FIELDX < SCR_FIELDX_DEFAULT)
8563   {
8564     font_nr_old = FONT_VALUE_OLD_NARROW;
8565     font_nr_new = FONT_VALUE_NARROW;
8566   }
8567
8568   // read existing key bindings from player setup
8569   custom_key = setup.input[player_nr].key;
8570
8571   FadeSetEnterMenu();
8572   FadeOut(REDRAW_FIELD);
8573
8574   ClearField();
8575
8576   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, "Keyboard Input");
8577
8578   step_nr = 0;
8579   DrawText(mSX, mSY + (2 + 2 * step_nr) * 32,
8580            customize_step[step_nr].text, FONT_INPUT_1_ACTIVE);
8581   DrawText(mSX, mSY + (2 + 2 * step_nr + 1) * 32,
8582            "Key:", FONT_INPUT_1_ACTIVE);
8583   DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
8584            getKeyNameFromKey(*customize_step[step_nr].key), font_nr_old);
8585
8586   FadeIn(REDRAW_FIELD);
8587
8588   while (!finished)
8589   {
8590     Event event;
8591     DelayCounter event_frame_delay = { GAME_FRAME_DELAY };
8592
8593     // reset frame delay counter directly after updating screen
8594     ResetDelayCounter(&event_frame_delay);
8595
8596     while (NextValidEvent(&event))
8597     {
8598       switch (event.type)
8599       {
8600         case EVENT_KEYPRESS:
8601           {
8602             Key key = GetEventKey((KeyEvent *)&event);
8603
8604             // press 'Escape' to abort and keep the old key bindings
8605             if (key == KSYM_Escape)
8606             {
8607               FadeSkipNextFadeIn();
8608
8609               finished = TRUE;
8610
8611               break;
8612             }
8613
8614             // press 'Enter' to keep the existing key binding
8615             if (key == KSYM_Return)
8616               key = *customize_step[step_nr].key;
8617
8618             // check if key already used
8619             for (i = 0; i < step_nr; i++)
8620               if (*customize_step[i].key == key)
8621                 break;
8622             if (i < step_nr)
8623               break;
8624
8625             // got new key binding
8626             *customize_step[step_nr].key = key;
8627             DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
8628                      "             ", font_nr_new);
8629             DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
8630                      getKeyNameFromKey(key), font_nr_new);
8631             step_nr++;
8632
8633             // un-highlight last query
8634             DrawText(mSX, mSY + (2 + 2 * (step_nr - 1)) * 32,
8635                      customize_step[step_nr - 1].text, FONT_MENU_1);
8636             DrawText(mSX, mSY + (2 + 2 * (step_nr - 1) + 1) * 32,
8637                      "Key:", FONT_MENU_1);
8638
8639             // all keys configured
8640             if (step_nr == 6)
8641             {
8642               finished = TRUE;
8643               success = TRUE;
8644
8645               break;
8646             }
8647
8648             // query next key binding
8649             DrawText(mSX, mSY + (2 + 2 * step_nr) * 32,
8650                      customize_step[step_nr].text, FONT_INPUT_1_ACTIVE);
8651             DrawText(mSX, mSY + (2 + 2 * step_nr + 1) * 32,
8652                      "Key:", FONT_INPUT_1_ACTIVE);
8653             DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
8654                      getKeyNameFromKey(*customize_step[step_nr].key),
8655                      font_nr_old);
8656           }
8657           break;
8658
8659         case EVENT_KEYRELEASE:
8660           key_joystick_mapping = 0;
8661           break;
8662
8663         default:
8664           HandleOtherEvents(&event);
8665           break;
8666       }
8667
8668       // do not handle events for longer than standard frame delay period
8669       if (DelayReached(&event_frame_delay))
8670         break;
8671     }
8672
8673     BackToFront();
8674   }
8675
8676   // write new key bindings back to player setup, if successfully finished
8677   if (success)
8678     setup.input[player_nr].key = custom_key;
8679
8680   return success;
8681 }
8682
8683 void CustomizeKeyboard(int player_nr)
8684 {
8685   boolean success = CustomizeKeyboardMain(player_nr);
8686
8687   if (success)
8688   {
8689     int font_nr = FONT_TITLE_1;
8690     int font_height = getFontHeight(font_nr);
8691     int ypos1 = SYSIZE / 2 - font_height * 2;
8692     int ypos2 = SYSIZE / 2 - font_height * 1;
8693     DelayCounter wait_frame_delay = { 2000 };
8694
8695     ResetDelayCounter(&wait_frame_delay);
8696
8697     ClearField();
8698
8699     DrawTextSCentered(ypos1, font_nr, "Keyboard");
8700     DrawTextSCentered(ypos2, font_nr, "configured!");
8701
8702     while (!DelayReached(&wait_frame_delay))
8703       BackToFront();
8704
8705     ClearEventQueue();
8706   }
8707
8708   DrawSetupScreen_Input();
8709 }
8710
8711 // game controller mapping generator by Gabriel Jacobo <gabomdq@gmail.com>
8712
8713 #define MARKER_BUTTON           1
8714 #define MARKER_AXIS_X           2
8715 #define MARKER_AXIS_Y           3
8716
8717 static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick)
8718 {
8719   static boolean bitmaps_initialized = FALSE;
8720   boolean screen_initialized = FALSE;
8721   static Bitmap *controller, *button, *axis_x, *axis_y;
8722   char *name;
8723   boolean success = TRUE;
8724   boolean done = FALSE, next = FALSE;
8725   Event event;
8726   int alpha = 200, alpha_step = -1;
8727   int alpha_ticks = 0;
8728   char mapping[4096], temp[256];
8729   int font_name = MENU_SETUP_FONT_TITLE;
8730   int font_info = MENU_SETUP_FONT_TEXT;
8731   int spacing_name = menu.line_spacing_setup[SETUP_MODE_INPUT];
8732   int spacing_line = menu.line_spacing_setup[SETUP_MODE_INPUT];
8733   int spacing_para = menu.paragraph_spacing_setup[SETUP_MODE_INPUT];
8734   int ystep_name = getMenuTextStep(spacing_name, font_name);
8735   int ystep_line = getMenuTextStep(spacing_line, font_info);
8736   int ystep_para = getMenuTextStep(spacing_para, font_info);
8737   int i, j;
8738
8739   struct
8740   {
8741     int x, y;
8742     int marker;
8743     char *field;
8744     int axis, button, hat, hat_value;
8745     char mapping[4096];
8746   }
8747   *step, *prev_step, steps[] =
8748   {
8749     { 356, 155, MARKER_BUTTON, "a",             },
8750     { 396, 122, MARKER_BUTTON, "b",             },
8751     { 320, 125, MARKER_BUTTON, "x",             },
8752     { 358,  95, MARKER_BUTTON, "y",             },
8753     { 162, 125, MARKER_BUTTON, "back",          },
8754     { 216, 125, MARKER_BUTTON, "guide",         },
8755     { 271, 125, MARKER_BUTTON, "start",         },
8756     { 110, 200, MARKER_BUTTON, "dpleft",        },
8757     { 146, 228, MARKER_BUTTON, "dpdown",        },
8758     { 178, 200, MARKER_BUTTON, "dpright",       },
8759     { 146, 172, MARKER_BUTTON, "dpup",          },
8760     {  50,  40, MARKER_BUTTON, "leftshoulder",  },
8761     {  88, -10, MARKER_AXIS_Y, "lefttrigger",   },
8762     { 382,  40, MARKER_BUTTON, "rightshoulder", },
8763     { 346, -10, MARKER_AXIS_Y, "righttrigger",  },
8764     {  73, 141, MARKER_BUTTON, "leftstick",     },
8765     { 282, 210, MARKER_BUTTON, "rightstick",    },
8766     {  73, 141, MARKER_AXIS_X, "leftx",         },
8767     {  73, 141, MARKER_AXIS_Y, "lefty",         },
8768     { 282, 210, MARKER_AXIS_X, "rightx",        },
8769     { 282, 210, MARKER_AXIS_Y, "righty",        },
8770   };
8771
8772   if (!bitmaps_initialized)
8773   {
8774     controller = LoadCustomImage("joystick/controller.png");
8775     button     = LoadCustomImage("joystick/button.png");
8776     axis_x     = LoadCustomImage("joystick/axis_x.png");
8777     axis_y     = LoadCustomImage("joystick/axis_y.png");
8778
8779     bitmaps_initialized = TRUE;
8780   }
8781
8782   name = getFormattedJoystickName(SDL_JoystickName(joystick));
8783
8784 #if DEBUG_JOYSTICKS
8785   // print info about the joystick we are watching
8786   Debug("joystick", "watching joystick %d: (%s)",
8787         SDL_JoystickInstanceID(joystick), name);
8788   Debug("joystick", "joystick has %d axes, %d hats, %d balls, and %d buttons",
8789         SDL_JoystickNumAxes(joystick), SDL_JoystickNumHats(joystick),
8790         SDL_JoystickNumBalls(joystick), SDL_JoystickNumButtons(joystick));
8791 #endif
8792
8793   // initialize mapping with GUID and name
8794   SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joystick), temp, sizeof(temp));
8795
8796   snprintf(mapping, sizeof(mapping), "%s,%s,platform:%s,",
8797            temp, name, SDL_GetPlatform());
8798
8799   // loop through all steps (buttons and axes), getting joystick events
8800   for (i = 0; i < SDL_arraysize(steps) && !done;)
8801   {
8802     Bitmap *marker = button;    // initialize with reliable default value
8803
8804     step = &steps[i];
8805     strcpy(step->mapping, mapping);
8806     step->axis = -1;
8807     step->button = -1;
8808     step->hat = -1;
8809     step->hat_value = -1;
8810
8811     marker = (step->marker == MARKER_BUTTON ? button :
8812               step->marker == MARKER_AXIS_X ? axis_x :
8813               step->marker == MARKER_AXIS_Y ? axis_y : marker);
8814
8815     next = FALSE;
8816
8817     while (!done && !next)
8818     {
8819       alpha += alpha_step * (int)(SDL_GetTicks() - alpha_ticks) / 5;
8820       alpha_ticks = SDL_GetTicks();
8821
8822       if (alpha >= 255)
8823       {
8824         alpha = 255;
8825         alpha_step = -1;
8826       }
8827       else if (alpha < 128)
8828       {
8829         alpha = 127;
8830         alpha_step = 1;
8831       }
8832
8833       int controller_x = SX + (SXSIZE - controller->width) / 2;
8834       int controller_y = SY + ystep_line;
8835
8836       int marker_x = controller_x + step->x;
8837       int marker_y = controller_y + step->y;
8838
8839       int ystart1 = mSY - 2 * SY + controller_y + controller->height;
8840       int ystart2 = ystart1 + ystep_name + ystep_line;
8841
8842       ClearField();
8843
8844       DrawTextSCentered(ystart1, font_name, name);
8845
8846       DrawTextSCentered(ystart2, font_info,
8847                         "Press buttons and move axes on");
8848       ystart2 += ystep_line;
8849       DrawTextSCentered(ystart2, font_info,
8850                         "your controller when indicated.");
8851       ystart2 += ystep_line;
8852       DrawTextSCentered(ystart2, font_info,
8853                         "(Your controller may look different.)");
8854       ystart2 += ystep_para;
8855
8856 #if defined(PLATFORM_ANDROID)
8857       DrawTextSCentered(ystart2, font_info,
8858                         "To correct a mistake,");
8859       ystart2 += ystep_line;
8860       DrawTextSCentered(ystart2, font_info,
8861                         "press the 'back' button.");
8862       ystart2 += ystep_line;
8863       DrawTextSCentered(ystart2, font_info,
8864                         "To skip a button or axis,");
8865       ystart2 += ystep_line;
8866       DrawTextSCentered(ystart2, font_info,
8867                         "press the 'menu' button.");
8868 #else
8869       DrawTextSCentered(ystart2, font_info,
8870                         "To correct a mistake,");
8871       ystart2 += ystep_line;
8872       DrawTextSCentered(ystart2, font_info,
8873                         "press the 'backspace' key.");
8874       ystart2 += ystep_line;
8875       DrawTextSCentered(ystart2, font_info,
8876                         "To skip a button or axis,");
8877       ystart2 += ystep_line;
8878       DrawTextSCentered(ystart2, font_info,
8879                         "press the 'return' key.");
8880       ystart2 += ystep_line;
8881       DrawTextSCentered(ystart2, font_info,
8882                         "To exit, press the 'escape' key.");
8883 #endif
8884
8885       BlitBitmapMasked(controller, drawto, 0, 0,
8886                        controller->width, controller->height,
8887                        controller_x, controller_y);
8888
8889       SDL_SetSurfaceBlendMode(marker->surface_masked, SDL_BLENDMODE_BLEND);
8890       SDL_SetSurfaceAlphaMod(marker->surface_masked, alpha);
8891
8892       BlitBitmapMasked(marker, drawto, 0, 0,
8893                        marker->width, marker->height,
8894                        marker_x, marker_y);
8895
8896       if (!screen_initialized)
8897         FadeIn(REDRAW_FIELD);
8898       else
8899         BackToFront();
8900
8901       screen_initialized = TRUE;
8902
8903       DelayCounter event_frame_delay = { GAME_FRAME_DELAY };
8904
8905       // reset frame delay counter directly after updating screen
8906       ResetDelayCounter(&event_frame_delay);
8907
8908       while (NextValidEvent(&event))
8909       {
8910         switch (event.type)
8911         {
8912           case SDL_JOYAXISMOTION:
8913             if (event.jaxis.value > 20000 ||
8914                 event.jaxis.value < -20000)
8915             {
8916               for (j = 0; j < i; j++)
8917                 if (steps[j].axis == event.jaxis.axis)
8918                   break;
8919
8920               if (j == i)
8921               {
8922                 if (step->marker != MARKER_AXIS_X &&
8923                     step->marker != MARKER_AXIS_Y)
8924                   break;
8925
8926                 step->axis = event.jaxis.axis;
8927                 strcat(mapping, step->field);
8928                 snprintf(temp, sizeof(temp), ":a%u,", event.jaxis.axis);
8929                 strcat(mapping, temp);
8930                 i++;
8931                 next = TRUE;
8932               }
8933             }
8934
8935             break;
8936
8937           case SDL_JOYHATMOTION:
8938             // ignore centering; we're probably just coming back
8939             // to the center from the previous item we set
8940             if (event.jhat.value == SDL_HAT_CENTERED)
8941               break;
8942
8943             for (j = 0; j < i; j++)
8944               if (steps[j].hat == event.jhat.hat &&
8945                   steps[j].hat_value == event.jhat.value)
8946                 break;
8947
8948             if (j == i)
8949             {
8950               step->hat = event.jhat.hat;
8951               step->hat_value = event.jhat.value;
8952               strcat(mapping, step->field);
8953               snprintf(temp, sizeof(temp), ":h%u.%u,",
8954                        event.jhat.hat, event.jhat.value );
8955               strcat(mapping, temp);
8956               i++;
8957               next = TRUE;
8958             }
8959
8960             break;
8961
8962           case SDL_JOYBALLMOTION:
8963             break;
8964
8965           case SDL_JOYBUTTONUP:
8966             for (j = 0; j < i; j++)
8967               if (steps[j].button == event.jbutton.button)
8968                 break;
8969
8970             if (j == i)
8971             {
8972               step->button = event.jbutton.button;
8973               strcat(mapping, step->field);
8974               snprintf(temp, sizeof(temp), ":b%u,", event.jbutton.button);
8975               strcat(mapping, temp);
8976               i++;
8977               next = TRUE;
8978             }
8979
8980             break;
8981
8982           case SDL_FINGERDOWN:
8983           case SDL_MOUSEBUTTONDOWN:
8984             // skip this step
8985             i++;
8986             next = TRUE;
8987
8988             break;
8989
8990           case SDL_KEYDOWN:
8991             if (event.key.keysym.sym == KSYM_BackSpace ||
8992                 event.key.keysym.sym == KSYM_Back)
8993             {
8994               if (i == 0)
8995               {
8996                 // leave screen
8997                 success = FALSE;
8998                 done = TRUE;
8999
9000                 break;
9001               }
9002
9003               // undo this step
9004               prev_step = &steps[i - 1];
9005               strcpy(mapping, prev_step->mapping);
9006               i--;
9007               next = TRUE;
9008
9009               break;
9010             }
9011
9012             if (event.key.keysym.sym == KSYM_space ||
9013                 event.key.keysym.sym == KSYM_Return ||
9014                 event.key.keysym.sym == KSYM_Menu)
9015             {
9016               // skip this step
9017               i++;
9018               next = TRUE;
9019
9020               break;
9021             }
9022
9023             if (event.key.keysym.sym == KSYM_Escape)
9024             {
9025               // leave screen
9026               success = FALSE;
9027               done = TRUE;
9028             }
9029
9030             break;
9031
9032           case SDL_QUIT:
9033             program.exit_function(0);
9034             break;
9035
9036           default:
9037             break;
9038         }
9039
9040         // do not handle events for longer than standard frame delay period
9041         if (DelayReached(&event_frame_delay))
9042           break;
9043       }
9044     }
9045   }
9046
9047   if (success)
9048   {
9049 #if DEBUG_JOYSTICKS
9050     Debug("joystick", "New game controller mapping:\n\n%s\n\n", mapping);
9051 #endif
9052
9053     // activate mapping for this game
9054     SDL_GameControllerAddMapping(mapping);
9055
9056     // save mapping to personal mappings
9057     SaveSetup_AddGameControllerMapping(mapping);
9058   }
9059
9060   // wait until the last pending event was removed from event queue
9061   while (NextValidEvent(&event));
9062
9063   return success;
9064 }
9065
9066 static int ConfigureJoystickMain(int player_nr)
9067 {
9068   char *device_name = setup.input[player_nr].joy.device_name;
9069   int joystick_nr = getJoystickNrFromDeviceName(device_name);
9070   boolean joystick_active = CheckJoystickOpened(joystick_nr);
9071   int success = FALSE;
9072   int i;
9073
9074   if (joystick.status == JOYSTICK_NOT_AVAILABLE)
9075     return JOYSTICK_NOT_AVAILABLE;
9076
9077   if (!joystick_active || !setup.input[player_nr].use_joystick)
9078     return JOYSTICK_NOT_AVAILABLE;
9079
9080   FadeSetEnterMenu();
9081   FadeOut(REDRAW_FIELD);
9082
9083   // close all joystick devices (potentially opened as game controllers)
9084   for (i = 0; i < SDL_NumJoysticks(); i++)
9085     SDLCloseJoystick(i);
9086
9087   // open joystick device as plain joystick to configure as game controller
9088   SDL_Joystick *joystick = SDL_JoystickOpen(joystick_nr);
9089
9090   // as the joystick was successfully opened before, this should not happen
9091   if (joystick == NULL)
9092     return FALSE;
9093
9094   // create new game controller mapping (buttons and axes) for joystick device
9095   success = ConfigureJoystickMapButtonsAndAxes(joystick);
9096
9097   // close joystick (and maybe re-open as configured game controller later)
9098   SDL_JoystickClose(joystick);
9099
9100   // re-open all joystick devices (potentially as game controllers)
9101   for (i = 0; i < SDL_NumJoysticks(); i++)
9102     SDLOpenJoystick(i);
9103
9104   // clear all joystick input actions for all joystick devices
9105   SDLClearJoystickState();
9106
9107   return (success ? JOYSTICK_CONFIGURED : JOYSTICK_NOT_CONFIGURED);
9108 }
9109
9110 void ConfigureJoystick(int player_nr)
9111 {
9112   boolean state = ConfigureJoystickMain(player_nr);
9113
9114   if (state != JOYSTICK_NOT_CONFIGURED)
9115   {
9116     boolean success = (state == JOYSTICK_CONFIGURED);
9117     char message1[MAX_OUTPUT_LINESIZE + 1];
9118     char *message2 = (success ? "configured!" : "not available!");
9119     char *device_name = setup.input[player_nr].joy.device_name;
9120     int nr = getJoystickNrFromDeviceName(device_name) + 1;
9121     int font_nr = FONT_TITLE_1;
9122     int font_height = getFontHeight(font_nr);
9123     int ypos1 = SYSIZE / 2 - font_height * 2;
9124     int ypos2 = SYSIZE / 2 - font_height * 1;
9125     DelayCounter wait_frame_delay = { 2000 };
9126
9127     ResetDelayCounter(&wait_frame_delay);
9128
9129     ClearField();
9130
9131     sprintf(message1, "Joystick %d", nr);
9132
9133     DrawTextSCentered(ypos1, font_nr, message1);
9134     DrawTextSCentered(ypos2, font_nr, message2);
9135
9136     while (!DelayReached(&wait_frame_delay))
9137       BackToFront();
9138
9139     ClearEventQueue();
9140   }
9141
9142   DrawSetupScreen_Input();
9143 }
9144
9145 static void MapScreenMenuGadgets_OverlayTouchButtons(int y)
9146 {
9147   if (y < video.screen_height / 3)
9148   {
9149     // remap touch gadgets to access upper part of the screen
9150     UnmapScreenMenuGadgets(SCREEN_MASK_TOUCH);
9151     MapScreenMenuGadgets(SCREEN_MASK_TOUCH2);
9152   }
9153   else if (y > 2 * video.screen_height / 3)
9154   {
9155     // remap touch gadgets to access lower part of the screen
9156     MapScreenMenuGadgets(SCREEN_MASK_TOUCH);
9157     UnmapScreenMenuGadgets(SCREEN_MASK_TOUCH2);
9158   }
9159 }
9160
9161 static boolean ConfigureVirtualButtonsMain(void)
9162 {
9163   static char *customize_step_text[] =
9164   {
9165     "Move Left",
9166     "Move Right",
9167     "Move Up",
9168     "Move Down",
9169     "Snap Field",
9170     "Drop Element"
9171   };
9172   char grid_button[] =
9173   {
9174     CHAR_GRID_BUTTON_LEFT,
9175     CHAR_GRID_BUTTON_RIGHT,
9176     CHAR_GRID_BUTTON_UP,
9177     CHAR_GRID_BUTTON_DOWN,
9178     CHAR_GRID_BUTTON_SNAP,
9179     CHAR_GRID_BUTTON_DROP
9180   };
9181   enum
9182   {
9183     ACTION_NONE,
9184     ACTION_ESCAPE,
9185     ACTION_BACK,
9186     ACTION_NEXT
9187   };
9188   int font_nr = FONT_INPUT_1_ACTIVE;
9189   int font_height = getFontHeight(font_nr);
9190   int ypos1 = SYSIZE / 2 - font_height * 2;
9191   int ypos2 = SYSIZE / 2 - font_height * 1;
9192   boolean success = FALSE;
9193   boolean finished = FALSE;
9194   int step_nr = 0;
9195   char grid_button_draw = CHAR_GRID_BUTTON_NONE;
9196   char grid_button_old[MAX_GRID_XSIZE][MAX_GRID_YSIZE];
9197   char grid_button_tmp[MAX_GRID_XSIZE][MAX_GRID_YSIZE];
9198   boolean set_grid_button = FALSE;
9199   int nr = GRID_ACTIVE_NR();
9200   int x, y;
9201
9202   for (x = 0; x < MAX_GRID_XSIZE; x++)
9203     for (y = 0; y < MAX_GRID_YSIZE; y++)
9204       grid_button_old[x][y] = grid_button_tmp[x][y] = overlay.grid_button[x][y];
9205
9206   overlay.grid_button_highlight = grid_button[step_nr];
9207
9208   UnmapAllGadgets();
9209
9210   FadeSetEnterMenu();
9211   FadeOut(REDRAW_FIELD);
9212
9213   ClearField();
9214
9215   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, "Virtual Buttons");
9216   DrawTextSCentered(ypos1, font_nr, "Select tiles to");
9217   DrawTextSCentered(ypos2, font_nr, customize_step_text[step_nr]);
9218
9219   FadeIn(REDRAW_FIELD);
9220
9221   SetOverlayShowGrid(TRUE);
9222
9223   // map gadgets for setup touch buttons menu screen
9224   MapScreenMenuGadgets(SCREEN_MASK_TOUCH);
9225
9226   while (!finished)
9227   {
9228     Event event;
9229
9230     while (NextValidEvent(&event))
9231     {
9232       int action = ACTION_NONE;
9233
9234       // ---------- handle events and set the resulting action ----------
9235
9236       switch (event.type)
9237       {
9238         case EVENT_USER:
9239           {
9240             UserEvent *user = (UserEvent *)&event;
9241             int id = user->value1;
9242
9243             action = (id == SCREEN_CTRL_ID_TOUCH_PREV_PAGE ||
9244                       id == SCREEN_CTRL_ID_TOUCH_PREV_PAGE2 ? ACTION_BACK :
9245                       id == SCREEN_CTRL_ID_TOUCH_NEXT_PAGE ||
9246                       id == SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2 ? ACTION_NEXT :
9247                       ACTION_NONE);
9248           }
9249           break;
9250
9251         case EVENT_KEYPRESS:
9252           {
9253             Key key = GetEventKey((KeyEvent *)&event);
9254
9255             action = (key == KSYM_Escape ?      ACTION_ESCAPE :
9256                       key == KSYM_BackSpace ||
9257                       key == KSYM_Back ?        ACTION_BACK :
9258                       key == KSYM_Return ||
9259                       key == KSYM_Menu ||
9260                       key == KSYM_space ?       ACTION_NEXT :
9261                       ACTION_NONE);
9262           }
9263           break;
9264
9265         case EVENT_KEYRELEASE:
9266           key_joystick_mapping = 0;
9267           break;
9268
9269         case EVENT_BUTTONPRESS:
9270         case EVENT_BUTTONRELEASE:
9271           {
9272             ButtonEvent *button = (ButtonEvent *)&event;
9273
9274             motion_status = FALSE;
9275
9276             if (button->type == EVENT_BUTTONPRESS)
9277               button_status = button->button;
9278             else
9279               button_status = MB_RELEASED;
9280
9281             if (HandleGadgets(button->x, button->y, button_status))
9282             {
9283               // do not handle this button event anymore
9284               break;
9285             }
9286
9287             button->x += video.screen_xoffset;
9288             button->y += video.screen_yoffset;
9289
9290             x = button->x * overlay.grid_xsize / video.screen_width;
9291             y = button->y * overlay.grid_ysize / video.screen_height;
9292
9293             if (button->type == EVENT_BUTTONPRESS)
9294             {
9295               grid_button_draw =
9296                 (overlay.grid_button[x][y] != grid_button[step_nr] ?
9297                  grid_button[step_nr] : CHAR_GRID_BUTTON_NONE);
9298
9299               set_grid_button = TRUE;
9300             }
9301
9302             MapScreenMenuGadgets_OverlayTouchButtons(button->y);
9303           }
9304           break;
9305
9306         case EVENT_MOTIONNOTIFY:
9307           {
9308             MotionEvent *motion = (MotionEvent *)&event;
9309
9310             motion_status = TRUE;
9311
9312             if (HandleGadgets(motion->x, motion->y, button_status))
9313             {
9314               // do not handle this button event anymore
9315               break;
9316             }
9317
9318             motion->x += video.screen_xoffset;
9319             motion->y += video.screen_yoffset;
9320
9321             x = motion->x * overlay.grid_xsize / video.screen_width;
9322             y = motion->y * overlay.grid_ysize / video.screen_height;
9323
9324             set_grid_button = TRUE;
9325
9326             MapScreenMenuGadgets_OverlayTouchButtons(motion->y);
9327           }
9328           break;
9329
9330         case SDL_WINDOWEVENT:
9331           HandleWindowEvent((WindowEvent *) &event);
9332
9333           // check if device has been rotated
9334           if (nr != GRID_ACTIVE_NR())
9335           {
9336             nr = GRID_ACTIVE_NR();
9337
9338             for (x = 0; x < MAX_GRID_XSIZE; x++)
9339               for (y = 0; y < MAX_GRID_YSIZE; y++)
9340                 grid_button_old[x][y] = grid_button_tmp[x][y] =
9341                   overlay.grid_button[x][y];
9342           }
9343
9344           break;
9345
9346         case SDL_APP_WILLENTERBACKGROUND:
9347         case SDL_APP_DIDENTERBACKGROUND:
9348         case SDL_APP_WILLENTERFOREGROUND:
9349         case SDL_APP_DIDENTERFOREGROUND:
9350           HandlePauseResumeEvent((PauseResumeEvent *) &event);
9351           break;
9352
9353         default:
9354           HandleOtherEvents(&event);
9355           break;
9356       }
9357
9358       // ---------- perform action set by handling events ----------
9359
9360       if (action == ACTION_ESCAPE)
9361       {
9362         // abort and restore the old key bindings
9363
9364         for (x = 0; x < MAX_GRID_XSIZE; x++)
9365           for (y = 0; y < MAX_GRID_YSIZE; y++)
9366             overlay.grid_button[x][y] = grid_button_old[x][y];
9367
9368         FadeSkipNextFadeIn();
9369
9370         finished = TRUE;
9371       }
9372       else if (action == ACTION_BACK)
9373       {
9374         // keep the configured key bindings and go to previous page
9375
9376         step_nr--;
9377
9378         if (step_nr < 0)
9379         {
9380           FadeSkipNextFadeIn();
9381
9382           finished = TRUE;
9383         }
9384       }
9385       else if (action == ACTION_NEXT)
9386       {
9387         // keep the configured key bindings and go to next page
9388
9389         step_nr++;
9390
9391         // all virtual buttons configured
9392         if (step_nr == 6)
9393         {
9394           finished = TRUE;
9395           success = TRUE;
9396         }
9397       }
9398
9399       if (action != ACTION_NONE && !finished)
9400       {
9401         for (x = 0; x < MAX_GRID_XSIZE; x++)
9402           for (y = 0; y < MAX_GRID_YSIZE; y++)
9403             grid_button_tmp[x][y] = overlay.grid_button[x][y];
9404
9405         overlay.grid_button_highlight = grid_button[step_nr];
9406
9407         // configure next virtual button
9408
9409         ClearField();
9410
9411         DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, "Virtual Buttons");
9412         DrawTextSCentered(ypos1, font_nr, "Select tiles to");
9413         DrawTextSCentered(ypos2, font_nr, customize_step_text[step_nr]);
9414       }
9415
9416       if (set_grid_button)
9417       {
9418         overlay.grid_button[x][y] =
9419           (grid_button_draw != CHAR_GRID_BUTTON_NONE ? grid_button_draw :
9420            grid_button_tmp[x][y] == grid_button[step_nr] ? CHAR_GRID_BUTTON_NONE :
9421            grid_button_tmp[x][y]);
9422
9423         set_grid_button = FALSE;
9424       }
9425     }
9426
9427     BackToFront();
9428   }
9429
9430   for (x = 0; x < MAX_GRID_XSIZE; x++)
9431     for (y = 0; y < MAX_GRID_YSIZE; y++)
9432       setup.touch.grid_button[nr][x][y] = overlay.grid_button[x][y];
9433
9434   overlay.grid_button_highlight = CHAR_GRID_BUTTON_NONE;
9435
9436   SetOverlayShowGrid(FALSE);
9437
9438   return success;
9439 }
9440
9441 void ConfigureVirtualButtons(void)
9442 {
9443   boolean success = ConfigureVirtualButtonsMain();
9444
9445   UnmapScreenMenuGadgets(SCREEN_MASK_TOUCH |
9446                          SCREEN_MASK_TOUCH2);
9447
9448   if (success)
9449   {
9450     int font_nr = FONT_TITLE_1;
9451     int font_height = getFontHeight(font_nr);
9452     int ypos1 = SYSIZE / 2 - font_height * 2;
9453     int ypos2 = SYSIZE / 2 - font_height * 1;
9454     DelayCounter wait_frame_delay = { 2000 };
9455
9456     ResetDelayCounter(&wait_frame_delay);
9457
9458     ClearField();
9459
9460     DrawTextSCentered(ypos1, font_nr, "Virtual buttons");
9461     DrawTextSCentered(ypos2, font_nr, "configured!");
9462
9463     while (!DelayReached(&wait_frame_delay))
9464       BackToFront();
9465
9466     ClearEventQueue();
9467   }
9468 }
9469
9470 void DrawSetupScreen(void)
9471 {
9472   align_xoffset = 0;
9473   align_yoffset = 0;
9474
9475   if (setup_mode == SETUP_MODE_INPUT)
9476     DrawSetupScreen_Input();
9477   else if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE)
9478     DrawChooseTree(&scores_type_current);
9479   else if (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED)
9480     DrawChooseTree(&game_speed_current);
9481   else if (setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY)
9482     DrawChooseTree(&scroll_delay_current);
9483   else if (setup_mode == SETUP_MODE_CHOOSE_SNAPSHOT_MODE)
9484     DrawChooseTree(&snapshot_mode_current);
9485   else if (setup_mode == SETUP_MODE_CHOOSE_WINDOW_SIZE)
9486     DrawChooseTree(&window_size_current);
9487   else if (setup_mode == SETUP_MODE_CHOOSE_SCALING_TYPE)
9488     DrawChooseTree(&scaling_type_current);
9489   else if (setup_mode == SETUP_MODE_CHOOSE_RENDERING)
9490     DrawChooseTree(&rendering_mode_current);
9491   else if (setup_mode == SETUP_MODE_CHOOSE_VSYNC)
9492     DrawChooseTree(&vsync_mode_current);
9493   else if (setup_mode == SETUP_MODE_CHOOSE_GRAPHICS)
9494     DrawChooseTree(&artwork.gfx_current);
9495   else if (setup_mode == SETUP_MODE_CHOOSE_SOUNDS)
9496     DrawChooseTree(&artwork.snd_current);
9497   else if (setup_mode == SETUP_MODE_CHOOSE_MUSIC)
9498     DrawChooseTree(&artwork.mus_current);
9499   else if (setup_mode == SETUP_MODE_CHOOSE_VOLUME_SIMPLE)
9500     DrawChooseTree(&volume_simple_current);
9501   else if (setup_mode == SETUP_MODE_CHOOSE_VOLUME_LOOPS)
9502     DrawChooseTree(&volume_loops_current);
9503   else if (setup_mode == SETUP_MODE_CHOOSE_VOLUME_MUSIC)
9504     DrawChooseTree(&volume_music_current);
9505   else if (setup_mode == SETUP_MODE_CHOOSE_TOUCH_CONTROL)
9506     DrawChooseTree(&touch_control_current);
9507   else if (setup_mode == SETUP_MODE_CHOOSE_MOVE_DISTANCE)
9508     DrawChooseTree(&move_distance_current);
9509   else if (setup_mode == SETUP_MODE_CHOOSE_DROP_DISTANCE)
9510     DrawChooseTree(&drop_distance_current);
9511   else if (setup_mode == SETUP_MODE_CHOOSE_TRANSPARENCY)
9512     DrawChooseTree(&transparency_current);
9513   else if (setup_mode == SETUP_MODE_CHOOSE_GRID_XSIZE_0)
9514     DrawChooseTree(&grid_size_current[0][0]);
9515   else if (setup_mode == SETUP_MODE_CHOOSE_GRID_YSIZE_0)
9516     DrawChooseTree(&grid_size_current[0][1]);
9517   else if (setup_mode == SETUP_MODE_CHOOSE_GRID_XSIZE_1)
9518     DrawChooseTree(&grid_size_current[1][0]);
9519   else if (setup_mode == SETUP_MODE_CHOOSE_GRID_YSIZE_1)
9520     DrawChooseTree(&grid_size_current[1][1]);
9521   else
9522     DrawSetupScreen_Generic();
9523
9524   PlayMenuSoundsAndMusic();
9525 }
9526
9527 void RedrawSetupScreenAfterFullscreenToggle(void)
9528 {
9529   if (setup_mode == SETUP_MODE_GRAPHICS ||
9530       setup_mode == SETUP_MODE_CHOOSE_WINDOW_SIZE)
9531   {
9532     // update list selection from "setup.window_scaling_percent"
9533     execSetupGraphics_setWindowSizes(TRUE);
9534
9535     DrawSetupScreen();
9536   }
9537 }
9538
9539 void RedrawSetupScreenAfterScreenRotation(int nr)
9540 {
9541   int x, y;
9542
9543   if (setup_mode == SETUP_MODE_TOUCH)
9544   {
9545     // update virtual button settings (depending on screen orientation)
9546     DrawSetupScreen();
9547   }
9548   else if (setup_mode == SETUP_MODE_CONFIG_VIRT_BUTTONS)
9549   {
9550     // save already configured virtual buttons
9551     for (x = 0; x < MAX_GRID_XSIZE; x++)
9552       for (y = 0; y < MAX_GRID_YSIZE; y++)
9553         setup.touch.grid_button[nr][x][y] = overlay.grid_button[x][y];
9554   }
9555 }
9556
9557 void HandleSetupScreen(int mx, int my, int dx, int dy, int button)
9558 {
9559   if (setup_mode == SETUP_MODE_INPUT)
9560     HandleSetupScreen_Input(mx, my, dx, dy, button);
9561   else if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE)
9562     HandleChooseTree(mx, my, dx, dy, button, &scores_type_current);
9563   else if (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED)
9564     HandleChooseTree(mx, my, dx, dy, button, &game_speed_current);
9565   else if (setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY)
9566     HandleChooseTree(mx, my, dx, dy, button, &scroll_delay_current);
9567   else if (setup_mode == SETUP_MODE_CHOOSE_SNAPSHOT_MODE)
9568     HandleChooseTree(mx, my, dx, dy, button, &snapshot_mode_current);
9569   else if (setup_mode == SETUP_MODE_CHOOSE_WINDOW_SIZE)
9570     HandleChooseTree(mx, my, dx, dy, button, &window_size_current);
9571   else if (setup_mode == SETUP_MODE_CHOOSE_SCALING_TYPE)
9572     HandleChooseTree(mx, my, dx, dy, button, &scaling_type_current);
9573   else if (setup_mode == SETUP_MODE_CHOOSE_RENDERING)
9574     HandleChooseTree(mx, my, dx, dy, button, &rendering_mode_current);
9575   else if (setup_mode == SETUP_MODE_CHOOSE_VSYNC)
9576     HandleChooseTree(mx, my, dx, dy, button, &vsync_mode_current);
9577   else if (setup_mode == SETUP_MODE_CHOOSE_GRAPHICS)
9578     HandleChooseTree(mx, my, dx, dy, button, &artwork.gfx_current);
9579   else if (setup_mode == SETUP_MODE_CHOOSE_SOUNDS)
9580     HandleChooseTree(mx, my, dx, dy, button, &artwork.snd_current);
9581   else if (setup_mode == SETUP_MODE_CHOOSE_MUSIC)
9582     HandleChooseTree(mx, my, dx, dy, button, &artwork.mus_current);
9583   else if (setup_mode == SETUP_MODE_CHOOSE_VOLUME_SIMPLE)
9584     HandleChooseTree(mx, my, dx, dy, button, &volume_simple_current);
9585   else if (setup_mode == SETUP_MODE_CHOOSE_VOLUME_LOOPS)
9586     HandleChooseTree(mx, my, dx, dy, button, &volume_loops_current);
9587   else if (setup_mode == SETUP_MODE_CHOOSE_VOLUME_MUSIC)
9588     HandleChooseTree(mx, my, dx, dy, button, &volume_music_current);
9589   else if (setup_mode == SETUP_MODE_CHOOSE_TOUCH_CONTROL)
9590     HandleChooseTree(mx, my, dx, dy, button, &touch_control_current);
9591   else if (setup_mode == SETUP_MODE_CHOOSE_MOVE_DISTANCE)
9592     HandleChooseTree(mx, my, dx, dy, button, &move_distance_current);
9593   else if (setup_mode == SETUP_MODE_CHOOSE_DROP_DISTANCE)
9594     HandleChooseTree(mx, my, dx, dy, button, &drop_distance_current);
9595   else if (setup_mode == SETUP_MODE_CHOOSE_TRANSPARENCY)
9596     HandleChooseTree(mx, my, dx, dy, button, &transparency_current);
9597   else if (setup_mode == SETUP_MODE_CHOOSE_GRID_XSIZE_0)
9598     HandleChooseTree(mx, my, dx, dy, button, &grid_size_current[0][0]);
9599   else if (setup_mode == SETUP_MODE_CHOOSE_GRID_YSIZE_0)
9600     HandleChooseTree(mx, my, dx, dy, button, &grid_size_current[0][1]);
9601   else if (setup_mode == SETUP_MODE_CHOOSE_GRID_XSIZE_1)
9602     HandleChooseTree(mx, my, dx, dy, button, &grid_size_current[1][0]);
9603   else if (setup_mode == SETUP_MODE_CHOOSE_GRID_YSIZE_1)
9604     HandleChooseTree(mx, my, dx, dy, button, &grid_size_current[1][1]);
9605   else
9606     HandleSetupScreen_Generic(mx, my, dx, dy, button);
9607 }
9608
9609 void HandleGameActions(void)
9610 {
9611   if (CheckRestartGame())
9612     return;
9613
9614   if (game_status != GAME_MODE_PLAYING)
9615     return;
9616
9617   GameActions();                // main game loop
9618
9619   if (tape.auto_play && !tape.playing)
9620     AutoPlayTapesContinue();    // continue automatically playing next tape
9621 }
9622
9623
9624 // ---------- new screen button stuff --------------------------------------
9625
9626 static struct
9627 {
9628   int gfx_unpressed, gfx_pressed, gfx_active;
9629   struct MenuPosInfo *pos;
9630   boolean *check_value;
9631   int gadget_id;
9632   int screen_mask;
9633   unsigned int event_mask;
9634   boolean is_touch_button;
9635   char *infotext;
9636 } menubutton_info[NUM_SCREEN_MENUBUTTONS] =
9637 {
9638   {
9639     IMG_MENU_BUTTON_PREV_LEVEL, IMG_MENU_BUTTON_PREV_LEVEL_ACTIVE, -1,
9640     &menu.main.button.prev_level, NULL,
9641     SCREEN_CTRL_ID_PREV_LEVEL,
9642     SCREEN_MASK_MAIN,
9643     GD_EVENT_PRESSED | GD_EVENT_REPEATED,
9644     FALSE, "previous level"
9645   },
9646   {
9647     IMG_MENU_BUTTON_NEXT_LEVEL, IMG_MENU_BUTTON_NEXT_LEVEL_ACTIVE, -1,
9648     &menu.main.button.next_level, NULL,
9649     SCREEN_CTRL_ID_NEXT_LEVEL,
9650     SCREEN_MASK_MAIN,
9651     GD_EVENT_PRESSED | GD_EVENT_REPEATED,
9652     FALSE, "next level"
9653   },
9654   {
9655     IMG_MENU_BUTTON_PREV_LEVEL2, IMG_MENU_BUTTON_PREV_LEVEL2_ACTIVE, -1,
9656     &menu.scores.button.prev_level, NULL,
9657     SCREEN_CTRL_ID_PREV_LEVEL2,
9658     SCREEN_MASK_SCORES | SCREEN_MASK_SCORES_INFO,
9659     GD_EVENT_PRESSED | GD_EVENT_REPEATED,
9660     FALSE, "previous level"
9661   },
9662   {
9663     IMG_MENU_BUTTON_NEXT_LEVEL2, IMG_MENU_BUTTON_NEXT_LEVEL2_ACTIVE, -1,
9664     &menu.scores.button.next_level, NULL,
9665     SCREEN_CTRL_ID_NEXT_LEVEL2,
9666     SCREEN_MASK_SCORES | SCREEN_MASK_SCORES_INFO,
9667     GD_EVENT_PRESSED | GD_EVENT_REPEATED,
9668     FALSE, "next level"
9669   },
9670   {
9671     IMG_MENU_BUTTON_PREV_SCORE, IMG_MENU_BUTTON_PREV_SCORE_ACTIVE, -1,
9672     &menu.scores.button.prev_score, NULL,
9673     SCREEN_CTRL_ID_PREV_SCORE,
9674     SCREEN_MASK_SCORES_INFO,
9675     GD_EVENT_PRESSED | GD_EVENT_REPEATED,
9676     FALSE, "previous score"
9677   },
9678   {
9679     IMG_MENU_BUTTON_NEXT_SCORE, IMG_MENU_BUTTON_NEXT_SCORE_ACTIVE, -1,
9680     &menu.scores.button.next_score, NULL,
9681     SCREEN_CTRL_ID_NEXT_SCORE,
9682     SCREEN_MASK_SCORES_INFO,
9683     GD_EVENT_PRESSED | GD_EVENT_REPEATED,
9684     FALSE, "next score"
9685   },
9686   {
9687     IMG_MENU_BUTTON_PLAY_TAPE, IMG_MENU_BUTTON_PLAY_TAPE, -1,
9688     &menu.scores.button.play_tape, NULL,
9689     SCREEN_CTRL_ID_PLAY_TAPE,
9690     SCREEN_MASK_SCORES_INFO,
9691     GD_EVENT_RELEASED,
9692     FALSE, "play tape"
9693   },
9694   {
9695     IMG_MENU_BUTTON_FIRST_LEVEL, IMG_MENU_BUTTON_FIRST_LEVEL_ACTIVE, -1,
9696     &menu.main.button.first_level, NULL,
9697     SCREEN_CTRL_ID_FIRST_LEVEL,
9698     SCREEN_MASK_MAIN,
9699     GD_EVENT_RELEASED,
9700     FALSE, "first level"
9701   },
9702   {
9703     IMG_MENU_BUTTON_LAST_LEVEL, IMG_MENU_BUTTON_LAST_LEVEL_ACTIVE, -1,
9704     &menu.main.button.last_level, NULL,
9705     SCREEN_CTRL_ID_LAST_LEVEL,
9706     SCREEN_MASK_MAIN,
9707     GD_EVENT_RELEASED,
9708     FALSE, "last level"
9709   },
9710   {
9711     IMG_MENU_BUTTON_LEVEL_NUMBER, IMG_MENU_BUTTON_LEVEL_NUMBER_ACTIVE, -1,
9712     &menu.main.button.level_number, NULL,
9713     SCREEN_CTRL_ID_LEVEL_NUMBER,
9714     SCREEN_MASK_MAIN,
9715     GD_EVENT_RELEASED,
9716     FALSE, "level number"
9717   },
9718   {
9719     IMG_MENU_BUTTON_LEFT, IMG_MENU_BUTTON_LEFT_ACTIVE, -1,
9720     &menu.setup.button.prev_player, NULL,
9721     SCREEN_CTRL_ID_PREV_PLAYER,
9722     SCREEN_MASK_INPUT,
9723     GD_EVENT_PRESSED | GD_EVENT_REPEATED,
9724     FALSE, "previous player"
9725   },
9726   {
9727     IMG_MENU_BUTTON_RIGHT, IMG_MENU_BUTTON_RIGHT_ACTIVE, -1,
9728     &menu.setup.button.next_player, NULL,
9729     SCREEN_CTRL_ID_NEXT_PLAYER,
9730     SCREEN_MASK_INPUT,
9731     GD_EVENT_PRESSED | GD_EVENT_REPEATED,
9732     FALSE, "next player"
9733   },
9734   {
9735     IMG_MENU_BUTTON_INSERT_SOLUTION, IMG_MENU_BUTTON_INSERT_SOLUTION_ACTIVE, -1,
9736     &menu.main.button.insert_solution, NULL,
9737     SCREEN_CTRL_ID_INSERT_SOLUTION,
9738     SCREEN_MASK_MAIN_HAS_SOLUTION,
9739     GD_EVENT_RELEASED,
9740     FALSE, "insert solution tape"
9741   },
9742   {
9743     IMG_MENU_BUTTON_PLAY_SOLUTION, IMG_MENU_BUTTON_PLAY_SOLUTION_ACTIVE, -1,
9744     &menu.main.button.play_solution, NULL,
9745     SCREEN_CTRL_ID_PLAY_SOLUTION,
9746     SCREEN_MASK_MAIN_HAS_SOLUTION,
9747     GD_EVENT_RELEASED,
9748     FALSE, "play solution tape"
9749   },
9750   {
9751     IMG_MENU_BUTTON_LEVELSET_INFO, IMG_MENU_BUTTON_LEVELSET_INFO_PRESSED,
9752     IMG_MENU_BUTTON_LEVELSET_INFO_ACTIVE,
9753     &menu.main.button.levelset_info, NULL,
9754     SCREEN_CTRL_ID_LEVELSET_INFO,
9755     SCREEN_MASK_MAIN_HAS_SET_INFO,
9756     GD_EVENT_RELEASED,
9757     FALSE, "show level set info"
9758   },
9759   {
9760     IMG_MENU_BUTTON_SWITCH_ECS_AGA, IMG_MENU_BUTTON_SWITCH_ECS_AGA_ACTIVE, -1,
9761     &menu.main.button.switch_ecs_aga, &setup.prefer_aga_graphics,
9762     SCREEN_CTRL_ID_SWITCH_ECS_AGA,
9763     SCREEN_MASK_MAIN,
9764     GD_EVENT_RELEASED | GD_EVENT_OFF_BORDERS,
9765     FALSE, "switch ECS/AGA chipset"
9766   },
9767   {
9768     IMG_MENU_BUTTON_TOUCH_BACK, IMG_MENU_BUTTON_TOUCH_BACK, -1,
9769     &menu.setup.button.touch_back, NULL,
9770     SCREEN_CTRL_ID_TOUCH_PREV_PAGE,
9771     SCREEN_MASK_TOUCH,
9772     GD_EVENT_RELEASED,
9773     TRUE, "previous page"
9774   },
9775   {
9776     IMG_MENU_BUTTON_TOUCH_NEXT, IMG_MENU_BUTTON_TOUCH_NEXT, -1,
9777     &menu.setup.button.touch_next, NULL,
9778     SCREEN_CTRL_ID_TOUCH_NEXT_PAGE,
9779     SCREEN_MASK_TOUCH,
9780     GD_EVENT_RELEASED,
9781     TRUE, "next page"
9782   },
9783   {
9784     IMG_MENU_BUTTON_TOUCH_BACK2, IMG_MENU_BUTTON_TOUCH_BACK2, -1,
9785     &menu.setup.button.touch_back2, NULL,
9786     SCREEN_CTRL_ID_TOUCH_PREV_PAGE2,
9787     SCREEN_MASK_TOUCH2,
9788     GD_EVENT_RELEASED,
9789     TRUE, "previous page"
9790   },
9791   {
9792     IMG_MENU_BUTTON_TOUCH_NEXT2, IMG_MENU_BUTTON_TOUCH_NEXT2, -1,
9793     &menu.setup.button.touch_next2, NULL,
9794     SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2,
9795     SCREEN_MASK_TOUCH2,
9796     GD_EVENT_RELEASED,
9797     TRUE, "next page"
9798   },
9799 };
9800
9801 static struct
9802 {
9803   int gfx_unpressed, gfx_pressed;
9804   int x, y;
9805   int gadget_id;
9806   char *infotext;
9807 } scrollbutton_info[NUM_SCREEN_SCROLLBUTTONS] =
9808 {
9809   {
9810     IMG_MENU_BUTTON_UP, IMG_MENU_BUTTON_UP_ACTIVE,
9811     -1, -1,     // these values are not constant, but can change at runtime
9812     SCREEN_CTRL_ID_SCROLL_UP,
9813     "scroll up"
9814   },
9815   {
9816     IMG_MENU_BUTTON_DOWN, IMG_MENU_BUTTON_DOWN_ACTIVE,
9817     -1, -1,     // these values are not constant, but can change at runtime
9818     SCREEN_CTRL_ID_SCROLL_DOWN,
9819     "scroll down"
9820   }
9821 };
9822
9823 static struct
9824 {
9825   int gfx_unpressed, gfx_pressed;
9826   int x, y;
9827   int width, height;
9828   int type;
9829   int gadget_id;
9830   char *infotext;
9831 } scrollbar_info[NUM_SCREEN_SCROLLBARS] =
9832 {
9833   {
9834     IMG_MENU_SCROLLBAR, IMG_MENU_SCROLLBAR_ACTIVE,
9835     -1, -1,     // these values are not constant, but can change at runtime
9836     -1, -1,     // these values are not constant, but can change at runtime
9837     GD_TYPE_SCROLLBAR_VERTICAL,
9838     SCREEN_CTRL_ID_SCROLL_VERTICAL,
9839     "scroll level series vertically"
9840   }
9841 };
9842
9843 static struct
9844 {
9845   int graphic;
9846   int gadget_id;
9847   int x, y;
9848   int size;
9849   char *value;
9850   char *infotext;
9851 } textinput_info[NUM_SCREEN_TEXTINPUT] =
9852 {
9853   {
9854     IMG_SETUP_INPUT_TEXT,
9855     SCREEN_CTRL_ID_NETWORK_SERVER,
9856     -1, -1,     // these values are not constant, but can change at runtime
9857     MAX_SETUP_TEXT_INPUT_LEN,
9858     network_server_hostname,
9859     "Network Server Hostname / IP"
9860   },
9861 };
9862
9863 static void CreateScreenMenubuttons(void)
9864 {
9865   struct GadgetInfo *gi;
9866   unsigned int event_mask;
9867   int i;
9868
9869   for (i = 0; i < NUM_SCREEN_MENUBUTTONS; i++)
9870   {
9871     struct MenuPosInfo *pos = menubutton_info[i].pos;
9872     int screen_mask = menubutton_info[i].screen_mask;
9873     boolean is_touch_button = menubutton_info[i].is_touch_button;
9874     boolean is_check_button = menubutton_info[i].check_value != NULL;
9875     boolean is_score_button = (screen_mask & SCREEN_MASK_SCORES_INFO);
9876     boolean has_gfx_pressed = (menubutton_info[i].gfx_pressed ==
9877                                menubutton_info[i].gfx_unpressed);
9878     boolean has_gfx_active = (menubutton_info[i].gfx_active != -1);
9879     Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed;
9880     Bitmap *gd_bitmap_unpressed_alt, *gd_bitmap_pressed_alt;
9881     int gfx_unpressed, gfx_pressed;
9882     int gfx_unpressed_alt, gfx_pressed_alt;
9883     int x, y, width, height;
9884     int gd_x1, gd_x2, gd_y1, gd_y2;
9885     int gd_x1a, gd_x2a, gd_y1a, gd_y2a;
9886     int id = menubutton_info[i].gadget_id;
9887     int type = GD_TYPE_NORMAL_BUTTON;
9888     boolean checked = FALSE;
9889
9890     // do not use touch buttons if overlay touch buttons are disabled
9891     if (is_touch_button && !setup.touch.overlay_buttons)
9892       continue;
9893
9894     event_mask = menubutton_info[i].event_mask;
9895
9896     x = (is_touch_button ? pos->x : mSX + GDI_ACTIVE_POS(pos->x));
9897     y = (is_touch_button ? pos->y : mSY + GDI_ACTIVE_POS(pos->y));
9898
9899     width  = graphic_info[menubutton_info[i].gfx_pressed].width;
9900     height = graphic_info[menubutton_info[i].gfx_pressed].height;
9901
9902     gfx_unpressed = menubutton_info[i].gfx_unpressed;
9903     gfx_pressed   = menubutton_info[i].gfx_pressed;
9904     gfx_unpressed_alt = gfx_unpressed;
9905     gfx_pressed_alt   = gfx_pressed;
9906
9907     if (has_gfx_active)
9908     {
9909       gfx_unpressed_alt = menubutton_info[i].gfx_active;
9910
9911       type = GD_TYPE_CHECK_BUTTON_2;
9912
9913       if (menubutton_info[i].check_value != NULL)
9914         checked = *menubutton_info[i].check_value;
9915     }
9916
9917     gd_bitmap_unpressed = graphic_info[gfx_unpressed].bitmap;
9918     gd_bitmap_pressed   = graphic_info[gfx_pressed].bitmap;
9919     gd_bitmap_unpressed_alt = graphic_info[gfx_unpressed_alt].bitmap;
9920     gd_bitmap_pressed_alt   = graphic_info[gfx_pressed_alt].bitmap;
9921
9922     gd_x1 = graphic_info[gfx_unpressed].src_x;
9923     gd_y1 = graphic_info[gfx_unpressed].src_y;
9924     gd_x2 = graphic_info[gfx_pressed].src_x;
9925     gd_y2 = graphic_info[gfx_pressed].src_y;
9926
9927     gd_x1a = graphic_info[gfx_unpressed_alt].src_x;
9928     gd_y1a = graphic_info[gfx_unpressed_alt].src_y;
9929     gd_x2a = graphic_info[gfx_pressed_alt].src_x;
9930     gd_y2a = graphic_info[gfx_pressed_alt].src_y;
9931
9932     if (has_gfx_pressed)
9933     {
9934       gd_x2 += graphic_info[gfx_pressed].pressed_xoffset;
9935       gd_y2 += graphic_info[gfx_pressed].pressed_yoffset;
9936     }
9937
9938     if (is_check_button)
9939     {
9940       gd_x1a += graphic_info[gfx_unpressed].active_xoffset;
9941       gd_y1a += graphic_info[gfx_unpressed].active_yoffset;
9942       gd_x2a += graphic_info[gfx_pressed].active_xoffset;
9943       gd_y2a += graphic_info[gfx_pressed].active_yoffset;
9944
9945       type = GD_TYPE_CHECK_BUTTON;
9946
9947       if (menubutton_info[i].check_value != NULL)
9948         checked = *menubutton_info[i].check_value;
9949     }
9950
9951     if (is_score_button)
9952     {
9953       // if x/y set to -1, dynamically place buttons next to title text
9954       int title_width = getTextWidth(INFOTEXT_SCORE_ENTRY, FONT_TITLE_1);
9955
9956       // special compatibility handling for "Snake Bite" graphics set
9957       if (strPrefix(leveldir_current->identifier, "snake_bite"))
9958         title_width = strlen(INFOTEXT_SCORE_ENTRY) * 32;
9959
9960       // use "SX" here to center buttons (ignore horizontal draw offset)
9961       if (pos->x == -1)
9962         x = (id == SCREEN_CTRL_ID_PREV_LEVEL2 ?
9963              SX + (SXSIZE - title_width) / 2 - width * 3 / 2 :
9964              id == SCREEN_CTRL_ID_NEXT_LEVEL2 ?
9965              SX + (SXSIZE + title_width) / 2 + width / 2 : 0);
9966
9967       // use "mSY" here to place buttons (respect vertical draw offset)
9968       if (pos->y == -1)
9969         y = (id == SCREEN_CTRL_ID_PREV_LEVEL2 ||
9970              id == SCREEN_CTRL_ID_NEXT_LEVEL2 ? mSY + MENU_TITLE1_YPOS : 0);
9971     }
9972
9973     if (id == SCREEN_CTRL_ID_LEVELSET_INFO)
9974     {
9975       if (pos->x == -1 && pos->y == -1)
9976       {
9977         // use "SX" here to place button (ignore draw offsets)
9978         x = SX + SXSIZE - 2 * TILESIZE;
9979         y = SY + SYSIZE - 2 * TILESIZE;
9980
9981         // special compatibility handling for "BD2K3" graphics set
9982         if (strPrefix(leveldir_current->identifier, "BD2K3"))
9983           x = SX + TILESIZE + MINI_TILESIZE;
9984
9985         // special compatibility handling for "jue0" graphics set
9986         if (strPrefix(artwork.gfx_current_identifier, "jue0"))
9987         {
9988           x = SX + SXSIZE - 4 * TILESIZE;
9989           y = SY + SYSIZE - 3 * TILESIZE;
9990         }
9991       }
9992     }
9993
9994     gi = CreateGadget(GDI_CUSTOM_ID, id,
9995                       GDI_CUSTOM_TYPE_ID, i,
9996                       GDI_IMAGE_ID, gfx_unpressed,
9997                       GDI_INFO_TEXT, menubutton_info[i].infotext,
9998                       GDI_X, x,
9999                       GDI_Y, y,
10000                       GDI_WIDTH, width,
10001                       GDI_HEIGHT, height,
10002                       GDI_TYPE, type,
10003                       GDI_STATE, GD_BUTTON_UNPRESSED,
10004                       GDI_CHECKED, checked,
10005                       GDI_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1, gd_y1,
10006                       GDI_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2, gd_y2,
10007                       GDI_ALT_DESIGN_UNPRESSED, gd_bitmap_unpressed_alt, gd_x1a, gd_y1a,
10008                       GDI_ALT_DESIGN_PRESSED, gd_bitmap_pressed_alt, gd_x2a, gd_y2a,
10009                       GDI_DIRECT_DRAW, FALSE,
10010                       GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
10011                       GDI_EVENT_MASK, event_mask,
10012                       GDI_CALLBACK_ACTION, HandleScreenGadgets,
10013                       GDI_END);
10014
10015     if (gi == NULL)
10016       Fail("cannot create gadget");
10017
10018     screen_gadget[id] = gi;
10019   }
10020 }
10021
10022 static void CreateScreenScrollbuttons(void)
10023 {
10024   struct GadgetInfo *gi;
10025   unsigned int event_mask;
10026   int i;
10027
10028   // these values are not constant, but can change at runtime
10029   scrollbutton_info[0].x = SC_SCROLL_UP_XPOS;
10030   scrollbutton_info[0].y = SC_SCROLL_UP_YPOS;
10031   scrollbutton_info[1].x = SC_SCROLL_DOWN_XPOS;
10032   scrollbutton_info[1].y = SC_SCROLL_DOWN_YPOS;
10033
10034   for (i = 0; i < NUM_SCREEN_SCROLLBUTTONS; i++)
10035   {
10036     Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed;
10037     int gfx_unpressed, gfx_pressed;
10038     int x, y, width, height;
10039     int gd_x1, gd_x2, gd_y1, gd_y2;
10040     int id = scrollbutton_info[i].gadget_id;
10041
10042     event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
10043
10044     x = mSX + scrollbutton_info[i].x + menu.scrollbar_xoffset;
10045     y = mSY + scrollbutton_info[i].y;
10046     width = SC_SCROLLBUTTON_XSIZE;
10047     height = SC_SCROLLBUTTON_YSIZE;
10048
10049     // correct scrollbar position if placed outside menu (playfield) area
10050     if (x > SX + SC_SCROLL_UP_XPOS)
10051       x = SX + SC_SCROLL_UP_XPOS;
10052
10053     if (id == SCREEN_CTRL_ID_SCROLL_DOWN)
10054       y = mSY + (SC_SCROLL_VERTICAL_YPOS +
10055                  (NUM_MENU_ENTRIES_ON_SCREEN - 2) * SC_SCROLLBUTTON_YSIZE);
10056
10057     gfx_unpressed = scrollbutton_info[i].gfx_unpressed;
10058     gfx_pressed   = scrollbutton_info[i].gfx_pressed;
10059     gd_bitmap_unpressed = graphic_info[gfx_unpressed].bitmap;
10060     gd_bitmap_pressed   = graphic_info[gfx_pressed].bitmap;
10061     gd_x1 = graphic_info[gfx_unpressed].src_x;
10062     gd_y1 = graphic_info[gfx_unpressed].src_y;
10063     gd_x2 = graphic_info[gfx_pressed].src_x;
10064     gd_y2 = graphic_info[gfx_pressed].src_y;
10065
10066     gi = CreateGadget(GDI_CUSTOM_ID, id,
10067                       GDI_CUSTOM_TYPE_ID, i,
10068                       GDI_IMAGE_ID, gfx_unpressed,
10069                       GDI_INFO_TEXT, scrollbutton_info[i].infotext,
10070                       GDI_X, x,
10071                       GDI_Y, y,
10072                       GDI_WIDTH, width,
10073                       GDI_HEIGHT, height,
10074                       GDI_TYPE, GD_TYPE_NORMAL_BUTTON,
10075                       GDI_STATE, GD_BUTTON_UNPRESSED,
10076                       GDI_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1, gd_y1,
10077                       GDI_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2, gd_y2,
10078                       GDI_DIRECT_DRAW, FALSE,
10079                       GDI_EVENT_MASK, event_mask,
10080                       GDI_CALLBACK_ACTION, HandleScreenGadgets,
10081                       GDI_END);
10082
10083     if (gi == NULL)
10084       Fail("cannot create gadget");
10085
10086     screen_gadget[id] = gi;
10087   }
10088 }
10089
10090 static void CreateScreenScrollbars(void)
10091 {
10092   int i;
10093
10094   // these values are not constant, but can change at runtime
10095   scrollbar_info[0].x = SC_SCROLL_VERTICAL_XPOS;
10096   scrollbar_info[0].y = SC_SCROLL_VERTICAL_YPOS;
10097   scrollbar_info[0].width  = SC_SCROLL_VERTICAL_XSIZE;
10098   scrollbar_info[0].height = SC_SCROLL_VERTICAL_YSIZE;
10099
10100   for (i = 0; i < NUM_SCREEN_SCROLLBARS; i++)
10101   {
10102     Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed;
10103     int gfx_unpressed, gfx_pressed;
10104     int x, y, width, height;
10105     int gd_x1, gd_x2, gd_y1, gd_y2;
10106     struct GadgetInfo *gi;
10107     int items_max, items_visible, item_position;
10108     unsigned int event_mask;
10109     int num_page_entries = NUM_MENU_ENTRIES_ON_SCREEN;
10110     int id = scrollbar_info[i].gadget_id;
10111
10112     event_mask = GD_EVENT_MOVING | GD_EVENT_OFF_BORDERS;
10113
10114     x = mSX + scrollbar_info[i].x + menu.scrollbar_xoffset;
10115     y = mSY + scrollbar_info[i].y;
10116     width  = scrollbar_info[i].width;
10117     height = scrollbar_info[i].height;
10118
10119     // correct scrollbar position if placed outside menu (playfield) area
10120     if (x > SX + SC_SCROLL_VERTICAL_XPOS)
10121       x = SX + SC_SCROLL_VERTICAL_XPOS;
10122
10123     if (id == SCREEN_CTRL_ID_SCROLL_VERTICAL)
10124       height = (NUM_MENU_ENTRIES_ON_SCREEN - 2) * SC_SCROLLBUTTON_YSIZE;
10125
10126     items_max = num_page_entries;
10127     items_visible = num_page_entries;
10128     item_position = 0;
10129
10130     gfx_unpressed = scrollbar_info[i].gfx_unpressed;
10131     gfx_pressed   = scrollbar_info[i].gfx_pressed;
10132     gd_bitmap_unpressed = graphic_info[gfx_unpressed].bitmap;
10133     gd_bitmap_pressed   = graphic_info[gfx_pressed].bitmap;
10134     gd_x1 = graphic_info[gfx_unpressed].src_x;
10135     gd_y1 = graphic_info[gfx_unpressed].src_y;
10136     gd_x2 = graphic_info[gfx_pressed].src_x;
10137     gd_y2 = graphic_info[gfx_pressed].src_y;
10138
10139     gi = CreateGadget(GDI_CUSTOM_ID, id,
10140                       GDI_CUSTOM_TYPE_ID, i,
10141                       GDI_IMAGE_ID, gfx_unpressed,
10142                       GDI_INFO_TEXT, scrollbar_info[i].infotext,
10143                       GDI_X, x,
10144                       GDI_Y, y,
10145                       GDI_WIDTH, width,
10146                       GDI_HEIGHT, height,
10147                       GDI_TYPE, scrollbar_info[i].type,
10148                       GDI_SCROLLBAR_ITEMS_MAX, items_max,
10149                       GDI_SCROLLBAR_ITEMS_VISIBLE, items_visible,
10150                       GDI_SCROLLBAR_ITEM_POSITION, item_position,
10151                       GDI_WHEEL_AREA_X, SX,
10152                       GDI_WHEEL_AREA_Y, SY,
10153                       GDI_WHEEL_AREA_WIDTH, SXSIZE,
10154                       GDI_WHEEL_AREA_HEIGHT, SYSIZE,
10155                       GDI_STATE, GD_BUTTON_UNPRESSED,
10156                       GDI_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1, gd_y1,
10157                       GDI_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2, gd_y2,
10158                       GDI_BORDER_SIZE, SC_BORDER_SIZE, SC_BORDER_SIZE,
10159                       GDI_DIRECT_DRAW, FALSE,
10160                       GDI_EVENT_MASK, event_mask,
10161                       GDI_CALLBACK_ACTION, HandleScreenGadgets,
10162                       GDI_END);
10163
10164     if (gi == NULL)
10165       Fail("cannot create gadget");
10166
10167     screen_gadget[id] = gi;
10168   }
10169 }
10170
10171 static void CreateScreenTextInputGadgets(void)
10172 {
10173   int i;
10174
10175   for (i = 0; i < NUM_SCREEN_TEXTINPUT; i++)
10176   {
10177     int graphic = textinput_info[i].graphic;
10178     struct GraphicInfo *gd = &graphic_info[graphic];
10179     int gd_x1 = gd->src_x;
10180     int gd_y1 = gd->src_y;
10181     int gd_x2 = gd->src_x + gd->active_xoffset;
10182     int gd_y2 = gd->src_y + gd->active_yoffset;
10183     struct GadgetInfo *gi;
10184     unsigned int event_mask;
10185     int id = textinput_info[i].gadget_id;
10186     int x = textinput_info[i].x;
10187     int y = textinput_info[i].y;
10188
10189     event_mask = GD_EVENT_TEXT_RETURN | GD_EVENT_TEXT_LEAVING;
10190
10191     gi = CreateGadget(GDI_CUSTOM_ID, id,
10192                       GDI_CUSTOM_TYPE_ID, i,
10193                       GDI_INFO_TEXT, textinput_info[i].infotext,
10194                       GDI_X, SX + x,
10195                       GDI_Y, SY + y,
10196                       GDI_TYPE, GD_TYPE_TEXT_INPUT_ALPHANUMERIC,
10197                       GDI_TEXT_VALUE, textinput_info[i].value,
10198                       GDI_TEXT_SIZE, textinput_info[i].size,
10199                       GDI_TEXT_FONT, getSetupValueFont(TYPE_STRING, NULL),
10200                       GDI_TEXT_FONT_ACTIVE, FONT_TEXT_1,
10201                       GDI_DESIGN_UNPRESSED, gd->bitmap, gd_x1, gd_y1,
10202                       GDI_DESIGN_PRESSED, gd->bitmap, gd_x2, gd_y2,
10203                       GDI_BORDER_SIZE, gd->border_size, gd->border_size,
10204                       GDI_DESIGN_WIDTH, gd->width,
10205                       GDI_EVENT_MASK, event_mask,
10206                       GDI_CALLBACK_ACTION, HandleScreenGadgets,
10207                       GDI_CALLBACK_ACTION_ALWAYS, TRUE,
10208                       GDI_END);
10209
10210     if (gi == NULL)
10211       Fail("cannot create gadget");
10212
10213     screen_gadget[id] = gi;
10214   }
10215 }
10216
10217 void CreateScreenGadgets(void)
10218 {
10219   CreateScreenMenubuttons();
10220
10221   CreateScreenScrollbuttons();
10222   CreateScreenScrollbars();
10223
10224   CreateScreenTextInputGadgets();
10225 }
10226
10227 void FreeScreenGadgets(void)
10228 {
10229   int i;
10230
10231   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
10232     FreeGadget(screen_gadget[i]);
10233 }
10234
10235 static void RedrawScreenMenuGadgets(int screen_mask)
10236 {
10237   int i;
10238
10239   for (i = 0; i < NUM_SCREEN_MENUBUTTONS; i++)
10240     if (screen_mask & menubutton_info[i].screen_mask)
10241       RedrawGadget(screen_gadget[menubutton_info[i].gadget_id]);
10242 }
10243
10244 static void MapScreenMenuGadgets(int screen_mask)
10245 {
10246   int i;
10247
10248   for (i = 0; i < NUM_SCREEN_MENUBUTTONS; i++)
10249     if (screen_mask & menubutton_info[i].screen_mask)
10250       MapGadget(screen_gadget[menubutton_info[i].gadget_id]);
10251 }
10252
10253 static void UnmapScreenMenuGadgets(int screen_mask)
10254 {
10255   int i;
10256
10257   for (i = 0; i < NUM_SCREEN_MENUBUTTONS; i++)
10258   {
10259     if (screen_mask & menubutton_info[i].screen_mask)
10260     {
10261       UnmapGadget(screen_gadget[menubutton_info[i].gadget_id]);
10262
10263       if (screen_mask & SCREEN_MASK_MAIN_HAS_SOLUTION)
10264         DrawBackground(screen_gadget[menubutton_info[i].gadget_id]->x,
10265                        screen_gadget[menubutton_info[i].gadget_id]->y,
10266                        screen_gadget[menubutton_info[i].gadget_id]->width,
10267                        screen_gadget[menubutton_info[i].gadget_id]->height);
10268     }
10269   }
10270 }
10271
10272 static void UpdateScreenMenuGadgets(int screen_mask, boolean map_gadgets)
10273 {
10274   if (map_gadgets)
10275     MapScreenMenuGadgets(screen_mask);
10276   else
10277     UnmapScreenMenuGadgets(screen_mask);
10278 }
10279
10280 static void MapScreenGadgets(int num_entries)
10281 {
10282   int i;
10283
10284   if (num_entries <= NUM_MENU_ENTRIES_ON_SCREEN)
10285     return;
10286
10287   for (i = 0; i < NUM_SCREEN_SCROLLBUTTONS; i++)
10288     MapGadget(screen_gadget[scrollbutton_info[i].gadget_id]);
10289
10290   for (i = 0; i < NUM_SCREEN_SCROLLBARS; i++)
10291     MapGadget(screen_gadget[scrollbar_info[i].gadget_id]);
10292 }
10293
10294 static void UnmapScreenGadgets(void)
10295 {
10296   int i;
10297
10298   for (i = 0; i < NUM_SCREEN_SCROLLBUTTONS; i++)
10299     UnmapGadget(screen_gadget[scrollbutton_info[i].gadget_id]);
10300
10301   for (i = 0; i < NUM_SCREEN_SCROLLBARS; i++)
10302     UnmapGadget(screen_gadget[scrollbar_info[i].gadget_id]);
10303 }
10304
10305 static void MapScreenTreeGadgets(TreeInfo *ti)
10306 {
10307   MapScreenGadgets(numTreeInfoInGroup(ti));
10308 }
10309
10310 static void UnmapScreenTreeGadgets(void)
10311 {
10312   UnmapScreenGadgets();
10313 }
10314
10315 static void AdjustScoreInfoButtons_SelectScore(int x, int y1, int y2)
10316 {
10317   struct GadgetInfo *gi_1 = screen_gadget[SCREEN_CTRL_ID_PREV_SCORE];
10318   struct GadgetInfo *gi_2 = screen_gadget[SCREEN_CTRL_ID_NEXT_SCORE];
10319   struct MenuPosInfo *pos_1 = menubutton_info[SCREEN_CTRL_ID_PREV_SCORE].pos;
10320   struct MenuPosInfo *pos_2 = menubutton_info[SCREEN_CTRL_ID_NEXT_SCORE].pos;
10321
10322   if (pos_1->x == -1 && pos_1->y == -1)
10323     ModifyGadget(gi_1, GDI_X, x, GDI_Y, y1, GDI_END);
10324
10325   if (pos_2->x == -1 && pos_2->y == -1)
10326     ModifyGadget(gi_2, GDI_X, x, GDI_Y, y2, GDI_END);
10327 }
10328
10329 static void AdjustScoreInfoButtons_PlayTape(int x, int y, boolean visible)
10330 {
10331   struct GadgetInfo *gi = screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE];
10332   struct MenuPosInfo *pos = menubutton_info[SCREEN_CTRL_ID_PLAY_TAPE].pos;
10333
10334   // set gadget position dynamically, pre-defined or off-screen
10335   int xx = (visible ? (pos->x == -1 ? x : pos->x) : POS_OFFSCREEN);
10336   int yy = (visible ? (pos->y == -1 ? y : pos->y) : POS_OFFSCREEN);
10337
10338   ModifyGadget(gi, GDI_X, xx, GDI_Y, yy, GDI_END);
10339   MapGadget(gi);        // (needed if deactivated on last score page)
10340 }
10341
10342 static void HandleScreenGadgets(struct GadgetInfo *gi)
10343 {
10344   int id = gi->custom_id;
10345   int button = gi->event.button;
10346   int step = (button == MB_LEFTBUTTON   ? 1 :
10347               button == MB_MIDDLEBUTTON ? 5 :
10348               button == MB_RIGHTBUTTON  ? 10 : 1);
10349
10350   switch (id)
10351   {
10352     case SCREEN_CTRL_ID_PREV_LEVEL:
10353       HandleMainMenu_SelectLevel(step, -1, NO_DIRECT_LEVEL_SELECT);
10354       break;
10355
10356     case SCREEN_CTRL_ID_NEXT_LEVEL:
10357       HandleMainMenu_SelectLevel(step, +1, NO_DIRECT_LEVEL_SELECT);
10358       break;
10359
10360     case SCREEN_CTRL_ID_PREV_LEVEL2:
10361       HandleHallOfFame_SelectLevel(step, -1);
10362       break;
10363
10364     case SCREEN_CTRL_ID_NEXT_LEVEL2:
10365       HandleHallOfFame_SelectLevel(step, +1);
10366       break;
10367
10368     case SCREEN_CTRL_ID_PREV_SCORE:
10369       HandleScoreInfo_SelectScore(step, -1);
10370       break;
10371
10372     case SCREEN_CTRL_ID_NEXT_SCORE:
10373       HandleScoreInfo_SelectScore(step, +1);
10374       break;
10375
10376     case SCREEN_CTRL_ID_PLAY_TAPE:
10377       HandleScoreInfo_PlayTape();
10378       break;
10379
10380     case SCREEN_CTRL_ID_FIRST_LEVEL:
10381       HandleMainMenu_SelectLevel(MAX_LEVELS, -1, NO_DIRECT_LEVEL_SELECT);
10382       break;
10383
10384     case SCREEN_CTRL_ID_LAST_LEVEL:
10385       HandleMainMenu_SelectLevel(MAX_LEVELS, +1, NO_DIRECT_LEVEL_SELECT);
10386       break;
10387
10388     case SCREEN_CTRL_ID_LEVEL_NUMBER:
10389       CloseDoor(DOOR_CLOSE_2);
10390       SetGameStatus(GAME_MODE_LEVELNR);
10391       DrawChooseLevelNr();
10392       break;
10393
10394     case SCREEN_CTRL_ID_PREV_PLAYER:
10395       HandleSetupScreen_Input_Player(step, -1);
10396       break;
10397
10398     case SCREEN_CTRL_ID_NEXT_PLAYER:
10399       HandleSetupScreen_Input_Player(step, +1);
10400       break;
10401
10402     case SCREEN_CTRL_ID_INSERT_SOLUTION:
10403       InsertSolutionTape();
10404       break;
10405
10406     case SCREEN_CTRL_ID_PLAY_SOLUTION:
10407       PlaySolutionTape();
10408       break;
10409
10410     case SCREEN_CTRL_ID_LEVELSET_INFO:
10411       DrawInfoScreen_FromMainMenu(INFO_MODE_LEVELSET);
10412       break;
10413
10414     case SCREEN_CTRL_ID_SWITCH_ECS_AGA:
10415       setup.prefer_aga_graphics = !setup.prefer_aga_graphics;
10416       DrawMainMenu();
10417       break;
10418
10419     case SCREEN_CTRL_ID_TOUCH_PREV_PAGE:
10420     case SCREEN_CTRL_ID_TOUCH_NEXT_PAGE:
10421     case SCREEN_CTRL_ID_TOUCH_PREV_PAGE2:
10422     case SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2:
10423       PushUserEvent(USEREVENT_GADGET_PRESSED, id, 0);
10424       break;
10425
10426     case SCREEN_CTRL_ID_SCROLL_UP:
10427       if (game_status == GAME_MODE_NAMES)
10428         HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
10429       else if (game_status == GAME_MODE_LEVELS)
10430         HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
10431       else if (game_status == GAME_MODE_LEVELNR)
10432         HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
10433       else if (game_status == GAME_MODE_SETUP)
10434         HandleSetupScreen(0, 0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
10435       else if (game_status == GAME_MODE_INFO)
10436         HandleInfoScreen(0, 0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
10437       else if (game_status == GAME_MODE_SCORES)
10438         HandleHallOfFame(0, 0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
10439       break;
10440
10441     case SCREEN_CTRL_ID_SCROLL_DOWN:
10442       if (game_status == GAME_MODE_NAMES)
10443         HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
10444       else if (game_status == GAME_MODE_LEVELS)
10445         HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
10446       else if (game_status == GAME_MODE_LEVELNR)
10447         HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
10448       else if (game_status == GAME_MODE_SETUP)
10449         HandleSetupScreen(0, 0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
10450       else if (game_status == GAME_MODE_INFO)
10451         HandleInfoScreen(0, 0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
10452       else if (game_status == GAME_MODE_SCORES)
10453         HandleHallOfFame(0, 0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
10454       break;
10455
10456     case SCREEN_CTRL_ID_SCROLL_VERTICAL:
10457       if (game_status == GAME_MODE_NAMES)
10458         HandleChoosePlayerName(0, 0, 999, gi->event.item_position, MB_MENU_INITIALIZE);
10459       else if (game_status == GAME_MODE_LEVELS)
10460         HandleChooseLevelSet(0, 0, 999, gi->event.item_position, MB_MENU_INITIALIZE);
10461       else if (game_status == GAME_MODE_LEVELNR)
10462         HandleChooseLevelNr(0, 0, 999, gi->event.item_position, MB_MENU_INITIALIZE);
10463       else if (game_status == GAME_MODE_SETUP)
10464         HandleSetupScreen(0, 0, 999, gi->event.item_position, MB_MENU_INITIALIZE);
10465       else if (game_status == GAME_MODE_INFO)
10466         HandleInfoScreen(0, 0, 999, gi->event.item_position, MB_MENU_INITIALIZE);
10467       else if (game_status == GAME_MODE_SCORES)
10468         HandleHallOfFame(0, 0, 999, gi->event.item_position, MB_MENU_INITIALIZE);
10469       break;
10470
10471     case SCREEN_CTRL_ID_NETWORK_SERVER:
10472     {
10473       if (!strEqual(gi->textinput.value, ""))
10474       {
10475         setString(&setup.network_server_hostname, gi->textinput.value);
10476
10477         network.server_host = setup.network_server_hostname;
10478       }
10479       else
10480       {
10481         setString(&setup.network_server_hostname, STR_NETWORK_AUTO_DETECT);
10482
10483         network.server_host = NULL;
10484       }
10485
10486       if (strEqual(network.server_host, STR_NETWORK_AUTO_DETECT))
10487         network.server_host = NULL;
10488
10489       execSetupGame_setNetworkServerText();
10490
10491       DrawSetupScreen();
10492
10493       break;
10494     }
10495
10496     default:
10497       break;
10498   }
10499 }
10500
10501 void HandleScreenGadgetKeys(Key key)
10502 {
10503   if (key == setup.shortcut.tape_play)
10504     HandleScreenGadgets(screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE]);
10505 }
10506
10507 void DumpScreenIdentifiers(void)
10508 {
10509   int i;
10510
10511   Print("Active screen elements on current screen:\n");
10512
10513   for (i = 0; main_controls[i].nr != -1; i++)
10514   {
10515     struct MainControlInfo *mci = &main_controls[i];
10516
10517     if (mci->button_graphic != -1)
10518     {
10519       char *token = getTokenFromImageID(mci->button_graphic);
10520
10521       Print("- '%s'\n", token);
10522     }
10523   }
10524
10525   Print("Done.\n");
10526 }
10527
10528 boolean DoScreenAction(int image_id)
10529 {
10530   int i;
10531
10532   if (game_status != GAME_MODE_MAIN)
10533     return FALSE;
10534
10535   for (i = 0; main_controls[i].nr != -1; i++)
10536   {
10537     struct MainControlInfo *mci = &main_controls[i];
10538     struct MenuPosInfo *pos = mci->pos_button;
10539
10540     if (mci->button_graphic == image_id)
10541     {
10542       int x = mSX + pos->x;
10543       int y = mSY + pos->y;
10544
10545       HandleMainMenu(x, y, 0, 0, MB_MENU_CHOICE);
10546
10547       return TRUE;
10548     }
10549   }
10550
10551   return FALSE;
10552 }
10553
10554 void DrawScreenAfterAddingSet(char *tree_subdir_new, int tree_type)
10555 {
10556   // get tree info node of newly added level or artwork set
10557   TreeInfo *tree_node_first = TREE_FIRST_NODE(tree_type);
10558   TreeInfo *tree_node_new = getTreeInfoFromIdentifier(tree_node_first,
10559                                                       tree_subdir_new);
10560   if (tree_node_new == NULL)    // should not happen
10561     return;
10562
10563   // if request dialog is active, do nothing
10564   if (game.request_active)
10565     return;
10566
10567   if (game_status == GAME_MODE_MAIN &&
10568       tree_type == TREE_TYPE_LEVEL_DIR)
10569   {
10570     // when adding new level set in main menu, select it as current level set
10571
10572     // change current level set to newly added level set from zip file
10573     leveldir_current = tree_node_new;
10574
10575     // change current level number to first level of newly added level set
10576     level_nr = leveldir_current->first_level;
10577
10578     // redraw screen to reflect changed level set
10579     DrawMainMenu();
10580
10581     // save this level set and level number as last selected level set
10582     SaveLevelSetup_LastSeries();
10583     SaveLevelSetup_SeriesInfo();
10584   }
10585   else if (game_status == GAME_MODE_LEVELS &&
10586            tree_type == TREE_TYPE_LEVEL_DIR)
10587   {
10588     // when adding new level set in level set menu, set cursor and update screen
10589
10590     leveldir_current = tree_node_new;
10591
10592     DrawChooseTree(&leveldir_current);
10593   }
10594   else if (game_status == GAME_MODE_SETUP)
10595   {
10596     // when adding new artwork set in setup menu, set cursor and update screen
10597
10598     if (setup_mode == SETUP_MODE_CHOOSE_GRAPHICS &&
10599         tree_type == TREE_TYPE_GRAPHICS_DIR)
10600     {
10601       artwork.gfx_current = tree_node_new;
10602
10603       DrawChooseTree(&artwork.gfx_current);
10604     }
10605     else if (setup_mode == SETUP_MODE_CHOOSE_SOUNDS &&
10606              tree_type == TREE_TYPE_SOUNDS_DIR)
10607     {
10608       artwork.snd_current = tree_node_new;
10609
10610       DrawChooseTree(&artwork.snd_current);
10611     }
10612     else if (setup_mode == SETUP_MODE_CHOOSE_MUSIC &&
10613              tree_type == TREE_TYPE_MUSIC_DIR)
10614     {
10615       artwork.mus_current = tree_node_new;
10616
10617       DrawChooseTree(&artwork.mus_current);
10618     }
10619   }
10620 }
10621
10622 static int UploadTapes(void)
10623 {
10624   SetGameStatus(GAME_MODE_LOADING);
10625
10626   FadeSetEnterScreen();
10627   FadeOut(REDRAW_ALL);
10628
10629   ClearRectangle(drawto, 0, 0, WIN_XSIZE, WIN_YSIZE);
10630
10631   FadeIn(REDRAW_ALL);
10632
10633   DrawInitTextHead("Uploading tapes");
10634
10635   global.autoplay_mode = AUTOPLAY_MODE_UPLOAD;
10636   global.autoplay_leveldir = "ALL";
10637   global.autoplay_all = TRUE;
10638
10639   int num_tapes_uploaded = AutoPlayTapes();
10640
10641   global.autoplay_mode = AUTOPLAY_MODE_NONE;
10642   global.autoplay_leveldir = NULL;
10643   global.autoplay_all = FALSE;
10644
10645   SetGameStatus(GAME_MODE_MAIN);
10646
10647   DrawMainMenu();
10648
10649   return num_tapes_uploaded;
10650 }
10651
10652 static boolean OfferUploadTapes(void)
10653 {
10654   if (!Request(setup.has_remaining_tapes ?
10655                "Upload missing tapes to the high score server now?" :
10656                "Upload all your tapes to the high score server now?", REQ_ASK))
10657     return FALSE;
10658
10659   // when uploading tapes, make sure that high score server is enabled
10660   runtime.use_api_server = setup.use_api_server = TRUE;
10661
10662   int num_tapes_uploaded = UploadTapes();
10663   char message[100];
10664
10665   if (num_tapes_uploaded < 0)
10666   {
10667     num_tapes_uploaded = -num_tapes_uploaded - 1;
10668
10669     if (num_tapes_uploaded == 0)
10670       sprintf(message, "Upload failed! No tapes uploaded!");
10671     else if (num_tapes_uploaded == 1)
10672       sprintf(message, "Upload failed! Only 1 tape uploaded!");
10673     else
10674       sprintf(message, "Upload failed! Only %d tapes uploaded!",
10675               num_tapes_uploaded);
10676
10677     Request(message, REQ_CONFIRM);
10678
10679     // if uploading tapes failed, add tape upload entry to setup menu
10680     setup.provide_uploading_tapes = TRUE;
10681     setup.has_remaining_tapes = TRUE;
10682
10683     SaveSetup_ServerSetup();
10684
10685     return FALSE;
10686   }
10687
10688   if (num_tapes_uploaded == 0)
10689     sprintf(message, "No tapes uploaded!");
10690   else if (num_tapes_uploaded == 1)
10691     sprintf(message, "1 tape uploaded!");
10692   else
10693     sprintf(message, "%d tapes uploaded!", num_tapes_uploaded);
10694
10695   Request(message, REQ_CONFIRM);
10696
10697   if (num_tapes_uploaded > 0)
10698     Request("New scores will be visible after a few minutes!", REQ_CONFIRM);
10699
10700   // after all tapes have been uploaded, remove entry from setup menu
10701   setup.provide_uploading_tapes = FALSE;
10702   setup.has_remaining_tapes = FALSE;
10703
10704   SaveSetup_ServerSetup();
10705
10706   return TRUE;
10707 }
10708
10709 static void CheckUploadTapes(void)
10710 {
10711   if (!setup.ask_for_uploading_tapes)
10712     return;
10713
10714   // after asking for uploading tapes, do not ask again
10715   setup.ask_for_uploading_tapes = FALSE;
10716   setup.ask_for_remaining_tapes = FALSE;
10717
10718   if (directoryExists(getTapeDir(NULL)))
10719   {
10720     boolean tapes_uploaded = OfferUploadTapes();
10721
10722     if (!tapes_uploaded)
10723     {
10724       Request(setup.has_remaining_tapes ?
10725               "You can upload missing tapes from the setup menu later!" :
10726               "You can upload your tapes from the setup menu later!",
10727               REQ_CONFIRM);
10728     }
10729   }
10730   else
10731   {
10732     // if tapes directory does not exist yet, never offer uploading all tapes
10733     setup.provide_uploading_tapes = FALSE;
10734   }
10735
10736   SaveSetup_ServerSetup();
10737 }
10738
10739 static void UpgradePlayerUUID(void)
10740 {
10741   ApiResetUUIDAsThread(getUUID());
10742 }
10743
10744 static void CheckUpgradePlayerUUID(void)
10745 {
10746   if (setup.player_version > 1)
10747     return;
10748
10749   UpgradePlayerUUID();
10750 }
10751
10752 void CheckApiServerTasks(void)
10753 {
10754   // check if the player's UUID has to be upgraded
10755   CheckUpgradePlayerUUID();
10756
10757   // check if there are any tapes to be uploaded
10758   CheckUploadTapes();
10759 }