diff options
-rw-r--r-- | src/quicktemplates2/qquickpopup.cpp | 23 | ||||
-rw-r--r-- | tests/auto/qquickpopup/data/destroyDuringExitTransition.qml | 114 | ||||
-rw-r--r-- | tests/auto/qquickpopup/tst_qquickpopup.cpp | 30 |
3 files changed, 163 insertions, 4 deletions
diff --git a/src/quicktemplates2/qquickpopup.cpp b/src/quicktemplates2/qquickpopup.cpp index 455b0425..f54b610c 100644 --- a/src/quicktemplates2/qquickpopup.cpp +++ b/src/quicktemplates2/qquickpopup.cpp @@ -51,6 +51,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcDimmer, "qt.quick.controls.popup.dimmer") + /*! \qmltype Popup \inherits QtObject @@ -499,8 +501,10 @@ void QQuickPopupPrivate::finalizeExitTransition() { Q_Q(QQuickPopup); getPositioner()->setParentItem(nullptr); - popupItem->setParentItem(nullptr); - popupItem->setVisible(false); + if (popupItem) { + popupItem->setParentItem(nullptr); + popupItem->setVisible(false); + } destroyOverlay(); if (hadActiveFocusBeforeExitTransition && window) { @@ -531,8 +535,10 @@ void QQuickPopupPrivate::finalizeExitTransition() hadActiveFocusBeforeExitTransition = false; emit q->visibleChanged(); emit q->closed(); - popupItem->setScale(prevScale); - popupItem->setOpacity(prevOpacity); + if (popupItem) { + popupItem->setScale(prevScale); + popupItem->setOpacity(prevOpacity); + } } void QQuickPopupPrivate::opened() @@ -733,6 +739,8 @@ static QQuickItem *createDimmer(QQmlComponent *component, QQuickPopup *popup, QQ if (component) component->completeCreate(); } + qCDebug(lcDimmer) << "finished creating dimmer from component" << component + << "for popup" << popup << "with parent" << parent << "- item is:" << item; return item; } @@ -759,6 +767,7 @@ void QQuickPopupPrivate::createOverlay() void QQuickPopupPrivate::destroyOverlay() { if (dimmer) { + qCDebug(lcDimmer) << "destroying dimmer" << dimmer; dimmer->setParentItem(nullptr); dimmer->deleteLater(); dimmer = nullptr; @@ -857,6 +866,12 @@ QQuickPopup::~QQuickPopup() d->popupItem = nullptr; delete d->positioner; d->positioner = nullptr; + + // If the popup is destroyed before the exit transition finishes, + // the necessary cleanup (removing modal dimmers that block mouse events, + // emitting closed signal, etc.) won't happen. That's why we do it manually here. + if (d->transitionState == QQuickPopupPrivate::ExitTransition && d->transitionManager.isRunning()) + d->finalizeExitTransition(); } /*! diff --git a/tests/auto/qquickpopup/data/destroyDuringExitTransition.qml b/tests/auto/qquickpopup/data/destroyDuringExitTransition.qml new file mode 100644 index 00000000..67fca2e7 --- /dev/null +++ b/tests/auto/qquickpopup/data/destroyDuringExitTransition.qml @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: window + width: 400 + height: 400 + title: "destroyDuringExitTransition" + + property Dialog dialog1 + property Dialog dialog2 + + Component { + id: dlg + + Dialog { + dim: true + modal: true + closePolicy: Popup.CloseOnEscape + visible: true + + property alias button: button + + Column { + Text { + text: "button is " + (button.down ? "down" : "up") + } + + Button { + id: button + text: "Try to press this button" + } + } + } + } + + Component { + id: brokenDlg + Dialog { + dim: true + modal: true + closePolicy: Popup.CloseOnEscape + visible: true + + Text { + text: "Press Esc key to reject this dialog" + } + + exit: Transition { + NumberAnimation { property: "opacity"; from: 1.0; to: 0.0; duration: 100 } + } + } + } + + + Component.onCompleted: { + dialog1 = dlg.createObject(window) + dialog2 = brokenDlg.createObject(window) + + dialog2.onRejected.connect(function(){ + dialog2.destroy() + }) + } +} diff --git a/tests/auto/qquickpopup/tst_qquickpopup.cpp b/tests/auto/qquickpopup/tst_qquickpopup.cpp index ff04c99d..c02b47fc 100644 --- a/tests/auto/qquickpopup/tst_qquickpopup.cpp +++ b/tests/auto/qquickpopup/tst_qquickpopup.cpp @@ -100,6 +100,7 @@ private slots: void tabFence(); void invisibleToolTipOpen(); void centerInOverlayWithinStackViewItem(); + void destroyDuringExitTransition(); }; void tst_QQuickPopup::initTestCase() @@ -1426,6 +1427,35 @@ void tst_QQuickPopup::centerInOverlayWithinStackViewItem() // Shouldn't crash on exit. } +void tst_QQuickPopup::destroyDuringExitTransition() +{ + QQuickApplicationHelper helper(this, "destroyDuringExitTransition.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + QPointer<QQuickPopup> dialog2 = window->property("dialog2").value<QQuickPopup*>(); + QVERIFY(dialog2); + QTRY_COMPARE(dialog2->isVisible(), true); + + // Close the second dialog, destroying it before its exit transition can finish. + QTest::keyClick(window, Qt::Key_Escape); + QTRY_VERIFY(!dialog2); + + // Events should go through to the dialog underneath. + QQuickPopup *dialog1 = window->property("dialog1").value<QQuickPopup*>(); + QVERIFY(dialog1); + QQuickButton *button = dialog1->property("button").value<QQuickButton*>(); + QVERIFY(button); + const auto buttonClickPos = button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, buttonClickPos); + QVERIFY(button->isDown()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, buttonClickPos); + QVERIFY(!button->isDown()); +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickPopup) #include "tst_qquickpopup.moc" |