summaryrefslogtreecommitdiffstats
path: root/tests/auto/widgets/widgets/qdockwidget
diff options
context:
space:
mode:
authorAxel Spoerl <Axel.Spoerl@qt.io>2022-03-04 11:05:47 +0100
committerAxel Spoerl <axel.spoerl@qt.io>2022-04-19 17:12:20 +0000
commit9ff40b59da58160dc26c54204a615a2456e07405 (patch)
tree58139d24f3009ace9166c70e0b8f3d92ed432c47 /tests/auto/widgets/widgets/qdockwidget
parent908e85cc85d18f56575ee040589bcd5745c62adb (diff)
Fix QDockWidget parenting and dock permissions
Check DockWidgetArea permissions of QDockWidgetGroupWindows with single dock widget. Obtain a dock widget's tab position from a dock widget group window if it can't be established otherwise. Remove hardcoded assumption that a dock widget is in the left dock. Both cases have lead to inconsistent entries and dangling pointers in QDockAreaLayoutInfo::item_list. Remove warning: QMainWindowLayout::tabPosition called with out-of-bounds value '0', which becomes obsolete by the fix. Create a QDockWidgetGroup window prepered to become a floating tab, whenever a dock widget is being hovered over. Store it in item_list so it can be found and deleted when required. No longer call e->ignore() after propagating close events to the first dock widget and thus preventing others from receiving the event. Add logging category qt.widgets.dockwidgets Update dock widget autotest with tests to check the fixes mentioned: plugging, unplugging, hiding, showing, closing and deleting. Blackist closeAndDelete, floatingTabs test on macos, QEMU, arm, android due to flaky isFloating() response after a dock widget has been closed or plugged. QSKIP dockPermissions and floatingTabs test on Windows due to mouse simulation malfunction. QSKIP hideAndShow test on Linux in case of xcb error (QTBUG-82059) Fixes: QTBUG-99136 Pick-to: 6.3 6.2 Change-Id: Ibd353e0acc9831a0d67c9f682429ab46b94bdbb0 Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Diffstat (limited to 'tests/auto/widgets/widgets/qdockwidget')
-rw-r--r--tests/auto/widgets/widgets/qdockwidget/BLACKLIST22
-rw-r--r--tests/auto/widgets/widgets/qdockwidget/CMakeLists.txt1
-rw-r--r--tests/auto/widgets/widgets/qdockwidget/tst_qdockwidget.cpp582
3 files changed, 603 insertions, 2 deletions
diff --git a/tests/auto/widgets/widgets/qdockwidget/BLACKLIST b/tests/auto/widgets/widgets/qdockwidget/BLACKLIST
index 6a3b189939..769bc1bd5c 100644
--- a/tests/auto/widgets/widgets/qdockwidget/BLACKLIST
+++ b/tests/auto/widgets/widgets/qdockwidget/BLACKLIST
@@ -1,3 +1,25 @@
# QTBUG-87415
[task169808_setFloating]
android
+#
+# QDockWidget::isFloating() is flaky after state change on these OS
+[closeAndDelete]
+macos
+[floatingTabs]
+macos
+[closeAndDelete]
+b2qt
+[floatingTabs]
+macos b2qt arm android
+[closeAndDelete]
+b2qt
+[floatingTabs]
+arm
+[closeAndDelete]
+macos b2qt arm android
+[floatingTabs]
+arm
+[closeAndDelete]
+android
+[floatingTabs]
+android
diff --git a/tests/auto/widgets/widgets/qdockwidget/CMakeLists.txt b/tests/auto/widgets/widgets/qdockwidget/CMakeLists.txt
index adf48ddb11..6d54deca0b 100644
--- a/tests/auto/widgets/widgets/qdockwidget/CMakeLists.txt
+++ b/tests/auto/widgets/widgets/qdockwidget/CMakeLists.txt
@@ -8,6 +8,7 @@ qt_internal_add_test(tst_qdockwidget
SOURCES
tst_qdockwidget.cpp
PUBLIC_LIBRARIES
+ Qt::Core
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
diff --git a/tests/auto/widgets/widgets/qdockwidget/tst_qdockwidget.cpp b/tests/auto/widgets/widgets/qdockwidget/tst_qdockwidget.cpp
index d726807b7c..c74b29ad4b 100644
--- a/tests/auto/widgets/widgets/qdockwidget/tst_qdockwidget.cpp
+++ b/tests/auto/widgets/widgets/qdockwidget/tst_qdockwidget.cpp
@@ -28,15 +28,24 @@
#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 <qtabbar.h>
#include <QScreen>
+#include <QTimer>
#include <QtGui/QPainter>
-#include "private/qdockwidget_p.h"
+#include <QLabel>
+
+#ifdef QT_BUILD_INTERNAL
+QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaDockWidgets, "qt.widgets.dockwidgets");
+QT_END_NAMESPACE
+#endif
bool hasFeature(QDockWidget *dockwidget, QDockWidget::DockWidgetFeature feature)
{ return (dockwidget->features() & feature) == feature; }
@@ -70,6 +79,7 @@ private slots:
void restoreDockWidget();
void restoreStateWhileStillFloating();
void setWindowTitle();
+
// task specific tests:
void task165177_deleteFocusWidget();
void task169808_setFloating();
@@ -78,8 +88,75 @@ 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 and item_tree consistency
+ void floatingTabs();
+
+ // test hide & show
+ void hideAndShow();
+
+ // test closing and deleting consistency
+ void closeAndDelete();
+
+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;
+
+ 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
+ void moveDockWidget(QDockWidget* dw, QPoint to, QPoint from = QPoint()) const;
+
+ // Message handling for xcb error QTBUG 82059
+ static void xcbMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
+public:
+ bool xcbError = false;
+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
+
};
+// Statics for xcb error / 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)
+
// Testing get/set functions
void tst_QDockWidget::getSetCheck()
{
@@ -446,6 +523,7 @@ void tst_QDockWidget::allowedAreas()
QVERIFY(!dw.isAreaAllowed(Qt::RightDockWidgetArea));
QVERIFY(dw.isAreaAllowed(Qt::TopDockWidgetArea));
QVERIFY(dw.isAreaAllowed(Qt::BottomDockWidgetArea));
+ //QVERIFY(!dw.isAreaAllowed(Qt::FloatingDockWidgetArea));
QCOMPARE(spy.count(), 1);
QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
dw.allowedAreas());
@@ -459,6 +537,7 @@ void tst_QDockWidget::allowedAreas()
QVERIFY(dw.isAreaAllowed(Qt::RightDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::TopDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::BottomDockWidgetArea));
+ //QVERIFY(!dw.isAreaAllowed(Qt::FloatingDockWidgetArea));
QCOMPARE(spy.count(), 1);
QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
dw.allowedAreas());
@@ -472,6 +551,22 @@ void tst_QDockWidget::allowedAreas()
QVERIFY(!dw.isAreaAllowed(Qt::RightDockWidgetArea));
QVERIFY(dw.isAreaAllowed(Qt::TopDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::BottomDockWidgetArea));
+ //QVERIFY(!dw.isAreaAllowed(Qt::FloatingDockWidgetArea));
+ QCOMPARE(spy.count(), 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);
+
+ //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.count(), 1);
QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
dw.allowedAreas());
@@ -485,6 +580,7 @@ void tst_QDockWidget::allowedAreas()
QVERIFY(dw.isAreaAllowed(Qt::RightDockWidgetArea));
QVERIFY(!dw.isAreaAllowed(Qt::TopDockWidgetArea));
QVERIFY(dw.isAreaAllowed(Qt::BottomDockWidgetArea));
+ //QVERIFY(!dw.isAreaAllowed(Qt::FloatingDockWidgetArea));
QCOMPARE(spy.count(), 1);
QCOMPARE(*static_cast<const Qt::DockWidgetAreas *>(spy.at(0).value(0).constData()),
dw.allowedAreas());
@@ -1046,5 +1142,487 @@ void tst_QDockWidget::setWindowTitle()
QCOMPARE(dock2.windowTitle(), dock2Title);
}
+// 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);
+
+ d1 = new QDockWidget(mainWindow);
+ d1->setWindowTitle("I am D1");
+ d1->setObjectName("D1");
+ d1->setFeatures(QDockWidget::DockWidgetFeatureMask);
+ d1->setAllowedAreas(Qt::DockWidgetArea::AllDockWidgetAreas);
+
+ d2 = new QDockWidget(mainWindow);
+ 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();
+ QApplication::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) 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(lcQpaDockWidgets) << "Move" << dw->objectName() << "from" << source;
+ qCDebug(lcQpaDockWidgets) << "Move" << dw->objectName() << "from" << from;
+ QTest::mousePress(dw, Qt::LeftButton, Qt::KeyboardModifiers(), source);
+ QTest::mouseMove(dw, target);
+ qCDebug(lcQpaDockWidgets) << "Move" << dw->objectName() << "to" << target;
+ qCDebug(lcQpaDockWidgets) << "Move" << dw->objectName() << "to" << to;
+ 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));
+}
+
+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;
+ }
+
+ // unplug and resize a dock Widget
+ qCDebug(lcQpaDockWidgets) << "*** 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()));
+ //QTest::mousePress(dw, Qt::LeftButton, Qt::KeyboardModifiers(), dw->mapFromGlobal(pos1));
+ QTRY_VERIFY(dw->isFloating());
+
+ qCDebug(lcQpaDockWidgets) << "Resizing" << dw->objectName() << "to" << size;
+ dw->setFixedSize(size);
+ QTest::qWait(waitingTime);
+ qCDebug(lcQpaDockWidgets) << "Move" << dw->objectName() << "to its home" << dw->mapFromGlobal(home);
+ dw->move(home);
+ //moveDockWidget(dw, 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(lcQpaDockWidgets) << "MainWindow has no DockWidgetGroupWindow" << mainWindow;
+ return false;
+ }
+
+ QTabBar* tab = ftabs->findChild<QTabBar*>();
+ if (!tab) {
+ qCDebug(lcQpaDockWidgets) << "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.count() > 0)
+ {
+ if (dwList.count() != children.count()) {
+ qCDebug(lcQpaDockWidgets) << "Expected DockWidgetGroupWindow children:" << dwList.count()
+ << "Children found:" << children.count();
+
+ qCDebug(lcQpaDockWidgets) << "Expected:" << dwList;
+ qCDebug(lcQpaDockWidgets) << "Found in" << ftabs << ":" << children.count();
+ return false;
+ }
+
+ for (const QDockWidget* child : dwList) {
+ if (!children.contains(child)) {
+ qCDebug(lcQpaDockWidgets) << "Expected child" << child << "not found in" << children;
+ return false;
+ }
+ }
+ }
+
+ // Always select first tab position
+ qCDebug(lcQpaDockWidgets) << "click on first tab";
+ QTest::mouseClick(tab, Qt::LeftButton, Qt::KeyboardModifiers(), tab->tabRect(0).center());
+ return true;
+}
+
+// detect xcb error
+// 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 && QString(context.category) == "qt.qpa.xcb" && msg.contains("internal error")) {
+ Q_ASSERT(qThis);
+ qThis->xcbError = true;
+ }
+
+ return oldMessageHandler(type, context, msg);
+}
+
+#endif // QT_BUILD_INTERNAL
+
+// test floating tabs and item_tree consistency
+void tst_QDockWidget::floatingTabs()
+{
+#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 dockwidgets, resize them and plug them into a joint floating tab
+ * expected behavior: QDOckWidgetGroupWindow with both widgets is created
+ */
+
+ // remember paths to d1 and d2
+ QMainWindowLayout* layout = qobject_cast<QMainWindowLayout*>(mainWindow->layout());
+ const QList<int> path1 = layout->layoutState.indexOf(d1);
+ const QList<int> 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));
+
+ // Test plugging
+ qCDebug(lcQpaDockWidgets) << "*** move d1 dock over d2 dock ***";
+ qCDebug(lcQpaDockWidgets) << "**********(test plugging)*************";
+ qCDebug(lcQpaDockWidgets) << "Move d1 over d2";
+ moveDockWidget(d1, d2->mapToGlobal(d2->rect().center()));
+
+ // 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");
+
+ // Now MainWindow has to have a floatingTab child
+ QPointer<QDockWidgetGroupWindow> ftabs;
+ QTRY_VERIFY(checkFloatingTabs(mainWindow, ftabs, QList<QDockWidget*>() << d1 << d2));
+
+ /*
+ * replug both dock widgets into their initial position
+ * expected behavior: both docks are plugged and no longer floating
+ */
+
+
+ // 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(lcQpaDockWidgets) << "*** 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(lcQpaDockWidgets) << "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(lcQpaDockWidgets) << "*** test plugging back to dock areas ***";
+ qCDebug(lcQpaDockWidgets) << "Move d1 to left dock";
+ //moveDockWidget(d1, d1->mapFrom(MainWindow, dockPoint(MainWindow, Qt::LeftDockWidgetArea)));
+ moveDockWidget(d1, dockPoint(mainWindow, Qt::LeftDockWidgetArea));
+ qCDebug(lcQpaDockWidgets) << "Move d2 to right dock";
+ moveDockWidget(d2, dockPoint(mainWindow, Qt::RightDockWidgetArea));
+
+ qCDebug(lcQpaDockWidgets) << "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
+ qCDebug(lcQpaDockWidgets) << "Checking path consistency" << layout->layoutState.indexOf(d1) << layout->layoutState.indexOf(d2);
+
+ // Path1 must be identical
+ QTRY_VERIFY(path1 == layout->layoutState.indexOf(d1));
+
+ // d1 must have a gap item due to size change
+ QTRY_VERIFY(layout->layoutState.indexOf(d2) == QList<int>() << path2 << 0);
+#else
+ QSKIP("test requires -developer-build option");
+#endif // QT_BUILD_INTERNAL
+}
+
+// test hide & show
+void tst_QDockWidget::hideAndShow()
+{
+#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(lcQpaDockWidgets) << "Hiding mainWindow with plugged dock widgets" << mainWindow;
+ mainWindow->hide();
+ QXCBVERIFY(!mainWindow->isVisible());
+ QXCBVERIFY(!d1->isVisible());
+ QXCBVERIFY(!d2->isVisible());
+
+ // Check showing everything again
+ qCDebug(lcQpaDockWidgets) << "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(lcQpaDockWidgets) << "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());
+
+ qCDebug(lcQpaDockWidgets) << "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()
+{
+#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(lcQpaDockWidgets) << "Move d1 over d2";
+ moveDockWidget(d1, d2->mapToGlobal(d2->rect().center()));
+
+ // 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
+ bool eventLoopStopped = true;
+ QTimer::singleShot(0, this, [mainWindow, d1, d2] {
+ 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());
+ });
+
+ // Fallback timer to report event loop still running
+ QTimer::singleShot(100, this, [&eventLoopStopped] {
+ qCDebug(lcQpaDockWidgets) << "Last dock widget hasn't shout down event loop!";
+ eventLoopStopped = false;
+ QApplication::quit();
+ });
+
+ QApplication::exec();
+
+ QTRY_VERIFY(eventLoopStopped);
+
+ // Check heap cleanup
+ qCDebug(lcQpaDockWidgets) << "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
+}
+
+// Test dock area permissions
+void tst_QDockWidget::dockPermissions()
+{
+#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.count() == 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(lcQpaDockWidgets) << "*** move d2 to forbidden docks ***";
+
+ // Move d2 to non allowed dock areas and verify it remains floating
+ qCDebug(lcQpaDockWidgets) << "Move d2 to top dock";
+ moveDockWidget(d2, dockPoint(mainWindow, Qt::TopDockWidgetArea));
+ QTRY_VERIFY(d2->isFloating());
+
+ qCDebug(lcQpaDockWidgets) << "Move d2 to left dock";
+ //moveDockWidget(d2, d2->mapFrom(MainWindow, dockPoint(MainWindow, Qt::LeftDockWidgetArea)));
+ moveDockWidget(d2, dockPoint(mainWindow, Qt::LeftDockWidgetArea));
+ QTRY_VERIFY(d2->isFloating());
+
+ qCDebug(lcQpaDockWidgets) << "Move d2 to bottom dock";
+ moveDockWidget(d2, dockPoint(mainWindow, Qt::BottomDockWidgetArea));
+ QTRY_VERIFY(d2->isFloating());
+
+ qCDebug(lcQpaDockWidgets) << "Waiting" << waitBeforeClose << "ms before closing.";
+ QTest::qWait(waitBeforeClose);
+#else
+ QSKIP("test requires -developer-build option");
+#endif // QT_BUILD_INTERNAL
+}
+
QTEST_MAIN(tst_QDockWidget)
#include "tst_qdockwidget.moc"