fixed inconsistent global animation state after event actions
[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_WHEEL      (DEBUG_EVENTS   * 1)
31 #define DEBUG_EVENTS_WINDOW     (DEBUG_EVENTS   * 0)
32 #define DEBUG_EVENTS_FINGER     (DEBUG_EVENTS   * 0)
33 #define DEBUG_EVENTS_TEXT       (DEBUG_EVENTS   * 1)
34 #define DEBUG_EVENTS_KEY        (DEBUG_EVENTS   * 1)
35
36
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
41
42 static boolean virtual_button_pressed = FALSE;
43
44
45 // forward declarations for internal use
46 static void HandleNoEvent(void);
47 static void HandleEventActions(void);
48
49
50 // event filter especially needed for SDL event filtering due to
51 // delay problems with lots of mouse motion events when mouse button
52 // not pressed (X11 can handle this with 'PointerMotionHintMask')
53
54 // event filter addition for SDL2: as SDL2 does not have a function to enable
55 // or disable keyboard auto-repeat, filter repeated keyboard events instead
56
57 static int FilterEvents(const Event *event)
58 {
59   MotionEvent *motion;
60
61   // skip repeated key press events if keyboard auto-repeat is disabled
62   if (event->type == EVENT_KEYPRESS &&
63       event->key.repeat &&
64       !keyrepeat_status)
65     return 0;
66
67   if (event->type == EVENT_BUTTONPRESS ||
68       event->type == EVENT_BUTTONRELEASE)
69   {
70     ((ButtonEvent *)event)->x -= video.screen_xoffset;
71     ((ButtonEvent *)event)->y -= video.screen_yoffset;
72   }
73   else if (event->type == EVENT_MOTIONNOTIFY)
74   {
75     ((MotionEvent *)event)->x -= video.screen_xoffset;
76     ((MotionEvent *)event)->y -= video.screen_yoffset;
77
78     gfx.mouse_x = ((MotionEvent *)event)->x;
79     gfx.mouse_y = ((MotionEvent *)event)->y;
80   }
81
82   // non-motion events are directly passed to event handler functions
83   if (event->type != EVENT_MOTIONNOTIFY)
84     return 1;
85
86   motion = (MotionEvent *)event;
87   cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
88                              motion->y >= SY && motion->y < SY + SYSIZE);
89
90   // do no reset mouse cursor before all pending events have been processed
91   if (gfx.cursor_mode == cursor_mode_last &&
92       ((game_status == GAME_MODE_TITLE &&
93         gfx.cursor_mode == CURSOR_NONE) ||
94        (game_status == GAME_MODE_PLAYING &&
95         gfx.cursor_mode == CURSOR_PLAYFIELD)))
96   {
97     SetMouseCursor(CURSOR_DEFAULT);
98
99     DelayReached(&special_cursor_delay, 0);
100
101     cursor_mode_last = CURSOR_DEFAULT;
102   }
103
104   // skip mouse motion events without pressed button outside level editor
105   if (button_status == MB_RELEASED &&
106       game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
107     return 0;
108
109   return 1;
110 }
111
112 // to prevent delay problems, skip mouse motion events if the very next
113 // event is also a mouse motion event (and therefore effectively only
114 // handling the last of a row of mouse motion events in the event queue)
115
116 static boolean SkipPressedMouseMotionEvent(const Event *event)
117 {
118   // nothing to do if the current event is not a mouse motion event
119   if (event->type != EVENT_MOTIONNOTIFY)
120     return FALSE;
121
122   // only skip motion events with pressed button outside the game
123   if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
124     return FALSE;
125
126   if (PendingEvent())
127   {
128     Event next_event;
129
130     PeekEvent(&next_event);
131
132     // if next event is also a mouse motion event, skip the current one
133     if (next_event.type == EVENT_MOTIONNOTIFY)
134       return TRUE;
135   }
136
137   return FALSE;
138 }
139
140 static boolean WaitValidEvent(Event *event)
141 {
142   WaitEvent(event);
143
144   if (!FilterEvents(event))
145     return FALSE;
146
147   if (SkipPressedMouseMotionEvent(event))
148     return FALSE;
149
150   return TRUE;
151 }
152
153 /* this is especially needed for event modifications for the Android target:
154    if mouse coordinates should be modified in the event filter function,
155    using a properly installed SDL event filter does not work, because in
156    the event filter, mouse coordinates in the event structure are still
157    physical pixel positions, not logical (scaled) screen positions, so this
158    has to be handled at a later stage in the event processing functions
159    (when device pixel positions are already converted to screen positions) */
160
161 boolean NextValidEvent(Event *event)
162 {
163   while (PendingEvent())
164     if (WaitValidEvent(event))
165       return TRUE;
166
167   return FALSE;
168 }
169
170 static void HandleEvents(void)
171 {
172   Event event;
173   unsigned int event_frame_delay = 0;
174   unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
175
176   ResetDelayCounter(&event_frame_delay);
177
178   while (NextValidEvent(&event))
179   {
180     switch (event.type)
181     {
182       case EVENT_BUTTONPRESS:
183       case EVENT_BUTTONRELEASE:
184         HandleButtonEvent((ButtonEvent *) &event);
185         break;
186
187       case EVENT_MOTIONNOTIFY:
188         HandleMotionEvent((MotionEvent *) &event);
189         break;
190
191       case EVENT_WHEELMOTION:
192         HandleWheelEvent((WheelEvent *) &event);
193         break;
194
195       case SDL_WINDOWEVENT:
196         HandleWindowEvent((WindowEvent *) &event);
197         break;
198
199       case EVENT_FINGERPRESS:
200       case EVENT_FINGERRELEASE:
201       case EVENT_FINGERMOTION:
202         HandleFingerEvent((FingerEvent *) &event);
203         break;
204
205       case EVENT_TEXTINPUT:
206         HandleTextEvent((TextEvent *) &event);
207         break;
208
209       case SDL_APP_WILLENTERBACKGROUND:
210       case SDL_APP_DIDENTERBACKGROUND:
211       case SDL_APP_WILLENTERFOREGROUND:
212       case SDL_APP_DIDENTERFOREGROUND:
213         HandlePauseResumeEvent((PauseResumeEvent *) &event);
214         break;
215
216       case EVENT_KEYPRESS:
217       case EVENT_KEYRELEASE:
218         HandleKeyEvent((KeyEvent *) &event);
219         break;
220
221       case EVENT_USER:
222         HandleUserEvent((UserEvent *) &event);
223         break;
224
225       default:
226         HandleOtherEvents(&event);
227         break;
228     }
229
230     // do not handle events for longer than standard frame delay period
231     if (DelayReached(&event_frame_delay, event_frame_delay_value))
232       break;
233   }
234 }
235
236 void HandleOtherEvents(Event *event)
237 {
238   switch (event->type)
239   {
240     case SDL_CONTROLLERBUTTONDOWN:
241     case SDL_CONTROLLERBUTTONUP:
242       // for any game controller button event, disable overlay buttons
243       SetOverlayEnabled(FALSE);
244
245       HandleSpecialGameControllerButtons(event);
246
247       // FALL THROUGH
248     case SDL_CONTROLLERDEVICEADDED:
249     case SDL_CONTROLLERDEVICEREMOVED:
250     case SDL_CONTROLLERAXISMOTION:
251     case SDL_JOYAXISMOTION:
252     case SDL_JOYBUTTONDOWN:
253     case SDL_JOYBUTTONUP:
254       HandleJoystickEvent(event);
255       break;
256
257     case SDL_DROPBEGIN:
258     case SDL_DROPCOMPLETE:
259     case SDL_DROPFILE:
260     case SDL_DROPTEXT:
261       HandleDropEvent(event);
262       break;
263
264     case EVENT_QUIT:
265       CloseAllAndExit(0);
266       break;
267
268     default:
269       break;
270   }
271 }
272
273 static void HandleMouseCursor(void)
274 {
275   if (game_status == GAME_MODE_TITLE)
276   {
277     // when showing title screens, hide mouse pointer (if not moved)
278
279     if (gfx.cursor_mode != CURSOR_NONE &&
280         DelayReached(&special_cursor_delay, special_cursor_delay_value))
281     {
282       SetMouseCursor(CURSOR_NONE);
283     }
284   }
285   else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
286                                                 tape.single_step))
287   {
288     // when playing, display a special mouse pointer inside the playfield
289
290     if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
291         cursor_inside_playfield &&
292         DelayReached(&special_cursor_delay, special_cursor_delay_value))
293     {
294       if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
295           tile_cursor.enabled)
296         SetMouseCursor(CURSOR_PLAYFIELD);
297     }
298   }
299   else if (gfx.cursor_mode != CURSOR_DEFAULT)
300   {
301     SetMouseCursor(CURSOR_DEFAULT);
302   }
303
304   // this is set after all pending events have been processed
305   cursor_mode_last = gfx.cursor_mode;
306 }
307
308 void EventLoop(void)
309 {
310   while (1)
311   {
312     if (PendingEvent())
313       HandleEvents();
314     else
315       HandleNoEvent();
316
317     // execute event related actions after pending events have been processed
318     HandleEventActions();
319
320     // don't use all CPU time when idle; the main loop while playing
321     // has its own synchronization and is CPU friendly, too
322
323     if (game_status == GAME_MODE_PLAYING)
324       HandleGameActions();
325
326     // always copy backbuffer to visible screen for every video frame
327     BackToFront();
328
329     // reset video frame delay to default (may change again while playing)
330     SetVideoFrameDelay(MenuFrameDelay);
331
332     if (game_status == GAME_MODE_QUIT)
333       return;
334   }
335 }
336
337 void ClearAutoRepeatKeyEvents(void)
338 {
339   while (PendingEvent())
340   {
341     Event next_event;
342
343     PeekEvent(&next_event);
344
345     // if event is repeated key press event, remove it from event queue
346     if (next_event.type == EVENT_KEYPRESS &&
347         next_event.key.repeat)
348       WaitEvent(&next_event);
349     else
350       break;
351   }
352 }
353
354 void ClearEventQueue(void)
355 {
356   Event event;
357
358   while (NextValidEvent(&event))
359   {
360     switch (event.type)
361     {
362       case EVENT_BUTTONRELEASE:
363         button_status = MB_RELEASED;
364         break;
365
366       case EVENT_KEYRELEASE:
367         ClearPlayerAction();
368         break;
369
370       case SDL_CONTROLLERBUTTONUP:
371         HandleJoystickEvent(&event);
372         ClearPlayerAction();
373         break;
374
375       default:
376         HandleOtherEvents(&event);
377         break;
378     }
379   }
380 }
381
382 static void ClearPlayerMouseAction(void)
383 {
384   local_player->mouse_action.lx = 0;
385   local_player->mouse_action.ly = 0;
386   local_player->mouse_action.button = 0;
387 }
388
389 void ClearPlayerAction(void)
390 {
391   int i;
392
393   // simulate key release events for still pressed keys
394   key_joystick_mapping = 0;
395   for (i = 0; i < MAX_PLAYERS; i++)
396   {
397     stored_player[i].action = 0;
398     stored_player[i].snap_action = 0;
399   }
400
401   ClearJoystickState();
402   ClearPlayerMouseAction();
403 }
404
405 static void SetPlayerMouseAction(int mx, int my, int button)
406 {
407   int lx = getLevelFromScreenX(mx);
408   int ly = getLevelFromScreenY(my);
409   int new_button = (!local_player->mouse_action.button && button);
410
411   if (local_player->mouse_action.button_hint)
412     button = local_player->mouse_action.button_hint;
413
414   ClearPlayerMouseAction();
415
416   if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
417     return;
418
419   local_player->mouse_action.lx = lx;
420   local_player->mouse_action.ly = ly;
421   local_player->mouse_action.button = button;
422
423   if (tape.recording && tape.pausing && tape.use_mouse)
424   {
425     // un-pause a paused game only if mouse button was newly pressed down
426     if (new_button)
427       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
428   }
429
430   SetTileCursorXY(lx, ly);
431 }
432
433 void HandleButtonEvent(ButtonEvent *event)
434 {
435 #if DEBUG_EVENTS_BUTTON
436   Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
437         event->button,
438         event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
439         event->x, event->y);
440 #endif
441
442   // for any mouse button event, disable playfield tile cursor
443   SetTileCursorEnabled(FALSE);
444
445 #if defined(HAS_SCREEN_KEYBOARD)
446   if (video.shifted_up)
447     event->y += video.shifted_up_pos;
448 #endif
449
450   motion_status = FALSE;
451
452   if (event->type == EVENT_BUTTONPRESS)
453     button_status = event->button;
454   else
455     button_status = MB_RELEASED;
456
457   HandleButton(event->x, event->y, button_status, event->button);
458 }
459
460 void HandleMotionEvent(MotionEvent *event)
461 {
462   if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
463     return;
464
465   motion_status = TRUE;
466
467 #if DEBUG_EVENTS_MOTION
468   Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
469         button_status, event->x, event->y);
470 #endif
471
472   HandleButton(event->x, event->y, button_status, button_status);
473 }
474
475 void HandleWheelEvent(WheelEvent *event)
476 {
477   int button_nr;
478
479 #if DEBUG_EVENTS_WHEEL
480 #if 1
481   Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
482         event->which, event->x, event->y);
483 #else
484   // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
485   Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
486         event->which, event->x, event->y,
487         (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
488          "SDL_MOUSEWHEEL_FLIPPED"));
489 #endif
490 #endif
491
492   button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
493                event->x > 0 ? MB_WHEEL_RIGHT :
494                event->y < 0 ? MB_WHEEL_DOWN :
495                event->y > 0 ? MB_WHEEL_UP : 0);
496
497 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
498   // accelerated mouse wheel available on Mac and Windows
499   wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
500 #else
501   // no accelerated mouse wheel available on Unix/Linux
502   wheel_steps = DEFAULT_WHEEL_STEPS;
503 #endif
504
505   motion_status = FALSE;
506
507   button_status = button_nr;
508   HandleButton(0, 0, button_status, -button_nr);
509
510   button_status = MB_RELEASED;
511   HandleButton(0, 0, button_status, -button_nr);
512 }
513
514 void HandleWindowEvent(WindowEvent *event)
515 {
516 #if DEBUG_EVENTS_WINDOW
517   int subtype = event->event;
518
519   char *event_name =
520     (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
521      subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
522      subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
523      subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
524      subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
525      subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
526      subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
527      subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
528      subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
529      subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
530      subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
531      subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
532      subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
533      subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
534      "(UNKNOWN)");
535
536   Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
537         event_name, event->data1, event->data2);
538 #endif
539
540 #if 0
541   // (not needed, as the screen gets redrawn every 20 ms anyway)
542   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
543       event->event == SDL_WINDOWEVENT_RESIZED ||
544       event->event == SDL_WINDOWEVENT_EXPOSED)
545     SDLRedrawWindow();
546 #endif
547
548   if (event->event == SDL_WINDOWEVENT_RESIZED)
549   {
550     if (!video.fullscreen_enabled)
551     {
552       int new_window_width  = event->data1;
553       int new_window_height = event->data2;
554
555       // if window size has changed after resizing, calculate new scaling factor
556       if (new_window_width  != video.window_width ||
557           new_window_height != video.window_height)
558       {
559         int new_xpercent = 100.0 * new_window_width  / video.screen_width  + .5;
560         int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
561
562         // (extreme window scaling allowed, but cannot be saved permanently)
563         video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
564         setup.window_scaling_percent =
565           MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
566               MAX_WINDOW_SCALING_PERCENT);
567
568         video.window_width  = new_window_width;
569         video.window_height = new_window_height;
570
571         if (game_status == GAME_MODE_SETUP)
572           RedrawSetupScreenAfterFullscreenToggle();
573
574         SetWindowTitle();
575       }
576     }
577 #if defined(PLATFORM_ANDROID)
578     else
579     {
580       int new_display_width  = event->data1;
581       int new_display_height = event->data2;
582
583       // if fullscreen display size has changed, device has been rotated
584       if (new_display_width  != video.display_width ||
585           new_display_height != video.display_height)
586       {
587         int nr = GRID_ACTIVE_NR();      // previous screen orientation
588
589         video.display_width  = new_display_width;
590         video.display_height = new_display_height;
591
592         SDLSetScreenProperties();
593
594         // check if screen orientation has changed (should always be true here)
595         if (nr != GRID_ACTIVE_NR())
596         {
597           int x, y;
598
599           if (game_status == GAME_MODE_SETUP)
600             RedrawSetupScreenAfterScreenRotation(nr);
601
602           nr = GRID_ACTIVE_NR();
603
604           overlay.grid_xsize = setup.touch.grid_xsize[nr];
605           overlay.grid_ysize = setup.touch.grid_ysize[nr];
606
607           for (x = 0; x < MAX_GRID_XSIZE; x++)
608             for (y = 0; y < MAX_GRID_YSIZE; y++)
609               overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
610         }
611       }
612     }
613 #endif
614   }
615 }
616
617 #define NUM_TOUCH_FINGERS               3
618
619 static struct
620 {
621   boolean touched;
622   SDL_FingerID finger_id;
623   int counter;
624   Key key;
625   byte action;
626 } touch_info[NUM_TOUCH_FINGERS];
627
628 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
629 {
630 #if 1
631   int x = event->x * overlay.grid_xsize;
632   int y = event->y * overlay.grid_ysize;
633   int grid_button = overlay.grid_button[x][y];
634   int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
635   Key key = (grid_button == CHAR_GRID_BUTTON_LEFT  ? setup.input[0].key.left :
636              grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
637              grid_button == CHAR_GRID_BUTTON_UP    ? setup.input[0].key.up :
638              grid_button == CHAR_GRID_BUTTON_DOWN  ? setup.input[0].key.down :
639              grid_button == CHAR_GRID_BUTTON_SNAP  ? setup.input[0].key.snap :
640              grid_button == CHAR_GRID_BUTTON_DROP  ? setup.input[0].key.drop :
641              KSYM_UNDEFINED);
642 #else
643   float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
644   float event_x = (event->x);
645   float event_y = (event->y - ypos) / (1 - ypos);
646   Key key = (event_x > 0         && event_x < 1.0 / 6.0 &&
647              event_y > 2.0 / 3.0 && event_y < 1 ?
648              setup.input[0].key.snap :
649              event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
650              event_y > 2.0 / 3.0 && event_y < 1 ?
651              setup.input[0].key.drop :
652              event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
653              event_y > 0         && event_y < 1.0 / 3.0 ?
654              setup.input[0].key.up :
655              event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
656              event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
657              setup.input[0].key.left :
658              event_x > 8.0 / 9.0 && event_x < 1 &&
659              event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
660              setup.input[0].key.right :
661              event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
662              event_y > 2.0 / 3.0 && event_y < 1 ?
663              setup.input[0].key.down :
664              KSYM_UNDEFINED);
665 #endif
666   int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
667                     KEY_PRESSED);
668   char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
669                            "KEY_PRESSED");
670   int i;
671
672   virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
673
674   // for any touch input event, enable overlay buttons (if activated)
675   SetOverlayEnabled(TRUE);
676
677   Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
678         getKeyNameFromKey(key), key_status_name, event->fingerId);
679
680   if (key_status == KEY_PRESSED)
681     overlay.grid_button_action |= grid_button_action;
682   else
683     overlay.grid_button_action &= ~grid_button_action;
684
685   // check if we already know this touch event's finger id
686   for (i = 0; i < NUM_TOUCH_FINGERS; i++)
687   {
688     if (touch_info[i].touched &&
689         touch_info[i].finger_id == event->fingerId)
690     {
691       // Error(ERR_DEBUG, "MARK 1: %d", i);
692
693       break;
694     }
695   }
696
697   if (i >= NUM_TOUCH_FINGERS)
698   {
699     if (key_status == KEY_PRESSED)
700     {
701       int oldest_pos = 0, oldest_counter = touch_info[0].counter;
702
703       // unknown finger id -- get new, empty slot, if available
704       for (i = 0; i < NUM_TOUCH_FINGERS; i++)
705       {
706         if (touch_info[i].counter < oldest_counter)
707         {
708           oldest_pos = i;
709           oldest_counter = touch_info[i].counter;
710
711           // Error(ERR_DEBUG, "MARK 2: %d", i);
712         }
713
714         if (!touch_info[i].touched)
715         {
716           // Error(ERR_DEBUG, "MARK 3: %d", i);
717
718           break;
719         }
720       }
721
722       if (i >= NUM_TOUCH_FINGERS)
723       {
724         // all slots allocated -- use oldest slot
725         i = oldest_pos;
726
727         // Error(ERR_DEBUG, "MARK 4: %d", i);
728       }
729     }
730     else
731     {
732       // release of previously unknown key (should not happen)
733
734       if (key != KSYM_UNDEFINED)
735       {
736         HandleKey(key, KEY_RELEASED);
737
738         Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
739               getKeyNameFromKey(key), "KEY_RELEASED", i);
740       }
741     }
742   }
743
744   if (i < NUM_TOUCH_FINGERS)
745   {
746     if (key_status == KEY_PRESSED)
747     {
748       if (touch_info[i].key != key)
749       {
750         if (touch_info[i].key != KSYM_UNDEFINED)
751         {
752           HandleKey(touch_info[i].key, KEY_RELEASED);
753
754           // undraw previous grid button when moving finger away
755           overlay.grid_button_action &= ~touch_info[i].action;
756
757           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
758                 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
759         }
760
761         if (key != KSYM_UNDEFINED)
762         {
763           HandleKey(key, KEY_PRESSED);
764
765           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
766                 getKeyNameFromKey(key), "KEY_PRESSED", i);
767         }
768       }
769
770       touch_info[i].touched = TRUE;
771       touch_info[i].finger_id = event->fingerId;
772       touch_info[i].counter = Counter();
773       touch_info[i].key = key;
774       touch_info[i].action = grid_button_action;
775     }
776     else
777     {
778       if (touch_info[i].key != KSYM_UNDEFINED)
779       {
780         HandleKey(touch_info[i].key, KEY_RELEASED);
781
782         Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
783               getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
784       }
785
786       touch_info[i].touched = FALSE;
787       touch_info[i].finger_id = 0;
788       touch_info[i].counter = 0;
789       touch_info[i].key = 0;
790       touch_info[i].action = JOY_NO_ACTION;
791     }
792   }
793 }
794
795 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
796 {
797   static Key motion_key_x = KSYM_UNDEFINED;
798   static Key motion_key_y = KSYM_UNDEFINED;
799   static Key button_key = KSYM_UNDEFINED;
800   static float motion_x1, motion_y1;
801   static float button_x1, button_y1;
802   static SDL_FingerID motion_id = -1;
803   static SDL_FingerID button_id = -1;
804   int move_trigger_distance_percent = setup.touch.move_distance;
805   int drop_trigger_distance_percent = setup.touch.drop_distance;
806   float move_trigger_distance = (float)move_trigger_distance_percent / 100;
807   float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
808   float event_x = event->x;
809   float event_y = event->y;
810
811   if (event->type == EVENT_FINGERPRESS)
812   {
813     if (event_x > 1.0 / 3.0)
814     {
815       // motion area
816
817       motion_id = event->fingerId;
818
819       motion_x1 = event_x;
820       motion_y1 = event_y;
821
822       motion_key_x = KSYM_UNDEFINED;
823       motion_key_y = KSYM_UNDEFINED;
824
825       Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
826     }
827     else
828     {
829       // button area
830
831       button_id = event->fingerId;
832
833       button_x1 = event_x;
834       button_y1 = event_y;
835
836       button_key = setup.input[0].key.snap;
837
838       HandleKey(button_key, KEY_PRESSED);
839
840       Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
841     }
842   }
843   else if (event->type == EVENT_FINGERRELEASE)
844   {
845     if (event->fingerId == motion_id)
846     {
847       motion_id = -1;
848
849       if (motion_key_x != KSYM_UNDEFINED)
850         HandleKey(motion_key_x, KEY_RELEASED);
851       if (motion_key_y != KSYM_UNDEFINED)
852         HandleKey(motion_key_y, KEY_RELEASED);
853
854       motion_key_x = KSYM_UNDEFINED;
855       motion_key_y = KSYM_UNDEFINED;
856
857       Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
858     }
859     else if (event->fingerId == button_id)
860     {
861       button_id = -1;
862
863       if (button_key != KSYM_UNDEFINED)
864         HandleKey(button_key, KEY_RELEASED);
865
866       button_key = KSYM_UNDEFINED;
867
868       Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
869     }
870   }
871   else if (event->type == EVENT_FINGERMOTION)
872   {
873     if (event->fingerId == motion_id)
874     {
875       float distance_x = ABS(event_x - motion_x1);
876       float distance_y = ABS(event_y - motion_y1);
877       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
878                               event_x > motion_x1 ? setup.input[0].key.right :
879                               KSYM_UNDEFINED);
880       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
881                               event_y > motion_y1 ? setup.input[0].key.down :
882                               KSYM_UNDEFINED);
883
884       if (distance_x < move_trigger_distance / 2 ||
885           distance_x < distance_y)
886         new_motion_key_x = KSYM_UNDEFINED;
887
888       if (distance_y < move_trigger_distance / 2 ||
889           distance_y < distance_x)
890         new_motion_key_y = KSYM_UNDEFINED;
891
892       if (distance_x > move_trigger_distance ||
893           distance_y > move_trigger_distance)
894       {
895         if (new_motion_key_x != motion_key_x)
896         {
897           if (motion_key_x != KSYM_UNDEFINED)
898             HandleKey(motion_key_x, KEY_RELEASED);
899           if (new_motion_key_x != KSYM_UNDEFINED)
900             HandleKey(new_motion_key_x, KEY_PRESSED);
901         }
902
903         if (new_motion_key_y != motion_key_y)
904         {
905           if (motion_key_y != KSYM_UNDEFINED)
906             HandleKey(motion_key_y, KEY_RELEASED);
907           if (new_motion_key_y != KSYM_UNDEFINED)
908             HandleKey(new_motion_key_y, KEY_PRESSED);
909         }
910
911         motion_x1 = event_x;
912         motion_y1 = event_y;
913
914         motion_key_x = new_motion_key_x;
915         motion_key_y = new_motion_key_y;
916
917         Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
918       }
919     }
920     else if (event->fingerId == button_id)
921     {
922       float distance_x = ABS(event_x - button_x1);
923       float distance_y = ABS(event_y - button_y1);
924
925       if (distance_x < drop_trigger_distance / 2 &&
926           distance_y > drop_trigger_distance)
927       {
928         if (button_key == setup.input[0].key.snap)
929           HandleKey(button_key, KEY_RELEASED);
930
931         button_x1 = event_x;
932         button_y1 = event_y;
933
934         button_key = setup.input[0].key.drop;
935
936         HandleKey(button_key, KEY_PRESSED);
937
938         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
939       }
940     }
941   }
942 }
943
944 void HandleFingerEvent(FingerEvent *event)
945 {
946 #if DEBUG_EVENTS_FINGER
947   Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
948         event->type == EVENT_FINGERPRESS ? "pressed" :
949         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
950         event->touchId,
951         event->fingerId,
952         event->x, event->y,
953         event->dx, event->dy,
954         event->pressure);
955 #endif
956
957   runtime.uses_touch_device = TRUE;
958
959   if (game_status != GAME_MODE_PLAYING)
960     return;
961
962   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
963   {
964     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
965       local_player->mouse_action.button_hint =
966         (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
967          event->x < 0.5                     ? MB_LEFTBUTTON  :
968          event->x > 0.5                     ? MB_RIGHTBUTTON :
969          MB_NOT_PRESSED);
970
971     return;
972   }
973
974   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
975     HandleFingerEvent_VirtualButtons(event);
976   else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
977     HandleFingerEvent_WipeGestures(event);
978 }
979
980 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
981 {
982   static int old_mx = 0, old_my = 0;
983   static int last_button = MB_LEFTBUTTON;
984   static boolean touched = FALSE;
985   static boolean tapped = FALSE;
986
987   // screen tile was tapped (but finger not touching the screen anymore)
988   // (this point will also be reached without receiving a touch event)
989   if (tapped && !touched)
990   {
991     SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
992
993     tapped = FALSE;
994   }
995
996   // stop here if this function was not triggered by a touch event
997   if (button == -1)
998     return;
999
1000   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1001   {
1002     // finger started touching the screen
1003
1004     touched = TRUE;
1005     tapped = TRUE;
1006
1007     if (!motion_status)
1008     {
1009       old_mx = mx;
1010       old_my = my;
1011
1012       ClearPlayerMouseAction();
1013
1014       Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1015     }
1016   }
1017   else if (button == MB_RELEASED && touched)
1018   {
1019     // finger stopped touching the screen
1020
1021     touched = FALSE;
1022
1023     if (tapped)
1024       SetPlayerMouseAction(old_mx, old_my, last_button);
1025     else
1026       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1027
1028     Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1029   }
1030
1031   if (touched)
1032   {
1033     // finger moved while touching the screen
1034
1035     int old_x = getLevelFromScreenX(old_mx);
1036     int old_y = getLevelFromScreenY(old_my);
1037     int new_x = getLevelFromScreenX(mx);
1038     int new_y = getLevelFromScreenY(my);
1039
1040     if (new_x != old_x || new_y != old_y)
1041       tapped = FALSE;
1042
1043     if (new_x != old_x)
1044     {
1045       // finger moved left or right from (horizontal) starting position
1046
1047       int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1048
1049       SetPlayerMouseAction(old_mx, old_my, button_nr);
1050
1051       last_button = button_nr;
1052
1053       Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1054     }
1055     else
1056     {
1057       // finger stays at or returned to (horizontal) starting position
1058
1059       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1060
1061       Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1062     }
1063   }
1064 }
1065
1066 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1067 {
1068   static int old_mx = 0, old_my = 0;
1069   static int last_button = MB_LEFTBUTTON;
1070   static boolean touched = FALSE;
1071   static boolean tapped = FALSE;
1072
1073   // screen tile was tapped (but finger not touching the screen anymore)
1074   // (this point will also be reached without receiving a touch event)
1075   if (tapped && !touched)
1076   {
1077     SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1078
1079     tapped = FALSE;
1080   }
1081
1082   // stop here if this function was not triggered by a touch event
1083   if (button == -1)
1084     return;
1085
1086   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1087   {
1088     // finger started touching the screen
1089
1090     touched = TRUE;
1091     tapped = TRUE;
1092
1093     if (!motion_status)
1094     {
1095       old_mx = mx;
1096       old_my = my;
1097
1098       ClearPlayerMouseAction();
1099
1100       Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1101     }
1102   }
1103   else if (button == MB_RELEASED && touched)
1104   {
1105     // finger stopped touching the screen
1106
1107     touched = FALSE;
1108
1109     if (tapped)
1110       SetPlayerMouseAction(old_mx, old_my, last_button);
1111     else
1112       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1113
1114     Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1115   }
1116
1117   if (touched)
1118   {
1119     // finger moved while touching the screen
1120
1121     int old_x = getLevelFromScreenX(old_mx);
1122     int old_y = getLevelFromScreenY(old_my);
1123     int new_x = getLevelFromScreenX(mx);
1124     int new_y = getLevelFromScreenY(my);
1125
1126     if (new_x != old_x || new_y != old_y)
1127     {
1128       // finger moved away from starting position
1129
1130       int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1131
1132       // quickly alternate between clicking and releasing for maximum speed
1133       if (FrameCounter % 2 == 0)
1134         button_nr = MB_RELEASED;
1135
1136       SetPlayerMouseAction(old_mx, old_my, button_nr);
1137
1138       if (button_nr)
1139         last_button = button_nr;
1140
1141       tapped = FALSE;
1142
1143       Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1144     }
1145     else
1146     {
1147       // finger stays at or returned to starting position
1148
1149       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1150
1151       Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1152     }
1153   }
1154 }
1155
1156 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1157 {
1158   static int old_mx = 0, old_my = 0;
1159   static Key motion_key_x = KSYM_UNDEFINED;
1160   static Key motion_key_y = KSYM_UNDEFINED;
1161   static boolean touched = FALSE;
1162   static boolean started_on_player = FALSE;
1163   static boolean player_is_dropping = FALSE;
1164   static int player_drop_count = 0;
1165   static int last_player_x = -1;
1166   static int last_player_y = -1;
1167
1168   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1169   {
1170     touched = TRUE;
1171
1172     old_mx = mx;
1173     old_my = my;
1174
1175     if (!motion_status)
1176     {
1177       started_on_player = FALSE;
1178       player_is_dropping = FALSE;
1179       player_drop_count = 0;
1180       last_player_x = -1;
1181       last_player_y = -1;
1182
1183       motion_key_x = KSYM_UNDEFINED;
1184       motion_key_y = KSYM_UNDEFINED;
1185
1186       Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1187     }
1188   }
1189   else if (button == MB_RELEASED && touched)
1190   {
1191     touched = FALSE;
1192
1193     old_mx = 0;
1194     old_my = 0;
1195
1196     if (motion_key_x != KSYM_UNDEFINED)
1197       HandleKey(motion_key_x, KEY_RELEASED);
1198     if (motion_key_y != KSYM_UNDEFINED)
1199       HandleKey(motion_key_y, KEY_RELEASED);
1200
1201     if (started_on_player)
1202     {
1203       if (player_is_dropping)
1204       {
1205         Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1206
1207         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1208       }
1209       else
1210       {
1211         Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1212
1213         HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1214       }
1215     }
1216
1217     motion_key_x = KSYM_UNDEFINED;
1218     motion_key_y = KSYM_UNDEFINED;
1219
1220     Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1221   }
1222
1223   if (touched)
1224   {
1225     int src_x = local_player->jx;
1226     int src_y = local_player->jy;
1227     int dst_x = getLevelFromScreenX(old_mx);
1228     int dst_y = getLevelFromScreenY(old_my);
1229     int dx = dst_x - src_x;
1230     int dy = dst_y - src_y;
1231     Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1232                             dx > 0 ? setup.input[0].key.right :
1233                             KSYM_UNDEFINED);
1234     Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1235                             dy > 0 ? setup.input[0].key.down :
1236                             KSYM_UNDEFINED);
1237
1238     if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1239         (last_player_x != local_player->jx ||
1240          last_player_y != local_player->jy))
1241     {
1242       // in case of asymmetric diagonal movement, use "preferred" direction
1243
1244       int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1245
1246       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1247         level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1248       else
1249         local_player->last_move_dir = last_move_dir;
1250
1251       // (required to prevent accidentally forcing direction for next movement)
1252       last_player_x = local_player->jx;
1253       last_player_y = local_player->jy;
1254     }
1255
1256     if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1257     {
1258       started_on_player = TRUE;
1259       player_drop_count = getPlayerInventorySize(0);
1260       player_is_dropping = (player_drop_count > 0);
1261
1262       if (player_is_dropping)
1263       {
1264         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1265
1266         HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1267       }
1268       else
1269       {
1270         Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1271
1272         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1273       }
1274     }
1275     else if (dx != 0 || dy != 0)
1276     {
1277       if (player_is_dropping &&
1278           player_drop_count == getPlayerInventorySize(0))
1279       {
1280         Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1281
1282         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1283         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1284
1285         player_is_dropping = FALSE;
1286       }
1287     }
1288
1289     if (new_motion_key_x != motion_key_x)
1290     {
1291       Error(ERR_DEBUG, "---------- %s %s ----------",
1292             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1293             dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1294
1295       if (motion_key_x != KSYM_UNDEFINED)
1296         HandleKey(motion_key_x, KEY_RELEASED);
1297       if (new_motion_key_x != KSYM_UNDEFINED)
1298         HandleKey(new_motion_key_x, KEY_PRESSED);
1299     }
1300
1301     if (new_motion_key_y != motion_key_y)
1302     {
1303       Error(ERR_DEBUG, "---------- %s %s ----------",
1304             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1305             dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1306
1307       if (motion_key_y != KSYM_UNDEFINED)
1308         HandleKey(motion_key_y, KEY_RELEASED);
1309       if (new_motion_key_y != KSYM_UNDEFINED)
1310         HandleKey(new_motion_key_y, KEY_PRESSED);
1311     }
1312
1313     motion_key_x = new_motion_key_x;
1314     motion_key_y = new_motion_key_y;
1315   }
1316 }
1317
1318 static void HandleButtonOrFinger(int mx, int my, int button)
1319 {
1320   if (game_status != GAME_MODE_PLAYING)
1321     return;
1322
1323   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1324   {
1325     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1326       HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1327     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1328       HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1329     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1330       SetPlayerMouseAction(mx, my, button);     // special case
1331   }
1332   else
1333   {
1334     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1335       HandleButtonOrFinger_FollowFinger(mx, my, button);
1336   }
1337 }
1338
1339 static boolean checkTextInputKeyModState(void)
1340 {
1341   // when playing, only handle raw key events and ignore text input
1342   if (game_status == GAME_MODE_PLAYING)
1343     return FALSE;
1344
1345   return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1346 }
1347
1348 void HandleTextEvent(TextEvent *event)
1349 {
1350   char *text = event->text;
1351   Key key = getKeyFromKeyName(text);
1352
1353 #if DEBUG_EVENTS_TEXT
1354   Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1355         text,
1356         strlen(text),
1357         text[0], (int)(text[0]),
1358         key,
1359         getKeyNameFromKey(key),
1360         GetKeyModState());
1361 #endif
1362
1363 #if !defined(HAS_SCREEN_KEYBOARD)
1364   // non-mobile devices: only handle key input with modifier keys pressed here
1365   // (every other key input is handled directly as physical key input event)
1366   if (!checkTextInputKeyModState())
1367     return;
1368 #endif
1369
1370   // process text input as "classic" (with uppercase etc.) key input event
1371   HandleKey(key, KEY_PRESSED);
1372   HandleKey(key, KEY_RELEASED);
1373 }
1374
1375 void HandlePauseResumeEvent(PauseResumeEvent *event)
1376 {
1377   if (event->type == SDL_APP_WILLENTERBACKGROUND)
1378   {
1379     Mix_PauseMusic();
1380   }
1381   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1382   {
1383     Mix_ResumeMusic();
1384   }
1385 }
1386
1387 void HandleKeyEvent(KeyEvent *event)
1388 {
1389   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1390   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1391   Key key = GetEventKey(event, with_modifiers);
1392   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1393
1394 #if DEBUG_EVENTS_KEY
1395   Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1396         event->type == EVENT_KEYPRESS ? "pressed" : "released",
1397         event->keysym.scancode,
1398         event->keysym.sym,
1399         keymod,
1400         GetKeyModState(),
1401         key,
1402         getKeyNameFromKey(key));
1403 #endif
1404
1405 #if defined(PLATFORM_ANDROID)
1406   if (key == KSYM_Back)
1407   {
1408     // always map the "back" button to the "escape" key on Android devices
1409     key = KSYM_Escape;
1410   }
1411   else if (key == KSYM_Menu)
1412   {
1413     // the "menu" button can be used to toggle displaying virtual buttons
1414     if (key_status == KEY_PRESSED)
1415       SetOverlayEnabled(!GetOverlayEnabled());
1416   }
1417   else
1418   {
1419     // for any other "real" key event, disable virtual buttons
1420     SetOverlayEnabled(FALSE);
1421   }
1422 #endif
1423
1424   HandleKeyModState(keymod, key_status);
1425
1426   // only handle raw key input without text modifier keys pressed
1427   if (!checkTextInputKeyModState())
1428     HandleKey(key, key_status);
1429 }
1430
1431 static int HandleDropFileEvent(char *filename)
1432 {
1433   Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1434
1435   // check and extract dropped zip files into correct user data directory
1436   if (!strSuffixLower(filename, ".zip"))
1437   {
1438     Error(ERR_WARN, "file '%s' not supported", filename);
1439
1440     return TREE_TYPE_UNDEFINED;
1441   }
1442
1443   TreeInfo *tree_node = NULL;
1444   int tree_type = GetZipFileTreeType(filename);
1445   char *directory = TREE_USERDIR(tree_type);
1446
1447   if (directory == NULL)
1448   {
1449     Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1450
1451     return TREE_TYPE_UNDEFINED;
1452   }
1453
1454   if (tree_type == TREE_TYPE_LEVEL_DIR &&
1455       game_status == GAME_MODE_LEVELS &&
1456       leveldir_current->node_parent != NULL)
1457   {
1458     // extract new level set next to currently selected level set
1459     tree_node = leveldir_current;
1460
1461     // get parent directory of currently selected level set directory
1462     directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1463
1464     // use private level directory instead of top-level package level directory
1465     if (strPrefix(directory, options.level_directory) &&
1466         strEqual(leveldir_current->node_parent->fullpath, "."))
1467       directory = getUserLevelDir(NULL);
1468   }
1469
1470   // extract level or artwork set from zip file to target directory
1471   char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1472
1473   if (top_dir == NULL)
1474   {
1475     // error message already issued by "ExtractZipFileIntoDirectory()"
1476
1477     return TREE_TYPE_UNDEFINED;
1478   }
1479
1480   // add extracted level or artwork set to tree info structure
1481   AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1482
1483   // update menu screen (and possibly change current level set)
1484   DrawScreenAfterAddingSet(top_dir, tree_type);
1485
1486   return tree_type;
1487 }
1488
1489 static void HandleDropTextEvent(char *text)
1490 {
1491   Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1492 }
1493
1494 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1495                                     int num_artwork_sets_succeeded,
1496                                     int num_files_failed)
1497 {
1498   // only show request dialog if no other request dialog already active
1499   if (game.request_active)
1500     return;
1501
1502   // this case can happen with drag-and-drop with older SDL versions
1503   if (num_level_sets_succeeded == 0 &&
1504       num_artwork_sets_succeeded == 0 &&
1505       num_files_failed == 0)
1506     return;
1507
1508   char message[100];
1509
1510   if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1511   {
1512     char message_part1[50];
1513
1514     sprintf(message_part1, "New %s set%s added",
1515             (num_artwork_sets_succeeded == 0 ? "level" :
1516              num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1517             (num_level_sets_succeeded +
1518              num_artwork_sets_succeeded > 1 ? "s" : ""));
1519
1520     if (num_files_failed > 0)
1521       sprintf(message, "%s, but %d dropped file%s failed!",
1522               message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1523     else
1524       sprintf(message, "%s!", message_part1);
1525   }
1526   else if (num_files_failed > 0)
1527   {
1528     sprintf(message, "Failed to process dropped file%s!",
1529             num_files_failed > 1 ? "s" : "");
1530   }
1531
1532   Request(message, REQ_CONFIRM);
1533 }
1534
1535 void HandleDropEvent(Event *event)
1536 {
1537   static boolean confirm_on_drop_complete = FALSE;
1538   static int num_level_sets_succeeded = 0;
1539   static int num_artwork_sets_succeeded = 0;
1540   static int num_files_failed = 0;
1541
1542   switch (event->type)
1543   {
1544     case SDL_DROPBEGIN:
1545     {
1546       confirm_on_drop_complete = TRUE;
1547       num_level_sets_succeeded = 0;
1548       num_artwork_sets_succeeded = 0;
1549       num_files_failed = 0;
1550
1551       break;
1552     }
1553
1554     case SDL_DROPFILE:
1555     {
1556       int tree_type = HandleDropFileEvent(event->drop.file);
1557
1558       if (tree_type == TREE_TYPE_LEVEL_DIR)
1559         num_level_sets_succeeded++;
1560       else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1561                tree_type == TREE_TYPE_SOUNDS_DIR ||
1562                tree_type == TREE_TYPE_MUSIC_DIR)
1563         num_artwork_sets_succeeded++;
1564       else
1565         num_files_failed++;
1566
1567       // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1568       if (!confirm_on_drop_complete)
1569       {
1570         // process all remaining events, including further SDL_DROPFILE events
1571         ClearEventQueue();
1572
1573         HandleDropCompleteEvent(num_level_sets_succeeded,
1574                                 num_artwork_sets_succeeded,
1575                                 num_files_failed);
1576
1577         num_level_sets_succeeded = 0;
1578         num_artwork_sets_succeeded = 0;
1579         num_files_failed = 0;
1580       }
1581
1582       break;
1583     }
1584
1585     case SDL_DROPTEXT:
1586     {
1587       HandleDropTextEvent(event->drop.file);
1588
1589       break;
1590     }
1591
1592     case SDL_DROPCOMPLETE:
1593     {
1594       HandleDropCompleteEvent(num_level_sets_succeeded,
1595                               num_artwork_sets_succeeded,
1596                               num_files_failed);
1597
1598       break;
1599     }
1600   }
1601
1602   if (event->drop.file != NULL)
1603     SDL_free(event->drop.file);
1604 }
1605
1606 void HandleUserEvent(UserEvent *event)
1607 {
1608   switch (event->code)
1609   {
1610     case USEREVENT_ANIM_EVENT_ACTION:
1611       // execute action functions until matching action was found
1612       if (DoKeysymAction(event->value1) ||
1613           DoGadgetAction(event->value1) ||
1614           DoScreenAction(event->value1))
1615         return;
1616       break;
1617
1618     default:
1619       break;
1620   }
1621 }
1622
1623 void HandleButton(int mx, int my, int button, int button_nr)
1624 {
1625   static int old_mx = 0, old_my = 0;
1626   boolean button_hold = FALSE;
1627   boolean handle_gadgets = TRUE;
1628
1629   if (button_nr < 0)
1630   {
1631     mx = old_mx;
1632     my = old_my;
1633     button_nr = -button_nr;
1634     button_hold = TRUE;
1635   }
1636   else
1637   {
1638     old_mx = mx;
1639     old_my = my;
1640   }
1641
1642 #if defined(PLATFORM_ANDROID)
1643   // when playing, only handle gadgets when using "follow finger" controls
1644   // or when using touch controls in combination with the MM game engine
1645   // or when using gadgets that do not overlap with virtual buttons
1646   handle_gadgets =
1647     (game_status != GAME_MODE_PLAYING ||
1648      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1649      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1650      (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1651       !virtual_button_pressed));
1652 #endif
1653
1654   if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1655   {
1656     // do not handle this button event anymore
1657     return;             // force mouse event not to be handled at all
1658   }
1659
1660   if (handle_gadgets && HandleGadgets(mx, my, button))
1661   {
1662     // do not handle this button event anymore
1663     mx = my = -32;      // force mouse event to be outside screen tiles
1664   }
1665
1666   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1667     return;
1668
1669   // do not use scroll wheel button events for anything other than gadgets
1670   if (IS_WHEEL_BUTTON(button_nr))
1671     return;
1672
1673   switch (game_status)
1674   {
1675     case GAME_MODE_TITLE:
1676       HandleTitleScreen(mx, my, 0, 0, button);
1677       break;
1678
1679     case GAME_MODE_MAIN:
1680       HandleMainMenu(mx, my, 0, 0, button);
1681       break;
1682
1683     case GAME_MODE_PSEUDO_TYPENAME:
1684       HandleTypeName(0, KSYM_Return);
1685       break;
1686
1687     case GAME_MODE_LEVELS:
1688       HandleChooseLevelSet(mx, my, 0, 0, button);
1689       break;
1690
1691     case GAME_MODE_LEVELNR:
1692       HandleChooseLevelNr(mx, my, 0, 0, button);
1693       break;
1694
1695     case GAME_MODE_SCORES:
1696       HandleHallOfFame(0, 0, 0, 0, button);
1697       break;
1698
1699     case GAME_MODE_EDITOR:
1700       HandleLevelEditorIdle();
1701       break;
1702
1703     case GAME_MODE_INFO:
1704       HandleInfoScreen(mx, my, 0, 0, button);
1705       break;
1706
1707     case GAME_MODE_SETUP:
1708       HandleSetupScreen(mx, my, 0, 0, button);
1709       break;
1710
1711     case GAME_MODE_PLAYING:
1712       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1713         HandleButtonOrFinger(mx, my, button);
1714       else
1715         SetPlayerMouseAction(mx, my, button);
1716
1717 #ifdef DEBUG
1718       if (button == MB_PRESSED && !motion_status && !button_hold &&
1719           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1720         DumpTileFromScreen(mx, my);
1721 #endif
1722
1723       break;
1724
1725     default:
1726       break;
1727   }
1728 }
1729
1730 static boolean is_string_suffix(char *string, char *suffix)
1731 {
1732   int string_len = strlen(string);
1733   int suffix_len = strlen(suffix);
1734
1735   if (suffix_len > string_len)
1736     return FALSE;
1737
1738   return (strEqual(&string[string_len - suffix_len], suffix));
1739 }
1740
1741 #define MAX_CHEAT_INPUT_LEN     32
1742
1743 static void HandleKeysSpecial(Key key)
1744 {
1745   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1746   char letter = getCharFromKey(key);
1747   int cheat_input_len = strlen(cheat_input);
1748   int i;
1749
1750   if (letter == 0)
1751     return;
1752
1753   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1754   {
1755     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1756       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1757
1758     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1759   }
1760
1761   cheat_input[cheat_input_len++] = letter;
1762   cheat_input[cheat_input_len] = '\0';
1763
1764 #if DEBUG_EVENTS_KEY
1765   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1766 #endif
1767
1768   if (game_status == GAME_MODE_MAIN)
1769   {
1770     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1771         is_string_suffix(cheat_input, ":ist"))
1772     {
1773       InsertSolutionTape();
1774     }
1775     else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1776              is_string_suffix(cheat_input, ":pst"))
1777     {
1778       PlaySolutionTape();
1779     }
1780     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1781              is_string_suffix(cheat_input, ":rg"))
1782     {
1783       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1784       DrawMainMenu();
1785     }
1786     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1787              is_string_suffix(cheat_input, ":rs"))
1788     {
1789       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1790       DrawMainMenu();
1791     }
1792     else if (is_string_suffix(cheat_input, ":reload-music") ||
1793              is_string_suffix(cheat_input, ":rm"))
1794     {
1795       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1796       DrawMainMenu();
1797     }
1798     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1799              is_string_suffix(cheat_input, ":ra"))
1800     {
1801       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1802                           1 << ARTWORK_TYPE_SOUNDS |
1803                           1 << ARTWORK_TYPE_MUSIC);
1804       DrawMainMenu();
1805     }
1806     else if (is_string_suffix(cheat_input, ":dump-level") ||
1807              is_string_suffix(cheat_input, ":dl"))
1808     {
1809       DumpLevel(&level);
1810     }
1811     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1812              is_string_suffix(cheat_input, ":dt"))
1813     {
1814       DumpTape(&tape);
1815     }
1816     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1817              is_string_suffix(cheat_input, ":ft"))
1818     {
1819       /* fix single-player tapes that contain player input for more than one
1820          player (due to a bug in 3.3.1.2 and earlier versions), which results
1821          in playing levels with more than one player in multi-player mode,
1822          even though the tape was originally recorded in single-player mode */
1823
1824       // remove player input actions for all players but the first one
1825       for (i = 1; i < MAX_PLAYERS; i++)
1826         tape.player_participates[i] = FALSE;
1827
1828       tape.changed = TRUE;
1829     }
1830     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1831              is_string_suffix(cheat_input, ":snl"))
1832     {
1833       SaveNativeLevel(&level);
1834     }
1835     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1836              is_string_suffix(cheat_input, ":fps"))
1837     {
1838       global.show_frames_per_second = !global.show_frames_per_second;
1839     }
1840   }
1841   else if (game_status == GAME_MODE_PLAYING)
1842   {
1843 #ifdef DEBUG
1844     if (is_string_suffix(cheat_input, ".q"))
1845       DEBUG_SetMaximumDynamite();
1846 #endif
1847   }
1848   else if (game_status == GAME_MODE_EDITOR)
1849   {
1850     if (is_string_suffix(cheat_input, ":dump-brush") ||
1851         is_string_suffix(cheat_input, ":DB"))
1852     {
1853       DumpBrush();
1854     }
1855     else if (is_string_suffix(cheat_input, ":DDB"))
1856     {
1857       DumpBrush_Small();
1858     }
1859
1860     if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1861     {
1862       if (letter == 'x')        // copy brush to clipboard (small size)
1863       {
1864         CopyBrushToClipboard_Small();
1865       }
1866       else if (letter == 'c')   // copy brush to clipboard (normal size)
1867       {
1868         CopyBrushToClipboard();
1869       }
1870       else if (letter == 'v')   // paste brush from Clipboard
1871       {
1872         CopyClipboardToBrush();
1873       }
1874     }
1875   }
1876
1877   // special key shortcuts for all game modes
1878   if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1879       is_string_suffix(cheat_input, ":dea") ||
1880       is_string_suffix(cheat_input, ":DEA"))
1881   {
1882     DumpGadgetIdentifiers();
1883     DumpScreenIdentifiers();
1884   }
1885 }
1886
1887 boolean HandleKeysDebug(Key key, int key_status)
1888 {
1889 #ifdef DEBUG
1890   int i;
1891
1892   if (key_status != KEY_PRESSED)
1893     return FALSE;
1894
1895   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1896   {
1897     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1898
1899     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1900     {
1901       if (key == setup.debug.frame_delay_key[i] &&
1902           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1903       {
1904         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1905                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1906
1907         if (!setup.debug.frame_delay_game_only)
1908           MenuFrameDelay = GameFrameDelay;
1909
1910         SetVideoFrameDelay(GameFrameDelay);
1911
1912         if (GameFrameDelay > ONE_SECOND_DELAY)
1913           Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1914         else if (GameFrameDelay != 0)
1915           Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1916                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1917                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1918         else
1919           Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1920
1921         return TRUE;
1922       }
1923     }
1924   }
1925
1926   if (game_status == GAME_MODE_PLAYING)
1927   {
1928     if (key == KSYM_d)
1929     {
1930       options.debug = !options.debug;
1931
1932       Error(ERR_INFO, "debug mode %s",
1933             (options.debug ? "enabled" : "disabled"));
1934
1935       return TRUE;
1936     }
1937     else if (key == KSYM_v)
1938     {
1939       Error(ERR_INFO, "currently using game engine version %d",
1940             game.engine_version);
1941
1942       return TRUE;
1943     }
1944   }
1945 #endif
1946
1947   return FALSE;
1948 }
1949
1950 void HandleKey(Key key, int key_status)
1951 {
1952   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1953   static boolean ignore_repeated_key = FALSE;
1954   static struct SetupKeyboardInfo ski;
1955   static struct SetupShortcutInfo ssi;
1956   static struct
1957   {
1958     Key *key_custom;
1959     Key *key_snap;
1960     Key key_default;
1961     byte action;
1962   } key_info[] =
1963   {
1964     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1965     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1966     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1967     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1968     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1969     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1970   };
1971   int joy = 0;
1972   int i;
1973
1974   if (HandleKeysDebug(key, key_status))
1975     return;             // do not handle already processed keys again
1976
1977   // map special keys (media keys / remote control buttons) to default keys
1978   if (key == KSYM_PlayPause)
1979     key = KSYM_space;
1980   else if (key == KSYM_Select)
1981     key = KSYM_Return;
1982
1983   HandleSpecialGameControllerKeys(key, key_status);
1984
1985   if (game_status == GAME_MODE_PLAYING)
1986   {
1987     // only needed for single-step tape recording mode
1988     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1989     int pnr;
1990
1991     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1992     {
1993       byte key_action = 0;
1994       byte key_snap_action = 0;
1995
1996       if (setup.input[pnr].use_joystick)
1997         continue;
1998
1999       ski = setup.input[pnr].key;
2000
2001       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2002         if (key == *key_info[i].key_custom)
2003           key_action |= key_info[i].action;
2004
2005       // use combined snap+direction keys for the first player only
2006       if (pnr == 0)
2007       {
2008         ssi = setup.shortcut;
2009
2010         // also remember normal snap key when handling snap+direction keys
2011         key_snap_action |= key_action & JOY_BUTTON_SNAP;
2012
2013         for (i = 0; i < NUM_DIRECTIONS; i++)
2014         {
2015           if (key == *key_info[i].key_snap)
2016           {
2017             key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
2018             key_snap_action |= key_info[i].action;
2019           }
2020         }
2021       }
2022
2023       if (key_status == KEY_PRESSED)
2024       {
2025         stored_player[pnr].action      |= key_action;
2026         stored_player[pnr].snap_action |= key_snap_action;
2027       }
2028       else
2029       {
2030         stored_player[pnr].action      &= ~key_action;
2031         stored_player[pnr].snap_action &= ~key_snap_action;
2032       }
2033
2034       // restore snap action if one of several pressed snap keys was released
2035       if (stored_player[pnr].snap_action)
2036         stored_player[pnr].action |= JOY_BUTTON_SNAP;
2037
2038       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2039       {
2040         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2041         {
2042           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2043
2044           // if snap key already pressed, keep pause mode when releasing
2045           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2046             has_snapped[pnr] = TRUE;
2047         }
2048         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2049         {
2050           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2051
2052           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2053               getRedDiskReleaseFlag_SP() == 0)
2054           {
2055             // add a single inactive frame before dropping starts
2056             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2057             stored_player[pnr].force_dropping = TRUE;
2058           }
2059         }
2060         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2061         {
2062           // if snap key was pressed without direction, leave pause mode
2063           if (!has_snapped[pnr])
2064             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2065
2066           has_snapped[pnr] = FALSE;
2067         }
2068       }
2069       else if (tape.recording && tape.pausing && !tape.use_mouse)
2070       {
2071         // prevent key release events from un-pausing a paused game
2072         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2073           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2074       }
2075
2076       // for MM style levels, handle in-game keyboard input in HandleJoystick()
2077       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2078         joy |= key_action;
2079     }
2080   }
2081   else
2082   {
2083     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2084       if (key == key_info[i].key_default)
2085         joy |= key_info[i].action;
2086   }
2087
2088   if (joy)
2089   {
2090     if (key_status == KEY_PRESSED)
2091       key_joystick_mapping |= joy;
2092     else
2093       key_joystick_mapping &= ~joy;
2094
2095     HandleJoystick();
2096   }
2097
2098   if (game_status != GAME_MODE_PLAYING)
2099     key_joystick_mapping = 0;
2100
2101   if (key_status == KEY_RELEASED)
2102   {
2103     // reset flag to ignore repeated "key pressed" events after key release
2104     ignore_repeated_key = FALSE;
2105
2106     return;
2107   }
2108
2109   if ((key == KSYM_F11 ||
2110        ((key == KSYM_Return ||
2111          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2112       video.fullscreen_available &&
2113       !ignore_repeated_key)
2114   {
2115     setup.fullscreen = !setup.fullscreen;
2116
2117     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2118
2119     if (game_status == GAME_MODE_SETUP)
2120       RedrawSetupScreenAfterFullscreenToggle();
2121
2122     // set flag to ignore repeated "key pressed" events
2123     ignore_repeated_key = TRUE;
2124
2125     return;
2126   }
2127
2128   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2129        key == KSYM_minus || key == KSYM_KP_Subtract ||
2130        key == KSYM_plus  || key == KSYM_KP_Add ||
2131        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2132       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2133       video.window_scaling_available &&
2134       !video.fullscreen_enabled)
2135   {
2136     if (key == KSYM_0 || key == KSYM_KP_0)
2137       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2138     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2139       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2140     else
2141       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2142
2143     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2144       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2145     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2146       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2147
2148     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2149
2150     if (game_status == GAME_MODE_SETUP)
2151       RedrawSetupScreenAfterFullscreenToggle();
2152
2153     return;
2154   }
2155
2156   // some key events are handled like clicks for global animations
2157   boolean click = (key == KSYM_space ||
2158                    key == KSYM_Return ||
2159                    key == KSYM_Escape);
2160
2161   if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2162   {
2163     // do not handle this key event anymore
2164     if (key != KSYM_Escape)     // always allow ESC key to be handled
2165       return;
2166   }
2167
2168   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2169       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2170   {
2171     GameEnd();
2172
2173     return;
2174   }
2175
2176   if (game_status == GAME_MODE_MAIN &&
2177       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2178   {
2179     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2180
2181     return;
2182   }
2183
2184   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2185   {
2186     if (key == setup.shortcut.save_game)
2187       TapeQuickSave();
2188     else if (key == setup.shortcut.load_game)
2189       TapeQuickLoad();
2190     else if (key == setup.shortcut.toggle_pause)
2191       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2192
2193     HandleTapeButtonKeys(key);
2194     HandleSoundButtonKeys(key);
2195   }
2196
2197   if (game_status == GAME_MODE_PLAYING && !network_playing)
2198   {
2199     int centered_player_nr_next = -999;
2200
2201     if (key == setup.shortcut.focus_player_all)
2202       centered_player_nr_next = -1;
2203     else
2204       for (i = 0; i < MAX_PLAYERS; i++)
2205         if (key == setup.shortcut.focus_player[i])
2206           centered_player_nr_next = i;
2207
2208     if (centered_player_nr_next != -999)
2209     {
2210       game.centered_player_nr_next = centered_player_nr_next;
2211       game.set_centered_player = TRUE;
2212
2213       if (tape.recording)
2214       {
2215         tape.centered_player_nr_next = game.centered_player_nr_next;
2216         tape.set_centered_player = TRUE;
2217       }
2218     }
2219   }
2220
2221   HandleKeysSpecial(key);
2222
2223   if (HandleGadgetsKeyInput(key))
2224     return;             // do not handle already processed keys again
2225
2226   switch (game_status)
2227   {
2228     case GAME_MODE_PSEUDO_TYPENAME:
2229       HandleTypeName(0, key);
2230       break;
2231
2232     case GAME_MODE_TITLE:
2233     case GAME_MODE_MAIN:
2234     case GAME_MODE_LEVELS:
2235     case GAME_MODE_LEVELNR:
2236     case GAME_MODE_SETUP:
2237     case GAME_MODE_INFO:
2238     case GAME_MODE_SCORES:
2239
2240       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2241         break;
2242
2243       switch (key)
2244       {
2245         case KSYM_space:
2246         case KSYM_Return:
2247           if (game_status == GAME_MODE_TITLE)
2248             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2249           else if (game_status == GAME_MODE_MAIN)
2250             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2251           else if (game_status == GAME_MODE_LEVELS)
2252             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2253           else if (game_status == GAME_MODE_LEVELNR)
2254             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2255           else if (game_status == GAME_MODE_SETUP)
2256             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2257           else if (game_status == GAME_MODE_INFO)
2258             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2259           else if (game_status == GAME_MODE_SCORES)
2260             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2261           break;
2262
2263         case KSYM_Escape:
2264           if (game_status != GAME_MODE_MAIN)
2265             FadeSkipNextFadeIn();
2266
2267           if (game_status == GAME_MODE_TITLE)
2268             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2269           else if (game_status == GAME_MODE_LEVELS)
2270             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2271           else if (game_status == GAME_MODE_LEVELNR)
2272             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2273           else if (game_status == GAME_MODE_SETUP)
2274             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2275           else if (game_status == GAME_MODE_INFO)
2276             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2277           else if (game_status == GAME_MODE_SCORES)
2278             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2279           break;
2280
2281         case KSYM_Page_Up:
2282           if (game_status == GAME_MODE_LEVELS)
2283             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2284           else if (game_status == GAME_MODE_LEVELNR)
2285             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2286           else if (game_status == GAME_MODE_SETUP)
2287             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2288           else if (game_status == GAME_MODE_INFO)
2289             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2290           else if (game_status == GAME_MODE_SCORES)
2291             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2292           break;
2293
2294         case KSYM_Page_Down:
2295           if (game_status == GAME_MODE_LEVELS)
2296             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2297           else if (game_status == GAME_MODE_LEVELNR)
2298             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2299           else if (game_status == GAME_MODE_SETUP)
2300             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2301           else if (game_status == GAME_MODE_INFO)
2302             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2303           else if (game_status == GAME_MODE_SCORES)
2304             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2305           break;
2306
2307         default:
2308           break;
2309       }
2310       break;
2311
2312     case GAME_MODE_EDITOR:
2313       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2314         HandleLevelEditorKeyInput(key);
2315       break;
2316
2317     case GAME_MODE_PLAYING:
2318     {
2319       switch (key)
2320       {
2321         case KSYM_Escape:
2322           RequestQuitGame(setup.ask_on_escape);
2323           break;
2324
2325         default:
2326           break;
2327       }
2328       break;
2329     }
2330
2331     default:
2332       if (key == KSYM_Escape)
2333       {
2334         SetGameStatus(GAME_MODE_MAIN);
2335
2336         DrawMainMenu();
2337
2338         return;
2339       }
2340   }
2341 }
2342
2343 void HandleNoEvent(void)
2344 {
2345   HandleMouseCursor();
2346
2347   switch (game_status)
2348   {
2349     case GAME_MODE_PLAYING:
2350       HandleButtonOrFinger(-1, -1, -1);
2351       break;
2352   }
2353 }
2354
2355 void HandleEventActions(void)
2356 {
2357   // if (button_status && game_status != GAME_MODE_PLAYING)
2358   if (button_status && (game_status != GAME_MODE_PLAYING ||
2359                         tape.pausing ||
2360                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2361   {
2362     HandleButton(0, 0, button_status, -button_status);
2363   }
2364   else
2365   {
2366     HandleJoystick();
2367   }
2368
2369   if (network.enabled)
2370     HandleNetworking();
2371
2372   switch (game_status)
2373   {
2374     case GAME_MODE_MAIN:
2375       DrawPreviewLevelAnimation();
2376       break;
2377
2378     case GAME_MODE_EDITOR:
2379       HandleLevelEditorIdle();
2380       break;
2381
2382     default:
2383       break;
2384   }
2385 }
2386
2387 static void HandleTileCursor(int dx, int dy, int button)
2388 {
2389   if (!dx || !button)
2390     ClearPlayerMouseAction();
2391
2392   if (!dx && !dy)
2393     return;
2394
2395   if (button)
2396   {
2397     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2398                          (dx < 0 ? MB_LEFTBUTTON :
2399                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2400   }
2401   else if (!tile_cursor.moving)
2402   {
2403     int old_xpos = tile_cursor.xpos;
2404     int old_ypos = tile_cursor.ypos;
2405     int new_xpos = old_xpos;
2406     int new_ypos = old_ypos;
2407
2408     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2409       new_xpos = old_xpos + dx;
2410
2411     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2412       new_ypos = old_ypos + dy;
2413
2414     SetTileCursorTargetXY(new_xpos, new_ypos);
2415   }
2416 }
2417
2418 static int HandleJoystickForAllPlayers(void)
2419 {
2420   int i;
2421   int result = 0;
2422   boolean no_joysticks_configured = TRUE;
2423   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2424   static byte joy_action_last[MAX_PLAYERS];
2425
2426   for (i = 0; i < MAX_PLAYERS; i++)
2427     if (setup.input[i].use_joystick)
2428       no_joysticks_configured = FALSE;
2429
2430   // if no joysticks configured, map connected joysticks to players
2431   if (no_joysticks_configured)
2432     use_as_joystick_nr = TRUE;
2433
2434   for (i = 0; i < MAX_PLAYERS; i++)
2435   {
2436     byte joy_action = 0;
2437
2438     joy_action = JoystickExt(i, use_as_joystick_nr);
2439     result |= joy_action;
2440
2441     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2442         joy_action != joy_action_last[i])
2443       stored_player[i].action = joy_action;
2444
2445     joy_action_last[i] = joy_action;
2446   }
2447
2448   return result;
2449 }
2450
2451 void HandleJoystick(void)
2452 {
2453   static unsigned int joytest_delay = 0;
2454   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2455   static int joytest_last = 0;
2456   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2457   int delay_value       = GADGET_FRAME_DELAY;
2458   int joystick  = HandleJoystickForAllPlayers();
2459   int keyboard  = key_joystick_mapping;
2460   int joy       = (joystick | keyboard);
2461   int joytest   = joystick;
2462   int left      = joy & JOY_LEFT;
2463   int right     = joy & JOY_RIGHT;
2464   int up        = joy & JOY_UP;
2465   int down      = joy & JOY_DOWN;
2466   int button    = joy & JOY_BUTTON;
2467   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2468   int dx        = (left ? -1    : right ? 1     : 0);
2469   int dy        = (up   ? -1    : down  ? 1     : 0);
2470   boolean use_delay_value_first = (joytest != joytest_last);
2471
2472   if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2473   {
2474     // do not handle this button event anymore
2475     return;
2476   }
2477
2478   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2479                     anyTextGadgetActive()))
2480   {
2481     // leave name input in main menu or text input gadget
2482     HandleKey(KSYM_Escape, KEY_PRESSED);
2483     HandleKey(KSYM_Escape, KEY_RELEASED);
2484
2485     return;
2486   }
2487
2488   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2489   {
2490     if (game_status == GAME_MODE_PLAYING)
2491     {
2492       // when playing MM style levels, also use delay for keyboard events
2493       joytest |= keyboard;
2494
2495       // only use first delay value for new events, but not for changed events
2496       use_delay_value_first = (!joytest != !joytest_last);
2497
2498       // only use delay after the initial keyboard event
2499       delay_value = 0;
2500     }
2501
2502     // for any joystick or keyboard event, enable playfield tile cursor
2503     if (dx || dy || button)
2504       SetTileCursorEnabled(TRUE);
2505   }
2506
2507   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2508   {
2509     // delay joystick/keyboard actions if axes/keys continually pressed
2510     newbutton = dx = dy = 0;
2511   }
2512   else
2513   {
2514     // first start with longer delay, then continue with shorter delay
2515     joytest_delay_value =
2516       (use_delay_value_first ? delay_value_first : delay_value);
2517   }
2518
2519   joytest_last = joytest;
2520
2521   switch (game_status)
2522   {
2523     case GAME_MODE_TITLE:
2524     case GAME_MODE_MAIN:
2525     case GAME_MODE_LEVELS:
2526     case GAME_MODE_LEVELNR:
2527     case GAME_MODE_SETUP:
2528     case GAME_MODE_INFO:
2529     case GAME_MODE_SCORES:
2530     {
2531       if (anyTextGadgetActive())
2532         break;
2533
2534       if (game_status == GAME_MODE_TITLE)
2535         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2536       else if (game_status == GAME_MODE_MAIN)
2537         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2538       else if (game_status == GAME_MODE_LEVELS)
2539         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2540       else if (game_status == GAME_MODE_LEVELNR)
2541         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2542       else if (game_status == GAME_MODE_SETUP)
2543         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2544       else if (game_status == GAME_MODE_INFO)
2545         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2546       else if (game_status == GAME_MODE_SCORES)
2547         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2548
2549       break;
2550     }
2551
2552     case GAME_MODE_PLAYING:
2553 #if 0
2554       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2555       if (tape.playing || keyboard)
2556         newbutton = ((joy & JOY_BUTTON) != 0);
2557 #endif
2558
2559       if (newbutton && game.all_players_gone)
2560       {
2561         GameEnd();
2562
2563         return;
2564       }
2565
2566       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2567       {
2568         if (joystick & JOY_ACTION)
2569           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2570       }
2571       else if (tape.recording && tape.pausing && !tape.use_mouse)
2572       {
2573         if (joystick & JOY_ACTION)
2574           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2575       }
2576
2577       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2578         HandleTileCursor(dx, dy, button);
2579
2580       break;
2581
2582     default:
2583       break;
2584   }
2585 }
2586
2587 void HandleSpecialGameControllerButtons(Event *event)
2588 {
2589   int key_status;
2590   Key key;
2591
2592   switch (event->type)
2593   {
2594     case SDL_CONTROLLERBUTTONDOWN:
2595       key_status = KEY_PRESSED;
2596       break;
2597
2598     case SDL_CONTROLLERBUTTONUP:
2599       key_status = KEY_RELEASED;
2600       break;
2601
2602     default:
2603       return;
2604   }
2605
2606   switch (event->cbutton.button)
2607   {
2608     case SDL_CONTROLLER_BUTTON_START:
2609       key = KSYM_space;
2610       break;
2611
2612     case SDL_CONTROLLER_BUTTON_BACK:
2613       key = KSYM_Escape;
2614       break;
2615
2616     default:
2617       return;
2618   }
2619
2620   HandleKey(key, key_status);
2621 }
2622
2623 void HandleSpecialGameControllerKeys(Key key, int key_status)
2624 {
2625 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2626   int button = SDL_CONTROLLER_BUTTON_INVALID;
2627
2628   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2629   if (key == KSYM_Rewind)
2630     button = SDL_CONTROLLER_BUTTON_A;
2631   else if (key == KSYM_FastForward || key == KSYM_Menu)
2632     button = SDL_CONTROLLER_BUTTON_B;
2633
2634   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2635   {
2636     Event event;
2637
2638     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2639                   SDL_CONTROLLERBUTTONUP);
2640
2641     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2642     event.cbutton.button = button;
2643     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2644                            SDL_RELEASED);
2645
2646     HandleJoystickEvent(&event);
2647   }
2648 #endif
2649 }
2650
2651 boolean DoKeysymAction(int keysym)
2652 {
2653   if (keysym < 0)
2654   {
2655     Key key = (Key)(-keysym);
2656
2657     HandleKey(key, KEY_PRESSED);
2658     HandleKey(key, KEY_RELEASED);
2659
2660     return TRUE;
2661   }
2662
2663   return FALSE;
2664 }