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