summaryrefslogtreecommitdiffstats
path: root/tests/auto/widgets/widgets/qdockwidget/tst_qdockwidget.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/widgets/widgets/qdockwidget/tst_qdockwidget.cpp')
-rw-r--r--tests/auto/widgets/widgets/qdockwidget/tst_qdockwidget.cpp1134
1 files changed, 1040 insertions, 94 deletions
diff --git a/tests/auto/widgets/widgets/qdockwidget/tst_qdockwidget.cpp b/tests/auto/widgets/widgets/qdockwidget/tst_qdockwidget.cpp
index d726807b7c..63a432578e 100644
--- a/tests/auto/widgets/widgets/qdockwidget/tst_qdockwidget.cpp
+++ b/tests/auto/widgets/widgets/qdockwidget/tst_qdockwidget.cpp
@@ -1,42 +1,25 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 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:GPL-EXCEPT$
-** 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.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QSignalSpy>
-
#include <qaction.h>
#include <qdockwidget.h>
#include <qmainwindow.h>
+#include "private/qdockwidget_p.h"
+#include "private/qmainwindowlayout_p.h"
+#include <QAbstractButton>
#include <qlineedit.h>
+#include <QtGui/qpa/qplatformwindow.h>
#include <qtabbar.h>
#include <QScreen>
+#include <QTimer>
#include <QtGui/QPainter>
-#include "private/qdockwidget_p.h"
+#include <QLabel>
+
+Q_LOGGING_CATEGORY(lcTestDockWidget, "qt.widgets.tests.qdockwidget")
+
+#include <QtWidgets/private/qapplication_p.h>
bool hasFeature(QDockWidget *dockwidget, QDockWidget::DockWidgetFeature feature)
{ return (dockwidget->features() & feature) == feature; }
@@ -70,6 +53,7 @@ private slots:
void restoreDockWidget();
void restoreStateWhileStillFloating();
void setWindowTitle();
+
// task specific tests:
void task165177_deleteFocusWidget();
void task169808_setFloating();
@@ -78,6 +62,98 @@ private slots:
void task258459_visibilityChanged();
void taskQTBUG_1665_closableChanged();
void taskQTBUG_9758_undockedGeometry();
+
+ // Dock area permissions for DockWidgets and DockWidgetGroupWindows
+ void dockPermissions();
+
+ // test floating tabs, item_tree and window title consistency
+ void floatingTabs();
+ void hoverWithoutDrop();
+
+ // floating tab gets removed, when last child goes away
+ void deleteFloatingTabWithSingleDockWidget_data();
+ void deleteFloatingTabWithSingleDockWidget();
+
+ // test hide & show
+ void hideAndShow();
+
+ // test closing and deleting consistency
+ void closeAndDelete();
+ void closeUnclosable();
+
+ // test save and restore consistency
+ void saveAndRestore();
+
+private:
+ // helpers and consts for dockPermissions, hideAndShow, closeAndDelete
+#ifdef QT_BUILD_INTERNAL
+ void createTestWidgets(QMainWindow* &MainWindow, QPointer<QWidget> &cent,
+ QPointer<QDockWidget> &d1, QPointer<QDockWidget> &d2) const;
+
+ void unplugAndResize(QMainWindow* MainWindow, QDockWidget* dw, QPoint home, QSize size) const;
+
+ void createFloatingTabs(QMainWindow* &MainWindow, QPointer<QWidget> &cent,
+ QPointer<QDockWidget> &d1, QPointer<QDockWidget> &d2,
+ QList<int> &path1, QList<int> &path2) const;
+
+ static inline QPoint dragPoint(QDockWidget* dockWidget);
+ static inline QPoint home1(QMainWindow* MainWindow)
+ { return MainWindow->mapToGlobal(MainWindow->rect().topLeft() + QPoint(0.1 * MainWindow->width(), 0.1 * MainWindow->height())); }
+
+ static inline QPoint home2(QMainWindow* MainWindow)
+ { return MainWindow->mapToGlobal(MainWindow->rect().topLeft() + QPoint(0.6 * MainWindow->width(), 0.15 * MainWindow->height())); }
+
+ static inline QSize size1(QMainWindow* MainWindow)
+ { return QSize (0.2 * MainWindow->width(), 0.25 * MainWindow->height()); }
+
+ static inline QSize size2(QMainWindow* MainWindow)
+ { return QSize (0.1 * MainWindow->width(), 0.15 * MainWindow->height()); }
+
+ static inline QPoint dockPoint(QMainWindow* mw, Qt::DockWidgetArea area)
+ { return mw->mapToGlobal(qobject_cast<QMainWindowLayout*>(mw->layout())->dockWidgetAreaRect(area, QMainWindowLayout::Maximum).center()); }
+
+ bool checkFloatingTabs(QMainWindow* MainWindow, QPointer<QDockWidgetGroupWindow> &ftabs, const QList<QDockWidget*> &dwList = {}) const;
+
+ // move a dock widget
+ enum class MoveDockWidgetRule {
+ Drop,
+ Abort
+ };
+
+ void moveDockWidget(QDockWidget* dw, QPoint to, QPoint from, MoveDockWidgetRule rule) const;
+
+#ifdef QT_BUILD_INTERNAL
+ // Message handling for xcb error QTBUG 82059
+ static void xcbMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
+
+ enum class ChildRemovalReason {
+ Destroyed,
+ Closed,
+ Reparented
+ };
+
+public:
+ bool xcbError = false;
+ bool platformSupportingRaise = true;
+#endif
+private:
+
+#ifdef QT_DEBUG
+ // Grace time between mouse events. Set to 400 for debugging.
+ const int waitingTime = 400;
+
+ // Waiting time before closing widgets successful test. Set to 20000 for debugging.
+ const int waitBeforeClose = 0;
+
+ // Enable logging
+ const bool dockWidgetLog = true;
+#else
+ const int waitingTime = 15;
+ const int waitBeforeClose = 0;
+ const bool dockWidgetLog = false;
+#endif // QT_DEBUG
+#endif // QT_BUILD_INTERNAL
+
};
// Testing get/set functions
@@ -219,12 +295,12 @@ void tst_QDockWidget::features()
QVERIFY(!hasFeature(&dw, QDockWidget::DockWidgetClosable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetMovable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetFloatable));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE((int)*(static_cast<const QDockWidget::DockWidgetFeature *>(spy.at(0).value(0).constData())),
(int)dw.features());
spy.clear();
dw.setFeatures(dw.features());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
spy.clear();
setFeature(&dw, QDockWidget::DockWidgetClosable);
@@ -232,12 +308,12 @@ void tst_QDockWidget::features()
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetClosable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetMovable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetFloatable));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE((int)*static_cast<const QDockWidget::DockWidgetFeature *>(spy.at(0).value(0).constData()),
(int)dw.features());
spy.clear();
dw.setFeatures(dw.features());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
spy.clear();
setFeature(&dw, QDockWidget::DockWidgetMovable, false);
@@ -245,12 +321,12 @@ void tst_QDockWidget::features()
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetClosable));
QVERIFY(!hasFeature(&dw, QDockWidget::DockWidgetMovable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetFloatable));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE((int)*static_cast<const QDockWidget::DockWidgetFeature *>(spy.at(0).value(0).constData()),
(int)dw.features());
spy.clear();
dw.setFeatures(dw.features());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
spy.clear();
setFeature(&dw, QDockWidget::DockWidgetMovable);
@@ -258,12 +334,12 @@ void tst_QDockWidget::features()
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetClosable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetMovable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetFloatable));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE((int)*static_cast<const QDockWidget::DockWidgetFeature *>(spy.at(0).value(0).constData()),
(int)dw.features());
spy.clear();
dw.setFeatures(dw.features());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
spy.clear();
setFeature(&dw, QDockWidget::DockWidgetFloatable, false);
@@ -271,12 +347,12 @@ void tst_QDockWidget::features()
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetClosable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetMovable));
QVERIFY(!hasFeature(&dw, QDockWidget::DockWidgetFloatable));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE((int)*static_cast<const QDockWidget::DockWidgetFeature *>(spy.at(0).value(0).constData()),
(int)dw.features());
spy.clear();
dw.setFeatures(dw.features());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
spy.clear();
setFeature(&dw, QDockWidget::DockWidgetFloatable);
@@ -284,12 +360,12 @@ void tst_QDockWidget::features()
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetClosable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetMovable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetFloatable));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE((int)*static_cast<const QDockWidget::DockWidgetFeature *>(spy.at(0).value(0).constData()),
(int)dw.features());
spy.clear();
dw.setFeatures(dw.features());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
spy.clear();
// set all at once
@@ -298,12 +374,12 @@ void tst_QDockWidget::features()
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetClosable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetMovable));
QVERIFY(!hasFeature(&dw, QDockWidget::DockWidgetFloatable));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE((int)*static_cast<const QDockWidget::DockWidgetFeature *>(spy.at(0).value(0).constData()),
(int)dw.features());
spy.clear();
dw.setFeatures(dw.features());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
spy.clear();
dw.setFeatures(QDockWidget::DockWidgetClosable);
@@ -311,12 +387,12 @@ void tst_QDockWidget::features()
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetClosable));
QVERIFY(!hasFeature(&dw, QDockWidget::DockWidgetMovable));
QVERIFY(!hasFeature(&dw, QDockWidget::DockWidgetFloatable));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE((int)*static_cast<const QDockWidget::DockWidgetFeature *>(spy.at(0).value(0).constData()),
(int)dw.features());
spy.clear();
dw.setFeatures(dw.features());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
spy.clear();
dw.setFeatures(allDockWidgetFeatures);
@@ -324,12 +400,12 @@ void tst_QDockWidget::features()
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetClosable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetMovable));
QVERIFY(hasFeature(&dw, QDockWidget::DockWidgetFloatable));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE((int)*static_cast<const QDockWidget::DockWidgetFeature *>(spy.at(0).value(0).constData()),
(int)dw.features());
spy.clear();
dw.setFeatures(dw.features());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
spy.clear();
}
@@ -356,20 +432,20 @@ void tst_QDockWidget::setFloating()
QVERIFY((dockedPosition - floatingPosition).manhattanLength() < 50);
QVERIFY(dw.isFloating());
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).value(0).toBool(), dw.isFloating());
spy.clear();
dw.setFloating(dw.isFloating());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
spy.clear();
dw.setFloating(false);
QVERIFY(!dw.isFloating());
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).value(0).toBool(), dw.isFloating());
spy.clear();
dw.setFloating(dw.isFloating());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
spy.clear();
}
@@ -393,12 +469,12 @@ void tst_QDockWidget::allowedAreas()
QVERIFY(!dw.isAreaAllowed(Qt::RightDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::TopDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::BottomDockWidgetArea));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
dw.allowedAreas());
spy.clear();
dw.setAllowedAreas(dw.allowedAreas());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
dw.setAllowedAreas(Qt::RightDockWidgetArea);
QCOMPARE(dw.allowedAreas(), Qt::RightDockWidgetArea);
@@ -406,12 +482,12 @@ void tst_QDockWidget::allowedAreas()
QVERIFY(dw.isAreaAllowed(Qt::RightDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::TopDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::BottomDockWidgetArea));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
dw.allowedAreas());
spy.clear();
dw.setAllowedAreas(dw.allowedAreas());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
dw.setAllowedAreas(Qt::TopDockWidgetArea);
QCOMPARE(dw.allowedAreas(), Qt::TopDockWidgetArea);
@@ -419,12 +495,12 @@ void tst_QDockWidget::allowedAreas()
QVERIFY(!dw.isAreaAllowed(Qt::RightDockWidgetArea));
QVERIFY(dw.isAreaAllowed(Qt::TopDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::BottomDockWidgetArea));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
dw.allowedAreas());
spy.clear();
dw.setAllowedAreas(dw.allowedAreas());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
dw.setAllowedAreas(Qt::BottomDockWidgetArea);
QCOMPARE(dw.allowedAreas(), Qt::BottomDockWidgetArea);
@@ -432,12 +508,12 @@ void tst_QDockWidget::allowedAreas()
QVERIFY(!dw.isAreaAllowed(Qt::RightDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::TopDockWidgetArea));
QVERIFY(dw.isAreaAllowed(Qt::BottomDockWidgetArea));
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
dw.allowedAreas());
spy.clear();
dw.setAllowedAreas(dw.allowedAreas());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
// multiple dock window areas
dw.setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
@@ -446,12 +522,13 @@ void tst_QDockWidget::allowedAreas()
QVERIFY(!dw.isAreaAllowed(Qt::RightDockWidgetArea));
QVERIFY(dw.isAreaAllowed(Qt::TopDockWidgetArea));
QVERIFY(dw.isAreaAllowed(Qt::BottomDockWidgetArea));
- QCOMPARE(spy.count(), 1);
+ //QVERIFY(!dw.isAreaAllowed(Qt::FloatingDockWidgetArea));
+ QCOMPARE(spy.size(), 1);
QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
dw.allowedAreas());
spy.clear();
dw.setAllowedAreas(dw.allowedAreas());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
dw.setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
QCOMPARE(dw.allowedAreas(), Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
@@ -459,12 +536,13 @@ void tst_QDockWidget::allowedAreas()
QVERIFY(dw.isAreaAllowed(Qt::RightDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::TopDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::BottomDockWidgetArea));
- QCOMPARE(spy.count(), 1);
+ //QVERIFY(!dw.isAreaAllowed(Qt::FloatingDockWidgetArea));
+ QCOMPARE(spy.size(), 1);
QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
dw.allowedAreas());
spy.clear();
dw.setAllowedAreas(dw.allowedAreas());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
dw.setAllowedAreas(Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea);
QCOMPARE(dw.allowedAreas(), Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea);
@@ -472,12 +550,28 @@ void tst_QDockWidget::allowedAreas()
QVERIFY(!dw.isAreaAllowed(Qt::RightDockWidgetArea));
QVERIFY(dw.isAreaAllowed(Qt::TopDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::BottomDockWidgetArea));
- QCOMPARE(spy.count(), 1);
+ //QVERIFY(!dw.isAreaAllowed(Qt::FloatingDockWidgetArea));
+ QCOMPARE(spy.size(), 1);
+ QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
+ dw.allowedAreas());
+ spy.clear();
+ dw.setAllowedAreas(dw.allowedAreas());
+ QCOMPARE(spy.size(), 0);
+
+ //dw.setAllowedAreas(Qt::BottomDockWidgetArea | Qt::FloatingDockWidgetArea);
+ dw.setAllowedAreas(Qt::BottomDockWidgetArea);
+ //QCOMPARE(dw.allowedAreas(), Qt::BottomDockWidgetArea | Qt::FloatingDockWidgetArea);
+ QVERIFY(!dw.isAreaAllowed(Qt::LeftDockWidgetArea));
+ QVERIFY(!dw.isAreaAllowed(Qt::RightDockWidgetArea));
+ QVERIFY(!dw.isAreaAllowed(Qt::TopDockWidgetArea));
+ QVERIFY(dw.isAreaAllowed(Qt::BottomDockWidgetArea));
+ //QVERIFY(dw.isAreaAllowed(Qt::FloatingDockWidgetArea));
+ QCOMPARE(spy.size(), 1);
QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
dw.allowedAreas());
spy.clear();
dw.setAllowedAreas(dw.allowedAreas());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
dw.setAllowedAreas(Qt::BottomDockWidgetArea | Qt::RightDockWidgetArea);
QCOMPARE(dw.allowedAreas(), Qt::BottomDockWidgetArea | Qt::RightDockWidgetArea);
@@ -485,12 +579,13 @@ void tst_QDockWidget::allowedAreas()
QVERIFY(dw.isAreaAllowed(Qt::RightDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::TopDockWidgetArea));
QVERIFY(dw.isAreaAllowed(Qt::BottomDockWidgetArea));
- QCOMPARE(spy.count(), 1);
+ //QVERIFY(!dw.isAreaAllowed(Qt::FloatingDockWidgetArea));
+ QCOMPARE(spy.size(), 1);
QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
dw.allowedAreas());
spy.clear();
dw.setAllowedAreas(dw.allowedAreas());
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
}
void tst_QDockWidget::toggleViewAction()
@@ -518,65 +613,65 @@ void tst_QDockWidget::visibilityChanged()
mw.addDockWidget(Qt::LeftDockWidgetArea, &dw);
mw.show();
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).at(0).toBool(), true);
spy.clear();
dw.hide();
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).at(0).toBool(), false);
spy.clear();
dw.hide();
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
dw.show();
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).at(0).toBool(), true);
spy.clear();
dw.show();
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
QDockWidget dw2;
mw.tabifyDockWidget(&dw, &dw2);
dw2.show();
dw2.raise();
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).at(0).toBool(), false);
spy.clear();
dw2.hide();
qApp->processEvents();
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).at(0).toBool(), true);
spy.clear();
dw2.show();
dw2.raise();
qApp->processEvents();
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).at(0).toBool(), false);
spy.clear();
dw.raise();
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).at(0).toBool(), true);
spy.clear();
dw.raise();
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
dw2.raise();
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).at(0).toBool(), false);
spy.clear();
dw2.raise();
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
mw.addDockWidget(Qt::RightDockWidgetArea, &dw2);
- QTRY_COMPARE(spy.count(), 1);
+ QTRY_COMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).at(0).toBool(), true);
}
@@ -603,6 +698,9 @@ void tst_QDockWidget::updateTabBarOnVisibilityChanged()
mw.tabifyDockWidget(dw1, dw2);
mw.tabifyDockWidget(dw2, dw3);
+ const auto list1 = QList<QDockWidget *>{dw1, dw2, dw3};
+ QCOMPARE(mw.tabifiedDockWidgets(dw0), list1);
+
QTabBar *tabBar = mw.findChild<QTabBar *>();
QVERIFY(tabBar);
tabBar->setCurrentIndex(2);
@@ -616,6 +714,8 @@ void tst_QDockWidget::updateTabBarOnVisibilityChanged()
dw1->hide();
QTRY_COMPARE(tabBar->count(), 2);
QCOMPARE(tabBar->currentIndex(), 0);
+
+ QCOMPARE(mw.tabifiedDockWidgets(dw2), {dw3});
}
Q_DECLARE_METATYPE(Qt::DockWidgetArea)
@@ -630,56 +730,56 @@ void tst_QDockWidget::dockLocationChanged()
QSignalSpy spy(&dw, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)));
mw.addDockWidget(Qt::LeftDockWidgetArea, &dw);
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(qvariant_cast<Qt::DockWidgetArea>(spy.at(0).at(0)),
Qt::LeftDockWidgetArea);
spy.clear();
mw.addDockWidget(Qt::LeftDockWidgetArea, &dw);
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(qvariant_cast<Qt::DockWidgetArea>(spy.at(0).at(0)),
Qt::LeftDockWidgetArea);
spy.clear();
mw.addDockWidget(Qt::RightDockWidgetArea, &dw);
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(qvariant_cast<Qt::DockWidgetArea>(spy.at(0).at(0)),
Qt::RightDockWidgetArea);
spy.clear();
mw.removeDockWidget(&dw);
- QCOMPARE(spy.count(), 0);
+ QCOMPARE(spy.size(), 0);
QDockWidget dw2;
dw2.setObjectName("dock2");
mw.addDockWidget(Qt::TopDockWidgetArea, &dw2);
mw.tabifyDockWidget(&dw2, &dw);
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(qvariant_cast<Qt::DockWidgetArea>(spy.at(0).at(0)),
Qt::TopDockWidgetArea);
spy.clear();
mw.splitDockWidget(&dw2, &dw, Qt::Horizontal);
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(qvariant_cast<Qt::DockWidgetArea>(spy.at(0).at(0)),
Qt::TopDockWidgetArea);
spy.clear();
dw.setFloating(true);
- QTRY_COMPARE(spy.count(), 1);
+ QTRY_COMPARE(spy.size(), 1);
QCOMPARE(qvariant_cast<Qt::DockWidgetArea>(spy.at(0).at(0)),
Qt::NoDockWidgetArea);
spy.clear();
dw.setFloating(false);
- QTRY_COMPARE(spy.count(), 1);
+ QTRY_COMPARE(spy.size(), 1);
QCOMPARE(qvariant_cast<Qt::DockWidgetArea>(spy.at(0).at(0)),
Qt::TopDockWidgetArea);
spy.clear();
QByteArray ba = mw.saveState();
mw.restoreState(ba);
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(qvariant_cast<Qt::DockWidgetArea>(spy.at(0).at(0)),
Qt::TopDockWidgetArea);
}
@@ -950,9 +1050,9 @@ void tst_QDockWidget::task258459_visibilityChanged()
QSignalSpy spy2(&dock2, SIGNAL(visibilityChanged(bool)));
win.show();
QVERIFY(QTest::qWaitForWindowActive(&win));
- QCOMPARE(spy1.count(), 1);
+ QCOMPARE(spy1.size(), 1);
QCOMPARE(spy1.first().first().toBool(), false); //dock1 is invisible
- QCOMPARE(spy2.count(), 1);
+ QCOMPARE(spy2.size(), 1);
QCOMPARE(spy2.first().first().toBool(), true); //dock1 is visible
}
@@ -1002,9 +1102,10 @@ void tst_QDockWidget::setWindowTitle()
QMainWindow window;
QDockWidget dock1(&window);
QDockWidget dock2(&window);
- const QString dock1Title = QStringLiteral("&Window");
- const QString dock2Title = QStringLiteral("&Modifiable Window [*]");
+ constexpr QLatin1StringView dock1Title("&Window");
+ constexpr QLatin1StringView dock2Title("&Modifiable Window [*]");
+ // Set title on docked dock widgets, before main window is shown
dock1.setWindowTitle(dock1Title);
dock2.setWindowTitle(dock2Title);
window.addDockWidget(Qt::RightDockWidgetArea, &dock1);
@@ -1015,6 +1116,7 @@ void tst_QDockWidget::setWindowTitle()
QCOMPARE(dock1.windowTitle(), dock1Title);
QCOMPARE(dock2.windowTitle(), dock2Title);
+ // Check if title remains unchanged when docking / undocking
dock1.setFloating(true);
dock1.show();
QVERIFY(QTest::qWaitForWindowExposed(&dock1));
@@ -1024,12 +1126,16 @@ void tst_QDockWidget::setWindowTitle()
dock1.setFloating(true);
dock1.show();
QVERIFY(QTest::qWaitForWindowExposed(&dock1));
- const QString changed = QStringLiteral("Changed ");
+
+ // Change a floating dock widget's title and check remains unchanged when docking
+ constexpr QLatin1StringView changed("Changed ");
dock1.setWindowTitle(QString(changed + dock1Title));
QCOMPARE(dock1.windowTitle(), QString(changed + dock1Title));
dock1.setFloating(false);
+ QVERIFY(QTest::qWaitFor([&dock1](){ return !dock1.windowHandle(); }));
QCOMPARE(dock1.windowTitle(), QString(changed + dock1Title));
+ // Test consistency after toggling modified and floating
dock2.setWindowModified(true);
QCOMPARE(dock2.windowTitle(), dock2Title);
dock2.setFloating(true);
@@ -1044,6 +1150,846 @@ void tst_QDockWidget::setWindowTitle()
dock2.show();
QVERIFY(QTest::qWaitForWindowExposed(&dock2));
QCOMPARE(dock2.windowTitle(), dock2Title);
+
+ // Test title change of a closed dock widget
+ static constexpr QLatin1StringView closedDock2("Closed D2");
+ dock2.close();
+ dock2.setWindowTitle(closedDock2);
+ QCOMPARE(dock2.windowTitle(), closedDock2);
+}
+
+// helpers for dockPermissions, hideAndShow, closeAndDelete
+#ifdef QT_BUILD_INTERNAL
+void tst_QDockWidget::createTestWidgets(QMainWindow* &mainWindow, QPointer<QWidget> &cent, QPointer<QDockWidget> &d1, QPointer<QDockWidget> &d2) const
+{
+ // Enable logging if required
+ if (dockWidgetLog)
+ QLoggingCategory::setFilterRules("qt.widgets.dockwidgets=true");
+
+ // Derive sizes and positions from primary screen
+ const QRect screen = QApplication::primaryScreen()->availableGeometry();
+ const QPoint m_topLeft = screen.topLeft();
+ const QSize s_mwindow = QApplication::primaryScreen()->availableSize() * 0.7;
+
+ mainWindow = new QMainWindow;
+ mainWindow->setMouseTracking(true);
+ mainWindow->setFixedSize(s_mwindow);
+ cent = new QWidget;
+ mainWindow->setCentralWidget(cent);
+ cent->setLayout(new QGridLayout);
+ cent->layout()->setContentsMargins(0, 0, 0, 0);
+ cent->setMinimumSize(0, 0);
+ mainWindow->setDockOptions(QMainWindow::AllowTabbedDocks | QMainWindow::GroupedDragging);
+ mainWindow->move(m_topLeft);
+
+ const int minWidth = QApplication::style()->pixelMetric(QStyle::PM_TitleBarHeight);
+ const QSize minSize(minWidth, 2 * minWidth);
+ d1 = new QDockWidget(mainWindow);
+ d1->setMinimumSize(minSize);
+ d1->setWindowTitle("I am D1");
+ d1->setObjectName("D1");
+ d1->setFeatures(QDockWidget::DockWidgetFeatureMask);
+ d1->setAllowedAreas(Qt::DockWidgetArea::AllDockWidgetAreas);
+
+ d2 = new QDockWidget(mainWindow);
+ d2->setMinimumSize(minSize);
+ d2->setWindowTitle("I am D2");
+ d2->setObjectName("D2");
+ d2->setFeatures(QDockWidget::DockWidgetFeatureMask);
+ d2->setAllowedAreas(Qt::DockWidgetArea::RightDockWidgetArea);
+
+ mainWindow->addDockWidget(Qt::DockWidgetArea::LeftDockWidgetArea, d1);
+ mainWindow->addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, d2);
+ d1->show();
+ d2->show();
+ mainWindow->show();
+ QApplicationPrivate::setActiveWindow(mainWindow);
+
+}
+
+QPoint tst_QDockWidget::dragPoint(QDockWidget* dockWidget)
+{
+ Q_ASSERT(dockWidget);
+ QDockWidgetLayout *dwlayout = qobject_cast<QDockWidgetLayout *>(dockWidget->layout());
+ Q_ASSERT(dwlayout);
+ return dockWidget->mapToGlobal(dwlayout->titleArea().center());
+}
+
+void tst_QDockWidget::moveDockWidget(QDockWidget* dw, QPoint to, QPoint from, MoveDockWidgetRule rule) const
+{
+ Q_ASSERT(dw);
+
+ // If no from point is given, use the drag point
+ if (from.isNull())
+ from = dragPoint(dw);
+
+ // move and log
+ const QPoint source = dw->mapFromGlobal(from);
+ const QPoint target = dw->mapFromGlobal(to);
+ qCDebug(lcTestDockWidget) << "Move" << dw->objectName() << "from" << source;
+ qCDebug(lcTestDockWidget) << "Move" << dw->objectName() << "from" << from;
+ QTest::mousePress(dw, Qt::LeftButton, Qt::KeyboardModifiers(), source);
+ QTest::mouseMove(dw, target);
+ qCDebug(lcTestDockWidget) << "Move" << dw->objectName() << "to" << target;
+ qCDebug(lcTestDockWidget) << "Move" << dw->objectName() << "to" << to;
+ if (rule == MoveDockWidgetRule::Drop) {
+ QTest::mouseRelease(dw, Qt::LeftButton, Qt::KeyboardModifiers(), target);
+ QTest::qWait(waitingTime);
+
+ // Verify WindowActive only for floating dock widgets
+ if (dw->isFloating())
+ QTRY_VERIFY(QTest::qWaitForWindowActive(dw));
+ return;
+ }
+ qCDebug(lcTestDockWidget) << "Aborting move and dropping at origin";
+
+ // Give animations some time
+ QTest::qWait(waitingTime);
+ QTest::mouseMove(dw, from);
+ QTest::mouseRelease(dw, Qt::LeftButton, Qt::KeyboardModifiers(), from);
+ QTest::qWait(waitingTime);
+}
+
+void tst_QDockWidget::unplugAndResize(QMainWindow* mainWindow, QDockWidget* dw, QPoint home, QSize size) const
+{
+ Q_ASSERT(mainWindow);
+ Q_ASSERT(dw);
+
+ // Return if floating
+ if (dw->isFloating())
+ return;
+
+ QMainWindowLayout* layout = qobject_cast<QMainWindowLayout*>(mainWindow->layout());
+ Qt::DockWidgetArea area = layout->dockWidgetArea(dw);
+
+ // calculate minimum lateral move to unplug a dock widget
+ const int unplugMargin = 80;
+ int my = 0;
+ int mx = 0;
+
+ switch (area) {
+ case Qt::LeftDockWidgetArea:
+ mx = unplugMargin;
+ break;
+ case Qt::TopDockWidgetArea:
+ my = unplugMargin;
+ break;
+ case Qt::RightDockWidgetArea:
+ mx = -unplugMargin;
+ break;
+ case Qt::BottomDockWidgetArea:
+ my = -unplugMargin;
+ break;
+ default:
+ return;
+ }
+
+ // Remember size for comparison with unplugged object
+#ifdef Q_OS_LINUX
+ const int pluggedWidth = dw->width();
+ const int pluggedHeight = dw->height();
+#endif
+
+ // unplug and resize a dock Widget
+ qCDebug(lcTestDockWidget) << "*** unplug and resize" << dw->objectName();
+ QPoint pos1 = dw->mapToGlobal(dw->rect().center());
+ pos1.rx() += mx;
+ pos1.ry() += my;
+ moveDockWidget(dw, pos1, dw->mapToGlobal(dw->rect().center()), MoveDockWidgetRule::Drop);
+ QTRY_VERIFY(dw->isFloating());
+
+ // Unplugged object's size may differ max. by 2x frame size
+#ifdef Q_OS_LINUX
+ const int xMargin = 2 * dw->frameSize().width();
+ const int yMargin = 2 * dw->frameSize().height();
+ QVERIFY(dw->height() - pluggedHeight <= xMargin);
+ QVERIFY(dw->width() - pluggedWidth <= yMargin);
+#endif
+
+ qCDebug(lcTestDockWidget) << "Resizing" << dw->objectName() << "to" << size;
+ dw->setFixedSize(size);
+ QTest::qWait(waitingTime);
+ qCDebug(lcTestDockWidget) << "Move" << dw->objectName() << "to its home" << dw->mapFromGlobal(home);
+ dw->move(home);
+}
+
+bool tst_QDockWidget::checkFloatingTabs(QMainWindow* mainWindow, QPointer<QDockWidgetGroupWindow> &ftabs, const QList<QDockWidget*> &dwList) const
+{
+ Q_ASSERT(mainWindow);
+
+ // Check if mainWindow has a floatingTab child
+ ftabs = mainWindow->findChild<QDockWidgetGroupWindow*>();
+ if (ftabs.isNull()) {
+ qCDebug(lcTestDockWidget) << "MainWindow has no DockWidgetGroupWindow" << mainWindow;
+ return false;
+ }
+
+ QTabBar* tab = ftabs->findChild<QTabBar*>();
+ if (!tab) {
+ qCDebug(lcTestDockWidget) << "DockWidgetGroupWindow has no tab bar" << ftabs;
+ return false;
+ }
+
+ // both dock widgets must be direct children of the main window
+ const QList<QDockWidget*> children = ftabs->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly);
+ if (dwList.size() > 0)
+ {
+ if (dwList.size() != children.size()) {
+ qCDebug(lcTestDockWidget) << "Expected DockWidgetGroupWindow children:" << dwList.size()
+ << "Children found:" << children.size();
+
+ qCDebug(lcTestDockWidget) << "Expected:" << dwList;
+ qCDebug(lcTestDockWidget) << "Found in" << ftabs << ":" << children.size();
+ return false;
+ }
+
+ for (const QDockWidget* child : dwList) {
+ if (!children.contains(child)) {
+ qCDebug(lcTestDockWidget) << "Expected child" << child << "not found in" << children;
+ return false;
+ }
+ }
+ }
+
+ // Always select first tab position
+ qCDebug(lcTestDockWidget) << "click on first tab";
+ QTest::mouseClick(tab, Qt::LeftButton, Qt::KeyboardModifiers(), tab->tabRect(0).center());
+ return true;
+}
+
+#ifdef QT_BUILD_INTERNAL
+// Statics for xcb error, raise() suppert / msg handler
+static tst_QDockWidget *qThis = nullptr;
+static void (*oldMessageHandler)(QtMsgType, const QMessageLogContext &, const QString &);
+#define QXCBVERIFY(cond) do { if (xcbError) QSKIP("Test skipped due to XCB error"); QVERIFY(cond); } while (0)
+
+// detect xcb error and missing raise() support
+// qt.qpa.xcb: internal error: void QXcbWindow::setNetWmStateOnUnmappedWindow() called on mapped window
+void tst_QDockWidget::xcbMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+ Q_ASSERT(oldMessageHandler);
+
+ if (type == QtWarningMsg) {
+ Q_ASSERT(qThis);
+ if (QString(context.category) == "qt.qpa.xcb" && msg.contains("internal error"))
+ qThis->xcbError = true;
+ if (msg.contains("does not support raise"))
+ qThis->platformSupportingRaise = false;
+ }
+
+ return oldMessageHandler(type, context, msg);
+}
+#endif
+
+void tst_QDockWidget::createFloatingTabs(QMainWindow* &mainWindow, QPointer<QWidget> &cent,
+ QPointer<QDockWidget> &d1, QPointer<QDockWidget> &d2,
+ QList<int> &path1, QList<int> &path2) const
+{
+ createTestWidgets(mainWindow, cent, d1, d2);
+
+#ifdef QT_BUILD_INTERNAL
+ qThis = const_cast<tst_QDockWidget *>(this);
+ oldMessageHandler = qInstallMessageHandler(xcbMessageHandler);
+ auto resetMessageHandler = qScopeGuard([] { qInstallMessageHandler(oldMessageHandler); });
+#endif
+
+ // Test will fail if platform doesn't support raise.
+ mainWindow->windowHandle()->handle()->raise();
+ if (!platformSupportingRaise)
+ QSKIP("Platform not supporting raise(). Floating tab based tests will fail.");
+
+ // remember paths to d1 and d2
+ QMainWindowLayout* layout = qobject_cast<QMainWindowLayout *>(mainWindow->layout());
+ path1 = layout->layoutState.indexOf(d1);
+ path2 = layout->layoutState.indexOf(d2);
+
+ // unplug and resize both dock widgets
+ unplugAndResize(mainWindow, d1, home1(mainWindow), size1(mainWindow));
+ unplugAndResize(mainWindow, d2, home2(mainWindow), size2(mainWindow));
+
+ // docks must be parented to the main window, no group window must exist
+ QCOMPARE(d1->parentWidget(), mainWindow);
+ QCOMPARE(d2->parentWidget(), mainWindow);
+ QVERIFY(mainWindow->findChildren<QDockWidgetGroupWindow *>().isEmpty());
+
+ // Test plugging
+ qCDebug(lcTestDockWidget) << "*** move d1 dock over d2 dock ***";
+ qCDebug(lcTestDockWidget) << "**********(test plugging)*************";
+ qCDebug(lcTestDockWidget) << "Move d1 over d2";
+ moveDockWidget(d1, d2->mapToGlobal(d2->rect().center()), QPoint(), MoveDockWidgetRule::Drop);
+
+ // Now MainWindow has to have a floatingTab child
+ QPointer<QDockWidgetGroupWindow> ftabs;
+ QTRY_VERIFY(checkFloatingTabs(mainWindow, ftabs, QList<QDockWidget *>() << d1 << d2));
+}
+#endif // QT_BUILD_INTERNAL
+
+// test floating tabs and item_tree consistency
+void tst_QDockWidget::floatingTabs()
+{
+ if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive))
+ QSKIP("Test skipped on Wayland.");
+#ifdef Q_OS_WIN
+ QSKIP("Test skipped on Windows platforms");
+#endif // Q_OS_WIN
+#ifdef QT_BUILD_INTERNAL
+ // Create a mainwindow with a central widget and two dock widgets
+ QPointer<QDockWidget> d1;
+ QPointer<QDockWidget> d2;
+ QPointer<QWidget> cent;
+ QMainWindow* mainWindow;
+ QList<int> path1;
+ QList<int> path2;
+ createFloatingTabs(mainWindow, cent, d1, d2, path1, path2);
+ std::unique_ptr<QMainWindow> up_mainWindow(mainWindow);
+
+ QCOMPARE(mainWindow->tabifiedDockWidgets(d1), {d2});
+ QCOMPARE(mainWindow->tabifiedDockWidgets(d2), {d1});
+
+ /*
+ * unplug both dockwidgets, resize them and plug them into a joint floating tab
+ * expected behavior: QDOckWidgetGroupWindow with both widgets is created
+ */
+
+ // disabled due to flakiness on macOS and Windows
+ if (d1->isFloating())
+ qWarning("OS flakiness: D1 is docked and reports being floating");
+ if (d2->isFloating())
+ qWarning("OS flakiness: D2 is docked and reports being floating");
+
+ // Now MainWindow has to have a floatingTab child
+ QPointer<QDockWidgetGroupWindow> ftabs;
+ QTRY_VERIFY(checkFloatingTabs(mainWindow, ftabs, QList<QDockWidget *>() << d1 << d2));
+
+ // Hide both dock widgets. Verify that the group window is also hidden.
+ qCDebug(lcTestDockWidget) << "*** Hide and show tabbed dock widgets ***";
+ d1->hide();
+ d2->hide();
+ QTRY_VERIFY(ftabs->isHidden());
+
+ // Show both dockwidgets again. Verify that the group window is visible.
+ d1->show();
+ d2->show();
+ QTRY_VERIFY(ftabs->isVisible());
+
+ /*
+ * replug both dock widgets into their initial position
+ * expected behavior:
+ - both docks are plugged
+ - both docks are no longer floating
+ - title changes have been propagated
+ */
+
+
+ // limitation: QTest cannot handle drag to unplug.
+ // reason: Object under mouse mutates from QTabBar::tab to QDockWidget. QTest cannot handle that.
+ // => click float button to unplug
+ qCDebug(lcTestDockWidget) << "*** test unplugging from floating dock ***";
+
+ // QDockWidget must have a QAbstractButton with object name "qt_dockwidget_floatbutton"
+ QAbstractButton* floatButton = d1->findChild<QAbstractButton*>("qt_dockwidget_floatbutton", Qt::FindDirectChildrenOnly);
+ QTRY_VERIFY(floatButton != nullptr);
+ QPoint pos1 = floatButton->rect().center();
+ qCDebug(lcTestDockWidget) << "unplug d1" << pos1;
+ QTest::mouseClick(floatButton, Qt::LeftButton, Qt::KeyboardModifiers(), pos1);
+ QTest::qWait(waitingTime);
+
+ // d1 must be floating again, while d2 is still in its GroupWindow
+ QTRY_VERIFY(d1->isFloating());
+ QTRY_VERIFY(!d2->isFloating());
+
+ // Plug back into dock areas
+ qCDebug(lcTestDockWidget) << "*** test plugging back to dock areas ***";
+ qCDebug(lcTestDockWidget) << "Move d1 to left dock";
+ moveDockWidget(d1, dockPoint(mainWindow, Qt::LeftDockWidgetArea), QPoint(), MoveDockWidgetRule::Drop);
+ qCDebug(lcTestDockWidget) << "Move d2 to right dock";
+ moveDockWidget(d2, dockPoint(mainWindow, Qt::RightDockWidgetArea), QPoint(), MoveDockWidgetRule::Drop);
+
+ qCDebug(lcTestDockWidget) << "Waiting" << waitBeforeClose << "ms before plugging back.";
+ QTest::qWait(waitBeforeClose);
+
+ // Both dock widgets must no longer be floating
+ QTRY_VERIFY(!d1->isFloating());
+ QTRY_VERIFY(!d2->isFloating());
+
+ // check if QDockWidgetGroupWindow has been removed from mainWindowLayout and properly deleted
+ QTRY_VERIFY(!mainWindow->findChild<QDockWidgetGroupWindow*>());
+ QTRY_VERIFY(ftabs.isNull());
+
+ // Check if paths are consistent
+ QMainWindowLayout* layout = qobject_cast<QMainWindowLayout *>(mainWindow->layout());
+ qCDebug(lcTestDockWidget) << "Checking path consistency" << layout->layoutState.indexOf(d1) << layout->layoutState.indexOf(d2);
+
+ // Paths must be identical
+ QTRY_COMPARE(layout->layoutState.indexOf(d1), path1);
+ QTRY_COMPARE(layout->layoutState.indexOf(d2), path2);
+
+ QCOMPARE(mainWindow->tabifiedDockWidgets(d1), {});
+ QCOMPARE(mainWindow->tabifiedDockWidgets(d2), {});
+#else
+ QSKIP("test requires -developer-build option");
+#endif // QT_BUILD_INTERNAL
+}
+
+void tst_QDockWidget::deleteFloatingTabWithSingleDockWidget_data()
+{
+#ifdef QT_BUILD_INTERNAL
+ QTest::addColumn<int>("reason");
+ QTest::addRow("Delete child") << static_cast<int>(ChildRemovalReason::Destroyed);
+ QTest::addRow("Close child") << static_cast<int>(ChildRemovalReason::Closed);
+ QTest::addRow("Reparent child") << static_cast<int>(ChildRemovalReason::Reparented);
+#endif
+}
+
+void tst_QDockWidget::deleteFloatingTabWithSingleDockWidget()
+{
+ if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive))
+ QSKIP("Test skipped on Wayland.");
+#ifdef Q_OS_WIN
+ QSKIP("Test skipped on Windows platforms");
+#endif // Q_OS_WIN
+#ifdef QT_BUILD_INTERNAL
+
+ QFETCH(int, reason);
+ const ChildRemovalReason removalReason = static_cast<ChildRemovalReason>(reason);
+
+ QPointer<QDockWidget> d1;
+ QPointer<QDockWidget> d2;
+ QPointer<QWidget> cent;
+ QMainWindow* mainWindow;
+ QList<int> path1;
+ QList<int> path2;
+ createFloatingTabs(mainWindow, cent, d1, d2, path1, path2);
+ std::unique_ptr<QMainWindow> up_mainWindow(mainWindow);
+
+ switch (removalReason) {
+ case ChildRemovalReason::Destroyed:
+ delete d1;
+ break;
+ case ChildRemovalReason::Closed:
+ d1->close();
+ break;
+ case ChildRemovalReason::Reparented:
+ // This will create an invalid state, because setParent() doesn't fix the item_list.
+ // Testing this case anyway, because setParent() includig item_list fixup is executed,
+ // when the 2nd last dock widget is dragged out of a floating tab.
+ // => despite of the broken state, the group window has to be gone.
+ d1->setParent(mainWindow);
+ break;
+ }
+
+ QTRY_VERIFY(!qobject_cast<QDockWidgetGroupWindow *>(d2->parentWidget()));
+ QTRY_VERIFY(mainWindow->findChildren<QDockWidgetGroupWindow *>().isEmpty());
+#endif // QT_BUILD_INTERNAL
+}
+
+void tst_QDockWidget::hoverWithoutDrop()
+{
+ if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive))
+ QSKIP("Test skipped on Wayland.");
+#ifdef QT_BUILD_INTERNAL
+
+ QPointer<QDockWidget> d1;
+ QPointer<QDockWidget> d2;
+ QPointer<QWidget> cent;
+ QMainWindow* mainWindow;
+ createTestWidgets(mainWindow, cent, d1, d2);
+ std::unique_ptr<QMainWindow> up_mainWindow(mainWindow);
+
+ // unplug and resize both dock widgets
+ unplugAndResize(mainWindow, d1, home1(mainWindow), size1(mainWindow));
+ unplugAndResize(mainWindow, d2, home2(mainWindow), size2(mainWindow));
+
+ // Test plugging
+ qCDebug(lcTestDockWidget) << "*** move d1 dock over d2 dock ***";
+ qCDebug(lcTestDockWidget) << "*******(test hovering)***********";
+ qCDebug(lcTestDockWidget) << "Move d1 over d2, wait and return to origin";
+ const QPoint source = d1->mapToGlobal(d1->rect().center());
+ const QPoint target = d2->mapToGlobal(d2->rect().center());
+ moveDockWidget(d1, target, source, MoveDockWidgetRule::Abort);
+ auto *groupWindow = mainWindow->findChild<QDockWidgetGroupWindow *>();
+ QCOMPARE(groupWindow, nullptr);
+#else
+ QSKIP("test requires -developer-build option");
+#endif // QT_BUILD_INTERNAL
+}
+
+// test hide & show
+void tst_QDockWidget::hideAndShow()
+{
+ if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive))
+ QSKIP("Test skipped on Wayland.");
+#ifdef QT_BUILD_INTERNAL
+ // Skip test if xcb error is launched
+ qThis = this;
+ oldMessageHandler = qInstallMessageHandler(xcbMessageHandler);
+ auto resetMessageHandler = qScopeGuard([] { qInstallMessageHandler(oldMessageHandler); });
+
+ // Create a mainwindow with a central widget and two dock widgets
+ QPointer<QDockWidget> d1;
+ QPointer<QDockWidget> d2;
+ QPointer<QWidget> cent;
+ QMainWindow* mainWindow;
+ createTestWidgets(mainWindow, cent, d1, d2);
+ std::unique_ptr<QMainWindow> up_mainWindow(mainWindow);
+
+ // Check hiding of docked widgets
+ qCDebug(lcTestDockWidget) << "Hiding mainWindow with plugged dock widgets" << mainWindow;
+ mainWindow->hide();
+ QXCBVERIFY(!mainWindow->isVisible());
+ QXCBVERIFY(!d1->isVisible());
+ QXCBVERIFY(!d2->isVisible());
+
+ // Check showing everything again
+ qCDebug(lcTestDockWidget) << "Showing mainWindow with plugged dock widgets" << mainWindow;
+ mainWindow->show();
+ QXCBVERIFY(QTest::qWaitForWindowActive(mainWindow));
+ QXCBVERIFY(QTest::qWaitForWindowExposed(mainWindow));
+ QXCBVERIFY(mainWindow->isVisible());
+ QXCBVERIFY(QTest::qWaitForWindowActive(d1));
+ QXCBVERIFY(d1->isVisible());
+ QXCBVERIFY(QTest::qWaitForWindowActive(d2));
+ QXCBVERIFY(d2->isVisible());
+
+ // in case of XCB errors, unplugAndResize will block and cause the test to time out.
+ // => force skip
+ QTest::qWait(waitingTime);
+ if (xcbError)
+ QSKIP("Test skipped due to XCB error");
+
+ // unplug and resize both dock widgets
+ unplugAndResize(mainWindow, d1, home1(mainWindow), size1(mainWindow));
+ unplugAndResize(mainWindow, d2, home2(mainWindow), size2(mainWindow));
+
+ // Check hiding of undocked widgets
+ qCDebug(lcTestDockWidget) << "Hiding mainWindow with unplugged dock widgets" << mainWindow;
+ mainWindow->hide();
+ QTRY_VERIFY(!mainWindow->isVisible());
+ QTRY_VERIFY(d1->isVisible());
+ QTRY_VERIFY(d2->isVisible());
+ d1->hide();
+ d2->hide();
+ QTRY_VERIFY(!d1->isVisible());
+ QTRY_VERIFY(!d2->isVisible());
+
+
+ // Check floating, hidden dock widgets remain hidden, when their state is restored
+ qCDebug(lcTestDockWidget) << "Restoring state of unplugged, hidden dock widgets" << mainWindow;
+ const QByteArray state = mainWindow->saveState();
+ mainWindow->restoreState(state);
+ mainWindow->show();
+ QVERIFY(QTest::qWaitForWindowExposed(mainWindow));
+ QTRY_VERIFY(!d1->isVisible());
+ QTRY_VERIFY(!d2->isVisible());
+
+ qCDebug(lcTestDockWidget) << "Waiting" << waitBeforeClose << "ms before closing.";
+ QTest::qWait(waitBeforeClose);
+#else
+ QSKIP("test requires -developer-build option");
+#endif // QT_BUILD_INTERNAL
+}
+
+// test closing and deleting consistency
+void tst_QDockWidget::closeAndDelete()
+{
+ if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive))
+ QSKIP("Test skipped on Wayland.");
+#ifdef QT_BUILD_INTERNAL
+ // Create a mainwindow with a central widget and two dock widgets
+ QPointer<QDockWidget> d1;
+ QPointer<QDockWidget> d2;
+ QPointer<QWidget> cent;
+ QMainWindow* mainWindow;
+ createTestWidgets(mainWindow, cent, d1, d2);
+ std::unique_ptr<QMainWindow> up_mainWindow(mainWindow);
+
+ // unplug and resize both dock widgets
+ unplugAndResize(mainWindow, d1, home1(mainWindow), size1(mainWindow));
+ unplugAndResize(mainWindow, d2, home2(mainWindow), size2(mainWindow));
+
+ // Create a floating tab and unplug it again
+ qCDebug(lcTestDockWidget) << "Move d1 over d2";
+ moveDockWidget(d1, d2->mapToGlobal(d2->rect().center()), QPoint(), MoveDockWidgetRule::Drop);
+
+ // Both dock widgets must no longer be floating
+ // disabled due to flakiness on macOS and Windows
+ //QTRY_VERIFY(!d1->isFloating());
+ //QTRY_VERIFY(!d2->isFloating());
+ if (d1->isFloating())
+ qWarning("OS flakiness: D1 is docked and reports being floating");
+ if (d2->isFloating())
+ qWarning("OS flakiness: D2 is docked and reports being floating");
+
+ // Close everything with a single shot. Expected behavior: Event loop stops
+ QSignalSpy closeSpy(qApp, &QApplication::lastWindowClosed);
+ QObject localContext;
+
+ QTimer::singleShot(0, &localContext, [&](){
+ mainWindow->close();
+ QTRY_VERIFY(!mainWindow->isVisible());
+ QTRY_VERIFY(d1->isVisible());
+ QTRY_VERIFY(d2->isVisible());
+ d1->close();
+ d2->close();
+ QTRY_VERIFY(!d1->isVisible());
+ QTRY_VERIFY(!d2->isVisible());
+ QTRY_COMPARE(closeSpy.count(), 1);
+ QApplication::quit();
+ });
+
+ QApplication::exec();
+
+ // Check heap cleanup
+ qCDebug(lcTestDockWidget) << "Deleting mainWindow";
+ up_mainWindow.reset();
+ QTRY_VERIFY(d1.isNull());
+ QTRY_VERIFY(d2.isNull());
+ QTRY_VERIFY(cent.isNull());
+#else
+ QSKIP("test requires -developer-build option");
+#endif // QT_BUILD_INTERNAL
+}
+
+void tst_QDockWidget::closeUnclosable()
+{
+ QDockWidget *dockWidget = new QDockWidget("dock");
+ dockWidget->setWidget(new QScrollArea);
+ dockWidget->setFeatures(QDockWidget::DockWidgetFloatable);
+
+ QMainWindow mw;
+ mw.addDockWidget(Qt::TopDockWidgetArea, dockWidget);
+ mw.show();
+
+ QVERIFY(QTest::qWaitForWindowExposed(&mw));
+ dockWidget->setFloating(true);
+
+ QCOMPARE(dockWidget->close(), false);
+ mw.close();
+ QCOMPARE(dockWidget->close(), true);
+}
+
+// Test dock area permissions
+void tst_QDockWidget::dockPermissions()
+{
+ if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive))
+ QSKIP("Test skipped on Wayland.");
+#ifdef Q_OS_WIN
+ QSKIP("Test skipped on Windows platforms");
+#endif // Q_OS_WIN
+#ifdef QT_BUILD_INTERNAL
+ // Create a mainwindow with a central widget and two dock widgets
+ QPointer<QDockWidget> d1;
+ QPointer<QDockWidget> d2;
+ QPointer<QWidget> cent;
+ QMainWindow* mainWindow;
+ createTestWidgets(mainWindow, cent, d1, d2);
+ std::unique_ptr<QMainWindow> up_mainWindow(mainWindow);
+
+ /*
+ * Unplug both dock widgets from their dock areas and hover them over each other
+ * expected behavior:
+ * - d2 hovering over d1 does nothing as d2 can only use right dock
+ * - hovering d2 over top, left and bottom dock area will do nothing due to lacking permissions
+ * - d1 hovering over d2 will create floating tabs as d1 has permission for DockWidgetArea::FloatingDockWidgetArea
+ * - resizing and tab creation will add two gap items in the right dock (d2)
+ */
+
+ // unplug and resize both dock widgets
+ unplugAndResize(mainWindow, d1, home1(mainWindow), size1(mainWindow));
+ unplugAndResize(mainWindow, d2, home2(mainWindow), size2(mainWindow));
+
+ // both dock widgets must be direct children of the main window
+ {
+ const QList<QDockWidget*> children = mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly);
+ QTRY_VERIFY(children.size() == 2);
+ for (const QDockWidget* child : children)
+ QTRY_VERIFY(child == d1 || child == d2);
+ }
+
+ // The main window must not contain floating tabs
+ QTRY_VERIFY(mainWindow->findChild<QDockWidgetGroupWindow*>() == nullptr);
+
+ // Test unpermitted dock areas with d2
+ qCDebug(lcTestDockWidget) << "*** move d2 to forbidden docks ***";
+
+ // Move d2 to non allowed dock areas and verify it remains floating
+ qCDebug(lcTestDockWidget) << "Move d2 to top dock";
+ moveDockWidget(d2, dockPoint(mainWindow, Qt::TopDockWidgetArea), QPoint(), MoveDockWidgetRule::Drop);
+ QTRY_VERIFY(d2->isFloating());
+
+ qCDebug(lcTestDockWidget) << "Move d2 to left dock";
+ //moveDockWidget(d2, d2->mapFrom(MainWindow, dockPoint(MainWindow, Qt::LeftDockWidgetArea)));
+ moveDockWidget(d2, dockPoint(mainWindow, Qt::LeftDockWidgetArea), QPoint(), MoveDockWidgetRule::Drop);
+ QTRY_VERIFY(d2->isFloating());
+
+ qCDebug(lcTestDockWidget) << "Move d2 to bottom dock";
+ moveDockWidget(d2, dockPoint(mainWindow, Qt::BottomDockWidgetArea), QPoint(), MoveDockWidgetRule::Drop);
+ QTRY_VERIFY(d2->isFloating());
+
+ qCDebug(lcTestDockWidget) << "Waiting" << waitBeforeClose << "ms before closing.";
+ QTest::qWait(waitBeforeClose);
+#else
+ QSKIP("test requires -developer-build option");
+#endif // QT_BUILD_INTERNAL
+}
+
+/*!
+ \internal
+
+ This test checks consistency of QMainWindow::saveState() / QMainWindow::restoreState().
+ These methods (de)serialize dock widget properties via a QDataStream into a QByteArray.
+
+ If the logic of (de)serializing Qt datatypes and classes changes, old settings can fail
+ to restore properly without triggering warnings or assertions.
+
+ The test consists of two parts:
+ \list 1
+ \li Read properties from a hard coded byte array and check if it is deserialized correctly.
+ \li Serialize properties into a \a QByteArray and check if it is serialized correctly.
+ \endlist
+*/
+void tst_QDockWidget::saveAndRestore()
+{
+ if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive))
+ QSKIP("Test skipped on Wayland.");
+#ifdef Q_OS_WIN
+ QSKIP("Test skipped on Windows platforms");
+#endif // Q_OS_WIN
+#ifndef QT_BUILD_INTERNAL
+ QSKIP("test requires -developer-build option");
+#else
+
+ // Hard coded byte array for test initialization
+ const QByteArray testArray = QByteArrayLiteral(
+ "\x00\x00\x00\xFF\x00\x00\x00\x00\xFD\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x13\x00\x00\x05\xE8\xFC\x02\x00\x00\x00\x01\xFB\x00\x00\x00\x04\x00"
+ "D\x00"
+ "1\x03\x00\x00\x01\f\x00\x00\x00\x97\x00\x00\x02\x19\x00\x00\x01z\x00\x00\x00\x01\x00\x00\x00\x13\x00\x00\x05\xE8\xFC\x02\x00\x00\x00\x01\xFB\x00\x00\x00\x04\x00"
+ "D\x00"
+ "2\x03\x00\x00\x06L\x00\x00\x00\xFF\x00\x00\x01\f\x00\x00\x00\xE2\x00\x00\n\x80\x00\x00\x05\xE8\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\b\x00\x00\x00\b\xFC\x00\x00\x00\x00"
+ );
+
+ QByteArray referenceArray; // Copy of testArray, corrected for current screen limits
+ QPoint topLeft1; // Top left point of dock widget d1
+ QPoint topLeft2; // Top left point of dock widget d2
+ QSize widgetSize1; // Size of dock widget d1
+ QSize widgetSize2; // Size of dock widget d2
+ bool isFloating1; // Floating status of dock widget d1
+ bool isFloating2; // Floating status of dock widget d2
+
+ // Create a mainwindow with a central widget and two dock widgets.
+ // Import properties from hard coded byte array.
+ // Use a scope to delete objects from screen after test.
+ {
+ QPointer<QDockWidget> d1;
+ QPointer<QDockWidget> d2;
+ QPointer<QWidget> cent;
+ QMainWindow* mainWindow;
+ createTestWidgets(mainWindow, cent, d1, d2);
+
+ // Failure to restore properties might lead to inconsistencies and crash.
+ // To leave a clean environment when the test inexpectedly goes out of scope,
+ // => store main window pointer in a std::unique_ptr
+ std::unique_ptr<QMainWindow> up_mainWindow(mainWindow);
+
+ // Restore, wait for events to be processed
+ mainWindow->restoreState(testArray);
+ QVERIFY(QTest::qWaitForWindowExposed(d1));
+ QVERIFY(QTest::qWaitForWindowExposed(d2));
+
+ // Serialized dock widget positions and sizes might be overridden due
+ // screen size limitations => do not check them here.
+ // If the test fails between here and scope end, serialization format/sequence have changed
+ QTRY_VERIFY(d1->isFloating());
+ QTRY_VERIFY(d2->isFloating());
+
+ // Hide main window and save their floating status.
+ // Reason:
+ // - KDE window managers do not take control over dock widgets.
+ // => They always close with the main window.
+ // - Some non KDE window managers do take control over dock widgets.
+ // => They prevent them from closing with the main window (QTBUG-103474).
+ // If properties are restored correctly, closing behavior must be consistent
+ // throughout this test.
+ mainWindow->hide();
+ // FIXME: No method exists in 6.5 to wait for a window to be hidden.
+ // => wait and hope the best, replace with qWaitForWindowHidden once implemented.
+ QTest::qWait(200);
+ isFloating1 = d1->isFloating();
+ isFloating2 = d2->isFloating();
+ }
+
+ // Create a mainwindow with a central widget and two dock widgets.
+ // Assign different properties to each dock widgets.
+ // Write properties to a byte array.
+ // Remember position and size properties for comparison.
+ // Use a scope to delete objects from screen after test.
+ {
+ QPointer<QDockWidget> d1;
+ QPointer<QDockWidget> d2;
+ QPointer<QWidget> cent;
+ QMainWindow* mainWindow;
+ createTestWidgets(mainWindow, cent, d1, d2);
+ std::unique_ptr<QMainWindow> up_mainWindow(mainWindow);
+
+ // unplug, position and resize both dock widgets relative to screen size
+ unplugAndResize(mainWindow, d1, home1(mainWindow), size1(mainWindow));
+ unplugAndResize(mainWindow, d2, home2(mainWindow), size2(mainWindow));
+
+ topLeft1 = d1->pos();
+ topLeft2 = d2->pos();
+ widgetSize1 = d1->size();
+ widgetSize2 = d2->size();
+
+ // save properties, potentially corrected for screen limits
+ referenceArray = mainWindow->saveState();
+
+ // Check closing behavior consistency
+ mainWindow->hide();
+ QTRY_VERIFY(d1->isFloating());
+ QTRY_VERIFY(d2->isFloating());
+ QCOMPARE(d1->isFloating(), isFloating1);
+ QCOMPARE(d2->isFloating(), isFloating2);
+ }
+
+ // Create a new main window, central window and two dock widgets.
+ QPointer<QDockWidget> d1;
+ QPointer<QDockWidget> d2;
+ QPointer<QWidget> cent;
+ QMainWindow* mainWindow;
+ createTestWidgets(mainWindow, cent, d1, d2);
+
+ // Failure to restore properties might lead to inconsistencies and crash.
+ // To leave a clean environment when the test inexpectedly goes out of scope,
+ // - store main window pointer in a std::unique_ptr
+ std::unique_ptr<QMainWindow> up_mainWindow(mainWindow);
+
+ // Restore properties and wait for events to be processed
+ mainWindow->restoreState(referenceArray);
+ QVERIFY(QTest::qWaitForWindowExposed(d1));
+ QVERIFY(QTest::qWaitForWindowExposed(d2));
+
+ // Compare positions, sizes and floating status
+ // If the test fails in the following 12 lines,
+ // the de-serialization format/sequence have changed
+ QCOMPARE(topLeft1, d1->pos());
+ QCOMPARE(topLeft2, d2->pos());
+ QCOMPARE(widgetSize1, d1->size());
+ QCOMPARE(widgetSize2, d2->size());
+ QVERIFY(d1->isFloating());
+ QVERIFY(d2->isFloating());
+
+ // Serialize again to compare all remaining properties
+ const QByteArray comparisonArray = mainWindow->saveState();
+ QCOMPARE(comparisonArray, referenceArray);
+
+ // Check closing behavior consistency
+ mainWindow->hide();
+ QTRY_VERIFY(d1->isFloating());
+ QTRY_VERIFY(d2->isFloating());
+ QCOMPARE(d1->isFloating(), isFloating1);
+ QCOMPARE(d2->isFloating(), isFloating2);
+
+#endif // QT_BUILD_INTERNAL
}
QTEST_MAIN(tst_QDockWidget)