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