summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorVolker Hilsheimer <volker.hilsheimer@qt.io>2020-06-15 00:23:41 +0200
committerVolker Hilsheimer <volker.hilsheimer@qt.io>2020-07-01 14:52:49 +0200
commitab6861b01ff9c14fd1648cb725da17d1ebf3faea (patch)
tree422f4ff7626e16fcfcd0a4aef35e9d6f27436e5f /tests
parent37d9e44cd010c9844b0dbe2b25f307eab15b3ea8 (diff)
Fix delivery of MouseMove events to newly opened popup windows
Amend d934fd7f54eae24ea3f719890e2c4dbbc445049d, which was too naive in assuming that any change to the popup stack while a popup had been pressed into should result in mouse move events to be delivered without buttons. Instead, add a new flag that is set explicitly when the qt_popup_down widget is closed, and remove buttons from the move move events only when that flag is set. Add the sorely missing test case as well, even if we have to accept that not all behavior can be tested reliably. Ie. on macOS, the simulated mouse event differs from the event we do get from the QPA plugin or the system; on Xcb, some of the behavior depends on the window manager. This is something we could try to clean up for Qt 6. Change-Id: Ibf0a0a6fb7d401915057365788947e5a35aa20c3 Fixes: QTBUG-84926 Task-number: QTBUG-82538 Pick-to: 5.15 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Liang Qi <liang.qi@qt.io>
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp186
1 files changed, 186 insertions, 0 deletions
diff --git a/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp b/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp
index a0842201ae..890a53541a 100644
--- a/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp
+++ b/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp
@@ -126,6 +126,9 @@ private slots:
void QTBUG_56277_resize_on_showEvent();
+ void mouseMoveWithPopup_data();
+ void mouseMoveWithPopup();
+
private:
QSize m_testWidgetSize;
const int m_fuzz;
@@ -1319,5 +1322,188 @@ void tst_QWidget_window::QTBUG_56277_resize_on_showEvent()
QVERIFY(geometry.top() > topmostY || geometry.left() > screen->availableGeometry().left());
}
+void tst_QWidget_window::mouseMoveWithPopup_data()
+{
+ QTest::addColumn<Qt::WindowType>("windowType");
+
+ QTest::addRow("Dialog") << Qt::Dialog;
+ QTest::addRow("Popup") << Qt::Popup;
+}
+
+void tst_QWidget_window::mouseMoveWithPopup()
+{
+ QFETCH(Qt::WindowType, windowType);
+
+ class Window : public QWidget
+ {
+ public:
+ Window(QWidget *parent = nullptr, Qt::WindowFlags flags = {})
+ : QWidget(parent, flags|Qt::CustomizeWindowHint|Qt::FramelessWindowHint)
+ {}
+
+ QSize sizeHint() const
+ {
+ if (parent())
+ return QSize(150, 100);
+ return QSize(250, 250);
+ }
+
+ Window *popup = nullptr;
+ Qt::WindowType type = Qt::Popup;
+ int mousePressCount = 0;
+ int mouseMoveCount = 0;
+ int mouseReleaseCount = 0;
+ void resetCounters()
+ {
+ mousePressCount = 0;
+ mouseMoveCount = 0;
+ mouseReleaseCount = 0;
+ }
+ protected:
+ void mousePressEvent(QMouseEvent *event)
+ {
+ ++mousePressCount;
+
+ if (event->button() == Qt::RightButton) {
+ if (!popup)
+ popup = new Window(this, type);
+ popup->move(event->globalPosition().toPoint());
+ popup->show();
+ if (!QTest::qWaitForWindowExposed(popup)) {
+ delete popup;
+ popup = nullptr;
+ QSKIP("Failed to expose popup window!");
+ }
+ } else {
+ QWidget::mousePressEvent(event);
+ }
+ }
+ void mouseReleaseEvent(QMouseEvent *event)
+ {
+ ++mouseReleaseCount;
+ QWidget::mouseReleaseEvent(event);
+ }
+ void mouseMoveEvent(QMouseEvent *event)
+ {
+ ++mouseMoveCount;
+ QWidget::mouseMoveEvent(event);
+ }
+ };
+ Window topLevel;
+ topLevel.setObjectName("topLevel");
+ topLevel.type = windowType;
+ topLevel.show();
+ if (!QTest::qWaitForWindowExposed(&topLevel))
+ QSKIP("Failed to expose window!");
+
+ QCOMPARE(QApplication::activePopupWidget(), nullptr);
+ QCOMPARE(QApplication::activeWindow(), &topLevel);
+
+ QPoint mousePos = topLevel.geometry().center();
+ QWindow *window = nullptr;
+ Qt::MouseButtons buttons = {};
+ auto mouseAction = [&](Qt::MouseButton button, QPoint offset = {}) -> QEvent::Type
+ {
+ QEvent::Type type;
+ if (offset != QPoint()) {
+ type = QEvent::MouseMove;
+ } else if (buttons & button) {
+ type = QEvent::MouseButtonRelease;
+ buttons &= ~button;
+ } else {
+ Q_ASSERT(button != Qt::NoButton);
+ type = QEvent::MouseButtonPress;
+ buttons |= button;
+ window = QApplication::activeWindow()->windowHandle();
+ }
+
+ mousePos += offset;
+
+ if (!window)
+ return QEvent::None;
+
+ bool result = QWindowSystemInterface::handleMouseEvent(window, window->mapFromGlobal(mousePos),
+ mousePos, buttons, button, type);
+ QCoreApplication::processEvents();
+ if (type == QEvent::MouseButtonRelease && buttons == Qt::NoButton)
+ window = nullptr;
+
+ if (!result)
+ return QEvent::None;
+ return type;
+ };
+
+ QCOMPARE(mouseAction(Qt::RightButton), QEvent::MouseButtonPress);
+ QCOMPARE(topLevel.mousePressCount, 1);
+ QVERIFY(topLevel.popup);
+ QCOMPARE(topLevel.popup->mousePressCount, 0);
+ topLevel.popup->setObjectName(windowType == Qt::Popup ? "Popup" : "Dialog");
+ QCOMPARE(QApplication::activePopupWidget(), windowType == Qt::Popup ? topLevel.popup : nullptr);
+ // if popup, then popup gets the mouse move even though it didn't get any press
+ QCOMPARE(mouseAction(Qt::NoButton, QPoint(10, 10)), QEvent::MouseMove);
+ QCOMPARE(topLevel.mouseMoveCount, windowType == Qt::Popup ? 0 : 1);
+ QCOMPARE(topLevel.popup->mouseMoveCount, windowType == Qt::Popup ? 1 : 0);
+ // if popup, then popup gets the release even though it didn't get any press
+ QCOMPARE(mouseAction(Qt::RightButton), QEvent::MouseButtonRelease);
+ QCOMPARE(topLevel.mouseReleaseCount, windowType == Qt::Popup ? 0 : 1);
+ QCOMPARE(topLevel.popup->mouseReleaseCount, windowType == Qt::Popup ? 1 : 0);
+
+ Q_ASSERT(buttons == Qt::NoButton);
+ topLevel.resetCounters();
+ topLevel.popup->resetCounters();
+
+ // nested popup, same procedure
+ QCOMPARE(mouseAction(Qt::RightButton), QEvent::MouseButtonPress);
+ QVERIFY(topLevel.popup);
+ QCOMPARE(topLevel.popup->mousePressCount, 1);
+ QVERIFY(topLevel.popup->popup);
+ topLevel.popup->popup->setObjectName("NestedPopup");
+ QCOMPARE(QApplication::activePopupWidget(), topLevel.popup->popup);
+ QCOMPARE(topLevel.popup->popup->mousePressCount, 0);
+
+ // nested popup is always a popup and grabs the mouse, so first popup gets nothing
+ QCOMPARE(mouseAction(Qt::NoButton, QPoint(10, 10)), QEvent::MouseMove);
+ QCOMPARE(topLevel.popup->mouseMoveCount, 0);
+ QCOMPARE(topLevel.popup->popup->mouseMoveCount, 1);
+
+ // nested popup gets the release, as before
+ QCOMPARE(mouseAction(Qt::RightButton), QEvent::MouseButtonRelease);
+ QCOMPARE(topLevel.popup->mouseReleaseCount, 0);
+ QCOMPARE(topLevel.popup->popup->mouseReleaseCount, 1);
+
+ Q_ASSERT(buttons == Qt::NoButton);
+
+ // move mouse back into first popup
+ mouseAction({}, QPoint(-15, -15));
+ QVERIFY(!topLevel.popup->popup->geometry().contains(mousePos));
+ QVERIFY(topLevel.popup->geometry().contains(mousePos));
+
+ topLevel.popup->resetCounters();
+ topLevel.popup->popup->resetCounters();
+
+ // closing the nested popup by clicking into the first popup/dialog; the nested popup gets the press
+ QCOMPARE(mouseAction(Qt::LeftButton), QEvent::MouseButtonPress);
+ QCOMPARE(topLevel.popup->popup->mousePressCount, 1);
+ QVERIFY(!topLevel.popup->popup->isVisible());
+ QCOMPARE(QApplication::activePopupWidget(), windowType == Qt::Popup ? topLevel.popup : nullptr);
+ QCOMPARE(QApplication::activeWindow(), windowType == Qt::Popup ? &topLevel : topLevel.popup);
+
+ // the move event following a press that closed the active popup should NOT be delivered to the first popup
+ QCOMPARE(mouseAction({}, QPoint(-10, -10)), QEvent::MouseMove);
+ // dialogs might or might not get the event - platform specific behavior in Qt 5
+ if (topLevel.popup->mouseMoveCount != 0)
+ QEXPECT_FAIL("Dialog", "Platform specific behavior", Continue);
+ QCOMPARE(topLevel.popup->mouseMoveCount, 0);
+ QCOMPARE(topLevel.popup->popup->mouseMoveCount, 0);
+
+ // but the release event will still be delivered to the first popup - dialogs might not get it
+ QCOMPARE(mouseAction(Qt::LeftButton), QEvent::MouseButtonRelease);
+ if (topLevel.popup->mouseMoveCount != 1
+ && (QGuiApplication::platformName().startsWith(QLatin1String("xcb"), Qt::CaseInsensitive)
+ || QGuiApplication::platformName().startsWith(QLatin1String("offscreen"), Qt::CaseInsensitive)))
+ QEXPECT_FAIL("Dialog", "Platform specific behavior", Continue);
+ QCOMPARE(topLevel.popup->mouseReleaseCount, 1);
+}
+
QTEST_MAIN(tst_QWidget_window)
#include "tst_qwidget_window.moc"