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