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