Dialogs: clean up native dialogs when object gets destroyed
QWidget::setVisible is virtual, and called via hide() by both the QDialog and the QWidget destructor. A dialog that becomes invisible when getting destroyed will at most execute the QDialog override. Subclassing QDialog and overriding setVisible() to update the state of the native platform dialog will not work, unless we explicitly call hide() in the respective subclass's destructor. Since e0bb9e81ab1a9d71f2893844ea82, QDialogPrivate::setVisible is also virtual, and gets called by QDialog::setVisible. So the clean solution is to move the implementation of the native dialog status update into an override of QDialogPrivate::setVisible. Add test that verifies that the transient parent of the dialog becomes inactive when the (native) dialog shows (and skip if that fails), and then becomes active again when the (native) dialog is closed through the destructor of the Q*Dialog class. The test of QFileDialog has to be skipped on Android for the same reason as the widgetlessNativeDialog. Fixes: QTBUG-116277 Change-Id: Ie3f93980d8653b8d933bf70aac3ef90de606f0ef Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io> (cherry picked from commit 505ed52cd4dcef081d9868424057451bd1dce497) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
864544eb1a
commit
9044a8e035
@ -120,6 +120,7 @@ public:
|
||||
bool handleColorPickingKeyPress(QKeyEvent *e);
|
||||
|
||||
bool canBeNativeDialog() const override;
|
||||
void setVisible(bool visible) override;
|
||||
|
||||
QWellArray *custom;
|
||||
QWellArray *standard;
|
||||
@ -2136,30 +2137,42 @@ QColorDialog::ColorDialogOptions QColorDialog::options() const
|
||||
*/
|
||||
void QColorDialog::setVisible(bool visible)
|
||||
{
|
||||
Q_D(QColorDialog);
|
||||
// will call QColorDialogPrivate::setVisible override
|
||||
QDialog::setVisible(visible);
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
|
||||
The implementation of QColorDialog::setVisible() has to live here so that the call
|
||||
to hide() in ~QDialog calls this function; it wouldn't call the override of
|
||||
QDialog::setVisible().
|
||||
*/
|
||||
void QColorDialogPrivate::setVisible(bool visible)
|
||||
{
|
||||
Q_Q(QColorDialog);
|
||||
if (visible){
|
||||
if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden))
|
||||
if (q->testAttribute(Qt::WA_WState_ExplicitShowHide) && !q->testAttribute(Qt::WA_WState_Hidden))
|
||||
return;
|
||||
} else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden))
|
||||
} else if (q->testAttribute(Qt::WA_WState_ExplicitShowHide) && q->testAttribute(Qt::WA_WState_Hidden))
|
||||
return;
|
||||
|
||||
if (visible)
|
||||
d->selectedQColor = QColor();
|
||||
selectedQColor = QColor();
|
||||
|
||||
if (d->nativeDialogInUse) {
|
||||
if (d->setNativeDialogVisible(visible)) {
|
||||
if (nativeDialogInUse) {
|
||||
if (setNativeDialogVisible(visible)) {
|
||||
// Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below
|
||||
// updates the state correctly, but skips showing the non-native version:
|
||||
setAttribute(Qt::WA_DontShowOnScreen);
|
||||
q->setAttribute(Qt::WA_DontShowOnScreen);
|
||||
} else {
|
||||
d->initWidgets();
|
||||
initWidgets();
|
||||
}
|
||||
} else {
|
||||
setAttribute(Qt::WA_DontShowOnScreen, false);
|
||||
q->setAttribute(Qt::WA_DontShowOnScreen, false);
|
||||
}
|
||||
|
||||
QDialog::setVisible(visible);
|
||||
QDialogPrivate::setVisible(visible);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -2207,7 +2220,6 @@ QColor QColorDialog::getColor(const QColor &initial, QWidget *parent, const QStr
|
||||
|
||||
QColorDialog::~QColorDialog()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -835,41 +835,53 @@ void QFileDialog::open(QObject *receiver, const char *member)
|
||||
*/
|
||||
void QFileDialog::setVisible(bool visible)
|
||||
{
|
||||
Q_D(QFileDialog);
|
||||
// will call QFileDialogPrivate::setVisible override
|
||||
QDialog::setVisible(visible);
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
|
||||
The logic has to live here so that the call to hide() in ~QDialog calls
|
||||
this function; it wouldn't call an override of QDialog::setVisible().
|
||||
*/
|
||||
void QFileDialogPrivate::setVisible(bool visible)
|
||||
{
|
||||
Q_Q(QFileDialog);
|
||||
if (visible){
|
||||
if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden))
|
||||
if (q->testAttribute(Qt::WA_WState_ExplicitShowHide) && !q->testAttribute(Qt::WA_WState_Hidden))
|
||||
return;
|
||||
} else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden))
|
||||
} else if (q->testAttribute(Qt::WA_WState_ExplicitShowHide) && q->testAttribute(Qt::WA_WState_Hidden))
|
||||
return;
|
||||
|
||||
if (d->canBeNativeDialog()){
|
||||
if (d->setNativeDialogVisible(visible)){
|
||||
// Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below
|
||||
if (canBeNativeDialog()){
|
||||
if (setNativeDialogVisible(visible)){
|
||||
// Set WA_DontShowOnScreen so that QDialogPrivate::setVisible(visible) below
|
||||
// updates the state correctly, but skips showing the non-native version:
|
||||
setAttribute(Qt::WA_DontShowOnScreen);
|
||||
q->setAttribute(Qt::WA_DontShowOnScreen);
|
||||
#if QT_CONFIG(fscompleter)
|
||||
// So the completer doesn't try to complete and therefore show a popup
|
||||
if (!d->nativeDialogInUse)
|
||||
d->completer->setModel(nullptr);
|
||||
if (!nativeDialogInUse)
|
||||
completer->setModel(nullptr);
|
||||
#endif
|
||||
} else {
|
||||
d->createWidgets();
|
||||
setAttribute(Qt::WA_DontShowOnScreen, false);
|
||||
createWidgets();
|
||||
q->setAttribute(Qt::WA_DontShowOnScreen, false);
|
||||
#if QT_CONFIG(fscompleter)
|
||||
if (!d->nativeDialogInUse) {
|
||||
if (d->proxyModel != nullptr)
|
||||
d->completer->setModel(d->proxyModel);
|
||||
if (!nativeDialogInUse) {
|
||||
if (proxyModel != nullptr)
|
||||
completer->setModel(proxyModel);
|
||||
else
|
||||
d->completer->setModel(d->model);
|
||||
completer->setModel(model);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (visible && d->usingWidgets())
|
||||
d->qFileDialogUi->fileNameEdit->setFocus();
|
||||
if (visible && usingWidgets())
|
||||
qFileDialogUi->fileNameEdit->setFocus();
|
||||
|
||||
QDialog::setVisible(visible);
|
||||
QDialogPrivate::setVisible(visible);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -221,6 +221,7 @@ public:
|
||||
// dialog. Returning false means that a non-native dialog must be
|
||||
// used instead.
|
||||
bool canBeNativeDialog() const override;
|
||||
void setVisible(bool visible) override;
|
||||
inline bool usingWidgets() const;
|
||||
|
||||
inline void setDirectory_sys(const QUrl &directory);
|
||||
|
@ -960,19 +960,32 @@ void QFontDialog::open(QObject *receiver, const char *member)
|
||||
*/
|
||||
void QFontDialog::setVisible(bool visible)
|
||||
{
|
||||
if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden) != visible)
|
||||
// will call QFontDialogPrivate::setVisible
|
||||
QDialog::setVisible(visible);
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
|
||||
The implementation of QFontDialog::setVisible() has to live here so that the call
|
||||
to hide() in ~QDialog calls this function; it wouldn't call the override of
|
||||
QDialog::setVisible().
|
||||
*/
|
||||
void QFontDialogPrivate::setVisible(bool visible)
|
||||
{
|
||||
Q_Q(QFontDialog);
|
||||
if (q->testAttribute(Qt::WA_WState_ExplicitShowHide) && q->testAttribute(Qt::WA_WState_Hidden) != visible)
|
||||
return;
|
||||
Q_D(QFontDialog);
|
||||
if (d->canBeNativeDialog())
|
||||
d->setNativeDialogVisible(visible);
|
||||
if (d->nativeDialogInUse) {
|
||||
if (canBeNativeDialog())
|
||||
setNativeDialogVisible(visible);
|
||||
if (nativeDialogInUse) {
|
||||
// Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below
|
||||
// updates the state correctly, but skips showing the non-native version:
|
||||
setAttribute(Qt::WA_DontShowOnScreen, true);
|
||||
q->setAttribute(Qt::WA_DontShowOnScreen, true);
|
||||
} else {
|
||||
setAttribute(Qt::WA_DontShowOnScreen, false);
|
||||
q->setAttribute(Qt::WA_DontShowOnScreen, false);
|
||||
}
|
||||
QDialog::setVisible(visible);
|
||||
QDialogPrivate::setVisible(visible);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -105,6 +105,7 @@ public:
|
||||
QByteArray memberToDisconnectOnClose;
|
||||
|
||||
bool canBeNativeDialog() const override;
|
||||
void setVisible(bool visible) override;
|
||||
void _q_runNativeAppModalPanel();
|
||||
|
||||
private:
|
||||
|
@ -146,9 +146,26 @@ void tst_QAbstractPrintDialog::hideNativeByDestruction()
|
||||
QWidget window;
|
||||
QWidget *child = new QWidget(&window);
|
||||
QPointer<QPrintDialog> dialog = new QPrintDialog(child);
|
||||
// Make it application modal so that we don't end up with a sheet on macOS
|
||||
dialog->setWindowModality(Qt::ApplicationModal);
|
||||
window.show();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&window));
|
||||
dialog->open();
|
||||
|
||||
// We test that the dialog opens and closes by watching the activation of the
|
||||
// transient parent window. If it doesn't deactivate, then we have to skip.
|
||||
const auto windowActive = [&window]{ return window.isActiveWindow(); };
|
||||
const auto windowInactive = [&window]{ return !window.isActiveWindow(); };
|
||||
if (!QTest::qWaitFor(windowInactive, 2000))
|
||||
QSKIP("Dialog didn't activate");
|
||||
|
||||
// This should destroy the dialog and close the native window
|
||||
child->deleteLater();
|
||||
QTRY_VERIFY(!dialog);
|
||||
// If the native window is still open, then the transient parent can't become
|
||||
// active
|
||||
window.activateWindow();
|
||||
QVERIFY(QTest::qWaitFor(windowActive));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -29,6 +29,8 @@ private slots:
|
||||
void QTBUG_43548_initialColor();
|
||||
void hexColor_data();
|
||||
void hexColor();
|
||||
|
||||
void hideNativeByDestruction();
|
||||
};
|
||||
|
||||
class TestNativeDialog : public QColorDialog
|
||||
@ -179,5 +181,32 @@ void tst_QColorDialog::hexColor()
|
||||
QCOMPARE(color.name(QColor::HexRgb), expectedHexColor.toLower());
|
||||
}
|
||||
|
||||
void tst_QColorDialog::hideNativeByDestruction()
|
||||
{
|
||||
QWidget window;
|
||||
QWidget *child = new QWidget(&window);
|
||||
QPointer<QColorDialog> dialog = new QColorDialog(child);
|
||||
// Make it application modal so that we don't end up with a sheet on macOS
|
||||
dialog->setWindowModality(Qt::ApplicationModal);
|
||||
window.show();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&window));
|
||||
dialog->open();
|
||||
|
||||
// We test that the dialog opens and closes by watching the activation of the
|
||||
// transient parent window. If it doesn't deactivate, then we have to skip.
|
||||
const auto windowActive = [&window]{ return window.isActiveWindow(); };
|
||||
const auto windowInactive = [&window]{ return !window.isActiveWindow(); };
|
||||
if (!QTest::qWaitFor(windowInactive, 2000))
|
||||
QSKIP("Dialog didn't activate");
|
||||
|
||||
// This should destroy the dialog and close the native window
|
||||
child->deleteLater();
|
||||
QTRY_VERIFY(!dialog);
|
||||
// If the native window is still open, then the transient parent can't become
|
||||
// active
|
||||
window.activateWindow();
|
||||
QVERIFY(QTest::qWaitFor(windowActive));
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QColorDialog)
|
||||
#include "tst_qcolordialog.moc"
|
||||
|
@ -128,9 +128,10 @@ private slots:
|
||||
void QTBUG49600_nativeIconProviderCrash();
|
||||
void focusObjectDuringDestruction();
|
||||
|
||||
// NOTE: Please keep widgetlessNativeDialog() as the LAST test!
|
||||
// NOTE: Please keep widgetlessNativeDialog() and
|
||||
// hideNativeByDestruction() as the LAST tests!
|
||||
//
|
||||
// widgetlessNativeDialog() is the only test function that creates
|
||||
// widgetlessNativeDialog() are the only test functions that create
|
||||
// a native file dialog instance. GTK+ versions prior 3.15.5 have
|
||||
// a nasty bug (https://bugzilla.gnome.org/show_bug.cgi?id=725164)
|
||||
// in GtkFileChooserWidget, which makes it leak its folder change
|
||||
@ -141,6 +142,7 @@ private slots:
|
||||
// The crash has been fixed in GTK+ 3.15.5, but the RHEL 7.2 CI has
|
||||
// GTK+ 3.14.13 installed (QTBUG-55276).
|
||||
void widgetlessNativeDialog();
|
||||
void hideNativeByDestruction();
|
||||
|
||||
private:
|
||||
void cleanupSettingsFile();
|
||||
@ -1437,7 +1439,7 @@ void tst_QFiledialog::widgetlessNativeDialog()
|
||||
QSKIP("This platform always uses widgets to realize its QFileDialog, instead of the native file dialog.");
|
||||
#ifdef Q_OS_ANDROID
|
||||
// QTBUG-101194
|
||||
QSKIP("Android: This keeeps the window open. Figure out why.");
|
||||
QSKIP("Android: This keeps the window open. Figure out why.");
|
||||
#endif
|
||||
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, false);
|
||||
QFileDialog fd;
|
||||
@ -1451,6 +1453,46 @@ void tst_QFiledialog::widgetlessNativeDialog()
|
||||
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, true);
|
||||
}
|
||||
|
||||
void tst_QFiledialog::hideNativeByDestruction()
|
||||
{
|
||||
if (!QGuiApplicationPrivate::platformTheme()->usePlatformNativeDialog(QPlatformTheme::FileDialog))
|
||||
QSKIP("This platform always uses widgets to realize its QFileDialog, instead of the native file dialog.");
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
// QTBUG-101194
|
||||
QSKIP("Android: This keeps the native window open. Figure out why.");
|
||||
#endif
|
||||
|
||||
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, false);
|
||||
auto resetAttribute = qScopeGuard([]{
|
||||
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, true);
|
||||
});
|
||||
|
||||
QWidget window;
|
||||
QWidget *child = new QWidget(&window);
|
||||
QPointer<QFileDialog> dialog = new QFileDialog(child);
|
||||
// Make it application modal so that we don't end up with a sheet on macOS
|
||||
dialog->setWindowModality(Qt::ApplicationModal);
|
||||
window.show();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&window));
|
||||
dialog->open();
|
||||
|
||||
// We test that the dialog opens and closes by watching the activation of the
|
||||
// transient parent window. If it doesn't deactivate, then we have to skip.
|
||||
const auto windowActive = [&window]{ return window.isActiveWindow(); };
|
||||
const auto windowInactive = [&window]{ return !window.isActiveWindow(); };
|
||||
if (!QTest::qWaitFor(windowInactive, 2000))
|
||||
QSKIP("Dialog didn't activate");
|
||||
|
||||
// This should destroy the dialog and close the native window
|
||||
child->deleteLater();
|
||||
QTRY_VERIFY(!dialog);
|
||||
// If the native window is still open, then the transient parent can't become
|
||||
// active
|
||||
window.activateWindow();
|
||||
QVERIFY(QTest::qWaitFor(windowActive, 2000));
|
||||
}
|
||||
|
||||
void tst_QFiledialog::selectedFilesWithoutWidgets()
|
||||
{
|
||||
// Test for a crash when widgets are not instantiated yet.
|
||||
|
@ -45,6 +45,7 @@ private slots:
|
||||
void qtbug_41513_stylesheetStyle();
|
||||
#endif
|
||||
|
||||
void hideNativeByDestruction();
|
||||
|
||||
private:
|
||||
void runSlotWithFailsafeTimer(const char *member);
|
||||
@ -238,5 +239,32 @@ void tst_QFontDialog::testNonStandardFontSize()
|
||||
qWarning("Fail using a non-standard font size.");
|
||||
}
|
||||
|
||||
void tst_QFontDialog::hideNativeByDestruction()
|
||||
{
|
||||
QWidget window;
|
||||
QWidget *child = new QWidget(&window);
|
||||
QPointer<QFontDialog> dialog = new QFontDialog(child);
|
||||
// Make it application modal so that we don't end up with a sheet on macOS
|
||||
dialog->setWindowModality(Qt::ApplicationModal);
|
||||
window.show();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&window));
|
||||
dialog->open();
|
||||
|
||||
// We test that the dialog opens and closes by watching the activation of the
|
||||
// transient parent window. If it doesn't deactivate, then we have to skip.
|
||||
const auto windowActive = [&window]{ return window.isActiveWindow(); };
|
||||
const auto windowInactive = [&window]{ return !window.isActiveWindow(); };
|
||||
if (!QTest::qWaitFor(windowInactive, 2000))
|
||||
QSKIP("Dialog didn't activate");
|
||||
|
||||
// This should destroy the dialog and close the native window
|
||||
child->deleteLater();
|
||||
QTRY_VERIFY(!dialog);
|
||||
// If the native window is still open, then the transient parent can't become
|
||||
// active
|
||||
window.activateWindow();
|
||||
QVERIFY(QTest::qWaitFor(windowActive));
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QFontDialog)
|
||||
#include "tst_qfontdialog.moc"
|
||||
|
@ -61,6 +61,8 @@ private slots:
|
||||
void overrideDone_data();
|
||||
void overrideDone();
|
||||
|
||||
void hideNativeByDestruction();
|
||||
|
||||
void cleanup();
|
||||
};
|
||||
|
||||
@ -799,5 +801,32 @@ void tst_QMessageBox::acceptedRejectedSignals_data()
|
||||
addCustomButtonsData();
|
||||
}
|
||||
|
||||
void tst_QMessageBox::hideNativeByDestruction()
|
||||
{
|
||||
QWidget window;
|
||||
QWidget *child = new QWidget(&window);
|
||||
QPointer<QMessageBox> dialog = new QMessageBox(child);
|
||||
// Make it application modal so that we don't end up with a sheet on macOS
|
||||
dialog->setWindowModality(Qt::ApplicationModal);
|
||||
window.show();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&window));
|
||||
dialog->open();
|
||||
|
||||
// We test that the dialog opens and closes by watching the activation of the
|
||||
// transient parent window. If it doesn't deactivate, then we have to skip.
|
||||
const auto windowActive = [&window]{ return window.isActiveWindow(); };
|
||||
const auto windowInactive = [&window]{ return !window.isActiveWindow(); };
|
||||
if (!QTest::qWaitFor(windowInactive, 2000))
|
||||
QSKIP("Dialog didn't activate");
|
||||
|
||||
// This should destroy the dialog and close the native window
|
||||
child->deleteLater();
|
||||
QTRY_VERIFY(!dialog);
|
||||
// If the native window is still open, then the transient parent can't become
|
||||
// active
|
||||
window.activateWindow();
|
||||
QVERIFY(QTest::qWaitFor(windowActive));
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QMessageBox)
|
||||
#include "tst_qmessagebox.moc"
|
||||
|
Loading…
x
Reference in New Issue
Block a user