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