rnd-20060407-1-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             strcmp(setup.player_name, "Artsoft") != 0)
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 (strcmp(list->title, UNKNOWN_NAME) != 0)
1144     {
1145       if (strcmp(list->title_header, UNKNOWN_NAME) != 0)
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 (strcmp(list->artist, UNKNOWN_NAME) != 0)
1152     {
1153       if (strcmp(list->artist_header, UNKNOWN_NAME) != 0)
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 (strcmp(list->album, UNKNOWN_NAME) != 0)
1162     {
1163       if (strcmp(list->album_header, UNKNOWN_NAME) != 0)
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 (strcmp(list->year, UNKNOWN_NAME) != 0)
1172     {
1173       if (strcmp(list->year_header, UNKNOWN_NAME) != 0)
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     if (strcmp(highscore[entry].Name, EMPTY_PLAYER_NAME) != 0)
1908       DrawText(mSX + dx2, sy, highscore[entry].Name, font_nr2);
1909     DrawText(mSX + dx3, sy, int2str(highscore[entry].Score, 5), font_nr4);
1910   }
1911
1912   redraw_mask |= REDRAW_FIELD;
1913 }
1914
1915 void HandleHallOfFame(int mx, int my, int dx, int dy, int button)
1916 {
1917   static int first_entry = 0;
1918   static int highlight_position = 0;
1919   int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
1920   int button_released = !button;
1921
1922   if (button == MB_MENU_INITIALIZE)
1923   {
1924     first_entry = 0;
1925     highlight_position = mx;
1926     drawHallOfFameList(first_entry, highlight_position);
1927
1928     return;
1929   }
1930
1931   if (ABS(dy) == SCROLL_PAGE)           /* handle scrolling one page */
1932     step = NUM_MENU_ENTRIES_ON_SCREEN - 1;
1933
1934   if (dy < 0)
1935   {
1936     if (first_entry > 0)
1937     {
1938       first_entry -= step;
1939       if (first_entry < 0)
1940         first_entry = 0;
1941
1942       drawHallOfFameList(first_entry, highlight_position);
1943     }
1944   }
1945   else if (dy > 0)
1946   {
1947     if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN < MAX_SCORE_ENTRIES)
1948     {
1949       first_entry += step;
1950       if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN > MAX_SCORE_ENTRIES)
1951         first_entry = MAX(0, MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN);
1952
1953       drawHallOfFameList(first_entry, highlight_position);
1954     }
1955   }
1956   else if (button_released)
1957   {
1958     FadeSound(SND_BACKGROUND_SCORES);
1959     game_status = GAME_MODE_MAIN;
1960     DrawMainMenu();
1961   }
1962
1963   if (game_status == GAME_MODE_SCORES)
1964     PlayMenuSoundIfLoop();
1965
1966   DoAnimation();
1967 }
1968
1969
1970 /* ========================================================================= */
1971 /* setup screen functions                                                    */
1972 /* ========================================================================= */
1973
1974 static struct TokenInfo *setup_info;
1975 static int num_setup_info;
1976
1977 static char *graphics_set_name;
1978 static char *sounds_set_name;
1979 static char *music_set_name;
1980
1981 static void execSetupMain()
1982 {
1983   setup_mode = SETUP_MODE_MAIN;
1984   DrawSetupScreen();
1985 }
1986
1987 static void execSetupGame()
1988 {
1989   setup_mode = SETUP_MODE_GAME;
1990   DrawSetupScreen();
1991 }
1992
1993 static void execSetupEditor()
1994 {
1995   setup_mode = SETUP_MODE_EDITOR;
1996   DrawSetupScreen();
1997 }
1998
1999 static void execSetupGraphics()
2000 {
2001   setup_mode = SETUP_MODE_GRAPHICS;
2002   DrawSetupScreen();
2003 }
2004
2005 static void execSetupSound()
2006 {
2007   setup_mode = SETUP_MODE_SOUND;
2008   DrawSetupScreen();
2009 }
2010
2011 static void execSetupArtwork()
2012 {
2013   setup.graphics_set = artwork.gfx_current->identifier;
2014   setup.sounds_set = artwork.snd_current->identifier;
2015   setup.music_set = artwork.mus_current->identifier;
2016
2017   /* needed if last screen (setup choice) changed graphics, sounds or music */
2018   ReloadCustomArtwork(0);
2019
2020   /* needed for displaying artwork name instead of artwork identifier */
2021   graphics_set_name = artwork.gfx_current->name;
2022   sounds_set_name = artwork.snd_current->name;
2023   music_set_name = artwork.mus_current->name;
2024
2025   setup_mode = SETUP_MODE_ARTWORK;
2026   DrawSetupScreen();
2027 }
2028
2029 static void execSetupChooseGraphics()
2030 {
2031   setup_mode = SETUP_MODE_CHOOSE_GRAPHICS;
2032   DrawSetupScreen();
2033 }
2034
2035 static void execSetupChooseSounds()
2036 {
2037   setup_mode = SETUP_MODE_CHOOSE_SOUNDS;
2038   DrawSetupScreen();
2039 }
2040
2041 static void execSetupChooseMusic()
2042 {
2043   setup_mode = SETUP_MODE_CHOOSE_MUSIC;
2044   DrawSetupScreen();
2045 }
2046
2047 static void execSetupInput()
2048 {
2049   setup_mode = SETUP_MODE_INPUT;
2050   DrawSetupScreen();
2051 }
2052
2053 static void execSetupShortcut1()
2054 {
2055   setup_mode = SETUP_MODE_SHORTCUT_1;
2056   DrawSetupScreen();
2057 }
2058
2059 static void execSetupShortcut2()
2060 {
2061   setup_mode = SETUP_MODE_SHORTCUT_2;
2062   DrawSetupScreen();
2063 }
2064
2065 static void execExitSetup()
2066 {
2067   game_status = GAME_MODE_MAIN;
2068   DrawMainMenu();
2069 }
2070
2071 static void execSaveAndExitSetup()
2072 {
2073   SaveSetup();
2074   execExitSetup();
2075 }
2076
2077 static struct TokenInfo setup_info_main[] =
2078 {
2079   { TYPE_ENTER_MENU,    execSetupGame,          "Game & Menu"           },
2080   { TYPE_ENTER_MENU,    execSetupEditor,        "Editor"                },
2081   { TYPE_ENTER_MENU,    execSetupGraphics,      "Graphics"              },
2082   { TYPE_ENTER_MENU,    execSetupSound,         "Sound & Music"         },
2083   { TYPE_ENTER_MENU,    execSetupArtwork,       "Custom Artwork"        },
2084   { TYPE_ENTER_MENU,    execSetupInput,         "Input Devices"         },
2085   { TYPE_ENTER_MENU,    execSetupShortcut1,     "Key Shortcuts 1"       },
2086   { TYPE_ENTER_MENU,    execSetupShortcut2,     "Key Shortcuts 2"       },
2087   { TYPE_EMPTY,         NULL,                   ""                      },
2088   { TYPE_LEAVE_MENU,    execExitSetup,          "Exit"                  },
2089   { TYPE_LEAVE_MENU,    execSaveAndExitSetup,   "Save and Exit"         },
2090
2091   { 0,                  NULL,                   NULL                    }
2092 };
2093
2094 static struct TokenInfo setup_info_game[] =
2095 {
2096   { TYPE_SWITCH,        &setup.team_mode,       "Team-Mode (Multi-Player):" },
2097   { TYPE_YES_NO,        &setup.input_on_focus,  "Only Move Focussed Player:" },
2098   { TYPE_SWITCH,        &setup.handicap,        "Handicap:"             },
2099   { TYPE_SWITCH,        &setup.skip_levels,     "Skip Unsolved Levels:" },
2100   { TYPE_SWITCH,        &setup.time_limit,      "Time Limit:"           },
2101   { TYPE_SWITCH,        &setup.autorecord,      "Auto-Record Tapes:"    },
2102   { TYPE_EMPTY,         NULL,                   ""                      },
2103   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2104
2105   { 0,                  NULL,                   NULL                    }
2106 };
2107
2108 static struct TokenInfo setup_info_editor[] =
2109 {
2110   { TYPE_SWITCH,        &setup.editor.el_boulderdash,   "Boulder Dash:" },
2111   { TYPE_SWITCH,        &setup.editor.el_emerald_mine,  "Emerald Mine:" },
2112   { TYPE_SWITCH, &setup.editor.el_emerald_mine_club,    "Emerald Mine Club:" },
2113   { TYPE_SWITCH,        &setup.editor.el_more,          "Rocks'n'Diamonds:" },
2114   { TYPE_SWITCH,        &setup.editor.el_sokoban,       "Sokoban:"      },
2115   { TYPE_SWITCH,        &setup.editor.el_supaplex,      "Supaplex:"     },
2116   { TYPE_SWITCH,        &setup.editor.el_diamond_caves, "Diamond Caves II:" },
2117   { TYPE_SWITCH,        &setup.editor.el_dx_boulderdash,"DX-Boulderdash:" },
2118   { TYPE_SWITCH,        &setup.editor.el_chars,         "Text Characters:" },
2119   { TYPE_SWITCH,        &setup.editor.el_custom,  "Custom & Group Elements:" },
2120   { TYPE_SWITCH,        &setup.editor.el_headlines,     "Headlines:"    },
2121   { TYPE_SWITCH, &setup.editor.el_user_defined, "User defined element list:" },
2122   { TYPE_SWITCH,        &setup.editor.el_dynamic,  "Dynamic level elements:" },
2123   { TYPE_EMPTY,         NULL,                   ""                      },
2124   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2125
2126   { 0,                  NULL,                   NULL                    }
2127 };
2128
2129 static struct TokenInfo setup_info_graphics[] =
2130 {
2131   { TYPE_SWITCH,        &setup.fullscreen,      "Fullscreen Mode:"      },
2132   { TYPE_SWITCH,        &setup.scroll_delay,    "Delayed Scrolling:"    },
2133 #if 0
2134   { TYPE_SWITCH,        &setup.soft_scrolling,  "Soft Scrolling:"       },
2135   { TYPE_SWITCH,        &setup.double_buffering,"Double-Buffering:"     },
2136   { TYPE_SWITCH,        &setup.fading,          "Fading:"               },
2137 #endif
2138   { TYPE_SWITCH,        &setup.quick_switch,    "Quick Player Focus Switch:" },
2139   { TYPE_SWITCH,        &setup.quick_doors,     "Quick Menu Doors:"     },
2140   { TYPE_SWITCH,        &setup.toons,           "Toons:"                },
2141   { TYPE_ECS_AGA,       &setup.prefer_aga_graphics,"EMC graphics preference:" },
2142   { TYPE_EMPTY,         NULL,                   ""                      },
2143   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2144
2145   { 0,                  NULL,                   NULL                    }
2146 };
2147
2148 static struct TokenInfo setup_info_sound[] =
2149 {
2150   { TYPE_SWITCH,        &setup.sound_simple,    "Sound Effects (Normal):"  },
2151   { TYPE_SWITCH,        &setup.sound_loops,     "Sound Effects (Looping):" },
2152   { TYPE_SWITCH,        &setup.sound_music,     "Music:"                },
2153   { TYPE_EMPTY,         NULL,                   ""                      },
2154   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2155
2156   { 0,                  NULL,                   NULL                    }
2157 };
2158
2159 static struct TokenInfo setup_info_artwork[] =
2160 {
2161   { TYPE_ENTER_MENU,    execSetupChooseGraphics,"Custom Graphics"       },
2162   { TYPE_STRING,        &graphics_set_name,     ""                      },
2163   { TYPE_ENTER_MENU,    execSetupChooseSounds,  "Custom Sounds"         },
2164   { TYPE_STRING,        &sounds_set_name,       ""                      },
2165   { TYPE_ENTER_MENU,    execSetupChooseMusic,   "Custom Music"          },
2166   { TYPE_STRING,        &music_set_name,        ""                      },
2167   { TYPE_EMPTY,         NULL,                   ""                      },
2168 #if 1
2169   { TYPE_YES_NO, &setup.override_level_graphics,"Override Level Graphics:" },
2170   { TYPE_YES_NO, &setup.override_level_sounds,  "Override Level Sounds:"   },
2171   { TYPE_YES_NO, &setup.override_level_music,   "Override Level Music:"    },
2172 #else
2173   { TYPE_STRING,        NULL,                   "Override Level Artwork:"},
2174   { TYPE_YES_NO,        &setup.override_level_graphics, "Graphics:"     },
2175   { TYPE_YES_NO,        &setup.override_level_sounds,   "Sounds:"       },
2176   { TYPE_YES_NO,        &setup.override_level_music,    "Music:"        },
2177 #endif
2178   { TYPE_EMPTY,         NULL,                   ""                      },
2179   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2180
2181   { 0,                  NULL,                   NULL                    }
2182 };
2183
2184 static struct TokenInfo setup_info_shortcut_1[] =
2185 {
2186   { TYPE_KEYTEXT,       NULL,           "Quick Save Game to Tape:",     },
2187   { TYPE_KEY,           &setup.shortcut.save_game, ""                   },
2188   { TYPE_KEYTEXT,       NULL,           "Quick Load Game from Tape:",   },
2189   { TYPE_KEY,           &setup.shortcut.load_game, ""                   },
2190   { TYPE_KEYTEXT,       NULL,           "Start Game & Toggle Pause:",   },
2191   { TYPE_KEY,           &setup.shortcut.toggle_pause, ""                },
2192   { TYPE_EMPTY,         NULL,                   ""                      },
2193   { TYPE_YES_NO,        &setup.ask_on_escape,   "Ask on 'Esc' Key:"     },
2194   { TYPE_YES_NO, &setup.ask_on_escape_editor,   "Ask on 'Esc' Key (Editor):" },
2195   { TYPE_EMPTY,         NULL,                   ""                      },
2196   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2197
2198   { 0,                  NULL,                   NULL                    }
2199 };
2200
2201 static struct TokenInfo setup_info_shortcut_2[] =
2202 {
2203   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 1:",       },
2204   { TYPE_KEY,           &setup.shortcut.focus_player[0], ""             },
2205   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 2:",       },
2206   { TYPE_KEY,           &setup.shortcut.focus_player[1], ""             },
2207   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 3:",       },
2208   { TYPE_KEY,           &setup.shortcut.focus_player[2], ""             },
2209   { TYPE_KEYTEXT,       NULL,           "Set Focus to Player 4:",       },
2210   { TYPE_KEY,           &setup.shortcut.focus_player[3], ""             },
2211   { TYPE_KEYTEXT,       NULL,           "Set Focus to All Players:",    },
2212   { TYPE_KEY,           &setup.shortcut.focus_player_all, ""            },
2213   { TYPE_EMPTY,         NULL,                   ""                      },
2214   { TYPE_LEAVE_MENU,    execSetupMain,          "Back"                  },
2215
2216   { 0,                  NULL,                   NULL                    }
2217 };
2218
2219 static Key getSetupKey()
2220 {
2221   Key key = KSYM_UNDEFINED;
2222   boolean got_key_event = FALSE;
2223
2224   while (!got_key_event)
2225   {
2226     if (PendingEvent())         /* got event */
2227     {
2228       Event event;
2229
2230       NextEvent(&event);
2231
2232       switch(event.type)
2233       {
2234         case EVENT_KEYPRESS:
2235           {
2236             key = GetEventKey((KeyEvent *)&event, TRUE);
2237
2238             /* press 'Escape' or 'Enter' to keep the existing key binding */
2239             if (key == KSYM_Escape || key == KSYM_Return)
2240               key = KSYM_UNDEFINED;     /* keep old value */
2241
2242             got_key_event = TRUE;
2243           }
2244           break;
2245
2246         case EVENT_KEYRELEASE:
2247           key_joystick_mapping = 0;
2248           break;
2249
2250         default:
2251           HandleOtherEvents(&event);
2252           break;
2253       }
2254     }
2255
2256     DoAnimation();
2257     BackToFront();
2258
2259     /* don't eat all CPU time */
2260     Delay(10);
2261   }
2262
2263   return key;
2264 }
2265
2266 static void drawSetupValue(int pos)
2267 {
2268   int xpos = MENU_SCREEN_VALUE_XPOS;
2269   int ypos = MENU_SCREEN_START_YPOS + pos;
2270   int font_nr = FONT_VALUE_1;
2271   int font_width = getFontWidth(font_nr);
2272   int type = setup_info[pos].type;
2273   void *value = setup_info[pos].value;
2274   char *value_string = getSetupValue(type, value);
2275   int i;
2276
2277   if (value_string == NULL)
2278     return;
2279
2280   if (type & TYPE_KEY)
2281   {
2282 #if 1
2283     xpos = 1;
2284 #else
2285     xpos = 3;
2286 #endif
2287
2288     if (type & TYPE_QUERY)
2289     {
2290       value_string = "<press key>";
2291       font_nr = FONT_INPUT_1_ACTIVE;
2292     }
2293   }
2294   else if (type & TYPE_STRING)
2295   {
2296     int max_value_len = (SCR_FIELDX - 2) * 2;
2297
2298     xpos = 1;
2299     font_nr = FONT_VALUE_2;
2300
2301     if (strlen(value_string) > max_value_len)
2302       value_string[max_value_len] = '\0';
2303   }
2304   else if (type & TYPE_ECS_AGA)
2305   {
2306     font_nr = FONT_VALUE_1;
2307   }
2308   else if (type & TYPE_BOOLEAN_STYLE)
2309   {
2310     font_nr = (*(boolean *)value ? FONT_OPTION_ON : FONT_OPTION_OFF);
2311   }
2312
2313 #if 1
2314   for (i = 0; i <= MENU_SCREEN_MAX_XPOS - xpos; i++)
2315     DrawText(mSX + xpos * 32 + i * font_width, mSY + ypos * 32, " ", font_nr);
2316 #else
2317 #if 1
2318   for (i = xpos; i <= MENU_SCREEN_MAX_XPOS; i++)
2319     DrawText(mSX + i * 32, mSY + ypos * 32, " ", font_nr);
2320 #else
2321   DrawText(mSX + xpos * 32, mSY + ypos * 32,
2322            (xpos == 3 ? "              " : "   "), font_nr);
2323 #endif
2324 #endif
2325
2326   DrawText(mSX + xpos * 32, mSY + ypos * 32, value_string, font_nr);
2327 }
2328
2329 static void changeSetupValue(int pos)
2330 {
2331   if (setup_info[pos].type & TYPE_BOOLEAN_STYLE)
2332   {
2333     *(boolean *)setup_info[pos].value ^= TRUE;
2334   }
2335   else if (setup_info[pos].type & TYPE_KEY)
2336   {
2337     Key key;
2338
2339     setup_info[pos].type |= TYPE_QUERY;
2340     drawSetupValue(pos);
2341     setup_info[pos].type &= ~TYPE_QUERY;
2342
2343     key = getSetupKey();
2344     if (key != KSYM_UNDEFINED)
2345       *(Key *)setup_info[pos].value = key;
2346   }
2347
2348   drawSetupValue(pos);
2349 }
2350
2351 static void DrawSetupScreen_Generic()
2352 {
2353   char *title_string = NULL;
2354   int i;
2355
2356   UnmapAllGadgets();
2357   CloseDoor(DOOR_CLOSE_2);
2358
2359   ClearWindow();
2360
2361   if (setup_mode == SETUP_MODE_MAIN)
2362   {
2363     setup_info = setup_info_main;
2364     title_string = "Setup";
2365   }
2366   else if (setup_mode == SETUP_MODE_GAME)
2367   {
2368     setup_info = setup_info_game;
2369     title_string = "Setup Game";
2370   }
2371   else if (setup_mode == SETUP_MODE_EDITOR)
2372   {
2373     setup_info = setup_info_editor;
2374     title_string = "Setup Editor";
2375   }
2376   else if (setup_mode == SETUP_MODE_GRAPHICS)
2377   {
2378     setup_info = setup_info_graphics;
2379     title_string = "Setup Graphics";
2380   }
2381   else if (setup_mode == SETUP_MODE_SOUND)
2382   {
2383     setup_info = setup_info_sound;
2384     title_string = "Setup Sound";
2385   }
2386   else if (setup_mode == SETUP_MODE_ARTWORK)
2387   {
2388     setup_info = setup_info_artwork;
2389     title_string = "Custom Artwork";
2390   }
2391   else if (setup_mode == SETUP_MODE_SHORTCUT_1)
2392   {
2393     setup_info = setup_info_shortcut_1;
2394     title_string = "Setup Shortcuts";
2395   }
2396   else if (setup_mode == SETUP_MODE_SHORTCUT_2)
2397   {
2398     setup_info = setup_info_shortcut_2;
2399     title_string = "Setup Shortcuts";
2400   }
2401
2402 #if 1
2403   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, title_string);
2404 #else
2405   DrawText(mSX + 16, mSY + 16, title_string, FONT_TITLE_1);
2406 #endif
2407
2408   num_setup_info = 0;
2409   for (i = 0; setup_info[i].type != 0 && i < NUM_MENU_ENTRIES_ON_SCREEN; i++)
2410   {
2411     void *value_ptr = setup_info[i].value;
2412     int ypos = MENU_SCREEN_START_YPOS + i;
2413     int font_nr = FONT_MENU_1;
2414
2415     /* set some entries to "unchangeable" according to other variables */
2416     if ((value_ptr == &setup.sound_simple && !audio.sound_available) ||
2417         (value_ptr == &setup.sound_loops  && !audio.loops_available) ||
2418         (value_ptr == &setup.sound_music  && !audio.music_available) ||
2419         (value_ptr == &setup.fullscreen   && !video.fullscreen_available))
2420       setup_info[i].type |= TYPE_GHOSTED;
2421
2422 #if 1
2423     if (setup_info[i].type & (TYPE_SWITCH |
2424                               TYPE_YES_NO |
2425                               TYPE_STRING |
2426                               TYPE_ECS_AGA |
2427                               TYPE_KEYTEXT))
2428       font_nr = FONT_MENU_2;
2429 #else
2430     if (setup_info[i].type & TYPE_STRING)
2431       font_nr = FONT_MENU_2;
2432 #endif
2433
2434     DrawText(mSX + 32, mSY + ypos * 32, setup_info[i].text, font_nr);
2435
2436     if (setup_info[i].type & TYPE_ENTER_MENU)
2437       initCursor(i, IMG_MENU_BUTTON_ENTER_MENU);
2438     else if (setup_info[i].type & TYPE_LEAVE_MENU)
2439       initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU);
2440     else if (setup_info[i].type & ~TYPE_SKIP_ENTRY)
2441       initCursor(i, IMG_MENU_BUTTON);
2442
2443     if (setup_info[i].type & TYPE_VALUE)
2444       drawSetupValue(i);
2445
2446     num_setup_info++;
2447   }
2448
2449 #if 0
2450   DrawTextSCentered(SYSIZE - 20, FONT_TEXT_4,
2451                     "Joysticks deactivated in setup menu");
2452 #endif
2453
2454   FadeToFront();
2455   InitAnimation();
2456   HandleSetupScreen_Generic(0, 0, 0, 0, MB_MENU_INITIALIZE);
2457 }
2458
2459 void HandleSetupScreen_Generic(int mx, int my, int dx, int dy, int button)
2460 {
2461   static int choice_store[MAX_SETUP_MODES];
2462   int choice = choice_store[setup_mode];        /* always starts with 0 */
2463   int x = 0;
2464   int y = choice;
2465
2466   if (button == MB_MENU_INITIALIZE)
2467   {
2468     /* advance to first valid menu entry */
2469     while (choice < num_setup_info &&
2470            setup_info[choice].type & TYPE_SKIP_ENTRY)
2471       choice++;
2472     choice_store[setup_mode] = choice;
2473
2474     drawCursor(choice, FC_RED);
2475     return;
2476   }
2477   else if (button == MB_MENU_LEAVE)
2478   {
2479     for (y = 0; y < num_setup_info; y++)
2480     {
2481       if (setup_info[y].type & TYPE_LEAVE_MENU)
2482       {
2483         void (*menu_callback_function)(void) = setup_info[y].value;
2484
2485         menu_callback_function();
2486         break;  /* absolutely needed because function changes 'setup_info'! */
2487       }
2488     }
2489
2490     return;
2491   }
2492
2493   if (mx || my)         /* mouse input */
2494   {
2495     x = (mx - mSX) / 32;
2496     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
2497   }
2498   else if (dx || dy)    /* keyboard input */
2499   {
2500     if (dx)
2501     {
2502       int menu_navigation_type = (dx < 0 ? TYPE_LEAVE_MENU : TYPE_ENTER_MENU);
2503
2504       if (setup_info[choice].type & menu_navigation_type ||
2505           setup_info[choice].type & TYPE_BOOLEAN_STYLE)
2506         button = MB_MENU_CHOICE;
2507     }
2508     else if (dy)
2509       y = choice + dy;
2510
2511     /* jump to next non-empty menu entry (up or down) */
2512     while (y > 0 && y < num_setup_info - 1 &&
2513            setup_info[y].type & TYPE_SKIP_ENTRY)
2514       y += dy;
2515   }
2516
2517   if (IN_VIS_FIELD(x, y) &&
2518       y >= 0 && y < num_setup_info && setup_info[y].type & ~TYPE_SKIP_ENTRY)
2519   {
2520     if (button)
2521     {
2522       if (y != choice)
2523       {
2524         drawCursor(y, FC_RED);
2525         drawCursor(choice, FC_BLUE);
2526         choice = choice_store[setup_mode] = y;
2527       }
2528     }
2529     else if (!(setup_info[y].type & TYPE_GHOSTED))
2530     {
2531       if (setup_info[y].type & TYPE_ENTER_OR_LEAVE_MENU)
2532       {
2533         void (*menu_callback_function)(void) = setup_info[choice].value;
2534
2535         menu_callback_function();
2536       }
2537       else
2538       {
2539         if (setup_info[y].type & TYPE_KEYTEXT &&
2540             setup_info[y + 1].type & TYPE_KEY)
2541           y++;
2542
2543         if (setup_info[y].type & TYPE_VALUE)
2544           changeSetupValue(y);
2545       }
2546     }
2547   }
2548 }
2549
2550 void DrawSetupScreen_Input()
2551 {
2552   ClearWindow();
2553
2554 #if 1
2555   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, "Setup Input");
2556 #else
2557   DrawText(mSX + 16, mSY + 16, "Setup Input", FONT_TITLE_1);
2558 #endif
2559
2560   initCursor(0,  IMG_MENU_BUTTON);
2561   initCursor(1,  IMG_MENU_BUTTON);
2562   initCursor(2,  IMG_MENU_BUTTON_ENTER_MENU);
2563   initCursor(13, IMG_MENU_BUTTON_LEAVE_MENU);
2564
2565   drawCursorXY(10, 0, IMG_MENU_BUTTON_LEFT);
2566   drawCursorXY(12, 0, IMG_MENU_BUTTON_RIGHT);
2567
2568   DrawText(mSX + 32, mSY +  2 * 32, "Player:", FONT_MENU_1);
2569   DrawText(mSX + 32, mSY +  3 * 32, "Device:", FONT_MENU_1);
2570   DrawText(mSX + 32, mSY + 15 * 32, "Back",   FONT_MENU_1);
2571
2572 #if 0
2573   DeactivateJoystickForCalibration();
2574 #endif
2575 #if 1
2576   DrawTextSCentered(SYSIZE - 20, FONT_TEXT_4,
2577                     "Joysticks deactivated on this screen");
2578 #endif
2579
2580   HandleSetupScreen_Input(0, 0, 0, 0, MB_MENU_INITIALIZE);
2581   FadeToFront();
2582   InitAnimation();
2583 }
2584
2585 static void setJoystickDeviceToNr(char *device_name, int device_nr)
2586 {
2587   if (device_name == NULL)
2588     return;
2589
2590   if (device_nr < 0 || device_nr >= MAX_PLAYERS)
2591     device_nr = 0;
2592
2593   if (strlen(device_name) > 1)
2594   {
2595     char c1 = device_name[strlen(device_name) - 1];
2596     char c2 = device_name[strlen(device_name) - 2];
2597
2598     if (c1 >= '0' && c1 <= '9' && !(c2 >= '0' && c2 <= '9'))
2599       device_name[strlen(device_name) - 1] = '0' + (char)(device_nr % 10);
2600   }
2601   else
2602     strncpy(device_name, getDeviceNameFromJoystickNr(device_nr),
2603             strlen(device_name));
2604 }
2605
2606 static void drawPlayerSetupInputInfo(int player_nr)
2607 {
2608   int i;
2609   static struct SetupKeyboardInfo custom_key;
2610   static struct
2611   {
2612     Key *key;
2613     char *text;
2614   } custom[] =
2615   {
2616     { &custom_key.left,  "Joystick Left"  },
2617     { &custom_key.right, "Joystick Right" },
2618     { &custom_key.up,    "Joystick Up"    },
2619     { &custom_key.down,  "Joystick Down"  },
2620     { &custom_key.snap,  "Button 1"       },
2621     { &custom_key.drop,  "Button 2"       }
2622   };
2623   static char *joystick_name[MAX_PLAYERS] =
2624   {
2625     "Joystick1",
2626     "Joystick2",
2627     "Joystick3",
2628     "Joystick4"
2629   };
2630
2631   InitJoysticks();
2632
2633   custom_key = setup.input[player_nr].key;
2634
2635   DrawText(mSX + 11 * 32, mSY + 2 * 32, int2str(player_nr + 1, 1),
2636            FONT_INPUT_1_ACTIVE);
2637
2638   ClearRectangleOnBackground(drawto, mSX + 8 * TILEX, mSY + 2 * TILEY,
2639                              TILEX, TILEY);
2640   DrawGraphicThruMaskExt(drawto, mSX + 8 * TILEX, mSY + 2 * TILEY,
2641                          PLAYER_NR_GFX(IMG_PLAYER_1, player_nr), 0);
2642
2643   if (setup.input[player_nr].use_joystick)
2644   {
2645     char *device_name = setup.input[player_nr].joy.device_name;
2646     char *text = joystick_name[getJoystickNrFromDeviceName(device_name)];
2647     int font_nr = (joystick.fd[player_nr] < 0 ? FONT_VALUE_OLD : FONT_VALUE_1);
2648
2649     DrawText(mSX + 8 * 32, mSY + 3 * 32, text, font_nr);
2650     DrawText(mSX + 32, mSY + 4 * 32, "Calibrate", FONT_MENU_1);
2651   }
2652   else
2653   {
2654     DrawText(mSX + 8 * 32, mSY + 3 * 32, "Keyboard ", FONT_VALUE_1);
2655     DrawText(mSX + 1 * 32, mSY + 4 * 32, "Customize", FONT_MENU_1);
2656   }
2657
2658   DrawText(mSX + 32, mSY + 5 * 32, "Actual Settings:", FONT_MENU_1);
2659   drawCursorXY(1, 4, IMG_MENU_BUTTON_LEFT);
2660   drawCursorXY(1, 5, IMG_MENU_BUTTON_RIGHT);
2661   drawCursorXY(1, 6, IMG_MENU_BUTTON_UP);
2662   drawCursorXY(1, 7, IMG_MENU_BUTTON_DOWN);
2663   DrawText(mSX + 2 * 32, mSY +  6 * 32, ":", FONT_VALUE_OLD);
2664   DrawText(mSX + 2 * 32, mSY +  7 * 32, ":", FONT_VALUE_OLD);
2665   DrawText(mSX + 2 * 32, mSY +  8 * 32, ":", FONT_VALUE_OLD);
2666   DrawText(mSX + 2 * 32, mSY +  9 * 32, ":", FONT_VALUE_OLD);
2667   DrawText(mSX + 1 * 32, mSY + 10 * 32, "Snap Field:", FONT_VALUE_OLD);
2668   DrawText(mSX + 1 * 32, mSY + 12 * 32, "Drop Element:", FONT_VALUE_OLD);
2669
2670   for (i = 0; i < 6; i++)
2671   {
2672     int ypos = 6 + i + (i > 3 ? i-3 : 0);
2673
2674     DrawText(mSX + 3 * 32, mSY + ypos * 32,
2675              "              ", FONT_VALUE_1);
2676     DrawText(mSX + 3 * 32, mSY + ypos * 32,
2677              (setup.input[player_nr].use_joystick ?
2678               custom[i].text :
2679               getKeyNameFromKey(*custom[i].key)), FONT_VALUE_1);
2680   }
2681 }
2682
2683 void HandleSetupScreen_Input(int mx, int my, int dx, int dy, int button)
2684 {
2685   static int choice = 0;
2686   static int player_nr = 0;
2687   int x = 0;
2688   int y = choice;
2689   int pos_start  = SETUPINPUT_SCREEN_POS_START;
2690   int pos_empty1 = SETUPINPUT_SCREEN_POS_EMPTY1;
2691   int pos_empty2 = SETUPINPUT_SCREEN_POS_EMPTY2;
2692   int pos_end    = SETUPINPUT_SCREEN_POS_END;
2693
2694   if (button == MB_MENU_INITIALIZE)
2695   {
2696     drawPlayerSetupInputInfo(player_nr);
2697     drawCursor(choice, FC_RED);
2698
2699     return;
2700   }
2701   else if (button == MB_MENU_LEAVE)
2702   {
2703     setup_mode = SETUP_MODE_MAIN;
2704     DrawSetupScreen();
2705     InitJoysticks();
2706
2707     return;
2708   }
2709
2710   if (mx || my)         /* mouse input */
2711   {
2712     x = (mx - mSX) / 32;
2713     y = (my - mSY) / 32 - MENU_SCREEN_START_YPOS;
2714   }
2715   else if (dx || dy)    /* keyboard input */
2716   {
2717     if (dx && choice == 0)
2718       x = (dx < 0 ? 10 : 12);
2719     else if ((dx && choice == 1) ||
2720              (dx == +1 && choice == 2) ||
2721              (dx == -1 && choice == pos_end))
2722       button = MB_MENU_CHOICE;
2723     else if (dy)
2724       y = choice + dy;
2725
2726     if (y >= pos_empty1 && y <= pos_empty2)
2727       y = (dy > 0 ? pos_empty2 + 1 : pos_empty1 - 1);
2728   }
2729
2730   if (IN_VIS_FIELD(x, y) &&
2731       y == 0 && ((x < 10 && !button) || ((x == 10 || x == 12) && button)))
2732   {
2733     static unsigned long delay = 0;
2734
2735     if (!DelayReached(&delay, GADGET_FRAME_DELAY))
2736       return;
2737
2738     player_nr = (player_nr + (x == 10 ? -1 : +1) + MAX_PLAYERS) % MAX_PLAYERS;
2739
2740     drawPlayerSetupInputInfo(player_nr);
2741   }
2742   else if (IN_VIS_FIELD(x, y) &&
2743            y >= pos_start && y <= pos_end &&
2744            !(y >= pos_empty1 && y <= pos_empty2))
2745   {
2746     if (button)
2747     {
2748       if (y != choice)
2749       {
2750         drawCursor(y, FC_RED);
2751         drawCursor(choice, FC_BLUE);
2752         choice = y;
2753       }
2754     }
2755     else
2756     {
2757       if (y == 1)
2758       {
2759         char *device_name = setup.input[player_nr].joy.device_name;
2760
2761         if (!setup.input[player_nr].use_joystick)
2762         {
2763           int new_device_nr = (dx >= 0 ? 0 : MAX_PLAYERS - 1);
2764
2765           setJoystickDeviceToNr(device_name, new_device_nr);
2766           setup.input[player_nr].use_joystick = TRUE;
2767         }
2768         else
2769         {
2770           int device_nr = getJoystickNrFromDeviceName(device_name);
2771           int new_device_nr = device_nr + (dx >= 0 ? +1 : -1);
2772
2773           if (new_device_nr < 0 || new_device_nr >= MAX_PLAYERS)
2774             setup.input[player_nr].use_joystick = FALSE;
2775           else
2776             setJoystickDeviceToNr(device_name, new_device_nr);
2777         }
2778
2779         drawPlayerSetupInputInfo(player_nr);
2780       }
2781       else if (y == 2)
2782       {
2783         if (setup.input[player_nr].use_joystick)
2784         {
2785           InitJoysticks();
2786           CalibrateJoystick(player_nr);
2787         }
2788         else
2789           CustomizeKeyboard(player_nr);
2790       }
2791       else if (y == pos_end)
2792       {
2793         InitJoysticks();
2794
2795         setup_mode = SETUP_MODE_MAIN;
2796         DrawSetupScreen();
2797       }
2798     }
2799   }
2800 }
2801
2802 void CustomizeKeyboard(int player_nr)
2803 {
2804   int i;
2805   int step_nr;
2806   boolean finished = FALSE;
2807   static struct SetupKeyboardInfo custom_key;
2808   static struct
2809   {
2810     Key *key;
2811     char *text;
2812   } customize_step[] =
2813   {
2814     { &custom_key.left,  "Move Left"    },
2815     { &custom_key.right, "Move Right"   },
2816     { &custom_key.up,    "Move Up"      },
2817     { &custom_key.down,  "Move Down"    },
2818     { &custom_key.snap,  "Snap Field"   },
2819     { &custom_key.drop,  "Drop Element" }
2820   };
2821
2822   /* read existing key bindings from player setup */
2823   custom_key = setup.input[player_nr].key;
2824
2825   ClearWindow();
2826
2827 #if 1
2828   DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, "Keyboard Input");
2829 #else
2830   DrawText(mSX + 16, mSY + 16, "Keyboard Input", FONT_TITLE_1);
2831 #endif
2832
2833   BackToFront();
2834   InitAnimation();
2835
2836   step_nr = 0;
2837   DrawText(mSX, mSY + (2 + 2 * step_nr) * 32,
2838            customize_step[step_nr].text, FONT_INPUT_1_ACTIVE);
2839   DrawText(mSX, mSY + (2 + 2 * step_nr + 1) * 32,
2840            "Key:", FONT_INPUT_1_ACTIVE);
2841   DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
2842            getKeyNameFromKey(*customize_step[step_nr].key), FONT_VALUE_OLD);
2843
2844   while (!finished)
2845   {
2846     if (PendingEvent())         /* got event */
2847     {
2848       Event event;
2849
2850       NextEvent(&event);
2851
2852       switch(event.type)
2853       {
2854         case EVENT_KEYPRESS:
2855           {
2856             Key key = GetEventKey((KeyEvent *)&event, FALSE);
2857
2858             if (key == KSYM_Escape || (key == KSYM_Return && step_nr == 6))
2859             {
2860               finished = TRUE;
2861               break;
2862             }
2863
2864             /* all keys configured -- wait for "Escape" or "Return" key */
2865             if (step_nr == 6)
2866               break;
2867
2868             /* press 'Enter' to keep the existing key binding */
2869             if (key == KSYM_Return)
2870               key = *customize_step[step_nr].key;
2871
2872             /* check if key already used */
2873             for (i = 0; i < step_nr; i++)
2874               if (*customize_step[i].key == key)
2875                 break;
2876             if (i < step_nr)
2877               break;
2878
2879             /* got new key binding */
2880             *customize_step[step_nr].key = key;
2881             DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
2882                      "             ", FONT_VALUE_1);
2883             DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
2884                      getKeyNameFromKey(key), FONT_VALUE_1);
2885             step_nr++;
2886
2887             /* un-highlight last query */
2888             DrawText(mSX, mSY + (2 + 2 * (step_nr - 1)) * 32,
2889                      customize_step[step_nr - 1].text, FONT_MENU_1);
2890             DrawText(mSX, mSY + (2 + 2 * (step_nr - 1) + 1) * 32,
2891                      "Key:", FONT_MENU_1);
2892
2893             /* press 'Enter' to leave */
2894             if (step_nr == 6)
2895             {
2896               DrawText(mSX + 16, mSY + 15 * 32 + 16,
2897                        "Press Enter", FONT_TITLE_1);
2898               break;
2899             }
2900
2901             /* query next key binding */
2902             DrawText(mSX, mSY + (2 + 2 * step_nr) * 32,
2903                      customize_step[step_nr].text, FONT_INPUT_1_ACTIVE);
2904             DrawText(mSX, mSY + (2 + 2 * step_nr + 1) * 32,
2905                      "Key:", FONT_INPUT_1_ACTIVE);
2906             DrawText(mSX + 4 * 32, mSY + (2 + 2 * step_nr + 1) * 32,
2907                      getKeyNameFromKey(*customize_step[step_nr].key),
2908                      FONT_VALUE_OLD);
2909           }
2910           break;
2911
2912         case EVENT_KEYRELEASE:
2913           key_joystick_mapping = 0;
2914           break;
2915
2916         default:
2917           HandleOtherEvents(&event);
2918           break;
2919       }
2920     }
2921
2922     DoAnimation();
2923     BackToFront();
2924
2925     /* don't eat all CPU time */
2926     Delay(10);
2927   }
2928
2929   /* write new key bindings back to player setup */
2930   setup.input[player_nr].key = custom_key;
2931
2932   StopAnimation();
2933   DrawSetupScreen_Input();
2934 }
2935
2936 static boolean CalibrateJoystickMain(int player_nr)
2937 {
2938   int new_joystick_xleft = JOYSTICK_XMIDDLE;
2939   int new_joystick_xright = JOYSTICK_XMIDDLE;
2940   int new_joystick_yupper = JOYSTICK_YMIDDLE;
2941   int new_joystick_ylower = JOYSTICK_YMIDDLE;
2942   int new_joystick_xmiddle, new_joystick_ymiddle;
2943
2944   int joystick_fd = joystick.fd[player_nr];
2945   int x, y, last_x, last_y, xpos = 8, ypos = 3;
2946   boolean check[3][3];
2947   int check_remaining = 3 * 3;
2948   int joy_x, joy_y;
2949   int joy_value;
2950   int result = -1;
2951
2952   if (joystick.status == JOYSTICK_NOT_AVAILABLE)
2953     return FALSE;
2954
2955   if (joystick_fd < 0 || !setup.input[player_nr].use_joystick)
2956     return FALSE;
2957
2958   ClearWindow();
2959
2960   for (y = 0; y < 3; y++)
2961   {
2962     for (x = 0; x < 3; x++)
2963     {
2964       DrawGraphic(xpos + x - 1, ypos + y - 1, IMG_MENU_CALIBRATE_BLUE, 0);
2965       check[x][y] = FALSE;
2966     }
2967   }
2968
2969   DrawTextSCentered(mSY - SY +  6 * 32, FONT_TITLE_1, "Rotate joystick");
2970   DrawTextSCentered(mSY - SY +  7 * 32, FONT_TITLE_1, "in all directions");
2971   DrawTextSCentered(mSY - SY +  9 * 32, FONT_TITLE_1, "if all balls");
2972   DrawTextSCentered(mSY - SY + 10 * 32, FONT_TITLE_1, "are marked,");
2973   DrawTextSCentered(mSY - SY + 11 * 32, FONT_TITLE_1, "center joystick");
2974   DrawTextSCentered(mSY - SY + 12 * 32, FONT_TITLE_1, "and");
2975   DrawTextSCentered(mSY - SY + 13 * 32, FONT_TITLE_1, "press any button!");
2976
2977   joy_value = Joystick(player_nr);
2978   last_x = (joy_value & JOY_LEFT ? -1 : joy_value & JOY_RIGHT ? +1 : 0);
2979   last_y = (joy_value & JOY_UP   ? -1 : joy_value & JOY_DOWN  ? +1 : 0);
2980
2981   /* eventually uncalibrated center position (joystick could be uncentered) */
2982   if (!ReadJoystick(joystick_fd, &joy_x, &joy_y, NULL, NULL))
2983     return FALSE;
2984
2985   new_joystick_xmiddle = joy_x;
2986   new_joystick_ymiddle = joy_y;
2987
2988   DrawGraphic(xpos + last_x, ypos + last_y, IMG_MENU_CALIBRATE_RED, 0);
2989   BackToFront();
2990
2991   while (Joystick(player_nr) & JOY_BUTTON);     /* wait for released button */
2992   InitAnimation();
2993
2994   while (result < 0)
2995   {
2996     if (PendingEvent())         /* got event */
2997     {
2998       Event event;
2999
3000       NextEvent(&event);
3001
3002       switch(event.type)
3003       {
3004         case EVENT_KEYPRESS:
3005           switch(GetEventKey((KeyEvent *)&event, TRUE))
3006           {
3007             case KSYM_Return:
3008               if (check_remaining == 0)
3009                 result = 1;
3010               break;
3011
3012             case KSYM_Escape:
3013               result = 0;
3014               break;
3015
3016             default:
3017               break;
3018           }
3019           break;
3020
3021         case EVENT_KEYRELEASE:
3022           key_joystick_mapping = 0;
3023           break;
3024
3025         default:
3026           HandleOtherEvents(&event);
3027           break;
3028       }
3029     }
3030
3031     if (!ReadJoystick(joystick_fd, &joy_x, &joy_y, NULL, NULL))
3032       return FALSE;
3033
3034     new_joystick_xleft  = MIN(new_joystick_xleft,  joy_x);
3035     new_joystick_xright = MAX(new_joystick_xright, joy_x);
3036     new_joystick_yupper = MIN(new_joystick_yupper, joy_y);
3037     new_joystick_ylower = MAX(new_joystick_ylower, joy_y);
3038
3039     setup.input[player_nr].joy.xleft = new_joystick_xleft;
3040     setup.input[player_nr].joy.yupper = new_joystick_yupper;
3041     setup.input[player_nr].joy.xright = new_joystick_xright;
3042     setup.input[player_nr].joy.ylower = new_joystick_ylower;
3043     setup.input[player_nr].joy.xmiddle = new_joystick_xmiddle;
3044     setup.input[player_nr].joy.ymiddle = new_joystick_ymiddle;
3045
3046     CheckJoystickData();
3047
3048     joy_value = Joystick(player_nr);
3049
3050     if (joy_value & JOY_BUTTON && check_remaining == 0)
3051       result = 1;
3052
3053     x = (joy_value & JOY_LEFT ? -1 : joy_value & JOY_RIGHT ? +1 : 0);
3054     y = (joy_value & JOY_UP   ? -1 : joy_value & JOY_DOWN  ? +1 : 0);
3055
3056     if (x != last_x || y != last_y)
3057     {
3058       DrawGraphic(xpos + last_x, ypos + last_y, IMG_MENU_CALIBRATE_YELLOW, 0);
3059       DrawGraphic(xpos + x,      ypos + y,      IMG_MENU_CALIBRATE_RED,    0);
3060
3061       last_x = x;
3062       last_y = y;
3063
3064       if (check_remaining > 0 && !check[x+1][y+1])
3065       {
3066         check[x+1][y+1] = TRUE;
3067         check_remaining--;
3068       }
3069
3070 #if 0
3071 #ifdef DEBUG
3072       printf("LEFT / MIDDLE / RIGHT == %d / %d / %d\n",
3073              setup.input[player_nr].joy.xleft,
3074              setup.input[player_nr].joy.xmiddle,
3075              setup.input[player_nr].joy.xright);
3076       printf("UP / MIDDLE / DOWN == %d / %d / %d\n",
3077              setup.input[player_nr].joy.yupper,
3078              setup.input[player_nr].joy.ymiddle,
3079              setup.input[player_nr].joy.ylower);
3080 #endif
3081 #endif
3082
3083     }
3084
3085     DoAnimation();
3086     BackToFront();
3087
3088     /* don't eat all CPU time */
3089     Delay(10);
3090   }
3091
3092   /* calibrated center position (joystick should now be centered) */
3093   if (!ReadJoystick(joystick_fd, &joy_x, &joy_y, NULL, NULL))
3094     return FALSE;
3095
3096   new_joystick_xmiddle = joy_x;
3097   new_joystick_ymiddle = joy_y;
3098
3099   StopAnimation();
3100
3101 #if 0
3102   DrawSetupScreen_Input();
3103 #endif
3104
3105   /* wait until the last pressed button was released */
3106   while (Joystick(player_nr) & JOY_BUTTON)
3107   {
3108     if (PendingEvent())         /* got event */
3109     {
3110       Event event;
3111
3112       NextEvent(&event);
3113       HandleOtherEvents(&event);
3114
3115       Delay(10);
3116     }
3117   }
3118
3119   return TRUE;
3120 }
3121
3122 void CalibrateJoystick(int player_nr)
3123 {
3124   if (!CalibrateJoystickMain(player_nr))
3125   {
3126     char *device_name = setup.input[player_nr].joy.device_name;
3127     int nr = getJoystickNrFromDeviceName(device_name) + 1;
3128     int xpos = mSX - SX;
3129     int ypos = mSY - SY;
3130
3131     ClearWindow();
3132
3133     DrawTextF(xpos + 16, ypos + 6 * 32, FONT_TITLE_1, "   JOYSTICK %d   ", nr);
3134     DrawTextF(xpos + 16, ypos + 7 * 32, FONT_TITLE_1, " NOT AVAILABLE! ");
3135     BackToFront();
3136
3137     Delay(2000);                /* show error message for a short time */
3138
3139     ClearEventQueue();
3140   }
3141
3142 #if 1
3143   DrawSetupScreen_Input();
3144 #endif
3145 }
3146
3147 void DrawSetupScreen()
3148 {
3149   DeactivateJoystick();
3150
3151   SetMainBackgroundImage(IMG_BACKGROUND_SETUP);
3152
3153   if (setup_mode == SETUP_MODE_INPUT)
3154     DrawSetupScreen_Input();
3155   else if (setup_mode == SETUP_MODE_CHOOSE_GRAPHICS)
3156     DrawChooseTree(&artwork.gfx_current);
3157   else if (setup_mode == SETUP_MODE_CHOOSE_SOUNDS)
3158     DrawChooseTree(&artwork.snd_current);
3159   else if (setup_mode == SETUP_MODE_CHOOSE_MUSIC)
3160     DrawChooseTree(&artwork.mus_current);
3161   else
3162     DrawSetupScreen_Generic();
3163
3164   PlayMenuSound();
3165   PlayMenuMusic();
3166 }
3167
3168 void HandleSetupScreen(int mx, int my, int dx, int dy, int button)
3169 {
3170   if (setup_mode == SETUP_MODE_INPUT)
3171     HandleSetupScreen_Input(mx, my, dx, dy, button);
3172   else if (setup_mode == SETUP_MODE_CHOOSE_GRAPHICS)
3173     HandleChooseTree(mx, my, dx, dy, button, &artwork.gfx_current);
3174   else if (setup_mode == SETUP_MODE_CHOOSE_SOUNDS)
3175     HandleChooseTree(mx, my, dx, dy, button, &artwork.snd_current);
3176   else if (setup_mode == SETUP_MODE_CHOOSE_MUSIC)
3177     HandleChooseTree(mx, my, dx, dy, button, &artwork.mus_current);
3178   else
3179     HandleSetupScreen_Generic(mx, my, dx, dy, button);
3180
3181   DoAnimation();
3182 }
3183
3184 void HandleGameActions()
3185 {
3186   if (game_status != GAME_MODE_PLAYING)
3187     return;
3188
3189   GameActions();        /* main game loop */
3190
3191   if (tape.auto_play && !tape.playing)
3192     AutoPlayTape();     /* continue automatically playing next tape */
3193 }
3194
3195 /* ---------- new screen button stuff -------------------------------------- */
3196
3197 /* graphic position and size values for buttons and scrollbars */
3198 #define SC_SCROLLBUTTON_XSIZE           TILEX
3199 #define SC_SCROLLBUTTON_YSIZE           TILEY
3200
3201 #define SC_SCROLL_VERTICAL_XSIZE        SC_SCROLLBUTTON_XSIZE
3202 #define SC_SCROLL_VERTICAL_YSIZE        ((MAX_MENU_ENTRIES_ON_SCREEN - 2) * \
3203                                          SC_SCROLLBUTTON_YSIZE)
3204 #define SC_SCROLL_UP_XPOS               (SXSIZE - SC_SCROLLBUTTON_XSIZE)
3205 #define SC_SCROLL_UP_YPOS               (2 * SC_SCROLLBUTTON_YSIZE)
3206 #define SC_SCROLL_VERTICAL_XPOS         SC_SCROLL_UP_XPOS
3207 #define SC_SCROLL_VERTICAL_YPOS         (SC_SCROLL_UP_YPOS + \
3208                                          SC_SCROLLBUTTON_YSIZE)
3209 #define SC_SCROLL_DOWN_XPOS             SC_SCROLL_UP_XPOS
3210 #define SC_SCROLL_DOWN_YPOS             (SC_SCROLL_VERTICAL_YPOS + \
3211                                          SC_SCROLL_VERTICAL_YSIZE)
3212
3213 #define SC_BORDER_SIZE                  14
3214
3215 static struct
3216 {
3217   int gfx_unpressed, gfx_pressed;
3218   int x, y;
3219   int gadget_id;
3220   char *infotext;
3221 } scrollbutton_info[NUM_SCREEN_SCROLLBUTTONS] =
3222 {
3223   {
3224     IMG_MENU_BUTTON_UP, IMG_MENU_BUTTON_UP_ACTIVE,
3225     SC_SCROLL_UP_XPOS, SC_SCROLL_UP_YPOS,
3226     SCREEN_CTRL_ID_SCROLL_UP,
3227     "scroll up"
3228   },
3229   {
3230     IMG_MENU_BUTTON_DOWN, IMG_MENU_BUTTON_DOWN_ACTIVE,
3231     SC_SCROLL_DOWN_XPOS, SC_SCROLL_DOWN_YPOS,
3232     SCREEN_CTRL_ID_SCROLL_DOWN,
3233     "scroll down"
3234   }
3235 };
3236
3237 static struct
3238 {
3239 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3240   Bitmap **gfx_unpressed, **gfx_pressed;
3241 #else
3242   int gfx_unpressed, gfx_pressed;
3243 #endif
3244   int x, y;
3245   int width, height;
3246   int type;
3247   int gadget_id;
3248   char *infotext;
3249 } scrollbar_info[NUM_SCREEN_SCROLLBARS] =
3250 {
3251   {
3252 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3253     &scrollbar_bitmap[0], &scrollbar_bitmap[1],
3254 #else
3255     IMG_MENU_SCROLLBAR, IMG_MENU_SCROLLBAR_ACTIVE,
3256 #endif
3257     SC_SCROLL_VERTICAL_XPOS, SC_SCROLL_VERTICAL_YPOS,
3258     SC_SCROLL_VERTICAL_XSIZE, SC_SCROLL_VERTICAL_YSIZE,
3259     GD_TYPE_SCROLLBAR_VERTICAL,
3260     SCREEN_CTRL_ID_SCROLL_VERTICAL,
3261     "scroll level series vertically"
3262   }
3263 };
3264
3265 static void CreateScreenScrollbuttons()
3266 {
3267   struct GadgetInfo *gi;
3268   unsigned long event_mask;
3269   int i;
3270
3271   for (i = 0; i < NUM_SCREEN_SCROLLBUTTONS; i++)
3272   {
3273     Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed;
3274     int gfx_unpressed, gfx_pressed;
3275     int x, y, width, height;
3276     int gd_x1, gd_x2, gd_y1, gd_y2;
3277     int id = scrollbutton_info[i].gadget_id;
3278
3279     event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
3280
3281     x = mSX + scrollbutton_info[i].x + menu.scrollbar_xoffset;
3282     y = mSY + scrollbutton_info[i].y;
3283     width = SC_SCROLLBUTTON_XSIZE;
3284     height = SC_SCROLLBUTTON_YSIZE;
3285
3286     if (id == SCREEN_CTRL_ID_SCROLL_DOWN)
3287       y = mSY + (SC_SCROLL_VERTICAL_YPOS +
3288                  (NUM_MENU_ENTRIES_ON_SCREEN - 2) * SC_SCROLLBUTTON_YSIZE);
3289
3290     gfx_unpressed = scrollbutton_info[i].gfx_unpressed;
3291     gfx_pressed   = scrollbutton_info[i].gfx_pressed;
3292     gd_bitmap_unpressed = graphic_info[gfx_unpressed].bitmap;
3293     gd_bitmap_pressed   = graphic_info[gfx_pressed].bitmap;
3294     gd_x1 = graphic_info[gfx_unpressed].src_x;
3295     gd_y1 = graphic_info[gfx_unpressed].src_y;
3296     gd_x2 = graphic_info[gfx_pressed].src_x;
3297     gd_y2 = graphic_info[gfx_pressed].src_y;
3298
3299     gi = CreateGadget(GDI_CUSTOM_ID, id,
3300                       GDI_CUSTOM_TYPE_ID, i,
3301                       GDI_INFO_TEXT, scrollbutton_info[i].infotext,
3302                       GDI_X, x,
3303                       GDI_Y, y,
3304                       GDI_WIDTH, width,
3305                       GDI_HEIGHT, height,
3306                       GDI_TYPE, GD_TYPE_NORMAL_BUTTON,
3307                       GDI_STATE, GD_BUTTON_UNPRESSED,
3308                       GDI_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1, gd_y1,
3309                       GDI_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2, gd_y2,
3310                       GDI_DIRECT_DRAW, FALSE,
3311                       GDI_EVENT_MASK, event_mask,
3312                       GDI_CALLBACK_ACTION, HandleScreenGadgets,
3313                       GDI_END);
3314
3315     if (gi == NULL)
3316       Error(ERR_EXIT, "cannot create gadget");
3317
3318     screen_gadget[id] = gi;
3319   }
3320 }
3321
3322 static void CreateScreenScrollbars()
3323 {
3324   int i;
3325
3326   for (i = 0; i < NUM_SCREEN_SCROLLBARS; i++)
3327   {
3328     Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed;
3329 #if !defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3330     int gfx_unpressed, gfx_pressed;
3331 #endif
3332     int x, y, width, height;
3333     int gd_x1, gd_x2, gd_y1, gd_y2;
3334     struct GadgetInfo *gi;
3335     int items_max, items_visible, item_position;
3336     unsigned long event_mask;
3337     int num_page_entries = NUM_MENU_ENTRIES_ON_SCREEN;
3338     int id = scrollbar_info[i].gadget_id;
3339
3340     event_mask = GD_EVENT_MOVING | GD_EVENT_OFF_BORDERS;
3341
3342     x = mSX + scrollbar_info[i].x + menu.scrollbar_xoffset;
3343     y = mSY + scrollbar_info[i].y;
3344     width  = scrollbar_info[i].width;
3345     height = scrollbar_info[i].height;
3346
3347     if (id == SCREEN_CTRL_ID_SCROLL_VERTICAL)
3348       height = (NUM_MENU_ENTRIES_ON_SCREEN - 2) * SC_SCROLLBUTTON_YSIZE;
3349
3350     items_max = num_page_entries;
3351     items_visible = num_page_entries;
3352     item_position = 0;
3353
3354 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3355     gd_bitmap_unpressed = *scrollbar_info[i].gfx_unpressed;
3356     gd_bitmap_pressed   = *scrollbar_info[i].gfx_pressed;
3357     gd_x1 = 0;
3358     gd_y1 = 0;
3359     gd_x2 = 0;
3360     gd_y2 = 0;
3361 #else
3362     gfx_unpressed = scrollbar_info[i].gfx_unpressed;
3363     gfx_pressed   = scrollbar_info[i].gfx_pressed;
3364     gd_bitmap_unpressed = graphic_info[gfx_unpressed].bitmap;
3365     gd_bitmap_pressed   = graphic_info[gfx_pressed].bitmap;
3366     gd_x1 = graphic_info[gfx_unpressed].src_x;
3367     gd_y1 = graphic_info[gfx_unpressed].src_y;
3368     gd_x2 = graphic_info[gfx_pressed].src_x;
3369     gd_y2 = graphic_info[gfx_pressed].src_y;
3370 #endif
3371
3372     gi = CreateGadget(GDI_CUSTOM_ID, id,
3373                       GDI_CUSTOM_TYPE_ID, i,
3374                       GDI_INFO_TEXT, scrollbar_info[i].infotext,
3375                       GDI_X, x,
3376                       GDI_Y, y,
3377                       GDI_WIDTH, width,
3378                       GDI_HEIGHT, height,
3379                       GDI_TYPE, scrollbar_info[i].type,
3380                       GDI_SCROLLBAR_ITEMS_MAX, items_max,
3381                       GDI_SCROLLBAR_ITEMS_VISIBLE, items_visible,
3382                       GDI_SCROLLBAR_ITEM_POSITION, item_position,
3383                       GDI_STATE, GD_BUTTON_UNPRESSED,
3384                       GDI_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1, gd_y1,
3385                       GDI_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2, gd_y2,
3386                       GDI_BORDER_SIZE, SC_BORDER_SIZE, SC_BORDER_SIZE,
3387                       GDI_DIRECT_DRAW, FALSE,
3388                       GDI_EVENT_MASK, event_mask,
3389                       GDI_CALLBACK_ACTION, HandleScreenGadgets,
3390                       GDI_END);
3391
3392     if (gi == NULL)
3393       Error(ERR_EXIT, "cannot create gadget");
3394
3395     screen_gadget[id] = gi;
3396   }
3397 }
3398
3399 void CreateScreenGadgets()
3400 {
3401   int last_game_status = game_status;   /* save current game status */
3402
3403 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3404   int i;
3405
3406   for (i = 0; i < NUM_SCROLLBAR_BITMAPS; i++)
3407   {
3408     scrollbar_bitmap[i] = CreateBitmap(TILEX, TILEY, DEFAULT_DEPTH);
3409
3410     /* copy pointers to clip mask and GC */
3411     scrollbar_bitmap[i]->clip_mask =
3412       graphic_info[IMG_MENU_SCROLLBAR + i].clip_mask;
3413     scrollbar_bitmap[i]->stored_clip_gc =
3414       graphic_info[IMG_MENU_SCROLLBAR + i].clip_gc;
3415
3416     BlitBitmap(graphic_info[IMG_MENU_SCROLLBAR + i].bitmap,
3417                scrollbar_bitmap[i],
3418                graphic_info[IMG_MENU_SCROLLBAR + i].src_x,
3419                graphic_info[IMG_MENU_SCROLLBAR + i].src_y,
3420                TILEX, TILEY, 0, 0);
3421   }
3422 #endif
3423
3424   /* force LEVELS draw offset for scrollbar / scrollbutton gadgets */
3425   game_status = GAME_MODE_LEVELS;
3426
3427   CreateScreenScrollbuttons();
3428   CreateScreenScrollbars();
3429
3430   game_status = last_game_status;       /* restore current game status */
3431 }
3432
3433 void FreeScreenGadgets()
3434 {
3435   int i;
3436
3437 #if defined(TARGET_X11_NATIVE_PERFORMANCE_WORKAROUND)
3438   for (i = 0; i < NUM_SCROLLBAR_BITMAPS; i++)
3439   {
3440     /* prevent freeing clip mask and GC twice */
3441     scrollbar_bitmap[i]->clip_mask = None;
3442     scrollbar_bitmap[i]->stored_clip_gc = None;
3443
3444     FreeBitmap(scrollbar_bitmap[i]);
3445   }
3446 #endif
3447
3448   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
3449     FreeGadget(screen_gadget[i]);
3450 }
3451
3452 void MapChooseTreeGadgets(TreeInfo *ti)
3453 {
3454   int num_entries = numTreeInfoInGroup(ti);
3455   int i;
3456
3457   if (num_entries <= NUM_MENU_ENTRIES_ON_SCREEN)
3458     return;
3459
3460   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
3461     MapGadget(screen_gadget[i]);
3462 }
3463
3464 #if 0
3465 void UnmapChooseTreeGadgets()
3466 {
3467   int i;
3468
3469   for (i = 0; i < NUM_SCREEN_GADGETS; i++)
3470     UnmapGadget(screen_gadget[i]);
3471 }
3472 #endif
3473
3474 static void HandleScreenGadgets(struct GadgetInfo *gi)
3475 {
3476   int id = gi->custom_id;
3477
3478   if (game_status != GAME_MODE_LEVELS && game_status != GAME_MODE_SETUP)
3479     return;
3480
3481   switch (id)
3482   {
3483     case SCREEN_CTRL_ID_SCROLL_UP:
3484       if (game_status == GAME_MODE_LEVELS)
3485         HandleChooseLevel(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
3486       else if (game_status == GAME_MODE_SETUP)
3487         HandleSetupScreen(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK);
3488       break;
3489
3490     case SCREEN_CTRL_ID_SCROLL_DOWN:
3491       if (game_status == GAME_MODE_LEVELS)
3492         HandleChooseLevel(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
3493       else if (game_status == GAME_MODE_SETUP)
3494         HandleSetupScreen(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK);
3495       break;
3496
3497     case SCREEN_CTRL_ID_SCROLL_VERTICAL:
3498       if (game_status == GAME_MODE_LEVELS)
3499         HandleChooseLevel(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE);
3500       else if (game_status == GAME_MODE_SETUP)
3501         HandleSetupScreen(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE);
3502       break;
3503
3504     default:
3505       break;
3506   }
3507 }