diff options
-rw-r--r-- | src/widgets/kernel/qapplication.cpp | 3 | ||||
-rw-r--r-- | src/widgets/kernel/qwidgetwindow.cpp | 6 | ||||
-rw-r--r-- | tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp | 186 |
3 files changed, 194 insertions, 1 deletions
diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp index e93eaf5b30..27ffd1cc7c 100644 --- a/src/widgets/kernel/qapplication.cpp +++ b/src/widgets/kernel/qapplication.cpp @@ -3534,6 +3534,7 @@ static void grabForPopup(QWidget *popup) extern QWidget *qt_popup_down; extern bool qt_replay_popup_mouse_event; +extern bool qt_popup_down_closed; void QApplicationPrivate::closePopup(QWidget *popup) { @@ -3543,12 +3544,14 @@ void QApplicationPrivate::closePopup(QWidget *popup) if (popup == qt_popup_down) { qt_button_down = nullptr; + qt_popup_down_closed = true; qt_popup_down = nullptr; } if (QApplicationPrivate::popupWidgets->count() == 0) { // this was the last popup delete QApplicationPrivate::popupWidgets; QApplicationPrivate::popupWidgets = nullptr; + qt_popup_down_closed = false; if (popupGrabOk) { popupGrabOk = false; diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp index 2faedccc20..51e785d0aa 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -62,6 +62,7 @@ Q_WIDGETS_EXPORT QWidget *qt_button_down = nullptr; // widget got last button-do // popup control QWidget *qt_popup_down = nullptr; // popup that contains the pressed widget extern int openPopupCount; +bool qt_popup_down_closed = false; // qt_popup_down has been closed bool qt_replay_popup_mouse_event = false; extern bool qt_try_modal(QWidget *widget, QEvent::Type type); @@ -526,6 +527,7 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event) case QEvent::MouseButtonDblClick: qt_button_down = popupChild; qt_popup_down = activePopupWidget; + qt_popup_down_closed = false; break; case QEvent::MouseButtonRelease: releaseAfter = true; @@ -570,7 +572,7 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event) if ((event->type() != QEvent::MouseButtonPress) || !(event->flags().testFlag(Qt::MouseEventCreatedDoubleClick))) { // if the widget that was pressed is gone, then deliver move events without buttons - const auto buttons = event->type() == QEvent::MouseMove && qt_button_down == nullptr + const auto buttons = event->type() == QEvent::MouseMove && qt_popup_down_closed ? Qt::NoButton : event->buttons(); QMouseEvent e(event->type(), widgetPos, event->scenePosition(), event->globalPosition(), event->button(), buttons, event->modifiers(), event->source()); @@ -643,11 +645,13 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event) if (releaseAfter) { qt_button_down = nullptr; + qt_popup_down_closed = false; qt_popup_down = nullptr; } return; } + qt_popup_down_closed = false; // modal event handling if (QApplicationPrivate::instance()->modalState() && !qt_try_modal(m_widget, event->type())) return; 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" |