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