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