e5f0e6da1eecaee7f7a72b41ac23dea97efab15a
[rocksndiamonds.git] / src / screens.c
1 /***********************************************************
2 * Rocks'n'Diamonds -- McDuffin Strikes Back!               *
3 *----------------------------------------------------------*
4 * (c) 1995-2002 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * screens.c                                                *
12 ***********************************************************/
13
14 #include "libgame/libgame.h"
15
16 #include "screens.h"
17 #include "events.h"
18 #include "game.h"
19 #include "tools.h"
20 #include "editor.h"
21 #include "files.h"
22 #include "tape.h"
23 #include "cartoons.h"
24 #include "network.h"
25 #include "init.h"
26
27 /* screens in the setup menu */
28 #define SETUP_MODE_MAIN                 0
29 #define SETUP_MODE_GAME                 1
30 #define SETUP_MODE_EDITOR               2
31 #define SETUP_MODE_INPUT                3
32 #define SETUP_MODE_SHORTCUT_1           4
33 #define SETUP_MODE_SHORTCUT_2           5
34 #define SETUP_MODE_GRAPHICS             6
35 #define SETUP_MODE_SOUND                7
36 #define SETUP_MODE_ARTWORK              8
37 #define SETUP_MODE_CHOOSE_GRAPHICS      9
38 #define SETUP_MODE_CHOOSE_SOUNDS        10
39 #define SETUP_MODE_CHOOSE_MUSIC         11
40
41 #define MAX_SETUP_MODES                 12
42
43 /* for input setup functions */
44 #define SETUPINPUT_SCREEN_POS_START     0
45 #define SETUPINPUT_SCREEN_POS_END       (SCR_FIELDY - 4)
46 #define SETUPINPUT_SCREEN_POS_EMPTY1    (SETUPINPUT_SCREEN_POS_START + 3)
47 #define SETUPINPUT_SCREEN_POS_EMPTY2    (SETUPINPUT_SCREEN_POS_END - 1)
48
49 /* screens on the info screen */
50 #define INFO_MODE_MAIN                  0
51 #define INFO_MODE_ELEMENTS              1
52 #define INFO_MODE_MUSIC                 2
53 #define INFO_MODE_CREDITS               3
54 #define INFO_MODE_PROGRAM               4
55 #define INFO_MODE_LEVELSET              5
56
57 #define MAX_INFO_MODES                  6
58
59 /* for various menu stuff  */
60 #define MAX_INFO_ELEMENTS_ON_SCREEN     10
61 #define MAX_MENU_ENTRIES_ON_SCREEN      (SCR_FIELDY - 2)
62 #define MENU_SCREEN_START_YPOS          2
63 #define MENU_SCREEN_VALUE_XPOS          14
64 #define MENU_SCREEN_MAX_XPOS            (SCR_FIELDX - 1)
65 #define MENU_TITLE1_YPOS                8
66 #define MENU_TITLE2_YPOS                46
67
68 /* buttons and scrollbars identifiers */
69 #define SCREEN_CTRL_ID_SCROLL_UP        0
70 #define SCREEN_CTRL_ID_SCROLL_DOWN      1
71 #define SCREEN_CTRL_ID_SCROLL_VERTICAL  2
72
73 #define NUM_SCREEN_SCROLLBUTTONS        2
74 #define NUM_SCREEN_SCROLLBARS           1
75 #define NUM_SCREEN_GADGETS              3
76
77 /* forward declarations of internal functions */
78 static void HandleScreenGadgets(struct GadgetInfo *);
79 static void HandleSetupScreen_Generic(int, int, int, int, int);
80 static void HandleSetupScreen_Input(int, int, int, int, int);
81 static void CustomizeKeyboard(int);
82 static void CalibrateJoystick(int);
83 static void execSetupArtwork(void);
84 static void HandleChooseTree(int, int, int, int, int, TreeInfo **);
85
86 static void DrawChooseLevel(void);
87 static void DrawInfoScreen(void);
88 static void DrawSetupScreen(void);
89
90 static void DrawInfoScreen_HelpAnim(int, int, boolean);
91 static void DrawInfoScreen_HelpText(int, int, int, int);
92 static void HandleInfoScreen_Main(int, int, int, int, int);
93 static void HandleInfoScreen_Elements(int);
94 static void HandleInfoScreen_Music(int);
95 static void HandleInfoScreen_Credits(int);
96 static void HandleInfoScreen_Program(int);
97
98 static void MapChooseTreeGadgets(TreeInfo *);
99
100 static struct GadgetInfo *screen_gadget[NUM_SCREEN_GADGETS];
101 static int setup_mode = SETUP_MODE_MAIN;
102 static int info_mode = INFO_MODE_MAIN;
103
104 #define DRAW_OFFSET_MODE(x)     (x >= GAME_MODE_MAIN &&                 \
105                                  x <= GAME_MODE_SETUP ? x :             \
106                                  x == GAME_MODE_PSEUDO_TYPENAME ?       \
107                                  GAME_MODE_MAIN : GAME_MODE_DEFAULT)
108
109 #define mSX (SX + menu.draw_xoffset[DRAW_OFFSET_MODE(game_status)])
110 #define mSY (SY + menu.draw_yoffset[DRAW_OFFSET_MODE(game_status)])
111
112 #define NUM_MENU_ENTRIES_ON_SCREEN (menu.list_size[game_status] > 2 ?   \
113                                     menu.list_size[game_status] :       \
114                                     MAX_MENU_ENTRIES_ON_SCREEN)
115
116 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
117 #define NUM_SCROLLBAR_BITMAPS           2
118 static Bitmap *scrollbar_bitmap[NUM_SCROLLBAR_BITMAPS];
119 #endif
120
121
122 static void drawCursorExt(int xpos, int ypos, int color, int g)
123 {
124   static int cursor_array[SCR_FIELDY];
125
126   if (xpos == 0)
127   {
128     if (g != 0)
129       cursor_array[ypos] = g;
130     else
131       g = cursor_array[ypos];
132   }
133
134   if (color == FC_RED)
135     g = (g == IMG_MENU_BUTTON_LEFT  ? IMG_MENU_BUTTON_LEFT_ACTIVE  :
136          g == IMG_MENU_BUTTON_RIGHT ? IMG_MENU_BUTTON_RIGHT_ACTIVE :
137          g == IMG_MENU_BUTTON_LEAVE_MENU ? IMG_MENU_BUTTON_LEAVE_MENU_ACTIVE :
138          g == IMG_MENU_BUTTON_ENTER_MENU ? IMG_MENU_BUTTON_ENTER_MENU_ACTIVE :
139          IMG_MENU_BUTTON_ACTIVE);
140
141   ypos += MENU_SCREEN_START_YPOS;
142
143   DrawBackground(mSX + xpos * TILEX, mSY + ypos * TILEY, TILEX, TILEY);
144   DrawGraphicThruMaskExt(drawto, mSX + xpos * TILEX, mSY + ypos * TILEY, g, 0);
145 }
146
147 static void initCursor(int ypos, int graphic)
148 {
149   drawCursorExt(0, ypos, FC_BLUE, graphic);
150 }
151
152 static void drawCursor(int ypos, int color)
153 {
154   drawCursorExt(0, ypos, color, 0);
155 }
156
157 static void drawCursorXY(int xpos, int ypos, int graphic)
158 {
159   drawCursorExt(xpos, ypos, -1, graphic);
160 }
161
162 static void drawChooseTreeCursor(int ypos, int color)
163 {
164   int last_game_status = game_status;   /* save current game status */
165
166   /* force LEVELS draw offset on artwork setup screen */
167   game_status = GAME_MODE_LEVELS;
168
169   drawCursorExt(0, ypos, color, 0);
170
171   game_status = last_game_status;       /* restore current game status */
172 }
173
174 static void PlayMenuSound()
175 {
176   int sound = menu.sound[game_status];
177
178   if (sound == SND_UNDEFINED)
179     return;
180
181   if (sound_info[sound].loop)
182     PlaySoundLoop(sound);
183   else
184     PlaySound(sound);
185 }
186
187 static void PlayMenuSoundIfLoop()
188 {
189   int sound = menu.sound[game_status];
190
191   if (sound == SND_UNDEFINED)
192     return;
193
194   if (sound_info[sound].loop)
195     PlaySoundLoop(sound);
196 }
197
198 static void PlayMenuMusic()
199 {
200   int music = menu.music[game_status];
201
202   if (music == MUS_UNDEFINED)
203     return;
204
205   PlayMusic(music);
206 }
207
208 void DrawHeadline()
209 {
210   DrawTextSCentered(MENU_TITLE1_YPOS, FONT_TITLE_1, PROGRAM_TITLE_STRING);
211   DrawTextSCentered(MENU_TITLE2_YPOS, FONT_TITLE_2, PROGRAM_COPYRIGHT_STRING);
212 }
213
214 static void ToggleFullscreenIfNeeded()
215 {
216   if (setup.fullscreen != video.fullscreen_enabled)
217   {
218     /* save old door content */
219     BlitBitmap(backbuffer, bitmap_db_door,
220                DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
221
222     /* toggle fullscreen */
223     ChangeVideoModeIfNeeded(setup.fullscreen);
224     setup.fullscreen = video.fullscreen_enabled;
225
226     /* redraw background to newly created backbuffer */
227     BlitBitmap(graphic_info[IMG_GLOBAL_BORDER].bitmap, backbuffer,
228                0,0, WIN_XSIZE,WIN_YSIZE, 0,0);
229
230     /* restore old door content */
231     BlitBitmap(bitmap_db_door, backbuffer,
232                DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
233
234     redraw_mask = REDRAW_ALL;
235   }
236 }
237
238 void DrawMainMenu()
239 {
240   static LevelDirTree *leveldir_last_valid = NULL;
241   char *name_text = (!options.network && setup.team_mode ? "Team:" : "Name:");
242 #if 1
243   char *level_text = "Levelset";
244 #else
245   char *level_text = "Level:";
246 #endif
247   int name_width, level_width;
248   int i;
249
250   UnmapAllGadgets();
251   FadeSoundsAndMusic();
252
253   KeyboardAutoRepeatOn();
254   ActivateJoystick();
255
256   SetDrawDeactivationMask(REDRAW_NONE);
257   SetDrawBackgroundMask(REDRAW_FIELD);
258
259   audio.sound_deactivated = FALSE;
260
261   /* needed if last screen was the playing screen, invoked from level editor */
262   if (level_editor_test_game)
263   {
264     game_status = GAME_MODE_EDITOR;
265     DrawLevelEd();
266
267     return;
268   }
269
270   /* needed if last screen was the editor screen */
271   UndrawSpecialEditorDoor();
272
273   /* needed if last screen was the setup screen and fullscreen state changed */
274   ToggleFullscreenIfNeeded();
275
276   /* leveldir_current may be invalid (level group, parent link) */
277   if (!validLevelSeries(leveldir_current))
278     leveldir_current = getFirstValidTreeInfoEntry(leveldir_last_valid);
279
280   /* store valid level series information */
281   leveldir_last_valid = leveldir_current;
282
283   /* needed if last screen (level choice) changed graphics, sounds or music */
284   ReloadCustomArtwork(0);
285
286 #ifdef TARGET_SDL
287   SetDrawtoField(DRAW_BACKBUFFER);
288 #endif
289
290 #if 0
291   /* map gadgets for main menu screen */
292   MapTapeButtons();
293 #endif
294
295   /* level_nr may have been set to value over handicap with level editor */
296   if (setup.handicap && level_nr > leveldir_current->handicap_level)
297     level_nr = leveldir_current->handicap_level;
298
299   GetPlayerConfig();
300   LoadLevel(level_nr);
301
302   SetMainBackgroundImage(IMG_BACKGROUND_MAIN);
303   ClearWindow();
304
305   DrawHeadline();
306
307   DrawText(mSX + 32, mSY + 2 * 32, name_text,       FONT_MENU_1);
308   DrawText(mSX + 32, mSY + 3 * 32, level_text,      FONT_MENU_1);
309   DrawText(mSX + 32, mSY + 4 * 32, "Hall Of Fame",  FONT_MENU_1);
310   DrawText(mSX + 32, mSY + 5 * 32, "Level Creator", FONT_MENU_1);
311   DrawText(mSX + 32, mSY + 6 * 32, "Info Screen",   FONT_MENU_1);
312   DrawText(mSX + 32, mSY + 7 * 32, "Start Game",    FONT_MENU_1);
313   DrawText(mSX + 32, mSY + 8 * 32, "Setup",         FONT_MENU_1);
314   DrawText(mSX + 32, mSY + 9 * 32, "Quit",          FONT_MENU_1);
315
316   /* calculated after (possible) reload of custom artwork */
317   name_width  = getTextWidth(name_text,  FONT_MENU_1);
318 #if 1
319   level_width = 9 * getFontWidth(FONT_MENU_1);
320 #else
321   level_width = getTextWidth(level_text, FONT_MENU_1);
322 #endif
323
324   DrawText(mSX + 32 + name_width, mSY + 2 * 32, setup.player_name,
325            FONT_INPUT_1);
326 #if 1
327   DrawText(mSX + level_width + 2 * 32, mSY + 3 * 32, int2str(level_nr, 3),
328            FONT_VALUE_1);
329 #else
330   DrawText(mSX + level_width + 5 * 32, mSY + 3 * 32, int2str(level_nr, 3),
331            FONT_VALUE_1);
332 #endif
333
334   DrawMicroLevel(MICROLEVEL_XPOS, MICROLEVEL_YPOS, TRUE);
335
336 #if 1
337
338 #if 1
339   {
340     int text_height = getFontHeight(FONT_TEXT_3);
341     int ypos2 = -SY + 3 * 32 + 16;
342     int ypos1 = ypos2 - text_height;
343
344     DrawTextF(mSX + level_width + 6 * 32, mSY + ypos1, FONT_TEXT_3,
345               "%03d", leveldir_current->first_level);
346     DrawTextF(mSX + level_width + 6 * 32, mSY + ypos2, FONT_TEXT_3,
347               "%03d", leveldir_current->last_level);
348   }
349 #else
350   DrawTextF(mSX + level_width + 6 * 32, mSY + 3 * 32 + 1, FONT_TEXT_3,
351             "%d", leveldir_current->levels);
352 #endif
353
354 #else
355   DrawTextF(mSX + 32 + level_width - 2, mSY + 3 * 32 + 1, FONT_TEXT_3, "%d-%d",
356             leveldir_current->first_level, leveldir_current->last_level);
357 #endif
358
359 #if 0
360   if (leveldir_current->readonly)
361   {
362     DrawTextS(mSX + level_width + 9 * 32 - 2,
363               mSY + 3 * 32 + 1 - 7, FONT_TEXT_3, "READ");
364     DrawTextS(mSX + level_width + 9 * 32 - 2,
365               mSY + 3 * 32 + 1 + 7, FONT_TEXT_3, "ONLY");
366   }
367 #endif
368
369   for (i = 0; i < 8; i++)
370     initCursor(i, (i == 1 || i == 4 || i == 6 ? IMG_MENU_BUTTON_ENTER_MENU :
371                    IMG_MENU_BUTTON));
372
373 #if 1
374   drawCursorXY(level_width / 32 + 1, 1, IMG_MENU_BUTTON_LEFT);
375   drawCursorXY(level_width / 32 + 5, 1, IMG_MENU_BUTTON_RIGHT);
376 #else
377   drawCursorXY(level_width / 32 + 4, 1, IMG_MENU_BUTTON_LEFT);
378   drawCursorXY(level_width / 32 + 8, 1, IMG_MENU_BUTTON_RIGHT);
379 #endif
380
381   DrawTextSCentered(326, FONT_TITLE_2, "A Game by Artsoft Entertainment");
382
383   FadeToFront();
384   InitAnimation();
385
386   HandleMainMenu(0, 0, 0, 0, MB_MENU_INITIALIZE);
387
388   TapeStop();
389   if (TAPE_IS_EMPTY(tape))
390     LoadTape(level_nr);
391   DrawCompleteVideoDisplay();
392
393   PlayMenuSound();
394   PlayMenuMusic();
395
396   OpenDoor(DOOR_CLOSE_1 | DOOR_OPEN_2);
397
398 #if 1
399   /* map gadgets for main menu screen */
400   MapTapeButtons();
401 #endif
402 }
403
404 #if 0
405 static void gotoTopLevelDir()
406 {
407   /* move upwards to top level directory */
408   while (leveldir_current->node_parent)
409   {
410     /* write a "path" into level tree for easy navigation to last level */
411     if (leveldir_current->node_parent->node_group->cl_first == -1)
412     {
413       int num_leveldirs = numTreeInfoInGroup(leveldir_current);
414       int leveldir_pos = posTreeInfo(leveldir_current);
415       int num_page_entries;
416       int cl_first, cl_cursor;
417
418       if (num_leveldirs <= NUM_MENU_ENTRIES_ON_SCREEN)
419         num_page_entries = num_leveldirs;
420       else
421         num_page_entries = NUM_MENU_ENTRIES_ON_SCREEN;
422
423       cl_first = MAX(0, leveldir_pos - num_page_entries + 1);
424       cl_cursor = leveldir_pos - cl_first;
425
426       leveldir_current->node_parent->node_group->cl_first = cl_first;
427       leveldir_current->node_parent->node_group->cl_cursor = cl_cursor;
428     }
429
430     leveldir_current = leveldir_current->node_parent;
431   }
432 }
433 #endif
434
435 void HandleMainMenu(int mx, int my, int dx, int dy, int button)
436 {
437   static unsigned long level_delay = 0;
438   static int choice = 5;
439   int x = 0;
440   int y = choice;
441
442   if (button == MB_MENU_INITIALIZE)
443   {
444     drawCursor(choice, FC_RED);
445     return;
446   }
447
448   if (mx || my)         /* mouse input */
449   {
450     x = (mx - mSX) / 32;
451     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
452   }
453   else if (dx || dy)    /* keyboard input */
454   {
455     if (dx && choice == 1)
456       x = (dx < 0 ? 10 : 14);
457     else if (dx > 0)
458     {
459       if (choice == 4 || choice == 6)
460         button = MB_MENU_CHOICE;
461     }
462     else if (dy)
463       y = choice + dy;
464   }
465
466   if (y == 1 && ((x == 10 && level_nr > leveldir_current->first_level) ||
467                  (x == 14 && level_nr < leveldir_current->last_level)) &&
468       button && DelayReached(&level_delay, GADGET_FRAME_DELAY))
469   {
470     int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
471     int old_level_nr = level_nr;
472     int new_level_nr;
473
474     new_level_nr = level_nr + (x == 10 ? -step : +step);
475     if (new_level_nr < leveldir_current->first_level)
476       new_level_nr = leveldir_current->first_level;
477     if (new_level_nr > leveldir_current->last_level)
478       new_level_nr = leveldir_current->last_level;
479
480     if (setup.handicap && new_level_nr > leveldir_current->handicap_level)
481     {
482       /* skipping levels is only allowed when trying to skip single level */
483       if (setup.skip_levels && step == 1 &&
484           Request("Level still unsolved ! Skip despite handicap ?", REQ_ASK))
485       {
486         leveldir_current->handicap_level++;
487         SaveLevelSetup_SeriesInfo();
488       }
489
490       new_level_nr = leveldir_current->handicap_level;
491     }
492
493     if (new_level_nr != old_level_nr)
494     {
495       level_nr = new_level_nr;
496
497       DrawText(mSX + 11 * 32, mSY + 3 * 32, int2str(level_nr, 3),
498                FONT_VALUE_1);
499
500       LoadLevel(level_nr);
501       DrawMicroLevel(MICROLEVEL_XPOS, MICROLEVEL_YPOS, TRUE);
502
503       TapeErase();
504       LoadTape(level_nr);
505       DrawCompleteVideoDisplay();
506
507       /* needed because DrawMicroLevel() takes some time */
508       BackToFront();
509       SyncDisplay();
510       DelayReached(&level_delay, 0);    /* reset delay counter */
511     }
512   }
513   else if (IN_VIS_FIELD(x, y) &&
514            y >= 0 && y <= 7 && (y != 1 || x < 10))
515   {
516     if (button)
517     {
518       if (y != choice)
519       {
520         drawCursor(y, FC_RED);
521         drawCursor(choice, FC_BLUE);
522         choice = y;
523       }
524     }
525     else
526     {
527       if (y == 0)
528       {
529         game_status = GAME_MODE_PSEUDO_TYPENAME;
530         HandleTypeName(strlen(setup.player_name), 0);
531       }
532       else if (y == 1)
533       {
534         if (leveldir_first)
535         {
536           game_status = GAME_MODE_LEVELS;
537           SaveLevelSetup_LastSeries();
538           SaveLevelSetup_SeriesInfo();
539
540 #if 0
541           gotoTopLevelDir();
542 #endif
543
544           DrawChooseLevel();
545         }
546       }
547       else if (y == 2)
548       {
549         game_status = GAME_MODE_SCORES;
550         DrawHallOfFame(-1);
551       }
552       else if (y == 3)
553       {
554         if (leveldir_current->readonly &&
555             strcmp(setup.player_name, "Artsoft") != 0)
556           Request("This level is read only !", REQ_CONFIRM);
557         game_status = GAME_MODE_EDITOR;
558         DrawLevelEd();
559       }
560       else if (y == 4)
561       {
562         game_status = GAME_MODE_INFO;
563         info_mode = INFO_MODE_MAIN;
564         DrawInfoScreen();
565       }
566       else if (y == 5)
567       {
568         StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
569       }
570       else if (y == 6)
571       {
572         game_status = GAME_MODE_SETUP;
573         setup_mode = SETUP_MODE_MAIN;
574
575         DrawSetupScreen();
576       }
577       else if (y == 7)
578       {
579         SaveLevelSetup_LastSeries();
580         SaveLevelSetup_SeriesInfo();
581
582         if (Request("Do you really want to quit ?", REQ_ASK | REQ_STAY_CLOSED))
583           game_status = GAME_MODE_QUIT;
584       }
585     }
586   }
587
588   if (game_status == GAME_MODE_MAIN)
589   {
590     DrawMicroLevel(MICROLEVEL_XPOS, MICROLEVEL_YPOS, FALSE);
591     DoAnimation();
592   }
593 }
594
595
596 /* ========================================================================= */
597 /* info screen functions                                                     */
598 /* ========================================================================= */
599
600 static struct TokenInfo *info_info;
601 static int num_info_info;
602
603 static void execInfoElements()
604 {
605   info_mode = INFO_MODE_ELEMENTS;
606   DrawInfoScreen();
607 }
608
609 static void execInfoMusic()
610 {
611   info_mode = INFO_MODE_MUSIC;
612   DrawInfoScreen();
613 }
614
615 static void execInfoCredits()
616 {
617   info_mode = INFO_MODE_CREDITS;
618   DrawInfoScreen();
619 }
620
621 static void execInfoProgram()
622 {
623   info_mode = INFO_MODE_PROGRAM;
624   DrawInfoScreen();
625 }
626
627 static void execInfoLevelSet()
628 {
629   info_mode = INFO_MODE_LEVELSET;
630   DrawInfoScreen();
631 }
632
633 static void execExitInfo()
634 {
635   game_status = GAME_MODE_MAIN;
636   DrawMainMenu();
637 }
638
639 static struct TokenInfo info_info_main[] =
640 {
641   { TYPE_ENTER_SCREEN,  execInfoElements,       "Elements Info"         },
642   { TYPE_ENTER_SCREEN,  execInfoMusic,          "Music Info"            },
643   { TYPE_ENTER_SCREEN,  execInfoCredits,        "Credits"               },
644   { TYPE_ENTER_SCREEN,  execInfoProgram,        "Program Info"          },
645   { TYPE_ENTER_SCREEN,  execInfoLevelSet,       "Level Set Info"        },
646   { TYPE_EMPTY,         NULL,                   ""                      },
647   { TYPE_LEAVE_MENU,    execExitInfo,           "Exit"                  },
648
649   { 0,                  NULL,                   NULL                    }
650 };
651
652 static void DrawInfoScreen_Main()
653 {
654   int i;
655
656   UnmapAllGadgets();
657   CloseDoor(DOOR_CLOSE_2);
658
659   ClearWindow();
660
661 #if 1
662   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, "Info Screen");
663 #else
664   DrawText(mSX + 16, mSY + 16, "Info Screen", FONT_TITLE_1);
665 #endif
666
667   info_info = info_info_main;
668   num_info_info = 0;
669
670   for (i = 0; info_info[i].type != 0 && i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
671   {
672     int ypos = MENU_SCREEN_START_YPOS + i;
673     int font_nr = FONT_MENU_1;
674
675     DrawText(mSX + 32, mSY + ypos * 32, info_info[i].text, font_nr);
676
677     if (info_info[i].type & TYPE_ENTER_MENU)
678       initCursor(i, IMG_MENU_BUTTON_ENTER_MENU);
679     else if (info_info[i].type & TYPE_LEAVE_MENU)
680       initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU);
681     else if (info_info[i].type & ~TYPE_SKIP_ENTRY)
682       initCursor(i, IMG_MENU_BUTTON);
683
684     num_info_info++;
685   }
686
687   FadeToFront();
688   InitAnimation();
689
690   PlayMenuSound();
691   PlayMenuMusic();
692
693   HandleInfoScreen_Main(0, 0, 0, 0, MB_MENU_INITIALIZE);
694 }
695
696 void HandleInfoScreen_Main(int mx, int my, int dx, int dy, int button)
697 {
698   static int choice_store[MAX_INFO_MODES];
699   int choice = choice_store[info_mode];         /* always starts with 0 */
700   int x = 0;
701   int y = choice;
702
703   if (button == MB_MENU_INITIALIZE)
704   {
705     /* advance to first valid menu entry */
706     while (choice < num_info_info &&
707            info_info[choice].type & TYPE_SKIP_ENTRY)
708       choice++;
709     choice_store[info_mode] = choice;
710
711     drawCursor(choice, FC_RED);
712     return;
713   }
714   else if (button == MB_MENU_LEAVE)
715   {
716     for (y = 0; y < num_info_info; y++)
717     {
718       if (info_info[y].type & TYPE_LEAVE_MENU)
719       {
720         void (*menu_callback_function)(void) = info_info[y].value;
721
722         menu_callback_function();
723         break;  /* absolutely needed because function changes 'info_info'! */
724       }
725     }
726
727     return;
728   }
729
730   if (mx || my)         /* mouse input */
731   {
732     x = (mx - mSX) / 32;
733     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
734   }
735   else if (dx || dy)    /* keyboard input */
736   {
737     if (dx)
738     {
739       int menu_navigation_type = (dx < 0 ? TYPE_LEAVE_MENU : TYPE_ENTER_MENU);
740
741       if (info_info[choice].type & menu_navigation_type ||
742           info_info[choice].type & TYPE_ENTER_SCREEN ||
743           info_info[choice].type & TYPE_BOOLEAN_STYLE)
744         button = MB_MENU_CHOICE;
745     }
746     else if (dy)
747       y = choice + dy;
748
749     /* jump to next non-empty menu entry (up or down) */
750     while (y > 0 && y < num_info_info - 1 &&
751            info_info[y].type & TYPE_SKIP_ENTRY)
752       y += dy;
753   }
754
755   if (IN_VIS_FIELD(x, y) &&
756       y >= 0 && y < num_info_info && info_info[y].type & ~TYPE_SKIP_ENTRY)
757   {
758     if (button)
759     {
760       if (y != choice)
761       {
762         drawCursor(y, FC_RED);
763         drawCursor(choice, FC_BLUE);
764         choice = choice_store[info_mode] = y;
765       }
766     }
767     else if (!(info_info[y].type & TYPE_GHOSTED))
768     {
769       if (info_info[y].type & TYPE_ENTER_OR_LEAVE_MENU)
770       {
771         void (*menu_callback_function)(void) = info_info[choice].value;
772
773         menu_callback_function();
774       }
775     }
776   }
777 }
778
779 void DrawInfoScreen_HelpAnim(int start, int max_anims, boolean init)
780 {
781   static int infoscreen_step[MAX_INFO_ELEMENTS_ON_SCREEN];
782   static int infoscreen_frame[MAX_INFO_ELEMENTS_ON_SCREEN];
783   int xstart = mSX + 16;
784   int ystart = mSY + 64 + 2 * 32;
785   int ystep = TILEY + 4;
786   int element, action, direction;
787   int graphic;
788   int delay;
789   int sync_frame;
790   int i, j;
791
792   if (init)
793   {
794     for (i = 0; i < MAX_INFO_ELEMENTS_ON_SCREEN; i++)
795       infoscreen_step[i] = infoscreen_frame[i] = 0;
796
797     ClearWindow();
798     DrawHeadline();
799
800     DrawTextSCentered(100, FONT_TEXT_1, "The Game Elements:");
801
802     DrawTextSCentered(SYSIZE - 20, FONT_TEXT_4,
803                       "Press any key or button for next page");
804
805     FrameCounter = 0;
806   }
807
808   i = j = 0;
809   while (helpanim_info[j].element != HELPANIM_LIST_END)
810   {
811     if (i >= start + MAX_INFO_ELEMENTS_ON_SCREEN ||
812         i >= max_anims)
813       break;
814     else if (i < start)
815     {
816       while (helpanim_info[j].element != HELPANIM_LIST_NEXT)
817         j++;
818
819       j++;
820       i++;
821
822       continue;
823     }
824
825     j += infoscreen_step[i - start];
826
827     element = helpanim_info[j].element;
828     action = helpanim_info[j].action;
829     direction = helpanim_info[j].direction;
830
831     if (element < 0)
832       element = EL_UNKNOWN;
833
834     if (action != -1 && direction != -1)
835       graphic = el_act_dir2img(element, action, direction);
836     else if (action != -1)
837       graphic = el_act2img(element, action);
838     else if (direction != -1)
839       graphic = el_dir2img(element, direction);
840     else
841       graphic = el2img(element);
842
843     delay = helpanim_info[j++].delay;
844
845     if (delay == -1)
846       delay = 1000000;
847
848     if (infoscreen_frame[i - start] == 0)
849     {
850       sync_frame = 0;
851       infoscreen_frame[i - start] = delay - 1;
852     }
853     else
854     {
855       sync_frame = delay - infoscreen_frame[i - start];
856       infoscreen_frame[i - start]--;
857     }
858
859     if (helpanim_info[j].element == HELPANIM_LIST_NEXT)
860     {
861       if (!infoscreen_frame[i - start])
862         infoscreen_step[i - start] = 0;
863     }
864     else
865     {
866       if (!infoscreen_frame[i - start])
867         infoscreen_step[i - start]++;
868       while (helpanim_info[j].element != HELPANIM_LIST_NEXT)
869         j++;
870     }
871
872     j++;
873
874     ClearRectangleOnBackground(drawto, xstart, ystart + (i - start) * ystep,
875                                TILEX, TILEY);
876     DrawGraphicAnimationExt(drawto, xstart, ystart + (i - start) * ystep,
877                             graphic, sync_frame, USE_MASKING);
878
879     if (init)
880       DrawInfoScreen_HelpText(element, action, direction, i - start);
881
882     i++;
883   }
884
885   redraw_mask |= REDRAW_FIELD;
886
887   FrameCounter++;
888 }
889
890 static char *getHelpText(int element, int action, int direction)
891 {
892   char token[MAX_LINE_LEN];
893
894   strcpy(token, element_info[element].token_name);
895
896   if (action != -1)
897     strcat(token, element_action_info[action].suffix);
898
899   if (direction != -1)
900     strcat(token, element_direction_info[MV_DIR_TO_BIT(direction)].suffix);
901
902   return getHashEntry(helptext_info, token);
903 }
904
905 void DrawInfoScreen_HelpText(int element, int action, int direction, int ypos)
906 {
907   int font_nr = FONT_LEVEL_NUMBER;
908   int font_width = getFontWidth(font_nr);
909   int sx = mSX + MINI_TILEX + TILEX + MINI_TILEX;
910   int sy = mSY + 65 + 2 * 32 + 1;
911   int ystep = TILEY + 4;
912   int pad_x = sx - SX;
913   int max_chars_per_line = (SXSIZE - pad_x - MINI_TILEX) / font_width;
914   int max_lines_per_text = 2;    
915   char *text = NULL;
916
917   if (action != -1 && direction != -1)          /* element.action.direction */
918     text = getHelpText(element, action, direction);
919
920   if (text == NULL && action != -1)             /* element.action */
921     text = getHelpText(element, action, -1);
922
923   if (text == NULL && direction != -1)          /* element.direction */
924     text = getHelpText(element, -1, direction);
925
926   if (text == NULL)                             /* base element */
927     text = getHelpText(element, -1, -1);
928
929   if (text == NULL)                             /* not found */
930     text = "No description available";
931
932   if (strlen(text) <= max_chars_per_line)       /* only one line of text */
933     sy += getFontHeight(font_nr) / 2;
934
935   DrawTextWrapped(sx, sy + ypos * ystep, text, font_nr,
936                   max_chars_per_line, max_lines_per_text);
937 }
938
939 void DrawInfoScreen_Elements()
940 {
941   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_ELEMENTS);
942
943   LoadHelpAnimInfo();
944   LoadHelpTextInfo();
945
946   HandleInfoScreen_Elements(MB_MENU_INITIALIZE);
947
948   FadeToFront();
949   InitAnimation();
950 }
951
952 void HandleInfoScreen_Elements(int button)
953 {
954   static unsigned long info_delay = 0;
955   static int num_anims;
956   static int num_pages;
957   static int page;
958   int anims_per_page = MAX_INFO_ELEMENTS_ON_SCREEN;
959   int button_released = !button;
960   int i;
961
962   if (button == MB_MENU_INITIALIZE)
963   {
964     boolean new_element = TRUE;
965
966     num_anims = 0;
967     for (i = 0; helpanim_info[i].element != HELPANIM_LIST_END; i++)
968     {
969       if (helpanim_info[i].element == HELPANIM_LIST_NEXT)
970         new_element = TRUE;
971       else if (new_element)
972       {
973         num_anims++;
974         new_element = FALSE;
975       }
976     }
977
978     num_pages = (num_anims + anims_per_page - 1) / anims_per_page;
979     page = 0;
980   }
981   else if (button == MB_MENU_LEAVE)
982   {
983     info_mode = INFO_MODE_MAIN;
984     DrawInfoScreen();
985
986     return;
987   }
988
989   if (button_released || button == MB_MENU_INITIALIZE)
990   {
991     if (button != MB_MENU_INITIALIZE)
992       page++;
993
994     if (page >= num_pages)
995     {
996       FadeSoundsAndMusic();
997
998       info_mode = INFO_MODE_MAIN;
999       DrawInfoScreen();
1000
1001       return;
1002     }
1003
1004     DrawInfoScreen_HelpAnim(page * anims_per_page, num_anims, TRUE);
1005   }
1006   else
1007   {
1008     if (DelayReached(&info_delay, GameFrameDelay))
1009       if (page < num_pages)
1010         DrawInfoScreen_HelpAnim(page * anims_per_page, num_anims, FALSE);
1011
1012     PlayMenuSoundIfLoop();
1013   }
1014 }
1015
1016 void DrawInfoScreen_Music()
1017 {
1018   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_MUSIC);
1019
1020   ClearWindow();
1021   DrawHeadline();
1022
1023   LoadMusicInfo();
1024
1025   HandleInfoScreen_Music(MB_MENU_INITIALIZE);
1026 }
1027
1028 void HandleInfoScreen_Music(int button)
1029 {
1030   static struct MusicFileInfo *list = NULL;
1031   int ystart = 150, dy = 30;
1032   int ybottom = SYSIZE - 20;
1033   int button_released = !button;
1034
1035   if (button == MB_MENU_INITIALIZE)
1036   {
1037     list = music_file_info;
1038
1039     if (list == NULL)
1040     {
1041       FadeSoundsAndMusic();
1042
1043       ClearWindow();
1044       DrawHeadline();
1045
1046       DrawTextSCentered(100, FONT_TEXT_1, "No music info for this level set.");
1047
1048       DrawTextSCentered(ybottom, FONT_TEXT_4,
1049                         "Press any key or button for info menu");
1050
1051       return;
1052     }
1053   }
1054   else if (button == MB_MENU_LEAVE)
1055   {
1056     info_mode = INFO_MODE_MAIN;
1057     DrawInfoScreen();
1058
1059     return;
1060   }
1061
1062   if (button_released || button == MB_MENU_INITIALIZE)
1063   {
1064     int y = 0;
1065
1066     if (button != MB_MENU_INITIALIZE)
1067       if (list != NULL)
1068         list = list->next;
1069
1070     if (list == NULL)
1071     {
1072       info_mode = INFO_MODE_MAIN;
1073       DrawInfoScreen();
1074
1075       return;
1076     }
1077
1078     FadeSoundsAndMusic();
1079
1080     ClearWindow();
1081     DrawHeadline();
1082
1083     if (list->is_sound)
1084     {
1085       int sound = list->music;
1086
1087       if (sound_info[sound].loop)
1088         PlaySoundLoop(sound);
1089       else
1090         PlaySound(sound);
1091
1092       DrawTextSCentered(100, FONT_TEXT_1, "The Game Background Sounds:");
1093     }
1094     else
1095     {
1096       PlayMusic(list->music);
1097
1098       DrawTextSCentered(100, FONT_TEXT_1, "The Game Background Music:");
1099     }
1100
1101     if (strcmp(list->title, UNKNOWN_NAME) != 0)
1102     {
1103       if (strcmp(list->title_header, UNKNOWN_NAME) != 0)
1104         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, list->title_header);
1105
1106       DrawTextFCentered(ystart + y++ * dy, FONT_TEXT_3, "\"%s\"", list->title);
1107     }
1108
1109     if (strcmp(list->artist, UNKNOWN_NAME) != 0)
1110     {
1111       if (strcmp(list->artist_header, UNKNOWN_NAME) != 0)
1112         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, list->artist_header);
1113       else
1114         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, "by");
1115
1116       DrawTextFCentered(ystart + y++ * dy, FONT_TEXT_3, "%s", list->artist);
1117     }
1118
1119     if (strcmp(list->album, UNKNOWN_NAME) != 0)
1120     {
1121       if (strcmp(list->album_header, UNKNOWN_NAME) != 0)
1122         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, list->album_header);
1123       else
1124         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, "from the album");
1125
1126       DrawTextFCentered(ystart + y++ * dy, FONT_TEXT_3, "\"%s\"", list->album);
1127     }
1128
1129     if (strcmp(list->year, UNKNOWN_NAME) != 0)
1130     {
1131       if (strcmp(list->year_header, UNKNOWN_NAME) != 0)
1132         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, list->year_header);
1133       else
1134         DrawTextSCentered(ystart + y++ * dy, FONT_TEXT_2, "from the year");
1135
1136       DrawTextFCentered(ystart + y++ * dy, FONT_TEXT_3, "%s", list->year);
1137     }
1138
1139     DrawTextSCentered(ybottom, FONT_TEXT_4,
1140                       "Press any key or button for next page");
1141   }
1142
1143   if (list != NULL && list->is_sound && sound_info[list->music].loop)
1144     PlaySoundLoop(list->music);
1145 }
1146
1147 void DrawInfoScreen_Credits()
1148 {
1149   int ystart = 150, ystep = 30;
1150   int ybottom = SYSIZE - 20;
1151
1152   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_CREDITS);
1153
1154   FadeSoundsAndMusic();
1155
1156   ClearWindow();
1157   DrawHeadline();
1158
1159   DrawTextSCentered(100, FONT_TEXT_1, "Credits:");
1160   DrawTextSCentered(ystart + 0 * ystep, FONT_TEXT_2, "DOS port of the game:");
1161   DrawTextSCentered(ystart + 1 * ystep, FONT_TEXT_3, "Guido Schulz");
1162   DrawTextSCentered(ystart + 2 * ystep, FONT_TEXT_2, "Additional toons:");
1163   DrawTextSCentered(ystart + 3 * ystep, FONT_TEXT_3, "Karl Hörnell");
1164   DrawTextSCentered(ystart + 5 * ystep, FONT_TEXT_2,
1165                     "...and many thanks to all contributors");
1166   DrawTextSCentered(ystart + 6 * ystep, FONT_TEXT_2, "of new levels!");
1167
1168   DrawTextSCentered(ybottom, FONT_TEXT_4,
1169                     "Press any key or button for info menu");
1170 }
1171
1172 void HandleInfoScreen_Credits(int button)
1173 {
1174   int button_released = !button;
1175
1176   if (button == MB_MENU_LEAVE)
1177   {
1178     info_mode = INFO_MODE_MAIN;
1179     DrawInfoScreen();
1180
1181     return;
1182   }
1183
1184   if (button_released)
1185   {
1186     FadeSoundsAndMusic();
1187
1188     info_mode = INFO_MODE_MAIN;
1189     DrawInfoScreen();
1190   }
1191   else
1192   {
1193     PlayMenuSoundIfLoop();
1194   }
1195 }
1196
1197 void DrawInfoScreen_Program()
1198 {
1199   int ystart = 150, ystep = 30;
1200   int ybottom = SYSIZE - 20;
1201
1202   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_PROGRAM);
1203
1204   ClearWindow();
1205   DrawHeadline();
1206
1207   DrawTextSCentered(100, FONT_TEXT_1, "Program Information:");
1208
1209   DrawTextSCentered(ystart + 0 * ystep, FONT_TEXT_2,
1210                     "This game is Freeware!");
1211   DrawTextSCentered(ystart + 1 * ystep, FONT_TEXT_2,
1212                     "If you like it, send e-mail to:");
1213   DrawTextSCentered(ystart + 2 * ystep, FONT_TEXT_3,
1214                     "info@artsoft.org");
1215   DrawTextSCentered(ystart + 3 * ystep, FONT_TEXT_2,
1216                     "or SnailMail to:");
1217   DrawTextSCentered(ystart + 4 * ystep + 0, FONT_TEXT_3,
1218                     "Holger Schemel");
1219   DrawTextSCentered(ystart + 4 * ystep + 20, FONT_TEXT_3,
1220                     "Detmolder Strasse 189");
1221   DrawTextSCentered(ystart + 4 * ystep + 40, FONT_TEXT_3,
1222                     "33604 Bielefeld");
1223   DrawTextSCentered(ystart + 4 * ystep + 60, FONT_TEXT_3,
1224                     "Germany");
1225
1226   DrawTextSCentered(ystart + 7 * ystep, FONT_TEXT_2,
1227                     "If you have created new levels,");
1228   DrawTextSCentered(ystart + 8 * ystep, FONT_TEXT_2,
1229                     "send them to me to include them!");
1230   DrawTextSCentered(ystart + 9 * ystep, FONT_TEXT_2,
1231                     ":-)");
1232
1233   DrawTextSCentered(ybottom, FONT_TEXT_4,
1234                     "Press any key or button for info menu");
1235 }
1236
1237 void HandleInfoScreen_Program(int button)
1238 {
1239   int button_released = !button;
1240
1241   if (button == MB_MENU_LEAVE)
1242   {
1243     info_mode = INFO_MODE_MAIN;
1244     DrawInfoScreen();
1245
1246     return;
1247   }
1248
1249   if (button_released)
1250   {
1251     FadeSoundsAndMusic();
1252
1253     info_mode = INFO_MODE_MAIN;
1254     DrawInfoScreen();
1255   }
1256   else
1257   {
1258     PlayMenuSoundIfLoop();
1259   }
1260 }
1261
1262 void DrawInfoScreen_LevelSet()
1263 {
1264   int ystart = 150;
1265   int ybottom = SYSIZE - 20;
1266   char *filename = getLevelSetInfoFilename();
1267   int font_nr = FONT_LEVEL_NUMBER;
1268   int font_width = getFontWidth(font_nr);
1269   int font_height = getFontHeight(font_nr);
1270   int pad_x = 32;
1271   int pad_y = ystart;
1272   int sx = SX + pad_x;
1273   int sy = SY + pad_y;
1274   int max_chars_per_line = (SXSIZE - 2 * pad_x) / font_width;
1275   int max_lines_per_screen = (SYSIZE - pad_y) / font_height - 1;
1276
1277   SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_LEVELSET);
1278
1279   ClearWindow();
1280   DrawHeadline();
1281
1282   DrawTextSCentered(100, FONT_TEXT_1, "Level Set Information:");
1283
1284   DrawTextSCentered(ybottom, FONT_TEXT_4,
1285                     "Press any key or button for info menu");
1286
1287   if (filename != NULL)
1288     DrawTextFromFile(sx, sy, filename, font_nr, max_chars_per_line,
1289                      max_lines_per_screen);
1290   else
1291     DrawTextSCentered(ystart, FONT_TEXT_2,
1292                       "No information for this level set.");
1293 }
1294
1295 void HandleInfoScreen_LevelSet(int button)
1296 {
1297   int button_released = !button;
1298
1299   if (button == MB_MENU_LEAVE)
1300   {
1301     info_mode = INFO_MODE_MAIN;
1302     DrawInfoScreen();
1303
1304     return;
1305   }
1306
1307   if (button_released)
1308   {
1309     FadeSoundsAndMusic();
1310
1311     info_mode = INFO_MODE_MAIN;
1312     DrawInfoScreen();
1313   }
1314   else
1315   {
1316     PlayMenuSoundIfLoop();
1317   }
1318 }
1319
1320 void DrawInfoScreen()
1321 {
1322   SetMainBackgroundImage(IMG_BACKGROUND_INFO);
1323
1324   if (info_mode == INFO_MODE_ELEMENTS)
1325     DrawInfoScreen_Elements();
1326   else if (info_mode == INFO_MODE_MUSIC)
1327     DrawInfoScreen_Music();
1328   else if (info_mode == INFO_MODE_CREDITS)
1329     DrawInfoScreen_Credits();
1330   else if (info_mode == INFO_MODE_PROGRAM)
1331     DrawInfoScreen_Program();
1332   else if (info_mode == INFO_MODE_LEVELSET)
1333     DrawInfoScreen_LevelSet();
1334   else
1335     DrawInfoScreen_Main();
1336
1337   if (info_mode != INFO_MODE_MUSIC)
1338   {
1339     PlayMenuSound();
1340     PlayMenuMusic();
1341   }
1342 }
1343
1344 void HandleInfoScreen(int mx, int my, int dx, int dy, int button)
1345 {
1346   if (info_mode == INFO_MODE_ELEMENTS)
1347     HandleInfoScreen_Elements(button);
1348   else if (info_mode == INFO_MODE_MUSIC)
1349     HandleInfoScreen_Music(button);
1350   else if (info_mode == INFO_MODE_CREDITS)
1351     HandleInfoScreen_Credits(button);
1352   else if (info_mode == INFO_MODE_PROGRAM)
1353     HandleInfoScreen_Program(button);
1354   else if (info_mode == INFO_MODE_LEVELSET)
1355     HandleInfoScreen_LevelSet(button);
1356   else
1357     HandleInfoScreen_Main(mx, my, dx, dy, button);
1358
1359   DoAnimation();
1360 }
1361
1362
1363 /* ========================================================================= */
1364 /* type name functions                                                       */
1365 /* ========================================================================= */
1366
1367 void HandleTypeName(int newxpos, Key key)
1368 {
1369   static int xpos = 0, ypos = 2;
1370   int font_width = getFontWidth(FONT_INPUT_1_ACTIVE);
1371   int name_width = getFontWidth(FONT_MENU_1) * strlen("Name:");
1372   int startx = mSX + 32 + name_width;
1373   int starty = mSY + ypos * 32;
1374
1375   if (newxpos)
1376   {
1377     xpos = newxpos;
1378
1379     DrawText(startx, starty, setup.player_name, FONT_INPUT_1_ACTIVE);
1380     DrawText(startx + xpos * font_width, starty, "_", FONT_INPUT_1_ACTIVE);
1381
1382     return;
1383   }
1384
1385   if (((key >= KSYM_A && key <= KSYM_Z) ||
1386        (key >= KSYM_a && key <= KSYM_z)) && 
1387       xpos < MAX_PLAYER_NAME_LEN)
1388   {
1389     char ascii;
1390
1391     if (key >= KSYM_A && key <= KSYM_Z)
1392       ascii = 'A' + (char)(key - KSYM_A);
1393     else
1394       ascii = 'a' + (char)(key - KSYM_a);
1395
1396     setup.player_name[xpos] = ascii;
1397     setup.player_name[xpos + 1] = 0;
1398     xpos++;
1399
1400     DrawText(startx, starty, setup.player_name, FONT_INPUT_1_ACTIVE);
1401     DrawText(startx + xpos * font_width, starty, "_", FONT_INPUT_1_ACTIVE);
1402   }
1403   else if ((key == KSYM_Delete || key == KSYM_BackSpace) && xpos > 0)
1404   {
1405     xpos--;
1406     setup.player_name[xpos] = 0;
1407
1408     DrawText(startx + xpos * font_width, starty, "_ ", FONT_INPUT_1_ACTIVE);
1409   }
1410   else if (key == KSYM_Return && xpos > 0)
1411   {
1412     DrawText(startx, starty, setup.player_name, FONT_INPUT_1);
1413     DrawText(startx + xpos * font_width, starty, " ", FONT_INPUT_1_ACTIVE);
1414
1415     SaveSetup();
1416     game_status = GAME_MODE_MAIN;
1417   }
1418 }
1419
1420
1421 /* ========================================================================= */
1422 /* tree menu functions                                                       */
1423 /* ========================================================================= */
1424
1425 static void DrawChooseTree(TreeInfo **ti_ptr)
1426 {
1427   UnmapAllGadgets();
1428
1429   FreeScreenGadgets();
1430   CreateScreenGadgets();
1431
1432   CloseDoor(DOOR_CLOSE_2);
1433
1434   ClearWindow();
1435
1436   HandleChooseTree(0, 0, 0, 0, MB_MENU_INITIALIZE, ti_ptr);
1437   MapChooseTreeGadgets(*ti_ptr);
1438
1439   FadeToFront();
1440   InitAnimation();
1441 }
1442
1443 static void AdjustChooseTreeScrollbar(int id, int first_entry, TreeInfo *ti)
1444 {
1445   struct GadgetInfo *gi = screen_gadget[id];
1446   int items_max, items_visible, item_position;
1447
1448   items_max = numTreeInfoInGroup(ti);
1449   items_visible = NUM_MENU_ENTRIES_ON_SCREEN;
1450   item_position = first_entry;
1451
1452   if (item_position > items_max - items_visible)
1453     item_position = items_max - items_visible;
1454
1455   ModifyGadget(gi, GDI_SCROLLBAR_ITEMS_MAX, items_max,
1456                GDI_SCROLLBAR_ITEMS_VISIBLE, items_visible,
1457                GDI_SCROLLBAR_ITEM_POSITION, item_position, GDI_END);
1458 }
1459
1460 static void drawChooseTreeList(int first_entry, int num_page_entries,
1461                                TreeInfo *ti)
1462 {
1463   int i;
1464   char buffer[SCR_FIELDX * 2];
1465   int max_buffer_len = (SCR_FIELDX - 2) * 2;
1466   char *title_string = NULL;
1467 #if 0
1468   int xoffset_sets = 16;
1469 #endif
1470   int yoffset_sets = MENU_TITLE1_YPOS;
1471 #if 0
1472   int xoffset_setup = 16;
1473 #endif
1474   int yoffset_setup = 16;
1475 #if 1
1476 #if 0
1477   int xoffset = (ti->type == TREE_TYPE_LEVEL_DIR ? xoffset_sets :
1478                  xoffset_setup);
1479 #endif
1480   int yoffset = (ti->type == TREE_TYPE_LEVEL_DIR ? yoffset_sets :
1481                  yoffset_setup);
1482 #else
1483   int xoffset = (ti->type == TREE_TYPE_LEVEL_DIR ? 0 : xoffset_setup);
1484   int yoffset = (ti->type == TREE_TYPE_LEVEL_DIR ? 0 : yoffset_setup);
1485 #endif
1486   int last_game_status = game_status;   /* save current game status */
1487
1488   title_string =
1489     (ti->type == TREE_TYPE_LEVEL_DIR ? "Level Sets" :
1490      ti->type == TREE_TYPE_GRAPHICS_DIR ? "Custom Graphics" :
1491      ti->type == TREE_TYPE_SOUNDS_DIR ? "Custom Sounds" :
1492      ti->type == TREE_TYPE_MUSIC_DIR ? "Custom Music" : "");
1493
1494 #if 1
1495   DrawTextSCentered(mSY - SY + yoffset, FONT_TITLE_1, title_string);
1496 #else
1497   DrawText(SX + xoffset, SY + yoffset, title_string, FONT_TITLE_1);
1498 #endif
1499
1500   /* force LEVELS font on artwork setup screen */
1501   game_status = GAME_MODE_LEVELS;
1502
1503   /* clear tree list area, but not title or scrollbar */
1504   DrawBackground(mSX, mSY + MENU_SCREEN_START_YPOS * 32,
1505                  SXSIZE - 32 + menu.scrollbar_xoffset,
1506                  MAX_MENU_ENTRIES_ON_SCREEN * 32);
1507
1508   for (i = 0; i < num_page_entries; i++)
1509   {
1510     TreeInfo *node, *node_first;
1511     int entry_pos = first_entry + i;
1512     int ypos = MENU_SCREEN_START_YPOS + i;
1513
1514     node_first = getTreeInfoFirstGroupEntry(ti);
1515     node = getTreeInfoFromPos(node_first, entry_pos);
1516
1517     strncpy(buffer, node->name, max_buffer_len);
1518     buffer[max_buffer_len] = '\0';
1519
1520     DrawText(mSX + 32, mSY + ypos * 32, buffer, FONT_TEXT_1 + node->color);
1521
1522     if (node->parent_link)
1523       initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU);
1524     else if (node->level_group)
1525       initCursor(i, IMG_MENU_BUTTON_ENTER_MENU);
1526     else
1527       initCursor(i, IMG_MENU_BUTTON);
1528   }
1529
1530   game_status = last_game_status;       /* restore current game status */
1531
1532   redraw_mask |= REDRAW_FIELD;
1533 }
1534
1535 static void drawChooseTreeInfo(int entry_pos, TreeInfo *ti)
1536 {
1537   TreeInfo *node, *node_first;
1538   int x, last_redraw_mask = redraw_mask;
1539 #if 1
1540   int ypos = MENU_TITLE2_YPOS;
1541 #else
1542   int ypos = 40;
1543 #endif
1544
1545   if (ti->type != TREE_TYPE_LEVEL_DIR)
1546     return;
1547
1548   node_first = getTreeInfoFirstGroupEntry(ti);
1549   node = getTreeInfoFromPos(node_first, entry_pos);
1550
1551   DrawBackground(SX, SY + ypos, SXSIZE, getFontHeight(FONT_TITLE_2));
1552
1553   if (node->parent_link)
1554     DrawTextFCentered(ypos, FONT_TITLE_2, "leave group \"%s\"",
1555                       node->class_desc);
1556   else if (node->level_group)
1557     DrawTextFCentered(ypos, FONT_TITLE_2, "enter group \"%s\"",
1558                       node->class_desc);
1559   else if (ti->type == TREE_TYPE_LEVEL_DIR)
1560     DrawTextFCentered(ypos, FONT_TITLE_2, "%3d levels (%s)",
1561                       node->levels, node->class_desc);
1562
1563   /* let BackToFront() redraw only what is needed */
1564   redraw_mask = last_redraw_mask | REDRAW_TILES;
1565   for (x = 0; x < SCR_FIELDX; x++)
1566     MarkTileDirty(x, 1);
1567 }
1568
1569 static void HandleChooseTree(int mx, int my, int dx, int dy, int button,
1570                              TreeInfo **ti_ptr)
1571 {
1572   TreeInfo *ti = *ti_ptr;
1573   int x = 0;
1574   int y = ti->cl_cursor;
1575   int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
1576   int num_entries = numTreeInfoInGroup(ti);
1577   int num_page_entries;
1578   int last_game_status = game_status;   /* save current game status */
1579   boolean position_set_by_scrollbar = (dx == 999);
1580
1581   /* force LEVELS draw offset on choose level and artwork setup screen */
1582   game_status = GAME_MODE_LEVELS;
1583
1584   if (num_entries <= NUM_MENU_ENTRIES_ON_SCREEN)
1585     num_page_entries = num_entries;
1586   else
1587     num_page_entries = NUM_MENU_ENTRIES_ON_SCREEN;
1588
1589   game_status = last_game_status;       /* restore current game status */
1590
1591   if (button == MB_MENU_INITIALIZE)
1592   {
1593     int num_entries = numTreeInfoInGroup(ti);
1594     int entry_pos = posTreeInfo(ti);
1595
1596     if (ti->cl_first == -1)
1597     {
1598       /* only on initialization */
1599       ti->cl_first = MAX(0, entry_pos - num_page_entries + 1);
1600       ti->cl_cursor = entry_pos - ti->cl_first;
1601     }
1602     else if (ti->cl_cursor >= num_page_entries ||
1603              (num_entries > num_page_entries &&
1604               num_entries - ti->cl_first < num_page_entries))
1605     {
1606       /* only after change of list size (by custom graphic configuration) */
1607       ti->cl_first = MAX(0, entry_pos - num_page_entries + 1);
1608       ti->cl_cursor = entry_pos - ti->cl_first;
1609     }
1610
1611     if (position_set_by_scrollbar)
1612       ti->cl_first = dy;
1613     else
1614       AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
1615                                 ti->cl_first, ti);
1616
1617     drawChooseTreeList(ti->cl_first, num_page_entries, ti);
1618     drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti);
1619     drawChooseTreeCursor(ti->cl_cursor, FC_RED);
1620
1621     return;
1622   }
1623   else if (button == MB_MENU_LEAVE)
1624   {
1625     if (ti->node_parent)
1626     {
1627       *ti_ptr = ti->node_parent;
1628       DrawChooseTree(ti_ptr);
1629     }
1630     else if (game_status == GAME_MODE_SETUP)
1631     {
1632       execSetupArtwork();
1633     }
1634     else
1635     {
1636       game_status = GAME_MODE_MAIN;
1637       DrawMainMenu();
1638     }
1639
1640     return;
1641   }
1642
1643   if (mx || my)         /* mouse input */
1644   {
1645     int last_game_status = game_status; /* save current game status */
1646
1647     /* force LEVELS draw offset on artwork setup screen */
1648     game_status = GAME_MODE_LEVELS;
1649
1650     x = (mx - mSX) / 32;
1651     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
1652
1653     game_status = last_game_status;     /* restore current game status */
1654   }
1655   else if (dx || dy)    /* keyboard or scrollbar/scrollbutton input */
1656   {
1657     /* move cursor instead of scrolling when already at start/end of list */
1658     if (dy == -1 * SCROLL_LINE && ti->cl_first == 0)
1659       dy = -1;
1660     else if (dy == +1 * SCROLL_LINE &&
1661              ti->cl_first + num_page_entries == num_entries)
1662       dy = 1;
1663
1664     /* handle scrolling screen one line or page */
1665     if (ti->cl_cursor + dy < 0 ||
1666         ti->cl_cursor + dy > num_page_entries - 1)
1667     {
1668       if (ABS(dy) == SCROLL_PAGE)
1669         step = num_page_entries - 1;
1670
1671       if (dy < 0 && ti->cl_first > 0)
1672       {
1673         /* scroll page/line up */
1674
1675         ti->cl_first -= step;
1676         if (ti->cl_first < 0)
1677           ti->cl_first = 0;
1678
1679         drawChooseTreeList(ti->cl_first, num_page_entries, ti);
1680         drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti);
1681         drawChooseTreeCursor(ti->cl_cursor, FC_RED);
1682         AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
1683                                   ti->cl_first, ti);
1684       }
1685       else if (dy > 0 && ti->cl_first + num_page_entries < num_entries)
1686       {
1687         /* scroll page/line down */
1688
1689         ti->cl_first += step;
1690         if (ti->cl_first + num_page_entries > num_entries)
1691           ti->cl_first = MAX(0, num_entries - num_page_entries);
1692
1693         drawChooseTreeList(ti->cl_first, num_page_entries, ti);
1694         drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti);
1695         drawChooseTreeCursor(ti->cl_cursor, FC_RED);
1696         AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
1697                                   ti->cl_first, ti);
1698       }
1699
1700       return;
1701     }
1702
1703     /* handle moving cursor one line */
1704     y = ti->cl_cursor + dy;
1705   }
1706
1707   if (dx == 1)
1708   {
1709     TreeInfo *node_first, *node_cursor;
1710     int entry_pos = ti->cl_first + y;
1711
1712     node_first = getTreeInfoFirstGroupEntry(ti);
1713     node_cursor = getTreeInfoFromPos(node_first, entry_pos);
1714
1715     if (node_cursor->node_group)
1716     {
1717       node_cursor->cl_first = ti->cl_first;
1718       node_cursor->cl_cursor = ti->cl_cursor;
1719       *ti_ptr = node_cursor->node_group;
1720       DrawChooseTree(ti_ptr);
1721
1722       return;
1723     }
1724   }
1725   else if (dx == -1 && ti->node_parent)
1726   {
1727     *ti_ptr = ti->node_parent;
1728     DrawChooseTree(ti_ptr);
1729
1730     return;
1731   }
1732
1733   if (!anyScrollbarGadgetActive() &&
1734       IN_VIS_FIELD(x, y) &&
1735       mx < screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->x &&
1736       y >= 0 && y < num_page_entries)
1737   {
1738     if (button)
1739     {
1740       if (y != ti->cl_cursor)
1741       {
1742         drawChooseTreeCursor(y, FC_RED);
1743         drawChooseTreeCursor(ti->cl_cursor, FC_BLUE);
1744         drawChooseTreeInfo(ti->cl_first + y, ti);
1745         ti->cl_cursor = y;
1746       }
1747     }
1748     else
1749     {
1750       TreeInfo *node_first, *node_cursor;
1751       int entry_pos = ti->cl_first + y;
1752
1753       node_first = getTreeInfoFirstGroupEntry(ti);
1754       node_cursor = getTreeInfoFromPos(node_first, entry_pos);
1755
1756       if (node_cursor->node_group)
1757       {
1758         node_cursor->cl_first = ti->cl_first;
1759         node_cursor->cl_cursor = ti->cl_cursor;
1760         *ti_ptr = node_cursor->node_group;
1761         DrawChooseTree(ti_ptr);
1762       }
1763       else if (node_cursor->parent_link)
1764       {
1765         *ti_ptr = node_cursor->node_parent;
1766         DrawChooseTree(ti_ptr);
1767       }
1768       else
1769       {
1770         node_cursor->cl_first = ti->cl_first;
1771         node_cursor->cl_cursor = ti->cl_cursor;
1772         *ti_ptr = node_cursor;
1773
1774         if (ti->type == TREE_TYPE_LEVEL_DIR)
1775         {
1776           LoadLevelSetup_SeriesInfo();
1777
1778           SaveLevelSetup_LastSeries();
1779           SaveLevelSetup_SeriesInfo();
1780           TapeErase();
1781         }
1782
1783         if (game_status == GAME_MODE_SETUP)
1784         {
1785           execSetupArtwork();
1786         }
1787         else
1788         {
1789           game_status = GAME_MODE_MAIN;
1790           DrawMainMenu();
1791         }
1792       }
1793     }
1794   }
1795 }
1796
1797 void DrawChooseLevel()
1798 {
1799   SetMainBackgroundImage(IMG_BACKGROUND_LEVELS);
1800
1801   DrawChooseTree(&leveldir_current);
1802
1803   PlayMenuSound();
1804   PlayMenuMusic();
1805 }
1806
1807 void HandleChooseLevel(int mx, int my, int dx, int dy, int button)
1808 {
1809   HandleChooseTree(mx, my, dx, dy, button, &leveldir_current);
1810
1811   DoAnimation();
1812 }
1813
1814 void DrawHallOfFame(int highlight_position)
1815 {
1816   UnmapAllGadgets();
1817   FadeSoundsAndMusic();
1818   CloseDoor(DOOR_CLOSE_2);
1819
1820   if (highlight_position < 0) 
1821     LoadScore(level_nr);
1822
1823   FadeToFront();
1824   InitAnimation();
1825
1826   PlayMenuSound();
1827   PlayMenuMusic();
1828
1829   HandleHallOfFame(highlight_position, 0, 0, 0, MB_MENU_INITIALIZE);
1830 }
1831
1832 static void drawHallOfFameList(int first_entry, int highlight_position)
1833 {
1834   int i;
1835
1836   SetMainBackgroundImage(IMG_BACKGROUND_SCORES);
1837   ClearWindow();
1838
1839 #if 1
1840   DrawTextSCentered(MENU_TITLE1_YPOS, FONT_TITLE_1, "Hall Of Fame");
1841   DrawTextFCentered(MENU_TITLE2_YPOS, FONT_TITLE_2,
1842                     "HighScores of Level %d", level_nr);
1843 #else
1844   DrawText(mSX + 80, mSY + MENU_TITLE1_YPOS, "Hall Of Fame", FONT_TITLE_1);
1845   DrawTextFCentered(MENU_TITLE2_YPOS, FONT_TITLE_2,
1846                     "HighScores of Level %d", level_nr);
1847 #endif
1848
1849   for (i = 0; i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
1850   {
1851     int entry = first_entry + i;
1852     boolean active = (entry == highlight_position);
1853     int font_nr1 = (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1);
1854     int font_nr2 = (active ? FONT_TEXT_2_ACTIVE : FONT_TEXT_2);
1855     int font_nr3 = (active ? FONT_TEXT_3_ACTIVE : FONT_TEXT_3);
1856     int font_nr4 = (active ? FONT_TEXT_4_ACTIVE : FONT_TEXT_4);
1857     int dx1 = 3 * getFontWidth(font_nr1);
1858     int dx2 = dx1 + getFontWidth(font_nr1);
1859     int dx3 = dx2 + 25 * getFontWidth(font_nr3);
1860     int sy = mSY + 64 + i * 32;
1861
1862     DrawText(mSX, sy, int2str(entry + 1, 3), font_nr1);
1863     DrawText(mSX + dx1, sy, ".", font_nr1);
1864     DrawText(mSX + dx2, sy, ".........................", font_nr3);
1865     if (strcmp(highscore[entry].Name, EMPTY_PLAYER_NAME) != 0)
1866       DrawText(mSX + dx2, sy, highscore[entry].Name, font_nr2);
1867     DrawText(mSX + dx3, sy, int2str(highscore[entry].Score, 5), font_nr4);
1868   }
1869
1870   redraw_mask |= REDRAW_FIELD;
1871 }
1872
1873 void HandleHallOfFame(int mx, int my, int dx, int dy, int button)
1874 {
1875   static int first_entry = 0;
1876   static int highlight_position = 0;
1877   int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
1878   int button_released = !button;
1879
1880   if (button == MB_MENU_INITIALIZE)
1881   {
1882     first_entry = 0;
1883     highlight_position = mx;
1884     drawHallOfFameList(first_entry, highlight_position);
1885
1886     return;
1887   }
1888
1889   if (ABS(dy) == SCROLL_PAGE)           /* handle scrolling one page */
1890     step = NUM_MENU_ENTRIES_ON_SCREEN - 1;
1891
1892   if (dy < 0)
1893   {
1894     if (first_entry > 0)
1895     {
1896       first_entry -= step;
1897       if (first_entry < 0)
1898         first_entry = 0;
1899
1900       drawHallOfFameList(first_entry, highlight_position);
1901     }
1902   }
1903   else if (dy > 0)
1904   {
1905     if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN < MAX_SCORE_ENTRIES)
1906     {
1907       first_entry += step;
1908       if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN > MAX_SCORE_ENTRIES)
1909         first_entry = MAX(0, MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN);
1910
1911       drawHallOfFameList(first_entry, highlight_position);
1912     }
1913   }
1914   else if (button_released)
1915   {
1916     FadeSound(SND_BACKGROUND_SCORES);
1917     game_status = GAME_MODE_MAIN;
1918     DrawMainMenu();
1919   }
1920
1921   if (game_status == GAME_MODE_SCORES)
1922     PlayMenuSoundIfLoop();
1923
1924   DoAnimation();
1925 }
1926
1927
1928 /* ========================================================================= */
1929 /* setup screen functions                                                    */
1930 /* ========================================================================= */
1931
1932 static struct TokenInfo *setup_info;
1933 static int num_setup_info;
1934
1935 static char *graphics_set_name;
1936 static char *sounds_set_name;
1937 static char *music_set_name;
1938
1939 static void execSetupMain()
1940 {
1941   setup_mode = SETUP_MODE_MAIN;
1942   DrawSetupScreen();
1943 }
1944
1945 static void execSetupGame()
1946 {
1947   setup_mode = SETUP_MODE_GAME;
1948   DrawSetupScreen();
1949 }
1950
1951 static void execSetupEditor()
1952 {
1953   setup_mode = SETUP_MODE_EDITOR;
1954   DrawSetupScreen();
1955 }
1956
1957 static void execSetupGraphics()
1958 {
1959   setup_mode = SETUP_MODE_GRAPHICS;
1960   DrawSetupScreen();
1961 }
1962
1963 static void execSetupSound()
1964 {
1965   setup_mode = SETUP_MODE_SOUND;
1966   DrawSetupScreen();
1967 }
1968
1969 static void execSetupArtwork()
1970 {
1971   setup.graphics_set = artwork.gfx_current->identifier;
1972   setup.sounds_set = artwork.snd_current->identifier;
1973   setup.music_set = artwork.mus_current->identifier;
1974
1975   /* needed if last screen (setup choice) changed graphics, sounds or music */
1976   ReloadCustomArtwork(0);
1977
1978   /* needed for displaying artwork name instead of artwork identifier */
1979   graphics_set_name = artwork.gfx_current->name;
1980   sounds_set_name = artwork.snd_current->name;
1981   music_set_name = artwork.mus_current->name;
1982
1983   setup_mode = SETUP_MODE_ARTWORK;
1984   DrawSetupScreen();
1985 }
1986
1987 static void execSetupChooseGraphics()
1988 {
1989   setup_mode = SETUP_MODE_CHOOSE_GRAPHICS;
1990   DrawSetupScreen();
1991 }
1992
1993 static void execSetupChooseSounds()
1994 {
1995   setup_mode = SETUP_MODE_CHOOSE_SOUNDS;
1996   DrawSetupScreen();
1997 }
1998
1999 static void execSetupChooseMusic()
2000 {
2001   setup_mode = SETUP_MODE_CHOOSE_MUSIC;
2002   DrawSetupScreen();
2003 }
2004
2005 static void execSetupInput()
2006 {
2007   setup_mode = SETUP_MODE_INPUT;
2008   DrawSetupScreen();
2009 }
2010
2011 static void execSetupShortcut1()
2012 {
2013   setup_mode = SETUP_MODE_SHORTCUT_1;
2014   DrawSetupScreen();
2015 }
2016
2017 static void execSetupShortcut2()
2018 {
2019   setup_mode = SETUP_MODE_SHORTCUT_2;
2020   DrawSetupScreen();
2021 }
2022
2023 static void execExitSetup()
2024 {
2025   game_status = GAME_MODE_MAIN;
2026   DrawMainMenu();
2027 }
2028
2029 static void execSaveAndExitSetup()
2030 {
2031   SaveSetup();
2032   execExitSetup();
2033 }
2034
2035 static struct TokenInfo setup_info_main[] =
2036 {
2037   { TYPE_ENTER_MENU,    execSetupGame,          "Game & Menu"           },
2038   { TYPE_ENTER_MENU,    execSetupEditor,        "Editor"                },
2039   { TYPE_ENTER_MENU,    execSetupGraphics,      "Graphics"              },
2040   { TYPE_ENTER_MENU,    execSetupSound,         "Sound & Music"         },
2041   { TYPE_ENTER_MENU,    execSetupArtwork,       "Custom Artwork"        },
2042   { TYPE_ENTER_MENU,    execSetupInput,         "Input Devices"         },
2043   { TYPE_ENTER_MENU,    execSetupShortcut1,     "Key Shortcuts 1"       },
2044   { TYPE_ENTER_MENU,    execSetupShortcut2,     "Key Shortcuts 2"       },
2045   { TYPE_EMPTY,         NULL,                   ""                      },
2046   { TYPE_LEAVE_MENU,    execExitSetup,          "Exit"                  },
2047   { TYPE_LEAVE_MENU,    execSaveAndExitSetup,   "Save and Exit"         },
2048
2049   { 0,                  NULL,                   NULL                    }
2050 };
2051
2052 static struct TokenInfo setup_info_game[] =
2053 {
2054   { TYPE_SWITCH,        &setup.team_mode,       "Team-Mode (Multi-Player):" },
2055   { TYPE_YES_NO,        &setup.input_on_focus,  "Only Move Focussed Player:" },
2056   { TYPE_SWITCH,        &setup.handicap,        "Handicap:"             },
2057   { TYPE_SWITCH,        &setup.skip_levels,     "Skip Unsolved Levels:" },
2058   { TYPE_SWITCH,        &setup.time_limit,      "Time Limit:"           },
2059   { TYPE_SWITCH,        &setup.autorecord,      "Auto-Record Tapes:"    },
2060   { TYPE_EMPTY,         NULL,                   ""                      },
2061   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2062
2063   { 0,                  NULL,                   NULL                    }
2064 };
2065
2066 static struct TokenInfo setup_info_editor[] =
2067 {
2068   { TYPE_SWITCH,        &setup.editor.el_boulderdash,   "Boulder Dash:" },
2069   { TYPE_SWITCH,        &setup.editor.el_emerald_mine,  "Emerald Mine:" },
2070   { TYPE_SWITCH, &setup.editor.el_emerald_mine_club,    "Emerald Mine Club:" },
2071   { TYPE_SWITCH,        &setup.editor.el_more,          "Rocks'n'Diamonds:" },
2072   { TYPE_SWITCH,        &setup.editor.el_sokoban,       "Sokoban:"      },
2073   { TYPE_SWITCH,        &setup.editor.el_supaplex,      "Supaplex:"     },
2074   { TYPE_SWITCH,        &setup.editor.el_diamond_caves, "Diamond Caves II:" },
2075   { TYPE_SWITCH,        &setup.editor.el_dx_boulderdash,"DX-Boulderdash:" },
2076   { TYPE_SWITCH,        &setup.editor.el_chars,         "Text Characters:" },
2077   { TYPE_SWITCH,        &setup.editor.el_custom,  "Custom & Group Elements:" },
2078   { TYPE_SWITCH,        &setup.editor.el_headlines,     "Headlines:"    },
2079   { TYPE_SWITCH, &setup.editor.el_user_defined, "User defined element list:" },
2080   { TYPE_SWITCH,        &setup.editor.el_dynamic,  "Dynamic level elements:" },
2081   { TYPE_EMPTY,         NULL,                   ""                      },
2082   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2083
2084   { 0,                  NULL,                   NULL                    }
2085 };
2086
2087 static struct TokenInfo setup_info_graphics[] =
2088 {
2089   { TYPE_SWITCH,        &setup.fullscreen,      "Fullscreen Mode:"      },
2090   { TYPE_SWITCH,        &setup.scroll_delay,    "Delayed Scrolling:"    },
2091   { TYPE_SWITCH,        &setup.soft_scrolling,  "Soft Scrolling:"       },
2092 #if 0
2093   { TYPE_SWITCH,        &setup.double_buffering,"Double-Buffering:"     },
2094   { TYPE_SWITCH,        &setup.fading,          "Fading:"               },
2095 #endif
2096   { TYPE_SWITCH,        &setup.quick_switch,    "Quick Player Focus Switch:" },
2097   { TYPE_SWITCH,        &setup.quick_doors,     "Quick Menu Doors:"     },
2098   { TYPE_SWITCH,        &setup.toons,           "Toons:"                },
2099   { TYPE_EMPTY,         NULL,                   ""                      },
2100   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2101
2102   { 0,                  NULL,                   NULL                    }
2103 };
2104
2105 static struct TokenInfo setup_info_sound[] =
2106 {
2107   { TYPE_SWITCH,        &setup.sound_simple,    "Sound Effects (Normal):"  },
2108   { TYPE_SWITCH,        &setup.sound_loops,     "Sound Effects (Looping):" },
2109   { TYPE_SWITCH,        &setup.sound_music,     "Music:"                },
2110   { TYPE_EMPTY,         NULL,                   ""                      },
2111   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2112
2113   { 0,                  NULL,                   NULL                    }
2114 };
2115
2116 static struct TokenInfo setup_info_artwork[] =
2117 {
2118   { TYPE_ENTER_MENU,    execSetupChooseGraphics,"Custom Graphics"       },
2119   { TYPE_STRING,        &graphics_set_name,     ""                      },
2120   { TYPE_ENTER_MENU,    execSetupChooseSounds,  "Custom Sounds"         },
2121   { TYPE_STRING,        &sounds_set_name,       ""                      },
2122   { TYPE_ENTER_MENU,    execSetupChooseMusic,   "Custom Music"          },
2123   { TYPE_STRING,        &music_set_name,        ""                      },
2124   { TYPE_EMPTY,         NULL,                   ""                      },
2125 #if 1
2126   { TYPE_YES_NO, &setup.override_level_graphics,"Override Level Graphics:" },
2127   { TYPE_YES_NO, &setup.override_level_sounds,  "Override Level Sounds:"   },
2128   { TYPE_YES_NO, &setup.override_level_music,   "Override Level Music:"    },
2129 #else
2130   { TYPE_STRING,        NULL,                   "Override Level Artwork:"},
2131   { TYPE_YES_NO,        &setup.override_level_graphics, "Graphics:"     },
2132   { TYPE_YES_NO,        &setup.override_level_sounds,   "Sounds:"       },
2133   { TYPE_YES_NO,        &setup.override_level_music,    "Music:"        },
2134 #endif
2135   { TYPE_EMPTY,         NULL,                   ""                      },
2136   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2137
2138   { 0,                  NULL,                   NULL                    }
2139 };
2140
2141 static struct TokenInfo setup_info_shortcut_1[] =
2142 {
2143   { TYPE_KEYTEXT,       NULL,           "Quick Save Game to Tape:",     },
2144   { TYPE_KEY,           &setup.shortcut.save_game, ""                   },
2145   { TYPE_KEYTEXT,       NULL,           "Quick Load Game from Tape:",   },
2146   { TYPE_KEY,           &setup.shortcut.load_game, ""                   },
2147   { TYPE_KEYTEXT,       NULL,           "Start Game & Toggle Pause:",   },
2148   { TYPE_KEY,           &setup.shortcut.toggle_pause, ""                },
2149   { TYPE_EMPTY,         NULL,                   ""                      },
2150   { TYPE_YES_NO,        &setup.ask_on_escape,   "Ask on 'Esc' Key:"     },
2151   { TYPE_YES_NO, &setup.ask_on_escape_editor,   "Ask on 'Esc' Key (Editor):" },
2152   { TYPE_EMPTY,         NULL,                   ""                      },
2153   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2154
2155   { 0,                  NULL,                   NULL                    }
2156 };
2157
2158 static struct TokenInfo setup_info_shortcut_2[] =
2159 {
2160   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 1:",       },
2161   { TYPE_KEY,           &setup.shortcut.focus_player[0], ""             },
2162   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 2:",       },
2163   { TYPE_KEY,           &setup.shortcut.focus_player[1], ""             },
2164   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 3:",       },
2165   { TYPE_KEY,           &setup.shortcut.focus_player[2], ""             },
2166   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 4:",       },
2167   { TYPE_KEY,           &setup.shortcut.focus_player[3], ""             },
2168   { TYPE_KEYTEXT,       NULL,           "Set Focus to All Players:",    },
2169   { TYPE_KEY,           &setup.shortcut.focus_player_all, ""            },
2170   { TYPE_EMPTY,         NULL,                   ""                      },
2171   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2172
2173   { 0,                  NULL,                   NULL                    }
2174 };
2175
2176 static Key getSetupKey()
2177 {
2178   Key key = KSYM_UNDEFINED;
2179   boolean got_key_event = FALSE;
2180
2181   while (!got_key_event)
2182   {
2183     if (PendingEvent())         /* got event */
2184     {
2185       Event event;
2186
2187       NextEvent(&event);
2188
2189       switch(event.type)
2190       {
2191         case EVENT_KEYPRESS:
2192           {
2193             key = GetEventKey((KeyEvent *)&event, TRUE);
2194
2195             /* press 'Escape' or 'Enter' to keep the existing key binding */
2196             if (key == KSYM_Escape || key == KSYM_Return)
2197               key = KSYM_UNDEFINED;     /* keep old value */
2198
2199             got_key_event = TRUE;
2200           }
2201           break;
2202
2203         case EVENT_KEYRELEASE:
2204           key_joystick_mapping = 0;
2205           break;
2206
2207         default:
2208           HandleOtherEvents(&event);
2209           break;
2210       }
2211     }
2212
2213     DoAnimation();
2214     BackToFront();
2215
2216     /* don't eat all CPU time */
2217     Delay(10);
2218   }
2219
2220   return key;
2221 }
2222
2223 static void drawSetupValue(int pos)
2224 {
2225   int xpos = MENU_SCREEN_VALUE_XPOS;
2226   int ypos = MENU_SCREEN_START_YPOS + pos;
2227   int font_nr = FONT_VALUE_1;
2228   int type = setup_info[pos].type;
2229   void *value = setup_info[pos].value;
2230   char *value_string = getSetupValue(type, value);
2231   int i;
2232
2233   if (value_string == NULL)
2234     return;
2235
2236   if (type & TYPE_KEY)
2237   {
2238 #if 1
2239     xpos = 1;
2240 #else
2241     xpos = 3;
2242 #endif
2243
2244     if (type & TYPE_QUERY)
2245     {
2246       value_string = "<press key>";
2247       font_nr = FONT_INPUT_1_ACTIVE;
2248     }
2249   }
2250   else if (type & TYPE_STRING)
2251   {
2252     int max_value_len = (SCR_FIELDX - 2) * 2;
2253
2254     xpos = 1;
2255     font_nr = FONT_VALUE_2;
2256
2257     if (strlen(value_string) > max_value_len)
2258       value_string[max_value_len] = '\0';
2259   }
2260   else if (type & TYPE_BOOLEAN_STYLE)
2261   {
2262     font_nr = (*(boolean *)value ? FONT_OPTION_ON : FONT_OPTION_OFF);
2263   }
2264
2265 #if 1
2266   for (i = xpos; i <= MENU_SCREEN_MAX_XPOS; i++)
2267     DrawText(mSX + i * 32, mSY + ypos * 32, " ", font_nr);
2268 #else
2269   DrawText(mSX + xpos * 32, mSY + ypos * 32,
2270            (xpos == 3 ? "              " : "   "), font_nr);
2271 #endif
2272
2273   DrawText(mSX + xpos * 32, mSY + ypos * 32, value_string, font_nr);
2274 }
2275
2276 static void changeSetupValue(int pos)
2277 {
2278   if (setup_info[pos].type & TYPE_BOOLEAN_STYLE)
2279   {
2280     *(boolean *)setup_info[pos].value ^= TRUE;
2281   }
2282   else if (setup_info[pos].type & TYPE_KEY)
2283   {
2284     Key key;
2285
2286     setup_info[pos].type |= TYPE_QUERY;
2287     drawSetupValue(pos);
2288     setup_info[pos].type &= ~TYPE_QUERY;
2289
2290     key = getSetupKey();
2291     if (key != KSYM_UNDEFINED)
2292       *(Key *)setup_info[pos].value = key;
2293   }
2294
2295   drawSetupValue(pos);
2296 }
2297
2298 static void DrawSetupScreen_Generic()
2299 {
2300   char *title_string = NULL;
2301   int i;
2302
2303   UnmapAllGadgets();
2304   CloseDoor(DOOR_CLOSE_2);
2305
2306   ClearWindow();
2307
2308   if (setup_mode == SETUP_MODE_MAIN)
2309   {
2310     setup_info = setup_info_main;
2311     title_string = "Setup";
2312   }
2313   else if (setup_mode == SETUP_MODE_GAME)
2314   {
2315     setup_info = setup_info_game;
2316     title_string = "Setup Game";
2317   }
2318   else if (setup_mode == SETUP_MODE_EDITOR)
2319   {
2320     setup_info = setup_info_editor;
2321     title_string = "Setup Editor";
2322   }
2323   else if (setup_mode == SETUP_MODE_GRAPHICS)
2324   {
2325     setup_info = setup_info_graphics;
2326     title_string = "Setup Graphics";
2327   }
2328   else if (setup_mode == SETUP_MODE_SOUND)
2329   {
2330     setup_info = setup_info_sound;
2331     title_string = "Setup Sound";
2332   }
2333   else if (setup_mode == SETUP_MODE_ARTWORK)
2334   {
2335     setup_info = setup_info_artwork;
2336     title_string = "Custom Artwork";
2337   }
2338   else if (setup_mode == SETUP_MODE_SHORTCUT_1)
2339   {
2340     setup_info = setup_info_shortcut_1;
2341     title_string = "Setup Shortcuts";
2342   }
2343   else if (setup_mode == SETUP_MODE_SHORTCUT_2)
2344   {
2345     setup_info = setup_info_shortcut_2;
2346     title_string = "Setup Shortcuts";
2347   }
2348
2349 #if 1
2350   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, title_string);
2351 #else
2352   DrawText(mSX + 16, mSY + 16, title_string, FONT_TITLE_1);
2353 #endif
2354
2355   num_setup_info = 0;
2356   for (i = 0; setup_info[i].type != 0 && i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
2357   {
2358     void *value_ptr = setup_info[i].value;
2359     int ypos = MENU_SCREEN_START_YPOS + i;
2360     int font_nr = FONT_MENU_1;
2361
2362     /* set some entries to "unchangeable" according to other variables */
2363     if ((value_ptr == &setup.sound_simple && !audio.sound_available) ||
2364         (value_ptr == &setup.sound_loops  && !audio.loops_available) ||
2365         (value_ptr == &setup.sound_music  && !audio.music_available) ||
2366         (value_ptr == &setup.fullscreen   && !video.fullscreen_available))
2367       setup_info[i].type |= TYPE_GHOSTED;
2368
2369 #if 1
2370     if (setup_info[i].type & (TYPE_SWITCH |
2371                               TYPE_YES_NO |
2372                               TYPE_STRING |
2373                               TYPE_KEYTEXT))
2374       font_nr = FONT_MENU_2;
2375 #else
2376     if (setup_info[i].type & TYPE_STRING)
2377       font_nr = FONT_MENU_2;
2378 #endif
2379
2380     DrawText(mSX + 32, mSY + ypos * 32, setup_info[i].text, font_nr);
2381
2382     if (setup_info[i].type & TYPE_ENTER_MENU)
2383       initCursor(i, IMG_MENU_BUTTON_ENTER_MENU);
2384     else if (setup_info[i].type & TYPE_LEAVE_MENU)
2385       initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU);
2386     else if (setup_info[i].type & ~TYPE_SKIP_ENTRY)
2387       initCursor(i, IMG_MENU_BUTTON);
2388
2389     if (setup_info[i].type & TYPE_VALUE)
2390       drawSetupValue(i);
2391
2392     num_setup_info++;
2393   }
2394
2395 #if 0
2396   DrawTextSCentered(SYSIZE - 20, FONT_TEXT_4,
2397                     "Joysticks deactivated in setup menu");
2398 #endif
2399
2400   FadeToFront();
2401   InitAnimation();
2402   HandleSetupScreen_Generic(0, 0, 0, 0, MB_MENU_INITIALIZE);
2403 }
2404
2405 void HandleSetupScreen_Generic(int mx, int my, int dx, int dy, int button)
2406 {
2407   static int choice_store[MAX_SETUP_MODES];
2408   int choice = choice_store[setup_mode];        /* always starts with 0 */
2409   int x = 0;
2410   int y = choice;
2411
2412   if (button == MB_MENU_INITIALIZE)
2413   {
2414     /* advance to first valid menu entry */
2415     while (choice < num_setup_info &&
2416            setup_info[choice].type & TYPE_SKIP_ENTRY)
2417       choice++;
2418     choice_store[setup_mode] = choice;
2419
2420     drawCursor(choice, FC_RED);
2421     return;
2422   }
2423   else if (button == MB_MENU_LEAVE)
2424   {
2425     for (y = 0; y < num_setup_info; y++)
2426     {
2427       if (setup_info[y].type & TYPE_LEAVE_MENU)
2428       {
2429         void (*menu_callback_function)(void) = setup_info[y].value;
2430
2431         menu_callback_function();
2432         break;  /* absolutely needed because function changes 'setup_info'! */
2433       }
2434     }
2435
2436     return;
2437   }
2438
2439   if (mx || my)         /* mouse input */
2440   {
2441     x = (mx - mSX) / 32;
2442     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
2443   }
2444   else if (dx || dy)    /* keyboard input */
2445   {
2446     if (dx)
2447     {
2448       int menu_navigation_type = (dx < 0 ? TYPE_LEAVE_MENU : TYPE_ENTER_MENU);
2449
2450       if (setup_info[choice].type & menu_navigation_type ||
2451           setup_info[choice].type & TYPE_BOOLEAN_STYLE)
2452         button = MB_MENU_CHOICE;
2453     }
2454     else if (dy)
2455       y = choice + dy;
2456
2457     /* jump to next non-empty menu entry (up or down) */
2458     while (y > 0 && y < num_setup_info - 1 &&
2459            setup_info[y].type & TYPE_SKIP_ENTRY)
2460       y += dy;
2461   }
2462
2463   if (IN_VIS_FIELD(x, y) &&
2464       y >= 0 && y < num_setup_info && setup_info[y].type & ~TYPE_SKIP_ENTRY)
2465   {
2466     if (button)
2467     {
2468       if (y != choice)
2469       {
2470         drawCursor(y, FC_RED);
2471         drawCursor(choice, FC_BLUE);
2472         choice = choice_store[setup_mode] = y;
2473       }
2474     }
2475     else if (!(setup_info[y].type & TYPE_GHOSTED))
2476     {
2477       if (setup_info[y].type & TYPE_ENTER_OR_LEAVE_MENU)
2478       {
2479         void (*menu_callback_function)(void) = setup_info[choice].value;
2480
2481         menu_callback_function();
2482       }
2483       else
2484       {
2485         if (setup_info[y].type & TYPE_KEYTEXT &&
2486             setup_info[y + 1].type & TYPE_KEY)
2487           y++;
2488
2489         if (setup_info[y].type & TYPE_VALUE)
2490           changeSetupValue(y);
2491       }
2492     }
2493   }
2494 }
2495
2496 void DrawSetupScreen_Input()
2497 {
2498   ClearWindow();
2499
2500 #if 1
2501   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, "Setup Input");
2502 #else
2503   DrawText(mSX + 16, mSY + 16, "Setup Input", FONT_TITLE_1);
2504 #endif
2505
2506   initCursor(0,  IMG_MENU_BUTTON);
2507   initCursor(1,  IMG_MENU_BUTTON);
2508   initCursor(2,  IMG_MENU_BUTTON_ENTER_MENU);
2509   initCursor(13, IMG_MENU_BUTTON_LEAVE_MENU);
2510
2511   drawCursorXY(10, 0, IMG_MENU_BUTTON_LEFT);
2512   drawCursorXY(12, 0, IMG_MENU_BUTTON_RIGHT);
2513
2514   DrawText(mSX + 32, mSY +  2 * 32, "Player:", FONT_MENU_1);
2515   DrawText(mSX + 32, mSY +  3 * 32, "Device:", FONT_MENU_1);
2516   DrawText(mSX + 32, mSY + 15 * 32, "Back",   FONT_MENU_1);
2517
2518 #if 0
2519   DeactivateJoystickForCalibration();
2520 #endif
2521 #if 1
2522   DrawTextSCentered(SYSIZE - 20, FONT_TEXT_4,
2523                     "Joysticks deactivated on this screen");
2524 #endif
2525
2526   HandleSetupScreen_Input(0, 0, 0, 0, MB_MENU_INITIALIZE);
2527   FadeToFront();
2528   InitAnimation();
2529 }
2530
2531 static void setJoystickDeviceToNr(char *device_name, int device_nr)
2532 {
2533   if (device_name == NULL)
2534     return;
2535
2536   if (device_nr < 0 || device_nr >= MAX_PLAYERS)
2537     device_nr = 0;
2538
2539   if (strlen(device_name) > 1)
2540   {
2541     char c1 = device_name[strlen(device_name) - 1];
2542     char c2 = device_name[strlen(device_name) - 2];
2543
2544     if (c1 >= '0' && c1 <= '9' && !(c2 >= '0' && c2 <= '9'))
2545       device_name[strlen(device_name) - 1] = '0' + (char)(device_nr % 10);
2546   }
2547   else
2548     strncpy(device_name, getDeviceNameFromJoystickNr(device_nr),
2549             strlen(device_name));
2550 }
2551
2552 static void drawPlayerSetupInputInfo(int player_nr)
2553 {
2554   int i;
2555   static struct SetupKeyboardInfo custom_key;
2556   static struct
2557   {
2558     Key *key;
2559     char *text;
2560   } custom[] =
2561   {
2562     { &custom_key.left,  "Joystick Left"  },
2563     { &custom_key.right, "Joystick Right" },
2564     { &custom_key.up,    "Joystick Up"    },
2565     { &custom_key.down,  "Joystick Down"  },
2566     { &custom_key.snap,  "Button 1"       },
2567     { &custom_key.drop,  "Button 2"       }
2568   };
2569   static char *joystick_name[MAX_PLAYERS] =
2570   {
2571     "Joystick1",
2572     "Joystick2",
2573     "Joystick3",
2574     "Joystick4"
2575   };
2576
2577   InitJoysticks();
2578
2579   custom_key = setup.input[player_nr].key;
2580
2581   DrawText(mSX + 11 * 32, mSY + 2 * 32, int2str(player_nr + 1, 1),
2582            FONT_INPUT_1_ACTIVE);
2583
2584   ClearRectangleOnBackground(drawto, mSX + 8 * TILEX, mSY + 2 * TILEY,
2585                              TILEX, TILEY);
2586   DrawGraphicThruMaskExt(drawto, mSX + 8 * TILEX, mSY + 2 * TILEY,
2587                          PLAYER_NR_GFX(IMG_PLAYER_1, player_nr), 0);
2588
2589   if (setup.input[player_nr].use_joystick)
2590   {
2591     char *device_name = setup.input[player_nr].joy.device_name;
2592     char *text = joystick_name[getJoystickNrFromDeviceName(device_name)];
2593     int font_nr = (joystick.fd[player_nr] < 0 ? FONT_VALUE_OLD : FONT_VALUE_1);
2594
2595     DrawText(mSX + 8 * 32, mSY + 3 * 32, text, font_nr);
2596     DrawText(mSX + 32, mSY + 4 * 32, "Calibrate", FONT_MENU_1);
2597   }
2598   else
2599   {
2600     DrawText(mSX + 8 * 32, mSY + 3 * 32, "Keyboard ", FONT_VALUE_1);
2601     DrawText(mSX + 1 * 32, mSY + 4 * 32, "Customize", FONT_MENU_1);
2602   }
2603
2604   DrawText(mSX + 32, mSY + 5 * 32, "Actual Settings:", FONT_MENU_1);
2605   drawCursorXY(1, 4, IMG_MENU_BUTTON_LEFT);
2606   drawCursorXY(1, 5, IMG_MENU_BUTTON_RIGHT);
2607   drawCursorXY(1, 6, IMG_MENU_BUTTON_UP);
2608   drawCursorXY(1, 7, IMG_MENU_BUTTON_DOWN);
2609   DrawText(mSX + 2 * 32, mSY +  6 * 32, ":", FONT_VALUE_OLD);
2610   DrawText(mSX + 2 * 32, mSY +  7 * 32, ":", FONT_VALUE_OLD);
2611   DrawText(mSX + 2 * 32, mSY +  8 * 32, ":", FONT_VALUE_OLD);
2612   DrawText(mSX + 2 * 32, mSY +  9 * 32, ":", FONT_VALUE_OLD);
2613   DrawText(mSX + 1 * 32, mSY + 10 * 32, "Snap Field:", FONT_VALUE_OLD);
2614   DrawText(mSX + 1 * 32, mSY + 12 * 32, "Drop Element:", FONT_VALUE_OLD);
2615
2616   for (i = 0; i < 6; i++)
2617   {
2618     int ypos = 6 + i + (i > 3 ? i-3 : 0);
2619
2620     DrawText(mSX + 3 * 32, mSY + ypos * 32,
2621              "              ", FONT_VALUE_1);
2622     DrawText(mSX + 3 * 32, mSY + ypos * 32,
2623              (setup.input[player_nr].use_joystick ?
2624               custom[i].text :
2625               getKeyNameFromKey(*custom[i].key)), FONT_VALUE_1);
2626   }
2627 }
2628
2629 void HandleSetupScreen_Input(int mx, int my, int dx, int dy, int button)
2630 {
2631   static int choice = 0;
2632   static int player_nr = 0;
2633   int x = 0;
2634   int y = choice;
2635   int pos_start  = SETUPINPUT_SCREEN_POS_START;
2636   int pos_empty1 = SETUPINPUT_SCREEN_POS_EMPTY1;
2637   int pos_empty2 = SETUPINPUT_SCREEN_POS_EMPTY2;
2638   int pos_end    = SETUPINPUT_SCREEN_POS_END;
2639
2640   if (button == MB_MENU_INITIALIZE)
2641   {
2642     drawPlayerSetupInputInfo(player_nr);
2643     drawCursor(choice, FC_RED);
2644
2645     return;
2646   }
2647   else if (button == MB_MENU_LEAVE)
2648   {
2649     setup_mode = SETUP_MODE_MAIN;
2650     DrawSetupScreen();
2651     InitJoysticks();
2652
2653     return;
2654   }
2655
2656   if (mx || my)         /* mouse input */
2657   {
2658     x = (mx - mSX) / 32;
2659     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
2660   }
2661   else if (dx || dy)    /* keyboard input */
2662   {
2663     if (dx && choice == 0)
2664       x = (dx < 0 ? 10 : 12);
2665     else if ((dx && choice == 1) ||
2666              (dx == +1 && choice == 2) ||
2667              (dx == -1 && choice == pos_end))
2668       button = MB_MENU_CHOICE;
2669     else if (dy)
2670       y = choice + dy;
2671
2672     if (y >= pos_empty1 && y <= pos_empty2)
2673       y = (dy > 0 ? pos_empty2 + 1 : pos_empty1 - 1);
2674   }
2675
2676   if (IN_VIS_FIELD(x, y) &&
2677       y == 0 && ((x < 10 && !button) || ((x == 10 || x == 12) && button)))
2678   {
2679     static unsigned long delay = 0;
2680
2681     if (!DelayReached(&delay, GADGET_FRAME_DELAY))
2682       return;
2683
2684     player_nr = (player_nr + (x == 10 ? -1 : +1) + MAX_PLAYERS) % MAX_PLAYERS;
2685
2686     drawPlayerSetupInputInfo(player_nr);
2687   }
2688   else if (IN_VIS_FIELD(x, y) &&
2689            y >= pos_start && y <= pos_end &&
2690            !(y >= pos_empty1 && y <= pos_empty2))
2691   {
2692     if (button)
2693     {
2694       if (y != choice)
2695       {
2696         drawCursor(y, FC_RED);
2697         drawCursor(choice, FC_BLUE);
2698         choice = y;
2699       }
2700     }
2701     else
2702     {
2703       if (y == 1)
2704       {
2705         char *device_name = setup.input[player_nr].joy.device_name;
2706
2707         if (!setup.input[player_nr].use_joystick)
2708         {
2709           int new_device_nr = (dx >= 0 ? 0 : MAX_PLAYERS - 1);
2710
2711           setJoystickDeviceToNr(device_name, new_device_nr);
2712           setup.input[player_nr].use_joystick = TRUE;
2713         }
2714         else
2715         {
2716           int device_nr = getJoystickNrFromDeviceName(device_name);
2717           int new_device_nr = device_nr + (dx >= 0 ? +1 : -1);
2718
2719           if (new_device_nr < 0 || new_device_nr >= MAX_PLAYERS)
2720             setup.input[player_nr].use_joystick = FALSE;
2721           else
2722             setJoystickDeviceToNr(device_name, new_device_nr);
2723         }
2724
2725         drawPlayerSetupInputInfo(player_nr);
2726       }
2727       else if (y == 2)
2728       {
2729         if (setup.input[player_nr].use_joystick)
2730         {
2731           InitJoysticks();
2732           CalibrateJoystick(player_nr);
2733         }
2734         else
2735           CustomizeKeyboard(player_nr);
2736       }
2737       else if (y == pos_end)
2738       {
2739         InitJoysticks();
2740
2741         setup_mode = SETUP_MODE_MAIN;
2742         DrawSetupScreen();
2743       }
2744     }
2745   }
2746 }
2747
2748 void CustomizeKeyboard(int player_nr)
2749 {
2750   int i;
2751   int step_nr;
2752   boolean finished = FALSE;
2753   static struct SetupKeyboardInfo custom_key;
2754   static struct
2755   {
2756     Key *key;
2757     char *text;
2758   } customize_step[] =
2759   {
2760     { &custom_key.left,  "Move Left"    },
2761     { &custom_key.right, "Move Right"   },
2762     { &custom_key.up,    "Move Up"      },
2763     { &custom_key.down,  "Move Down"    },
2764     { &custom_key.snap,  "Snap Field"   },
2765     { &custom_key.drop,  "Drop Element" }
2766   };
2767
2768   /* read existing key bindings from player setup */
2769   custom_key = setup.input[player_nr].key;
2770
2771   ClearWindow();
2772
2773 #if 1
2774   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, "Keyboard Input");
2775 #else
2776   DrawText(mSX + 16, mSY + 16, "Keyboard Input", FONT_TITLE_1);
2777 #endif
2778
2779   BackToFront();
2780   InitAnimation();
2781
2782   step_nr = 0;
2783   DrawText(mSX, mSY + (2 + 2 * step_nr) * 32,
2784            customize_step[step_nr].text, FONT_INPUT_1_ACTIVE);
2785   DrawText(mSX, mSY + (2 + 2 * step_nr + 1) * 32,
2786            "Key:", FONT_INPUT_1_ACTIVE);
2787   DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
2788            getKeyNameFromKey(*customize_step[step_nr].key), FONT_VALUE_OLD);
2789
2790   while (!finished)
2791   {
2792     if (PendingEvent())         /* got event */
2793     {
2794       Event event;
2795
2796       NextEvent(&event);
2797
2798       switch(event.type)
2799       {
2800         case EVENT_KEYPRESS:
2801           {
2802             Key key = GetEventKey((KeyEvent *)&event, FALSE);
2803
2804             if (key == KSYM_Escape || (key == KSYM_Return && step_nr == 6))
2805             {
2806               finished = TRUE;
2807               break;
2808             }
2809
2810             /* all keys configured -- wait for "Escape" or "Return" key */
2811             if (step_nr == 6)
2812               break;
2813
2814             /* press 'Enter' to keep the existing key binding */
2815             if (key == KSYM_Return)
2816               key = *customize_step[step_nr].key;
2817
2818             /* check if key already used */
2819             for (i = 0; i < step_nr; i++)
2820               if (*customize_step[i].key == key)
2821                 break;
2822             if (i < step_nr)
2823               break;
2824
2825             /* got new key binding */
2826             *customize_step[step_nr].key = key;
2827             DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
2828                      "             ", FONT_VALUE_1);
2829             DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
2830                      getKeyNameFromKey(key), FONT_VALUE_1);
2831             step_nr++;
2832
2833             /* un-highlight last query */
2834             DrawText(mSX, mSY + (2 + 2 * (step_nr - 1)) * 32,
2835                      customize_step[step_nr - 1].text, FONT_MENU_1);
2836             DrawText(mSX, mSY + (2 + 2 * (step_nr - 1) + 1) * 32,
2837                      "Key:", FONT_MENU_1);
2838
2839             /* press 'Enter' to leave */
2840             if (step_nr == 6)
2841             {
2842               DrawText(mSX + 16, mSY + 15 * 32 + 16,
2843                        "Press Enter", FONT_TITLE_1);
2844               break;
2845             }
2846
2847             /* query next key binding */
2848             DrawText(mSX, mSY + (2 + 2 * step_nr) * 32,
2849                      customize_step[step_nr].text, FONT_INPUT_1_ACTIVE);
2850             DrawText(mSX, mSY + (2 + 2 * step_nr + 1) * 32,
2851                      "Key:", FONT_INPUT_1_ACTIVE);
2852             DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
2853                      getKeyNameFromKey(*customize_step[step_nr].key),
2854                      FONT_VALUE_OLD);
2855           }
2856           break;
2857
2858         case EVENT_KEYRELEASE:
2859           key_joystick_mapping = 0;
2860           break;
2861
2862         default:
2863           HandleOtherEvents(&event);
2864           break;
2865       }
2866     }
2867
2868     DoAnimation();
2869     BackToFront();
2870
2871     /* don't eat all CPU time */
2872     Delay(10);
2873   }
2874
2875   /* write new key bindings back to player setup */
2876   setup.input[player_nr].key = custom_key;
2877
2878   StopAnimation();
2879   DrawSetupScreen_Input();
2880 }
2881
2882 static boolean CalibrateJoystickMain(int player_nr)
2883 {
2884   int new_joystick_xleft = JOYSTICK_XMIDDLE;
2885   int new_joystick_xright = JOYSTICK_XMIDDLE;
2886   int new_joystick_yupper = JOYSTICK_YMIDDLE;
2887   int new_joystick_ylower = JOYSTICK_YMIDDLE;
2888   int new_joystick_xmiddle, new_joystick_ymiddle;
2889
2890   int joystick_fd = joystick.fd[player_nr];
2891   int x, y, last_x, last_y, xpos = 8, ypos = 3;
2892   boolean check[3][3];
2893   int check_remaining = 3 * 3;
2894   int joy_x, joy_y;
2895   int joy_value;
2896   int result = -1;
2897
2898   if (joystick.status == JOYSTICK_NOT_AVAILABLE)
2899     return FALSE;
2900
2901   if (joystick_fd < 0 || !setup.input[player_nr].use_joystick)
2902     return FALSE;
2903
2904   ClearWindow();
2905
2906   for (y = 0; y < 3; y++)
2907   {
2908     for (x = 0; x < 3; x++)
2909     {
2910       DrawGraphic(xpos + x - 1, ypos + y - 1, IMG_MENU_CALIBRATE_BLUE, 0);
2911       check[x][y] = FALSE;
2912     }
2913   }
2914
2915   DrawTextSCentered(mSY - SY +  6 * 32, FONT_TITLE_1, "Rotate joystick");
2916   DrawTextSCentered(mSY - SY +  7 * 32, FONT_TITLE_1, "in all directions");
2917   DrawTextSCentered(mSY - SY +  9 * 32, FONT_TITLE_1, "if all balls");
2918   DrawTextSCentered(mSY - SY + 10 * 32, FONT_TITLE_1, "are marked,");
2919   DrawTextSCentered(mSY - SY + 11 * 32, FONT_TITLE_1, "center joystick");
2920   DrawTextSCentered(mSY - SY + 12 * 32, FONT_TITLE_1, "and");
2921   DrawTextSCentered(mSY - SY + 13 * 32, FONT_TITLE_1, "press any button!");
2922
2923   joy_value = Joystick(player_nr);
2924   last_x = (joy_value & JOY_LEFT ? -1 : joy_value & JOY_RIGHT ? +1 : 0);
2925   last_y = (joy_value & JOY_UP   ? -1 : joy_value & JOY_DOWN  ? +1 : 0);
2926
2927   /* eventually uncalibrated center position (joystick could be uncentered) */
2928   if (!ReadJoystick(joystick_fd, &joy_x, &joy_y, NULL, NULL))
2929     return FALSE;
2930
2931   new_joystick_xmiddle = joy_x;
2932   new_joystick_ymiddle = joy_y;
2933
2934   DrawGraphic(xpos + last_x, ypos + last_y, IMG_MENU_CALIBRATE_RED, 0);
2935   BackToFront();
2936
2937   while (Joystick(player_nr) & JOY_BUTTON);     /* wait for released button */
2938   InitAnimation();
2939
2940   while (result < 0)
2941   {
2942     if (PendingEvent())         /* got event */
2943     {
2944       Event event;
2945
2946       NextEvent(&event);
2947
2948       switch(event.type)
2949       {
2950         case EVENT_KEYPRESS:
2951           switch(GetEventKey((KeyEvent *)&event, TRUE))
2952           {
2953             case KSYM_Return:
2954               if (check_remaining == 0)
2955                 result = 1;
2956               break;
2957
2958             case KSYM_Escape:
2959               result = 0;
2960               break;
2961
2962             default:
2963               break;
2964           }
2965           break;
2966
2967         case EVENT_KEYRELEASE:
2968           key_joystick_mapping = 0;
2969           break;
2970
2971         default:
2972           HandleOtherEvents(&event);
2973           break;
2974       }
2975     }
2976
2977     if (!ReadJoystick(joystick_fd, &joy_x, &joy_y, NULL, NULL))
2978       return FALSE;
2979
2980     new_joystick_xleft  = MIN(new_joystick_xleft,  joy_x);
2981     new_joystick_xright = MAX(new_joystick_xright, joy_x);
2982     new_joystick_yupper = MIN(new_joystick_yupper, joy_y);
2983     new_joystick_ylower = MAX(new_joystick_ylower, joy_y);
2984
2985     setup.input[player_nr].joy.xleft = new_joystick_xleft;
2986     setup.input[player_nr].joy.yupper = new_joystick_yupper;
2987     setup.input[player_nr].joy.xright = new_joystick_xright;
2988     setup.input[player_nr].joy.ylower = new_joystick_ylower;
2989     setup.input[player_nr].joy.xmiddle = new_joystick_xmiddle;
2990     setup.input[player_nr].joy.ymiddle = new_joystick_ymiddle;
2991
2992     CheckJoystickData();
2993
2994     joy_value = Joystick(player_nr);
2995
2996     if (joy_value & JOY_BUTTON && check_remaining == 0)
2997       result = 1;
2998
2999     x = (joy_value & JOY_LEFT ? -1 : joy_value & JOY_RIGHT ? +1 : 0);
3000     y = (joy_value & JOY_UP   ? -1 : joy_value & JOY_DOWN  ? +1 : 0);
3001
3002     if (x != last_x || y != last_y)
3003     {
3004       DrawGraphic(xpos + last_x, ypos + last_y, IMG_MENU_CALIBRATE_YELLOW, 0);
3005       DrawGraphic(xpos + x,      ypos + y,      IMG_MENU_CALIBRATE_RED,    0);
3006
3007       last_x = x;
3008       last_y = y;
3009
3010       if (check_remaining > 0 && !check[x+1][y+1])
3011       {
3012         check[x+1][y+1] = TRUE;
3013         check_remaining--;
3014       }
3015
3016 #if 0
3017 #ifdef DEBUG
3018       printf("LEFT / MIDDLE / RIGHT == %d / %d / %d\n",
3019              setup.input[player_nr].joy.xleft,
3020              setup.input[player_nr].joy.xmiddle,
3021              setup.input[player_nr].joy.xright);
3022       printf("UP / MIDDLE / DOWN == %d / %d / %d\n",
3023              setup.input[player_nr].joy.yupper,
3024              setup.input[player_nr].joy.ymiddle,
3025              setup.input[player_nr].joy.ylower);
3026 #endif
3027 #endif
3028
3029     }
3030
3031     DoAnimation();
3032     BackToFront();
3033
3034     /* don't eat all CPU time */
3035     Delay(10);
3036   }
3037
3038   /* calibrated center position (joystick should now be centered) */
3039   if (!ReadJoystick(joystick_fd, &joy_x, &joy_y, NULL, NULL))
3040     return FALSE;
3041
3042   new_joystick_xmiddle = joy_x;
3043   new_joystick_ymiddle = joy_y;
3044
3045   StopAnimation();
3046
3047 #if 0
3048   DrawSetupScreen_Input();
3049 #endif
3050
3051   /* wait until the last pressed button was released */
3052   while (Joystick(player_nr) & JOY_BUTTON)
3053   {
3054     if (PendingEvent())         /* got event */
3055     {
3056       Event event;
3057
3058       NextEvent(&event);
3059       HandleOtherEvents(&event);
3060
3061       Delay(10);
3062     }
3063   }
3064
3065   return TRUE;
3066 }
3067
3068 void CalibrateJoystick(int player_nr)
3069 {
3070   if (!CalibrateJoystickMain(player_nr))
3071   {
3072     char *device_name = setup.input[player_nr].joy.device_name;
3073     int nr = getJoystickNrFromDeviceName(device_name) + 1;
3074     int xpos = mSX - SX;
3075     int ypos = mSY - SY;
3076
3077     ClearWindow();
3078
3079     DrawTextF(xpos + 16, ypos + 6 * 32, FONT_TITLE_1, "   JOYSTICK %d   ", nr);
3080     DrawTextF(xpos + 16, ypos + 7 * 32, FONT_TITLE_1, " NOT AVAILABLE! ");
3081     BackToFront();
3082
3083     Delay(2000);                /* show error message for a short time */
3084
3085     ClearEventQueue();
3086   }
3087
3088 #if 1
3089   DrawSetupScreen_Input();
3090 #endif
3091 }
3092
3093 void DrawSetupScreen()
3094 {
3095   DeactivateJoystick();
3096
3097   SetMainBackgroundImage(IMG_BACKGROUND_SETUP);
3098
3099   if (setup_mode == SETUP_MODE_INPUT)
3100     DrawSetupScreen_Input();
3101   else if (setup_mode == SETUP_MODE_CHOOSE_GRAPHICS)
3102     DrawChooseTree(&artwork.gfx_current);
3103   else if (setup_mode == SETUP_MODE_CHOOSE_SOUNDS)
3104     DrawChooseTree(&artwork.snd_current);
3105   else if (setup_mode == SETUP_MODE_CHOOSE_MUSIC)
3106     DrawChooseTree(&artwork.mus_current);
3107   else
3108     DrawSetupScreen_Generic();
3109
3110   PlayMenuSound();
3111   PlayMenuMusic();
3112 }
3113
3114 void HandleSetupScreen(int mx, int my, int dx, int dy, int button)
3115 {
3116   if (setup_mode == SETUP_MODE_INPUT)
3117     HandleSetupScreen_Input(mx, my, dx, dy, button);
3118   else if (setup_mode == SETUP_MODE_CHOOSE_GRAPHICS)
3119     HandleChooseTree(mx, my, dx, dy, button, &artwork.gfx_current);
3120   else if (setup_mode == SETUP_MODE_CHOOSE_SOUNDS)
3121     HandleChooseTree(mx, my, dx, dy, button, &artwork.snd_current);
3122   else if (setup_mode == SETUP_MODE_CHOOSE_MUSIC)
3123     HandleChooseTree(mx, my, dx, dy, button, &artwork.mus_current);
3124   else
3125     HandleSetupScreen_Generic(mx, my, dx, dy, button);
3126
3127   DoAnimation();
3128 }
3129
3130 void HandleGameActions()
3131 {
3132   if (game_status != GAME_MODE_PLAYING)
3133     return;
3134
3135   GameActions();        /* main game loop */
3136
3137   if (tape.auto_play && !tape.playing)
3138     AutoPlayTape();     /* continue automatically playing next tape */
3139 }
3140
3141 /* ---------- new screen button stuff -------------------------------------- */
3142
3143 /* graphic position and size values for buttons and scrollbars */
3144 #define SC_SCROLLBUTTON_XSIZE           TILEX
3145 #define SC_SCROLLBUTTON_YSIZE           TILEY
3146
3147 #define SC_SCROLL_VERTICAL_XSIZE        SC_SCROLLBUTTON_XSIZE
3148 #define SC_SCROLL_VERTICAL_YSIZE        ((MAX_MENU_ENTRIES_ON_SCREEN - 2) * \
3149                                          SC_SCROLLBUTTON_YSIZE)
3150 #define SC_SCROLL_UP_XPOS               (SXSIZE - SC_SCROLLBUTTON_XSIZE)
3151 #define SC_SCROLL_UP_YPOS               (2 * SC_SCROLLBUTTON_YSIZE)
3152 #define SC_SCROLL_VERTICAL_XPOS         SC_SCROLL_UP_XPOS
3153 #define SC_SCROLL_VERTICAL_YPOS         (SC_SCROLL_UP_YPOS + \
3154                                          SC_SCROLLBUTTON_YSIZE)
3155 #define SC_SCROLL_DOWN_XPOS             SC_SCROLL_UP_XPOS
3156 #define SC_SCROLL_DOWN_YPOS             (SC_SCROLL_VERTICAL_YPOS + \
3157                                          SC_SCROLL_VERTICAL_YSIZE)
3158
3159 #define SC_BORDER_SIZE                  14
3160
3161 static struct
3162 {
3163   int gfx_unpressed, gfx_pressed;
3164   int x, y;
3165   int gadget_id;
3166   char *infotext;
3167 } scrollbutton_info[NUM_SCREEN_SCROLLBUTTONS] =
3168 {
3169   {
3170     IMG_MENU_BUTTON_UP, IMG_MENU_BUTTON_UP_ACTIVE,
3171     SC_SCROLL_UP_XPOS, SC_SCROLL_UP_YPOS,
3172     SCREEN_CTRL_ID_SCROLL_UP,
3173     "scroll up"
3174   },
3175   {
3176     IMG_MENU_BUTTON_DOWN, IMG_MENU_BUTTON_DOWN_ACTIVE,
3177     SC_SCROLL_DOWN_XPOS, SC_SCROLL_DOWN_YPOS,
3178     SCREEN_CTRL_ID_SCROLL_DOWN,
3179     "scroll down"
3180   }
3181 };
3182
3183 static struct
3184 {
3185 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3186   Bitmap **gfx_unpressed, **gfx_pressed;
3187 #else
3188   int gfx_unpressed, gfx_pressed;
3189 #endif
3190   int x, y;
3191   int width, height;
3192   int type;
3193   int gadget_id;
3194   char *infotext;
3195 } scrollbar_info[NUM_SCREEN_SCROLLBARS] =
3196 {
3197   {
3198 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3199     &scrollbar_bitmap[0], &scrollbar_bitmap[1],
3200 #else
3201     IMG_MENU_SCROLLBAR, IMG_MENU_SCROLLBAR_ACTIVE,
3202 #endif
3203     SC_SCROLL_VERTICAL_XPOS, SC_SCROLL_VERTICAL_YPOS,
3204     SC_SCROLL_VERTICAL_XSIZE, SC_SCROLL_VERTICAL_YSIZE,
3205     GD_TYPE_SCROLLBAR_VERTICAL,
3206     SCREEN_CTRL_ID_SCROLL_VERTICAL,
3207     "scroll level series vertically"
3208   }
3209 };
3210
3211 static void CreateScreenScrollbuttons()
3212 {
3213   struct GadgetInfo *gi;
3214   unsigned long event_mask;
3215   int i;
3216
3217   for (i = 0; i < NUM_SCREEN_SCROLLBUTTONS; i++)
3218   {
3219     Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed;
3220     int gfx_unpressed, gfx_pressed;
3221     int x, y, width, height;
3222     int gd_x1, gd_x2, gd_y1, gd_y2;
3223     int id = scrollbutton_info[i].gadget_id;
3224
3225     event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
3226
3227     x = mSX + scrollbutton_info[i].x + menu.scrollbar_xoffset;
3228     y = mSY + scrollbutton_info[i].y;
3229     width = SC_SCROLLBUTTON_XSIZE;
3230     height = SC_SCROLLBUTTON_YSIZE;
3231
3232     if (id == SCREEN_CTRL_ID_SCROLL_DOWN)
3233       y = mSY + (SC_SCROLL_VERTICAL_YPOS +
3234                  (NUM_MENU_ENTRIES_ON_SCREEN - 2) * SC_SCROLLBUTTON_YSIZE);
3235
3236     gfx_unpressed = scrollbutton_info[i].gfx_unpressed;
3237     gfx_pressed   = scrollbutton_info[i].gfx_pressed;
3238     gd_bitmap_unpressed = graphic_info[gfx_unpressed].bitmap;
3239     gd_bitmap_pressed   = graphic_info[gfx_pressed].bitmap;
3240     gd_x1 = graphic_info[gfx_unpressed].src_x;
3241     gd_y1 = graphic_info[gfx_unpressed].src_y;
3242     gd_x2 = graphic_info[gfx_pressed].src_x;
3243     gd_y2 = graphic_info[gfx_pressed].src_y;
3244
3245     gi = CreateGadget(GDI_CUSTOM_ID, id,
3246                       GDI_CUSTOM_TYPE_ID, i,
3247                       GDI_INFO_TEXT, scrollbutton_info[i].infotext,
3248                       GDI_X, x,
3249                       GDI_Y, y,
3250                       GDI_WIDTH, width,
3251                       GDI_HEIGHT, height,
3252                       GDI_TYPE, GD_TYPE_NORMAL_BUTTON,
3253                       GDI_STATE, GD_BUTTON_UNPRESSED,
3254                       GDI_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1, gd_y1,
3255                       GDI_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2, gd_y2,
3256                       GDI_DIRECT_DRAW, FALSE,
3257                       GDI_EVENT_MASK, event_mask,
3258                       GDI_CALLBACK_ACTION, HandleScreenGadgets,
3259                       GDI_END);
3260
3261     if (gi == NULL)
3262       Error(ERR_EXIT, "cannot create gadget");
3263
3264     screen_gadget[id] = gi;
3265   }
3266 }
3267
3268 static void CreateScreenScrollbars()
3269 {
3270   int i;
3271
3272   for (i = 0; i < NUM_SCREEN_SCROLLBARS; i++)
3273   {
3274     Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed;
3275 #if !defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3276     int gfx_unpressed, gfx_pressed;
3277 #endif
3278     int x, y, width, height;
3279     int gd_x1, gd_x2, gd_y1, gd_y2;
3280     struct GadgetInfo *gi;
3281     int items_max, items_visible, item_position;
3282     unsigned long event_mask;
3283     int num_page_entries = NUM_MENU_ENTRIES_ON_SCREEN;
3284     int id = scrollbar_info[i].gadget_id;
3285
3286     event_mask = GD_EVENT_MOVING | GD_EVENT_OFF_BORDERS;
3287
3288     x = mSX + scrollbar_info[i].x + menu.scrollbar_xoffset;
3289     y = mSY + scrollbar_info[i].y;
3290     width  = scrollbar_info[i].width;
3291     height = scrollbar_info[i].height;
3292
3293     if (id == SCREEN_CTRL_ID_SCROLL_VERTICAL)
3294       height = (NUM_MENU_ENTRIES_ON_SCREEN - 2) * SC_SCROLLBUTTON_YSIZE;
3295
3296     items_max = num_page_entries;
3297     items_visible = num_page_entries;
3298     item_position = 0;
3299
3300 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3301     gd_bitmap_unpressed = *scrollbar_info[i].gfx_unpressed;
3302     gd_bitmap_pressed   = *scrollbar_info[i].gfx_pressed;
3303     gd_x1 = 0;
3304     gd_y1 = 0;
3305     gd_x2 = 0;
3306     gd_y2 = 0;
3307 #else
3308     gfx_unpressed = scrollbar_info[i].gfx_unpressed;
3309     gfx_pressed   = scrollbar_info[i].gfx_pressed;
3310     gd_bitmap_unpressed = graphic_info[gfx_unpressed].bitmap;
3311     gd_bitmap_pressed   = graphic_info[gfx_pressed].bitmap;
3312     gd_x1 = graphic_info[gfx_unpressed].src_x;
3313     gd_y1 = graphic_info[gfx_unpressed].src_y;
3314     gd_x2 = graphic_info[gfx_pressed].src_x;
3315     gd_y2 = graphic_info[gfx_pressed].src_y;
3316 #endif
3317
3318     gi = CreateGadget(GDI_CUSTOM_ID, id,
3319                       GDI_CUSTOM_TYPE_ID, i,
3320                       GDI_INFO_TEXT, scrollbar_info[i].infotext,
3321                       GDI_X, x,
3322                       GDI_Y, y,
3323                       GDI_WIDTH, width,
3324                       GDI_HEIGHT, height,
3325                       GDI_TYPE, scrollbar_info[i].type,
3326                       GDI_SCROLLBAR_ITEMS_MAX, items_max,
3327                       GDI_SCROLLBAR_ITEMS_VISIBLE, items_visible,
3328                       GDI_SCROLLBAR_ITEM_POSITION, item_position,
3329                       GDI_STATE, GD_BUTTON_UNPRESSED,
3330                       GDI_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1, gd_y1,
3331                       GDI_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2, gd_y2,
3332                       GDI_BORDER_SIZE, SC_BORDER_SIZE, SC_BORDER_SIZE,
3333                       GDI_DIRECT_DRAW, FALSE,
3334                       GDI_EVENT_MASK, event_mask,
3335                       GDI_CALLBACK_ACTION, HandleScreenGadgets,
3336                       GDI_END);
3337
3338     if (gi == NULL)
3339       Error(ERR_EXIT, "cannot create gadget");
3340
3341     screen_gadget[id] = gi;
3342   }
3343 }
3344
3345 void CreateScreenGadgets()
3346 {
3347   int last_game_status = game_status;   /* save current game status */
3348
3349 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3350   int i;
3351
3352   for (i = 0; i < NUM_SCROLLBAR_BITMAPS; i++)
3353   {
3354     scrollbar_bitmap[i] = CreateBitmap(TILEX, TILEY, DEFAULT_DEPTH);
3355
3356     /* copy pointers to clip mask and GC */
3357     scrollbar_bitmap[i]->clip_mask =
3358       graphic_info[IMG_MENU_SCROLLBAR + i].clip_mask;
3359     scrollbar_bitmap[i]->stored_clip_gc =
3360       graphic_info[IMG_MENU_SCROLLBAR + i].clip_gc;
3361
3362     BlitBitmap(graphic_info[IMG_MENU_SCROLLBAR + i].bitmap,
3363                scrollbar_bitmap[i],
3364                graphic_info[IMG_MENU_SCROLLBAR + i].src_x,
3365                graphic_info[IMG_MENU_SCROLLBAR + i].src_y,
3366                TILEX, TILEY, 0, 0);
3367   }
3368 #endif
3369
3370   /* force LEVELS draw offset for scrollbar / scrollbutton gadgets */
3371   game_status = GAME_MODE_LEVELS;
3372
3373   CreateScreenScrollbuttons();
3374   CreateScreenScrollbars();
3375
3376   game_status = last_game_status;       /* restore current game status */
3377 }
3378
3379 void FreeScreenGadgets()
3380 {
3381   int i;
3382
3383 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3384   for (i = 0; i < NUM_SCROLLBAR_BITMAPS; i++)
3385   {
3386     /* prevent freeing clip mask and GC twice */
3387     scrollbar_bitmap[i]->clip_mask = None;
3388     scrollbar_bitmap[i]->stored_clip_gc = None;
3389
3390     FreeBitmap(scrollbar_bitmap[i]);
3391   }
3392 #endif
3393
3394   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
3395     FreeGadget(screen_gadget[i]);
3396 }
3397
3398 void MapChooseTreeGadgets(TreeInfo *ti)
3399 {
3400   int num_entries = numTreeInfoInGroup(ti);
3401   int i;
3402
3403   if (num_entries <= NUM_MENU_ENTRIES_ON_SCREEN)
3404     return;
3405
3406   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
3407     MapGadget(screen_gadget[i]);
3408 }
3409
3410 #if 0
3411 void UnmapChooseTreeGadgets()
3412 {
3413   int i;
3414
3415   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
3416     UnmapGadget(screen_gadget[i]);
3417 }
3418 #endif
3419
3420 static void HandleScreenGadgets(struct GadgetInfo *gi)
3421 {
3422   int id = gi->custom_id;
3423
3424   if (game_status != GAME_MODE_LEVELS && game_status != GAME_MODE_SETUP)
3425     return;
3426
3427   switch (id)
3428   {
3429     case SCREEN_CTRL_ID_SCROLL_UP:
3430       if (game_status == GAME_MODE_LEVELS)
3431         HandleChooseLevel(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
3432       else if (game_status == GAME_MODE_SETUP)
3433         HandleSetupScreen(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
3434       break;
3435
3436     case SCREEN_CTRL_ID_SCROLL_DOWN:
3437       if (game_status == GAME_MODE_LEVELS)
3438         HandleChooseLevel(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
3439       else if (game_status == GAME_MODE_SETUP)
3440         HandleSetupScreen(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
3441       break;
3442
3443     case SCREEN_CTRL_ID_SCROLL_VERTICAL:
3444       if (game_status == GAME_MODE_LEVELS)
3445         HandleChooseLevel(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE);
3446       else if (game_status == GAME_MODE_SETUP)
3447         HandleSetupScreen(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE);
3448       break;
3449
3450     default:
3451       break;
3452   }
3453 }