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