Fix ref counted window close handling.

Instead of refcounting QWindow visibility, we ask the Application
subclass whether quitting is appropriate.

Task-Id: QTBUG-24120
Change-Id: Idd19cc1a3e5742fddded89c7638aaaa5e47c568d
Reviewed-by: Bradley T. Hughes <bradley.hughes@nokia.com>
Reviewed-by: Robin Burchell <robin+qt@viroteck.net>
This commit is contained in:
Stephen Kelly 2012-02-11 01:33:55 +01:00 committed by Qt by Nokia
parent 90feedb642
commit 66603985f2
9 changed files with 79 additions and 522 deletions

View File

@ -1506,8 +1506,14 @@ void QCoreApplicationPrivate::ref()
void QCoreApplicationPrivate::deref()
{
if (!quitLockRef.deref() && in_exec && quitLockRefEnabled)
QCoreApplication::postEvent(qApp, new QEvent(QEvent::Quit));
if (!quitLockRef.deref())
maybeQuit();
}
void QCoreApplicationPrivate::maybeQuit()
{
if (quitLockRef.load() == 0 && in_exec && quitLockRefEnabled && shouldQuit())
QCoreApplication::postEvent(QCoreApplication::instance(), new QEvent(QEvent::Quit));
}
/*!

View File

@ -92,6 +92,10 @@ public:
QAtomicInt quitLockRef;
void ref();
void deref();
virtual bool shouldQuit() {
return true;
}
void maybeQuit();
static QThread *theMainThread;
static QThread *mainThread();

View File

@ -1477,6 +1477,17 @@ void QGuiApplicationPrivate::emitLastWindowClosed()
}
}
bool QGuiApplicationPrivate::shouldQuit()
{
/* if there is no visible top-level window left, we allow the quit */
QWindowList list = QGuiApplication::topLevelWindows();
for (int i = 0; i < list.size(); ++i) {
QWindow *w = list.at(i);
if (w->visible())
return false;
}
return true;
}
/*!
\property QGuiApplication::layoutDirection

View File

@ -74,6 +74,8 @@ public:
virtual void notifyLayoutDirectionChange();
virtual void notifyActiveWindowChange(QWindow *previous);
virtual bool shouldQuit();
static Qt::KeyboardModifiers modifier_buttons;
static Qt::MouseButtons mouse_buttons;

View File

@ -165,14 +165,6 @@ void QWindow::setVisible(bool visible)
return;
d->visible = visible;
emit visibleChanged(visible);
if (QCoreApplication::instance() && !transientParent()) {
QCoreApplicationPrivate *applicationPrivate = static_cast<QCoreApplicationPrivate*>(QObjectPrivate::get(QCoreApplication::instance()));
if (visible) {
applicationPrivate->ref();
} else {
applicationPrivate->deref();
}
}
if (!d->platformWindow)
create();
@ -514,15 +506,6 @@ void QWindow::setTransientParent(QWindow *parent)
QWindow *previousParent = d->transientParent;
d->transientParent = parent;
if (QCoreApplication::instance() && d->visible) {
QCoreApplicationPrivate *applicationPrivate = static_cast<QCoreApplicationPrivate*>(QObjectPrivate::get(QCoreApplication::instance()));
if (parent && !previousParent) {
applicationPrivate->deref();
} else if (!parent && previousParent) {
applicationPrivate->ref();
}
}
}
QWindow *QWindow::transientParent() const
@ -1116,13 +1099,16 @@ void QWindowPrivate::maybeQuitOnLastWindowClosed()
bool lastWindowClosed = true;
for (int i = 0; i < list.size(); ++i) {
QWindow *w = list.at(i);
if (!w->visible() || w->parent())
if (!w->visible())
continue;
lastWindowClosed = false;
break;
}
if (lastWindowClosed)
if (lastWindowClosed) {
QGuiApplicationPrivate::emitLastWindowClosed();
QCoreApplicationPrivate *applicationPrivate = static_cast<QCoreApplicationPrivate*>(QObjectPrivate::get(QCoreApplication::instance()));
applicationPrivate->maybeQuit();
}
}
}

View File

@ -67,6 +67,7 @@
#include "private/qstylesheetstyle_p.h"
#include "private/qstyle_p.h"
#include "qmessagebox.h"
#include "qwidgetwindow_qpa_p.h"
#include <QtWidgets/qgraphicsproxywidget.h>
#include <QtGui/qstylehints.h>
#include <QtGui/qinputmethod.h>
@ -3289,6 +3290,20 @@ int QApplication::exec()
return QGuiApplication::exec();
}
bool QApplicationPrivate::shouldQuit()
{
/* if there is no non-withdrawn primary window left (except
the ones without QuitOnClose), we emit the lastWindowClosed
signal */
QWidgetList list = QApplication::topLevelWidgets();
for (int i = 0; i < list.size(); ++i) {
QWidget *w = list.at(i);
if (w->isVisible() && !w->parentWidget() && w->testAttribute(Qt::WA_QuitOnClose))
return false;
}
return true;
}
/*! \reimp
*/
bool QApplication::notify(QObject *receiver, QEvent *e)

View File

@ -178,6 +178,8 @@ public:
virtual void notifyLayoutDirectionChange();
virtual void notifyActiveWindowChange(QWindow *);
virtual bool shouldQuit();
#if defined(Q_WS_X11)
#ifndef QT_NO_SETTINGS
static bool x11_apply_settings();

View File

@ -7410,8 +7410,24 @@ bool QWidgetPrivate::close_helper(CloseMode mode)
// Attempt to close the application only if this has WA_QuitOnClose set and a non-visible parent
quitOnClose = quitOnClose && (parentWidget.isNull() || !parentWidget->isVisible());
if (quitOnClose && q->windowHandle()) {
static_cast<QWindowPrivate*>(QObjectPrivate::get(q->windowHandle()))->maybeQuitOnLastWindowClosed();
if (quitOnClose) {
/* if there is no non-withdrawn primary window left (except
the ones without QuitOnClose), we emit the lastWindowClosed
signal */
QWidgetList list = QApplication::topLevelWidgets();
bool lastWindowClosed = true;
for (int i = 0; i < list.size(); ++i) {
QWidget *w = list.at(i);
if (!w->isVisible() || w->parentWidget() || !w->testAttribute(Qt::WA_QuitOnClose))
continue;
lastWindowClosed = false;
break;
}
if (lastWindowClosed) {
QGuiApplicationPrivate::emitLastWindowClosed();
QCoreApplicationPrivate *applicationPrivate = static_cast<QCoreApplicationPrivate*>(QObjectPrivate::get(QCoreApplication::instance()));
applicationPrivate->maybeQuit();
}
}

View File

@ -137,16 +137,7 @@ private slots:
void touchEventPropagation();
void qtbug_12673();
void testQuitLockRef();
void testQuitLock1();
void testQuitLock2();
void testQuitLock3();
void testQuitLock4();
void testQuitLock5();
void testQuitLock6();
void testQuitLock7();
void testQuitLock8();
void noQuitOnHide();
void globalStaticObjectDestruction(); // run this last
@ -2060,506 +2051,30 @@ void tst_QApplication::qtbug_12673()
QCOMPARE(testProcess.exitStatus(), QProcess::NormalExit);
}
class JobObject : public QObject
class NoQuitOnHideWidget : public QWidget
{
Q_OBJECT
public:
JobObject(int milliseconds, QObject *parent = 0)
: QObject(parent)
NoQuitOnHideWidget(QWidget *parent = 0)
: QWidget(parent)
{
QTimer::singleShot(milliseconds, this, SLOT(timeout()));
}
JobObject(QObject *parent = 0)
: QObject(parent)
{
QTimer::singleShot(1000, this, SLOT(timeout()));
QTimer::singleShot(0, this, SLOT(hide()));
QTimer::singleShot(500, this, SLOT(exitApp()));
}
private slots:
void timeout()
{
emit done();
deleteLater();
}
signals:
void done();
private:
QEventLoopLocker locker;
};
class QuitLockRefTester : public QObject
{
Q_OBJECT
public:
QuitLockRefTester(QObject *parent = 0)
: QObject(parent)
{
QTimer::singleShot(0, this, SLOT(doTest()));
}
private slots:
void doTest()
{
QApplicationPrivate *privateClass = static_cast<QApplicationPrivate*>(QObjectPrivate::get(qApp));
{
QDialog *win1 = new QDialog;
// Test with a lock active so that the refcount doesn't drop to zero during these tests, causing a quit.
// (until we exit the scope)
QEventLoopLocker locker;
QCOMPARE(privateClass->quitLockRef.load(), 1);
win1->show();
QCOMPARE(privateClass->quitLockRef.load(), 2);
QDialog *win2 = new QDialog;
win2->show();
QCOMPARE(privateClass->quitLockRef.load(), 3);
delete win1;
QCOMPARE(privateClass->quitLockRef.load(), 2);
delete win2;
QCOMPARE(privateClass->quitLockRef.load(), 1);
win1 = new QDialog;
win1->show();
QCOMPARE(privateClass->quitLockRef.load(), 2);
JobObject *job1 = new JobObject(this);
QCOMPARE(privateClass->quitLockRef.load(), 3);
delete win1;
QCOMPARE(privateClass->quitLockRef.load(), 2);
delete job1;
QCOMPARE(privateClass->quitLockRef.load(), 1);
QWidget *w1 = new QWidget;
w1->show();
QCOMPARE(privateClass->quitLockRef.load(), 2);
QWidget *w2 = new QMainWindow;
w2->show();
QCOMPARE(privateClass->quitLockRef.load(), 3);
QWidget *w3 = new QWidget(0, Qt::Dialog);
w3->show();
QCOMPARE(privateClass->quitLockRef.load(), 4);
delete w3;
QCOMPARE(privateClass->quitLockRef.load(), 3);
delete w2;
QCOMPARE(privateClass->quitLockRef.load(), 2);
QWidget *subWidget1 = new QWidget(w1, Qt::Window);
// Even though We create a new widget and show it,
// the ref count does not go up because it is a child of
// w1, which is the top-level, and what we are actually
// refcounting.
subWidget1->show();
QCOMPARE(privateClass->quitLockRef.load(), 2);
// When we use setParent(0) and re-show, the
// ref count does increase:
QCOMPARE(subWidget1->isVisible(), true);
subWidget1->setParent(0);
QCOMPARE(subWidget1->isVisible(), false);
QCOMPARE(privateClass->quitLockRef.load(), 2);
subWidget1->show();
QCOMPARE(subWidget1->isVisible(), true);
QCOMPARE(privateClass->quitLockRef.load(), 3);
subWidget1->setParent(w1);
QCOMPARE(privateClass->quitLockRef.load(), 2);
QWidget *subWidget2 = new QWidget(w1);
QCOMPARE(privateClass->quitLockRef.load(), 2);
subWidget2->show();
QCOMPARE(privateClass->quitLockRef.load(), 2);
delete subWidget2;
QCOMPARE(privateClass->quitLockRef.load(), 2);
QWidget *subWidget3 = new QWidget(w1);
QCOMPARE(privateClass->quitLockRef.load(), 2);
subWidget3->show();
QCOMPARE(privateClass->quitLockRef.load(), 2);
subWidget3->hide();
QCOMPARE(privateClass->quitLockRef.load(), 2);
delete subWidget3;
QCOMPARE(privateClass->quitLockRef.load(), 2);
QWidget *subWidget4 = new QWidget(subWidget1);
QWidget *subWidget5 = new QWidget(subWidget1);
QCOMPARE(privateClass->quitLockRef.load(), 2);
QWidget *subWidget6 = new QWidget(subWidget4, Qt::Window);
subWidget6->show();
QCOMPARE(privateClass->quitLockRef.load(), 2);
delete w1;
QCOMPARE(privateClass->quitLockRef.load(), 1);
w1 = new QWidget;
w2 = new QWidget;
w3 = new QWidget;
QHBoxLayout *layout = new QHBoxLayout(w1);
layout->addWidget(w2);
layout->addWidget(w3);
QCOMPARE(privateClass->quitLockRef.load(), 1);
w1->show();
QCOMPARE(privateClass->quitLockRef.load(), 2);
w1->hide();
QCOMPARE(privateClass->quitLockRef.load(), 1);
delete w1;
}
QCOMPARE(privateClass->quitLockRef.load(), 0);
void exitApp() {
qApp->exit(1);
}
};
void tst_QApplication::testQuitLockRef()
void tst_QApplication::noQuitOnHide()
{
int argc = 1;
char *argv[] = { "tst_qapplication" };
QApplication app(argc, argv);
QuitLockRefTester tester;
app.exec();
}
void tst_QApplication::testQuitLock1()
{
int argc = 1;
char *argv[] = { "tst_qcoreapplication" };
QApplication app(argc, argv);
QWidget *w = new QWidget;
w->show();
QMetaObject::invokeMethod(w, "close", Qt::QueuedConnection);
app.exec();
// No hang = pass.
}
void tst_QApplication::testQuitLock2()
{
int argc = 1;
char *argv[] = { "tst_qcoreapplication" };
QApplication app(argc, argv);
QWidget *w1 = new QWidget;
w1->show();
QWidget *w2 = new QWidget;
w2->show();
QMetaObject::invokeMethod(w1, "deleteLater", Qt::QueuedConnection);
QMetaObject::invokeMethod(w2, "hide", Qt::QueuedConnection);
app.exec();
// No hang = pass.
}
class Result : public QObject
{
Q_OBJECT
public:
Result(QObject *parent = 0)
: QObject(parent), m_passes(false)
{
}
bool result() const
{
return m_passes;
}
public slots:
void setPasses()
{
setResult(true);
}
void setFails()
{
setResult(false);
}
void setResult(bool result)
{
m_passes = result;
}
private:
bool m_passes;
};
void tst_QApplication::testQuitLock3()
{
int argc = 1;
char *argv[] = { "tst_qcoreapplication" };
QApplication app(argc, argv);
Result *result = new Result(&app);
JobObject *job = new JobObject(&app);
QObject::connect(job, SIGNAL(done()), result, SLOT(setPasses()));
app.exec();
QVERIFY(result->result());
}
void tst_QApplication::testQuitLock4()
{
int argc = 1;
char *argv[] = { "tst_qcoreapplication" };
QApplication app(argc, argv);
QWidget *w = new QWidget;
w->show();
Result *result = new Result(&app);
JobObject *job = new JobObject(1000, &app);
QTimer::singleShot(500, w, SLOT(deleteLater()));
QObject::connect(w, SIGNAL(destroyed()), result, SLOT(setFails()));
QObject::connect(job, SIGNAL(done()), result, SLOT(setPasses()));
app.exec();
QVERIFY(result->result());
}
class JobBeforeWindowRunner : public QObject
{
Q_OBJECT
public:
JobBeforeWindowRunner(QObject *parent = 0)
: QObject(parent), m_result(new Result(this))
{
}
void start()
{
JobObject *job = new JobObject(this);
connect(job, SIGNAL(done()), m_result, SLOT(setFails()));
connect(job, SIGNAL(destroyed()), SLOT(showWindowDelayed()), Qt::QueuedConnection);
}
bool result() const { return m_result->result(); }
private slots:
void showWindowDelayed()
{
qApp->setQuitLockEnabled(true);
QTimer::singleShot(500, this, SLOT(showWindow()));
}
void showWindow()
{
QWidget *w = new QWidget;
w->show();
w->deleteLater();
connect(w, SIGNAL(destroyed()), m_result, SLOT(setPasses()));
}
private:
Result * const m_result;
};
void tst_QApplication::testQuitLock5()
{
int argc = 1;
char *argv[] = { "tst_qcoreapplication" };
QApplication app(argc, argv);
app.setQuitLockEnabled(false);
// Run a job before showing a window, and only enable the refcounting
// after doing so.
// Although the job brings the refcount to zero, the app does not exit
// until setQuitLockEnabled is called and the feature re-enabled.
JobBeforeWindowRunner *eventRunner = new JobBeforeWindowRunner(&app);
eventRunner->start();
app.exec();
QVERIFY(eventRunner->result());
}
class JobDuringWindowRunner : public QObject
{
Q_OBJECT
public:
JobDuringWindowRunner(QObject *parent = 0)
: QObject(parent), m_result(new Result(this))
{
}
void start()
{
JobObject *job = new JobObject(this);
QWidget *w = new QWidget;
w->show();
w->deleteLater();
QObject::connect(w, SIGNAL(destroyed()), m_result, SLOT(setFails()));
QObject::connect(job, SIGNAL(done()), m_result, SLOT(setPasses()));
}
bool result() const { return m_result->result(); }
private:
Result * const m_result;
};
void tst_QApplication::testQuitLock6()
{
int argc = 1;
char *argv[] = { "tst_qcoreapplication" };
QApplication app(argc, argv);
// A job runs, and while it is running, a window is shown and closed,
// then the job ends, which causes the quit.
JobDuringWindowRunner *eventRunner = new JobDuringWindowRunner(&app);
eventRunner->start();
app.exec();
QVERIFY(eventRunner->result());
}
class JobWindowJobWindowRunner : public QObject
{
Q_OBJECT
public:
JobWindowJobWindowRunner(QObject *parent = 0)
: QObject(parent), m_result(new Result(this))
{
}
void start()
{
JobObject *job = new JobObject(500, this);
QWidget *w = new QWidget;
w->show();
QTimer::singleShot(1000, w, SLOT(deleteLater()));
QObject::connect(w, SIGNAL(destroyed()), m_result, SLOT(setPasses()));
QObject::connect(job, SIGNAL(done()), m_result, SLOT(setFails()));
}
bool result() const { return m_result->result(); }
private:
Result * const m_result;
};
void tst_QApplication::testQuitLock7()
{
int argc = 1;
char *argv[] = { "tst_qcoreapplication" };
QApplication app(argc, argv);
// A job runs, and while it is running, a window is shown
// then the job ends, then the window is closed, which causes the quit.
JobWindowJobWindowRunner *eventRunner = new JobWindowJobWindowRunner(&app);
eventRunner->start();
app.exec();
QVERIFY(eventRunner->result());
}
void tst_QApplication::testQuitLock8()
{
int argc = 1;
char *argv[] = { "tst_qcoreapplication" };
QApplication app(argc, argv);
QMainWindow *mw1 = new QMainWindow;
mw1->show();
QMainWindow *mw2 = new QMainWindow;
mw2->show();
QMetaObject::invokeMethod(mw1, "close", Qt::QueuedConnection);
QMetaObject::invokeMethod(mw2, "close", Qt::QueuedConnection);
app.exec();
// No hang = pass
int argc = 0;
QApplication app(argc, 0);
QWidget *window1 = new NoQuitOnHideWidget(false);
window1->show();
QCOMPARE(app.exec(), 1);
}
class ShowCloseShowWidget : public QWidget