diff options
author | Błażej Szczygieł <spaz16@wp.pl> | 2016-01-19 22:32:52 +0100 |
---|---|---|
committer | Błażej Szczygieł <spaz16@wp.pl> | 2016-02-24 09:03:14 +0000 |
commit | 7091be1b7999d93fe2126042161dcd1d8fd20026 (patch) | |
tree | cdfed192630f706bbfc2b7f9073c690b7df86b9e | |
parent | 63b5f5cb98fe19af5bf20d4f581723cb91a46dcb (diff) |
xcb: Deliver mouse enter event to window when closing modal window
When a modal window is closed and the mouse is not under the modal
window - find a proper window and send a fake enter event.
Added auto test for checking enter event on window when modal window
is closed.
Task-number: QTBUG-35109
Change-Id: I370b52d386503820ac9de21e6d05fd019ca456ec
Reviewed-by: Shawn Rutledge <shawn.rutledge@theqtcompany.com>
-rw-r--r-- | src/plugins/platforms/xcb/qxcbwindow.cpp | 48 | ||||
-rw-r--r-- | tests/auto/gui/kernel/qwindow/BLACKLIST | 2 | ||||
-rw-r--r-- | tests/auto/gui/kernel/qwindow/tst_qwindow.cpp | 193 |
3 files changed, 243 insertions, 0 deletions
diff --git a/src/plugins/platforms/xcb/qxcbwindow.cpp b/src/plugins/platforms/xcb/qxcbwindow.cpp index 46b7b70f80..7eae2d92ab 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.cpp +++ b/src/plugins/platforms/xcb/qxcbwindow.cpp @@ -50,6 +50,7 @@ #include "qxcbsystemtraytracker.h" #include <qpa/qplatformintegration.h> +#include <qpa/qplatformcursor.h> #include <algorithm> @@ -261,6 +262,26 @@ static inline XTextProperty* qstringToXTP(Display *dpy, const QString& s) } #endif // XCB_USE_XLIB +// TODO move this into a utility function in QWindow or QGuiApplication +static QWindow *childWindowAt(QWindow *win, const QPoint &p) +{ + foreach (QObject *obj, win->children()) { + if (obj->isWindowType()) { + QWindow *childWin = static_cast<QWindow *>(obj); + if (childWin->isVisible()) { + if (QWindow *recurse = childWindowAt(childWin, p)) + return recurse; + } + } + } + if (!win->isTopLevel() + && !(win->flags() & Qt::WindowTransparentForInput) + && win->geometry().contains(win->parent()->mapFromGlobal(p))) { + return win; + } + return Q_NULLPTR; +} + static const char *wm_window_type_property_id = "_q_xcb_wm_window_type"; QXcbWindow::QXcbWindow(QWindow *window) @@ -855,6 +876,33 @@ void QXcbWindow::hide() connection()->setMouseGrabber(Q_NULLPTR); m_mapped = false; + + // Hiding a modal window doesn't send an enter event to its transient parent when the + // mouse is already over the parent window, so the enter event must be emulated. + if (window()->isModal()) { + // Get the cursor position at modal window screen + const QPoint nativePos = xcbScreen()->cursor()->pos(); + const QPoint cursorPos = QHighDpi::fromNativePixels(nativePos, xcbScreen()->screenForPosition(nativePos)->screen()); + + // Find the top level window at cursor position. + // Don't use QGuiApplication::topLevelAt(): search only the virtual siblings of this window's screen + QWindow *enterWindow = Q_NULLPTR; + foreach (QPlatformScreen *screen, xcbScreen()->virtualSiblings()) { + if (screen->geometry().contains(cursorPos)) { + const QPoint devicePosition = QHighDpi::toNativePixels(cursorPos, screen->screen()); + enterWindow = screen->topLevelAt(devicePosition); + break; + } + } + + if (enterWindow && enterWindow != window()) { + // Find the child window at cursor position, otherwise use the top level window + if (QWindow *childWindow = childWindowAt(enterWindow, cursorPos)) + enterWindow = childWindow; + const QPoint localPos = enterWindow->mapFromGlobal(cursorPos); + QWindowSystemInterface::handleEnterEvent(enterWindow, localPos, cursorPos); + } + } } static QWindow *tlWindow(QWindow *window) diff --git a/tests/auto/gui/kernel/qwindow/BLACKLIST b/tests/auto/gui/kernel/qwindow/BLACKLIST index 26cace1403..cfbd47745f 100644 --- a/tests/auto/gui/kernel/qwindow/BLACKLIST +++ b/tests/auto/gui/kernel/qwindow/BLACKLIST @@ -6,3 +6,5 @@ ubuntu-14.04 ubuntu-14.04 [modalWithChildWindow] ubuntu-14.04 +[modalWindowEnterEventOnHide_QTBUG35109] +ubuntu-14.04 diff --git a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp index a89f0da4d2..0cce5a072c 100644 --- a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp +++ b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp @@ -92,6 +92,9 @@ private slots: void modalWithChildWindow(); void modalWindowModallity(); void modalWindowPosition(); +#ifndef QT_NO_CURSOR + void modalWindowEnterEventOnHide_QTBUG35109(); +#endif void windowsTransientChildren(); void requestUpdate(); void initTestCase(); @@ -706,10 +709,24 @@ public: } } } + bool event(QEvent *e) { + switch (e->type()) { + case QEvent::Enter: + ++enterEventCount; + break; + case QEvent::Leave: + ++leaveEventCount; + break; + default: + break; + } + return QWindow::event(e); + } void resetCounters() { mousePressedCount = mouseReleasedCount = mouseMovedCount = mouseDoubleClickedCount = 0; mouseSequenceSignature = QString(); touchPressedCount = touchReleasedCount = touchMovedCount = 0; + enterEventCount = leaveEventCount = 0; } InputTestWindow() { @@ -727,6 +744,7 @@ public: QPointF mousePressScreenPos, mouseMoveScreenPos, mousePressLocalPos; int touchPressedCount, touchReleasedCount, touchMovedCount; QEvent::Type touchEventType; + int enterEventCount, leaveEventCount; bool ignoreMouse, ignoreTouch; @@ -1732,6 +1750,181 @@ void tst_QWindow::modalWindowPosition() QCOMPARE(window.geometry(), origGeo); } +#ifndef QT_NO_CURSOR +void tst_QWindow::modalWindowEnterEventOnHide_QTBUG35109() +{ + if (QGuiApplication::platformName() == QLatin1String("cocoa")) + QSKIP("This test fails on OS X on CI"); + + const QPoint center = QGuiApplication::primaryScreen()->availableGeometry().center(); + + const int childOffset = 16; + const QPoint rootPos = center - QPoint(m_testWindowSize.width(), + m_testWindowSize.height())/2; + const QPoint modalPos = rootPos + QPoint(childOffset * 5, + childOffset * 5); + const QPoint cursorPos = rootPos - QPoint(80, 80); + + // Test whether tlw can receive the enter event + { + QCursor::setPos(cursorPos); + QCoreApplication::processEvents(); + + InputTestWindow root; + root.setTitle(__FUNCTION__); + root.setGeometry(QRect(rootPos, m_testWindowSize)); + root.show(); + QVERIFY(QTest::qWaitForWindowExposed(&root)); + root.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&root)); + + // Move the mouse over the root window, but not over the modal window. + QCursor::setPos(rootPos + QPoint(childOffset * 5 / 2, + childOffset * 5 / 2)); + + // Wait for the enter event. It must be delivered here, otherwise second + // compare can PASS because of this event even after "resetCounters()". + QTRY_COMPARE(root.enterEventCount, 1); + QTRY_COMPARE(root.leaveEventCount, 0); + + QWindow modal; + modal.setTitle(QLatin1String("Modal - ") + __FUNCTION__); + modal.setTransientParent(&root); + modal.resize(m_testWindowSize/2); + modal.setFramePosition(modalPos); + modal.setModality(Qt::ApplicationModal); + modal.show(); + QVERIFY(QTest::qWaitForWindowExposed(&modal)); + modal.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&modal)); + + QCoreApplication::processEvents(); + QTRY_COMPARE(root.leaveEventCount, 1); + + root.resetCounters(); + modal.close(); + + // Check for the enter event + QTRY_COMPARE(root.enterEventCount, 1); + } + + // Test whether child window can receive the enter event + { + QCursor::setPos(cursorPos); + QCoreApplication::processEvents(); + + QWindow root; + root.setTitle(__FUNCTION__); + root.setGeometry(QRect(rootPos, m_testWindowSize)); + + QWindow childLvl1; + childLvl1.setParent(&root); + childLvl1.setGeometry(childOffset, + childOffset, + m_testWindowSize.width() - childOffset, + m_testWindowSize.height() - childOffset); + + InputTestWindow childLvl2; + childLvl2.setParent(&childLvl1); + childLvl2.setGeometry(childOffset, + childOffset, + childLvl1.width() - childOffset, + childLvl1.height() - childOffset); + + root.show(); + childLvl1.show(); + childLvl2.show(); + + QVERIFY(QTest::qWaitForWindowExposed(&root)); + root.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&root)); + QVERIFY(childLvl1.isVisible()); + QVERIFY(childLvl2.isVisible()); + + // Move the mouse over the child window, but not over the modal window. + // Be sure that the value is almost left-top of second child window for + // checking proper position mapping. + QCursor::setPos(rootPos + QPoint(childOffset * 5 / 2, + childOffset * 5 / 2)); + + // Wait for the enter event. It must be delivered here, otherwise second + // compare can PASS because of this event even after "resetCounters()". + QTRY_COMPARE(childLvl2.enterEventCount, 1); + QTRY_COMPARE(childLvl2.leaveEventCount, 0); + + QWindow modal; + modal.setTitle(QLatin1String("Modal - ") + __FUNCTION__); + modal.setTransientParent(&root); + modal.resize(m_testWindowSize/2); + modal.setFramePosition(modalPos); + modal.setModality(Qt::ApplicationModal); + modal.show(); + QVERIFY(QTest::qWaitForWindowExposed(&modal)); + modal.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&modal)); + + QCoreApplication::processEvents(); + QTRY_COMPARE(childLvl2.leaveEventCount, 1); + + childLvl2.resetCounters(); + modal.close(); + + // Check for the enter event + QTRY_COMPARE(childLvl2.enterEventCount, 1); + } + + // Test whether tlw can receive the enter event if mouse is over the invisible child windnow + { + QCursor::setPos(cursorPos); + QCoreApplication::processEvents(); + + InputTestWindow root; + root.setTitle(__FUNCTION__); + root.setGeometry(QRect(rootPos, m_testWindowSize)); + + QWindow child; + child.setParent(&root); + child.setGeometry(QRect(QPoint(), m_testWindowSize)); + + root.show(); + + QVERIFY(QTest::qWaitForWindowExposed(&root)); + root.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&root)); + QVERIFY(!child.isVisible()); + + // Move the mouse over the child window, but not over the modal window. + QCursor::setPos(rootPos + QPoint(childOffset * 5 / 2, + childOffset * 5 / 2)); + + // Wait for the enter event. It must be delivered here, otherwise second + // compare can PASS because of this event even after "resetCounters()". + QTRY_COMPARE(root.enterEventCount, 1); + QTRY_COMPARE(root.leaveEventCount, 0); + + QWindow modal; + modal.setTitle(QLatin1String("Modal - ") + __FUNCTION__); + modal.setTransientParent(&root); + modal.resize(m_testWindowSize/2); + modal.setFramePosition(modalPos); + modal.setModality(Qt::ApplicationModal); + modal.show(); + QVERIFY(QTest::qWaitForWindowExposed(&modal)); + modal.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&modal)); + + QCoreApplication::processEvents(); + QTRY_COMPARE(root.leaveEventCount, 1); + + root.resetCounters(); + modal.close(); + + // Check for the enter event + QTRY_COMPARE(root.enterEventCount, 1); + } +} +#endif + class ColoredWindow : public QRasterWindow { public: explicit ColoredWindow(const QColor &color, QWindow *parent = 0) : QRasterWindow(parent), m_color(color) {} |