fixed setting effective window scaling value when window gets resized
[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 (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
464       event->event == SDL_WINDOWEVENT_RESIZED ||
465       event->event == SDL_WINDOWEVENT_EXPOSED)
466     SDLRedrawWindow();
467
468   if (event->event == SDL_WINDOWEVENT_RESIZED && !video.fullscreen_enabled)
469   {
470     int new_window_width  = event->data1;
471     int new_window_height = event->data2;
472
473     // if window size has changed after resizing, calculate new scaling factor
474     if (new_window_width  != video.window_width ||
475         new_window_height != video.window_height)
476     {
477       int new_xpercent = (100 * new_window_width  / video.width);
478       int new_ypercent = (100 * new_window_height / video.height);
479
480       // (extreme window scaling allowed, but cannot be saved permanently)
481       video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
482       setup.window_scaling_percent =
483         MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
484             MAX_WINDOW_SCALING_PERCENT);
485
486       video.window_width  = new_window_width;
487       video.window_height = new_window_height;
488
489       if (game_status == GAME_MODE_SETUP)
490         RedrawSetupScreenAfterFullscreenToggle();
491
492       SetWindowTitle();
493     }
494   }
495 }
496
497 #define NUM_TOUCH_FINGERS               3
498
499 static struct
500 {
501   boolean touched;
502   SDL_FingerID finger_id;
503   int counter;
504   Key key;
505 } touch_info[NUM_TOUCH_FINGERS];
506
507 void HandleFingerEvent(FingerEvent *event)
508 {
509   static Key motion_key_x = KSYM_UNDEFINED;
510   static Key motion_key_y = KSYM_UNDEFINED;
511   static Key button_key = KSYM_UNDEFINED;
512   static float motion_x1, motion_y1;
513   static float button_x1, button_y1;
514   static SDL_FingerID motion_id = -1;
515   static SDL_FingerID button_id = -1;
516   int move_trigger_distance_percent = 2;   // percent of touchpad width/height
517   int drop_trigger_distance_percent = 5;   // percent of touchpad width/height
518   float move_trigger_distance = (float)move_trigger_distance_percent / 100;
519   float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
520   float event_x = event->x;
521   float event_y = event->y;
522
523 #if DEBUG_EVENTS_FINGER
524   Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
525         event->type == EVENT_FINGERPRESS ? "pressed" :
526         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
527         event->touchId,
528         event->fingerId,
529         event->x, event->y,
530         event->dx, event->dy,
531         event->pressure);
532 #endif
533
534   if (game_status != GAME_MODE_PLAYING)
535     return;
536
537   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
538   {
539     int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
540                       KEY_PRESSED);
541     Key key = (event->x < 1.0 / 3.0 ?
542                (event->y < 1.0 / 2.0 ? setup.input[0].key.snap :
543                 setup.input[0].key.drop) :
544                event->x > 2.0 / 3.0 ?
545                (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
546                 event->y > 2.0 / 3.0 ? setup.input[0].key.down :
547                 event->x < 5.0 / 6.0 ? setup.input[0].key.left :
548                 setup.input[0].key.right) :
549                KSYM_UNDEFINED);
550     char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
551                              "KEY_PRESSED");
552     int i;
553
554     Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
555           getKeyNameFromKey(key), key_status_name, event->fingerId);
556
557     // check if we already know this touch event's finger id
558     for (i = 0; i < NUM_TOUCH_FINGERS; i++)
559     {
560       if (touch_info[i].touched &&
561           touch_info[i].finger_id == event->fingerId)
562       {
563         // Error(ERR_DEBUG, "MARK 1: %d", i);
564
565         break;
566       }
567     }
568
569     if (i >= NUM_TOUCH_FINGERS)
570     {
571       if (key_status == KEY_PRESSED)
572       {
573         int oldest_pos = 0, oldest_counter = touch_info[0].counter;
574
575         // unknown finger id -- get new, empty slot, if available
576         for (i = 0; i < NUM_TOUCH_FINGERS; i++)
577         {
578           if (touch_info[i].counter < oldest_counter)
579           {
580             oldest_pos = i;
581             oldest_counter = touch_info[i].counter;
582
583             // Error(ERR_DEBUG, "MARK 2: %d", i);
584           }
585
586           if (!touch_info[i].touched)
587           {
588             // Error(ERR_DEBUG, "MARK 3: %d", i);
589
590             break;
591           }
592         }
593
594         if (i >= NUM_TOUCH_FINGERS)
595         {
596           // all slots allocated -- use oldest slot
597           i = oldest_pos;
598
599           // Error(ERR_DEBUG, "MARK 4: %d", i);
600         }
601       }
602       else
603       {
604         // release of previously unknown key (should not happen)
605
606         if (key != KSYM_UNDEFINED)
607         {
608           HandleKey(key, KEY_RELEASED);
609
610           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
611                 getKeyNameFromKey(key), "KEY_RELEASED", i);
612         }
613       }
614     }
615
616     if (i < NUM_TOUCH_FINGERS)
617     {
618       if (key_status == KEY_PRESSED)
619       {
620         if (touch_info[i].key != key)
621         {
622           if (touch_info[i].key != KSYM_UNDEFINED)
623           {
624             HandleKey(touch_info[i].key, KEY_RELEASED);
625
626             Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
627                   getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
628           }
629
630           if (key != KSYM_UNDEFINED)
631           {
632             HandleKey(key, KEY_PRESSED);
633
634             Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
635                   getKeyNameFromKey(key), "KEY_PRESSED", i);
636           }
637         }
638
639         touch_info[i].touched = TRUE;
640         touch_info[i].finger_id = event->fingerId;
641         touch_info[i].counter = Counter();
642         touch_info[i].key = key;
643       }
644       else
645       {
646         if (touch_info[i].key != KSYM_UNDEFINED)
647         {
648           HandleKey(touch_info[i].key, KEY_RELEASED);
649
650           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
651                 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
652         }
653
654         touch_info[i].touched = FALSE;
655         touch_info[i].finger_id = 0;
656         touch_info[i].counter = 0;
657         touch_info[i].key = 0;
658       }
659     }
660
661     return;
662   }
663
664   // use touch direction control
665
666   if (event->type == EVENT_FINGERPRESS)
667   {
668     if (event_x > 1.0 / 3.0)
669     {
670       // motion area
671
672       motion_id = event->fingerId;
673
674       motion_x1 = event_x;
675       motion_y1 = event_y;
676
677       motion_key_x = KSYM_UNDEFINED;
678       motion_key_y = KSYM_UNDEFINED;
679
680       Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
681     }
682     else
683     {
684       // button area
685
686       button_id = event->fingerId;
687
688       button_x1 = event_x;
689       button_y1 = event_y;
690
691       button_key = setup.input[0].key.snap;
692
693       HandleKey(button_key, KEY_PRESSED);
694
695       Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
696     }
697   }
698   else if (event->type == EVENT_FINGERRELEASE)
699   {
700     if (event->fingerId == motion_id)
701     {
702       motion_id = -1;
703
704       if (motion_key_x != KSYM_UNDEFINED)
705         HandleKey(motion_key_x, KEY_RELEASED);
706       if (motion_key_y != KSYM_UNDEFINED)
707         HandleKey(motion_key_y, KEY_RELEASED);
708
709       motion_key_x = KSYM_UNDEFINED;
710       motion_key_y = KSYM_UNDEFINED;
711
712       Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
713     }
714     else if (event->fingerId == button_id)
715     {
716       button_id = -1;
717
718       if (button_key != KSYM_UNDEFINED)
719         HandleKey(button_key, KEY_RELEASED);
720
721       button_key = KSYM_UNDEFINED;
722
723       Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
724     }
725   }
726   else if (event->type == EVENT_FINGERMOTION)
727   {
728     if (event->fingerId == motion_id)
729     {
730       float distance_x = ABS(event_x - motion_x1);
731       float distance_y = ABS(event_y - motion_y1);
732       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
733                               event_x > motion_x1 ? setup.input[0].key.right :
734                               KSYM_UNDEFINED);
735       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
736                               event_y > motion_y1 ? setup.input[0].key.down :
737                               KSYM_UNDEFINED);
738
739       if (distance_x < move_trigger_distance / 2 ||
740           distance_x < distance_y)
741         new_motion_key_x = KSYM_UNDEFINED;
742
743       if (distance_y < move_trigger_distance / 2 ||
744           distance_y < distance_x)
745         new_motion_key_y = KSYM_UNDEFINED;
746
747       if (distance_x > move_trigger_distance ||
748           distance_y > move_trigger_distance)
749       {
750         if (new_motion_key_x != motion_key_x)
751         {
752           if (motion_key_x != KSYM_UNDEFINED)
753             HandleKey(motion_key_x, KEY_RELEASED);
754           if (new_motion_key_x != KSYM_UNDEFINED)
755             HandleKey(new_motion_key_x, KEY_PRESSED);
756         }
757
758         if (new_motion_key_y != motion_key_y)
759         {
760           if (motion_key_y != KSYM_UNDEFINED)
761             HandleKey(motion_key_y, KEY_RELEASED);
762           if (new_motion_key_y != KSYM_UNDEFINED)
763             HandleKey(new_motion_key_y, KEY_PRESSED);
764         }
765
766         motion_x1 = event_x;
767         motion_y1 = event_y;
768
769         motion_key_x = new_motion_key_x;
770         motion_key_y = new_motion_key_y;
771
772         Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
773       }
774     }
775     else if (event->fingerId == button_id)
776     {
777       float distance_x = ABS(event_x - button_x1);
778       float distance_y = ABS(event_y - button_y1);
779
780       if (distance_x < drop_trigger_distance / 2 &&
781           distance_y > drop_trigger_distance)
782       {
783         if (button_key == setup.input[0].key.snap)
784           HandleKey(button_key, KEY_RELEASED);
785
786         button_x1 = event_x;
787         button_y1 = event_y;
788
789         button_key = setup.input[0].key.drop;
790
791         HandleKey(button_key, KEY_PRESSED);
792
793         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
794       }
795     }
796   }
797 }
798
799 static boolean checkTextInputKeyModState()
800 {
801   // when playing, only handle raw key events and ignore text input
802   if (game_status == GAME_MODE_PLAYING)
803     return FALSE;
804
805   return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
806 }
807
808 void HandleTextEvent(TextEvent *event)
809 {
810   char *text = event->text;
811   Key key = getKeyFromKeyName(text);
812
813 #if DEBUG_EVENTS_TEXT
814   Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
815         text,
816         strlen(text),
817         text[0], (int)(text[0]),
818         key,
819         getKeyNameFromKey(key),
820         GetKeyModState());
821 #endif
822
823 #if defined(PLATFORM_ANDROID)
824   if (game_status == GAME_MODE_PSEUDO_TYPENAME)
825   {
826     HandleTypeName(0, key);
827
828     return;
829   }
830 #endif
831
832   // only handle key input with text modifier keys pressed
833   if (checkTextInputKeyModState())
834   {
835     HandleKey(key, KEY_PRESSED);
836     HandleKey(key, KEY_RELEASED);
837   }
838 }
839
840 void HandlePauseResumeEvent(PauseResumeEvent *event)
841 {
842   if (event->type == SDL_APP_WILLENTERBACKGROUND)
843   {
844     Mix_PauseMusic();
845   }
846   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
847   {
848     Mix_ResumeMusic();
849   }
850 }
851
852 #endif
853
854 void HandleKeyEvent(KeyEvent *event)
855 {
856   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
857   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
858   Key key = GetEventKey(event, with_modifiers);
859   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
860
861 #if DEBUG_EVENTS_KEY
862   Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
863         event->type == EVENT_KEYPRESS ? "pressed" : "released",
864         event->keysym.scancode,
865         event->keysym.sym,
866         keymod,
867         GetKeyModState(),
868         key,
869         getKeyNameFromKey(key));
870 #endif
871
872 #if defined(PLATFORM_ANDROID)
873   // always map the "back" button to the "escape" key on Android devices
874   if (key == KSYM_Back)
875     key = KSYM_Escape;
876 #endif
877
878   HandleKeyModState(keymod, key_status);
879
880 #if defined(TARGET_SDL2)
881   // only handle raw key input without text modifier keys pressed
882   if (!checkTextInputKeyModState())
883     HandleKey(key, key_status);
884 #else
885   HandleKey(key, key_status);
886 #endif
887 }
888
889 void HandleFocusEvent(FocusChangeEvent *event)
890 {
891   static int old_joystick_status = -1;
892
893   if (event->type == EVENT_FOCUSOUT)
894   {
895     KeyboardAutoRepeatOn();
896     old_joystick_status = joystick.status;
897     joystick.status = JOYSTICK_NOT_AVAILABLE;
898
899     ClearPlayerAction();
900   }
901   else if (event->type == EVENT_FOCUSIN)
902   {
903     /* When there are two Rocks'n'Diamonds windows which overlap and
904        the player moves the pointer from one game window to the other,
905        a 'FocusOut' event is generated for the window the pointer is
906        leaving and a 'FocusIn' event is generated for the window the
907        pointer is entering. In some cases, it can happen that the
908        'FocusIn' event is handled by the one game process before the
909        'FocusOut' event by the other game process. In this case the
910        X11 environment would end up with activated keyboard auto repeat,
911        because unfortunately this is a global setting and not (which
912        would be far better) set for each X11 window individually.
913        The effect would be keyboard auto repeat while playing the game
914        (game_status == GAME_MODE_PLAYING), which is not desired.
915        To avoid this special case, we just wait 1/10 second before
916        processing the 'FocusIn' event.
917     */
918
919     if (game_status == GAME_MODE_PLAYING)
920     {
921       Delay(100);
922       KeyboardAutoRepeatOffUnlessAutoplay();
923     }
924
925     if (old_joystick_status != -1)
926       joystick.status = old_joystick_status;
927   }
928 }
929
930 void HandleClientMessageEvent(ClientMessageEvent *event)
931 {
932   if (CheckCloseWindowEvent(event))
933     CloseAllAndExit(0);
934 }
935
936 void HandleWindowManagerEvent(Event *event)
937 {
938 #if defined(TARGET_SDL)
939   SDLHandleWindowManagerEvent(event);
940 #endif
941 }
942
943 void HandleButton(int mx, int my, int button, int button_nr)
944 {
945   static int old_mx = 0, old_my = 0;
946   boolean button_hold = FALSE;
947
948   if (button < 0)
949   {
950     mx = old_mx;
951     my = old_my;
952     button = -button;
953     button_hold = TRUE;
954   }
955   else
956   {
957     old_mx = mx;
958     old_my = my;
959   }
960
961 #if defined(PLATFORM_ANDROID)
962   // !!! for now, do not handle gadgets when playing -- maybe fix this !!!
963   if (game_status != GAME_MODE_PLAYING &&
964       HandleGadgets(mx, my, button))
965   {
966     /* do not handle this button event anymore */
967     mx = my = -32;      /* force mouse event to be outside screen tiles */
968   }
969 #else
970   if (HandleGadgets(mx, my, button))
971   {
972     /* do not handle this button event anymore */
973     mx = my = -32;      /* force mouse event to be outside screen tiles */
974   }
975 #endif
976
977   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
978     return;
979
980   /* do not use scroll wheel button events for anything other than gadgets */
981   if (IS_WHEEL_BUTTON(button_nr))
982     return;
983
984   switch (game_status)
985   {
986     case GAME_MODE_TITLE:
987       HandleTitleScreen(mx, my, 0, 0, button);
988       break;
989
990     case GAME_MODE_MAIN:
991       HandleMainMenu(mx, my, 0, 0, button);
992       break;
993
994     case GAME_MODE_PSEUDO_TYPENAME:
995       HandleTypeName(0, KSYM_Return);
996       break;
997
998     case GAME_MODE_LEVELS:
999       HandleChooseLevelSet(mx, my, 0, 0, button);
1000       break;
1001
1002     case GAME_MODE_LEVELNR:
1003       HandleChooseLevelNr(mx, my, 0, 0, button);
1004       break;
1005
1006     case GAME_MODE_SCORES:
1007       HandleHallOfFame(0, 0, 0, 0, button);
1008       break;
1009
1010     case GAME_MODE_EDITOR:
1011       HandleLevelEditorIdle();
1012       break;
1013
1014     case GAME_MODE_INFO:
1015       HandleInfoScreen(mx, my, 0, 0, button);
1016       break;
1017
1018     case GAME_MODE_SETUP:
1019       HandleSetupScreen(mx, my, 0, 0, button);
1020       break;
1021
1022     case GAME_MODE_PLAYING:
1023 #ifdef DEBUG
1024       if (button == MB_PRESSED && !motion_status && IN_GFX_FIELD_PLAY(mx, my))
1025         DumpTile(LEVELX((mx - SX) / TILESIZE_VAR),
1026                  LEVELY((my - SY) / TILESIZE_VAR));
1027         // DumpTile(LEVELX((mx - SX) / TILEX), LEVELY((my - SY) / TILEY));
1028 #endif
1029       break;
1030
1031     default:
1032       break;
1033   }
1034 }
1035
1036 static boolean is_string_suffix(char *string, char *suffix)
1037 {
1038   int string_len = strlen(string);
1039   int suffix_len = strlen(suffix);
1040
1041   if (suffix_len > string_len)
1042     return FALSE;
1043
1044   return (strEqual(&string[string_len - suffix_len], suffix));
1045 }
1046
1047 #define MAX_CHEAT_INPUT_LEN     32
1048
1049 static void HandleKeysSpecial(Key key)
1050 {
1051   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1052   char letter = getCharFromKey(key);
1053   int cheat_input_len = strlen(cheat_input);
1054   int i;
1055
1056   if (letter == 0)
1057     return;
1058
1059   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1060   {
1061     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1062       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1063
1064     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1065   }
1066
1067   cheat_input[cheat_input_len++] = letter;
1068   cheat_input[cheat_input_len] = '\0';
1069
1070 #if DEBUG_EVENTS_KEY
1071   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1072 #endif
1073
1074   if (game_status == GAME_MODE_MAIN)
1075   {
1076     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1077         is_string_suffix(cheat_input, ":ist"))
1078     {
1079       InsertSolutionTape();
1080     }
1081     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1082              is_string_suffix(cheat_input, ":rg"))
1083     {
1084       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1085       DrawMainMenu();
1086     }
1087     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1088              is_string_suffix(cheat_input, ":rs"))
1089     {
1090       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1091       DrawMainMenu();
1092     }
1093     else if (is_string_suffix(cheat_input, ":reload-music") ||
1094              is_string_suffix(cheat_input, ":rm"))
1095     {
1096       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1097       DrawMainMenu();
1098     }
1099     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1100              is_string_suffix(cheat_input, ":ra"))
1101     {
1102       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1103                           1 << ARTWORK_TYPE_SOUNDS |
1104                           1 << ARTWORK_TYPE_MUSIC);
1105       DrawMainMenu();
1106     }
1107     else if (is_string_suffix(cheat_input, ":dump-level") ||
1108              is_string_suffix(cheat_input, ":dl"))
1109     {
1110       DumpLevel(&level);
1111     }
1112     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1113              is_string_suffix(cheat_input, ":dt"))
1114     {
1115       DumpTape(&tape);
1116     }
1117     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1118              is_string_suffix(cheat_input, ":ft"))
1119     {
1120       /* fix single-player tapes that contain player input for more than one
1121          player (due to a bug in 3.3.1.2 and earlier versions), which results
1122          in playing levels with more than one player in multi-player mode,
1123          even though the tape was originally recorded in single-player mode */
1124
1125       /* remove player input actions for all players but the first one */
1126       for (i = 1; i < MAX_PLAYERS; i++)
1127         tape.player_participates[i] = FALSE;
1128
1129       tape.changed = TRUE;
1130     }
1131     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1132              is_string_suffix(cheat_input, ":snl"))
1133     {
1134       SaveNativeLevel(&level);
1135     }
1136   }
1137   else if (game_status == GAME_MODE_PLAYING)
1138   {
1139 #ifdef DEBUG
1140     if (is_string_suffix(cheat_input, ".q"))
1141       DEBUG_SetMaximumDynamite();
1142 #endif
1143   }
1144   else if (game_status == GAME_MODE_EDITOR)
1145   {
1146     if (is_string_suffix(cheat_input, ":dump-brush") ||
1147         is_string_suffix(cheat_input, ":DB"))
1148     {
1149       DumpBrush();
1150     }
1151     else if (is_string_suffix(cheat_input, ":DDB"))
1152     {
1153       DumpBrush_Small();
1154     }
1155   }
1156 }
1157
1158 void HandleKey(Key key, int key_status)
1159 {
1160   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1161   static boolean ignore_repeated_key = FALSE;
1162   static struct SetupKeyboardInfo ski;
1163   static struct SetupShortcutInfo ssi;
1164   static struct
1165   {
1166     Key *key_custom;
1167     Key *key_snap;
1168     Key key_default;
1169     byte action;
1170   } key_info[] =
1171   {
1172     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1173     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1174     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1175     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1176     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1177     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1178   };
1179   int joy = 0;
1180   int i;
1181
1182   if (game_status == GAME_MODE_PLAYING)
1183   {
1184     /* only needed for single-step tape recording mode */
1185     static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1186     static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1187     static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1188     static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1189     int pnr;
1190
1191     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1192     {
1193       byte key_action = 0;
1194
1195       if (setup.input[pnr].use_joystick)
1196         continue;
1197
1198       ski = setup.input[pnr].key;
1199
1200       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1201         if (key == *key_info[i].key_custom)
1202           key_action |= key_info[i].action;
1203
1204       /* use combined snap+direction keys for the first player only */
1205       if (pnr == 0)
1206       {
1207         ssi = setup.shortcut;
1208
1209         for (i = 0; i < NUM_DIRECTIONS; i++)
1210           if (key == *key_info[i].key_snap)
1211             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1212       }
1213
1214       /* clear delayed snap and drop actions in single step mode (see below) */
1215       if (tape.single_step)
1216       {
1217         if (clear_snap_button[pnr])
1218         {
1219           stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1220           clear_snap_button[pnr] = FALSE;
1221         }
1222
1223         if (clear_drop_button[pnr])
1224         {
1225           stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1226           clear_drop_button[pnr] = FALSE;
1227         }
1228       }
1229
1230       if (key_status == KEY_PRESSED)
1231         stored_player[pnr].action |= key_action;
1232       else
1233         stored_player[pnr].action &= ~key_action;
1234
1235       if (tape.single_step && tape.recording && tape.pausing)
1236       {
1237         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1238         {
1239           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1240
1241           /* if snap key already pressed, don't snap when releasing (below) */
1242           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1243             element_snapped[pnr] = TRUE;
1244
1245           /* if drop key already pressed, don't drop when releasing (below) */
1246           if (stored_player[pnr].action & KEY_BUTTON_DROP)
1247             element_dropped[pnr] = TRUE;
1248         }
1249         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1250         {
1251           if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1252               level.game_engine_type == GAME_ENGINE_TYPE_SP)
1253           {
1254
1255             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1256                 getRedDiskReleaseFlag_SP() == 0)
1257               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1258
1259             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1260           }
1261         }
1262         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1263         {
1264           if (key_action & KEY_BUTTON_SNAP)
1265           {
1266             /* if snap key was released without moving (see above), snap now */
1267             if (!element_snapped[pnr])
1268             {
1269               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1270
1271               stored_player[pnr].action |= KEY_BUTTON_SNAP;
1272
1273               /* clear delayed snap button on next event */
1274               clear_snap_button[pnr] = TRUE;
1275             }
1276
1277             element_snapped[pnr] = FALSE;
1278           }
1279
1280           if (key_action & KEY_BUTTON_DROP &&
1281               level.game_engine_type == GAME_ENGINE_TYPE_RND)
1282           {
1283             /* if drop key was released without moving (see above), drop now */
1284             if (!element_dropped[pnr])
1285             {
1286               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1287
1288               if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1289                   getRedDiskReleaseFlag_SP() != 0)
1290                 stored_player[pnr].action |= KEY_BUTTON_DROP;
1291
1292               /* clear delayed drop button on next event */
1293               clear_drop_button[pnr] = TRUE;
1294             }
1295
1296             element_dropped[pnr] = FALSE;
1297           }
1298         }
1299       }
1300       else if (tape.recording && tape.pausing)
1301       {
1302         /* prevent key release events from un-pausing a paused game */
1303         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1304           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1305       }
1306     }
1307   }
1308   else
1309   {
1310     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1311       if (key == key_info[i].key_default)
1312         joy |= key_info[i].action;
1313   }
1314
1315   if (joy)
1316   {
1317     if (key_status == KEY_PRESSED)
1318       key_joystick_mapping |= joy;
1319     else
1320       key_joystick_mapping &= ~joy;
1321
1322     HandleJoystick();
1323   }
1324
1325   if (game_status != GAME_MODE_PLAYING)
1326     key_joystick_mapping = 0;
1327
1328   if (key_status == KEY_RELEASED)
1329   {
1330     // reset flag to ignore repeated "key pressed" events after key release
1331     ignore_repeated_key = FALSE;
1332
1333     return;
1334   }
1335
1336   if ((key == KSYM_F11 ||
1337        ((key == KSYM_Return ||
1338          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1339       video.fullscreen_available &&
1340       !ignore_repeated_key)
1341   {
1342     setup.fullscreen = !setup.fullscreen;
1343
1344     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1345
1346     if (game_status == GAME_MODE_SETUP)
1347       RedrawSetupScreenAfterFullscreenToggle();
1348
1349     // set flag to ignore repeated "key pressed" events
1350     ignore_repeated_key = TRUE;
1351
1352     return;
1353   }
1354
1355   if ((key == KSYM_minus ||
1356        key == KSYM_plus ||
1357        key == KSYM_0) &&
1358       ((GetKeyModState() & KMOD_Control) ||
1359        (GetKeyModState() & KMOD_Alt)) &&
1360       video.window_scaling_available &&
1361       !video.fullscreen_enabled)
1362   {
1363     if (key == KSYM_0)
1364       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1365     else
1366       setup.window_scaling_percent +=
1367         (key == KSYM_minus ? -1 : +1) * STEP_WINDOW_SCALING_PERCENT;
1368
1369     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1370       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1371     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1372       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1373
1374     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1375
1376     if (game_status == GAME_MODE_SETUP)
1377       RedrawSetupScreenAfterFullscreenToggle();
1378
1379     return;
1380   }
1381
1382   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1383       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1384   {
1385     GameEnd();
1386
1387     return;
1388   }
1389
1390   if (game_status == GAME_MODE_MAIN &&
1391       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1392   {
1393     StartGameActions(options.network, setup.autorecord, level.random_seed);
1394
1395     return;
1396   }
1397
1398   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1399   {
1400     if (key == setup.shortcut.save_game)
1401       TapeQuickSave();
1402     else if (key == setup.shortcut.load_game)
1403       TapeQuickLoad();
1404     else if (key == setup.shortcut.toggle_pause)
1405       TapeTogglePause(TAPE_TOGGLE_MANUAL);
1406
1407     HandleTapeButtonKeys(key);
1408     HandleSoundButtonKeys(key);
1409   }
1410
1411   if (game_status == GAME_MODE_PLAYING && !network_playing)
1412   {
1413     int centered_player_nr_next = -999;
1414
1415     if (key == setup.shortcut.focus_player_all)
1416       centered_player_nr_next = -1;
1417     else
1418       for (i = 0; i < MAX_PLAYERS; i++)
1419         if (key == setup.shortcut.focus_player[i])
1420           centered_player_nr_next = i;
1421
1422     if (centered_player_nr_next != -999)
1423     {
1424       game.centered_player_nr_next = centered_player_nr_next;
1425       game.set_centered_player = TRUE;
1426
1427       if (tape.recording)
1428       {
1429         tape.centered_player_nr_next = game.centered_player_nr_next;
1430         tape.set_centered_player = TRUE;
1431       }
1432     }
1433   }
1434
1435   HandleKeysSpecial(key);
1436
1437   if (HandleGadgetsKeyInput(key))
1438   {
1439     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1440       key = KSYM_UNDEFINED;
1441   }
1442
1443   switch (game_status)
1444   {
1445     case GAME_MODE_PSEUDO_TYPENAME:
1446       HandleTypeName(0, key);
1447       break;
1448
1449     case GAME_MODE_TITLE:
1450     case GAME_MODE_MAIN:
1451     case GAME_MODE_LEVELS:
1452     case GAME_MODE_LEVELNR:
1453     case GAME_MODE_SETUP:
1454     case GAME_MODE_INFO:
1455     case GAME_MODE_SCORES:
1456       switch (key)
1457       {
1458         case KSYM_space:
1459         case KSYM_Return:
1460           if (game_status == GAME_MODE_TITLE)
1461             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1462           else if (game_status == GAME_MODE_MAIN)
1463             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1464           else if (game_status == GAME_MODE_LEVELS)
1465             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1466           else if (game_status == GAME_MODE_LEVELNR)
1467             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1468           else if (game_status == GAME_MODE_SETUP)
1469             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1470           else if (game_status == GAME_MODE_INFO)
1471             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1472           else if (game_status == GAME_MODE_SCORES)
1473             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1474           break;
1475
1476         case KSYM_Escape:
1477           if (game_status != GAME_MODE_MAIN)
1478             FadeSkipNextFadeIn();
1479
1480           if (game_status == GAME_MODE_TITLE)
1481             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1482           else if (game_status == GAME_MODE_LEVELS)
1483             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1484           else if (game_status == GAME_MODE_LEVELNR)
1485             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1486           else if (game_status == GAME_MODE_SETUP)
1487             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1488           else if (game_status == GAME_MODE_INFO)
1489             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1490           else if (game_status == GAME_MODE_SCORES)
1491             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1492           break;
1493
1494         case KSYM_Page_Up:
1495           if (game_status == GAME_MODE_LEVELS)
1496             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1497           else if (game_status == GAME_MODE_LEVELNR)
1498             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1499           else if (game_status == GAME_MODE_SETUP)
1500             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1501           else if (game_status == GAME_MODE_INFO)
1502             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1503           else if (game_status == GAME_MODE_SCORES)
1504             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1505           break;
1506
1507         case KSYM_Page_Down:
1508           if (game_status == GAME_MODE_LEVELS)
1509             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1510           else if (game_status == GAME_MODE_LEVELNR)
1511             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1512           else if (game_status == GAME_MODE_SETUP)
1513             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1514           else if (game_status == GAME_MODE_INFO)
1515             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1516           else if (game_status == GAME_MODE_SCORES)
1517             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1518           break;
1519
1520 #ifdef DEBUG
1521         case KSYM_0:
1522           GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
1523           break;
1524
1525         case KSYM_b:
1526           setup.sp_show_border_elements = !setup.sp_show_border_elements;
1527           printf("Supaplex border elements %s\n",
1528                  setup.sp_show_border_elements ? "enabled" : "disabled");
1529           break;
1530 #endif
1531
1532         default:
1533           break;
1534       }
1535       break;
1536
1537     case GAME_MODE_EDITOR:
1538       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1539         HandleLevelEditorKeyInput(key);
1540       break;
1541
1542     case GAME_MODE_PLAYING:
1543     {
1544       switch (key)
1545       {
1546         case KSYM_Escape:
1547           RequestQuitGame(setup.ask_on_escape);
1548           break;
1549
1550 #ifdef DEBUG
1551         case KSYM_0:
1552           if (key == KSYM_0)
1553           {
1554             if (GameFrameDelay == 500)
1555               GameFrameDelay = GAME_FRAME_DELAY;
1556             else
1557               GameFrameDelay = 500;
1558           }
1559           else
1560             GameFrameDelay = (key - KSYM_0) * 10;
1561           printf("Game speed == %d%% (%d ms delay between two frames)\n",
1562                  GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
1563           break;
1564
1565         case KSYM_d:
1566           if (options.debug)
1567           {
1568             options.debug = FALSE;
1569             printf("debug mode disabled\n");
1570           }
1571           else
1572           {
1573             options.debug = TRUE;
1574             printf("debug mode enabled\n");
1575           }
1576           break;
1577
1578         case KSYM_v:
1579           printf("::: currently using game engine version %d\n",
1580                  game.engine_version);
1581           break;
1582 #endif
1583
1584         default:
1585           break;
1586       }
1587       break;
1588     }
1589
1590     default:
1591       if (key == KSYM_Escape)
1592       {
1593         SetGameStatus(GAME_MODE_MAIN);
1594
1595         DrawMainMenu();
1596
1597         return;
1598       }
1599   }
1600 }
1601
1602 void HandleNoEvent()
1603 {
1604   // if (button_status && game_status != GAME_MODE_PLAYING)
1605   if (button_status && (game_status != GAME_MODE_PLAYING || tape.pausing))
1606   {
1607     HandleButton(0, 0, -button_status, button_status);
1608   }
1609   else
1610   {
1611     HandleJoystick();
1612   }
1613
1614 #if defined(NETWORK_AVALIABLE)
1615   if (options.network)
1616     HandleNetworking();
1617 #endif
1618
1619   switch (game_status)
1620   {
1621     case GAME_MODE_MAIN:
1622       DrawPreviewLevelAnimation();
1623       DoAnimation();
1624       break;
1625
1626     case GAME_MODE_LEVELS:
1627     case GAME_MODE_LEVELNR:
1628     case GAME_MODE_SETUP:
1629     case GAME_MODE_INFO:
1630     case GAME_MODE_SCORES:
1631       DoAnimation();
1632       break;
1633
1634     case GAME_MODE_EDITOR:
1635       HandleLevelEditorIdle();
1636       break;
1637
1638     default:
1639       break;
1640   }
1641 }
1642
1643 static int HandleJoystickForAllPlayers()
1644 {
1645   int i;
1646   int result = 0;
1647
1648   for (i = 0; i < MAX_PLAYERS; i++)
1649   {
1650     byte joy_action = 0;
1651
1652     /*
1653     if (!setup.input[i].use_joystick)
1654       continue;
1655       */
1656
1657     joy_action = Joystick(i);
1658     result |= joy_action;
1659
1660     if (!setup.input[i].use_joystick)
1661       continue;
1662
1663     stored_player[i].action = joy_action;
1664   }
1665
1666   return result;
1667 }
1668
1669 void HandleJoystick()
1670 {
1671   int joystick  = HandleJoystickForAllPlayers();
1672   int keyboard  = key_joystick_mapping;
1673   int joy       = (joystick | keyboard);
1674   int left      = joy & JOY_LEFT;
1675   int right     = joy & JOY_RIGHT;
1676   int up        = joy & JOY_UP;
1677   int down      = joy & JOY_DOWN;
1678   int button    = joy & JOY_BUTTON;
1679   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1680   int dx        = (left ? -1    : right ? 1     : 0);
1681   int dy        = (up   ? -1    : down  ? 1     : 0);
1682
1683   switch (game_status)
1684   {
1685     case GAME_MODE_TITLE:
1686     case GAME_MODE_MAIN:
1687     case GAME_MODE_LEVELS:
1688     case GAME_MODE_LEVELNR:
1689     case GAME_MODE_SETUP:
1690     case GAME_MODE_INFO:
1691     {
1692       static unsigned int joystickmove_delay = 0;
1693
1694       if (joystick && !button &&
1695           !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1696         newbutton = dx = dy = 0;
1697
1698       if (game_status == GAME_MODE_TITLE)
1699         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1700       else if (game_status == GAME_MODE_MAIN)
1701         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1702       else if (game_status == GAME_MODE_LEVELS)
1703         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1704       else if (game_status == GAME_MODE_LEVELNR)
1705         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1706       else if (game_status == GAME_MODE_SETUP)
1707         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1708       else if (game_status == GAME_MODE_INFO)
1709         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1710       break;
1711     }
1712
1713     case GAME_MODE_SCORES:
1714       HandleHallOfFame(0, 0, dx, dy, !newbutton);
1715       break;
1716
1717     case GAME_MODE_PLAYING:
1718       if (tape.playing || keyboard)
1719         newbutton = ((joy & JOY_BUTTON) != 0);
1720
1721       if (newbutton && AllPlayersGone)
1722       {
1723         GameEnd();
1724
1725         return;
1726       }
1727
1728       break;
1729
1730     default:
1731       break;
1732   }
1733 }