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