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