/* This file is part of KDDockWidgets. SPDX-FileCopyrightText: 2019-2020 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Sérgio Martins SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only Contact KDAB at for commercial licensing options. */ // We don't care about performance related checks in the tests // clazy:excludeall=ctor-missing-parent-argument,missing-qobject-macro,range-loop,missing-typeinfo,detaching-member,function-args-by-ref,non-pod-global-static,reserve-candidates,qstring-allocations #include "Testing.h" #include "utils.h" #include "DockWidgetBase.h" #include "multisplitter/Separator_p.h" #include "private/MultiSplitter_p.h" #include "TitleBar_p.h" #include "Position_p.h" #include "DropAreaWithCentralFrame_p.h" #include "WindowBeingDragged_p.h" #include #include #include #ifdef KDDOCKWIDGETS_QTQUICK # include "quick/DockWidgetQuick.h" # include # include # else # include "DockWidget.h" # include #endif using namespace KDDockWidgets; using namespace Layouting; using namespace KDDockWidgets::Tests; class TestCommon : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase() { qputenv("KDDOCKWIDGETS_SHOW_DEBUG_WINDOW", ""); qApp->setOrganizationName("KDAB"); qApp->setApplicationName("dockwidgets-unit-tests"); Testing::installFatalMessageHandler(); #ifdef KDDOCKWIDGETS_QTQUICK QQuickStyle::setStyle("Material"); // so we don't load KDE pluginss KDDockWidgets::Config::self().setQmlEngine(new QQmlEngine(this)); #endif QTest::qWait(10); // the DND state machine needs the event loop to start, otherwise activeState() is nullptr. (for offscreen QPA) } void cleanupTestCase() { #ifdef KDDOCKWIDGETS_QTQUICK delete KDDockWidgets::Config::Config::self().qmlEngine(); #endif } private Q_SLOTS: void tst_simple1(); void tst_simple2(); void tst_doesntHaveNativeTitleBar(); void tst_resizeWindow2(); void tst_hasLastDockedLocation(); void tst_ghostSeparator(); void tst_detachFromMainWindow(); void tst_detachPos(); void tst_floatingWindowSize(); void tst_sizeAfterRedock(); void tst_tabbingWithAffinities(); void tst_honourUserGeometry(); void tst_floatingWindowTitleBug(); void tst_setFloatingSimple(); void tst_resizeWindow_data(); void tst_resizeWindow(); void tst_restoreEmpty(); void tst_restoreCentralFrame(); void tst_shutdown(); void tst_doubleClose(); void tst_dockInternal(); void tst_maximizeAndRestore(); void tst_propagateResize2(); void tst_28NestedWidgets(); void tst_28NestedWidgets_data(); void tst_negativeAnchorPosition2(); void tst_negativeAnchorPosition3(); void tst_negativeAnchorPosition4(); void tst_negativeAnchorPosition5(); void tst_startHidden(); void tst_closeReparentsToNull(); void tst_invalidAnchorGroup(); }; void TestCommon::tst_simple1() { // Simply create a MainWindow EnsureTopLevelsDeleted e; auto m = createMainWindow(); m->multiSplitter()->checkSanity(); } void TestCommon::tst_simple2() { // Simply create a MainWindow, and dock something on top EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dw = createDockWidget("dw", new MyWidget("dw", Qt::blue)); auto fw = dw->floatingWindow(); m->addDockWidget(dw, KDDockWidgets::Location_OnTop); m->multiSplitter()->checkSanity(); delete fw; } void TestCommon::tst_doesntHaveNativeTitleBar() { // Tests that a floating window doesn't have a native title bar // This test is mostly to test a bug that was happening with QtQuick, where the native title bar // would appear on linux EnsureTopLevelsDeleted e; auto dw1 = createDockWidget("dock1"); FloatingWindow *fw = dw1->floatingWindow(); QVERIFY(fw); QVERIFY(fw->windowFlags() & Qt::Tool); #if defined(Q_OS_LINUX) QVERIFY(fw->windowFlags() & Qt::FramelessWindowHint); #elif defined(Q_OS_WIN) QVERIFY(!(fw->windowFlags() & Qt::FramelessWindowHint)); #endif delete dw1->window(); } void TestCommon::tst_resizeWindow2() { // Tests that resizing the width of the main window will never move horizontal anchors EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto dock1 = createDockWidget("1"); auto dock2 = createDockWidget("2"); FloatingWindow *fw1 = dock1->floatingWindow(); FloatingWindow *fw2 = dock2->floatingWindow(); m->addDockWidget(dock1, Location_OnTop); m->addDockWidget(dock2, Location_OnBottom); auto layout = m->multiSplitter(); Separator *anchor = layout->separators().at(0); const int oldPosY = anchor->position(); m->resize(QSize(m->width() + 10, m->height())); QCOMPARE(anchor->position(), oldPosY); layout->checkSanity(); delete fw1; delete fw2; } void TestCommon::tst_hasLastDockedLocation() { // Tests DockWidgetBase::hasPreviousDockedLocation() EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto dock1 = createDockWidget("1"); m->multiSplitter()->checkSanity(); m->multiSplitter()->setObjectName("mainWindow-dropArea"); dock1->floatingWindow()->multiSplitter()->setObjectName("first-dropArea1"); dock1->floatingWindow()->multiSplitter()->checkSanity(); auto window1 = dock1->window(); QVERIFY(dock1->isFloating()); QVERIFY(!dock1->hasPreviousDockedLocation()); QVERIFY(dock1->setFloating(true)); QVERIFY(!dock1->setFloating(false)); // No docking location, so it's not docked QVERIFY(dock1->isFloating()); QVERIFY(!dock1->hasPreviousDockedLocation()); m->addDockWidget(dock1, Location_OnBottom); m->multiSplitter()->checkSanity(); QVERIFY(!dock1->isFloating()); QVERIFY(dock1->setFloating(true)); auto ms1 = dock1->floatingWindow()->multiSplitter(); ms1->setObjectName("dropArea1"); ms1->checkSanity(); QVERIFY(dock1->hasPreviousDockedLocation()); auto window11 = dock1->window(); QVERIFY(dock1->setFloating(false)); delete window1; delete window11; } void TestCommon::tst_ghostSeparator() { // Tests a situation where a separator wouldn't be removed after a widget had been removed EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto dock1 = createDockWidget("1"); auto dock2 = createDockWidget("2"); auto dock3 = createDockWidget("3"); QPointer fw1 = dock1->floatingWindow(); QPointer fw2 = dock2->floatingWindow(); QPointer fw3 = dock3->floatingWindow(); dock1->addDockWidgetToContainingWindow(dock2, Location_OnRight); QCOMPARE(fw1->multiSplitter()->separators().size(), 1); QCOMPARE(Layouting::Separator::numSeparators(), 1); m->addDockWidget(dock3, Location_OnBottom); QCOMPARE(m->multiSplitter()->separators().size(), 0); QCOMPARE(Layouting::Separator::numSeparators(), 1); m->multiSplitter()->addMultiSplitter(fw1->multiSplitter(), Location_OnRight); QCOMPARE(m->multiSplitter()->separators().size(), 2); QCOMPARE(Layouting::Separator::numSeparators(), 2); delete fw1; delete fw2; delete fw3; } void TestCommon::tst_detachFromMainWindow() { // Tests a situation where clicking the float button wouldn't work on QtQuick EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto dock1 = createDockWidget("1"); auto fw1 = dock1->window(); m->addDockWidget(dock1, Location_OnTop); QVERIFY(m->multiSplitter()->mainWindow() != nullptr); QVERIFY(!dock1->isFloating()); TitleBar *tb = dock1->titleBar(); QVERIFY(tb == dock1->frame()->titleBar()); QVERIFY(tb->isVisible()); QVERIFY(!tb->isFloating()); delete fw1; } void TestCommon::tst_detachPos() { // Tests a situation where detaching a dock widget would send it to a bogus position EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new MyWidget(QStringLiteral("1"), Qt::black), {}, /** show = */false); // we're creating the dock widgets without showing them as floating initially, so it doesn't record the previous floating position auto dock2 = createDockWidget("2", new MyWidget(QStringLiteral("2"), Qt::black), {}, /** show = */false); QVERIFY(!dock1->isVisible()); QVERIFY(!dock2->isVisible()); m->addDockWidget(dock1, Location_OnLeft); m->addDockWidget(dock2, Location_OnRight); QVERIFY(!dock1->lastPositions().lastFloatingGeometry().isValid()); QVERIFY(!dock2->lastPositions().lastFloatingGeometry().isValid()); const int previousWidth = dock1->width(); dock1->setFloating(true); QTest::qWait(400); // Needed for QtQuick QVERIFY(qAbs(previousWidth - dock1->width()) < 15); // 15px of difference when floating is fine, due to margins and what not. delete dock1->window(); } void TestCommon::tst_floatingWindowSize() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto dock1 = createDockWidget("1"); auto fw1 = dock1->window(); QTest::qWait(2000); QVERIFY(!fw1->geometry().isNull()); QCOMPARE(fw1->size(), fw1->windowHandle()->size()); delete fw1; } void TestCommon::tst_tabbingWithAffinities() { EnsureTopLevelsDeleted e; // Tests that dock widgets with different affinities should not tab together auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None); m1->setAffinities({ "af1", "af2" }); auto dw1 = new DockWidgetType("1"); dw1->setAffinities({ "af1" }); dw1->show(); auto dw2 = new DockWidgetType("2"); dw2->setAffinities({ "af2" }); dw2->show(); FloatingWindow *fw1 = dw1->floatingWindow(); FloatingWindow *fw2 = dw2->floatingWindow(); { SetExpectedWarning ignoreWarning("Refusing to dock widget with incompatible affinity"); dw1->addDockWidgetAsTab(dw2); QVERIFY(dw1->window() != dw2->window()); } m1->addDockWidget(dw1, Location_OnBottom); QVERIFY(!dw1->isFloating()); { SetExpectedWarning ignoreWarning("Refusing to dock widget with incompatible affinity"); auto dropArea = m1->dropArea(); WindowBeingDragged wbd(fw2, fw2); QVERIFY(!dropArea->drop(&wbd, dw1->frame(), DropIndicatorOverlayInterface::DropLocation_Center)); QVERIFY(dw1->window() != dw2->window()); } delete fw1; delete fw2; } void TestCommon::tst_sizeAfterRedock() { EnsureTopLevelsDeleted e; auto dw1 = new DockWidgetType(QStringLiteral("1")); auto dw2 = new DockWidgetType(QStringLiteral("2")); dw2->setWidget(new MyWidget("2", Qt::red)); dw1->addDockWidgetToContainingWindow(dw2, Location_OnBottom); const int height2 = dw2->frame()->height(); dw2->setFloating(true); QCOMPARE(height2, dw2->window()->height()); auto oldFw2 = dw2->floatingWindow(); // Redock FloatingWindow *fw1 = dw1->floatingWindow(); DropArea *dropArea = fw1->dropArea(); MultiSplitter *ms1 = fw1->multiSplitter(); { WindowBeingDragged wbd2(oldFw2); const QRect suggestedDropRect = ms1->rectForDrop(&wbd2, Location_OnBottom, nullptr); QCOMPARE(suggestedDropRect.height(), height2); } dropArea->drop(dw2->floatingWindow(), Location_OnBottom, nullptr); QCOMPARE(dw2->frame()->height(), height2); delete dw1->window(); delete oldFw2; } void TestCommon::tst_honourUserGeometry() { EnsureTopLevelsDeleted e; auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None); auto dw1 = new DockWidgetType(QStringLiteral("1")); const QPoint pt(10, 10); dw1->move(pt); dw1->show(); FloatingWindow *fw1 = dw1->floatingWindow(); QCOMPARE(fw1->windowHandle()->geometry().topLeft(), pt); delete dw1->window(); } void TestCommon::tst_floatingWindowTitleBug() { // Test for #74 EnsureTopLevelsDeleted e; auto dw1 = new DockWidgetType(QStringLiteral("1")); auto dw2 = new DockWidgetType(QStringLiteral("2")); auto dw3 = new DockWidgetType(QStringLiteral("3")); dw1->setObjectName(QStringLiteral("1")); dw2->setObjectName(QStringLiteral("2")); dw3->setObjectName(QStringLiteral("3")); dw1->show(); dw1->addDockWidgetAsTab(dw2); dw1->addDockWidgetToContainingWindow(dw3, Location_OnBottom); dw1->titleBar()->onFloatClicked(); QCOMPARE(dw3->titleBar()->title(), QLatin1String("3")); delete dw1->window(); delete dw3->window(); } void TestCommon::tst_resizeWindow_data() { QTest::addColumn("doASaveRestore"); QTest::newRow("false") << false; QTest::newRow("true") << true; } void TestCommon::tst_resizeWindow() { QFETCH(bool, doASaveRestore); EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new MyWidget("1", Qt::red)); auto dock2 = createDockWidget("2", new MyWidget("2", Qt::blue)); QPointer fw1 = dock1->floatingWindow(); QPointer fw2 = dock2->floatingWindow(); m->addDockWidget(dock1, Location_OnLeft); m->addDockWidget(dock2, Location_OnRight); auto layout = m->multiSplitter(); layout->checkSanity(); const int oldWidth1 = dock1->width(); const int oldWidth2 = dock2->width(); QVERIFY(oldWidth2 - oldWidth1 <= 1); // They're not equal if separator thickness if even if (doASaveRestore) { LayoutSaver saver; saver.restoreLayout(saver.serializeLayout()); } m->showMaximized(); Testing::waitForResize(m.get()); const int maximizedWidth1 = dock1->width(); const int maximizedWidth2 = dock2->width(); const double relativeDifference = qAbs((maximizedWidth1 - maximizedWidth2) / (1.0 * layout->width())); qDebug() << oldWidth1 << oldWidth2 << maximizedWidth1 << maximizedWidth2 << relativeDifference; QVERIFY(relativeDifference <= 0.01); m->showNormal(); Testing::waitForResize(m.get()); const int newWidth1 = dock1->width(); const int newWidth2 = dock2->width(); QCOMPARE(oldWidth1, newWidth1); QCOMPARE(oldWidth2, newWidth2); layout->checkSanity(); delete fw1; delete fw2; } void TestCommon::tst_restoreEmpty() { EnsureTopLevelsDeleted e; // Create an empty main window, save it to disk. auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto layout = m->multiSplitter(); LayoutSaver saver; const QSize oldSize = m->size(); QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreEmpty.json"))); saver.restoreFromFile(QStringLiteral("layout_tst_restoreEmpty.json")); QVERIFY(m->multiSplitter()->checkSanity()); QCOMPARE(layout->separators().size(), 0); QCOMPARE(layout->count(), 0); QCOMPARE(m->size(), oldSize); QVERIFY(layout->checkSanity()); } void TestCommon::tst_restoreCentralFrame() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500)); auto layout = m->multiSplitter(); QCOMPARE(layout->count(), 1); Item *item = m->dropArea()->centralFrame(); QVERIFY(item); auto frame = static_cast(item->guestAsQObject()); QCOMPARE(frame->options(), FrameOption_IsCentralFrame | FrameOption_AlwaysShowsTabs); QVERIFY(!frame->titleBar()->isVisible()); LayoutSaver saver; QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreCentralFrame.json"))); QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_restoreCentralFrame.json"))); QCOMPARE(layout->count(), 1); item = m->dropArea()->centralFrame(); QVERIFY(item); frame = static_cast(item->guestAsQObject()); QCOMPARE(frame->options(), FrameOption_IsCentralFrame | FrameOption_AlwaysShowsTabs); QVERIFY(!frame->titleBar()->isVisible()); } void TestCommon::tst_setFloatingSimple() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new MyWidget("one")); m->addDockWidget(dock1, Location_OnTop); auto l = m->multiSplitter(); dock1->setFloating(true); QVERIFY(l->checkSanity()); dock1->setFloating(false); QVERIFY(l->checkSanity()); dock1->setFloating(true); QVERIFY(l->checkSanity()); dock1->setFloating(false); QVERIFY(l->checkSanity()); } int main(int argc, char *argv[]) { if (!qpaPassedAsArgument(argc, argv)) { // Use offscreen by default as it's less annoying, doesn't create visible windows qputenv("QT_QPA_PLATFORM", "offscreen"); } QApplication app(argc, argv); if (shouldSkipTests()) return 0; TestCommon test; return QTest::qExec(&test, argc, argv); } void TestCommon::tst_doubleClose() { EnsureTopLevelsDeleted e; { // Via close() auto dock1 = createDockWidget("hello", Qt::green); dock1->close(); dock1->close(); delete dock1->window(); } { // Via the button auto dock1 = createDockWidget("hello", Qt::green); auto fw1 = dock1->floatingWindow(); auto t = dock1->frame()->titleBar(); t->onCloseClicked(); t->onCloseClicked(); delete dock1; delete fw1; } } void TestCommon::tst_dockInternal() { /** * Here we dock relative to an existing widget, and not to the drop-area. */ EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dropArea = m->dropArea(); auto centralWidget = static_cast(dropArea->items()[0]->guestAsQObject()); nestDockWidget(dock1, dropArea, centralWidget, KDDockWidgets::Location_OnRight); QVERIFY(dock1->width() < dropArea->width() - centralWidget->width()); } void TestCommon::tst_maximizeAndRestore() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); m->addDockWidget(dock2, KDDockWidgets::Location_OnRight); auto dropArea = m->dropArea(); QVERIFY(dropArea->checkSanity()); m->showMaximized(); Testing::waitForResize(m.get()); QVERIFY(dropArea->checkSanity()); qDebug() << "About to show normal"; m->showNormal(); Testing::waitForResize(m.get()); QVERIFY(dropArea->checkSanity()); } void TestCommon::tst_propagateResize2() { // |5|1|2| // | |3|4| EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); m->addDockWidget(dock1, KDDockWidgets::Location_OnTop); m->addDockWidget(dock2, KDDockWidgets::Location_OnRight, dock1); auto dock3 = createDockWidget("dock3", new QPushButton("three")); auto dock4 = createDockWidget("dock4", new QPushButton("four")); m->addDockWidget(dock3, KDDockWidgets::Location_OnBottom); m->addDockWidget(dock4, KDDockWidgets::Location_OnRight, dock3); auto dock5 = createDockWidget("dock5", new QPushButton("five")); m->addDockWidget(dock5, KDDockWidgets::Location_OnLeft); auto dropArea = m->dropArea(); dropArea->checkSanity(); } void TestCommon::tst_shutdown() { EnsureTopLevelsDeleted e; auto dock = createDockWidget("doc1", Qt::green); auto m = createMainWindow(); m->show(); QVERIFY(QTest::qWaitForWindowActive(m->windowHandle())); delete dock->window(); } void TestCommon::tst_28NestedWidgets_data() { QTest::addColumn>("docksToCreate"); QTest::addColumn>("docksToHide"); QVector docks = { {Location_OnLeft, -1, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnTop, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnLeft, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnBottom, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None } }; QTest::newRow("28") << docks << QVector{11, 0}; docks = { {Location_OnLeft, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnTop, -1, nullptr, AddingOption_None }, {Location_OnLeft, -1, nullptr, AddingOption_None }, }; QVector docksToHide; for (int i = 0; i < docks.size(); ++i) { docksToHide << i; } QTest::newRow("anchor_intersection") << docks << docksToHide; docks = { {Location_OnLeft, -1, nullptr, AddingOption_None }, {Location_OnBottom, -1, nullptr, AddingOption_None }, {Location_OnBottom, -1, nullptr, AddingOption_None }, {Location_OnBottom, -1, nullptr, AddingOption_None }, {Location_OnBottom, -1, nullptr, AddingOption_None }, {Location_OnBottom, -1, nullptr, AddingOption_None }, {Location_OnTop, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, }; // 2. Produced valgrind invalid reads while adding QTest::newRow("valgrind") << docks << QVector{}; docks = { {Location_OnLeft, -1, nullptr, AddingOption_None }, {Location_OnBottom, -1, nullptr, AddingOption_None }, {Location_OnTop, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, }; QTest::newRow("bug_when_closing") << docks << QVector{}; // Q_ASSERT(!isSquashed()) docks = { {Location_OnLeft, -1, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnBottom, -1, nullptr, AddingOption_None }, }; QTest::newRow("bug_when_closing2") << docks << QVector{}; // Tests for void KDDockWidgets::Anchor::setPosition(int, KDDockWidgets::Anchor::SetPositionOptions) Negative position -69 docks = { {Location_OnLeft, -1, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnBottom, 0, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnTop, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnLeft, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnBottom, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None } }; docksToHide.clear(); for (int i = 0; i < 28; ++i) { if (i != 16 && i != 17 && i != 18 && i != 27) docksToHide << i; } QTest::newRow("bug_with_holes") << docks << docksToHide; docks = { {Location_OnLeft, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnTop, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnLeft, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnLeft, 17, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None } }; docksToHide.clear(); QTest::newRow("add_as_placeholder") << docks << docksToHide; docks = { {Location_OnLeft, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden } }; QTest::newRow("add_as_placeholder_simple") << docks << docksToHide; docks = { {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden } }; docksToHide.clear(); QTest::newRow("isSquashed_assert") << docks << docksToHide; docks = { {Location_OnLeft, -1, nullptr, AddingOption_StartHidden }, {Location_OnTop, -1, nullptr, AddingOption_None }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden } }; docksToHide.clear(); QTest::newRow("negative_pos_warning") << docks << docksToHide; docks = { {Location_OnTop, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_None } }; docksToHide.clear(); QTest::newRow("bug") << docks << docksToHide; docks = { {Location_OnTop, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_None } }; docksToHide.clear(); QTest::newRow("bug2") << docks << docksToHide; docks = { {Location_OnLeft, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_StartHidden }, {Location_OnTop, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnLeft, -1, nullptr, AddingOption_None }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_None } }; docksToHide.clear(); QTest::newRow("bug3") << docks << docksToHide; } void TestCommon::tst_28NestedWidgets() { QFETCH(QVector, docksToCreate); QFETCH(QVector, docksToHide); // Tests a case that used to cause negative anchor position when turning into placeholder EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; int i = 0; for (DockDescriptor &desc : docksToCreate) { desc.createdDock = createDockWidget(QString("%1").arg(i), new QPushButton(QString("%1").arg(i).toLatin1()), {}, false); DockWidgetBase *relativeTo = nullptr; if (desc.relativeToIndex != -1) relativeTo = docksToCreate.at(desc.relativeToIndex).createdDock; m->addDockWidget(desc.createdDock, desc.loc, relativeTo, desc.option); QVERIFY(layout->checkSanity()); ++i; } layout->checkSanity(); // Run the saver in these complex scenarios: LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); QVERIFY(!saved.isEmpty()); QVERIFY(saver.restoreLayout(saved)); layout->checkSanity(); for (int i : docksToHide) { docksToCreate.at(i).createdDock->close(); layout->checkSanity(); QTest::qWait(200); } layout->checkSanity(); for (int i : docksToHide) { docksToCreate.at(i).createdDock->deleteLater(); QVERIFY(Testing::waitForDeleted(docksToCreate.at(i).createdDock)); } layout->checkSanity(); // And hide the remaining ones i = 0; for (auto dock : docksToCreate) { if (dock.createdDock && dock.createdDock->isVisible()) { dock.createdDock->close(); QTest::qWait(200); // Wait for the docks to be closed. TODO Replace with a global event filter and wait for any resize ? } ++i; } layout->checkSanity(); // Cleanup for (auto dock : DockRegistry::self()->dockwidgets()) { dock->deleteLater(); QVERIFY(Testing::waitForDeleted(dock)); } } void TestCommon::tst_closeReparentsToNull() { EnsureTopLevelsDeleted e; auto dock1 = createDockWidget("1", new QPushButton("1")); auto fw1 = dock1->window(); QVERIFY(dock1->parent() != nullptr); dock1->close(); QVERIFY(dock1->parent() == nullptr); delete fw1; delete dock1; } void TestCommon::tst_startHidden() { // A really simple test for AddingOption_StartHidden EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new QPushButton("1"), {}, /*show=*/false); m->addDockWidget(dock1, Location_OnRight, nullptr, AddingOption_StartHidden); delete dock1; } void TestCommon::tst_negativeAnchorPosition2() { // Tests that the "Out of bounds position" warning doesn't appear. Test will abort if yes. EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; auto dock1 = createDockWidget("1", new QPushButton("1"), {}, /*show=*/false); auto dock2 = createDockWidget("2", new QPushButton("2"), {}, /*show=*/false); auto dock3 = createDockWidget("3", new QPushButton("3"), {}, /*show=*/false); m->addDockWidget(dock1, Location_OnLeft); m->addDockWidget(dock2, Location_OnRight, nullptr, AddingOption_StartHidden); m->addDockWidget(dock3, Location_OnRight); QCOMPARE(layout->placeholderCount(), 1); QCOMPARE(layout->count(), 3); dock1->setFloating(true); dock1->setFloating(false); dock2->deleteLater(); layout->checkSanity(); QVERIFY(Testing::waitForDeleted(dock2)); } void TestCommon::tst_negativeAnchorPosition3() { // 1. Another case, when floating a dock: EnsureTopLevelsDeleted e; QVector docks = { {Location_OnLeft, -1, nullptr, AddingOption_None }, {Location_OnRight, -1, nullptr, AddingOption_None }, {Location_OnLeft, -1, nullptr, AddingOption_None }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnRight, -1, nullptr, AddingOption_None } }; auto m = createMainWindow(docks); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; layout->checkSanity(); auto dock1 = docks.at(1).createdDock; auto dock3 = docks.at(3).createdDock; dock1->setFloating(true); delete dock1->window(); delete dock3->window(); layout->checkSanity(); } void TestCommon::tst_negativeAnchorPosition4() { // 1. Tests that we don't get a warning // Out of bounds position= -5 ; oldPosition= 0 KDDockWidgets::Anchor(0x55e726be9090, name = "left") KDDockWidgets::MainWindow(0x55e726beb8d0) EnsureTopLevelsDeleted e; QVector docks = { { Location_OnLeft, -1, nullptr, AddingOption_StartHidden }, { Location_OnTop, -1, nullptr, AddingOption_None }, { Location_OnRight, -1, nullptr, AddingOption_None }, { Location_OnLeft, -1, nullptr, AddingOption_None }, { Location_OnRight, -1, nullptr, AddingOption_None } }; auto m = createMainWindow(docks); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; layout->checkSanity(); auto dock1 = docks.at(1).createdDock; auto dock2 = docks.at(2).createdDock; dock2->setFloating(true); auto fw2 = dock2->floatingWindow(); dropArea->addWidget(fw2->dropArea(), Location_OnLeft, dock1->frame()); dock2->setFloating(true); fw2 = dock2->floatingWindow(); dropArea->addWidget(fw2->dropArea(), Location_OnRight, dock1->frame()); layout->checkSanity(); docks.at(0).createdDock->deleteLater(); docks.at(4).createdDock->deleteLater(); Testing::waitForDeleted(docks.at(4).createdDock); } void TestCommon::tst_negativeAnchorPosition5() { EnsureTopLevelsDeleted e; QVector docks = { {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, {Location_OnBottom, -1, nullptr, AddingOption_StartHidden }, }; auto m = createMainWindow(docks); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; layout->checkSanity(); auto dock0 = docks.at(0).createdDock; auto dock1 = docks.at(1).createdDock; dock1->show(); dock0->show(); layout->checkSanity(); // Cleanup for (auto dock : DockRegistry::self()->dockwidgets()) dock->deleteLater(); QVERIFY(Testing::waitForDeleted(dock0)); } void TestCommon::tst_invalidAnchorGroup() { // Tests a bug I got. Should not warn. EnsureTopLevelsDeleted e; { auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); QPointer fw = dock2->morphIntoFloatingWindow(); nestDockWidget(dock1, fw->dropArea(), nullptr, KDDockWidgets::Location_OnTop); dock1->close(); Testing::waitForResize(dock2); auto layout = fw->dropArea(); layout->checkSanity(); dock2->close(); dock1->deleteLater(); dock2->deleteLater(); Testing::waitForDeleted(dock1); } { // Stack 1, 2, 3, close 2, close 1 auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto dock3 = createDockWidget("dock3", new QPushButton("three")); m->addDockWidget(dock3, Location_OnTop); m->addDockWidget(dock2, Location_OnTop); m->addDockWidget(dock1, Location_OnTop); dock2->close(); dock1->close(); dock1->deleteLater(); dock2->deleteLater(); Testing::waitForDeleted(dock1); } } #include "tst_common.moc"