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