Fix Windows mouse enter/leave event generation
authorMiikka Heikkinen <miikka.heikkinen@digia.com>
Mon, 1 Oct 2012 14:09:24 +0000 (17:09 +0300)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Thu, 4 Oct 2012 12:24:44 +0000 (14:24 +0200)
QWidget::underMouse() did not report correct widget in cases where
mouse was grabbed by popup, which was especially disruptive in case
of QCompleter popup, as that wouldn't close anymore with off-popup
clicks.

Root problem was that mouse capture in Windows caused enter/leave
events for QWindows to be generated incorrectly.
QPlatformWindow documentation specifies that enter/leave events
should be sent independent of explicit mouse grabs and only automatic
mouse grabbing done when button is pressed should suppress
enter/leave events. Updated Windows mouse handling to conform to
this.

Task-number: QTBUG-27283
Change-Id: Iecf786a702f7d29e6026c42ff8ec4c9cbf1b6ac3
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
src/plugins/platforms/windows/qwindowsmousehandler.cpp
src/plugins/platforms/windows/qwindowsmousehandler.h

index 5217232..55cf3a3 100644 (file)
@@ -44,6 +44,7 @@
 #include "qwindowscontext.h"
 #include "qwindowswindow.h"
 #include "qwindowsintegration.h"
+#include "qwindowsscreen.h"
 
 #include <qpa/qwindowsysteminterface.h>
 #include <QtGui/QGuiApplication>
@@ -127,6 +128,7 @@ static inline void compressMouseMove(MSG *msg)
 
 QWindowsMouseHandler::QWindowsMouseHandler() :
     m_windowUnderMouse(0),
+    m_trackedWindow(0),
     m_touchDevice(0)
 {
 }
@@ -162,20 +164,32 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
         return false; // Allow further event processing (dragging of windows).
     }
 
+
     *result = 0;
     if (msg.message == WM_MOUSELEAVE) {
-        // When moving out of a child, MouseMove within parent is received first
-        // (see below)
         if (QWindowsContext::verboseEvents)
-            qDebug() << "WM_MOUSELEAVE for " << window << " current= " << m_windowUnderMouse;
-        if (window == m_windowUnderMouse) {
-            QWindowSystemInterface::handleLeaveEvent(window);
+            qDebug() << "WM_MOUSELEAVE for " << window << " previous window under mouse = " << m_windowUnderMouse << " tracked window =" << m_trackedWindow;
+
+        // When moving out of a window, WM_MOUSEMOVE within the moved-to window is received first,
+        // so if m_trackedWindow is not the window here, it means the cursor has left the
+        // application.
+        if (window == m_trackedWindow) {
+            QWindow *leaveTarget = m_windowUnderMouse ? m_windowUnderMouse : m_trackedWindow;
+            if (QWindowsContext::verboseEvents)
+                qDebug() << "Generating leave event for " << leaveTarget;
+            QWindowSystemInterface::handleLeaveEvent(leaveTarget);
+            m_trackedWindow = 0;
             m_windowUnderMouse = 0;
         }
         return true;
     }
-    compressMouseMove(&msg);
+
     QWindowsWindow *platformWindow = static_cast<QWindowsWindow *>(window->handle());
+    const QPoint globalPosition = QWindowsGeometryHint::mapToGlobal(hwnd, winEventPosition);
+    QWindow *currentWindowUnderMouse = platformWindow->hasMouseCapture() ?
+        QWindowsScreen::windowAt(globalPosition) : window;
+
+    compressMouseMove(&msg);
     // Qt expects the platform plugin to capture the mouse on
     // any button press until release.
     if (!platformWindow->hasMouseCapture()
@@ -202,22 +216,13 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
             return true;
         }
     }
-    // Enter new window: track to generate leave event.
-    if (m_windowUnderMouse != window) {
-        // The tracking on m_windowUnderMouse might still be active and
-        // trigger later on.
-        if (m_windowUnderMouse) {
-            if (QWindowsContext::verboseEvents)
-                qDebug() << "Synthetic leave for " << m_windowUnderMouse;
-            QWindowSystemInterface::handleLeaveEvent(m_windowUnderMouse);
-        }
-        m_windowUnderMouse = window;
-        if (QWindowsContext::verboseEvents)
-            qDebug() << "Entering " << window;
-        QWindowsWindow::baseWindowOf(window)->applyCursor();
-//#ifndef Q_OS_WINCE
-        QWindowSystemInterface::handleEnterEvent(window);
+
 #ifndef Q_OS_WINCE
+    // Enter new window: track to generate leave event.
+    // If there is an active capture, we must track the actual capture window instead of window
+    // under cursor or leaves will trigger constantly, so always track the window we got
+    // native mouse event for.
+    if (window != m_trackedWindow) {
         TRACKMOUSEEVENT tme;
         tme.cbSize = sizeof(TRACKMOUSEEVENT);
         tme.dwFlags = TME_LEAVE;
@@ -225,11 +230,40 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
         tme.dwHoverTime = HOVER_DEFAULT; //
         if (!TrackMouseEvent(&tme))
             qWarning("TrackMouseEvent failed.");
+        m_trackedWindow =  window;
+    }
 #endif // !Q_OS_WINCE
+
+    // Qt expects enter/leave events for windows even when some window is capturing mouse input,
+    // except for automatic capture when mouse button is pressed - in that case enter/leave
+    // should be sent only after the last button is released.
+    // We need to track m_windowUnderMouse separately from m_trackedWindow, as
+    // Windows mouse tracking will not trigger WM_MOUSELEAVE for leaving window when
+    // mouse capture is set.
+    if (!platformWindow->hasMouseCapture()
+        || !platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)) {
+        if (m_windowUnderMouse != currentWindowUnderMouse) {
+            if (m_windowUnderMouse) {
+                if (QWindowsContext::verboseEvents)
+                    qDebug() << "Synthetic leave for " << m_windowUnderMouse;
+                QWindowSystemInterface::handleLeaveEvent(m_windowUnderMouse);
+                // Clear tracking if we are no longer over application,
+                // since we have already sent the leave.
+                if (!currentWindowUnderMouse)
+                    m_trackedWindow = 0;
+            }
+
+            if (currentWindowUnderMouse) {
+                if (QWindowsContext::verboseEvents)
+                    qDebug() << "Entering " << currentWindowUnderMouse;
+                QWindowsWindow::baseWindowOf(currentWindowUnderMouse)->applyCursor();
+                QWindowSystemInterface::handleEnterEvent(currentWindowUnderMouse);
+            }
+        }
+        m_windowUnderMouse = currentWindowUnderMouse;
     }
-    const QPoint clientPosition = winEventPosition;
-    QWindowSystemInterface::handleMouseEvent(window, clientPosition,
-                                             QWindowsGeometryHint::mapToGlobal(hwnd, clientPosition),
+
+    QWindowSystemInterface::handleMouseEvent(window, winEventPosition, globalPosition,
                                              keyStateToMouseButtons((int)msg.wParam),
                                              QWindowsKeyMapper::queryKeyboardModifiers());
     return true;
index 9524c26..1b19b34 100644 (file)
@@ -78,6 +78,7 @@ private:
                                          MSG msg, LRESULT *result);
 
     QPointer<QWindow> m_windowUnderMouse;
+    QPointer<QWindow> m_trackedWindow;
     QHash<DWORD, int> m_touchInputIDToTouchPointID;
     QTouchDevice *m_touchDevice;
 };