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