Fix ref counted window close handling.
authorStephen Kelly <stephen.kelly@kdab.com>
Sat, 11 Feb 2012 00:33:55 +0000 (01:33 +0100)
committerQt by Nokia <qt-info@nokia.com>
Thu, 16 Feb 2012 00:56:27 +0000 (01:56 +0100)
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>
src/corelib/kernel/qcoreapplication.cpp
src/corelib/kernel/qcoreapplication_p.h
src/gui/kernel/qguiapplication.cpp
src/gui/kernel/qguiapplication_p.h
src/gui/kernel/qwindow.cpp
src/widgets/kernel/qapplication.cpp
src/widgets/kernel/qapplication_p.h
src/widgets/kernel/qwidget.cpp
tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp

index cf3ae1b..80e4233 100644 (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));
 }
 
 /*!
index 861a046..112b313 100644 (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();
index 4d80aaf..5f50fe3 100644 (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
index 7fafe03..f86d826 100644 (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;
 
index 61ae2a2..d15198c 100644 (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();
+        }
     }
 
 }
index 4387d2a..b04925d 100644 (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)
index 790176a..e728868 100644 (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();
index f98f7fb..f095e47 100644 (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();
+        }
     }
 
 
index c600956..fcb6b93 100644 (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
-{
-    Q_OBJECT
-public:
-    JobObject(int milliseconds, QObject *parent = 0)
-        : QObject(parent)
-    {
-        QTimer::singleShot(milliseconds, this, SLOT(timeout()));
-    }
-
-    JobObject(QObject *parent = 0)
-        : QObject(parent)
-    {
-        QTimer::singleShot(1000, this, SLOT(timeout()));
-    }
-
-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 tst_QApplication::testQuitLockRef()
-{
-    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
+class NoQuitOnHideWidget : public QWidget
 {
     Q_OBJECT
 public:
-    Result(QObject *parent = 0)
-        : QObject(parent), m_passes(false)
+    NoQuitOnHideWidget(QWidget *parent = 0)
+      : QWidget(parent)
     {
-
-    }
-
-    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);
+        QTimer::singleShot(0, this, SLOT(hide()));
+        QTimer::singleShot(500, this, SLOT(exitApp()));
     }
 
-    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()));
+    void exitApp() {
+      qApp->exit(1);
     }
-
-    bool result() const { return m_result->result(); }
-private:
-    Result * const m_result;
 };
 
-void tst_QApplication::testQuitLock7()
+void tst_QApplication::noQuitOnHide()
 {
-    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