added global animation actions executed after init/anim/post delay
[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_DELAY_ACTION:
1611     case USEREVENT_ANIM_EVENT_ACTION:
1612       // execute action functions until matching action was found
1613       if (DoKeysymAction(event->value1) ||
1614           DoGadgetAction(event->value1) ||
1615           DoScreenAction(event->value1))
1616         return;
1617       break;
1618
1619     default:
1620       break;
1621   }
1622 }
1623
1624 void HandleButton(int mx, int my, int button, int button_nr)
1625 {
1626   static int old_mx = 0, old_my = 0;
1627   boolean button_hold = FALSE;
1628   boolean handle_gadgets = TRUE;
1629
1630   if (button_nr < 0)
1631   {
1632     mx = old_mx;
1633     my = old_my;
1634     button_nr = -button_nr;
1635     button_hold = TRUE;
1636   }
1637   else
1638   {
1639     old_mx = mx;
1640     old_my = my;
1641   }
1642
1643 #if defined(PLATFORM_ANDROID)
1644   // when playing, only handle gadgets when using "follow finger" controls
1645   // or when using touch controls in combination with the MM game engine
1646   // or when using gadgets that do not overlap with virtual buttons
1647   handle_gadgets =
1648     (game_status != GAME_MODE_PLAYING ||
1649      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1650      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1651      (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1652       !virtual_button_pressed));
1653 #endif
1654
1655   if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1656   {
1657     // do not handle this button event anymore
1658     return;             // force mouse event not to be handled at all
1659   }
1660
1661   if (handle_gadgets && HandleGadgets(mx, my, button))
1662   {
1663     // do not handle this button event anymore
1664     mx = my = -32;      // force mouse event to be outside screen tiles
1665   }
1666
1667   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1668     return;
1669
1670   // do not use scroll wheel button events for anything other than gadgets
1671   if (IS_WHEEL_BUTTON(button_nr))
1672     return;
1673
1674   switch (game_status)
1675   {
1676     case GAME_MODE_TITLE:
1677       HandleTitleScreen(mx, my, 0, 0, button);
1678       break;
1679
1680     case GAME_MODE_MAIN:
1681       HandleMainMenu(mx, my, 0, 0, button);
1682       break;
1683
1684     case GAME_MODE_PSEUDO_TYPENAME:
1685       HandleTypeName(0, KSYM_Return);
1686       break;
1687
1688     case GAME_MODE_LEVELS:
1689       HandleChooseLevelSet(mx, my, 0, 0, button);
1690       break;
1691
1692     case GAME_MODE_LEVELNR:
1693       HandleChooseLevelNr(mx, my, 0, 0, button);
1694       break;
1695
1696     case GAME_MODE_SCORES:
1697       HandleHallOfFame(0, 0, 0, 0, button);
1698       break;
1699
1700     case GAME_MODE_EDITOR:
1701       HandleLevelEditorIdle();
1702       break;
1703
1704     case GAME_MODE_INFO:
1705       HandleInfoScreen(mx, my, 0, 0, button);
1706       break;
1707
1708     case GAME_MODE_SETUP:
1709       HandleSetupScreen(mx, my, 0, 0, button);
1710       break;
1711
1712     case GAME_MODE_PLAYING:
1713       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1714         HandleButtonOrFinger(mx, my, button);
1715       else
1716         SetPlayerMouseAction(mx, my, button);
1717
1718 #ifdef DEBUG
1719       if (button == MB_PRESSED && !motion_status && !button_hold &&
1720           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1721         DumpTileFromScreen(mx, my);
1722 #endif
1723
1724       break;
1725
1726     default:
1727       break;
1728   }
1729 }
1730
1731 static boolean is_string_suffix(char *string, char *suffix)
1732 {
1733   int string_len = strlen(string);
1734   int suffix_len = strlen(suffix);
1735
1736   if (suffix_len > string_len)
1737     return FALSE;
1738
1739   return (strEqual(&string[string_len - suffix_len], suffix));
1740 }
1741
1742 #define MAX_CHEAT_INPUT_LEN     32
1743
1744 static void HandleKeysSpecial(Key key)
1745 {
1746   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1747   char letter = getCharFromKey(key);
1748   int cheat_input_len = strlen(cheat_input);
1749   int i;
1750
1751   if (letter == 0)
1752     return;
1753
1754   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1755   {
1756     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1757       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1758
1759     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1760   }
1761
1762   cheat_input[cheat_input_len++] = letter;
1763   cheat_input[cheat_input_len] = '\0';
1764
1765 #if DEBUG_EVENTS_KEY
1766   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1767 #endif
1768
1769   if (game_status == GAME_MODE_MAIN)
1770   {
1771     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1772         is_string_suffix(cheat_input, ":ist"))
1773     {
1774       InsertSolutionTape();
1775     }
1776     else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1777              is_string_suffix(cheat_input, ":pst"))
1778     {
1779       PlaySolutionTape();
1780     }
1781     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1782              is_string_suffix(cheat_input, ":rg"))
1783     {
1784       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1785       DrawMainMenu();
1786     }
1787     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1788              is_string_suffix(cheat_input, ":rs"))
1789     {
1790       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1791       DrawMainMenu();
1792     }
1793     else if (is_string_suffix(cheat_input, ":reload-music") ||
1794              is_string_suffix(cheat_input, ":rm"))
1795     {
1796       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1797       DrawMainMenu();
1798     }
1799     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1800              is_string_suffix(cheat_input, ":ra"))
1801     {
1802       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1803                           1 << ARTWORK_TYPE_SOUNDS |
1804                           1 << ARTWORK_TYPE_MUSIC);
1805       DrawMainMenu();
1806     }
1807     else if (is_string_suffix(cheat_input, ":dump-level") ||
1808              is_string_suffix(cheat_input, ":dl"))
1809     {
1810       DumpLevel(&level);
1811     }
1812     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1813              is_string_suffix(cheat_input, ":dt"))
1814     {
1815       DumpTape(&tape);
1816     }
1817     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1818              is_string_suffix(cheat_input, ":ft"))
1819     {
1820       /* fix single-player tapes that contain player input for more than one
1821          player (due to a bug in 3.3.1.2 and earlier versions), which results
1822          in playing levels with more than one player in multi-player mode,
1823          even though the tape was originally recorded in single-player mode */
1824
1825       // remove player input actions for all players but the first one
1826       for (i = 1; i < MAX_PLAYERS; i++)
1827         tape.player_participates[i] = FALSE;
1828
1829       tape.changed = TRUE;
1830     }
1831     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1832              is_string_suffix(cheat_input, ":snl"))
1833     {
1834       SaveNativeLevel(&level);
1835     }
1836     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1837              is_string_suffix(cheat_input, ":fps"))
1838     {
1839       global.show_frames_per_second = !global.show_frames_per_second;
1840     }
1841   }
1842   else if (game_status == GAME_MODE_PLAYING)
1843   {
1844 #ifdef DEBUG
1845     if (is_string_suffix(cheat_input, ".q"))
1846       DEBUG_SetMaximumDynamite();
1847 #endif
1848   }
1849   else if (game_status == GAME_MODE_EDITOR)
1850   {
1851     if (is_string_suffix(cheat_input, ":dump-brush") ||
1852         is_string_suffix(cheat_input, ":DB"))
1853     {
1854       DumpBrush();
1855     }
1856     else if (is_string_suffix(cheat_input, ":DDB"))
1857     {
1858       DumpBrush_Small();
1859     }
1860
1861     if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1862     {
1863       if (letter == 'x')        // copy brush to clipboard (small size)
1864       {
1865         CopyBrushToClipboard_Small();
1866       }
1867       else if (letter == 'c')   // copy brush to clipboard (normal size)
1868       {
1869         CopyBrushToClipboard();
1870       }
1871       else if (letter == 'v')   // paste brush from Clipboard
1872       {
1873         CopyClipboardToBrush();
1874       }
1875     }
1876   }
1877
1878   // special key shortcuts for all game modes
1879   if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1880       is_string_suffix(cheat_input, ":dea") ||
1881       is_string_suffix(cheat_input, ":DEA"))
1882   {
1883     DumpGadgetIdentifiers();
1884     DumpScreenIdentifiers();
1885   }
1886 }
1887
1888 boolean HandleKeysDebug(Key key, int key_status)
1889 {
1890 #ifdef DEBUG
1891   int i;
1892
1893   if (key_status != KEY_PRESSED)
1894     return FALSE;
1895
1896   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1897   {
1898     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1899
1900     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1901     {
1902       if (key == setup.debug.frame_delay_key[i] &&
1903           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1904       {
1905         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1906                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1907
1908         if (!setup.debug.frame_delay_game_only)
1909           MenuFrameDelay = GameFrameDelay;
1910
1911         SetVideoFrameDelay(GameFrameDelay);
1912
1913         if (GameFrameDelay > ONE_SECOND_DELAY)
1914           Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1915         else if (GameFrameDelay != 0)
1916           Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1917                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1918                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1919         else
1920           Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1921
1922         return TRUE;
1923       }
1924     }
1925   }
1926
1927   if (game_status == GAME_MODE_PLAYING)
1928   {
1929     if (key == KSYM_d)
1930     {
1931       options.debug = !options.debug;
1932
1933       Error(ERR_INFO, "debug mode %s",
1934             (options.debug ? "enabled" : "disabled"));
1935
1936       return TRUE;
1937     }
1938     else if (key == KSYM_v)
1939     {
1940       Error(ERR_INFO, "currently using game engine version %d",
1941             game.engine_version);
1942
1943       return TRUE;
1944     }
1945   }
1946 #endif
1947
1948   return FALSE;
1949 }
1950
1951 void HandleKey(Key key, int key_status)
1952 {
1953   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1954   static boolean ignore_repeated_key = FALSE;
1955   static struct SetupKeyboardInfo ski;
1956   static struct SetupShortcutInfo ssi;
1957   static struct
1958   {
1959     Key *key_custom;
1960     Key *key_snap;
1961     Key key_default;
1962     byte action;
1963   } key_info[] =
1964   {
1965     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1966     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1967     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1968     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1969     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1970     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1971   };
1972   int joy = 0;
1973   int i;
1974
1975   if (HandleKeysDebug(key, key_status))
1976     return;             // do not handle already processed keys again
1977
1978   // map special keys (media keys / remote control buttons) to default keys
1979   if (key == KSYM_PlayPause)
1980     key = KSYM_space;
1981   else if (key == KSYM_Select)
1982     key = KSYM_Return;
1983
1984   HandleSpecialGameControllerKeys(key, key_status);
1985
1986   if (game_status == GAME_MODE_PLAYING)
1987   {
1988     // only needed for single-step tape recording mode
1989     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1990     int pnr;
1991
1992     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1993     {
1994       byte key_action = 0;
1995       byte key_snap_action = 0;
1996
1997       if (setup.input[pnr].use_joystick)
1998         continue;
1999
2000       ski = setup.input[pnr].key;
2001
2002       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2003         if (key == *key_info[i].key_custom)
2004           key_action |= key_info[i].action;
2005
2006       // use combined snap+direction keys for the first player only
2007       if (pnr == 0)
2008       {
2009         ssi = setup.shortcut;
2010
2011         // also remember normal snap key when handling snap+direction keys
2012         key_snap_action |= key_action & JOY_BUTTON_SNAP;
2013
2014         for (i = 0; i < NUM_DIRECTIONS; i++)
2015         {
2016           if (key == *key_info[i].key_snap)
2017           {
2018             key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
2019             key_snap_action |= key_info[i].action;
2020           }
2021         }
2022       }
2023
2024       if (key_status == KEY_PRESSED)
2025       {
2026         stored_player[pnr].action      |= key_action;
2027         stored_player[pnr].snap_action |= key_snap_action;
2028       }
2029       else
2030       {
2031         stored_player[pnr].action      &= ~key_action;
2032         stored_player[pnr].snap_action &= ~key_snap_action;
2033       }
2034
2035       // restore snap action if one of several pressed snap keys was released
2036       if (stored_player[pnr].snap_action)
2037         stored_player[pnr].action |= JOY_BUTTON_SNAP;
2038
2039       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2040       {
2041         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2042         {
2043           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2044
2045           // if snap key already pressed, keep pause mode when releasing
2046           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2047             has_snapped[pnr] = TRUE;
2048         }
2049         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2050         {
2051           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2052
2053           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2054               getRedDiskReleaseFlag_SP() == 0)
2055           {
2056             // add a single inactive frame before dropping starts
2057             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2058             stored_player[pnr].force_dropping = TRUE;
2059           }
2060         }
2061         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2062         {
2063           // if snap key was pressed without direction, leave pause mode
2064           if (!has_snapped[pnr])
2065             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2066
2067           has_snapped[pnr] = FALSE;
2068         }
2069       }
2070       else if (tape.recording && tape.pausing && !tape.use_mouse)
2071       {
2072         // prevent key release events from un-pausing a paused game
2073         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2074           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2075       }
2076
2077       // for MM style levels, handle in-game keyboard input in HandleJoystick()
2078       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2079         joy |= key_action;
2080     }
2081   }
2082   else
2083   {
2084     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2085       if (key == key_info[i].key_default)
2086         joy |= key_info[i].action;
2087   }
2088
2089   if (joy)
2090   {
2091     if (key_status == KEY_PRESSED)
2092       key_joystick_mapping |= joy;
2093     else
2094       key_joystick_mapping &= ~joy;
2095
2096     HandleJoystick();
2097   }
2098
2099   if (game_status != GAME_MODE_PLAYING)
2100     key_joystick_mapping = 0;
2101
2102   if (key_status == KEY_RELEASED)
2103   {
2104     // reset flag to ignore repeated "key pressed" events after key release
2105     ignore_repeated_key = FALSE;
2106
2107     return;
2108   }
2109
2110   if ((key == KSYM_F11 ||
2111        ((key == KSYM_Return ||
2112          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2113       video.fullscreen_available &&
2114       !ignore_repeated_key)
2115   {
2116     setup.fullscreen = !setup.fullscreen;
2117
2118     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2119
2120     if (game_status == GAME_MODE_SETUP)
2121       RedrawSetupScreenAfterFullscreenToggle();
2122
2123     // set flag to ignore repeated "key pressed" events
2124     ignore_repeated_key = TRUE;
2125
2126     return;
2127   }
2128
2129   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2130        key == KSYM_minus || key == KSYM_KP_Subtract ||
2131        key == KSYM_plus  || key == KSYM_KP_Add ||
2132        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2133       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2134       video.window_scaling_available &&
2135       !video.fullscreen_enabled)
2136   {
2137     if (key == KSYM_0 || key == KSYM_KP_0)
2138       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2139     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2140       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2141     else
2142       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2143
2144     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2145       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2146     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2147       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2148
2149     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2150
2151     if (game_status == GAME_MODE_SETUP)
2152       RedrawSetupScreenAfterFullscreenToggle();
2153
2154     return;
2155   }
2156
2157   // some key events are handled like clicks for global animations
2158   boolean click = (key == KSYM_space ||
2159                    key == KSYM_Return ||
2160                    key == KSYM_Escape);
2161
2162   if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2163   {
2164     // do not handle this key event anymore
2165     if (key != KSYM_Escape)     // always allow ESC key to be handled
2166       return;
2167   }
2168
2169   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2170       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2171   {
2172     GameEnd();
2173
2174     return;
2175   }
2176
2177   if (game_status == GAME_MODE_MAIN &&
2178       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2179   {
2180     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2181
2182     return;
2183   }
2184
2185   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2186   {
2187     if (key == setup.shortcut.save_game)
2188       TapeQuickSave();
2189     else if (key == setup.shortcut.load_game)
2190       TapeQuickLoad();
2191     else if (key == setup.shortcut.toggle_pause)
2192       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2193
2194     HandleTapeButtonKeys(key);
2195     HandleSoundButtonKeys(key);
2196   }
2197
2198   if (game_status == GAME_MODE_PLAYING && !network_playing)
2199   {
2200     int centered_player_nr_next = -999;
2201
2202     if (key == setup.shortcut.focus_player_all)
2203       centered_player_nr_next = -1;
2204     else
2205       for (i = 0; i < MAX_PLAYERS; i++)
2206         if (key == setup.shortcut.focus_player[i])
2207           centered_player_nr_next = i;
2208
2209     if (centered_player_nr_next != -999)
2210     {
2211       game.centered_player_nr_next = centered_player_nr_next;
2212       game.set_centered_player = TRUE;
2213
2214       if (tape.recording)
2215       {
2216         tape.centered_player_nr_next = game.centered_player_nr_next;
2217         tape.set_centered_player = TRUE;
2218       }
2219     }
2220   }
2221
2222   HandleKeysSpecial(key);
2223
2224   if (HandleGadgetsKeyInput(key))
2225     return;             // do not handle already processed keys again
2226
2227   switch (game_status)
2228   {
2229     case GAME_MODE_PSEUDO_TYPENAME:
2230       HandleTypeName(0, key);
2231       break;
2232
2233     case GAME_MODE_TITLE:
2234     case GAME_MODE_MAIN:
2235     case GAME_MODE_LEVELS:
2236     case GAME_MODE_LEVELNR:
2237     case GAME_MODE_SETUP:
2238     case GAME_MODE_INFO:
2239     case GAME_MODE_SCORES:
2240
2241       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2242         break;
2243
2244       switch (key)
2245       {
2246         case KSYM_space:
2247         case KSYM_Return:
2248           if (game_status == GAME_MODE_TITLE)
2249             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2250           else if (game_status == GAME_MODE_MAIN)
2251             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2252           else if (game_status == GAME_MODE_LEVELS)
2253             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2254           else if (game_status == GAME_MODE_LEVELNR)
2255             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2256           else if (game_status == GAME_MODE_SETUP)
2257             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2258           else if (game_status == GAME_MODE_INFO)
2259             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2260           else if (game_status == GAME_MODE_SCORES)
2261             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2262           break;
2263
2264         case KSYM_Escape:
2265           if (game_status != GAME_MODE_MAIN)
2266             FadeSkipNextFadeIn();
2267
2268           if (game_status == GAME_MODE_TITLE)
2269             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2270           else if (game_status == GAME_MODE_LEVELS)
2271             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2272           else if (game_status == GAME_MODE_LEVELNR)
2273             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2274           else if (game_status == GAME_MODE_SETUP)
2275             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2276           else if (game_status == GAME_MODE_INFO)
2277             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2278           else if (game_status == GAME_MODE_SCORES)
2279             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2280           break;
2281
2282         case KSYM_Page_Up:
2283           if (game_status == GAME_MODE_LEVELS)
2284             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2285           else if (game_status == GAME_MODE_LEVELNR)
2286             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2287           else if (game_status == GAME_MODE_SETUP)
2288             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2289           else if (game_status == GAME_MODE_INFO)
2290             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2291           else if (game_status == GAME_MODE_SCORES)
2292             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2293           break;
2294
2295         case KSYM_Page_Down:
2296           if (game_status == GAME_MODE_LEVELS)
2297             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2298           else if (game_status == GAME_MODE_LEVELNR)
2299             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2300           else if (game_status == GAME_MODE_SETUP)
2301             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2302           else if (game_status == GAME_MODE_INFO)
2303             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2304           else if (game_status == GAME_MODE_SCORES)
2305             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2306           break;
2307
2308         default:
2309           break;
2310       }
2311       break;
2312
2313     case GAME_MODE_EDITOR:
2314       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2315         HandleLevelEditorKeyInput(key);
2316       break;
2317
2318     case GAME_MODE_PLAYING:
2319     {
2320       switch (key)
2321       {
2322         case KSYM_Escape:
2323           RequestQuitGame(setup.ask_on_escape);
2324           break;
2325
2326         default:
2327           break;
2328       }
2329       break;
2330     }
2331
2332     default:
2333       if (key == KSYM_Escape)
2334       {
2335         SetGameStatus(GAME_MODE_MAIN);
2336
2337         DrawMainMenu();
2338
2339         return;
2340       }
2341   }
2342 }
2343
2344 void HandleNoEvent(void)
2345 {
2346   HandleMouseCursor();
2347
2348   switch (game_status)
2349   {
2350     case GAME_MODE_PLAYING:
2351       HandleButtonOrFinger(-1, -1, -1);
2352       break;
2353   }
2354 }
2355
2356 void HandleEventActions(void)
2357 {
2358   // if (button_status && game_status != GAME_MODE_PLAYING)
2359   if (button_status && (game_status != GAME_MODE_PLAYING ||
2360                         tape.pausing ||
2361                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2362   {
2363     HandleButton(0, 0, button_status, -button_status);
2364   }
2365   else
2366   {
2367     HandleJoystick();
2368   }
2369
2370   if (network.enabled)
2371     HandleNetworking();
2372
2373   switch (game_status)
2374   {
2375     case GAME_MODE_MAIN:
2376       DrawPreviewLevelAnimation();
2377       break;
2378
2379     case GAME_MODE_EDITOR:
2380       HandleLevelEditorIdle();
2381       break;
2382
2383     default:
2384       break;
2385   }
2386 }
2387
2388 static void HandleTileCursor(int dx, int dy, int button)
2389 {
2390   if (!dx || !button)
2391     ClearPlayerMouseAction();
2392
2393   if (!dx && !dy)
2394     return;
2395
2396   if (button)
2397   {
2398     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2399                          (dx < 0 ? MB_LEFTBUTTON :
2400                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2401   }
2402   else if (!tile_cursor.moving)
2403   {
2404     int old_xpos = tile_cursor.xpos;
2405     int old_ypos = tile_cursor.ypos;
2406     int new_xpos = old_xpos;
2407     int new_ypos = old_ypos;
2408
2409     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2410       new_xpos = old_xpos + dx;
2411
2412     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2413       new_ypos = old_ypos + dy;
2414
2415     SetTileCursorTargetXY(new_xpos, new_ypos);
2416   }
2417 }
2418
2419 static int HandleJoystickForAllPlayers(void)
2420 {
2421   int i;
2422   int result = 0;
2423   boolean no_joysticks_configured = TRUE;
2424   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2425   static byte joy_action_last[MAX_PLAYERS];
2426
2427   for (i = 0; i < MAX_PLAYERS; i++)
2428     if (setup.input[i].use_joystick)
2429       no_joysticks_configured = FALSE;
2430
2431   // if no joysticks configured, map connected joysticks to players
2432   if (no_joysticks_configured)
2433     use_as_joystick_nr = TRUE;
2434
2435   for (i = 0; i < MAX_PLAYERS; i++)
2436   {
2437     byte joy_action = 0;
2438
2439     joy_action = JoystickExt(i, use_as_joystick_nr);
2440     result |= joy_action;
2441
2442     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2443         joy_action != joy_action_last[i])
2444       stored_player[i].action = joy_action;
2445
2446     joy_action_last[i] = joy_action;
2447   }
2448
2449   return result;
2450 }
2451
2452 void HandleJoystick(void)
2453 {
2454   static unsigned int joytest_delay = 0;
2455   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2456   static int joytest_last = 0;
2457   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2458   int delay_value       = GADGET_FRAME_DELAY;
2459   int joystick  = HandleJoystickForAllPlayers();
2460   int keyboard  = key_joystick_mapping;
2461   int joy       = (joystick | keyboard);
2462   int joytest   = joystick;
2463   int left      = joy & JOY_LEFT;
2464   int right     = joy & JOY_RIGHT;
2465   int up        = joy & JOY_UP;
2466   int down      = joy & JOY_DOWN;
2467   int button    = joy & JOY_BUTTON;
2468   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2469   int dx        = (left ? -1    : right ? 1     : 0);
2470   int dy        = (up   ? -1    : down  ? 1     : 0);
2471   boolean use_delay_value_first = (joytest != joytest_last);
2472
2473   if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2474   {
2475     // do not handle this button event anymore
2476     return;
2477   }
2478
2479   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2480                     anyTextGadgetActive()))
2481   {
2482     // leave name input in main menu or text input gadget
2483     HandleKey(KSYM_Escape, KEY_PRESSED);
2484     HandleKey(KSYM_Escape, KEY_RELEASED);
2485
2486     return;
2487   }
2488
2489   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2490   {
2491     if (game_status == GAME_MODE_PLAYING)
2492     {
2493       // when playing MM style levels, also use delay for keyboard events
2494       joytest |= keyboard;
2495
2496       // only use first delay value for new events, but not for changed events
2497       use_delay_value_first = (!joytest != !joytest_last);
2498
2499       // only use delay after the initial keyboard event
2500       delay_value = 0;
2501     }
2502
2503     // for any joystick or keyboard event, enable playfield tile cursor
2504     if (dx || dy || button)
2505       SetTileCursorEnabled(TRUE);
2506   }
2507
2508   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2509   {
2510     // delay joystick/keyboard actions if axes/keys continually pressed
2511     newbutton = dx = dy = 0;
2512   }
2513   else
2514   {
2515     // first start with longer delay, then continue with shorter delay
2516     joytest_delay_value =
2517       (use_delay_value_first ? delay_value_first : delay_value);
2518   }
2519
2520   joytest_last = joytest;
2521
2522   switch (game_status)
2523   {
2524     case GAME_MODE_TITLE:
2525     case GAME_MODE_MAIN:
2526     case GAME_MODE_LEVELS:
2527     case GAME_MODE_LEVELNR:
2528     case GAME_MODE_SETUP:
2529     case GAME_MODE_INFO:
2530     case GAME_MODE_SCORES:
2531     {
2532       if (anyTextGadgetActive())
2533         break;
2534
2535       if (game_status == GAME_MODE_TITLE)
2536         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2537       else if (game_status == GAME_MODE_MAIN)
2538         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2539       else if (game_status == GAME_MODE_LEVELS)
2540         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2541       else if (game_status == GAME_MODE_LEVELNR)
2542         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2543       else if (game_status == GAME_MODE_SETUP)
2544         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2545       else if (game_status == GAME_MODE_INFO)
2546         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2547       else if (game_status == GAME_MODE_SCORES)
2548         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2549
2550       break;
2551     }
2552
2553     case GAME_MODE_PLAYING:
2554 #if 0
2555       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2556       if (tape.playing || keyboard)
2557         newbutton = ((joy & JOY_BUTTON) != 0);
2558 #endif
2559
2560       if (newbutton && game.all_players_gone)
2561       {
2562         GameEnd();
2563
2564         return;
2565       }
2566
2567       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2568       {
2569         if (joystick & JOY_ACTION)
2570           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2571       }
2572       else if (tape.recording && tape.pausing && !tape.use_mouse)
2573       {
2574         if (joystick & JOY_ACTION)
2575           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2576       }
2577
2578       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2579         HandleTileCursor(dx, dy, button);
2580
2581       break;
2582
2583     default:
2584       break;
2585   }
2586 }
2587
2588 void HandleSpecialGameControllerButtons(Event *event)
2589 {
2590   int key_status;
2591   Key key;
2592
2593   switch (event->type)
2594   {
2595     case SDL_CONTROLLERBUTTONDOWN:
2596       key_status = KEY_PRESSED;
2597       break;
2598
2599     case SDL_CONTROLLERBUTTONUP:
2600       key_status = KEY_RELEASED;
2601       break;
2602
2603     default:
2604       return;
2605   }
2606
2607   switch (event->cbutton.button)
2608   {
2609     case SDL_CONTROLLER_BUTTON_START:
2610       key = KSYM_space;
2611       break;
2612
2613     case SDL_CONTROLLER_BUTTON_BACK:
2614       key = KSYM_Escape;
2615       break;
2616
2617     default:
2618       return;
2619   }
2620
2621   HandleKey(key, key_status);
2622 }
2623
2624 void HandleSpecialGameControllerKeys(Key key, int key_status)
2625 {
2626 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2627   int button = SDL_CONTROLLER_BUTTON_INVALID;
2628
2629   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2630   if (key == KSYM_Rewind)
2631     button = SDL_CONTROLLER_BUTTON_A;
2632   else if (key == KSYM_FastForward || key == KSYM_Menu)
2633     button = SDL_CONTROLLER_BUTTON_B;
2634
2635   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2636   {
2637     Event event;
2638
2639     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2640                   SDL_CONTROLLERBUTTONUP);
2641
2642     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2643     event.cbutton.button = button;
2644     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2645                            SDL_RELEASED);
2646
2647     HandleJoystickEvent(&event);
2648   }
2649 #endif
2650 }
2651
2652 boolean DoKeysymAction(int keysym)
2653 {
2654   if (keysym < 0)
2655   {
2656     Key key = (Key)(-keysym);
2657
2658     HandleKey(key, KEY_PRESSED);
2659     HandleKey(key, KEY_RELEASED);
2660
2661     return TRUE;
2662   }
2663
2664   return FALSE;
2665 }