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