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