added easier accessible shortcuts for scaling up or zoom in on US keyboards
[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 "anim.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_equal ||     // ("Shift-=" is "+" on US keyboards)
1361        key == KSYM_0) &&
1362       ((GetKeyModState() & KMOD_Control) ||
1363        (GetKeyModState() & KMOD_Alt) ||
1364        (GetKeyModState() & KMOD_Meta)) &&
1365       video.window_scaling_available &&
1366       !video.fullscreen_enabled)
1367   {
1368     if (key == KSYM_0)
1369       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1370     else
1371       setup.window_scaling_percent +=
1372         (key == KSYM_minus ? -1 : +1) * STEP_WINDOW_SCALING_PERCENT;
1373
1374     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1375       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1376     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1377       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1378
1379     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1380
1381     if (game_status == GAME_MODE_SETUP)
1382       RedrawSetupScreenAfterFullscreenToggle();
1383
1384     return;
1385   }
1386
1387   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1388       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1389   {
1390     GameEnd();
1391
1392     return;
1393   }
1394
1395   if (game_status == GAME_MODE_MAIN &&
1396       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1397   {
1398     StartGameActions(options.network, setup.autorecord, level.random_seed);
1399
1400     return;
1401   }
1402
1403   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1404   {
1405     if (key == setup.shortcut.save_game)
1406       TapeQuickSave();
1407     else if (key == setup.shortcut.load_game)
1408       TapeQuickLoad();
1409     else if (key == setup.shortcut.toggle_pause)
1410       TapeTogglePause(TAPE_TOGGLE_MANUAL);
1411
1412     HandleTapeButtonKeys(key);
1413     HandleSoundButtonKeys(key);
1414   }
1415
1416   if (game_status == GAME_MODE_PLAYING && !network_playing)
1417   {
1418     int centered_player_nr_next = -999;
1419
1420     if (key == setup.shortcut.focus_player_all)
1421       centered_player_nr_next = -1;
1422     else
1423       for (i = 0; i < MAX_PLAYERS; i++)
1424         if (key == setup.shortcut.focus_player[i])
1425           centered_player_nr_next = i;
1426
1427     if (centered_player_nr_next != -999)
1428     {
1429       game.centered_player_nr_next = centered_player_nr_next;
1430       game.set_centered_player = TRUE;
1431
1432       if (tape.recording)
1433       {
1434         tape.centered_player_nr_next = game.centered_player_nr_next;
1435         tape.set_centered_player = TRUE;
1436       }
1437     }
1438   }
1439
1440   HandleKeysSpecial(key);
1441
1442   if (HandleGadgetsKeyInput(key))
1443   {
1444     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1445       key = KSYM_UNDEFINED;
1446   }
1447
1448   switch (game_status)
1449   {
1450     case GAME_MODE_PSEUDO_TYPENAME:
1451       HandleTypeName(0, key);
1452       break;
1453
1454     case GAME_MODE_TITLE:
1455     case GAME_MODE_MAIN:
1456     case GAME_MODE_LEVELS:
1457     case GAME_MODE_LEVELNR:
1458     case GAME_MODE_SETUP:
1459     case GAME_MODE_INFO:
1460     case GAME_MODE_SCORES:
1461       switch (key)
1462       {
1463         case KSYM_space:
1464         case KSYM_Return:
1465           if (game_status == GAME_MODE_TITLE)
1466             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1467           else if (game_status == GAME_MODE_MAIN)
1468             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1469           else if (game_status == GAME_MODE_LEVELS)
1470             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1471           else if (game_status == GAME_MODE_LEVELNR)
1472             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1473           else if (game_status == GAME_MODE_SETUP)
1474             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1475           else if (game_status == GAME_MODE_INFO)
1476             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1477           else if (game_status == GAME_MODE_SCORES)
1478             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1479           break;
1480
1481         case KSYM_Escape:
1482           if (game_status != GAME_MODE_MAIN)
1483             FadeSkipNextFadeIn();
1484
1485           if (game_status == GAME_MODE_TITLE)
1486             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1487           else if (game_status == GAME_MODE_LEVELS)
1488             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1489           else if (game_status == GAME_MODE_LEVELNR)
1490             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1491           else if (game_status == GAME_MODE_SETUP)
1492             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1493           else if (game_status == GAME_MODE_INFO)
1494             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1495           else if (game_status == GAME_MODE_SCORES)
1496             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1497           break;
1498
1499         case KSYM_Page_Up:
1500           if (game_status == GAME_MODE_LEVELS)
1501             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1502           else if (game_status == GAME_MODE_LEVELNR)
1503             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1504           else if (game_status == GAME_MODE_SETUP)
1505             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1506           else if (game_status == GAME_MODE_INFO)
1507             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1508           else if (game_status == GAME_MODE_SCORES)
1509             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1510           break;
1511
1512         case KSYM_Page_Down:
1513           if (game_status == GAME_MODE_LEVELS)
1514             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1515           else if (game_status == GAME_MODE_LEVELNR)
1516             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1517           else if (game_status == GAME_MODE_SETUP)
1518             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1519           else if (game_status == GAME_MODE_INFO)
1520             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1521           else if (game_status == GAME_MODE_SCORES)
1522             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1523           break;
1524
1525 #ifdef DEBUG
1526         case KSYM_0:
1527           GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
1528           break;
1529
1530         case KSYM_b:
1531           setup.sp_show_border_elements = !setup.sp_show_border_elements;
1532           printf("Supaplex border elements %s\n",
1533                  setup.sp_show_border_elements ? "enabled" : "disabled");
1534           break;
1535 #endif
1536
1537         default:
1538           break;
1539       }
1540       break;
1541
1542     case GAME_MODE_EDITOR:
1543       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1544         HandleLevelEditorKeyInput(key);
1545       break;
1546
1547     case GAME_MODE_PLAYING:
1548     {
1549       switch (key)
1550       {
1551         case KSYM_Escape:
1552           RequestQuitGame(setup.ask_on_escape);
1553           break;
1554
1555 #ifdef DEBUG
1556         case KSYM_0:
1557           if (key == KSYM_0)
1558           {
1559             if (GameFrameDelay == 500)
1560               GameFrameDelay = GAME_FRAME_DELAY;
1561             else
1562               GameFrameDelay = 500;
1563           }
1564           else
1565             GameFrameDelay = (key - KSYM_0) * 10;
1566           printf("Game speed == %d%% (%d ms delay between two frames)\n",
1567                  GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
1568           break;
1569
1570         case KSYM_d:
1571           if (options.debug)
1572           {
1573             options.debug = FALSE;
1574             printf("debug mode disabled\n");
1575           }
1576           else
1577           {
1578             options.debug = TRUE;
1579             printf("debug mode enabled\n");
1580           }
1581           break;
1582
1583         case KSYM_v:
1584           printf("::: currently using game engine version %d\n",
1585                  game.engine_version);
1586           break;
1587 #endif
1588
1589         default:
1590           break;
1591       }
1592       break;
1593     }
1594
1595     default:
1596       if (key == KSYM_Escape)
1597       {
1598         SetGameStatus(GAME_MODE_MAIN);
1599
1600         DrawMainMenu();
1601
1602         return;
1603       }
1604   }
1605 }
1606
1607 void HandleNoEvent()
1608 {
1609   // if (button_status && game_status != GAME_MODE_PLAYING)
1610   if (button_status && (game_status != GAME_MODE_PLAYING || tape.pausing))
1611   {
1612     HandleButton(0, 0, -button_status, button_status);
1613   }
1614   else
1615   {
1616     HandleJoystick();
1617   }
1618
1619 #if defined(NETWORK_AVALIABLE)
1620   if (options.network)
1621     HandleNetworking();
1622 #endif
1623
1624   switch (game_status)
1625   {
1626     case GAME_MODE_MAIN:
1627       DrawPreviewLevelAnimation();
1628       break;
1629
1630     case GAME_MODE_EDITOR:
1631       HandleLevelEditorIdle();
1632       break;
1633
1634     default:
1635       break;
1636   }
1637 }
1638
1639 static int HandleJoystickForAllPlayers()
1640 {
1641   int i;
1642   int result = 0;
1643
1644   for (i = 0; i < MAX_PLAYERS; i++)
1645   {
1646     byte joy_action = 0;
1647
1648     /*
1649     if (!setup.input[i].use_joystick)
1650       continue;
1651       */
1652
1653     joy_action = Joystick(i);
1654     result |= joy_action;
1655
1656     if (!setup.input[i].use_joystick)
1657       continue;
1658
1659     stored_player[i].action = joy_action;
1660   }
1661
1662   return result;
1663 }
1664
1665 void HandleJoystick()
1666 {
1667   int joystick  = HandleJoystickForAllPlayers();
1668   int keyboard  = key_joystick_mapping;
1669   int joy       = (joystick | keyboard);
1670   int left      = joy & JOY_LEFT;
1671   int right     = joy & JOY_RIGHT;
1672   int up        = joy & JOY_UP;
1673   int down      = joy & JOY_DOWN;
1674   int button    = joy & JOY_BUTTON;
1675   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1676   int dx        = (left ? -1    : right ? 1     : 0);
1677   int dy        = (up   ? -1    : down  ? 1     : 0);
1678
1679   switch (game_status)
1680   {
1681     case GAME_MODE_TITLE:
1682     case GAME_MODE_MAIN:
1683     case GAME_MODE_LEVELS:
1684     case GAME_MODE_LEVELNR:
1685     case GAME_MODE_SETUP:
1686     case GAME_MODE_INFO:
1687     {
1688       static unsigned int joystickmove_delay = 0;
1689
1690       if (joystick && !button &&
1691           !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1692         newbutton = dx = dy = 0;
1693
1694       if (game_status == GAME_MODE_TITLE)
1695         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1696       else if (game_status == GAME_MODE_MAIN)
1697         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1698       else if (game_status == GAME_MODE_LEVELS)
1699         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1700       else if (game_status == GAME_MODE_LEVELNR)
1701         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1702       else if (game_status == GAME_MODE_SETUP)
1703         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1704       else if (game_status == GAME_MODE_INFO)
1705         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1706       break;
1707     }
1708
1709     case GAME_MODE_SCORES:
1710       HandleHallOfFame(0, 0, dx, dy, !newbutton);
1711       break;
1712
1713     case GAME_MODE_PLAYING:
1714       if (tape.playing || keyboard)
1715         newbutton = ((joy & JOY_BUTTON) != 0);
1716
1717       if (newbutton && AllPlayersGone)
1718       {
1719         GameEnd();
1720
1721         return;
1722       }
1723
1724       break;
1725
1726     default:
1727       break;
1728   }
1729 }