added optional button to restart game (door, panel and touch variants)
[rocksndiamonds.git] / src / events.c
index c18a8a468a3b24ac32d7c21e7b7ecd7573eb1a98..2e3c7ca9bbd232639c4d62e34153f2cd1ed2b0ea 100644 (file)
 
 static boolean cursor_inside_playfield = FALSE;
 static int cursor_mode_last = CURSOR_DEFAULT;
-static unsigned int special_cursor_delay = 0;
-static unsigned int special_cursor_delay_value = 1000;
+static DelayCounter special_cursor_delay = { 1000 };
+static boolean special_cursor_enabled = FALSE;
 
 static boolean stop_processing_events = FALSE;
+static boolean is_global_anim_event = FALSE;
 
 
 // forward declarations for internal use
+static void ClearTouchInfo(void);
 static void HandleNoEvent(void);
 static void HandleEventActions(void);
 
 
+void SetPlayfieldMouseCursorEnabled(boolean enabled)
+{
+  special_cursor_enabled = enabled;
+}
+
 // event filter to set mouse x/y position (for pointer class global animations)
 // (this is especially required to ensure smooth global animation mouse pointer
 // movement when the screen is updated without handling events; this can happen
@@ -107,7 +114,7 @@ static int FilterEvents(const Event *event)
     {
       SetMouseCursor(CURSOR_DEFAULT);
 
-      DelayReached(&special_cursor_delay, 0);
+      ResetDelayCounter(&special_cursor_delay);
 
       cursor_mode_last = CURSOR_DEFAULT;
     }
@@ -204,8 +211,7 @@ void StopProcessingEvents(void)
 static void HandleEvents(void)
 {
   Event event;
-  unsigned int event_frame_delay = 0;
-  unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
+  DelayCounter event_frame_delay = { GAME_FRAME_DELAY };
 
   ResetDelayCounter(&event_frame_delay);
 
@@ -213,6 +219,8 @@ static void HandleEvents(void)
 
   while (NextValidEvent(&event))
   {
+    int game_status_last = game_status;
+
     switch (event.type)
     {
       case EVENT_BUTTONPRESS:
@@ -263,8 +271,12 @@ static void HandleEvents(void)
        break;
     }
 
+    // always handle events within delay period if game status has changed
+    if (game_status != game_status_last)
+      ResetDelayCounter(&event_frame_delay);
+
     // do not handle events for longer than standard frame delay period
-    if (DelayReached(&event_frame_delay, event_frame_delay_value))
+    if (DelayReached(&event_frame_delay))
       break;
 
     // do not handle any further events if triggered by a special flag
@@ -317,7 +329,7 @@ static void HandleMouseCursor(void)
     // when showing title screens, hide mouse pointer (if not moved)
 
     if (gfx.cursor_mode != CURSOR_NONE &&
-       DelayReached(&special_cursor_delay, special_cursor_delay_value))
+       DelayReached(&special_cursor_delay))
     {
       SetMouseCursor(CURSOR_NONE);
     }
@@ -329,15 +341,14 @@ static void HandleMouseCursor(void)
 
     // display normal pointer if mouse pressed
     if (button_status != MB_RELEASED)
-      DelayReached(&special_cursor_delay, 0);
+      ResetDelayCounter(&special_cursor_delay);
 
     if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
        cursor_inside_playfield &&
-       DelayReached(&special_cursor_delay, special_cursor_delay_value))
+       special_cursor_enabled &&
+       DelayReached(&special_cursor_delay))
     {
-      if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
-         tile_cursor.enabled)
-       SetMouseCursor(CURSOR_PLAYFIELD);
+      SetMouseCursor(CURSOR_PLAYFIELD);
     }
   }
   else if (gfx.cursor_mode != CURSOR_DEFAULT)
@@ -407,6 +418,7 @@ void ClearEventQueue(void)
        button_status = MB_RELEASED;
        break;
 
+      case EVENT_FINGERRELEASE:
       case EVENT_KEYRELEASE:
        ClearPlayerAction();
        break;
@@ -442,6 +454,10 @@ void ClearPlayerAction(void)
     stored_player[i].snap_action = 0;
   }
 
+  // simulate finger release events for still pressed virtual buttons
+  overlay.grid_button_action = JOY_NO_ACTION;
+
+  ClearTouchInfo();
   ClearJoystickState();
   ClearPlayerMouseAction();
 }
@@ -503,7 +519,7 @@ static boolean CheckVirtualButtonPressed(int mx, int my, int button)
 void HandleButtonEvent(ButtonEvent *event)
 {
 #if DEBUG_EVENTS_BUTTON
-  Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
+  Debug("event:button", "button %d %s, x/y %d/%d\n",
        event->button,
        event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
        event->x, event->y);
@@ -512,6 +528,10 @@ void HandleButtonEvent(ButtonEvent *event)
   // for any mouse button event, disable playfield tile cursor
   SetTileCursorEnabled(FALSE);
 
+  // for any mouse button event, disable playfield mouse cursor
+  if (cursor_inside_playfield)
+    SetPlayfieldMouseCursorEnabled(FALSE);
+
 #if defined(HAS_SCREEN_KEYBOARD)
   if (video.shifted_up)
     event->y += video.shifted_up_pos;
@@ -535,7 +555,7 @@ void HandleMotionEvent(MotionEvent *event)
   motion_status = TRUE;
 
 #if DEBUG_EVENTS_MOTION
-  Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
+  Debug("event:motion", "button %d moved, x/y %d/%d\n",
        button_status, event->x, event->y);
 #endif
 
@@ -548,11 +568,11 @@ void HandleWheelEvent(WheelEvent *event)
 
 #if DEBUG_EVENTS_WHEEL
 #if 1
-  Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
+  Debug("event:wheel", "mouse == %d, x/y == %d/%d\n",
        event->which, event->x, event->y);
 #else
   // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
-  Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
+  Debug("event:wheel", "mouse == %d, x/y == %d/%d, direction == %s\n",
        event->which, event->x, event->y,
        (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
         "SDL_MOUSEWHEEL_FLIPPED"));
@@ -564,7 +584,7 @@ void HandleWheelEvent(WheelEvent *event)
               event->y < 0 ? MB_WHEEL_DOWN :
               event->y > 0 ? MB_WHEEL_UP : 0);
 
-#if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
+#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_MAC)
   // accelerated mouse wheel available on Mac and Windows
   wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
 #else
@@ -601,9 +621,11 @@ void HandleWindowEvent(WindowEvent *event)
      subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
      subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
      subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
+     subtype == SDL_WINDOWEVENT_TAKE_FOCUS ? "SDL_WINDOWEVENT_TAKE_FOCUS" :
+     subtype == SDL_WINDOWEVENT_HIT_TEST ? "SDL_WINDOWEVENT_HIT_TEST" :
      "(UNKNOWN)");
 
-  Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
+  Debug("event:window", "name: '%s', data1: %ld, data2: %ld",
        event_name, event->data1, event->data2);
 #endif
 
@@ -667,19 +689,10 @@ void HandleWindowEvent(WindowEvent *event)
        // check if screen orientation has changed (should always be true here)
        if (nr != GRID_ACTIVE_NR())
        {
-         int x, y;
-
          if (game_status == GAME_MODE_SETUP)
            RedrawSetupScreenAfterScreenRotation(nr);
 
-         nr = GRID_ACTIVE_NR();
-
-         overlay.grid_xsize = setup.touch.grid_xsize[nr];
-         overlay.grid_ysize = setup.touch.grid_ysize[nr];
-
-         for (x = 0; x < MAX_GRID_XSIZE; x++)
-           for (y = 0; y < MAX_GRID_YSIZE; y++)
-             overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
+         SetOverlayGridSizeAndButtons();
        }
       }
     }
@@ -698,6 +711,24 @@ static struct
   byte action;
 } touch_info[NUM_TOUCH_FINGERS];
 
+static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
+                        Key key, byte action)
+{
+  touch_info[pos].touched = (action != JOY_NO_ACTION);
+  touch_info[pos].finger_id = finger_id;
+  touch_info[pos].counter = counter;
+  touch_info[pos].key = key;
+  touch_info[pos].action = action;
+}
+
+static void ClearTouchInfo(void)
+{
+  int i;
+
+  for (i = 0; i < NUM_TOUCH_FINGERS; i++)
+    SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
+}
+
 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
 {
   int x = event->x * overlay.grid_xsize;
@@ -714,7 +745,7 @@ static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
   // for any touch input event, enable overlay buttons (if activated)
   SetOverlayEnabled(TRUE);
 
-  Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
+  Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
        getKeyNameFromKey(key), key_status_name, event->fingerId);
 
   if (key_status == KEY_PRESSED)
@@ -728,7 +759,7 @@ static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
     if (touch_info[i].touched &&
        touch_info[i].finger_id == event->fingerId)
     {
-      // Error(ERR_DEBUG, "MARK 1: %d", i);
+      // Debug("event:finger", "MARK 1: %d", i);
 
       break;
     }
@@ -748,12 +779,12 @@ static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
          oldest_pos = i;
          oldest_counter = touch_info[i].counter;
 
-         // Error(ERR_DEBUG, "MARK 2: %d", i);
+         // Debug("event:finger", "MARK 2: %d", i);
        }
 
        if (!touch_info[i].touched)
        {
-         // Error(ERR_DEBUG, "MARK 3: %d", i);
+         // Debug("event:finger", "MARK 3: %d", i);
 
          break;
        }
@@ -764,7 +795,7 @@ static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
        // all slots allocated -- use oldest slot
        i = oldest_pos;
 
-       // Error(ERR_DEBUG, "MARK 4: %d", i);
+       // Debug("event:finger", "MARK 4: %d", i);
       }
     }
     else
@@ -775,7 +806,7 @@ static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
       {
        HandleKey(key, KEY_RELEASED);
 
-       Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
+       Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
              getKeyNameFromKey(key), "KEY_RELEASED", i);
       }
     }
@@ -794,7 +825,7 @@ static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
          // undraw previous grid button when moving finger away
          overlay.grid_button_action &= ~touch_info[i].action;
 
-         Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
+         Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
                getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
        }
 
@@ -802,16 +833,12 @@ static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
        {
          HandleKey(key, KEY_PRESSED);
 
-         Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
+         Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
                getKeyNameFromKey(key), "KEY_PRESSED", i);
        }
       }
 
-      touch_info[i].touched = TRUE;
-      touch_info[i].finger_id = event->fingerId;
-      touch_info[i].counter = Counter();
-      touch_info[i].key = key;
-      touch_info[i].action = grid_button_action;
+      SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
     }
     else
     {
@@ -819,15 +846,11 @@ static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
       {
        HandleKey(touch_info[i].key, KEY_RELEASED);
 
-       Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
+       Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
              getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
       }
 
-      touch_info[i].touched = FALSE;
-      touch_info[i].finger_id = 0;
-      touch_info[i].counter = 0;
-      touch_info[i].key = 0;
-      touch_info[i].action = JOY_NO_ACTION;
+      SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
     }
   }
 }
@@ -862,7 +885,7 @@ static void HandleFingerEvent_WipeGestures(FingerEvent *event)
       motion_key_x = KSYM_UNDEFINED;
       motion_key_y = KSYM_UNDEFINED;
 
-      Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
+      Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
     }
     else
     {
@@ -877,7 +900,7 @@ static void HandleFingerEvent_WipeGestures(FingerEvent *event)
 
       HandleKey(button_key, KEY_PRESSED);
 
-      Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
+      Debug("event:finger", "---------- SNAP STARTED ----------");
     }
   }
   else if (event->type == EVENT_FINGERRELEASE)
@@ -894,7 +917,7 @@ static void HandleFingerEvent_WipeGestures(FingerEvent *event)
       motion_key_x = KSYM_UNDEFINED;
       motion_key_y = KSYM_UNDEFINED;
 
-      Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
+      Debug("event:finger", "---------- MOVE STOPPED ----------");
     }
     else if (event->fingerId == button_id)
     {
@@ -905,7 +928,7 @@ static void HandleFingerEvent_WipeGestures(FingerEvent *event)
 
       button_key = KSYM_UNDEFINED;
 
-      Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
+      Debug("event:finger", "---------- SNAP STOPPED ----------");
     }
   }
   else if (event->type == EVENT_FINGERMOTION)
@@ -954,7 +977,7 @@ static void HandleFingerEvent_WipeGestures(FingerEvent *event)
        motion_key_x = new_motion_key_x;
        motion_key_y = new_motion_key_y;
 
-       Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
+       Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
       }
     }
     else if (event->fingerId == button_id)
@@ -975,7 +998,7 @@ static void HandleFingerEvent_WipeGestures(FingerEvent *event)
 
        HandleKey(button_key, KEY_PRESSED);
 
-       Error(ERR_DEBUG, "---------- DROP STARTED ----------");
+       Debug("event:finger", "---------- DROP STARTED ----------");
       }
     }
   }
@@ -984,7 +1007,7 @@ static void HandleFingerEvent_WipeGestures(FingerEvent *event)
 void HandleFingerEvent(FingerEvent *event)
 {
 #if DEBUG_EVENTS_FINGER
-  Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
+  Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
        event->type == EVENT_FINGERPRESS ? "pressed" :
        event->type == EVENT_FINGERRELEASE ? "released" : "moved",
        event->touchId,
@@ -1051,7 +1074,7 @@ static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
 
       ClearPlayerMouseAction();
 
-      Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
+      Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
     }
   }
   else if (button == MB_RELEASED && touched)
@@ -1065,7 +1088,7 @@ static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
     else
       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
 
-    Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
+    Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
   }
 
   if (touched)
@@ -1090,7 +1113,7 @@ static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
 
       last_button = button_nr;
 
-      Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
+      Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
     }
     else
     {
@@ -1098,7 +1121,7 @@ static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
 
       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
 
-      Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
+      Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
     }
   }
 }
@@ -1137,7 +1160,7 @@ static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
 
       ClearPlayerMouseAction();
 
-      Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
+      Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
     }
   }
   else if (button == MB_RELEASED && touched)
@@ -1151,7 +1174,7 @@ static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
     else
       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
 
-    Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
+    Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
   }
 
   if (touched)
@@ -1180,7 +1203,7 @@ static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
 
       tapped = FALSE;
 
-      Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
+      Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
     }
     else
     {
@@ -1188,7 +1211,7 @@ static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
 
       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
 
-      Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
+      Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
     }
   }
 }
@@ -1223,7 +1246,7 @@ static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
       motion_key_x = KSYM_UNDEFINED;
       motion_key_y = KSYM_UNDEFINED;
 
-      Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
+      Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
     }
   }
   else if (button == MB_RELEASED && touched)
@@ -1242,13 +1265,13 @@ static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
     {
       if (player_is_dropping)
       {
-       Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
+       Debug("event:finger", "---------- DROP STOPPED ----------");
 
        HandleKey(setup.input[0].key.drop, KEY_RELEASED);
       }
       else
       {
-       Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
+       Debug("event:finger", "---------- SNAP STOPPED ----------");
 
        HandleKey(setup.input[0].key.snap, KEY_RELEASED);
       }
@@ -1257,7 +1280,7 @@ static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
     motion_key_x = KSYM_UNDEFINED;
     motion_key_y = KSYM_UNDEFINED;
 
-    Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
+    Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
   }
 
   if (touched)
@@ -1301,13 +1324,13 @@ static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
 
       if (player_is_dropping)
       {
-       Error(ERR_DEBUG, "---------- DROP STARTED ----------");
+       Debug("event:finger", "---------- DROP STARTED ----------");
 
        HandleKey(setup.input[0].key.drop, KEY_PRESSED);
       }
       else
       {
-       Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
+       Debug("event:finger", "---------- SNAP STARTED ----------");
 
        HandleKey(setup.input[0].key.snap, KEY_PRESSED);
       }
@@ -1317,7 +1340,7 @@ static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
       if (player_is_dropping &&
          player_drop_count == getPlayerInventorySize(0))
       {
-       Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
+       Debug("event:finger", "---------- DROP -> SNAP ----------");
 
        HandleKey(setup.input[0].key.drop, KEY_RELEASED);
        HandleKey(setup.input[0].key.snap, KEY_PRESSED);
@@ -1328,7 +1351,7 @@ static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
 
     if (new_motion_key_x != motion_key_x)
     {
-      Error(ERR_DEBUG, "---------- %s %s ----------",
+      Debug("event:finger", "---------- %s %s ----------",
            started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
            dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
 
@@ -1340,7 +1363,7 @@ static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
 
     if (new_motion_key_y != motion_key_y)
     {
-      Error(ERR_DEBUG, "---------- %s %s ----------",
+      Debug("event:finger", "---------- %s %s ----------",
            started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
            dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
 
@@ -1386,6 +1409,10 @@ static boolean checkTextInputKey(Key key)
   if (game_status == GAME_MODE_PLAYING)
     return FALSE;
 
+  // if Shift or right Alt key is pressed, handle key as text input
+  if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
+    return TRUE;
+
   // ignore raw keys as text input when not in text input mode
   if (KSYM_RAW(key) && !textinput_status)
     return FALSE;
@@ -1400,7 +1427,7 @@ void HandleTextEvent(TextEvent *event)
   Key key = getKeyFromKeyName(text);
 
 #if DEBUG_EVENTS_TEXT
-  Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
+  Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
        text,
        strlen(text),
        text[0], (int)(text[0]),
@@ -1432,16 +1459,13 @@ void HandlePauseResumeEvent(PauseResumeEvent *event)
 void HandleKeyEvent(KeyEvent *event)
 {
   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
-  boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
-  Key key = GetEventKey(event, with_modifiers);
-  Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
+  Key key = GetEventKey(event);
 
 #if DEBUG_EVENTS_KEY
-  Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
+  Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
        event->type == EVENT_KEYPRESS ? "pressed" : "released",
        event->keysym.scancode,
        event->keysym.sym,
-       keymod,
        GetKeyModState(),
        key,
        getKeyNameFromKey(key));
@@ -1459,7 +1483,7 @@ void HandleKeyEvent(KeyEvent *event)
     if (key_status == KEY_PRESSED)
       SetOverlayEnabled(!GetOverlayEnabled());
   }
-  else
+  else if (!textinput_status)
   {
     // for any other "real" key event, disable virtual buttons
     SetOverlayEnabled(FALSE);
@@ -1469,7 +1493,7 @@ void HandleKeyEvent(KeyEvent *event)
   }
 #endif
 
-  HandleKeyModState(keymod, key_status);
+  HandleKeyModState(key, key_status);
 
   // process all keys if not in text input mode or if non-printable keys
   if (!checkTextInputKey(key))
@@ -1478,12 +1502,12 @@ void HandleKeyEvent(KeyEvent *event)
 
 static int HandleDropFileEvent(char *filename)
 {
-  Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
+  Debug("event:dropfile", "filename == '%s'", filename);
 
   // check and extract dropped zip files into correct user data directory
   if (!strSuffixLower(filename, ".zip"))
   {
-    Error(ERR_WARN, "file '%s' not supported", filename);
+    Warn("file '%s' not supported", filename);
 
     return TREE_TYPE_UNDEFINED;
   }
@@ -1494,7 +1518,7 @@ static int HandleDropFileEvent(char *filename)
 
   if (directory == NULL)
   {
-    Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
+    Warn("zip file '%s' has invalid content!", filename);
 
     return TREE_TYPE_UNDEFINED;
   }
@@ -1528,6 +1552,15 @@ static int HandleDropFileEvent(char *filename)
   // add extracted level or artwork set to tree info structure
   AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
 
+  // force restart after adding level collection
+  if (getTreeInfoFromIdentifier(TREE_FIRST_NODE(tree_type), top_dir) == NULL)
+  {
+    Request("Program must be restarted after adding a new level collection!",
+           REQ_CONFIRM);
+
+    CloseAllAndExit(0);
+  }
+
   // update menu screen (and possibly change current level set)
   DrawScreenAfterAddingSet(top_dir, tree_type);
 
@@ -1536,7 +1569,7 @@ static int HandleDropFileEvent(char *filename)
 
 static void HandleDropTextEvent(char *text)
 {
-  Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
+  Debug("event:droptext", "text == '%s'", text);
 }
 
 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
@@ -1674,6 +1707,7 @@ void HandleButton(int mx, int my, int button, int button_nr)
   static int old_mx = 0, old_my = 0;
   boolean button_hold = FALSE;
   boolean handle_gadgets = TRUE;
+  int game_status_last = game_status;
 
   if (button_nr < 0)
   {
@@ -1692,9 +1726,11 @@ void HandleButton(int mx, int my, int button, int button_nr)
   // when playing, only handle gadgets when using "follow finger" controls
   // or when using touch controls in combination with the MM game engine
   // or when using gadgets that do not overlap with virtual buttons
+  // or when touch controls are disabled (e.g., with mouse-only levels)
   handle_gadgets =
     (game_status != GAME_MODE_PLAYING ||
      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
+     strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF) ||
      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
      (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
       !CheckVirtualButtonPressed(mx, my, button)));
@@ -1716,8 +1752,12 @@ void HandleButton(int mx, int my, int button, int button_nr)
 
   if (handle_gadgets && HandleGadgets(mx, my, button))
   {
-    // do not handle this button event anymore
+    // do not handle this button event anymore with position on screen
     mx = my = -32;     // force mouse event to be outside screen tiles
+
+    // do not handle this button event anymore if game status has changed
+    if (game_status != game_status_last)
+      return;
   }
 
   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
@@ -1738,7 +1778,12 @@ void HandleButton(int mx, int my, int button, int button_nr)
       break;
 
     case GAME_MODE_PSEUDO_TYPENAME:
-      HandleTypeName(0, KSYM_Return);
+    case GAME_MODE_PSEUDO_TYPENAMES:
+      HandleTypeName(KSYM_Return);
+      break;
+
+    case GAME_MODE_NAMES:
+      HandleChoosePlayerName(mx, my, 0, 0, button);
       break;
 
     case GAME_MODE_LEVELS:
@@ -1750,7 +1795,11 @@ void HandleButton(int mx, int my, int button, int button_nr)
       break;
 
     case GAME_MODE_SCORES:
-      HandleHallOfFame(0, 0, 0, 0, button);
+      HandleHallOfFame(mx, my, 0, 0, button);
+      break;
+
+    case GAME_MODE_SCOREINFO:
+      HandleScoreInfo(mx, my, 0, 0, button);
       break;
 
     case GAME_MODE_EDITOR:
@@ -1808,7 +1857,7 @@ static void HandleKeysSpecial(Key key)
   cheat_input[cheat_input_len] = '\0';
 
 #if DEBUG_EVENTS_KEY
-  Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
+  Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
 #endif
 
   if (game_status == GAME_MODE_MAIN)
@@ -1859,6 +1908,11 @@ static void HandleKeysSpecial(Key key)
     {
       DumpTape(&tape);
     }
+    else if (strSuffix(cheat_input, ":undo-tape") ||
+            strSuffix(cheat_input, ":ut"))
+    {
+      UndoTape();
+    }
     else if (strSuffix(cheat_input, ":fix-tape") ||
             strSuffix(cheat_input, ":ft"))
     {
@@ -1874,6 +1928,10 @@ static void HandleKeysSpecial(Key key)
     {
       global.show_frames_per_second = !global.show_frames_per_second;
     }
+    else if (strSuffix(cheat_input, ":xsn"))
+    {
+      tile_cursor.xsn_debug = TRUE;
+    }
   }
   else if (game_status == GAME_MODE_PLAYING)
   {
@@ -1954,13 +2012,13 @@ boolean HandleKeysDebug(Key key, int key_status)
        SetVideoFrameDelay(GameFrameDelay);
 
        if (GameFrameDelay > ONE_SECOND_DELAY)
-         Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
+         Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
        else if (GameFrameDelay != 0)
-         Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
+         Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
                GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
                GAME_FRAME_DELAY * 100 / GameFrameDelay);
        else
-         Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
+         Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
 
        return TRUE;
       }
@@ -1973,14 +2031,14 @@ boolean HandleKeysDebug(Key key, int key_status)
     {
       options.debug = !options.debug;
 
-      Error(ERR_INFO, "debug mode %s",
+      Debug("event:key:debug", "debug mode %s",
            (options.debug ? "enabled" : "disabled"));
 
       return TRUE;
     }
     else if (key == KSYM_v)
     {
-      Error(ERR_INFO, "currently using game engine version %d",
+      Debug("event:key:debug", "currently using game engine version %d",
            game.engine_version);
 
       return TRUE;
@@ -2060,6 +2118,8 @@ void HandleKey(Key key, int key_status)
          {
            key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
            key_snap_action |= key_info[i].action;
+
+           tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
          }
        }
       }
@@ -2123,6 +2183,10 @@ void HandleKey(Key key, int key_status)
       // for MM style levels, handle in-game keyboard input in HandleJoystick()
       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
        joy |= key_action;
+
+      // for any keyboard event, enable playfield mouse cursor
+      if (key_action && key_status == KEY_PRESSED)
+       SetPlayfieldMouseCursorEnabled(TRUE);
     }
   }
   else
@@ -2150,6 +2214,10 @@ void HandleKey(Key key, int key_status)
     // reset flag to ignore repeated "key pressed" events after key release
     ignore_repeated_key = FALSE;
 
+    // send key release event to global animation event handling
+    if (!is_global_anim_event)
+      HandleGlobalAnimClicks(-1, -1, KEY_RELEASED, FALSE);
+
     return;
   }
 
@@ -2205,9 +2273,9 @@ void HandleKey(Key key, int key_status)
   }
 
   // some key events are handled like clicks for global animations
-  boolean click = (key == KSYM_space ||
-                  key == KSYM_Return ||
-                  key == KSYM_Escape);
+  boolean click = (!is_global_anim_event && (key == KSYM_space ||
+                                            key == KSYM_Return ||
+                                            key == KSYM_Escape));
 
   if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
   {
@@ -2232,12 +2300,26 @@ void HandleKey(Key key, int key_status)
     return;
   }
 
+  if (game_status == GAME_MODE_MAIN &&
+      (setup.internal.info_screens_from_main ||
+       leveldir_current->info_screens_from_main) &&
+      (key >= KSYM_KP_1 && key <= KSYM_KP_9))
+  {
+    DrawInfoScreen_FromMainMenu(key - KSYM_KP_1 + 1);
+
+    return;
+  }
+
   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
   {
     if (key == setup.shortcut.save_game)
       TapeQuickSave();
     else if (key == setup.shortcut.load_game)
       TapeQuickLoad();
+    else if (key == setup.shortcut.restart_game)
+      TapeRestartGame();
+    else if (key == setup.shortcut.pause_before_end)
+      TapeReplayAndPauseBeforeEnd();
     else if (key == setup.shortcut.toggle_pause)
       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
 
@@ -2245,6 +2327,11 @@ void HandleKey(Key key, int key_status)
     HandleSoundButtonKeys(key);
   }
 
+  if (game_status == GAME_MODE_SCOREINFO)
+  {
+    HandleScreenGadgetKeys(key);
+  }
+
   if (game_status == GAME_MODE_PLAYING && !network_playing)
   {
     int centered_player_nr_next = -999;
@@ -2274,19 +2361,30 @@ void HandleKey(Key key, int key_status)
   if (HandleGadgetsKeyInput(key))
     return;            // do not handle already processed keys again
 
+  // special case: on "space" key, either continue playing or go to main menu
+  if (game_status == GAME_MODE_SCORES && key == KSYM_space)
+  {
+    HandleHallOfFame(0, 0, 0, 0, MB_MENU_CONTINUE);
+
+    return;
+  }
+
   switch (game_status)
   {
     case GAME_MODE_PSEUDO_TYPENAME:
-      HandleTypeName(0, key);
+    case GAME_MODE_PSEUDO_TYPENAMES:
+      HandleTypeName(key);
       break;
 
     case GAME_MODE_TITLE:
     case GAME_MODE_MAIN:
+    case GAME_MODE_NAMES:
     case GAME_MODE_LEVELS:
     case GAME_MODE_LEVELNR:
     case GAME_MODE_SETUP:
     case GAME_MODE_INFO:
     case GAME_MODE_SCORES:
+    case GAME_MODE_SCOREINFO:
 
       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
        break;
@@ -2299,6 +2397,8 @@ void HandleKey(Key key, int key_status)
            HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
          else if (game_status == GAME_MODE_MAIN)
            HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
+          else if (game_status == GAME_MODE_NAMES)
+            HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
           else if (game_status == GAME_MODE_LEVELS)
             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
           else if (game_status == GAME_MODE_LEVELNR)
@@ -2309,6 +2409,8 @@ void HandleKey(Key key, int key_status)
            HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
          else if (game_status == GAME_MODE_SCORES)
            HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
+         else if (game_status == GAME_MODE_SCOREINFO)
+           HandleScoreInfo(0, 0, 0, 0, MB_MENU_CHOICE);
          break;
 
        case KSYM_Escape:
@@ -2317,6 +2419,8 @@ void HandleKey(Key key, int key_status)
 
          if (game_status == GAME_MODE_TITLE)
            HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
+          else if (game_status == GAME_MODE_NAMES)
+            HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
           else if (game_status == GAME_MODE_LEVELS)
             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
           else if (game_status == GAME_MODE_LEVELNR)
@@ -2327,10 +2431,14 @@ void HandleKey(Key key, int key_status)
            HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
          else if (game_status == GAME_MODE_SCORES)
            HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
+         else if (game_status == GAME_MODE_SCOREINFO)
+           HandleScoreInfo(0, 0, 0, 0, MB_MENU_LEAVE);
          break;
 
         case KSYM_Page_Up:
-          if (game_status == GAME_MODE_LEVELS)
+          if (game_status == GAME_MODE_NAMES)
+            HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
+          else if (game_status == GAME_MODE_LEVELS)
             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
           else if (game_status == GAME_MODE_LEVELNR)
             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
@@ -2340,10 +2448,14 @@ void HandleKey(Key key, int key_status)
            HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
          else if (game_status == GAME_MODE_SCORES)
            HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
+         else if (game_status == GAME_MODE_SCOREINFO)
+           HandleScoreInfo(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
          break;
 
         case KSYM_Page_Down:
-          if (game_status == GAME_MODE_LEVELS)
+          if (game_status == GAME_MODE_NAMES)
+            HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
+          else if (game_status == GAME_MODE_LEVELS)
             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
           else if (game_status == GAME_MODE_LEVELNR)
             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
@@ -2353,6 +2465,8 @@ void HandleKey(Key key, int key_status)
            HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
          else if (game_status == GAME_MODE_SCORES)
            HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
+         else if (game_status == GAME_MODE_SCOREINFO)
+           HandleScoreInfo(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
          break;
 
        default:
@@ -2370,7 +2484,7 @@ void HandleKey(Key key, int key_status)
       switch (key)
       {
         case KSYM_Escape:
-         RequestQuitGame(setup.ask_on_escape);
+         RequestQuitGame(TRUE);
          break;
 
        default:
@@ -2453,14 +2567,14 @@ static void HandleTileCursor(int dx, int dy, int button)
   {
     int old_xpos = tile_cursor.xpos;
     int old_ypos = tile_cursor.ypos;
-    int new_xpos = old_xpos;
-    int new_ypos = old_ypos;
+    int new_xpos = tile_cursor.xpos + dx;
+    int new_ypos = tile_cursor.ypos + dy;
 
-    if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
-      new_xpos = old_xpos + dx;
+    if (!IN_LEV_FIELD(new_xpos, old_ypos) || !IN_SCR_FIELD(new_xpos, old_ypos))
+      new_xpos = old_xpos;
 
-    if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
-      new_ypos = old_ypos + dy;
+    if (!IN_LEV_FIELD(old_xpos, new_ypos) || !IN_SCR_FIELD(old_xpos, new_ypos))
+      new_ypos = old_ypos;
 
     SetTileCursorTargetXY(new_xpos, new_ypos);
   }
@@ -2501,8 +2615,7 @@ static int HandleJoystickForAllPlayers(void)
 
 void HandleJoystick(void)
 {
-  static unsigned int joytest_delay = 0;
-  static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
+  static DelayCounter joytest_delay = { GADGET_FRAME_DELAY };
   static int joytest_last = 0;
   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
   int delay_value       = GADGET_FRAME_DELAY;
@@ -2515,18 +2628,22 @@ void HandleJoystick(void)
   int up       = joy & JOY_UP;
   int down     = joy & JOY_DOWN;
   int button   = joy & JOY_BUTTON;
-  int newbutton        = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
+  int anybutton = AnyJoystickButton();
+  int newbutton        = (anybutton == JOY_BUTTON_NEW_PRESSED);
   int dx       = (left ? -1    : right ? 1     : 0);
   int dy       = (up   ? -1    : down  ? 1     : 0);
   boolean use_delay_value_first = (joytest != joytest_last);
+  boolean new_button_event = (anybutton == JOY_BUTTON_NEW_PRESSED ||
+                             anybutton == JOY_BUTTON_NEW_RELEASED);
 
-  if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
+  if (new_button_event && HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
   {
     // do not handle this button event anymore
     return;
   }
 
   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
+                   game_status == GAME_MODE_PSEUDO_TYPENAMES ||
                    anyTextGadgetActive()))
   {
     // leave name input in main menu or text input gadget
@@ -2555,7 +2672,11 @@ void HandleJoystick(void)
       SetTileCursorEnabled(TRUE);
   }
 
-  if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
+  // for any joystick event, enable playfield mouse cursor
+  if (dx || dy || button)
+    SetPlayfieldMouseCursorEnabled(TRUE);
+
+  if (joytest && !button && !DelayReached(&joytest_delay))
   {
     // delay joystick/keyboard actions if axes/keys continually pressed
     newbutton = dx = dy = 0;
@@ -2563,7 +2684,7 @@ void HandleJoystick(void)
   else
   {
     // first start with longer delay, then continue with shorter delay
-    joytest_delay_value =
+    joytest_delay.value =
       (use_delay_value_first ? delay_value_first : delay_value);
   }
 
@@ -2573,11 +2694,13 @@ void HandleJoystick(void)
   {
     case GAME_MODE_TITLE:
     case GAME_MODE_MAIN:
+    case GAME_MODE_NAMES:
     case GAME_MODE_LEVELS:
     case GAME_MODE_LEVELNR:
     case GAME_MODE_SETUP:
     case GAME_MODE_INFO:
     case GAME_MODE_SCORES:
+    case GAME_MODE_SCOREINFO:
     {
       if (anyTextGadgetActive())
        break;
@@ -2586,6 +2709,8 @@ void HandleJoystick(void)
        HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
       else if (game_status == GAME_MODE_MAIN)
        HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
+      else if (game_status == GAME_MODE_NAMES)
+        HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
       else if (game_status == GAME_MODE_LEVELS)
         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
       else if (game_status == GAME_MODE_LEVELNR)
@@ -2596,6 +2721,8 @@ void HandleJoystick(void)
        HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
       else if (game_status == GAME_MODE_SCORES)
        HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
+      else if (game_status == GAME_MODE_SCOREINFO)
+       HandleScoreInfo(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
 
       break;
     }
@@ -2708,9 +2835,13 @@ boolean DoKeysymAction(int keysym)
   {
     Key key = (Key)(-keysym);
 
+    is_global_anim_event = TRUE;
+
     HandleKey(key, KEY_PRESSED);
     HandleKey(key, KEY_RELEASED);
 
+    is_global_anim_event = FALSE;
+
     return TRUE;
   }