major cleanup of preprocessor hell
[rocksndiamonds.git] / src / events.c
1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // events.c
10 // ============================================================================
11
12 #include "libgame/libgame.h"
13
14 #include "events.h"
15 #include "init.h"
16 #include "screens.h"
17 #include "tools.h"
18 #include "game.h"
19 #include "editor.h"
20 #include "files.h"
21 #include "tape.h"
22 #include "network.h"
23
24
25 #define DEBUG_EVENTS            0
26
27
28 static boolean cursor_inside_playfield = FALSE;
29 static boolean playfield_cursor_set = FALSE;
30 static unsigned int playfield_cursor_delay = 0;
31
32
33 /* event filter especially needed for SDL event filtering due to
34    delay problems with lots of mouse motion events when mouse button
35    not pressed (X11 can handle this with 'PointerMotionHintMask') */
36
37 /* event filter addition for SDL2: as SDL2 does not have a function to enable
38    or disable keyboard auto-repeat, filter repeated keyboard events instead */
39
40 static int FilterEventsExt(const Event *event)
41 {
42   MotionEvent *motion;
43
44 #if defined(TARGET_SDL2)
45   /* skip repeated key press events if keyboard auto-repeat is disabled */
46   if (event->type == EVENT_KEYPRESS &&
47       event->key.repeat &&
48       !keyrepeat_status)
49     return 0;
50 #endif
51
52   /* non-motion events are directly passed to event handler functions */
53   if (event->type != EVENT_MOTIONNOTIFY)
54     return 1;
55
56   motion = (MotionEvent *)event;
57   cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
58                              motion->y >= SY && motion->y < SY + SYSIZE);
59
60   if (game_status == GAME_MODE_PLAYING && playfield_cursor_set)
61   {
62     SetMouseCursor(CURSOR_DEFAULT);
63     playfield_cursor_set = FALSE;
64     DelayReached(&playfield_cursor_delay, 0);
65   }
66
67   /* skip mouse motion events without pressed button outside level editor */
68   if (button_status == MB_RELEASED &&
69       game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
70     return 0;
71
72   return 1;
73 }
74
75 #if defined(TARGET_SDL2)
76 int FilterEvents(void *userdata, Event *event)
77 {
78   return FilterEventsExt(event);
79 }
80 #else
81 int FilterEvents(const Event *event)
82 {
83   return FilterEventsExt(event);
84 }
85 #endif
86
87 /* to prevent delay problems, skip mouse motion events if the very next
88    event is also a mouse motion event (and therefore effectively only
89    handling the last of a row of mouse motion events in the event queue) */
90
91 boolean SkipPressedMouseMotionEvent(const Event *event)
92 {
93   /* nothing to do if the current event is not a mouse motion event */
94   if (event->type != EVENT_MOTIONNOTIFY)
95     return FALSE;
96
97   /* only skip motion events with pressed button outside level editor */
98   if (button_status == MB_RELEASED ||
99       game_status == GAME_MODE_EDITOR || game_status == GAME_MODE_PLAYING)
100     return FALSE;
101
102   if (PendingEvent())
103   {
104     Event next_event;
105
106     PeekEvent(&next_event);
107
108     /* if next event is also a mouse motion event, skip the current one */
109     if (next_event.type == EVENT_MOTIONNOTIFY)
110       return TRUE;
111   }
112
113   return FALSE;
114 }
115
116 /* this is only really needed for non-SDL targets to filter unwanted events;
117    when using SDL with properly installed event filter, this function can be
118    replaced with a simple "NextEvent()" call, but it doesn't hurt either */
119
120 static boolean NextValidEvent(Event *event)
121 {
122   while (PendingEvent())
123   {
124     boolean handle_this_event = FALSE;
125
126     NextEvent(event);
127
128     if (FilterEventsExt(event))
129       handle_this_event = TRUE;
130
131     if (SkipPressedMouseMotionEvent(event))
132       handle_this_event = FALSE;
133
134     if (handle_this_event)
135       return TRUE;
136   }
137
138   return FALSE;
139 }
140
141 void EventLoop(void)
142 {
143   while (1)
144   {
145     if (PendingEvent())         /* got event */
146     {
147       Event event;
148
149       while (NextValidEvent(&event))
150       {
151         switch (event.type)
152         {
153           case EVENT_BUTTONPRESS:
154           case EVENT_BUTTONRELEASE:
155             HandleButtonEvent((ButtonEvent *) &event);
156             break;
157   
158           case EVENT_MOTIONNOTIFY:
159             HandleMotionEvent((MotionEvent *) &event);
160             break;
161
162 #if defined(TARGET_SDL2)
163           case SDL_WINDOWEVENT:
164             HandleWindowEvent((WindowEvent *) &event);
165             break;
166
167           case EVENT_FINGERPRESS:
168           case EVENT_FINGERRELEASE:
169           case EVENT_FINGERMOTION:
170             HandleFingerEvent((FingerEvent *) &event);
171             break;
172
173           case EVENT_TEXTINPUT:
174             HandleTextEvent((TextEvent *) &event);
175             break;
176
177           case SDL_APP_WILLENTERBACKGROUND:
178           case SDL_APP_DIDENTERBACKGROUND:
179           case SDL_APP_WILLENTERFOREGROUND:
180           case SDL_APP_DIDENTERFOREGROUND:
181             HandlePauseResumeEvent((PauseResumeEvent *) &event);
182             break;
183 #endif
184
185           case EVENT_KEYPRESS:
186           case EVENT_KEYRELEASE:
187             HandleKeyEvent((KeyEvent *) &event);
188             break;
189
190           default:
191             HandleOtherEvents(&event);
192             break;
193         }
194       }
195     }
196     else
197     {
198       /* when playing, display a special mouse pointer inside the playfield */
199       if (game_status == GAME_MODE_PLAYING && !tape.pausing)
200       {
201         if (!playfield_cursor_set && cursor_inside_playfield &&
202             DelayReached(&playfield_cursor_delay, 1000))
203         {
204           SetMouseCursor(CURSOR_PLAYFIELD);
205           playfield_cursor_set = TRUE;
206         }
207       }
208       else if (playfield_cursor_set)
209       {
210         SetMouseCursor(CURSOR_DEFAULT);
211         playfield_cursor_set = FALSE;
212       }
213     }
214
215     /* also execute after pending events have been processed before */
216     HandleNoEvent();
217
218     /* don't use all CPU time when idle; the main loop while playing
219        has its own synchronization and is CPU friendly, too */
220
221     if (game_status == GAME_MODE_PLAYING)
222     {
223       HandleGameActions();
224     }
225     else
226     {
227       SyncDisplay();
228
229       if (!PendingEvent())      /* delay only if no pending events */
230         Delay(10);
231     }
232
233     /* refresh window contents from drawing buffer, if needed */
234     BackToFront();
235
236     if (game_status == GAME_MODE_QUIT)
237       return;
238   }
239 }
240
241 void HandleOtherEvents(Event *event)
242 {
243   switch (event->type)
244   {
245     case EVENT_EXPOSE:
246       HandleExposeEvent((ExposeEvent *) event);
247       break;
248
249     case EVENT_UNMAPNOTIFY:
250 #if 0
251       /* This causes the game to stop not only when iconified, but also
252          when on another virtual desktop, which might be not desired. */
253       SleepWhileUnmapped();
254 #endif
255       break;
256
257     case EVENT_FOCUSIN:
258     case EVENT_FOCUSOUT:
259       HandleFocusEvent((FocusChangeEvent *) event);
260       break;
261
262     case EVENT_CLIENTMESSAGE:
263       HandleClientMessageEvent((ClientMessageEvent *) event);
264       break;
265
266 #if defined(TARGET_SDL)
267     case SDL_JOYAXISMOTION:
268     case SDL_JOYBUTTONDOWN:
269     case SDL_JOYBUTTONUP:
270       HandleJoystickEvent(event);
271       break;
272
273     case SDL_SYSWMEVENT:
274       HandleWindowManagerEvent(event);
275       break;
276 #endif
277
278     default:
279       break;
280   }
281 }
282
283 void ClearEventQueue()
284 {
285   while (PendingEvent())
286   {
287     Event event;
288
289     NextEvent(&event);
290
291     switch (event.type)
292     {
293       case EVENT_BUTTONRELEASE:
294         button_status = MB_RELEASED;
295         break;
296
297       case EVENT_KEYRELEASE:
298         ClearPlayerAction();
299         break;
300
301       default:
302         HandleOtherEvents(&event);
303         break;
304     }
305   }
306 }
307
308 void ClearPlayerAction()
309 {
310   int i;
311
312   /* simulate key release events for still pressed keys */
313   key_joystick_mapping = 0;
314   for (i = 0; i < MAX_PLAYERS; i++)
315     stored_player[i].action = 0;
316 }
317
318 void SleepWhileUnmapped()
319 {
320   boolean window_unmapped = TRUE;
321
322   KeyboardAutoRepeatOn();
323
324   while (window_unmapped)
325   {
326     Event event;
327
328     NextEvent(&event);
329
330     switch (event.type)
331     {
332       case EVENT_BUTTONRELEASE:
333         button_status = MB_RELEASED;
334         break;
335
336       case EVENT_KEYRELEASE:
337         key_joystick_mapping = 0;
338         break;
339
340       case EVENT_MAPNOTIFY:
341         window_unmapped = FALSE;
342         break;
343
344       case EVENT_UNMAPNOTIFY:
345         /* this is only to surely prevent the 'should not happen' case
346          * of recursively looping between 'SleepWhileUnmapped()' and
347          * 'HandleOtherEvents()' which usually calls this funtion.
348          */
349         break;
350
351       default:
352         HandleOtherEvents(&event);
353         break;
354     }
355   }
356
357   if (game_status == GAME_MODE_PLAYING)
358     KeyboardAutoRepeatOffUnlessAutoplay();
359 }
360
361 void HandleExposeEvent(ExposeEvent *event)
362 {
363 }
364
365 void HandleButtonEvent(ButtonEvent *event)
366 {
367 #if DEBUG_EVENTS
368   Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
369         event->button,
370         event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
371         event->x, event->y);
372 #endif
373
374   motion_status = FALSE;
375
376   if (event->type == EVENT_BUTTONPRESS)
377     button_status = event->button;
378   else
379     button_status = MB_RELEASED;
380
381   HandleButton(event->x, event->y, button_status, event->button);
382 }
383
384 void HandleMotionEvent(MotionEvent *event)
385 {
386   if (!PointerInWindow(window))
387     return;     /* window and pointer are on different screens */
388
389   if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
390     return;
391
392   motion_status = TRUE;
393
394 #if DEBUG_EVENTS
395   Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
396         button_status, event->x, event->y);
397 #endif
398
399   HandleButton(event->x, event->y, button_status, button_status);
400 }
401
402 #if defined(TARGET_SDL2)
403
404 void HandleWindowEvent(WindowEvent *event)
405 {
406 #if DEBUG_EVENTS
407   int subtype = event->event;
408
409   char *event_name =
410     (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
411      subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
412      subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
413      subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
414      subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
415      subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
416      subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
417      subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
418      subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
419      subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
420      subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
421      subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
422      subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
423      subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
424      "(UNKNOWN)");
425
426   Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
427         event_name, event->data1, event->data2);
428 #endif
429
430   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
431       event->event == SDL_WINDOWEVENT_RESIZED ||
432       event->event == SDL_WINDOWEVENT_EXPOSED)
433     SDLRedrawWindow();
434
435   if (event->event == SDL_WINDOWEVENT_RESIZED && !video.fullscreen_enabled)
436   {
437     int new_window_width  = event->data1;
438     int new_window_height = event->data2;
439
440     // if window size has changed after resizing, calculate new scaling factor
441     if (new_window_width  != video.window_width ||
442         new_window_height != video.window_height)
443     {
444       int new_xpercent = (100 * new_window_width  / video.width);
445       int new_ypercent = (100 * new_window_height / video.height);
446
447       setup.window_scaling_percent = video.window_scaling_percent =
448         MIN(MAX(MIN_WINDOW_SCALING_PERCENT, MIN(new_xpercent, new_ypercent)),
449             MAX_WINDOW_SCALING_PERCENT);
450
451       video.window_width  = new_window_width;
452       video.window_height = new_window_height;
453
454       if (game_status == GAME_MODE_SETUP)
455         RedrawSetupScreenAfterFullscreenToggle();
456
457       SetWindowTitle();
458     }
459   }
460 }
461
462 #define NUM_TOUCH_FINGERS               3
463
464 static struct
465 {
466   boolean touched;
467   SDL_FingerID finger_id;
468   int counter;
469   Key key;
470 } touch_info[NUM_TOUCH_FINGERS];
471
472 void HandleFingerEvent(FingerEvent *event)
473 {
474   static Key motion_key_x = KSYM_UNDEFINED;
475   static Key motion_key_y = KSYM_UNDEFINED;
476   static Key button_key = KSYM_UNDEFINED;
477   static float motion_x1, motion_y1;
478   static float button_x1, button_y1;
479   static SDL_FingerID motion_id = -1;
480   static SDL_FingerID button_id = -1;
481   int move_trigger_distance_percent = 2;   // percent of touchpad width/height
482   int drop_trigger_distance_percent = 5;   // percent of touchpad width/height
483   float move_trigger_distance = (float)move_trigger_distance_percent / 100;
484   float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
485   float event_x = event->x;
486   float event_y = event->y;
487
488 #if DEBUG_EVENTS
489   Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
490         event->type == EVENT_FINGERPRESS ? "pressed" :
491         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
492         event->touchId,
493         event->fingerId,
494         event->x, event->y,
495         event->dx, event->dy,
496         event->pressure);
497 #endif
498
499   if (game_status != GAME_MODE_PLAYING)
500     return;
501
502   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
503   {
504     int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
505                       KEY_PRESSED);
506     Key key = (event->x < 1.0 / 3.0 ?
507                (event->y < 1.0 / 2.0 ? setup.input[0].key.snap :
508                 setup.input[0].key.drop) :
509                event->x > 2.0 / 3.0 ?
510                (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
511                 event->y > 2.0 / 3.0 ? setup.input[0].key.down :
512                 event->x < 5.0 / 6.0 ? setup.input[0].key.left :
513                 setup.input[0].key.right) :
514                KSYM_UNDEFINED);
515     char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
516                              "KEY_PRESSED");
517     int i;
518
519     Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
520           getKeyNameFromKey(key), key_status_name, event->fingerId);
521
522     // check if we already know this touch event's finger id
523     for (i = 0; i < NUM_TOUCH_FINGERS; i++)
524     {
525       if (touch_info[i].touched &&
526           touch_info[i].finger_id == event->fingerId)
527       {
528         // Error(ERR_DEBUG, "MARK 1: %d", i);
529
530         break;
531       }
532     }
533
534     if (i >= NUM_TOUCH_FINGERS)
535     {
536       if (key_status == KEY_PRESSED)
537       {
538         int oldest_pos = 0, oldest_counter = touch_info[0].counter;
539
540         // unknown finger id -- get new, empty slot, if available
541         for (i = 0; i < NUM_TOUCH_FINGERS; i++)
542         {
543           if (touch_info[i].counter < oldest_counter)
544           {
545             oldest_pos = i;
546             oldest_counter = touch_info[i].counter;
547
548             // Error(ERR_DEBUG, "MARK 2: %d", i);
549           }
550
551           if (!touch_info[i].touched)
552           {
553             // Error(ERR_DEBUG, "MARK 3: %d", i);
554
555             break;
556           }
557         }
558
559         if (i >= NUM_TOUCH_FINGERS)
560         {
561           // all slots allocated -- use oldest slot
562           i = oldest_pos;
563
564           // Error(ERR_DEBUG, "MARK 4: %d", i);
565         }
566       }
567       else
568       {
569         // release of previously unknown key (should not happen)
570
571         if (key != KSYM_UNDEFINED)
572         {
573           HandleKey(key, KEY_RELEASED);
574
575           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
576                 getKeyNameFromKey(key), "KEY_RELEASED", i);
577         }
578       }
579     }
580
581     if (i < NUM_TOUCH_FINGERS)
582     {
583       if (key_status == KEY_PRESSED)
584       {
585         if (touch_info[i].key != key)
586         {
587           if (touch_info[i].key != KSYM_UNDEFINED)
588           {
589             HandleKey(touch_info[i].key, KEY_RELEASED);
590
591             Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
592                   getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
593           }
594
595           if (key != KSYM_UNDEFINED)
596           {
597             HandleKey(key, KEY_PRESSED);
598
599             Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
600                   getKeyNameFromKey(key), "KEY_PRESSED", i);
601           }
602         }
603
604         touch_info[i].touched = TRUE;
605         touch_info[i].finger_id = event->fingerId;
606         touch_info[i].counter = Counter();
607         touch_info[i].key = key;
608       }
609       else
610       {
611         if (touch_info[i].key != KSYM_UNDEFINED)
612         {
613           HandleKey(touch_info[i].key, KEY_RELEASED);
614
615           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
616                 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
617         }
618
619         touch_info[i].touched = FALSE;
620         touch_info[i].finger_id = 0;
621         touch_info[i].counter = 0;
622         touch_info[i].key = 0;
623       }
624     }
625
626     return;
627   }
628
629   // use touch direction control
630
631   if (event->type == EVENT_FINGERPRESS)
632   {
633     if (event_x > 1.0 / 3.0)
634     {
635       // motion area
636
637       motion_id = event->fingerId;
638
639       motion_x1 = event_x;
640       motion_y1 = event_y;
641
642       motion_key_x = KSYM_UNDEFINED;
643       motion_key_y = KSYM_UNDEFINED;
644
645       Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
646     }
647     else
648     {
649       // button area
650
651       button_id = event->fingerId;
652
653       button_x1 = event_x;
654       button_y1 = event_y;
655
656       button_key = setup.input[0].key.snap;
657
658       HandleKey(button_key, KEY_PRESSED);
659
660       Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
661     }
662   }
663   else if (event->type == EVENT_FINGERRELEASE)
664   {
665     if (event->fingerId == motion_id)
666     {
667       motion_id = -1;
668
669       if (motion_key_x != KSYM_UNDEFINED)
670         HandleKey(motion_key_x, KEY_RELEASED);
671       if (motion_key_y != KSYM_UNDEFINED)
672         HandleKey(motion_key_y, KEY_RELEASED);
673
674       motion_key_x = KSYM_UNDEFINED;
675       motion_key_y = KSYM_UNDEFINED;
676
677       Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
678     }
679     else if (event->fingerId == button_id)
680     {
681       button_id = -1;
682
683       if (button_key != KSYM_UNDEFINED)
684         HandleKey(button_key, KEY_RELEASED);
685
686       button_key = KSYM_UNDEFINED;
687
688       Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
689     }
690   }
691   else if (event->type == EVENT_FINGERMOTION)
692   {
693     if (event->fingerId == motion_id)
694     {
695       float distance_x = ABS(event_x - motion_x1);
696       float distance_y = ABS(event_y - motion_y1);
697       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
698                               event_x > motion_x1 ? setup.input[0].key.right :
699                               KSYM_UNDEFINED);
700       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
701                               event_y > motion_y1 ? setup.input[0].key.down :
702                               KSYM_UNDEFINED);
703
704       if (distance_x < move_trigger_distance / 2 ||
705           distance_x < distance_y)
706         new_motion_key_x = KSYM_UNDEFINED;
707
708       if (distance_y < move_trigger_distance / 2 ||
709           distance_y < distance_x)
710         new_motion_key_y = KSYM_UNDEFINED;
711
712       if (distance_x > move_trigger_distance ||
713           distance_y > move_trigger_distance)
714       {
715         if (new_motion_key_x != motion_key_x)
716         {
717           if (motion_key_x != KSYM_UNDEFINED)
718             HandleKey(motion_key_x, KEY_RELEASED);
719           if (new_motion_key_x != KSYM_UNDEFINED)
720             HandleKey(new_motion_key_x, KEY_PRESSED);
721         }
722
723         if (new_motion_key_y != motion_key_y)
724         {
725           if (motion_key_y != KSYM_UNDEFINED)
726             HandleKey(motion_key_y, KEY_RELEASED);
727           if (new_motion_key_y != KSYM_UNDEFINED)
728             HandleKey(new_motion_key_y, KEY_PRESSED);
729         }
730
731         motion_x1 = event_x;
732         motion_y1 = event_y;
733
734         motion_key_x = new_motion_key_x;
735         motion_key_y = new_motion_key_y;
736
737         Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
738       }
739     }
740     else if (event->fingerId == button_id)
741     {
742       float distance_x = ABS(event_x - button_x1);
743       float distance_y = ABS(event_y - button_y1);
744
745       if (distance_x < drop_trigger_distance / 2 &&
746           distance_y > drop_trigger_distance)
747       {
748         if (button_key == setup.input[0].key.snap)
749           HandleKey(button_key, KEY_RELEASED);
750
751         button_x1 = event_x;
752         button_y1 = event_y;
753
754         button_key = setup.input[0].key.drop;
755
756         HandleKey(button_key, KEY_PRESSED);
757
758         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
759       }
760     }
761   }
762 }
763
764 static boolean checkTextInputKeyModState()
765 {
766   // when playing, only handle raw key events and ignore text input
767   if (game_status == GAME_MODE_PLAYING)
768     return FALSE;
769
770   return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
771 }
772
773 void HandleTextEvent(TextEvent *event)
774 {
775   char *text = event->text;
776   Key key = getKeyFromKeyName(text);
777
778 #if DEBUG_EVENTS
779   Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
780         text,
781         strlen(text),
782         text[0], (int)(text[0]),
783         key,
784         getKeyNameFromKey(key),
785         GetKeyModState());
786 #endif
787
788   // if (game_status != GAME_MODE_PLAYING && GetKeyModState() != KMOD_None)
789   /*
790   if (game_status != GAME_MODE_PLAYING &&
791       (GetKeyModState() & KMOD_TextInput) != KMOD_None)
792   */
793   if (checkTextInputKeyModState())
794   {
795     HandleKey(key, KEY_PRESSED);
796     HandleKey(key, KEY_RELEASED);
797   }
798 }
799
800 void HandlePauseResumeEvent(PauseResumeEvent *event)
801 {
802   if (event->type == SDL_APP_WILLENTERBACKGROUND)
803   {
804     Mix_PauseMusic();
805   }
806   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
807   {
808     Mix_ResumeMusic();
809   }
810 }
811
812 #endif
813
814 void HandleKeyEvent(KeyEvent *event)
815 {
816   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
817   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
818   Key key = GetEventKey(event, with_modifiers);
819   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
820
821 #if DEBUG_EVENTS
822   Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
823         event->type == EVENT_KEYPRESS ? "pressed" : "released",
824         event->keysym.scancode,
825         event->keysym.sym,
826         keymod,
827         GetKeyModState(),
828         key,
829         getKeyNameFromKey(key));
830 #endif
831
832 #if defined(PLATFORM_ANDROID)
833   // always map the "back" button to the "escape" key on Android devices
834   if (key == KSYM_Back)
835     key = KSYM_Escape;
836 #endif
837
838   HandleKeyModState(keymod, key_status);
839
840 #if defined(TARGET_SDL2)
841   if (!checkTextInputKeyModState())
842     HandleKey(key, key_status);
843 #else
844   HandleKey(key, key_status);
845 #endif
846 }
847
848 void HandleFocusEvent(FocusChangeEvent *event)
849 {
850   static int old_joystick_status = -1;
851
852   if (event->type == EVENT_FOCUSOUT)
853   {
854     KeyboardAutoRepeatOn();
855     old_joystick_status = joystick.status;
856     joystick.status = JOYSTICK_NOT_AVAILABLE;
857
858     ClearPlayerAction();
859   }
860   else if (event->type == EVENT_FOCUSIN)
861   {
862     /* When there are two Rocks'n'Diamonds windows which overlap and
863        the player moves the pointer from one game window to the other,
864        a 'FocusOut' event is generated for the window the pointer is
865        leaving and a 'FocusIn' event is generated for the window the
866        pointer is entering. In some cases, it can happen that the
867        'FocusIn' event is handled by the one game process before the
868        'FocusOut' event by the other game process. In this case the
869        X11 environment would end up with activated keyboard auto repeat,
870        because unfortunately this is a global setting and not (which
871        would be far better) set for each X11 window individually.
872        The effect would be keyboard auto repeat while playing the game
873        (game_status == GAME_MODE_PLAYING), which is not desired.
874        To avoid this special case, we just wait 1/10 second before
875        processing the 'FocusIn' event.
876     */
877
878     if (game_status == GAME_MODE_PLAYING)
879     {
880       Delay(100);
881       KeyboardAutoRepeatOffUnlessAutoplay();
882     }
883
884     if (old_joystick_status != -1)
885       joystick.status = old_joystick_status;
886   }
887 }
888
889 void HandleClientMessageEvent(ClientMessageEvent *event)
890 {
891   if (CheckCloseWindowEvent(event))
892     CloseAllAndExit(0);
893 }
894
895 void HandleWindowManagerEvent(Event *event)
896 {
897 #if defined(TARGET_SDL)
898   SDLHandleWindowManagerEvent(event);
899 #endif
900 }
901
902 void HandleButton(int mx, int my, int button, int button_nr)
903 {
904   static int old_mx = 0, old_my = 0;
905
906   if (button < 0)
907   {
908     mx = old_mx;
909     my = old_my;
910     button = -button;
911   }
912   else
913   {
914     old_mx = mx;
915     old_my = my;
916   }
917
918 #if defined(PLATFORM_ANDROID)
919   if (game_status != GAME_MODE_PLAYING &&
920       HandleGadgets(mx, my, button))
921   {
922     /* do not handle this button event anymore */
923     mx = my = -32;      /* force mouse event to be outside screen tiles */
924   }
925 #else
926   if (HandleGadgets(mx, my, button))
927   {
928     /* do not handle this button event anymore */
929     mx = my = -32;      /* force mouse event to be outside screen tiles */
930   }
931 #endif
932
933   /* do not use scroll wheel button events for anything other than gadgets */
934   if (IS_WHEEL_BUTTON(button_nr))
935     return;
936
937   switch (game_status)
938   {
939     case GAME_MODE_TITLE:
940       HandleTitleScreen(mx, my, 0, 0, button);
941       break;
942
943     case GAME_MODE_MAIN:
944       HandleMainMenu(mx, my, 0, 0, button);
945       break;
946
947     case GAME_MODE_PSEUDO_TYPENAME:
948       HandleTypeName(0, KSYM_Return);
949       break;
950
951     case GAME_MODE_LEVELS:
952       HandleChooseLevelSet(mx, my, 0, 0, button);
953       break;
954
955     case GAME_MODE_LEVELNR:
956       HandleChooseLevelNr(mx, my, 0, 0, button);
957       break;
958
959     case GAME_MODE_SCORES:
960       HandleHallOfFame(0, 0, 0, 0, button);
961       break;
962
963     case GAME_MODE_EDITOR:
964       HandleLevelEditorIdle();
965       break;
966
967     case GAME_MODE_INFO:
968       HandleInfoScreen(mx, my, 0, 0, button);
969       break;
970
971     case GAME_MODE_SETUP:
972       HandleSetupScreen(mx, my, 0, 0, button);
973       break;
974
975     case GAME_MODE_PLAYING:
976 #ifdef DEBUG
977       if (button == MB_PRESSED && !motion_status && IN_GFX_FIELD_PLAY(mx, my))
978         DumpTile(LEVELX((mx - SX) / TILESIZE_VAR),
979                  LEVELY((my - SY) / TILESIZE_VAR));
980         // DumpTile(LEVELX((mx - SX) / TILEX), LEVELY((my - SY) / TILEY));
981 #endif
982       break;
983
984     default:
985       break;
986   }
987 }
988
989 static boolean is_string_suffix(char *string, char *suffix)
990 {
991   int string_len = strlen(string);
992   int suffix_len = strlen(suffix);
993
994   if (suffix_len > string_len)
995     return FALSE;
996
997   return (strEqual(&string[string_len - suffix_len], suffix));
998 }
999
1000 #define MAX_CHEAT_INPUT_LEN     32
1001
1002 static void HandleKeysSpecial(Key key)
1003 {
1004   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1005   char letter = getCharFromKey(key);
1006   int cheat_input_len = strlen(cheat_input);
1007   int i;
1008
1009   if (letter == 0)
1010     return;
1011
1012   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1013   {
1014     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1015       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1016
1017     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1018   }
1019
1020   cheat_input[cheat_input_len++] = letter;
1021   cheat_input[cheat_input_len] = '\0';
1022
1023 #if DEBUG_EVENTS
1024   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1025 #endif
1026
1027   if (game_status == GAME_MODE_MAIN)
1028   {
1029     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1030         is_string_suffix(cheat_input, ":ist"))
1031     {
1032       InsertSolutionTape();
1033     }
1034     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1035              is_string_suffix(cheat_input, ":rg"))
1036     {
1037       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1038       DrawMainMenu();
1039     }
1040     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1041              is_string_suffix(cheat_input, ":rs"))
1042     {
1043       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1044       DrawMainMenu();
1045     }
1046     else if (is_string_suffix(cheat_input, ":reload-music") ||
1047              is_string_suffix(cheat_input, ":rm"))
1048     {
1049       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1050       DrawMainMenu();
1051     }
1052     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1053              is_string_suffix(cheat_input, ":ra"))
1054     {
1055       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1056                           1 << ARTWORK_TYPE_SOUNDS |
1057                           1 << ARTWORK_TYPE_MUSIC);
1058       DrawMainMenu();
1059     }
1060     else if (is_string_suffix(cheat_input, ":dump-level") ||
1061              is_string_suffix(cheat_input, ":dl"))
1062     {
1063       DumpLevel(&level);
1064     }
1065     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1066              is_string_suffix(cheat_input, ":dt"))
1067     {
1068       DumpTape(&tape);
1069     }
1070     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1071              is_string_suffix(cheat_input, ":ft"))
1072     {
1073       /* fix single-player tapes that contain player input for more than one
1074          player (due to a bug in 3.3.1.2 and earlier versions), which results
1075          in playing levels with more than one player in multi-player mode,
1076          even though the tape was originally recorded in single-player mode */
1077
1078       /* remove player input actions for all players but the first one */
1079       for (i = 1; i < MAX_PLAYERS; i++)
1080         tape.player_participates[i] = FALSE;
1081
1082       tape.changed = TRUE;
1083     }
1084     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1085              is_string_suffix(cheat_input, ":snl"))
1086     {
1087       SaveNativeLevel(&level);
1088     }
1089   }
1090   else if (game_status == GAME_MODE_PLAYING)
1091   {
1092 #ifdef DEBUG
1093     if (is_string_suffix(cheat_input, ".q"))
1094       DEBUG_SetMaximumDynamite();
1095 #endif
1096   }
1097   else if (game_status == GAME_MODE_EDITOR)
1098   {
1099     if (is_string_suffix(cheat_input, ":dump-brush") ||
1100         is_string_suffix(cheat_input, ":DB"))
1101     {
1102       DumpBrush();
1103     }
1104     else if (is_string_suffix(cheat_input, ":DDB"))
1105     {
1106       DumpBrush_Small();
1107     }
1108   }
1109 }
1110
1111 void HandleKey(Key key, int key_status)
1112 {
1113   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1114   static struct SetupKeyboardInfo ski;
1115   static struct SetupShortcutInfo ssi;
1116   static struct
1117   {
1118     Key *key_custom;
1119     Key *key_snap;
1120     Key key_default;
1121     byte action;
1122   } key_info[] =
1123   {
1124     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1125     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1126     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1127     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1128     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1129     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1130   };
1131   int joy = 0;
1132   int i;
1133
1134   if (game_status == GAME_MODE_PLAYING)
1135   {
1136     /* only needed for single-step tape recording mode */
1137     static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1138     static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1139     static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1140     static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1141     int pnr;
1142
1143     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1144     {
1145       byte key_action = 0;
1146
1147       if (setup.input[pnr].use_joystick)
1148         continue;
1149
1150       ski = setup.input[pnr].key;
1151
1152       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1153         if (key == *key_info[i].key_custom)
1154           key_action |= key_info[i].action;
1155
1156       /* use combined snap+direction keys for the first player only */
1157       if (pnr == 0)
1158       {
1159         ssi = setup.shortcut;
1160
1161         for (i = 0; i < NUM_DIRECTIONS; i++)
1162           if (key == *key_info[i].key_snap)
1163             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1164       }
1165
1166       /* clear delayed snap and drop actions in single step mode (see below) */
1167       if (tape.single_step)
1168       {
1169         if (clear_snap_button[pnr])
1170         {
1171           stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1172           clear_snap_button[pnr] = FALSE;
1173         }
1174
1175         if (clear_drop_button[pnr])
1176         {
1177           stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1178           clear_drop_button[pnr] = FALSE;
1179         }
1180       }
1181
1182       if (key_status == KEY_PRESSED)
1183         stored_player[pnr].action |= key_action;
1184       else
1185         stored_player[pnr].action &= ~key_action;
1186
1187       if (tape.single_step && tape.recording && tape.pausing)
1188       {
1189         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1190         {
1191           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1192
1193           /* if snap key already pressed, don't snap when releasing (below) */
1194           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1195             element_snapped[pnr] = TRUE;
1196
1197           /* if drop key already pressed, don't drop when releasing (below) */
1198           if (stored_player[pnr].action & KEY_BUTTON_DROP)
1199             element_dropped[pnr] = TRUE;
1200         }
1201         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1202         {
1203           if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1204               level.game_engine_type == GAME_ENGINE_TYPE_SP)
1205           {
1206
1207             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1208                 getRedDiskReleaseFlag_SP() == 0)
1209               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1210
1211             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1212           }
1213         }
1214         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1215         {
1216           if (key_action & KEY_BUTTON_SNAP)
1217           {
1218             /* if snap key was released without moving (see above), snap now */
1219             if (!element_snapped[pnr])
1220             {
1221               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1222
1223               stored_player[pnr].action |= KEY_BUTTON_SNAP;
1224
1225               /* clear delayed snap button on next event */
1226               clear_snap_button[pnr] = TRUE;
1227             }
1228
1229             element_snapped[pnr] = FALSE;
1230           }
1231
1232           if (key_action & KEY_BUTTON_DROP &&
1233               level.game_engine_type == GAME_ENGINE_TYPE_RND)
1234           {
1235             /* if drop key was released without moving (see above), drop now */
1236             if (!element_dropped[pnr])
1237             {
1238               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1239
1240               if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1241                   getRedDiskReleaseFlag_SP() != 0)
1242                 stored_player[pnr].action |= KEY_BUTTON_DROP;
1243
1244               /* clear delayed drop button on next event */
1245               clear_drop_button[pnr] = TRUE;
1246             }
1247
1248             element_dropped[pnr] = FALSE;
1249           }
1250         }
1251       }
1252       else if (tape.recording && tape.pausing)
1253       {
1254         /* prevent key release events from un-pausing a paused game */
1255         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1256           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1257       }
1258     }
1259   }
1260   else
1261   {
1262     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1263       if (key == key_info[i].key_default)
1264         joy |= key_info[i].action;
1265   }
1266
1267   if (joy)
1268   {
1269     if (key_status == KEY_PRESSED)
1270       key_joystick_mapping |= joy;
1271     else
1272       key_joystick_mapping &= ~joy;
1273
1274     HandleJoystick();
1275   }
1276
1277   if (game_status != GAME_MODE_PLAYING)
1278     key_joystick_mapping = 0;
1279
1280   if (key_status == KEY_RELEASED)
1281     return;
1282
1283   if ((key == KSYM_F11 ||
1284        ((key == KSYM_Return ||
1285          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1286       video.fullscreen_available)
1287   {
1288     setup.fullscreen = !setup.fullscreen;
1289
1290     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1291
1292     if (game_status == GAME_MODE_SETUP)
1293       RedrawSetupScreenAfterFullscreenToggle();
1294
1295     return;
1296   }
1297
1298   if ((key == KSYM_minus ||
1299        key == KSYM_plus ||
1300        key == KSYM_0) &&
1301       ((GetKeyModState() & KMOD_Control) ||
1302        (GetKeyModState() & KMOD_Alt)) &&
1303       video.window_scaling_available &&
1304       !video.fullscreen_enabled)
1305   {
1306     if (key == KSYM_0)
1307       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1308     else
1309       setup.window_scaling_percent +=
1310         (key == KSYM_minus ? -1 : +1) * STEP_WINDOW_SCALING_PERCENT;
1311
1312     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1313       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1314     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1315       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1316
1317     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1318
1319     if (game_status == GAME_MODE_SETUP)
1320       RedrawSetupScreenAfterFullscreenToggle();
1321
1322     return;
1323   }
1324
1325   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1326       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1327   {
1328     GameEnd();
1329
1330     return;
1331   }
1332
1333   if (game_status == GAME_MODE_MAIN &&
1334       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1335   {
1336     StartGameActions(options.network, setup.autorecord, level.random_seed);
1337
1338     return;
1339   }
1340
1341   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1342   {
1343     if (key == setup.shortcut.save_game)
1344       TapeQuickSave();
1345     else if (key == setup.shortcut.load_game)
1346       TapeQuickLoad();
1347     else if (key == setup.shortcut.toggle_pause)
1348       TapeTogglePause(TAPE_TOGGLE_MANUAL);
1349
1350     HandleTapeButtonKeys(key);
1351     HandleSoundButtonKeys(key);
1352   }
1353
1354   if (game_status == GAME_MODE_PLAYING && !network_playing)
1355   {
1356     int centered_player_nr_next = -999;
1357
1358     if (key == setup.shortcut.focus_player_all)
1359       centered_player_nr_next = -1;
1360     else
1361       for (i = 0; i < MAX_PLAYERS; i++)
1362         if (key == setup.shortcut.focus_player[i])
1363           centered_player_nr_next = i;
1364
1365     if (centered_player_nr_next != -999)
1366     {
1367       game.centered_player_nr_next = centered_player_nr_next;
1368       game.set_centered_player = TRUE;
1369
1370       if (tape.recording)
1371       {
1372         tape.centered_player_nr_next = game.centered_player_nr_next;
1373         tape.set_centered_player = TRUE;
1374       }
1375     }
1376   }
1377
1378   HandleKeysSpecial(key);
1379
1380   if (HandleGadgetsKeyInput(key))
1381   {
1382     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1383       key = KSYM_UNDEFINED;
1384   }
1385
1386   switch (game_status)
1387   {
1388     case GAME_MODE_PSEUDO_TYPENAME:
1389       HandleTypeName(0, key);
1390       break;
1391
1392     case GAME_MODE_TITLE:
1393     case GAME_MODE_MAIN:
1394     case GAME_MODE_LEVELS:
1395     case GAME_MODE_LEVELNR:
1396     case GAME_MODE_SETUP:
1397     case GAME_MODE_INFO:
1398     case GAME_MODE_SCORES:
1399       switch (key)
1400       {
1401         case KSYM_space:
1402         case KSYM_Return:
1403           if (game_status == GAME_MODE_TITLE)
1404             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1405           else if (game_status == GAME_MODE_MAIN)
1406             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1407           else if (game_status == GAME_MODE_LEVELS)
1408             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1409           else if (game_status == GAME_MODE_LEVELNR)
1410             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1411           else if (game_status == GAME_MODE_SETUP)
1412             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1413           else if (game_status == GAME_MODE_INFO)
1414             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1415           else if (game_status == GAME_MODE_SCORES)
1416             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1417           break;
1418
1419         case KSYM_Escape:
1420           if (game_status != GAME_MODE_MAIN)
1421             FadeSkipNextFadeIn();
1422
1423           if (game_status == GAME_MODE_TITLE)
1424             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1425           else if (game_status == GAME_MODE_LEVELS)
1426             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1427           else if (game_status == GAME_MODE_LEVELNR)
1428             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1429           else if (game_status == GAME_MODE_SETUP)
1430             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1431           else if (game_status == GAME_MODE_INFO)
1432             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1433           else if (game_status == GAME_MODE_SCORES)
1434             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1435           break;
1436
1437         case KSYM_Page_Up:
1438           if (game_status == GAME_MODE_LEVELS)
1439             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1440           else if (game_status == GAME_MODE_LEVELNR)
1441             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1442           else if (game_status == GAME_MODE_SETUP)
1443             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1444           else if (game_status == GAME_MODE_INFO)
1445             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1446           else if (game_status == GAME_MODE_SCORES)
1447             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1448           break;
1449
1450         case KSYM_Page_Down:
1451           if (game_status == GAME_MODE_LEVELS)
1452             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1453           else if (game_status == GAME_MODE_LEVELNR)
1454             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1455           else if (game_status == GAME_MODE_SETUP)
1456             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1457           else if (game_status == GAME_MODE_INFO)
1458             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1459           else if (game_status == GAME_MODE_SCORES)
1460             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1461           break;
1462
1463 #ifdef DEBUG
1464         case KSYM_0:
1465           GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
1466           break;
1467
1468         case KSYM_b:
1469           setup.sp_show_border_elements = !setup.sp_show_border_elements;
1470           printf("Supaplex border elements %s\n",
1471                  setup.sp_show_border_elements ? "enabled" : "disabled");
1472           break;
1473 #endif
1474
1475         default:
1476           break;
1477       }
1478       break;
1479
1480     case GAME_MODE_EDITOR:
1481       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1482         HandleLevelEditorKeyInput(key);
1483       break;
1484
1485     case GAME_MODE_PLAYING:
1486     {
1487       switch (key)
1488       {
1489         case KSYM_Escape:
1490           RequestQuitGame(setup.ask_on_escape);
1491           break;
1492
1493 #ifdef DEBUG
1494         case KSYM_0:
1495           if (key == KSYM_0)
1496           {
1497             if (GameFrameDelay == 500)
1498               GameFrameDelay = GAME_FRAME_DELAY;
1499             else
1500               GameFrameDelay = 500;
1501           }
1502           else
1503             GameFrameDelay = (key - KSYM_0) * 10;
1504           printf("Game speed == %d%% (%d ms delay between two frames)\n",
1505                  GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
1506           break;
1507
1508         case KSYM_d:
1509           if (options.debug)
1510           {
1511             options.debug = FALSE;
1512             printf("debug mode disabled\n");
1513           }
1514           else
1515           {
1516             options.debug = TRUE;
1517             printf("debug mode enabled\n");
1518           }
1519           break;
1520
1521 #if 0
1522         case KSYM_s:
1523           if (!global.fps_slowdown)
1524           {
1525             global.fps_slowdown = TRUE;
1526             global.fps_slowdown_factor = 2;
1527             printf("fps slowdown enabled -- display only every 2nd frame\n");
1528           }
1529           else if (global.fps_slowdown_factor == 2)
1530           {
1531             global.fps_slowdown_factor = 4;
1532             printf("fps slowdown enabled -- display only every 4th frame\n");
1533           }
1534           else
1535           {
1536             global.fps_slowdown = FALSE;
1537             global.fps_slowdown_factor = 1;
1538             printf("fps slowdown disabled\n");
1539           }
1540           break;
1541 #endif
1542
1543         case KSYM_v:
1544           printf("::: currently using game engine version %d\n",
1545                  game.engine_version);
1546           break;
1547 #endif
1548
1549         default:
1550           break;
1551       }
1552       break;
1553     }
1554
1555     default:
1556       if (key == KSYM_Escape)
1557       {
1558         game_status = GAME_MODE_MAIN;
1559         DrawMainMenu();
1560
1561         return;
1562       }
1563   }
1564 }
1565
1566 void HandleNoEvent()
1567 {
1568   if (button_status && game_status != GAME_MODE_PLAYING)
1569   {
1570     HandleButton(0, 0, -button_status, button_status);
1571   }
1572   else
1573   {
1574     HandleJoystick();
1575   }
1576
1577 #if defined(NETWORK_AVALIABLE)
1578   if (options.network)
1579     HandleNetworking();
1580 #endif
1581
1582   switch (game_status)
1583   {
1584     case GAME_MODE_MAIN:
1585       DrawPreviewLevelAnimation();
1586       DoAnimation();
1587       break;
1588
1589     case GAME_MODE_LEVELS:
1590     case GAME_MODE_LEVELNR:
1591     case GAME_MODE_SETUP:
1592     case GAME_MODE_INFO:
1593     case GAME_MODE_SCORES:
1594       DoAnimation();
1595       break;
1596
1597     case GAME_MODE_EDITOR:
1598       HandleLevelEditorIdle();
1599       break;
1600
1601     default:
1602       break;
1603   }
1604 }
1605
1606 static int HandleJoystickForAllPlayers()
1607 {
1608   int i;
1609   int result = 0;
1610
1611   for (i = 0; i < MAX_PLAYERS; i++)
1612   {
1613     byte joy_action = 0;
1614
1615     /*
1616     if (!setup.input[i].use_joystick)
1617       continue;
1618       */
1619
1620     joy_action = Joystick(i);
1621     result |= joy_action;
1622
1623     if (!setup.input[i].use_joystick)
1624       continue;
1625
1626     stored_player[i].action = joy_action;
1627   }
1628
1629   return result;
1630 }
1631
1632 void HandleJoystick()
1633 {
1634   int joystick  = HandleJoystickForAllPlayers();
1635   int keyboard  = key_joystick_mapping;
1636   int joy       = (joystick | keyboard);
1637   int left      = joy & JOY_LEFT;
1638   int right     = joy & JOY_RIGHT;
1639   int up        = joy & JOY_UP;
1640   int down      = joy & JOY_DOWN;
1641   int button    = joy & JOY_BUTTON;
1642   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1643   int dx        = (left ? -1    : right ? 1     : 0);
1644   int dy        = (up   ? -1    : down  ? 1     : 0);
1645
1646   switch (game_status)
1647   {
1648     case GAME_MODE_TITLE:
1649     case GAME_MODE_MAIN:
1650     case GAME_MODE_LEVELS:
1651     case GAME_MODE_LEVELNR:
1652     case GAME_MODE_SETUP:
1653     case GAME_MODE_INFO:
1654     {
1655       static unsigned int joystickmove_delay = 0;
1656
1657       if (joystick && !button &&
1658           !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1659         newbutton = dx = dy = 0;
1660
1661       if (game_status == GAME_MODE_TITLE)
1662         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1663       else if (game_status == GAME_MODE_MAIN)
1664         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1665       else if (game_status == GAME_MODE_LEVELS)
1666         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1667       else if (game_status == GAME_MODE_LEVELNR)
1668         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1669       else if (game_status == GAME_MODE_SETUP)
1670         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1671       else if (game_status == GAME_MODE_INFO)
1672         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1673       break;
1674     }
1675
1676     case GAME_MODE_SCORES:
1677       HandleHallOfFame(0, 0, dx, dy, !newbutton);
1678       break;
1679
1680     case GAME_MODE_PLAYING:
1681       if (tape.playing || keyboard)
1682         newbutton = ((joy & JOY_BUTTON) != 0);
1683
1684       if (newbutton && AllPlayersGone)
1685       {
1686         GameEnd();
1687
1688         return;
1689       }
1690
1691       break;
1692
1693     default:
1694       break;
1695   }
1696 }