/* This file is part of KDDockWidgets. SPDX-FileCopyrightText: 2019-2021 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 "tst_docks.h" #include "Config.h" #include "DockWidgetBase.h" #include "DockWidgetBase_p.h" #include "DropAreaWithCentralFrame_p.h" #include "LayoutSaver_p.h" #include "MDILayoutWidget_p.h" #include "MainWindowMDI.h" #include "Position_p.h" #include "SideBar_p.h" #include "TabWidget_p.h" #include "TitleBar_p.h" #include "WindowBeingDragged_p.h" #include "multisplitter/Separator_p.h" #include "private/MultiSplitter_p.h" #include #ifdef Q_OS_WIN # include #endif using namespace KDDockWidgets; using namespace Layouting; using namespace KDDockWidgets::Tests; static int osWindowMinWidth() { #ifdef Q_OS_WIN return GetSystemMetrics(SM_CXMIN); #else return 140; // Some random value for our windows. It's only important on Windows #endif } static QPoint dragPointForWidget(Frame *frame, int index) { if (frame->hasSingleDockWidget()) { Q_ASSERT(index == 0); return frame->titleBar()->mapToGlobal(QPoint(5, 5)); } else { QRect rect = frame->tabWidget()->tabBar()->rectForTab(index); return frame->tabWidget()->tabBar()->asWidget()->mapToGlobal(rect.center()); } } template inline int widgetMinLength(const T *w, Qt::Orientation o) { const QSize sz = Widget::widgetMinSize(w); return o == Qt::Vertical ? sz.height() : sz.width(); } static DockWidgetBase *createAndNestDockWidget(DropArea *dropArea, Frame *relativeTo, KDDockWidgets::Location location) { static int count = 0; count++; const QString name = QString("dock%1").arg(count); auto dock = createDockWidget(name, Qt::red); dock->setObjectName(name); nestDockWidget(dock, dropArea, relativeTo, location); dropArea->checkSanity(); return dock; } static std::unique_ptr createSimpleNestedMainWindow(DockWidgetBase * *centralDock, DockWidgetBase * *leftDock, DockWidgetBase * *rightDock) { auto window = createMainWindow({900, 500}); *centralDock = createDockWidget("centralDock", Qt::green); window->addDockWidgetAsTab(*centralDock); auto dropArea = window->dropArea(); *leftDock = createAndNestDockWidget(dropArea, nullptr, KDDockWidgets::Location_OnLeft); *rightDock = createAndNestDockWidget(dropArea, nullptr, KDDockWidgets::Location_OnRight); return window; } void TestDocks::tst_simple1() { // Simply create a MainWindow EnsureTopLevelsDeleted e; auto m = createMainWindow(); m->layoutWidget()->checkSanity(); } void TestDocks::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->layoutWidget()->checkSanity(); delete fw; } void TestDocks::tst_restoreSimple() { EnsureTopLevelsDeleted e; // Tests restoring a very simple layout, composed of just 1 docked widget auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto layout = m->multiSplitter(); auto dock1 = createDockWidget("one", new QTextEdit()); auto dock2 = createDockWidget("two", new QTextEdit()); auto dock3 = createDockWidget("three", new QTextEdit()); m->addDockWidget(dock1, Location_OnTop); // Dock2 floats at 150,150 const QPoint dock2FloatingPoint = QPoint(150, 150); dock2->window()->move(dock2FloatingPoint); QVERIFY(dock2->isVisible()); const QPoint dock3FloatingPoint = QPoint(200, 200); dock3->window()->move(dock3FloatingPoint); dock3->close(); LayoutSaver saver; QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreSimple.json"))); auto f1 = dock1->dptr()->frame(); dock2->window()->move(QPoint(0, 0)); // Move *after* we saved. dock3->window()->move(QPoint(0, 0)); // Move *after* we saved. dock1->close(); dock2->close(); QVERIFY(!dock2->isVisible()); QCOMPARE(layout->count(), 1); QVERIFY(Testing::waitForDeleted(f1)); QCOMPARE(layout->placeholderCount(), 1); QCOMPARE(DockRegistry::self()->floatingWindows().size(), 0); QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_restoreSimple.json"))); QVERIFY(layout->checkSanity()); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); QVERIFY(dock1->isVisible()); QCOMPARE(saver.restoredDockWidgets().size(), 3); // Test a crash I got: dock1->setFloating(true); QVERIFY(layout->checkSanity()); dock1->setFloating(false); auto fw2 = dock2->floatingWindow(); QVERIFY(fw2); QVERIFY(fw2->isVisible()); QVERIFY(fw2->isTopLevel()); QCOMPARE(fw2->pos(), dock2FloatingPoint); QCOMPARE(fw2->windowHandle()->transientParent(), m->windowHandle()); QVERIFY(dock2->isFloating()); QVERIFY(dock2->isVisible()); QVERIFY(!dock3->isVisible()); // Remains closed QVERIFY(dock3->parentWidget() == nullptr); dock3->show(); dock3->dptr()->morphIntoFloatingWindow(); // as it would take 1 event loop. Do it now so we can // compare already. QCOMPARE(dock3->window()->pos(), dock3FloatingPoint); } void TestDocks::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 TestDocks::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 TestDocks::tst_hasLastDockedLocation() { // Tests DockWidgetBase::hasPreviousDockedLocation() EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto dock1 = createDockWidget("1"); m->layoutWidget()->checkSanity(); m->multiSplitter()->setObjectName("mainWindow-dropArea"); dock1->floatingWindow()->layoutWidget()->setObjectName("first-dropArea1"); dock1->floatingWindow()->layoutWidget()->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->layoutWidget()->checkSanity(); QVERIFY(!dock1->isFloating()); QVERIFY(dock1->setFloating(true)); auto ms1 = dock1->floatingWindow()->layoutWidget(); ms1->setObjectName("dropArea1"); ms1->checkSanity(); QVERIFY(dock1->hasPreviousDockedLocation()); auto window11 = dock1->window(); QVERIFY(dock1->setFloating(false)); delete window1; delete window11; } void TestDocks::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 TestDocks::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->layoutWidget()->mainWindow() != nullptr); QVERIFY(!dock1->isFloating()); TitleBar *tb = dock1->titleBar(); QVERIFY(tb == dock1->dptr()->frame()->titleBar()); QVERIFY(tb->isVisible()); QVERIFY(!tb->isFloating()); delete fw1; } void TestDocks::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->dptr()->lastPositions().lastFloatingGeometry().isValid()); QVERIFY(!dock2->dptr()->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 TestDocks::tst_floatingWindowSize() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto dock1 = createDockWidget("1"); auto fw1 = dock1->window(); QTest::qWait(100); QVERIFY(!fw1->geometry().isNull()); QCOMPARE(fw1->size(), fw1->windowHandle()->size()); delete fw1; } void TestDocks::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->dptr()->frame(), DropIndicatorOverlayInterface::DropLocation_Center)); QVERIFY(dw1->window() != dw2->window()); } delete fw1; delete fw2; } void TestDocks::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->dptr()->frame()->height(); dw2->setFloating(true); QTest::qWait(100); 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->dptr()->frame()->height(), height2); delete dw1->window(); delete oldFw2; } void TestDocks::tst_honourUserGeometry() { EnsureTopLevelsDeleted e; auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None); auto dw1 = new DockWidgetType(QStringLiteral("1")); QVERIFY(!dw1->testAttribute(Qt::WA_PendingMoveEvent)); const QPoint pt(10, 10); dw1->move(pt); dw1->show(); FloatingWindow *fw1 = dw1->floatingWindow(); QCOMPARE(fw1->windowHandle()->geometry().topLeft(), pt); delete dw1->window(); } void TestDocks::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 TestDocks::tst_resizeWindow_data() { QTest::addColumn("doASaveRestore"); QTest::newRow("false") << false; QTest::newRow("true") << true; } void TestDocks::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())); 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 TestDocks::tst_restoreTwice() { // Tests that restoring multiple times doesn't hide the floating windows for some reason EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(500, 500), MainWindowOption_HasCentralFrame, "tst_restoreTwice"); auto dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidgetAsTab(dock1); auto dock2 = createDockWidget("2", new QPushButton("2")); auto dock3 = createDockWidget("3", new QPushButton("3")); dock2->dptr()->morphIntoFloatingWindow(); dock3->dptr()->morphIntoFloatingWindow(); { LayoutSaver saver; QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreTwice.json"))); QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_restoreTwice.json"))); QVERIFY(dock2->isVisible()); QVERIFY(dock3->isVisible()); } { LayoutSaver saver; QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_restoreTwice.json"))); QVERIFY(dock2->isVisible()); QVERIFY(dock3->isVisible()); QVERIFY(dock2->window()->isVisible()); QVERIFY(dock3->window()->isVisible()); auto fw = dock2->floatingWindow(); QVERIFY(fw); } } void TestDocks::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->layoutWidget()->checkSanity()); QCOMPARE(layout->separators().size(), 0); QCOMPARE(layout->count(), 0); QCOMPARE(m->size(), oldSize); QVERIFY(layout->checkSanity()); } void TestDocks::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 TestDocks::tst_restoreMaximizedState() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); m->showMaximized(); QCOMPARE(m->windowHandle()->windowState(), Qt::WindowMaximized); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); m->showNormal(); QVERIFY(m->windowHandle()->windowState() != Qt::WindowMaximized); saver.restoreLayout(saved); QCOMPARE(m->windowHandle()->windowState(), Qt::WindowMaximized); } void TestDocks::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()); } void TestDocks::tst_nonDockable() { { // First test without Option_NotDockable auto dock = new DockWidgetType("1"); dock->show(); TitleBar *tb = dock->titleBar(); QVERIFY(tb->isVisible()); QVERIFY(tb->isFloatButtonVisible()); delete dock->window(); } { // Test that when using Option_NotDockable we don't get a dock/undock icon auto dock = new DockWidgetType("1", DockWidgetBase::Option_NotDockable); dock->show(); TitleBar *tb = dock->titleBar(); QVERIFY(tb->isVisible()); QVERIFY(!tb->isFloatButtonVisible()); delete dock->window(); } } 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; TestDocks test; return QTest::qExec(&test, argc, argv); } void TestDocks::tst_closeDockWidgets() { EnsureTopLevelsDeleted e; auto dock1 = createDockWidget("hello1", Qt::green); auto dock2 = createDockWidget("hello2", Qt::green); auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); m->addDockWidget(dock1, Location_OnBottom); m->addDockWidget(dock2, Location_OnBottom); QVERIFY(m->closeDockWidgets(true)); QCOMPARE(m->layoutWidget()->visibleCount(), 0); } void TestDocks::tst_layoutEqually() { EnsureTopLevelsDeleted e; const QString mainWindowId = "{7829427d-88e3-402e-9120-50c628dfd0bc}"; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None, mainWindowId); m->setAffinities({ mainWindowId }); auto dock1 = createDockWidget("Favorite-481", new MyWidget2(QSize(536, 438))); auto dock2 = createDockWidget("Favorite-482", new MyWidget2(QSize(229, 118))); auto dock3 = createDockWidget("Favorite-483", new MyWidget2(QSize(356, 90))); #ifdef KDDOCKWIDGETS_QTWIDGETS m->setContentsMargins(10, 0, 10, 0); #endif dock1->setAffinities({ mainWindowId }); dock2->setAffinities({ mainWindowId }); dock3->setAffinities({ mainWindowId }); LayoutSaver restorer; restorer.restoreFromFile(":/layouts/layoutEquallyCrash.json"); m->layoutEqually(); } void TestDocks::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->dptr()->frame()->titleBar(); t->onCloseClicked(); t->onCloseClicked(); delete dock1; delete fw1; } { // Test for #141, double delete would ruin lastPositions() EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnBottom); QVERIFY(!dock1->dptr()->lastPositions().wasFloating()); dock1->close(); QVERIFY(!dock1->dptr()->lastPositions().wasFloating()); dock1->close(); QVERIFY(!dock1->dptr()->lastPositions().wasFloating()); } } void TestDocks::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 TestDocks::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()); m->showNormal(); Testing::waitForResize(m.get()); QVERIFY(dropArea->checkSanity()); } void TestDocks::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 TestDocks::tst_shutdown() { EnsureTopLevelsDeleted e; auto dock = createDockWidget("doc1", Qt::green); auto m = createMainWindow(); m->show(); QVERIFY(QTest::qWaitForWindowActive(m->windowHandle())); delete dock->window(); } #ifdef KDDOCKWIDGETS_QTWIDGETS void TestDocks::tst_complex() { // Tests some anchors out of bounds I got EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(600, 500), MainWindowOption_None); auto layout = m->multiSplitter(); m->resize(3266, 2239); m->show(); // TODO: Remove and see if it crashes DockWidgetBase::List docks; QVector locations = {Location_OnLeft, Location_OnLeft, Location_OnLeft, Location_OnRight, Location_OnRight, Location_OnRight, Location_OnRight, Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom }; QVector options = { InitialVisibilityOption::StartVisible, InitialVisibilityOption::StartVisible, InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartVisible, InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden,InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden,InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartVisible, InitialVisibilityOption::StartVisible, InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden,InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden,InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden,InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden }; QVector floatings = {true, false, true, false, false, false, false, false, false, false, false, false, true, false, false, true, true, true, true, true, false }; QVector minSizes= { QSize(316, 219), QSize(355, 237), QSize(293, 66), QSize(158, 72), QSize(30, 141), QSize(104, 143), QSize(104, 105), QSize(84, 341), QSize(130, 130), QSize(404, 205), QSize(296, 177), QSize(914, 474), QSize(355, 237), QSize(104, 104), QSize(104, 138), QSize(1061, 272), QSize(165, 196), QSize(296, 177), QSize(104, 104), QSize(355, 237), QSize(104, 138) }; const int num = 21; for (int i = 0; i < num; ++i) { auto widget = new MyWidget2(minSizes.at(i)); auto dw = new DockWidgetType(QString::number(i)); dw->setWidget(widget); docks << dw; } for (int i = 0; i < num; ++i) { m->addDockWidget(docks[i], locations[i], nullptr, options[i]); layout->checkSanity(); docks[i]->setFloating(floatings[i]); layout->checkSanity(); } m->show(); // Cleanup qDeleteAll(docks); qDeleteAll(DockRegistry::self()->frames()); } #endif void TestDocks::tst_28NestedWidgets_data() { QTest::addColumn>("docksToCreate"); QTest::addColumn>("docksToHide"); QVector docks = { {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnTop, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible } }; #ifdef KDDOCKWIDGETS_QTWIDGETS QTest::newRow("28") << docks << QVector{11, 0}; #endif docks = { {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnTop, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, }; QVector docksToHide; for (int i = 0; i < docks.size(); ++i) { docksToHide << i; } QTest::newRow("anchor_intersection") << docks << docksToHide; docks = { {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnTop, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, }; #ifdef KDDOCKWIDGETS_QTWIDGETS // 2. Produced valgrind invalid reads while adding QTest::newRow("valgrind") << docks << QVector{}; #endif docks = { {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnTop, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, }; #ifdef KDDOCKWIDGETS_QTWIDGETS QTest::newRow("bug_when_closing") << docks << QVector{}; // Q_ASSERT(!isSquashed()) #endif docks = { {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartVisible }, }; #ifdef KDDOCKWIDGETS_QTWIDGETS QTest::newRow("bug_when_closing2") << docks << QVector{}; // Tests for void KDDockWidgets::Anchor::setPosition(int, KDDockWidgets::Anchor::SetPositionOptions) Negative position -69 #endif docks = { {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, 0, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnTop, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible } }; docksToHide.clear(); for (int i = 0; i < 28; ++i) { if (i != 16 && i != 17 && i != 18 && i != 27) docksToHide << i; } #ifdef KDDOCKWIDGETS_QTWIDGETS QTest::newRow("bug_with_holes") << docks << docksToHide; #endif docks = { {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnTop, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnLeft, 17, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible } }; docksToHide.clear(); QTest::newRow("add_as_placeholder") << docks << docksToHide; docks = { {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden } }; QTest::newRow("add_as_placeholder_simple") << docks << docksToHide; docks = { {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden } }; docksToHide.clear(); QTest::newRow("isSquashed_assert") << docks << docksToHide; docks = { {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnTop, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden } }; docksToHide.clear(); QTest::newRow("negative_pos_warning") << docks << docksToHide; docks = { {Location_OnTop, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible } }; docksToHide.clear(); QTest::newRow("bug") << docks << docksToHide; docks = { {Location_OnTop, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible } }; docksToHide.clear(); QTest::newRow("bug2") << docks << docksToHide; docks = { {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnTop, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible } }; docksToHide.clear(); QTest::newRow("bug3") << docks << docksToHide; } void TestDocks::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 TestDocks::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 TestDocks::tst_startHidden() { // A really simple test for InitialVisibilityOption::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, InitialVisibilityOption::StartHidden); delete dock1; } void TestDocks::tst_startHidden2() { EnsureTopLevelsDeleted e; { auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("dock1", new QPushButton("one"), {}, {}, false); auto dock2 = createDockWidget("dock2", new QPushButton("two"), {}, {}, false); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; m->addDockWidget(dock1, Location_OnTop, nullptr, InitialVisibilityOption::StartHidden); QVERIFY(layout->checkSanity()); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 1); m->addDockWidget(dock2, Location_OnTop); QVERIFY(layout->checkSanity()); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); qDebug() << dock1->isVisible(); dock1->show(); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 0); Testing::waitForResize(dock2); } { auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("dock1", new QPushButton("one"), {}, {}, false); auto dock2 = createDockWidget("dock2", new QPushButton("two"), {}, {}, false); auto dock3 = createDockWidget("dock3", new QPushButton("three"), {}, {}, false); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; m->addDockWidget(dock1, Location_OnLeft, nullptr, InitialVisibilityOption::StartHidden); m->addDockWidget(dock2, Location_OnBottom, nullptr, InitialVisibilityOption::StartHidden); m->addDockWidget(dock3, Location_OnRight, nullptr, InitialVisibilityOption::StartHidden); dock1->show(); QCOMPARE(layout->count(), 3); QCOMPARE(layout->placeholderCount(), 2); dock2->show(); dock3->show(); Testing::waitForResize(dock2); layout->checkSanity(); } } void TestDocks::tst_negativeAnchorPosition() { // Tests that we don't hit: // void KDDockWidgets::Anchor::setPosition(int, KDDockWidgets::Anchor::SetPositionOptions) Negative position EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(1002, 806)); auto w1 = new MyWidget2(QSize(104, 104)); w1->resize(994, 718); auto w2 = new MyWidget2(QSize(133, 343)); w2->resize(392, 362); auto w3 = new MyWidget2(QSize(133, 343)); w3->resize(392, 362); MultiSplitter *layout = m->multiSplitter(); auto d1 = createDockWidget("1", w1); auto d2 = createDockWidget("2", w2); auto d3 = createDockWidget("3", w3); m->addDockWidgetAsTab(d1); m->addDockWidget(d2, Location_OnTop); m->addDockWidget(d3, Location_OnTop); d2->close(); Testing::waitForResize(d3); d2->show(); // Should not result in negative anchor positions (Test will fail due to a qWarning) Testing::waitForResize(d3); layout->checkSanity(); d2->close(); Testing::waitForResize(d3); layout->checkSanity(); // Now resize the Window, after removing middle one const int availableToShrink = layout->size().height() - layout->minimumSize().height(); const QSize newSize = { layout->width(), layout->height() - availableToShrink }; if (layout->layoutMinimumSize().expandedTo(newSize) != newSize) { qDebug() << "Size to set is too small=" << newSize << "; min=" << layout->layoutMinimumSize(); QFAIL(""); } layout->setLayoutSize(newSize); d2->deleteLater(); Testing::waitForDeleted(d2); layout->checkSanity(); } void TestDocks::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, InitialVisibilityOption::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 TestDocks::tst_negativeAnchorPosition3() { // 1. Another case, when floating a dock: EnsureTopLevelsDeleted e; QVector docks = { {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible } }; 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 TestDocks::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, InitialVisibilityOption::StartHidden }, { Location_OnTop, -1, nullptr, InitialVisibilityOption::StartVisible }, { Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, { Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, { Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible } }; 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->dptr()->frame()); dock2->setFloating(true); fw2 = dock2->floatingWindow(); dropArea->addWidget(fw2->dropArea(), Location_OnRight, dock1->dptr()->frame()); layout->checkSanity(); docks.at(0).createdDock->deleteLater(); docks.at(4).createdDock->deleteLater(); Testing::waitForDeleted(docks.at(4).createdDock); } void TestDocks::tst_negativeAnchorPosition5() { EnsureTopLevelsDeleted e; QVector docks = { {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::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(); QVERIFY(layout->checkSanity()); dock0->show(); QVERIFY(layout->checkSanity()); // Cleanup for (auto dock : DockRegistry::self()->dockwidgets()) dock->deleteLater(); QVERIFY(Testing::waitForDeleted(dock0)); } void TestDocks::tst_negativeAnchorPosition6() { // Tests a case when we add a widget to left/right but the layout doesn't have enough height (or vice-versa) EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); m->resize(QSize(100, 100)); m->show(); auto layout = m->multiSplitter(); auto w1 = new MyWidget2(QSize(400,100)); auto w2 = new MyWidget2(QSize(400,100)); auto w3 = new MyWidget2(QSize(400,100)); auto w4 = new MyWidget2(QSize(400,900)); auto d1 = createDockWidget("1", w1); auto d2 = createDockWidget("2", w2); auto d3 = createDockWidget("3", w3); auto d4 = createDockWidget("4", w4); m->addDockWidget(d1, Location_OnBottom); m->addDockWidget(d2, Location_OnBottom); m->addDockWidget(d3, Location_OnBottom); QCOMPARE(layout->count(), 3); QCOMPARE(layout->placeholderCount(), 0); m->addDockWidget(d4, Location_OnRight, d3); layout->checkSanity(); Item *centralItem = m->dropArea()->centralFrame(); layout->rectForDrop(nullptr, Location_OnTop, centralItem); layout->checkSanity(); } void TestDocks::tst_negativeAnchorPosition7() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); m->show(); auto w1 = new MyWidget2(QSize(400,400)); auto w2 = new MyWidget2(QSize(400,400)); auto d1 = new DockWidgetType("1"); d1->setWidget(w1); auto d2 = new DockWidgetType("2"); d2->setWidget(w2); auto w3 = new MyWidget2(QSize(100,100)); auto d3 = new DockWidgetType("3"); d3->setWidget(w3); // Stack 1, 2 m->addDockWidget(d2, Location_OnTop); m->addDockWidget(d1, Location_OnTop); // add a small one to the middle // Stack: 1, 3, 2 m->addDockWidget(d3, Location_OnTop, d2); m->layoutWidget()->checkSanity(); } void TestDocks::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->dptr()->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); } } void TestDocks::tst_addAsPlaceholder() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("dock1", new QPushButton("one"), {}, {}, false); auto dock2 = createDockWidget("dock2", new QPushButton("two"), {}, {}, false); m->addDockWidget(dock1, Location_OnBottom); m->addDockWidget(dock2, Location_OnTop, nullptr, InitialVisibilityOption::StartHidden); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); QVERIFY(!dock2->isVisible()); dock2->show(); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 0); layout->checkSanity(); // Cleanup dock2->deleteLater(); Testing::waitForDeleted(dock2); } void TestDocks::tst_removeItem() { // Tests that MultiSplitterLayout::removeItem() works EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two"), {}, {}, false); auto dock3 = createDockWidget("dock3", new QPushButton("three")); m->addDockWidget(dock1, Location_OnBottom); m->addDockWidget(dock2, Location_OnTop, nullptr, InitialVisibilityOption::StartHidden); Item *item2 = dock2->dptr()->lastPositions().lastItem(); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); // 1. Remove an item that's a placeholder layout->removeItem(item2); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); // 2. Remove an item that has an actual widget Item *item1 = dock1->dptr()->lastPositions().lastItem(); layout->removeItem(item1); QCOMPARE(layout->count(), 0); QCOMPARE(layout->placeholderCount(), 0); // 3. Remove an item that has anchors following one of its other anchors (Tests that anchors stop following) // Stack 1, 2, 3 m->addDockWidget(dock3, Location_OnBottom); m->addDockWidget(dock2, Location_OnBottom); m->addDockWidget(dock1, Location_OnBottom); QCOMPARE(layout->count(), 3); QCOMPARE(layout->placeholderCount(), 0); dock2->close(); auto frame1 = dock1->dptr()->frame(); dock1->close(); QVERIFY(Testing::waitForDeleted(frame1)); QCOMPARE(layout->count(), 3); QCOMPARE(layout->placeholderCount(), 2); // Now remove the items layout->removeItem(dock2->dptr()->lastPositions().lastItem()); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); layout->checkSanity(); layout->removeItem(dock1->dptr()->lastPositions().lastItem()); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); // Add again m->addDockWidget(dock2, Location_OnBottom); m->addDockWidget(dock1, Location_OnBottom); dock2->close(); frame1 = dock1->dptr()->frame(); dock1->close(); QVERIFY(Testing::waitForDeleted(frame1)); // Now remove the items, but first dock1 layout->removeItem(dock1->dptr()->lastPositions().lastItem()); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); layout->checkSanity(); layout->removeItem(dock2->dptr()->lastPositions().lastItem()); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); layout->checkSanity(); // Add again, stacked as 1, 2, 3, then close 2 and 3. m->addDockWidget(dock2, Location_OnTop); m->addDockWidget(dock1, Location_OnTop); auto frame2 = dock2->dptr()->frame(); dock2->close(); Testing::waitForDeleted(frame2); auto frame3 = dock3->dptr()->frame(); dock3->close(); Testing::waitForDeleted(frame3); // The second anchor is now following the 3rd, while the 3rd is following 'bottom' layout->removeItem(dock3->dptr()->lastPositions().lastItem()); // will trigger the 3rd anchor to // be removed QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); layout->checkSanity(); dock1->deleteLater(); dock2->deleteLater(); dock3->deleteLater(); Testing::waitForDeleted(dock3); } void TestDocks::tst_clear() { // Tests MultiSplitterLayout::clear() EnsureTopLevelsDeleted e; QCOMPARE(Frame::dbg_numFrames(), 0); auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new QPushButton("1")); auto dock2 = createDockWidget("2", new QPushButton("2")); auto dock3 = createDockWidget("3", new QPushButton("3")); auto fw3 = dock3->floatingWindow(); m->addDockWidget(dock1, Location_OnLeft); m->addDockWidget(dock2, Location_OnRight); m->addDockWidget(dock3, Location_OnRight); QVERIFY(Testing::waitForDeleted(fw3)); dock3->close(); QCOMPARE(Frame::dbg_numFrames(), 3); auto layout = m->multiSplitter(); layout->clearLayout(); QCOMPARE(layout->count(), 0); QCOMPARE(layout->placeholderCount(), 0); layout->checkSanity(); // Cleanup dock3->deleteLater(); QVERIFY(Testing::waitForDeleted(dock3)); } void TestDocks::tst_samePositionAfterHideRestore() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new QPushButton("1")); auto dock2 = createDockWidget("2", new QPushButton("2")); auto dock3 = createDockWidget("3", new QPushButton("3")); m->addDockWidget(dock1, Location_OnLeft); m->addDockWidget(dock2, Location_OnRight); m->addDockWidget(dock3, Location_OnRight); QRect geo2 = dock2->dptr()->frame()->QWidgetAdapter::geometry(); dock2->setFloating(true); auto fw2 = dock2->floatingWindow(); dock2->setFloating(false); QVERIFY(Testing::waitForDeleted(fw2)); QCOMPARE(geo2, dock2->dptr()->frame()->QWidgetAdapter::geometry()); m->layoutWidget()->checkSanity(); } void TestDocks::tst_startClosed() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; m->addDockWidget(dock1, Location_OnTop); Frame *frame1 = dock1->dptr()->frame(); dock1->close(); Testing::waitForDeleted(frame1); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 1); m->addDockWidget(dock2, Location_OnTop); layout->checkSanity(); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); dock1->show(); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 0); } void TestDocks::tst_dockDockWidgetNested() { EnsureTopLevelsDeleted e; // Test detaching too, and check if the window size is correct // TODO } void TestDocks::tst_dockFloatingWindowNested() { EnsureTopLevelsDeleted e; // TODO } void TestDocks::tst_crash() { // tests some crash I got EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto layout = m->multiSplitter(); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); Item *item1 = layout->itemForFrame(dock1->dptr()->frame()); dock1->addDockWidgetAsTab(dock2); QVERIFY(!dock1->isFloating()); dock1->setFloating(true); QVERIFY(dock1->isFloating()); QVERIFY(!dock1->isInMainWindow()); Item *layoutItem = dock1->dptr()->lastPositions().lastItem(); QVERIFY(layoutItem && DockRegistry::self()->itemIsInMainWindow(layoutItem)); QCOMPARE(layoutItem, item1); QCOMPARE(layout->placeholderCount(), 0); QCOMPARE(layout->count(), 1); // Move from tab to bottom m->addDockWidget(dock2, KDDockWidgets::Location_OnBottom); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); delete dock1->window(); } void TestDocks::tst_refUnrefItem() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("1")); auto dock2 = createDockWidget("dock2", new QPushButton("2")); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); m->addDockWidget(dock2, KDDockWidgets::Location_OnRight); auto dropArea = m->dropArea(); auto layout = dropArea; QPointer frame1 = dock1->dptr()->frame(); QPointer frame2 = dock2->dptr()->frame(); QPointer item1 = layout->itemForFrame(frame1); QPointer item2 = layout->itemForFrame(frame2); QVERIFY(item1.data()); QVERIFY(item2.data()); QCOMPARE(item1->refCount(), 2); // 2 - the item and its frame, which can be persistent QCOMPARE(item2->refCount(), 2); // 1. Delete a dock widget directly. It should delete its frame and also the Item delete dock1; Testing::waitForDeleted(frame1); QVERIFY(!frame1.data()); QVERIFY(!item1.data()); // 2. Delete dock3, but neither the frame or the item is deleted, since there were two tabs to begin with auto dock3 = createDockWidget("dock3", new QPushButton("3")); QCOMPARE(item2->refCount(), 2); dock2->addDockWidgetAsTab(dock3); QCOMPARE(item2->refCount(), 3); delete dock3; QVERIFY(item2.data()); QCOMPARE(frame2->dockWidgets().size(), 1); // 3. Close dock2. frame2 should be deleted, but item2 preserved. QCOMPARE(item2->refCount(), 2); dock2->close(); Testing::waitForDeleted(frame2); QVERIFY(dock2); QVERIFY(item2.data()); QCOMPARE(item2->refCount(), 1); QCOMPARE(dock2->dptr()->lastPositions().lastItem(), item2.data()); delete dock2; QVERIFY(!item2.data()); QCOMPARE(layout->count(), 1); // 4. Move a closed dock widget from one mainwindow to another // It should delete its old placeholder auto dock4 = createDockWidget("dock4", new QPushButton("4")); m->addDockWidget(dock4, KDDockWidgets::Location_OnLeft); QPointer frame4 = dock4->dptr()->frame(); QPointer item4 = layout->itemForFrame(frame4); dock4->close(); Testing::waitForDeleted(frame4); QCOMPARE(item4->refCount(), 1); QVERIFY(item4->isPlaceholder()); layout->checkSanity(); auto m2 = createMainWindow(); m2->addDockWidget(dock4, KDDockWidgets::Location_OnLeft); m2->layoutWidget()->checkSanity(); QVERIFY(!item4.data()); } void TestDocks::tst_placeholderCount() { EnsureTopLevelsDeleted e; // Tests MultiSplitterLayout::count(),visibleCount() and placeholdercount() // 1. MainWindow with just the initial frame. auto m = createMainWindow(); auto dock1 = createDockWidget("1", new QPushButton("1")); auto dock2 = createDockWidget("2", new QPushButton("2")); auto dropArea = m->dropArea(); auto layout = dropArea; QCOMPARE(layout->count(), 1); QCOMPARE(layout->visibleCount(), 1); QCOMPARE(layout->placeholderCount(), 0); // 2. MainWindow with central frame and left widget m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); QCOMPARE(layout->count(), 2); QCOMPARE(layout->visibleCount(), 2); QCOMPARE(layout->placeholderCount(), 0); // 3. Add another dockwidget, this time tabbed in the center. It won't increase count, as it reuses an existing frame. m->addDockWidgetAsTab(dock2); QCOMPARE(layout->count(), 2); QCOMPARE(layout->visibleCount(), 2); QCOMPARE(layout->placeholderCount(), 0); // 4. Float dock1. It should create a placeholder dock1->setFloating(true); auto fw = dock1->floatingWindow(); QCOMPARE(layout->count(), 2); QCOMPARE(layout->visibleCount(), 1); QCOMPARE(layout->placeholderCount(), 1); // 5. Re-dock dock1. It should reuse the placeholder m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); QCOMPARE(layout->count(), 2); QCOMPARE(layout->visibleCount(), 2); QCOMPARE(layout->placeholderCount(), 0); // 6. Again dock1->setFloating(true); fw = dock1->floatingWindow(); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); QCOMPARE(layout->count(), 2); QCOMPARE(layout->visibleCount(), 2); QCOMPARE(layout->placeholderCount(), 0); layout->checkSanity(); Testing::waitForDeleted(fw); } void TestDocks::tst_availableLengthForOrientation() { EnsureTopLevelsDeleted e; // 1. Test a completely empty window, it's available space is its size minus the static separators thickness auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; int availableWidth = layout->availableLengthForOrientation(Qt::Horizontal); int availableHeight = layout->availableLengthForOrientation(Qt::Vertical); QCOMPARE(availableWidth, layout->width()); QCOMPARE(availableHeight, layout->height()); //2. Now do the same, but we have some widget docked auto dock1 = createDockWidget("dock1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnLeft); const int dock1MinWidth = layout->itemForFrame(dock1->dptr()->frame())->minLength(Qt::Horizontal); const int dock1MinHeight = layout->itemForFrame(dock1->dptr()->frame())->minLength(Qt::Vertical); availableWidth = layout->availableLengthForOrientation(Qt::Horizontal); availableHeight = layout->availableLengthForOrientation(Qt::Vertical); QCOMPARE(availableWidth, layout->width() - dock1MinWidth); QCOMPARE(availableHeight, layout->height() - dock1MinHeight); m->layoutWidget()->checkSanity(); } void TestDocks::tst_closeShowWhenNoCentralFrame() { EnsureTopLevelsDeleted e; // Tests a crash I got when hidding and showing and no central frame auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame QPointer dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnLeft); dock1->close(); m->layoutWidget()->checkSanity(); QVERIFY(!dock1->dptr()->frame()); QVERIFY(!Testing::waitForDeleted(dock1)); // It was being deleted due to a bug QVERIFY(dock1); dock1->show(); m->layoutWidget()->checkSanity(); } void TestDocks::tst_setAsCurrentTab() { EnsureTopLevelsDeleted e; // Tests DockWidget::setAsCurrentTab() and DockWidget::isCurrentTab() // 1. a single dock widget is current, by definition auto dock1 = createDockWidget("1", new QPushButton("1")); QVERIFY(dock1->isCurrentTab()); // 2. Tab dock2 to the group, dock2 is current now auto dock2 = createDockWidget("2", new QPushButton("2")); dock1->addDockWidgetAsTab(dock2); QVERIFY(!dock1->isCurrentTab()); QVERIFY(dock2->isCurrentTab()); // 3. Set dock1 as current dock1->setAsCurrentTab(); QVERIFY(dock1->isCurrentTab()); QVERIFY(!dock2->isCurrentTab()); auto fw = dock1->floatingWindow(); QVERIFY(fw); fw->layoutWidget()->checkSanity(); delete dock1; delete dock2; Testing::waitForDeleted(fw); } void TestDocks::tst_placeholderDisappearsOnReadd() { // This tests that addMultiSplitter also updates refcount of placeholders // 1. Detach a widget and dock it on the opposite side. Placeholder // should have been deleted and anchors properly positioned EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame MultiSplitter *layout = m->multiSplitter(); QPointer dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnLeft); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); dock1->setFloating(true); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 1); dock1->dptr()->morphIntoFloatingWindow(); auto fw = dock1->floatingWindow(); layout->addMultiSplitter(fw->dropArea(), Location_OnRight ); QCOMPARE(layout->placeholderCount(), 0); QCOMPARE(layout->count(), 1); layout->checkSanity(); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); // The dock1 should occupy the entire width QCOMPARE(dock1->dptr()->frame()->width(), layout->width()); QVERIFY(Testing::waitForDeleted(fw)); } void TestDocks::tst_placeholdersAreRemovedProperly() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame MultiSplitter *layout = m->multiSplitter(); QPointer dock1 = createDockWidget("1", new QPushButton("1")); QPointer dock2 = createDockWidget("2", new QPushButton("2")); m->addDockWidget(dock1, Location_OnLeft); Item *item = layout->items().constFirst(); m->addDockWidget(dock2, Location_OnRight); QVERIFY(!item->isPlaceholder()); dock1->setFloating(true); QVERIFY(item->isPlaceholder()); QCOMPARE(layout->separators().size(), 0); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); layout->removeItem(item); QCOMPARE(layout->separators().size(), 0); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); // 2. Recreate the placeholder. This time delete the dock widget to see if placeholder is deleted too. m->addDockWidget(dock1, Location_OnLeft); dock1->setFloating(true); auto window1 = Tests::make_qpointer(dock1->window()); delete dock1; QCOMPARE(layout->separators().size(), 0); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); layout->checkSanity(); delete window1; } void TestDocks::tst_floatMaintainsSize() { // Tests that when we make a window float by pressing the float button, it will popup with // the same size it had when docked EnsureTopLevelsDeleted e; auto dw1 = new DockWidgetType("1"); auto dw2 = new DockWidgetType("2"); dw1->addDockWidgetToContainingWindow(dw2, Location_OnRight); const int oldWidth2 = dw2->width(); dw1->show(); dw2->setFloating(true); QTest::qWait(100); QVERIFY(qAbs(dw2->width() - oldWidth2) < 16); // 15px for margins delete dw1->window(); delete dw2->window(); } void TestDocks::tst_preferredInitialSize() { EnsureTopLevelsDeleted e; auto dw1 = new DockWidgetType("1"); auto dw2 = new DockWidgetType("2"); auto m = createMainWindow(QSize(1200, 1200), MainWindowOption_None); m->addDockWidget(dw1, Location_OnTop); m->addDockWidget(dw2, Location_OnBottom, nullptr, QSize(0, 200)); QCOMPARE(dw2->dptr()->frame()->height(), 200); } void TestDocks::tst_crash2_data() { QTest::addColumn("show"); QTest::newRow("true") << true; QTest::newRow("false") << false; } void TestDocks::tst_crash2() { QFETCH(bool, show); { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto layout = m->multiSplitter(); m->setVisible(show); DockWidgetBase::List docks; const int num = 4; for (int i = 0; i < num; ++i) docks << new DockWidgetType(QString::number(i)); QVector locations = {Location_OnLeft, Location_OnRight, Location_OnRight, Location_OnRight}; QVector options = { InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartVisible, InitialVisibilityOption::StartHidden}; QVector floatings = {true, false, false, false}; for (int i = 0; i < num; ++i) { m->addDockWidget(docks[i], locations[i], nullptr, options[i]); layout->checkSanity(); docks[i]->setFloating(floatings[i]); } qDeleteAll(docks); qDeleteAll(DockRegistry::self()->frames()); } { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto layout = m->multiSplitter(); m->show(); const int num = 3; DockWidgetBase::List docks; for (int i = 0; i < num; ++i) docks << new DockWidgetType(QString::number(i)); QVector locations = {Location_OnLeft, Location_OnLeft, Location_OnRight}; QVector options = { InitialVisibilityOption::StartVisible, InitialVisibilityOption::StartVisible, InitialVisibilityOption::StartHidden}; QVector floatings = {true, false, false}; for (int i = 0; i < num; ++i) { m->addDockWidget(docks[i], locations[i], nullptr, options[i]); layout->checkSanity(); if (i == 2) { // Wait for the resizes. This used to make the app crash. QTest::qWait(1000); } docks[i]->setFloating(floatings[i]); } layout->checkSanity(); qDeleteAll(docks); qDeleteAll(DockRegistry::self()->frames()); } } void TestDocks::tst_closeAllDockWidgets() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dropArea = m->dropArea(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("one")); auto dock3 = createDockWidget("dock3", new QPushButton("one")); auto dock4 = createDockWidget("dock4", new QPushButton("one")); auto dock5 = createDockWidget("dock5", new QPushButton("one")); auto dock6 = createDockWidget("dock6", new QPushButton("one")); QPointer fw = dock3->dptr()->morphIntoFloatingWindow(); nestDockWidget(dock4, dropArea, nullptr, KDDockWidgets::Location_OnRight); nestDockWidget(dock5, dropArea, nullptr, KDDockWidgets::Location_OnTop); const int oldFWHeight = fw->height(); nestDockWidget(dock6, fw->dropArea(), nullptr, KDDockWidgets::Location_OnTop); QVERIFY(oldFWHeight <= fw->height()); QCOMPARE(fw->frames().size(), 2); QCOMPARE(dock3->window(), fw.data()); QCOMPARE(dock4->window(), m.get()); QCOMPARE(dock5->window(), m.get()); QCOMPARE(dock6->window(), fw.data()); auto layout = m->multiSplitter(); layout->checkSanity(); DockRegistry::self()->clear(); layout->checkSanity(); Testing::waitForDeleted(fw); QVERIFY(!fw); QCOMPARE(dock1->window(), dock1); QCOMPARE(dock2->window(), dock2); QCOMPARE(dock3->window(), dock3); QCOMPARE(dock4->window(), dock4); QCOMPARE(dock5->window(), dock5); QCOMPARE(dock6->window(), dock6); QVERIFY(!dock1->isVisible()); QVERIFY(!dock2->isVisible()); QVERIFY(!dock3->isVisible()); QVERIFY(!dock4->isVisible()); QVERIFY(!dock5->isVisible()); QVERIFY(!dock6->isVisible()); delete dock1; delete dock2; delete dock3; delete dock4; delete dock5; delete dock6; } void TestDocks::tst_toggleMiddleDockCrash() { // tests some crash I got EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame MultiSplitter *layout = m->multiSplitter(); QPointer dock1 = createDockWidget("1", new QPushButton("1")); QPointer dock2 = createDockWidget("2", new QPushButton("2")); QPointer dock3 = createDockWidget("3", new QPushButton("3")); m->addDockWidget(dock1, Location_OnLeft); m->addDockWidget(dock2, Location_OnRight); m->addDockWidget(dock3, Location_OnRight); QCOMPARE(layout->count(), 3); QCOMPARE(layout->placeholderCount(), 0); auto frame = dock2->dptr()->frame(); dock2->close(); QVERIFY(Testing::waitForDeleted(frame)); QCOMPARE(layout->count(), 3); QCOMPARE(layout->placeholderCount(), 1); QVERIFY(layout->checkSanity()); dock2->show(); layout->checkSanity(); } void TestDocks::tst_stealFrame() { // Tests using addWidget() with dock widgets which are already in a layout EnsureTopLevelsDeleted e; auto m1 = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto m2 = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock3 = createDockWidget("dock3", new QPushButton("three")); auto dock4 = createDockWidget("dock4", new QPushButton("four")); auto dropArea1 = m1->dropArea(); auto dropArea2 = m2->dropArea(); m1->addDockWidget(dock1, Location_OnRight); m1->addDockWidget(dock2, Location_OnRight); m2->addDockWidget(dock3, Location_OnRight); m2->addDockWidget(dock4, Location_OnRight); // 1. MainWindow #1 steals a widget from MainWindow2 and vice-versa m1->addDockWidget(dock3, Location_OnRight); m1->addDockWidget(dock4, Location_OnRight); m2->addDockWidget(dock1, Location_OnRight); QPointer item2 = dropArea1->itemForFrame(dock2->dptr()->frame()); m2->addDockWidget(dock2, Location_OnRight); QVERIFY(!item2.data()); QCOMPARE(dropArea1->count(), 2); QCOMPARE(dropArea2->count(), 2); QCOMPARE(dropArea1->placeholderCount(), 0); QCOMPARE(dropArea2->placeholderCount(), 0); // 2. MainWindow #1 steals a widget from MainWindow2 and vice-versa, but adds as tabs dock1->addDockWidgetAsTab(dock3); QPointer f2 = dock2->dptr()->frame(); dock4->addDockWidgetAsTab(dock2); QVERIFY(Testing::waitForDeleted(f2.data())); QVERIFY(!f2.data()); QCOMPARE(dropArea1->count(), 1); QCOMPARE(dropArea2->count(), 1); QCOMPARE(dropArea1->placeholderCount(), 0); QCOMPARE(dropArea2->placeholderCount(), 0); // 3. Test stealing a tab from the same tab-widget we're in. Nothing happens { SetExpectedWarning sew("Already contains KDDockWidgets::DockWidget"); // Suppress the qFatal this time dock1->addDockWidgetAsTab(dock3); QCOMPARE(dock1->dptr()->frame()->dockWidgetCount(), 2); } // 4. Steal from another tab which resides in another Frame, which resides in the same main window m1->addDockWidget(dock1, Location_OnTop); f2 = dock2->dptr()->frame(); dock1->addDockWidgetAsTab(dock2); QCOMPARE(dock1->dptr()->frame()->dockWidgetCount(), 2); QCOMPARE(dock4->dptr()->frame()->dockWidgetCount(), 1); QCOMPARE(dropArea1->count(), 2); QCOMPARE(dropArea1->placeholderCount(), 0); // 5. And also steal a side-by-side one into the tab QPointer f4 = dock4->dptr()->frame(); dock1->addDockWidgetAsTab(dock4); QVERIFY(Testing::waitForDeleted(f4.data())); QCOMPARE(dropArea1->count(), 1); QCOMPARE(dropArea1->placeholderCount(), 0); // 6. Steal from tab to side-by-side within the same MainWindow m1->addDockWidget(dock1, Location_OnLeft); QCOMPARE(dropArea1->count(), 2); QCOMPARE(dropArea1->placeholderCount(), 0); // 6. side-by-side to side-by-side within same MainWindow m2->addDockWidget(dock1, Location_OnRight); QCOMPARE(dropArea2->count(), 2); QCOMPARE(dropArea2->placeholderCount(), 0); { SetExpectedWarning sew("Invalid parameters KDDockWidgets::DockWidget"); // Suppress the qFatal this time m2->addDockWidget(dock1, Location_OnLeft, dock1); QCOMPARE(dropArea2->count(), 2); // Nothing happened QCOMPARE(dropArea2->placeholderCount(), 0); QVERIFY(dock1->isVisible()); } QVERIFY(dock1->isVisible()); m2->addDockWidget(dock1, Location_OnLeft, nullptr); // Should not warn QVERIFY(dock1->isVisible()); QCOMPARE(dropArea2->count(), 2); // Nothing happened QCOMPARE(dropArea2->placeholderCount(), 0); m2->addDockWidget(dock1, Location_OnLeft, nullptr); QVERIFY(dock1->isVisible()); QCOMPARE(dropArea2->count(), 2); // Nothing happened QCOMPARE(dropArea2->placeholderCount(), 0); dropArea1->checkSanity(); dropArea2->checkSanity(); } void TestDocks::tst_setFloatingWhenWasTabbed() { // Tests DockWidget::isTabbed() and DockWidget::setFloating(false|true) when tabbed (it should redock) // setFloating(false) for side-by-side is tested in another function EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); // 1. Two floating dock widgets. They are floating, not tabbed. QVERIFY(!dock1->isTabbed()); QVERIFY(!dock2->isTabbed()); QVERIFY(dock1->isFloating()); QVERIFY(dock2->isFloating()); // 2. Dock a floating dock into another floating dock. They're not floating anymore, just tabbed. dock1->addDockWidgetAsTab(dock2); QVERIFY(dock1->isTabbed()); QVERIFY(dock2->isTabbed()); QVERIFY(!dock1->isFloating()); QVERIFY(!dock2->isFloating()); // 2.1 Set one of them invisible. // Not much will happen, the tab will be still there, just showing an empty space. // Users should use close() instead. Tabwidgets control visibility, they hide the widget when it's not the current tab. dock2->setVisible(false); QVERIFY(dock2->isTabbed()); QVERIFY(!dock1->isFloating()); QCOMPARE(dock2->dptr()->frame()->dockWidgetCount(), 2); // 3. Set one floating. Now both cease to be tabbed, and both are floating. dock1->setFloating(true); QVERIFY(dock1->isFloating()); QVERIFY(dock2->isFloating()); QVERIFY(!dock1->isTabbed()); QVERIFY(!dock2->isTabbed()); // 4. Dock one floating dock into another, side-by-side. They're neither docking or tabbed now. dock1->addDockWidgetToContainingWindow(dock2, KDDockWidgets::Location_OnLeft); QVERIFY(!dock1->isFloating()); QVERIFY(!dock2->isFloating()); QVERIFY(!dock1->isTabbed()); QVERIFY(!dock2->isTabbed()); // 5. float one of them, now both are floating, not tabbed anymore. dock2->setFloating(true); QVERIFY(dock1->isFloating()); QVERIFY(dock2->isFloating()); QVERIFY(!dock1->isTabbed()); QVERIFY(!dock2->isTabbed()); // 6. With two dock widgets tabbed, detach 1, and reattach it, via DockWidget::setFloating(false) m->addDockWidgetAsTab(dock1); m->addDockWidgetAsTab(dock2); qDebug() << "6."; dock2->setFloating(true); QVERIFY(dock1->isTabbed()); QVERIFY(!dock2->isTabbed()); QVERIFY(!dock1->isFloating()); QVERIFY(dock2->isFloating()); QCOMPARE(dock2->dptr()->lastPositions().lastTabIndex(), 1); QVERIFY(dock2->dptr()->lastPositions().isValid()); dock2->setFloating(false); QVERIFY(dock1->isTabbed()); QVERIFY(dock2->isTabbed()); QVERIFY(!dock1->isFloating()); QVERIFY(!dock2->isFloating()); // 7. Call setFloating(true) on an already docked widget auto dock3 = createDockWidget("dock3", new QPushButton("three")); dock3->setFloating(true); dock3->setFloating(true); // 8. Tab 3 together, detach the middle one, reattach the middle one, it should go to the middle. m->addDockWidgetAsTab(dock3); dock2->setFloating(true); QVERIFY(dock2->isFloating()); dock2->setFloating(false); QVERIFY(!dock2->isFloating()); QVERIFY(dock2->isTabbed()); QCOMPARE(dock2->dptr()->frame()->indexOfDockWidget(dock2), 1); // 10. Float dock1, and dock it to main window as tab. This tests Option_AlwaysShowsTabs. dock1->setFloating(true); dock2->setFloating(true); dock3->setFloating(true); m->addDockWidgetAsTab(dock1); QVERIFY(!dock1->isFloating()); QVERIFY(dock1->isTabbed()); dock1->setFloating(true); dock1->setFloating(false); QCOMPARE(dock1->dptr()->frame()->dockWidgetCount(), 1); // Cleanup m->addDockWidgetAsTab(dock2); m->addDockWidgetAsTab(dock3); m->deleteLater(); auto window = m.release(); Testing::waitForDeleted(window); } void TestDocks::tst_setFloatingWhenSideBySide() { // Tests DockWidget::setFloating(false|true) when side-by-side (it should put it where it was) EnsureTopLevelsDeleted e; { // 1. Create a MainWindow with two docked dock-widgets, then float the first one. 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); QPointer frame1 = dock1->dptr()->frame(); dock1->setFloating(true); QVERIFY(dock1->isFloating()); auto fw = dock1->floatingWindow(); QVERIFY(fw); //2. Put it back, via setFloating(). It should return to its place. dock1->setFloating(false); QVERIFY(!dock1->isFloating()); QVERIFY(!dock1->isTabbed()); Testing::waitForDeleted(fw); } { // 2. Tests a case where restoring a dock widget wouldn't make it use all its available space 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")); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); m->addDockWidget(dock2, KDDockWidgets::Location_OnRight); m->addDockWidget(dock3, KDDockWidgets::Location_OnRight); auto f2 = dock2->dptr()->frame(); Item *item2 = layout->itemForFrame(f2); QVERIFY(item2); dock2->close(); dock3->close(); Testing::waitForDeleted(f2); dock2->show(); Testing::waitForResize(dock2); QCOMPARE(item2->geometry(), dock2->dptr()->frame()->QWidgetAdapter::geometry()); layout->checkSanity(); // Cleanup dock3->deleteLater(); Testing::waitForDeleted(dock3); } } void TestDocks::tst_dockWindowWithTwoSideBySideFramesIntoCenter() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto fw = createFloatingWindow(); auto dock2 = createDockWidget("doc2", Qt::red); nestDockWidget(dock2, fw->dropArea(), nullptr, KDDockWidgets::Location_OnLeft); QCOMPARE(fw->frames().size(), 2); QVERIFY(fw->dropArea()->checkSanity()); auto fw2 = createFloatingWindow(); fw2->move(fw->x() + fw->width() + 100, fw->y()); dragFloatingWindowTo(fw, fw2->geometry().center()); QVERIFY(fw2->dropArea()->checkSanity()); QCOMPARE(fw2->frames().size(), 1); auto f2 = fw2->frames().constFirst(); QCOMPARE(f2->dockWidgetCount(), 3); QVERIFY(Testing::waitForDeleted(fw)); delete fw2; } void TestDocks::tst_tabTitleChanges() { // Tests that the tab's title changes if the dock widget's title changes EnsureTopLevelsDeleted e; auto dw1 = new DockWidgetType(QStringLiteral("1")); auto dw2 = new DockWidgetType(QStringLiteral("2")); dw1->addDockWidgetAsTab(dw2); TabBar *tb = dw1->dptr()->frame()->tabWidget()->tabBar(); QCOMPARE(tb->text(0), QStringLiteral("1")); dw1->setTitle(QStringLiteral("other")); QCOMPARE(tb->text(0), QStringLiteral("other")); delete dw1->window(); } void TestDocks::tst_dockWidgetGetsFocusWhenDocked() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_TitleBarIsFocusable); // We drag dw2 onto dw2 and drop it auto dw1 = new DockWidgetType(QStringLiteral("1")); auto dw2 = new DockWidgetType(QStringLiteral("2")); auto le1 = new FocusableWidget(); auto le2 = new FocusableWidget(); dw1->setWidget(le1); dw2->setWidget(le2); dw2->show(); dw1->show(); QTest::qWait(200); auto fw1 = dw1->floatingWindow(); QPointer fw2 = dw2->floatingWindow(); // Focus dock widget 1 first QVERIFY(!dw1->isFocused()); dw1->window()->activateWindow(); le1->setFocus(Qt::MouseFocusReason); QTest::qWait(200); QVERIFY(dw1->isFocused()); QVERIFY(fw1->isActiveWindow()); dragFloatingWindowTo(fw2, fw1->dropArea(), DropIndicatorOverlayInterface::DropLocation_Left); Testing::waitForEvent(fw1, QEvent::WindowActivate); /// We dropped into floating window 1, it should still be active QVERIFY(fw1->isActiveWindow()); // DockWidget 2 was dropped, it should now be focused QVERIFY(!dw1->isFocused()); QVERIFY(dw2->isFocused()); delete fw1; delete fw2; } void TestDocks::tst_isFocused() { EnsureTopLevelsDeleted e; // 1. Create 2 floating windows auto dock1 = createDockWidget(QStringLiteral("dock1"), new FocusableWidget()); auto dock2 = createDockWidget(QStringLiteral("dock2"), new FocusableWidget()); QTest::qWait(200); // macOS is flaky here, needs dock2 to be shown first before focusing dock1, otherwise dock1 looses again dock1->window()->move(400, 200); // 2. Raise dock1 and focus its line edit dock1->raise(); dock1->widget()->setFocus(Qt::OtherFocusReason); Testing::waitForEvent(dock1->widget(), QEvent::FocusIn); QVERIFY(dock1->isFocused()); QVERIFY(!dock2->isFocused()); // 3. Raise dock2 and focus its line edit dock2->raiseAndActivate(); if (!dock2->window()->windowHandle()->isActive()) Testing::waitForEvent(dock2->window()->windowHandle(), QEvent::WindowActivate); dock2->widget()->setFocus(Qt::OtherFocusReason); Testing::waitForEvent(dock2->widget(), QEvent::FocusIn); QVERIFY(!dock1->isFocused()); QVERIFY(dock2->widget()->hasFocus()); QVERIFY(dock2->isFocused()); // 4. Tab dock1, it's current tab now auto oldFw1 = dock1->window(); dock2->addDockWidgetAsTab(dock1); delete oldFw1; QVERIFY(dock1->isFocused()); QVERIFY(!dock2->isFocused()); // 5. Set dock2 as current tab again dock2->raise(); QVERIFY(!dock1->isFocused()); QVERIFY(dock2->isFocused()); // 6. Create dock3, focus it auto dock3 = createDockWidget(QStringLiteral("dock3"), new FocusableWidget()); auto oldFw3 = dock3->window(); dock3->raise(); dock3->widget()->setFocus(Qt::OtherFocusReason); Testing::waitForEvent(dock2->widget(), QEvent::FocusIn); QVERIFY(!dock1->isFocused()); QVERIFY(!dock2->isFocused()); QVERIFY(dock3->isFocused()); // 4. Add dock3 to the 1st window, nested, focus 2 again dock2->addDockWidgetToContainingWindow(dock3, Location_OnLeft); delete oldFw3; dock2->raise(); dock2->widget()->setFocus(Qt::OtherFocusReason); Testing::waitForEvent(dock2->widget(), QEvent::FocusIn); QVERIFY(!dock1->isFocused()); QVERIFY(dock2->isFocused()); QVERIFY(!dock3->isFocused()); delete dock2->window(); } void TestDocks::tst_setWidget() { EnsureTopLevelsDeleted e; auto dw = new DockWidgetType(QStringLiteral("FOO")); auto button1 = new QPushButton("button1"); auto button2 = new QPushButton("button2"); dw->setWidget(button1); dw->setWidget(button2); delete button1; delete dw; } void TestDocks::tst_floatingLastPosAfterDoubleClose() { EnsureTopLevelsDeleted e; auto d1 = new DockWidgetType(QStringLiteral("a")); QVERIFY(d1->dptr()->lastPositions().lastFloatingGeometry().isNull()); QVERIFY(!d1->isVisible()); d1->close(); QVERIFY(d1->dptr()->lastPositions().lastFloatingGeometry().isNull()); delete d1; } void TestDocks::tst_0_data() { QTest::addColumn("thickness"); QTest::newRow("2") << 2; QTest::newRow("1") << 1; QTest::newRow("0") << 0; } void TestDocks::tst_0() { QFETCH(int, thickness); EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setSeparatorThickness(thickness); auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); m->resize(QSize(502, 500)); m->show(); auto d1 = createDockWidget("1", new QTextEdit()); auto d2 = createDockWidget("2", new QTextEdit()); m->addDockWidget(d1, Location_OnLeft); m->addDockWidget(d2, Location_OnRight); } void TestDocks::tst_honourGeometryOfHiddenWindow() { EnsureTopLevelsDeleted e; auto d1 = new DockWidgetType("1"); d1->setWidget(new QTextEdit()); QVERIFY(!d1->isVisible()); // Clear had a bug where it saved the position of all dock widgets being closed DockRegistry::self()->clear(); const QRect suggestedGeo(150, 150, 250, 250); d1->setGeometry(suggestedGeo); d1->show(); Testing::waitForEvent(d1, QEvent::Show); QCOMPARE(d1->window()->windowHandle()->geometry(), suggestedGeo); delete d1->window(); } void TestDocks::tst_registry() { EnsureTopLevelsDeleted e; auto dr = DockRegistry::self(); QCOMPARE(dr->dockwidgets().size(), 0); auto dw = new DockWidgetType(QStringLiteral("dw1")); auto guest = new QWidgetOrQuick(); dw->setWidget(guest); QCOMPARE(dr->dockWidgetForGuest(nullptr), nullptr); QCOMPARE(dr->dockWidgetForGuest(guest), dw); delete dw; } void TestDocks::tst_dockWindowWithTwoSideBySideFramesIntoRight() { EnsureTopLevelsDeleted e; auto fw = createFloatingWindow(); auto dock2 = createDockWidget("doc2", Qt::red); nestDockWidget(dock2, fw->dropArea(), nullptr, KDDockWidgets::Location_OnTop); // No we stack on top, unlike in previous test QCOMPARE(fw->frames().size(), 2); auto fw2 = createFloatingWindow(); fw2->move(fw->x() + fw->width() + 100, fw->y()); dragFloatingWindowTo(fw, fw2->dropArea(), DropIndicatorOverlayInterface::DropLocation_Right); // Outter right instead of Left QCOMPARE(fw2->frames().size(), 3); QVERIFY(fw2->dropArea()->checkSanity()); fw2->deleteLater(); Testing::waitForDeleted(fw2); } void TestDocks::tst_dockWindowWithTwoSideBySideFramesIntoLeft() { EnsureTopLevelsDeleted e; auto fw = createFloatingWindow(); fw->setObjectName("fw1"); auto dock2 = createDockWidget("doc2", Qt::red); nestDockWidget(dock2, fw->dropArea(), nullptr, KDDockWidgets::Location_OnLeft); QCOMPARE(fw->frames().size(), 2); auto fw2 = createFloatingWindow(); fw2->setObjectName("fw2"); fw2->move(fw->x() + fw->width() + 100, fw->y()); QVERIFY(fw2->dropArea()->checkSanity()); dragFloatingWindowTo(fw, fw2->dropArea(), DropIndicatorOverlayInterface::DropLocation_Left); QCOMPARE(fw2->frames().size(), 3); QVERIFY(fw2->dropArea()->checkSanity()); ///Cleanup fw2->deleteLater(); Testing::waitForDeleted(fw2); } void TestDocks::tst_posAfterLeftDetach() { { EnsureTopLevelsDeleted e; auto fw = createFloatingWindow(); auto dock2 = createDockWidget("doc2", Qt::red); nestDockWidget(dock2, fw->dropArea(), nullptr, KDDockWidgets::Location_OnRight); QVERIFY(fw->dropArea()->checkSanity()); // When dragging the right one there was a bug where it jumped const QPoint globalSrc = dock2->mapToGlobal(QPoint(0, 0)); const int offset = 10; const QPoint globalDest = globalSrc + QPoint(offset, 0); drag(dock2, globalDest); QVERIFY(fw->dropArea()->checkSanity()); const QPoint actualEndPos = dock2->mapToGlobal(QPoint(0, 0)); QVERIFY(actualEndPos.x() - globalSrc.x() < offset + 5); // 5px so we have margin for window system fluctuations. The actual bug was a very big jump like 50px, so a 5 margin is fine to test that the bug doesn't happen delete dock2; fw->deleteLater(); Testing::waitForDeleted(fw); } { EnsureTopLevelsDeleted e; auto fw = createFloatingWindow(); auto dock2 = createDockWidget("doc2", Qt::red); nestDockWidget(dock2, fw->dropArea(), nullptr, KDDockWidgets::Location_OnRight); QVERIFY(fw->dropArea()->checkSanity()); const int originalX = dock2->mapToGlobal(QPoint(0, 0)).x(); dock2->dptr()->frame()->titleBar()->makeWindow(); const int finalX = dock2->mapToGlobal(QPoint(0, 0)).x(); QVERIFY(finalX - originalX < 10); // 10 or some other small number that is less than say 200 delete dock2; fw->deleteLater(); Testing::waitForDeleted(fw); } } void TestDocks::tst_preventClose() { EnsureTopLevelsDeleted e; auto nonClosableWidget = new NonClosableWidget(); auto dock1 = new DockWidgetType("1"); dock1->setWidget(nonClosableWidget); // 1. Test a floating dock widget dock1->resize(200, 200); dock1->show(); QVERIFY(dock1->isVisible()); dock1->close(); QVERIFY(dock1->isVisible()); // 2. Morph it into a FlatingWindow dock1->dptr()->morphIntoFloatingWindow(); dock1->close(); QVERIFY(dock1->isVisible()); dock1->dptr()->frame()->titleBar()->onCloseClicked(); QVERIFY(dock1->isVisible()); auto fw = dock1->floatingWindow(); fw->close(); QVERIFY(dock1->isVisible()); dock1->deleteLater(); QVERIFY(Testing::waitForDeleted(dock1)); } void TestDocks::tst_propagateMinSize() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dropArea = m->dropArea(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto dock3 = createDockWidget("dock3", new QPushButton("three")); nestDockWidget(dock1, dropArea, nullptr, KDDockWidgets::Location_OnRight); nestDockWidget(dock2, dropArea, nullptr, KDDockWidgets::Location_OnRight); nestDockWidget(dock3, dropArea, nullptr, KDDockWidgets::Location_OnRight); // TODO finish this when the 3 dock widgets have proper sizes //QTest::qWait(50000); } void TestDocks::tst_createFloatingWindow() { EnsureTopLevelsDeleted e; auto dock = createDockWidget("doc1", Qt::green); QVERIFY(dock); QVERIFY(dock->isFloating()); QCOMPARE(dock->uniqueName(), QLatin1String("doc1")); // 1.0 objectName() is inherited QPointer window = dock->floatingWindow(); QVERIFY(window); // 1.1 DockWidget creates a FloatingWindow and is reparented QVERIFY(window->dropArea()->checkSanity()); dock->deleteLater(); QVERIFY(Testing::waitForDeleted(dock)); QVERIFY(Testing::waitForDeleted(window)); // 1.2 Floating Window is destroyed when DockWidget is destroyed QVERIFY(!window); } void TestDocks::tst_addAndReadd() { EnsureTopLevelsDeleted e; // 1. This just tests some crash I got. // Make a dock widget float and immediately reattach it auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("1")); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); dock1->setFloating(true); m->layoutWidget()->checkSanity(); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); dock1->dptr()->frame()->titleBar()->makeWindow(); m->layoutWidget()->checkSanity(); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); dock1->dptr()->frame()->titleBar()->makeWindow(); m->layoutWidget()->checkSanity(); auto fw = dock1->floatingWindow(); QVERIFY(fw); auto dropArea = m->dropArea(); dragFloatingWindowTo(fw, dropArea, DropIndicatorOverlayInterface::DropLocation_Right); QVERIFY(dock1->dptr()->frame()->titleBar()->isVisible()); fw->titleBar()->makeWindow(); m->layoutWidget()->checkSanity(); //Cleanup delete dock1; Testing::waitForDeleted(fw); } void TestDocks::tst_addToSmallMainWindow1() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new MyWidget2()); auto dock2 = createDockWidget("dock2", new MyWidget2()); auto dock3 = createDockWidget("dock3", new MyWidget2()); auto dock4 = createDockWidget("dock4", new MyWidget2()); const int mainWindowLength = 400; m->windowHandle()->resize(mainWindowLength, mainWindowLength); QTest::qWait(100); dock1->resize(800, 800); dock2->resize(800, 800); dock3->resize(800, 800); // Add as tabbed: m->addDockWidgetAsTab(dock1); QCOMPARE(m->height(), mainWindowLength); QTest::qWait(100); QVERIFY(dock1->height() <= mainWindowLength); QVERIFY(dock1->width() <= mainWindowLength); //Add in area: m->addDockWidget(dock2, Location_OnLeft); m->addDockWidget(dock3, Location_OnTop, dock2); m->addDockWidget(dock4, Location_OnBottom); auto dropArea = m->dropArea(); QVERIFY(dropArea->checkSanity()); QVERIFY(dock2->width() < mainWindowLength); QVERIFY(dock3->height() < m->height()); QVERIFY(dock4->height() < m->height()); } void TestDocks::tst_addToSmallMainWindow2() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dropArea = m->dropArea(); auto dock1 = createDockWidget("dock1", new MyWidget2(QSize(100, 100))); auto dock2 = createDockWidget("dock2", new MyWidget2(QSize(100, 100))); m->addDockWidgetAsTab(dock1); m->windowHandle()->resize(osWindowMinWidth(), 200); Testing::waitForResize(m.get()); QVERIFY(qAbs(m->width() - osWindowMinWidth()) < 15); // Not very important verification. Anyway, using 15 to account for margins and what not. m->addDockWidget(dock2, KDDockWidgets::Location_OnRight); #ifdef KDDOCKWIDGETS_QTWIDGETS QVERIFY(Testing::waitForResize(m.get())); #else QTest::qWait(100); #endif QVERIFY(dropArea->width() > osWindowMinWidth()); QMargins margins = m->centerWidgetMargins(); QCOMPARE(dropArea->width(), m->width() - margins.left() - margins.right()); QVERIFY(m->dropArea()->checkSanity()); } void TestDocks::tst_addToSmallMainWindow3() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dropArea = m->dropArea(); auto dock1 = createDockWidget("dock1", new MyWidget2()); auto dock2 = createDockWidget("dock2", new MyWidget2()); m->addDockWidgetAsTab(dock1); m->windowHandle()->resize(osWindowMinWidth(), 200); QTest::qWait(200); QVERIFY(qAbs(m->width() - osWindowMinWidth()) < 15); // Not very important verification. Anyway, using 15 to account for margins and what not. auto fw = dock2->dptr()->morphIntoFloatingWindow(); QVERIFY(fw->isVisible()); QVERIFY(dropArea->checkSanity()); dragFloatingWindowTo(fw, dropArea, DropIndicatorOverlayInterface::DropLocation_Right); QVERIFY(m->dropArea()->checkSanity()); delete fw; } void TestDocks::tst_addToSmallMainWindow4() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(100, 100), MainWindowOption_None); QTest::qWait(100); QCOMPARE(m->height(), 100); auto dropArea = m->dropArea(); auto dock1 = createDockWidget("dock1", new MyWidget2(QSize(50, 50))); auto dock2 = createDockWidget("dock2", new MyWidget2(QSize(50, 50))); MultiSplitter *layout = dropArea; m->addDockWidget(dock1, KDDockWidgets::Location_OnBottom); Testing::waitForResize(m.get()); m->addDockWidget(dock2, KDDockWidgets::Location_OnBottom); Testing::waitForResize(m.get()); QVERIFY(m->dropArea()->checkSanity()); const int item2MinHeight = layout->itemForFrame(dock2->dptr()->frame())->minLength(Qt::Vertical); QCOMPARE(dropArea->height(), dock1->dptr()->frame()->height() + item2MinHeight + Item::separatorThickness); } void TestDocks::tst_addToSmallMainWindow5() { EnsureTopLevelsDeleted e; // Test test shouldn't spit any warnings auto m = createMainWindow(QSize(100, 100), MainWindowOption_None); auto dock1 = createDockWidget("dock1", new MyWidget2(QSize(50, 240))); auto dock2 = createDockWidget("dock2", new MyWidget2(QSize(50, 240))); m->addDockWidget(dock1, KDDockWidgets::Location_OnBottom); m->addDockWidget(dock2, KDDockWidgets::Location_OnBottom); QVERIFY(m->dropArea()->checkSanity()); } void TestDocks::tst_fairResizeAfterRemoveWidget() { // 1. Add 3 dock widgets horizontally, remove the middle one, make sure // both left and right widgets get a share of the new available space EnsureTopLevelsDeleted e; DockWidgetBase *dock1 = createDockWidget("dock1", new QPushButton("one")); DockWidgetBase *dock2 = createDockWidget("dock2", new QPushButton("two")); DockWidgetBase *dock3 = createDockWidget("dock3", new QPushButton("three")); dock1->addDockWidgetToContainingWindow(dock2, Location_OnRight); dock1->addDockWidgetToContainingWindow(dock3, Location_OnRight, dock2); auto fw = dock1->floatingWindow(); QPointer frame2 = dock2->dptr()->frame(); const int oldWidth1 = dock1->dptr()->frame()->width(); const int oldWidth2 = dock2->dptr()->frame()->width(); const int oldWidth3 = dock3->dptr()->frame()->width(); MultiSplitter *layout = fw->dropArea(); QCOMPARE(layout->count(), 3); QCOMPARE(layout->visibleCount(), 3); QCOMPARE(layout->placeholderCount(), 0); delete dock2; QVERIFY(Testing::waitForResize(dock1)); QVERIFY(!frame2); QCOMPARE(layout->count(), 2); QCOMPARE(layout->visibleCount(), 2); QCOMPARE(layout->placeholderCount(), 0); const int delta1 = (dock1->dptr()->frame()->width() - oldWidth1); const int delta3 = (dock3->dptr()->frame()->width() - oldWidth3); qDebug() << "old1=" << oldWidth1 << "; old3=" << oldWidth3 << "; to spread=" << oldWidth2 << "; Delta1=" << delta1 << "; Delta3=" << delta3; QVERIFY(delta1 > 0); QVERIFY(delta3 > 0); QVERIFY(qAbs(delta3 - delta1) <= 1); // Both dock1 and dock3 should have increased by the same amount delete dock1->window(); } void TestDocks::tst_invalidJSON_data() { // Be sure that the main windows in the json are called "MyMainWindow1" and the dock widgets // dock-x where x starts at 0 QTest::addColumn("layoutFileName"); QTest::addColumn("numDockWidgets"); QTest::addColumn("expectedWarning"); QTest::addColumn("expectedResult"); QTest::newRow("unsupported-serialization-version") << "unsupported-serialization-version.json" << 10 << "Serialization format is too old" << false; QTest::newRow("invalid") << "invalid.json" << 29 << "" << false; QTest::newRow("overlapping-item") << "overlapping-item.json" << 2 << "Unexpected pos" << true; } void TestDocks::tst_invalidJSON() { QFETCH(QString, layoutFileName); QFETCH(int, numDockWidgets); QFETCH(QString, expectedWarning); QFETCH(bool, expectedResult); const QString absoluteLayoutFileName = QStringLiteral(":/layouts/%1").arg(layoutFileName); EnsureTopLevelsDeleted e; auto m1 = createMainWindow(QSize(800, 500), MainWindowOption_None, "MyMainWindow1"); for (int i = 0; i < numDockWidgets; ++i) { createDockWidget(QStringLiteral("dock-%1").arg(i), new QPushButton("one")); } SetExpectedWarning sew(expectedWarning); LayoutSaver restorer; QCOMPARE(restorer.restoreFromFile(absoluteLayoutFileName), expectedResult); } void TestDocks::tst_invalidPlaceholderPosition_data() { QTest::addColumn("restore1First"); QTest::newRow("restore1First") << true; QTest::newRow("restore2First") << false; } void TestDocks::tst_invalidPlaceholderPosition() { QFETCH(bool, restore1First); // Tests a bug I saw: 3 widgets stacked, close the top one, then the second top one // result: the bottom most one didn't have it's top separator at y=0 EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new QPushButton("1")); auto dock2 = createDockWidget("2", new QPushButton("2")); auto dock3 = createDockWidget("3", new QPushButton("3")); MultiSplitter *layout = m->multiSplitter(); // Stack: 1, 2, 3 vertically m->addDockWidget(dock3, Location_OnTop); m->addDockWidget(dock2, Location_OnTop); m->addDockWidget(dock1, Location_OnTop); auto frame1 = dock1->dptr()->frame(); auto frame2 = dock2->dptr()->frame(); auto frame3 = dock3->dptr()->frame(); QCOMPARE(frame1->QWidgetAdapter::y(), 0); // Close 1 dock1->close(); Testing::waitForResize(frame2); // Check that frame2 moved up to y=1 QCOMPARE(frame2->QWidgetAdapter::y(), 0); // Close 2 dock2->close(); Testing::waitForResize(dock3); QVERIFY(layout->checkSanity()); QCOMPARE(layout->count(), 3); QCOMPARE(layout->placeholderCount(), 2); // Check that frame3 moved up to y=1 QCOMPARE(frame3->QWidgetAdapter::y(), 0); // Now restore: auto toRestore1 = restore1First ? dock1 : dock2; auto toRestore2 = restore1First ? dock2 : dock1; toRestore1->show(); QCOMPARE(layout->placeholderCount(), 1); QVERIFY(dock3->isVisible()); QVERIFY(!dock3->size().isNull()); toRestore2->show(); Testing::waitForResize(frame3); QVERIFY(layout->checkSanity()); QCOMPARE(layout->count(), 3); QCOMPARE(layout->placeholderCount(), 0); layout->checkSanity(); dock1->deleteLater(); dock2->deleteLater(); QVERIFY(Testing::waitForDeleted(dock2)); } void TestDocks::tst_setVisibleFalseWhenSideBySide_data() { QTest::addColumn("useSetVisible"); QTest::newRow("false") << false; // QTest::newRow("true") << true; // We don't support closing dock widgets via setVisible(false). (Yet ? Maybe never). } void TestDocks::tst_setVisibleFalseWhenSideBySide() { QFETCH(bool, useSetVisible); auto setVisible = [useSetVisible] (DockWidgetBase *dw, bool visible) { if (useSetVisible) dw->setVisible(visible); else if (visible) dw->show(); else dw->close(); }; 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); const QRect oldGeo = dock1->geometry(); auto oldParent = dock1->parentWidget(); // 1. Just toggle visibility and check that stuff remained sane QVERIFY(dock1->titleBar()->isVisible()); setVisible(dock1, false); QVERIFY(!dock1->titleBar()); QVERIFY(!dock1->isTabbed()); QVERIFY(dock1->isFloating()); setVisible(dock1, true); QVERIFY(dock1->titleBar()->isVisible()); QVERIFY(!dock1->isTabbed()); QVERIFY(!dock1->isFloating()); QCOMPARE(dock1->geometry(), oldGeo); QCOMPARE(dock1->parentWidget(), oldParent); // 2. Check that the parent frame also is hidden now //auto fw1 = dock1->window(); setVisible(dock1, false); QVERIFY(!dock1->dptr()->frame()); delete dock1; } void TestDocks::tst_restoreSimplest() { EnsureTopLevelsDeleted e; // Tests restoring a very simple layout, composed of just 1 docked widget auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto layout = m->multiSplitter(); auto dock1 = createDockWidget("one", new QTextEdit()); m->addDockWidget(dock1, Location_OnTop); LayoutSaver saver; QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreSimplest.json"))); QTest::qWait(200); QVERIFY(layout->checkSanity()); QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_restoreSimplest.json"))); QVERIFY(layout->checkSanity()); } void TestDocks::tst_restoreNonClosable() { // Tests that restoring state also restores the Option_NotClosable option { // Basic case: EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("one", new QTextEdit(), DockWidgetBase::Option_NotClosable); QCOMPARE(dock1->options(), DockWidgetBase::Option_NotClosable); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); QVERIFY(saver.restoreLayout(saved)); QCOMPARE(dock1->options(), DockWidgetBase::Option_NotClosable); } { // Case from issue #137 auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new QTextEdit()); auto dock2 = createDockWidget("2", new QTextEdit(), DockWidgetBase::Option_NotClosable); auto dock3 = createDockWidget("3", new QTextEdit()); m->addDockWidget(dock1, Location_OnLeft); m->addDockWidget(dock2, Location_OnLeft); m->addDockWidget(dock3, Location_OnLeft); QCOMPARE(dock2->options(), DockWidgetBase::Option_NotClosable); dock2->setFloating(true); QCOMPARE(dock2->options(), DockWidgetBase::Option_NotClosable); TitleBar *tb = dock2->dptr()->frame()->actualTitleBar(); QVERIFY(tb->isVisible()); QVERIFY(!tb->closeButtonEnabled()); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); QVERIFY(saver.restoreLayout(saved)); QCOMPARE(dock2->options(), DockWidgetBase::Option_NotClosable); tb = dock2->dptr()->frame()->actualTitleBar(); QVERIFY(tb->isVisible()); QVERIFY(!tb->closeButtonEnabled()); } } void TestDocks::tst_resizeViaAnchorsAfterPlaceholderCreation() { EnsureTopLevelsDeleted e; // Stack 1, 2, 3, close 2, close 2 { auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); MultiSplitter *layout = m->multiSplitter(); 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); QCOMPARE(layout->separators().size(), 2); dock2->close(); Testing::waitForResize(dock3); QCOMPARE(layout->separators().size(), 1); layout->checkSanity(); // Cleanup: dock2->deleteLater(); Testing::waitForDeleted(dock2); } { 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")); auto dock4 = createDockWidget("dock4", new QPushButton("four")); m->addDockWidget(dock1, Location_OnRight); m->addDockWidget(dock2, Location_OnRight); m->addDockWidget(dock3, Location_OnRight); m->addDockWidget(dock4, Location_OnRight); MultiSplitter *layout = m->multiSplitter(); Item *item1 = layout->itemForFrame(dock1->dptr()->frame()); Item *item2 = layout->itemForFrame(dock2->dptr()->frame()); Item *item3 = layout->itemForFrame(dock3->dptr()->frame()); Item *item4 = layout->itemForFrame(dock4->dptr()->frame()); const auto separators = layout->separators(); QCOMPARE(separators.size(), 3); Separator *anchor1 = separators[0]; int boundToTheRight = layout->rootItem()->maxPosForSeparator(anchor1); int expectedBoundToTheRight = layout->size().width() - 3*Item::separatorThickness - item2->minLength(Qt::Horizontal) - item3->minLength(Qt::Horizontal) - item4->minLength(Qt::Horizontal); QCOMPARE(boundToTheRight, expectedBoundToTheRight); dock3->close(); Testing::waitForResize(dock2); QVERIFY(!item1->isPlaceholder()); QVERIFY(!item2->isPlaceholder()); QVERIFY(item3->isPlaceholder()); QVERIFY(!item4->isPlaceholder()); boundToTheRight = layout->rootItem()->maxPosForSeparator(anchor1); expectedBoundToTheRight = layout->size().width() - 2*Item::separatorThickness - item2->minLength(Qt::Horizontal) - item4->minLength(Qt::Horizontal) ; QCOMPARE(boundToTheRight, expectedBoundToTheRight); dock3->deleteLater(); Testing::waitForDeleted(dock3); } } void TestDocks::tst_rectForDropCrash() { // Tests a crash I got in MultiSplitterLayout::rectForDrop() (asserts being hit) EnsureTopLevelsDeleted e; auto m = createMainWindow(); m->resize(QSize(500, 500)); m->show(); auto layout = m->multiSplitter(); auto w1 = new MyWidget2(QSize(400,400)); auto w2 = new MyWidget2(QSize(400,400)); auto d1 = createDockWidget("1", w1); auto d2 = createDockWidget("2", w2); m->addDockWidget(d1, Location_OnTop); Item *centralItem = m->dropArea()->centralFrame(); { WindowBeingDragged wbd2(d2->floatingWindow()); layout->rectForDrop(&wbd2, Location_OnTop, centralItem); } layout->checkSanity(); } void TestDocks::tst_restoreAfterResize() { // Tests a crash I got when the layout received a resize event *while* restoring EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(500, 500), {}, "tst_restoreAfterResize"); auto dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnLeft); auto layout = m->multiSplitter(); const QSize oldContentsSize = layout->size(); const QSize oldWindowSize = m->size(); LayoutSaver saver; QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreAfterResize.json"))); m->resize(1000, 1000); QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_restoreAfterResize.json"))); QCOMPARE(oldContentsSize, layout->size()); QCOMPARE(oldWindowSize, m->size()); } void TestDocks::tst_restoreWithNonClosableWidget() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(500, 500), {}, "tst_restoreWithNonClosableWidget"); auto dock1 = createDockWidget("1", new NonClosableWidget(), DockWidgetBase::Option_NotClosable); m->addDockWidget(dock1, Location_OnLeft); auto layout = m->multiSplitter(); LayoutSaver saver; QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreWithNonClosableWidget.json"))); QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_restoreWithNonClosableWidget.json"))); QVERIFY(layout->checkSanity()); } void TestDocks::tst_restoreNestedAndTabbed() { // Just a more involved test EnsureTopLevelsDeleted e; QPoint oldFW4Pos; QRect oldGeo; { auto m = createMainWindow(QSize(800, 500), MainWindowOption_None, "tst_restoreNestedAndTabbed"); m->move(500, 500); oldGeo = m->geometry(); auto layout = m->multiSplitter(); auto dock1 = createDockWidget("1", new QTextEdit()); auto dock2 = createDockWidget("2", new QTextEdit()); auto dock3 = createDockWidget("3", new QTextEdit()); auto dock4 = createDockWidget("4", new QTextEdit()); auto dock5 = createDockWidget("5", new QTextEdit()); dock4->addDockWidgetAsTab(dock5); oldFW4Pos = dock4->window()->pos(); m->addDockWidget(dock1, Location_OnLeft); m->addDockWidget(dock2, Location_OnRight); dock2->addDockWidgetAsTab(dock3); dock2->setAsCurrentTab(); QCOMPARE(dock2->dptr()->frame()->currentTabIndex(), 0); QCOMPARE(dock4->dptr()->frame()->currentTabIndex(), 1); LayoutSaver saver; QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreNestedAndTabbed.json"))); QVERIFY(layout->checkSanity()); delete dock4->window(); // Let it be destroyed, we'll restore a new one } auto m = createMainWindow(QSize(800, 500), MainWindowOption_None, "tst_restoreNestedAndTabbed"); auto layout = m->multiSplitter(); auto dock1 = createDockWidget("1", new QTextEdit()); auto dock2 = createDockWidget("2", new QTextEdit()); auto dock3 = createDockWidget("3", new QTextEdit()); auto dock4 = createDockWidget("4", new QTextEdit()); auto dock5 = createDockWidget("5", new QTextEdit()); LayoutSaver saver; QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_restoreNestedAndTabbed.json"))); QVERIFY(layout->checkSanity()); auto fw4 = dock4->floatingWindow(); QVERIFY(fw4); QCOMPARE(dock4->window(), dock5->window()); QCOMPARE(fw4->pos(), oldFW4Pos); QCOMPARE(dock1->window(), m.get()); QCOMPARE(dock2->window(), m.get()); QCOMPARE(dock3->window(), m.get()); QCOMPARE(dock2->dptr()->frame()->currentTabIndex(), 0); QCOMPARE(dock4->dptr()->frame()->currentTabIndex(), 1); QCOMPARE(m->geometry(), oldGeo); } void TestDocks::tst_restoreCrash() { EnsureTopLevelsDeleted e; { // Create a main window, with a left dock, save it to disk. auto m = createMainWindow({}, {}, "tst_restoreCrash"); auto dock1 = createDockWidget("dock1", new QPushButton("one")); m->addDockWidget(dock1, Location_OnLeft); LayoutSaver saver; QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreCrash.json"))); } // Restore auto m = createMainWindow({}, {}, "tst_restoreCrash"); auto layout = m->multiSplitter(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); QVERIFY(dock1->isFloating()); QVERIFY(layout->checkSanity()); LayoutSaver saver; QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_restoreCrash.json"))); QVERIFY(layout->checkSanity()); QVERIFY(!dock1->isFloating()); } void TestDocks::tst_restoreSideBySide() { // Save a layout that has a floating window with nesting EnsureTopLevelsDeleted e; QSize item2MinSize; { EnsureTopLevelsDeleted e1; // MainWindow: auto m = createMainWindow(QSize(500, 500), MainWindowOption_HasCentralFrame, "tst_restoreTwice"); auto dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidgetAsTab(dock1); auto layout = m->multiSplitter(); // FloatingWindow: auto dock2 = createDockWidget("2", new QPushButton("2")); auto dock3 = createDockWidget("3", new QPushButton("3")); dock2->addDockWidgetToContainingWindow(dock3, Location_OnRight); auto fw2 = dock2->floatingWindow(); item2MinSize = fw2->layoutWidget()->itemForFrame(dock2->dptr()->frame())->minSize(); LayoutSaver saver; QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreSideBySide.json"))); QVERIFY(layout->checkSanity()); } { auto m = createMainWindow(QSize(500, 500), MainWindowOption_HasCentralFrame, "tst_restoreTwice"); auto dock1 = createDockWidget("1", new QPushButton("1")); auto dock2 = createDockWidget("2", new QPushButton("2")); auto dock3 = createDockWidget("3", new QPushButton("3")); LayoutSaver restorer; QVERIFY(restorer.restoreFromFile(QStringLiteral("layout_tst_restoreSideBySide.json"))); DockRegistry::self()->checkSanityAll(); QCOMPARE(dock1->window(), m.get()); QCOMPARE(dock2->window(), dock3->window()); } } void TestDocks::tst_restoreWithPlaceholder() { // Float dock1, save and restore, then unfloat and see if dock2 goes back to where it was EnsureTopLevelsDeleted e; { auto m = createMainWindow(QSize(500, 500), {}, "tst_restoreWithPlaceholder"); auto dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnLeft); auto layout = m->multiSplitter(); dock1->setFloating(true); LayoutSaver saver; QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreWithPlaceholder.json"))); dock1->close(); QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_restoreWithPlaceholder.json"))); QVERIFY(layout->checkSanity()); QVERIFY(dock1->isFloating()); QVERIFY(dock1->isVisible()); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 1); dock1->setFloating(false); // Put it back. Should go back because the placeholder was restored. QVERIFY(!dock1->isFloating()); QVERIFY(dock1->isVisible()); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); } // Try again, but on a different main window auto m = createMainWindow(QSize(500, 500), {}, "tst_restoreWithPlaceholder"); auto dock1 = createDockWidget("1", new QPushButton("1")); auto layout = m->multiSplitter(); LayoutSaver saver; QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_restoreWithPlaceholder.json"))); QVERIFY(layout->checkSanity()); QVERIFY(dock1->isFloating()); QVERIFY(dock1->isVisible()); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 1); dock1->setFloating(false); // Put it back. Should go back because the placeholder was restored. QVERIFY(!dock1->isFloating()); QVERIFY(dock1->isVisible()); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); } void TestDocks::tst_restoreWithAffinity() { EnsureTopLevelsDeleted e; auto m1 = createMainWindow(QSize(500, 500)); m1->setAffinities({ "a1" }); auto m2 = createMainWindow(QSize(500, 500)); m2->setAffinities({ "a2" }); auto dock1 = createDockWidget("1", new QPushButton("1"), {}, {}, true, "a1"); m1->addDockWidget(dock1, Location_OnLeft); auto dock2 = createDockWidget("2", new QPushButton("2"), {}, {}, true, "a2"); dock2->setFloating(true); dock2->show(); LayoutSaver saver; saver.setAffinityNames({"a1"}); const QByteArray saved1 = saver.serializeLayout(); QPointer fw2 = dock2->floatingWindow(); saver.restoreLayout(saved1); // Restoring affinity 1 shouldn't close affinity 2 QVERIFY(!fw2.isNull()); QVERIFY(dock2->isVisible()); // Close all and restore again DockRegistry::self()->clear(); saver.restoreLayout(saved1); // dock2 continues closed QVERIFY(!dock2->isVisible()); // dock1 was restored QVERIFY(dock1->isVisible()); QVERIFY(!dock1->isFloating()); QCOMPARE(dock1->window(), m1.get()); delete dock2->window(); } void TestDocks::tst_marginsAfterRestore() { EnsureTopLevelsDeleted e; { EnsureTopLevelsDeleted e1; // MainWindow: auto m = createMainWindow(QSize(500, 500), {}, "tst_marginsAfterRestore"); auto dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnLeft); auto layout = m->multiSplitter(); LayoutSaver saver; QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_marginsAfterRestore.json"))); QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_marginsAfterRestore.json"))); QVERIFY(layout->checkSanity()); dock1->setFloating(true); auto fw = dock1->floatingWindow(); QVERIFY(fw); layout->addWidget(fw->dropArea(), Location_OnRight); layout->checkSanity(); } } void TestDocks::tst_restoreWithNewDockWidgets() { // Tests that if the LayoutSaver doesn't know about some dock widget // when it saves the layout, then it won't close it when restoring layout // it will just be ignored. EnsureTopLevelsDeleted e; LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); QVERIFY(!saved.isEmpty()); auto dock1 = createDockWidget("dock1", new QPushButton("dock1")); dock1->show(); QVERIFY(saver.restoreLayout(saved)); QVERIFY(dock1->isVisible()); delete dock1->window(); } void TestDocks::tst_restoreWithDockFactory() { // Tests that restore the layout with a missing dock widget will recreate the dock widget using a factory EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnLeft); auto layout = m->multiSplitter(); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); QCOMPARE(layout->visibleCount(), 1); LayoutSaver saver; QByteArray saved = saver.serializeLayout(); QVERIFY(!saved.isEmpty()); QPointer f1 = dock1->dptr()->frame(); delete dock1; Testing::waitForDeleted(f1); QVERIFY(!f1); // Directly deleted don't leave placeolders. We could though. QCOMPARE(layout->count(), 0); { // We don't know how to create the dock widget SetExpectedWarning expectedWarning("Couldn't find dock widget"); QVERIFY(saver.restoreLayout(saved)); QCOMPARE(layout->count(), 0); } // Now try with a factory func DockWidgetFactoryFunc func = [] (const QString &) { return createDockWidget("1", new QPushButton("1"), {}, {}, /*show=*/ false); }; KDDockWidgets::Config::self().setDockWidgetFactoryFunc(func); QVERIFY(saver.restoreLayout(saved)); QCOMPARE(layout->count(), 1); QCOMPARE(layout->visibleCount(), 1); layout->checkSanity(); } void TestDocks::tst_restoreWithDockFactory2() { // Teste that the factory function can do id remapping. // For example, if id "foo" is missing, the factory can return a // dock widget with id "bar" if it feels like it auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto dock1 = createDockWidget("dw1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnLeft); dock1->setFloating(true); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); delete dock1; DockWidgetFactoryFunc func = [] (const QString &) { // A factory func which does id remapping return createDockWidget("dw2", new QPushButton("w"), {}, {}, /*show=*/ false); }; KDDockWidgets::Config::self().setDockWidgetFactoryFunc(func); saver.restoreLayout(saved); } void TestDocks::tst_addDockWidgetToMainWindow() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); m->addDockWidget(dock1, Location_OnRight, nullptr); m->addDockWidget(dock2, Location_OnTop, dock1); QVERIFY(m->dropArea()->checkSanity()); QCOMPARE(dock1->window(), m.get()); QCOMPARE(dock2->window(), m.get()); QVERIFY(dock1->dptr()->frame()->QWidgetAdapter::y() > dock2->dptr()->frame()->QWidgetAdapter::y()); QCOMPARE(dock1->dptr()->frame()->QWidgetAdapter::x(), dock2->dptr()->frame()->QWidgetAdapter::x()); } void TestDocks::tst_addDockWidgetToContainingWindow() { { // Test with a floating window EnsureTopLevelsDeleted e; auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto dock3 = createDockWidget("dock3", new QPushButton("three")); dock1->addDockWidgetToContainingWindow(dock2, Location_OnRight); dock1->addDockWidgetToContainingWindow(dock3, Location_OnTop, dock2); QCOMPARE(dock1->window(), dock2->window()); QCOMPARE(dock2->window(), dock3->window()); QVERIFY(dock3->dptr()->frame()->QWidgetAdapter::y() < dock2->dptr()->frame()->QWidgetAdapter::y()); QVERIFY(dock1->dptr()->frame()->QWidgetAdapter::x() < dock2->dptr()->frame()->QWidgetAdapter::x()); QCOMPARE(dock2->dptr()->frame()->QWidgetAdapter::x(), dock3->dptr()->frame()->QWidgetAdapter::x()); } { // Also test with a main window EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); m->addDockWidget(dock1, Location_OnRight, nullptr); dock1->addDockWidgetToContainingWindow(dock2, Location_OnRight); QCOMPARE(dock1->window(), dock2->window()); QCOMPARE(m.get(), dock2->window()); } } void TestDocks::tst_notClosable() { EnsureTopLevelsDeleted e; { auto dock1 = createDockWidget("dock1", new QPushButton("one"), DockWidgetBase::Option_NotClosable); auto dock2 = createDockWidget("dock2", new QPushButton("two")); dock1->addDockWidgetAsTab(dock2); auto fw = dock1->floatingWindow(); QVERIFY(fw); TitleBar *titlebarFW = fw->titleBar(); TitleBar *titleBarFrame = fw->frames().at(0)->titleBar(); QVERIFY(titlebarFW->isCloseButtonVisible()); QVERIFY(!titlebarFW->isCloseButtonEnabled()); QVERIFY(!titleBarFrame->isCloseButtonVisible()); QVERIFY(!titleBarFrame->isCloseButtonEnabled()); dock1->setOptions(DockWidgetBase::Option_None); QVERIFY(titlebarFW->isCloseButtonVisible()); QVERIFY(titlebarFW->isCloseButtonEnabled()); QVERIFY(!titleBarFrame->isCloseButtonVisible()); QVERIFY(!titleBarFrame->isCloseButtonEnabled()); dock1->setOptions(DockWidgetBase::Option_NotClosable); QVERIFY(titlebarFW->isCloseButtonVisible()); QVERIFY(!titlebarFW->isCloseButtonEnabled()); QVERIFY(!titleBarFrame->isCloseButtonVisible()); QVERIFY(!titleBarFrame->isCloseButtonEnabled()); auto window = dock1->window(); window->deleteLater(); Testing::waitForDeleted(window); } { // Now dock dock1 into dock1 instead auto dock1 = createDockWidget("dock1", new QPushButton("one"), DockWidgetBase::Option_NotClosable); auto dock2 = createDockWidget("dock2", new QPushButton("two")); dock2->dptr()->morphIntoFloatingWindow(); dock2->addDockWidgetAsTab(dock1); auto fw = dock1->floatingWindow(); QVERIFY(fw); TitleBar *titlebarFW = fw->titleBar(); TitleBar *titleBarFrame = fw->frames().at(0)->titleBar(); QVERIFY(titlebarFW->isCloseButtonVisible()); QVERIFY(!titleBarFrame->isCloseButtonVisible()); QVERIFY(!titleBarFrame->isCloseButtonEnabled()); auto window = dock2->window(); window->deleteLater(); Testing::waitForDeleted(window); } } void TestDocks::tst_dragOverTitleBar() { // Tests that dragging over the title bar is returning DropLocation_None EnsureTopLevelsDeleted e; auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("Two")); DropArea *da = dock1->floatingWindow()->dropArea(); FloatingWindow *fw1 = dock1->floatingWindow(); FloatingWindow *fw2 = dock2->floatingWindow(); WindowBeingDragged wbd(fw2, fw2); const QPoint titleBarPoint = fw1->titleBar()->mapToGlobal(QPoint(5, 5)); auto loc = da->hover(&wbd, titleBarPoint); QCOMPARE(loc, DropIndicatorOverlayInterface::DropLocation_None); delete fw1; delete fw2; } void TestDocks::tst_setFloatingGeometry() { EnsureTopLevelsDeleted e; auto dock1 = createDockWidget("dock1", new MyWidget("one")); QVERIFY(dock1->isVisible()); const QRect requestedGeo = QRect(70, 70, 400, 400); dock1->setFloatingGeometry(requestedGeo); QCOMPARE(dock1->window()->geometry(), requestedGeo); dock1->close(); QVERIFY(!dock1->isVisible()); const QRect requestedGeo2 = QRect(80, 80, 400, 400); dock1->setFloatingGeometry(requestedGeo2); dock1->show(); QCOMPARE(dock1->window()->geometry(), requestedGeo2); } void TestDocks::tst_setFloatingAfterDraggedFromTabToSideBySide() { EnsureTopLevelsDeleted e; { auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto dropArea = m->dropArea(); auto layout = dropArea; m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); dock1->addDockWidgetAsTab(dock2); // Move from tab to bottom m->addDockWidget(dock2, KDDockWidgets::Location_OnBottom); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 0); dock2->setFloating(true); dock2->setFloating(false); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 0); QVERIFY(!dock2->isFloating()); } { // 2. Try again, but now detach from tab before putting it on the bottom. What was happening was that MultiSplitterLayout::addWidget() // called with a MultiSplitter as widget wasn't setting the layout items for the dock widgets auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto dropArea = m->dropArea(); auto layout = dropArea; m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); dock1->addDockWidgetAsTab(dock2); Item *oldItem2 = dock2->dptr()->lastPositions().lastItem(); QCOMPARE(oldItem2, layout->itemForFrame(dock2->dptr()->frame())); // Detach tab dock1->dptr()->frame()->detachTab(dock2); QVERIFY(layout->checkSanity()); auto fw2 = dock2->floatingWindow(); QVERIFY(fw2); QCOMPARE(dock2->dptr()->lastPositions().lastItem(), oldItem2); Item *item2 = fw2->dropArea()->itemForFrame(dock2->dptr()->frame()); QVERIFY(item2); QCOMPARE(item2->hostWidget()->asQObject(), fw2->dropArea()); QVERIFY(!layout->itemForFrame(dock2->dptr()->frame())); // Move from tab to bottom layout->addWidget(fw2->dropArea(), KDDockWidgets::Location_OnRight, nullptr); QVERIFY(layout->checkSanity()); QVERIFY(dock2->dptr()->lastPositions().lastItem()); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 0); dock2->setFloating(true); QVERIFY(layout->checkSanity()); dock2->setFloating(false); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 0); QVERIFY(!dock2->isFloating()); QVERIFY(layout->checkSanity()); Testing::waitForDeleted(fw2); } } void TestDocks::tst_setFloatingAFrameWithTabs() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dropArea = m->dropArea(); auto layout = dropArea; auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); dock1->addDockWidgetAsTab(dock2); // Make it float dock1->dptr()->frame()->titleBar()->onFloatClicked(); auto fw = dock1->floatingWindow(); QVERIFY(fw); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); auto frame1 = dock1->dptr()->frame(); QVERIFY(frame1->layoutItem()); // Attach it again dock1->dptr()->frame()->titleBar()->onFloatClicked(); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 0); QCOMPARE(dock1->window(), m.get()); Testing::waitForDeleted(fw); } void TestDocks::tst_tabBarWithHiddenTitleBar_data() { QTest::addColumn("hiddenTitleBar"); QTest::addColumn("tabsAlwaysVisible"); QTest::newRow("false-false") << false << false; QTest::newRow("true-false") << true << false; QTest::newRow("false-true") << false << true; QTest::newRow("true-true") << true << true; } void TestDocks::tst_tabBarWithHiddenTitleBar() { EnsureTopLevelsDeleted e; QFETCH(bool, hiddenTitleBar); QFETCH(bool, tabsAlwaysVisible); const auto originalFlags = KDDockWidgets::Config::self().flags(); auto newFlags = originalFlags; if (hiddenTitleBar) newFlags = newFlags | KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible; if (tabsAlwaysVisible) newFlags = newFlags | KDDockWidgets::Config::Flag_AlwaysShowTabs; KDDockWidgets::Config::self().setFlags(newFlags); auto m = createMainWindow(); auto d1 = createDockWidget("1", new QTextEdit()); auto d2 = createDockWidget("2", new QTextEdit()); m->addDockWidget(d1, Location_OnTop); if (tabsAlwaysVisible) { if (hiddenTitleBar) QVERIFY(!d1->dptr()->frame()->titleBar()->isVisible()); else QVERIFY(d1->dptr()->frame()->titleBar()->isVisible()); } else { QVERIFY(d1->dptr()->frame()->titleBar()->isVisible()); } d1->addDockWidgetAsTab(d2); QVERIFY(d2->dptr()->frame()->titleBar()->isVisible() ^ hiddenTitleBar); d2->close(); m->layoutWidget()->checkSanity(); delete d2; if (tabsAlwaysVisible) { if (hiddenTitleBar) QVERIFY(!d1->dptr()->frame()->titleBar()->isVisible()); else QVERIFY(d1->dptr()->frame()->titleBar()->isVisible()); } else { QVERIFY(d1->dptr()->frame()->titleBar()->isVisible()); } } void TestDocks::tst_toggleDockWidgetWithHiddenTitleBar() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible | KDDockWidgets::Config::Flag_AlwaysShowTabs); auto m = createMainWindow(); auto d1 = createDockWidget("1", new QTextEdit()); m->addDockWidget(d1, Location_OnTop); QVERIFY(!d1->dptr()->frame()->titleBar()->isVisible()); d1->toggleAction()->setChecked(false); auto f1 = d1->dptr()->frame(); Testing::waitForDeleted(f1); d1->toggleAction()->setChecked(true); QVERIFY(d1->dptr()->frame()); QVERIFY(!d1->dptr()->frame()->titleBar()->isVisible()); } void TestDocks::tst_availableSizeWithPlaceholders() { // Tests MultiSplitterLayout::available() with and without placeholders. The result should be the same. EnsureTopLevelsDeleted e; QVector docks1 = { {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartHidden }, }; QVector docks2 = { {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnBottom, -1, nullptr, InitialVisibilityOption::StartVisible }, }; QVector empty; auto m1 = createMainWindow(docks1); auto m2 = createMainWindow(docks2); auto m3 = createMainWindow(empty); auto layout1 = m1->multiSplitter(); auto layout2 = m2->multiSplitter(); auto layout3 = m3->multiSplitter(); auto f20 = docks2.at(0).createdDock->dptr()->frame(); docks2.at(0).createdDock->close(); docks2.at(1).createdDock->close(); docks2.at(2).createdDock->close(); QVERIFY(Testing::waitForDeleted(f20)); QCOMPARE(layout1->size(), layout2->size()); QCOMPARE(layout1->size(), layout3->size()); QCOMPARE(layout1->availableSize(), layout2->availableSize()); QCOMPARE(layout1->availableSize(), layout3->availableSize()); // Now show 1 widget in m1 and m3 docks1.at(0).createdDock->show(); m3->addDockWidget(docks2.at(0).createdDock, Location_OnBottom); // just steal from m2 QCOMPARE(layout1->size(), layout3->size()); Frame *f10 = docks1.at(0).createdDock->dptr()->frame(); Item *item10 = layout1->itemForFrame(f10); Item *item30 = layout3->itemForFrame(docks2.at(0).createdDock->dptr()->frame()); QCOMPARE(item10->geometry(), item30->geometry()); QCOMPARE(item10->guestWidget()->minSize(), item10->guestWidget()->minSize()); QCOMPARE(item10->minSize(), item30->minSize()); QCOMPARE(layout1->availableSize(), layout3->availableSize()); layout1->checkSanity(); layout2->checkSanity(); layout3->checkSanity(); // Cleanup docks1.at(0).createdDock->deleteLater(); docks1.at(1).createdDock->deleteLater(); docks1.at(2).createdDock->deleteLater(); docks2.at(0).createdDock->deleteLater(); docks2.at(1).createdDock->deleteLater(); docks2.at(2).createdDock->deleteLater(); QVERIFY(Testing::waitForDeleted(docks2.at(2).createdDock)); } void TestDocks::tst_anchorFollowingItselfAssert() { // 1. Tests that we don't assert in Anchor::setFollowee() // ASSERT: "this != m_followee" in file ../src/multisplitter/Anchor.cpp EnsureTopLevelsDeleted e; QVector docks = { {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnTop, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnLeft, -1, nullptr, InitialVisibilityOption::StartVisible }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartHidden }, {Location_OnRight, -1, nullptr, InitialVisibilityOption::StartVisible } }; 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->dptr()->frame()); dock2->setFloating(true); fw2 = dock2->floatingWindow(); dropArea->addWidget(fw2->dropArea(), Location_OnRight, dock1->dptr()->frame()); docks.at(0).createdDock->deleteLater(); docks.at(4).createdDock->deleteLater(); Testing::waitForDeleted(docks.at(4).createdDock); } void TestDocks::tst_positionWhenShown() { // Tests that when showing a dockwidget it shows in the same position as before EnsureTopLevelsDeleted e; auto window = createMainWindow(); auto dock1 = new DockWidgetType("1"); dock1->show(); dock1->window()->move(100, 100); QCOMPARE(dock1->window()->windowHandle()->frameGeometry().topLeft(), QPoint(100, 100)); dock1->close(); dock1->show(); QCOMPARE(dock1->window()->windowHandle()->frameGeometry().topLeft(), QPoint(100, 100)); // Cleanup window->layoutWidget()->checkSanity(); dock1->deleteLater(); QVERIFY(Testing::waitForDeleted(dock1)); } void TestDocks::tst_moreTitleBarCornerCases() { { EnsureTopLevelsDeleted e; auto dock1 = createDockWidget("dock1", new QPushButton("foo1")); auto dock2 = createDockWidget("dock2", new QPushButton("foo2")); dock1->show(); dock2->show(); auto fw2 = dock2->window(); dock1->addDockWidgetToContainingWindow(dock2, Location_OnLeft); QVERIFY(dock1->dptr()->frame()->titleBar()->isVisible()); QVERIFY(dock2->dptr()->frame()->titleBar()->isVisible()); QVERIFY(dock1->dptr()->frame()->titleBar() != dock2->dptr()->frame()->titleBar()); auto fw = dock1->floatingWindow(); QVERIFY(fw->titleBar()->isVisible()); QVERIFY(fw->titleBar() != dock1->dptr()->frame()->titleBar()); QVERIFY(fw->titleBar() != dock2->dptr()->frame()->titleBar()); delete fw; delete fw2; } { EnsureTopLevelsDeleted e; auto dock1 = createDockWidget("dock1", new QPushButton("foo1")); auto dock2 = createDockWidget("dock2", new QPushButton("foo2")); dock1->show(); dock2->show(); auto fw1 = dock1->floatingWindow(); auto fw2 = dock2->floatingWindow(); fw1->dropArea()->drop(fw2, Location_OnRight, nullptr); QVERIFY(fw1->titleBar()->isVisible()); QVERIFY(dock1->dptr()->frame()->titleBar()->isVisible()); QVERIFY(dock2->dptr()->frame()->titleBar()->isVisible()); QVERIFY(dock1->dptr()->frame()->titleBar() != dock2->dptr()->frame()->titleBar()); QVERIFY(fw1->titleBar() != dock1->dptr()->frame()->titleBar()); QVERIFY(fw1->titleBar() != dock2->dptr()->frame()->titleBar()); delete fw1; delete fw2; } { // Tests that restoring a single floating dock widget doesn't make it show two title-bars // As reproduced myself... and fixed in this commit EnsureTopLevelsDeleted e; auto dock1 = createDockWidget("dock1", new QPushButton("foo1")); dock1->show(); auto fw1 = dock1->floatingWindow(); QVERIFY(!dock1->dptr()->frame()->titleBar()->isVisible()); QVERIFY(fw1->titleBar()->isVisible()); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); saver.restoreLayout(saved); delete fw1; // the old window fw1 = dock1->floatingWindow(); QVERIFY(dock1->isVisible()); QVERIFY(!dock1->dptr()->frame()->titleBar()->isVisible()); QVERIFY(fw1->titleBar()->isVisible()); delete dock1->window(); } } void TestDocks::tst_isInMainWindow() { EnsureTopLevelsDeleted e; auto dw = new DockWidgetType(QStringLiteral("FOO")); dw->show(); auto fw = dw->window(); QVERIFY(!dw->isInMainWindow()); auto m1 = createMainWindow(QSize(2560, 809), MainWindowOption_None, "MainWindow1"); m1->addDockWidget(dw, KDDockWidgets::Location_OnLeft); QVERIFY(dw->isInMainWindow()); delete fw; // Also test after creating the MainWindow, as the FloatingWindow will get parented to it auto dw2 = new DockWidgetType(QStringLiteral("2")); dw2->show(); QVERIFY(!dw2->isInMainWindow()); delete dw2->window(); } void TestDocks::tst_sizeConstraintWarning() { // Tests that we don't get the warning: MultiSplitterLayout::checkSanity: Widget has height= 122 but minimum is 144 KDDockWidgets::Item // Code autogenerated by the fuzzer: EnsureTopLevelsDeleted e; SetExpectedWarning sew("Dock widget already exists in the layout"); auto window = createMainWindow(); QList listDockWidget; { auto dock = new DockWidgetType("foo-0"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-1"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-2"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-3"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-4"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-5"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-6"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-7"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-8"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-9"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-10"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-11"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-12"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-13"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-14"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-15"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-16"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-17"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } { auto dock = new DockWidgetType("foo-18"); dock->setWidget(new QTextEdit()); listDockWidget.append(dock); } auto dropArea = window->dropArea(); window->addDockWidget(listDockWidget.at(0), static_cast(2)); dropArea->checkSanity(); window->addDockWidget(listDockWidget.at(1), static_cast(1)); dropArea->checkSanity(); listDockWidget.at(2 - 1)->addDockWidgetAsTab(listDockWidget.at(2)); dropArea->checkSanity(); window->addDockWidget(listDockWidget.at(3-1), static_cast(2), listDockWidget.at(3), static_cast(1)); dropArea->checkSanity(); listDockWidget.at(4 - 1)->addDockWidgetAsTab(listDockWidget.at(4)); dropArea->checkSanity(); window->addDockWidget(listDockWidget.at(5), static_cast(1)); dropArea->checkSanity(); window->addDockWidget(listDockWidget.at(6), static_cast(1)); dropArea->checkSanity(); window->addDockWidget(listDockWidget.at(7), static_cast(4)); dropArea->checkSanity(); window->addDockWidget(listDockWidget.at(8-1), static_cast(1), listDockWidget.at(8), static_cast(1)); dropArea->checkSanity(); window->addDockWidget(listDockWidget.at(9), static_cast(2)); dropArea->checkSanity(); window->addDockWidget(listDockWidget.at(10-1), static_cast(2), listDockWidget.at(10), static_cast(1)); dropArea->checkSanity(); listDockWidget.at(11 - 1)->addDockWidgetAsTab(listDockWidget.at(11)); dropArea->checkSanity(); listDockWidget.at(12 - 1)->addDockWidgetAsTab(listDockWidget.at(12)); dropArea->checkSanity(); window->addDockWidget(listDockWidget.at(13), static_cast(4)); dropArea->checkSanity(); window->addDockWidget(listDockWidget.at(14), static_cast(2)); dropArea->checkSanity(); window->addDockWidget(listDockWidget.at(15), static_cast(3)); dropArea->checkSanity(); window->addDockWidget(listDockWidget.at(16), static_cast(4)); dropArea->checkSanity(); listDockWidget.at(17 - 1)->addDockWidgetAsTab(listDockWidget.at(17)); dropArea->checkSanity(); listDockWidget.at(18 - 1)->addDockWidgetAsTab(listDockWidget.at(18)); dropArea->checkSanity(); auto docks = DockRegistry::self()->dockwidgets(); auto lastDock = docks.last(); for (auto dock: docks) dock->deleteLater(); Testing::waitForDeleted(lastDock); } void TestDocks::tst_stuckSeparator() { const QString absoluteLayoutFileName = QStringLiteral(":/layouts/stuck-separator.json"); EnsureTopLevelsDeleted e; auto m1 = createMainWindow(QSize(2560, 809), MainWindowOption_None, "MainWindow1"); const int numDockWidgets = 26; DockWidgetBase *dw25 = nullptr; for (int i = 0; i < numDockWidgets; ++i) { auto createdDw = createDockWidget(QStringLiteral("dock-%1").arg(i)); if (i == 25) dw25 = createdDw; } LayoutSaver restorer; QVERIFY(restorer.restoreFromFile(absoluteLayoutFileName)); Frame *frame25 = dw25->dptr()->frame(); ItemBoxContainer *root = m1->multiSplitter()->rootItem(); Item *item25 = root->itemForWidget(frame25); ItemBoxContainer *container25 = item25->parentBoxContainer(); Separator::List separators = container25->separators(); QCOMPARE(separators.size(), 1); Separator *separator25 = separators.constFirst(); const int sepMin = container25->minPosForSeparator_global(separator25); const int sepMax = container25->maxPosForSeparator_global(separator25); QVERIFY(sepMin <= sepMax); for (auto dw : DockRegistry::self()->dockwidgets()) { delete dw; } } void TestDocks::tst_titlebar_getter() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralFrame); m->resize(QSize(500, 500)); m->show(); auto w1 = new MyWidget2(QSize(400, 400)); auto d1 = createDockWidget("1", w1); m->addDockWidget(d1, Location_OnTop); QVERIFY(d1->titleBar()->isVisible()); d1->setFloating(true); QVERIFY(d1->floatingWindow()); QVERIFY(d1->floatingWindow()->isVisible()); QVERIFY(d1->titleBar()->isVisible()); } void TestDocks::tst_dockNotFillingSpace() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(1000, 1000)); m->resize(QSize(500, 500)); m->show(); auto d1 = createDockWidget("1", new QTextEdit()); auto d2 = createDockWidget("2", new QTextEdit()); auto d3 = createDockWidget("3", new QTextEdit()); m->addDockWidget(d1, Location_OnTop); m->addDockWidget(d2, Location_OnBottom); m->addDockWidget(d3, Location_OnBottom); Frame *frame2 = d2->dptr()->frame(); d1->close(); d2->close(); Testing::waitForDeleted(frame2); auto layout = m->multiSplitter(); QVERIFY(layout->checkSanity()); delete d1; delete d2; } void TestDocks::tst_lastFloatingPositionIsRestored() { EnsureTopLevelsDeleted e; auto m1 = createMainWindow(); auto dock1 = createDockWidget("dock1"); dock1->show(); QPoint targetPos = QPoint(340, 340); dock1->window()->move(targetPos); auto oldFw = dock1->window(); LayoutSaver saver; QByteArray saved = saver.serializeLayout(); dock1->window()->move(0, 0); dock1->close(); delete oldFw; saver.restoreLayout(saved); QCOMPARE(dock1->window()->pos(), targetPos); QCOMPARE(dock1->window()->frameGeometry().topLeft(), targetPos); // Adjsut to what we got without the frame targetPos = dock1->window()->geometry().topLeft(); // Now dock it: m1->addDockWidget(dock1, Location_OnTop); QCOMPARE(dock1->dptr()->lastPositions().lastFloatingGeometry().topLeft(), targetPos); dock1->setFloating(true); QCOMPARE(dock1->window()->geometry().topLeft(), targetPos); saver.restoreLayout(saved); QCOMPARE(dock1->window()->geometry().topLeft(), targetPos); // Dock again and save: m1->addDockWidget(dock1, Location_OnTop); saved = saver.serializeLayout(); dock1->setFloating(true); dock1->window()->move(0, 0); saver.restoreLayout(saved); QVERIFY(!dock1->isFloating()); dock1->setFloating(true); QCOMPARE(dock1->window()->geometry().topLeft(), targetPos); delete dock1->window(); } void TestDocks::tst_titleBarFocusedWhenTabsChange() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_TitleBarIsFocusable); auto le1 = new FocusableWidget(); le1->setObjectName("le1"); auto dock1 = createDockWidget(QStringLiteral("dock1"), le1); auto dock2 = createDockWidget(QStringLiteral("dock2"), new FocusableWidget()); auto dock3 = createDockWidget(QStringLiteral("dock3"), new FocusableWidget()); auto oldFw1 = dock1->window(); auto oldFw2 = dock2->window(); auto oldFw3 = dock3->window(); auto m1 = createMainWindow(QSize(2560, 809), MainWindowOption_None, "MainWindow1"); m1->addDockWidget(dock1, Location_OnLeft); m1->addDockWidget(dock2, Location_OnRight); delete oldFw1; delete oldFw2; dock2->addDockWidgetAsTab(dock3); delete oldFw3; TitleBar *titleBar1 = dock1->titleBar(); dock1->widget()->setFocus(Qt::MouseFocusReason); QVERIFY(dock1->isFocused() || Testing::waitForEvent(dock1->widget(), QEvent::FocusIn)); QVERIFY(titleBar1->isFocused()); auto frame2 = dock2->dptr()->frame(); TabWidget *tb2 = frame2->tabWidget(); QCOMPARE(tb2->currentIndex(), 1); // Was the last to be added auto tabBar2 = tb2->tabBar(); const QRect rect0 = tabBar2->rectForTab(0); const QPoint globalPos = tabBar2->asWidget()->mapToGlobal(rect0.topLeft()) + QPoint(5, 5); Tests::clickOn(globalPos, tabBar2->asWidget()); QVERIFY(!titleBar1->isFocused()); QVERIFY(dock2->titleBar()->isFocused()); // Test that clicking on a tab that is already current will also set focus dock1->setFocus(Qt::MouseFocusReason); QVERIFY(dock1->titleBar()->isFocused()); QVERIFY(!dock2->titleBar()->isFocused()); #ifdef KDDOCKWIDGETS_QTWIDGETS // TODO: Not yet ready for QtQuick. The TitleBar.qml is clicked, but we check the C++ TitleBar for focus Tests::clickOn(globalPos, tabBar2->asWidget()); QVERIFY(!dock1->titleBar()->isFocused()); QVERIFY(dock2->titleBar()->isFocused()); #endif } #ifdef KDDOCKWIDGETS_QTWIDGETS void TestDocks::tst_tabsNotClickable() { // Well, not a great unit-test, as it's only repro when it's Windows sending the native event // Can't repro with fabricated events. Uncomment the WAIT and test different configs manually EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_Default | KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible); auto dock1 = createDockWidget("dock1", new QWidget()); auto dock2 = createDockWidget("dock2", new QWidget()); dock1->addDockWidgetAsTab(dock2); auto frame = qobject_cast(dock1->dptr()->frame()); QCOMPARE(frame->currentIndex(), 1); QTest::qWait(500); // wait for window to get proper geometry const QPoint clickPoint = frame->tabBar()->mapToGlobal(frame->tabBar()->tabRect(0).center()); QCursor::setPos(clickPoint); // Just for visual debug when needed pressOn(clickPoint, frame->tabBar()); releaseOn(clickPoint, frame->tabBar()); // WAIT // Uncomment for MANUAL test. Also test by adding Flag_AlwaysShowTabs QCOMPARE(frame->currentIndex(), 0); delete frame->window(); } void TestDocks::tst_mainWindowAlwaysHasCentralWidget() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); QWidget *central = m->centralWidget(); auto dropArea = m->dropArea(); QVERIFY(dropArea); QPointer centralFrame = static_cast(dropArea->centralFrame()->guestAsQObject()); QVERIFY(central); QVERIFY(dropArea); QCOMPARE(dropArea->count(), 1); QVERIFY(centralFrame); QCOMPARE(centralFrame->dockWidgetCount(), 0); // Add a tab auto dock = createDockWidget("doc1", Qt::green); m->addDockWidgetAsTab(dock); QCOMPARE(dropArea->count(), 1); QCOMPARE(centralFrame->dockWidgetCount(), 1); qDebug() << "Central widget width=" << central->size() << "; mainwindow=" << m->size(); // Detach tab QPoint globalPressPos = dragPointForWidget(centralFrame.data(), 0); QTabBar *tabBar = static_cast(centralFrame.data())->tabBar(); QVERIFY(tabBar); qDebug() << "Detaching tab from dropArea->size=" << dropArea->QWidget::size() << "; dropArea=" << dropArea; drag(tabBar, globalPressPos, m->geometry().bottomRight() + QPoint(30, 30)); QVERIFY(centralFrame); QCOMPARE(dropArea->count(), 1); QCOMPARE(centralFrame->dockWidgetCount(), 0); QVERIFY(dropArea->checkSanity()); delete dock->window(); } void TestDocks::tst_dockableMainWindows() { EnsureTopLevelsDeleted e; auto m1 = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("foo")); m1->addDockWidget(dock1, Location_OnTop); auto m2 = new KDDockWidgets::MainWindow("mainwindow-dockable"); auto m2Container = createDockWidget("mainwindow-dw", m2); auto menubar = m2->menuBar(); menubar->addMenu("File"); menubar->addMenu("View"); menubar->addMenu("Help"); m2Container->show(); auto dock21 = createDockWidget("dock21", new QPushButton("foo")); auto dock22 = createDockWidget("dock22", new QPushButton("foo")); m2->addDockWidget(dock21, Location_OnLeft); m2->addDockWidget(dock22, Location_OnRight); auto fw = m2Container->floatingWindow(); TitleBar *fwTitleBar = fw->titleBar(); QVERIFY(fw->hasSingleFrame()); QVERIFY(fw->hasSingleDockWidget()); // Check that the inner-inner dock widgets have a visible title-bar QVERIFY(dock21->titleBar()->isVisible()); QVERIFY(dock22->titleBar()->isVisible()); QVERIFY(dock21->titleBar() != fwTitleBar); QVERIFY(dock22->titleBar() != fwTitleBar); const QPoint startPoint = fwTitleBar->mapToGlobal(QPoint(5, 5)); const QPoint destination = startPoint + QPoint(20, 20); // Check that we don't get the "Refusing to itself" warning. not actually dropping anywhere drag(fwTitleBar, startPoint, destination); // The FloatingWindow has a single DockWidget, so it shows the title bar, while the Frame doesn't QVERIFY(fwTitleBar->isVisible()); QVERIFY(!m2Container->dptr()->frame()->titleBar()->isVisible()); fw->dropArea()->addDockWidget(dock1, Location::Location_OnLeft, nullptr); // Now the FloatingWindow has two dock widgets, so our main window dock widget also shows the title bar QVERIFY(fwTitleBar->isVisible()); QVERIFY(m2Container->dptr()->frame()->titleBar()->isVisible()); // Put it how it was, FloatingWindow is single dock again auto frame1 = dock1->dptr()->frame(); dock1->close(); Testing::waitForDeleted(frame1); QVERIFY(fwTitleBar->isVisible()); QVERIFY(!m2Container->dptr()->frame()->titleBar()->isVisible()); // Repeat, but instead of closing dock1, we float it fw->dropArea()->addDockWidget(dock1, Location::Location_OnLeft, nullptr); QVERIFY(fwTitleBar->isVisible()); QVERIFY(m2Container->dptr()->frame()->titleBar()->isVisible()); frame1 = dock1->dptr()->frame(); frame1->titleBar()->onFloatClicked(); QVERIFY(fwTitleBar->isVisible()); QVERIFY(!m2Container->dptr()->frame()->titleBar()->isVisible()); fw->dropArea()->addDockWidget(dock1, Location::Location_OnLeft, nullptr); } // No need to port to QtQuick void TestDocks::tst_floatingWindowDeleted() { // Tests a case where the empty floating dock widget wouldn't be deleted // Doesn't repro QTBUG-83030 unfortunately, as we already have an event loop running // but let's leave this here nontheless class MyMainWindow : public KDDockWidgets::MainWindow { public: MyMainWindow() : KDDockWidgets::MainWindow("tst_floatingWindowDeleted", MainWindowOption_None) { auto dock1 = new KDDockWidgets::DockWidget(QStringLiteral("DockWidget #1")); auto myWidget = new QWidget(); dock1->setWidget(myWidget); dock1->resize(600, 600); dock1->show(); auto dock2 = new KDDockWidgets::DockWidget(QStringLiteral("DockWidget #2")); myWidget = new QWidget(); dock2->setWidget(myWidget); dock2->resize(600, 600); dock2->show(); dock1->addDockWidgetAsTab(dock2); } }; MyMainWindow m; } void TestDocks::tst_addToSmallMainWindow6() { EnsureTopLevelsDeleted e; // Test test shouldn't spit any warnings QWidget container; auto lay = new QVBoxLayout(&container); MainWindow m("MyMainWindow_tst_addToSmallMainWindow8", MainWindowOption_None); lay->addWidget(&m); container.resize(100, 100); Testing::waitForResize(&container); container.show(); Testing::waitForResize(&m); auto dock1 = createDockWidget("dock1", new MyWidget2(QSize(50, 240))); auto dock2 = createDockWidget("dock2", new MyWidget2(QSize(50, 240))); m.addDockWidget(dock1, KDDockWidgets::Location_OnBottom); m.addDockWidget(dock2, KDDockWidgets::Location_OnBottom); Testing::waitForResize(&m); QVERIFY(m.dropArea()->checkSanity()); } void TestDocks::tst_closeRemovesFromSideBar() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None); auto dw1 = new DockWidgetType(QStringLiteral("1")); auto fw1 = dw1->window(); m1->addDockWidget(dw1, Location_OnBottom); m1->moveToSideBar(dw1); QVERIFY(!dw1->isOverlayed()); QVERIFY(!dw1->isVisible()); QVERIFY(dw1->isInSideBar()); SideBar *sb = m1->sideBarForDockWidget(dw1); QVERIFY(sb); // Overlay it: sb->toggleOverlay(dw1); QVERIFY(dw1->isOverlayed()); QVERIFY(dw1->isVisible()); QCOMPARE(dw1->sideBarLocation(), sb->location()); QVERIFY(dw1->isInMainWindow()); QVERIFY(!dw1->isFloating()); // Close it while it's overlayed: dw1->close(); QVERIFY(!dw1->isInMainWindow()); QVERIFY(!dw1->isOverlayed()); QVERIFY(!dw1->isVisible()); QCOMPARE(dw1->sideBarLocation(), SideBarLocation::None); delete fw1; } void TestDocks::tst_restoreSideBar() { SideBarLocation loc; QByteArray serialized; // serialization after having 1 sidebar visible QByteArray beforeSideBarSerialized; // serialization without any sidebar visible { LayoutSaver saver; EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1"); auto dw1 = new DockWidgetType(QStringLiteral("1")); auto fw1 = dw1->window(); m1->addDockWidget(dw1, Location_OnBottom); beforeSideBarSerialized = saver.serializeLayout(); QVERIFY(!m1->anySideBarIsVisible()); m1->moveToSideBar(dw1); QVERIFY(m1->anySideBarIsVisible()); QVERIFY(!dw1->isOverlayed()); QVERIFY(!dw1->isVisible()); loc = dw1->sideBarLocation(); QVERIFY(loc != SideBarLocation::None); serialized = saver.serializeLayout(); delete fw1; } EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1"); auto dw1 = new DockWidgetType(QStringLiteral("1")); auto fw1 = dw1->window(); m1->addDockWidget(dw1, Location_OnBottom); QVERIFY(!m1->anySideBarIsVisible()); QVERIFY(!dw1->isOverlayed()); QVERIFY(dw1->isVisible()); QVERIFY(!dw1->isFloating()); QVERIFY(dw1->isInMainWindow()); LayoutSaver restorer; restorer.restoreLayout(serialized); QVERIFY(!dw1->isOverlayed()); QVERIFY(!dw1->isVisible()); QVERIFY(!dw1->isInMainWindow()); QVERIFY(m1->anySideBarIsVisible()); QCOMPARE(loc, dw1->sideBarLocation()); restorer.restoreLayout(beforeSideBarSerialized); QVERIFY(!dw1->isOverlayed()); QVERIFY(dw1->isVisible()); QVERIFY(!dw1->isFloating()); QVERIFY(dw1->isInMainWindow()); QVERIFY(!m1->anySideBarIsVisible()); delete fw1; } void TestDocks::tst_toggleActionOnSideBar() { // When a dock widget is in the sidebar and we use DockWidget::toggleAction() then it should // toggle visibility without removing it from the sidebar EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1"); auto dw1 = new DockWidgetType("1"); m1->addDockWidget(dw1, Location_OnBottom); dw1->moveToSideBar(); QVERIFY(!dw1->isVisible()); QVERIFY(!dw1->isOverlayed()); QVERIFY(dw1->isInSideBar()); QVERIFY(!dw1->isInMainWindow()); QAction *action = dw1->toggleAction(); action->trigger(); QVERIFY(dw1->isVisible()); QVERIFY(dw1->isOverlayed()); QVERIFY(dw1->isInMainWindow()); QVERIFY(dw1->isInSideBar()); action->trigger(); QVERIFY(!dw1->isOverlayed()); QVERIFY(!dw1->isInMainWindow()); QVERIFY(dw1->isInSideBar()); QVERIFY(!dw1->isInMainWindow()); } void TestDocks::tst_deleteOnCloseWhenOnSideBar() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); QPointer dock1 = createDockWidget("dock1", new MyWidget2(QSize(400, 400)), DockWidgetBase::Option_DeleteOnClose); m->addDockWidget(dock1, Location_OnLeft); dock1->moveToSideBar(); QVERIFY(dock1); QVERIFY(dock1->isInSideBar()); QTest::qWait(500); QVERIFY(dock1); } void TestDocks::tst_sidebarOverlayGetsHiddenOnClick() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); { // Case #1 click on another dockwidget should hide the overlay auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1"); auto dw1 = new DockWidgetType(QStringLiteral("1")); auto dw2 = new DockWidgetType(QStringLiteral("2")); m1->addDockWidget(dw1, Location_OnBottom); m1->addDockWidget(dw2, Location_OnBottom); m1->moveToSideBar(dw1); m1->overlayOnSideBar(dw1); QVERIFY(dw1->isOverlayed()); Tests::clickOn(dw2->mapToGlobal(dw2->rect().bottomLeft() + QPoint(5, -5)), dw2); QVERIFY(!dw1->isOverlayed()); auto widget2 = new MyWidget("foo"); dw2->setWidget(widget2); m1->overlayOnSideBar(dw1); QVERIFY(dw1->isOverlayed()); Tests::clickOn(widget2->mapToGlobal(widget2->rect().bottomLeft() + QPoint(5, -5)), widget2); QVERIFY(!dw1->isOverlayed()); delete dw1; } { // Case #1 click on empty main window space, should hide the overlay auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1"); auto dw1 = new DockWidgetType(QStringLiteral("1")); m1->addDockWidget(dw1, Location_OnBottom); m1->moveToSideBar(dw1); m1->overlayOnSideBar(dw1); QVERIFY(dw1->isOverlayed()); const QPoint localPt(100, 250); Tests::clickOn(m1->mapToGlobal(m1->rect().topLeft() + localPt), m1->childAt(localPt)); QVERIFY(!dw1->isOverlayed()); delete dw1; } } void TestDocks::tst_floatRemovesFromSideBar() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1"); auto dw1 = new DockWidgetType(QStringLiteral("1")); m1->addDockWidget(dw1, Location_OnBottom); m1->moveToSideBar(dw1); m1->overlayOnSideBar(dw1); QVERIFY(dw1->isOverlayed()); QVERIFY(!dw1->isFloating()); QVERIFY(dw1->isInMainWindow()); dw1->setFloating(true); QVERIFY(!dw1->isOverlayed()); QVERIFY(dw1->isFloating()); QVERIFY(!dw1->isInMainWindow()); QCOMPARE(dw1->sideBarLocation(), SideBarLocation::None); // Also test a crash I got m1->addDockWidget(dw1, Location_OnBottom); auto tb = dw1->titleBar(); QVERIFY(tb->isVisible()); tb->onFloatClicked(); } void TestDocks::tst_overlayedGeometryIsSaved() { // Tests that after resizing an overlayed widget, and then hide+show, its size is preserved EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1"); auto dw1 = new DockWidgetType(QStringLiteral("1")); m1->addDockWidget(dw1, Location_OnBottom); m1->moveToSideBar(dw1, SideBarLocation::North); m1->overlayOnSideBar(dw1); Frame *frame = dw1->dptr()->frame(); QVERIFY(frame->isOverlayed()); QCOMPARE(dw1->sideBarLocation(), SideBarLocation::North); QVERIFY(frame->height() > 0); const int newHeight = frame->height() + 300; frame->setHeight(newHeight); m1->toggleOverlayOnSideBar(dw1); m1->toggleOverlayOnSideBar(dw1); frame = dw1->dptr()->frame(); QCOMPARE(frame->height(), newHeight); } void TestDocks::tst_overlayCrash() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1"); auto dw1 = new DockWidgetType(QStringLiteral("1")); m1->addDockWidget(dw1, Location_OnBottom); auto dw2 = new DockWidgetType(QStringLiteral("2")); m1->addDockWidget(dw2, Location_OnBottom); m1->moveToSideBar(dw1); m1->toggleOverlayOnSideBar(dw1); dw1->close(); auto tb = dw2->titleBar(); QVERIFY(tb->isVisible()); pressOn(tb->mapToGlobal(QPoint(5, 5)), tb); } void TestDocks::tst_embeddedMainWindow() { EnsureTopLevelsDeleted e; // Tests a MainWindow which isn't a top-level window, but is embedded in another window EmbeddedWindow *window = createEmbeddedMainWindow(QSize(800, 800)); auto dock1 = createDockWidget("1", new QPushButton("1")); window->mainWindow->addDockWidget(dock1, Location_OnTop); dock1->setFloating(true); auto dropArea = window->mainWindow->dropArea(); auto fw = dock1->floatingWindow(); dragFloatingWindowTo(fw, dropArea, DropIndicatorOverlayInterface::DropLocation_Left); auto layout = dropArea; QVERIFY(Testing::waitForDeleted(fw)); QCOMPARE(layout->count(), 2); // 2, as it has the central frame QCOMPARE(layout->visibleCount(), 2); layout->checkSanity(); delete window; } void TestDocks::tst_restoreEmbeddedMainWindow() { EnsureTopLevelsDeleted e; // Tests a MainWindow which isn't a top-level window, but is embedded in another window EmbeddedWindow *window = createEmbeddedMainWindow(QSize(800, 800)); auto dock1 = createDockWidget("1", new QPushButton("1")); window->mainWindow->addDockWidget(dock1, Location_OnTop); const QPoint originalPos(250, 250); const QSize originalSize = window->size(); window->move(originalPos); LayoutSaver saver; QByteArray saved = saver.serializeLayout(); QVERIFY(!saved.isEmpty()); window->resize(555, 555); const QPoint newPos(500, 500); window->move(newPos); QVERIFY(saver.restoreLayout(saved)); QCOMPARE(window->pos(), originalPos); QCOMPARE(window->size(), originalSize); window->mainWindow->layoutWidget()->checkSanity(); delete window; } void TestDocks::tst_negativeAnchorPositionWhenEmbedded_data() { QTest::addColumn("embedded"); QTest::newRow("false") << false; QTest::newRow("true") << true; } void TestDocks::tst_negativeAnchorPositionWhenEmbedded() { QFETCH(bool, embedded); EnsureTopLevelsDeleted e; MainWindowBase *m = nullptr; if (embedded) { auto em = createEmbeddedMainWindow(QSize(500, 500)); m = em->mainWindow; } else { m = createMainWindow(QSize(500, 500), MainWindowOption_HasCentralFrame).release(); m->resize(QSize(500, 500)); m->show(); } auto layout = m->multiSplitter(); auto w1 = new MyWidget2(QSize(400,400)); auto w2 = new MyWidget2(QSize(400,400)); auto d1 = createDockWidget("1", w1); auto d2 = createDockWidget("2", w2); auto d3 = createDockWidget("3", w2); m->addDockWidget(d1, Location_OnLeft); m->addDockWidget(d2, Location_OnLeft); m->addDockWidget(d3, Location_OnLeft); layout->checkSanity(); delete m->window(); } void TestDocks::tst_restoreResizesLayout() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(500, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnLeft); LayoutSaver saver; QVERIFY(saver.saveToFile("layout_tst_restoreResizesLayout.json")); // Now resize the window, and then restore. The layout should have the new size auto layout = m->multiSplitter(); m->resize(1050, 1050); QCOMPARE(m->size(), QSize(1050, 1050)); LayoutSaver restorer(RestoreOption_RelativeToMainWindow); QVERIFY(restorer.restoreFromFile("layout_tst_restoreResizesLayout.json")); QVERIFY(layout->checkSanity()); QCOMPARE(m->dropArea()->QWidgetAdapter::size(), layout->size()); QVERIFY(layout->checkSanity()); } void TestDocks::tst_restoreNonRelativeFloatingWindowGeometry() { // Tests that disabling RelativeFloatingWindowGeometry works EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(500, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new QPushButton("1")); dock1->show(); LayoutSaver saver(RestoreOption_RelativeToMainWindow); saver.dptr()->m_restoreOptions.setFlag(InternalRestoreOption::RelativeFloatingWindowGeometry, false); const QByteArray saved = saver.serializeLayout(); const QSize floatingWindowSize = dock1->window()->size(); m->resize(m->width() * 2, m->height()); saver.restoreLayout(saved); QCOMPARE(dock1->window()->size(), floatingWindowSize); } void TestDocks::tst_maximumSizePolicy() { EnsureTopLevelsDeleted e; auto widget = new MyWidget2(); const int maxHeight = 250; widget->setMinimumSize(QSize(200, 200)); widget->setSizeHint(QSize(250, maxHeight)); widget->setSizePolicy({QSizePolicy::Preferred, QSizePolicy::Maximum}); auto dock1 = createDockWidget("dock1", widget); dock1->show(); dock1->window()->resize(QSize(500, 500)); auto oldFw = Tests::make_qpointer(dock1->window()); dock1->close(); dock1->show(); auto oldFw2 = dock1->window(); const int tollerance = 50; QVERIFY(dock1->window()->height() <= maxHeight + tollerance); // +tollerance as the floating window is a bit bigger, due to margins etc. QVERIFY(dock1->height() <= maxHeight); auto m1 = createMainWindow(); auto dock2 = createDockWidget("dock2", new QPushButton("foo")); m1->addDockWidget(dock2, Location_OnTop); m1->resize(2000, 3000); // Make the floating window big, and see if the suggested highlight is still small dock1->window()->resize(QSize(dock1->width(), 800)); { WindowBeingDragged wbd1(dock1->floatingWindow()); const QRect highlightRect = m1->multiSplitter()->rectForDrop(&wbd1, Location_OnBottom, nullptr); QVERIFY(highlightRect.height() <= maxHeight + tollerance); } // Now drop it, and check too m1->addDockWidget(dock1, Location_OnBottom); QVERIFY(dock1->height() <= maxHeight); delete oldFw.data(); delete oldFw2; } void TestDocks::tst_minSizeChanges() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(600, 600), MainWindowOption_None); m->show(); auto w1 = new MyWidget2(QSize(400,400)); auto w2 = new MyWidget2(QSize(400,400)); auto d1 = new DockWidgetType("1"); d1->setWidget(w1); auto d2 = new DockWidgetType("2"); d2->setWidget(w2); m->addDockWidget(d1, Location_OnTop); m->addDockWidget(d2, Location_OnTop, nullptr, InitialVisibilityOption::StartHidden); auto layout = m->multiSplitter(); // 1. d2 is a placeholder, let's change its min size before showing it w2->setMinimumSize(QSize(800, 800)); d2->show(); Item *item2 = layout->itemForFrame(d2->dptr()->frame()); QVERIFY(layout->checkSanity()); Testing::waitForResize(m.get()); QVERIFY(item2->width() >= 800); QVERIFY(item2->height() >= 800); QVERIFY(m->height() >= 1200); // 2. d1 is visible, let's change its min size w1->setMinimumSize(QSize(800, 800)); Testing::waitForResize(m.get()); layout->checkSanity(); QVERIFY(m->height() >= 1600); // add a small one to the middle auto w3 = new MyWidget2(QSize(100,100)); auto d3 = new DockWidgetType("3"); d3->setWidget(w3); m->addDockWidget(d3, Location_OnTop, d1); } void TestDocks::tst_maxSizePropagates() { // Tests that the DockWidget gets the min and max size of its guest widget EnsureTopLevelsDeleted e; auto dock1 = new DockWidgetType("dock1"); auto w = new MyWidget2(QSize(200, 200)); w->setMinimumSize(120, 120); w->setMaximumSize(500, 500); dock1->setWidget(w); dock1->show(); QCOMPARE(Widget_qwidget::widgetMinSize(dock1), Widget_qwidget::widgetMinSize(w)); QCOMPARE(dock1->maximumSize(), w->maximumSize()); w->setMinimumSize(121, 121); w->setMaximumSize(501, 501); Testing::waitForEvent(w, QEvent::LayoutRequest); QCOMPARE(Widget_qwidget::widgetMinSize(dock1), Widget_qwidget::widgetMinSize(w)); QCOMPARE(dock1->maximumSize(), w->maximumSize()); // Now let's see if our Frame also has proper size-constraints Frame *frame = dock1->dptr()->frame(); QCOMPARE(frame->maximumSize().expandedTo(w->maximumSize()), frame->maximumSize()); delete dock1->window(); } void TestDocks::tst_maxSizedFloatingWindow() { // Tests that FloatingWindows get a proper max-size, if its dock widget has one EnsureTopLevelsDeleted e; auto dock1 = new DockWidgetType("dock1"); auto dock2 = new DockWidgetType("dock2"); auto w = new MyWidget("foo"); w->setMinimumSize(120, 100); w->setMaximumSize(300, 300); dock1->setWidget(w); dock1->show(); dock2->show(); auto window1 = dock1->window(); auto window2 = dock2->window(); Testing::waitForEvent(window1, QEvent::LayoutRequest); QVERIFY(window1->maximumSize().width() < 500); QVERIFY(window1->maximumSize().height() < 500); QVERIFY(window2->maximumSize().width() > 500); QVERIFY(window2->maximumSize().height() > 500); auto hasMax = [window1] { const QSize max = window1->maximumSize(); return max.width() < 500 && max.height() < 500; }; // Adding side-by-side, we don't honour max size (yet) dock1->addDockWidgetToContainingWindow(dock2, Location_OnBottom); Testing::waitForEvent(window1, QEvent::LayoutRequest); QVERIFY(window1->maximumSize().width() > 500); QVERIFY(window1->maximumSize().height() > 500); // Close dw2, we have a single dock widget again, we honour max-size dock2->close(); Testing::waitForEvent(window1, QEvent::LayoutRequest); QVERIFY(hasMax()); dock1->addDockWidgetAsTab(dock2); Testing::waitForEvent(window1, QEvent::LayoutRequest); QVERIFY(!hasMax()); dock2->close(); Testing::waitForEvent(window1, QEvent::LayoutRequest); QVERIFY(hasMax()); } void TestDocks::tst_maxSizeHonouredWhenAnotherDropped() { // dock1 is docked, and has small max-height. // When dropping dock2, which is small too, dock2 should occupy all the height except dock1's max-height // i.e. dock2 should expand and eat all available space EnsureTopLevelsDeleted e; auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None); auto dock1 = new DockWidgetType("dock1"); auto w = new MyWidget2(QSize(400,400)); w->setMinimumSize(120, 100); w->setMaximumSize(300, 150); dock1->setWidget(w); m1->addDockWidget(dock1, Location_OnLeft); auto dock2 = new DockWidgetType("dock2"); m1->addDockWidget(dock2, Location_OnBottom); auto root = m1->multiSplitter()->rootItem(); Separator *separator = root->separators().constFirst(); const int min1 = root->minPosForSeparator_global(separator); const int max2 = root->maxPosForSeparator_global(separator); QVERIFY(separator->position() >= min1); QVERIFY(separator->position() <= max2); const int item1MaxHeight = dock1->dptr()->frame()->maxSizeHint().height(); QVERIFY(dock1->dptr()->frame()->height() <= item1MaxHeight); root->dumpLayout(); QCOMPARE(dock2->dptr()->frame()->height(), root->height() - item1MaxHeight - Item::separatorThickness); } void TestDocks::tst_addToHiddenMainWindow() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralFrame, {}, false); auto w1 = new MyWidget2(QSize(400,400)); auto w2 = new MyWidget2(QSize(400,400)); auto d1 = createDockWidget("1", w1); auto d2 = createDockWidget("2", w2); m->addDockWidget(d1, Location_OnTop); m->addDockWidget(d2, Location_OnTop); QVERIFY(!m->isVisible()); d1->setFloating(true); d2->setFloating(false); m->layoutWidget()->checkSanity(); } void TestDocks::tst_maxSizePropagates2() { EnsureTopLevelsDeleted e; auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None); auto dock1 = new DockWidgetType("dock1"); auto w = new MyWidget2(QSize(200, 200)); w->setMinimumSize(120, 120); w->setMaximumSize(300, 500); dock1->setWidget(w); dock1->show(); auto dock2 = new DockWidgetType("dock2"); auto dock3 = new DockWidgetType("dock3"); auto dock4 = new DockWidgetType("dock4"); m1->addDockWidget(dock2, Location_OnLeft); m1->addDockWidget(dock3, Location_OnRight); m1->addDockWidget(dock4, Location_OnBottom, dock3); m1->addDockWidget(dock1, Location_OnLeft, dock4); Frame *frame1 = dock1->dptr()->frame(); Layouting::ItemBoxContainer *root = m1->multiSplitter()->rootItem(); Item *item1 = root->itemForWidget(frame1); auto vertSep1 = root->separators().constFirst(); const int min1 = root->minPosForSeparator_global(vertSep1); ItemBoxContainer *container1 = item1->parentBoxContainer(); auto innerVertSep1 = container1->separators().constFirst(); const int minInnerSep = container1->minPosForSeparator_global(innerVertSep1); const int maxInnerSep = container1->maxPosForSeparator_global(innerVertSep1); root->requestSeparatorMove(vertSep1, -(vertSep1->position() - min1)); QVERIFY(frame1->width() <= frame1->maxSizeHint().width()); container1->requestSeparatorMove(innerVertSep1, -(innerVertSep1->position() - minInnerSep)); QVERIFY(frame1->width() <= frame1->maxSizeHint().width()); container1->requestSeparatorMove(innerVertSep1, maxInnerSep - innerVertSep1->position()); QVERIFY(frame1->width() <= frame1->maxSizeHint().width()); } void TestDocks::tst_maxSizeHonouredWhenDropped() { EnsureTopLevelsDeleted e; auto m1 = createMainWindow(); auto dock1 = new DockWidgetType("dock1"); auto dock2 = new DockWidgetType("dock2"); m1->addDockWidget(dock1, Location_OnTop); m1->resize(2000, 2000); auto w2 = new MyWidget2(QSize(400,400)); dock2->setWidget(w2); const int maxWidth = 200; w2->setMaximumSize(maxWidth, 200); m1->addDockWidget(dock2, Location_OnLeft); const int droppedWidth = dock2->dptr()->frame()->width(); QVERIFY(droppedWidth < maxWidth + 50); // +50 to cover any margins and waste by QTabWidget // Try again, but now dropping a multisplitter dock2->setFloating(true); auto fw = dock2->floatingWindow(); m1->dropArea()->drop(fw, Location_OnLeft, nullptr); QCOMPARE(dock2->dptr()->frame()->width(), droppedWidth); } void TestDocks::tst_fixedSizePolicy() { // tests that KDDW also takes into account QSizePolicy::Fixed for calculating the max size hint. // Since QPushButton for example doesn't set QWidget::maximumSize(), but instead uses sizeHint() // + QSizePolicy::Fixed. EnsureTopLevelsDeleted e; auto button = new QPushButton("one"); auto dock1 = createDockWidget("dock1", button); Frame *frame = dock1->dptr()->frame(); // Just a precondition from the test. If QPushButton ever changes, replace with a QWidget and set fixed size policy QCOMPARE(button->sizePolicy().verticalPolicy(), QSizePolicy::Fixed); const int buttonMaxHeight = button->sizeHint().height(); QCOMPARE(dock1->sizeHint(), button->sizeHint()); QCOMPARE(dock1->sizePolicy().verticalPolicy(), button->sizePolicy().verticalPolicy()); QCOMPARE(dock1->sizePolicy().horizontalPolicy(), button->sizePolicy().horizontalPolicy()); QCOMPARE(frame->maxSizeHint().height(), qMax(buttonMaxHeight, Layouting::Item::hardcodedMinimumSize.height())); delete dock1->window(); } #endif void TestDocks::tst_floatingAction() { // Tests DockWidget::floatAction() EnsureTopLevelsDeleted e; { // 1. Create a MainWindow with two docked dock-widgets, then float the first one. 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 action = dock1->floatAction(); QVERIFY(!dock1->isFloating()); QVERIFY(!action->isChecked()); QVERIFY(action->isEnabled()); QCOMPARE(action->toolTip(), tr("Detach")); action->toggle(); QVERIFY(dock1->isFloating()); QVERIFY(action->isChecked()); QVERIFY(action->isEnabled()); QCOMPARE(action->toolTip(), tr("Dock")); auto fw = dock1->floatingWindow(); QVERIFY(fw); //2. Put it back, via setFloating(). It should return to its place. action->toggle(); QVERIFY(!dock1->isFloating()); QVERIFY(!action->isChecked()); QVERIFY(action->isEnabled()); QVERIFY(!dock1->isTabbed()); QCOMPARE(action->toolTip(), tr("Detach"));; Testing::waitForDeleted(fw); } { // 1. Create a MainWindow with one docked dock-widgets, and one floating. auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); //The floating window action should be disabled as it has no previous place auto action = dock2->floatAction(); QVERIFY(dock2->isFloating()); QVERIFY(action->isChecked()); QVERIFY(!action->isEnabled()); QCOMPARE(action->toolTip(), tr("Dock")); m->addDockWidget(dock2, KDDockWidgets::Location_OnRight); QVERIFY(!dock2->isFloating()); QVERIFY(!action->isChecked()); QVERIFY(action->isEnabled()); QCOMPARE(action->toolTip(), tr("Detach")); action->toggle(); QVERIFY(dock2->isFloating()); QVERIFY(action->isChecked()); QVERIFY(action->isEnabled()); QCOMPARE(action->toolTip(), tr("Dock")); auto fw = dock2->floatingWindow(); QVERIFY(fw); //2. Put it back, via setFloating(). It should return to its place. action->toggle(); QVERIFY(!dock1->isFloating()); QVERIFY(!action->isChecked()); QVERIFY(action->isEnabled()); QVERIFY(!dock1->isTabbed()); QCOMPARE(action->toolTip(), tr("Detach")); Testing::waitForDeleted(fw); } { // 3. A floating window with two tabs auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); bool dock1IsFloating = dock1->floatAction()->isChecked(); bool dock2IsFloating = dock2->floatAction()->isChecked(); connect(dock1->floatAction(), &QAction::toggled, [&dock1IsFloating] (bool t) { Q_ASSERT(dock1IsFloating != t); dock1IsFloating = t; }); connect(dock2->floatAction(), &QAction::toggled, [&dock2IsFloating] (bool t) { Q_ASSERT(dock2IsFloating != t); dock2IsFloating = t; }); auto fw2 = dock2->floatingWindow(); QVERIFY(dock1->isFloating()); QVERIFY(dock2->isFloating()); QVERIFY(dock1->floatAction()->isChecked()); QVERIFY(dock2->floatAction()->isChecked()); dock1->addDockWidgetAsTab(dock2); QVERIFY(!dock1->isFloating()); QVERIFY(!dock2->isFloating()); QVERIFY(!dock1->floatAction()->isChecked()); QVERIFY(!dock2->floatAction()->isChecked()); dock2->setFloating(true); QVERIFY(dock1->isFloating()); QVERIFY(dock1->floatAction()->isChecked()); QVERIFY(dock2->isFloating()); QVERIFY(dock2->floatAction()->isChecked()); QVERIFY(dock1IsFloating); QVERIFY(dock2IsFloating); delete fw2; delete dock1->window(); delete dock2->window(); } { // If the dock widget is alone then it's floating, but we suddenly dock a widget side-by-side // to it, then both aren't floating anymore. This test tests if the signal was emitted auto dock1 = createDockWidget("one", new QPushButton("one")); auto dock2 = createDockWidget("two", new QPushButton("two")); QVERIFY(dock1->isFloating()); QVERIFY(dock2->isFloating()); QVERIFY(dock1->floatAction()->isChecked()); QVERIFY(dock2->floatAction()->isChecked()); auto oldFw2 = dock2->window(); QSignalSpy spy1(dock1->floatAction(), &QAction::toggled); QSignalSpy spy2(dock2->floatAction(), &QAction::toggled); QSignalSpy spy11(dock1, &DockWidgetBase::isFloatingChanged); QSignalSpy spy21(dock2, &DockWidgetBase::isFloatingChanged); dock1->addDockWidgetToContainingWindow(dock2, Location_OnRight); QCOMPARE(spy1.count(), 1); QCOMPARE(spy2.count(), 1); QCOMPARE(spy11.count(), 1); QCOMPARE(spy21.count(), 1); QVERIFY(!dock1->isFloating()); QVERIFY(!dock2->isFloating()); QVERIFY(!dock2->floatAction()->isChecked()); QVERIFY(!dock1->floatAction()->isChecked()); delete dock1->window(); delete oldFw2->window(); } { // Like before, but now we use addMultiSplitter() auto dock1 = createDockWidget("one", new QPushButton("one")); auto dock2 = createDockWidget("two", new QPushButton("two")); QVERIFY(dock1->isFloating()); QVERIFY(dock2->isFloating()); QVERIFY(dock1->floatAction()->isChecked()); QVERIFY(dock2->floatAction()->isChecked()); auto oldFw2 = dock2->floatingWindow(); QSignalSpy spy1(dock1->floatAction(), &QAction::toggled); QSignalSpy spy2(dock2->floatAction(), &QAction::toggled); QSignalSpy spy11(dock1, &DockWidgetBase::isFloatingChanged); QSignalSpy spy21(dock2, &DockWidgetBase::isFloatingChanged); auto dropArea1 = dock1->floatingWindow()->dropArea(); dropArea1->drop(oldFw2, Location_OnRight, nullptr); QCOMPARE(spy1.count(), 1); QCOMPARE(spy2.count(), 1); QCOMPARE(spy11.count(), 1); QCOMPARE(spy21.count(), 1); QVERIFY(!dock1->isFloating()); QVERIFY(!dock2->isFloating()); QVERIFY(!dock2->floatAction()->isChecked()); QVERIFY(!dock1->floatAction()->isChecked()); // Let's now remove dock1, dock2 should be floating dock1->setFloating(true); QVERIFY(dock1->isFloating()); QVERIFY(dock2->isFloating()); QVERIFY(dock2->floatAction()->isChecked()); QVERIFY(dock1->floatAction()->isChecked()); delete dock1->window(); delete dock2->window(); delete oldFw2->window(); } { // Same test as before, but now tab instead of side-by-side auto dock1 = createDockWidget("one", new QPushButton("one")); auto dock2 = createDockWidget("two", new QPushButton("two")); QVERIFY(dock1->isFloating()); QVERIFY(dock2->isFloating()); QVERIFY(dock1->floatAction()->isChecked()); QVERIFY(dock2->floatAction()->isChecked()); auto oldFw2 = dock2->window(); QSignalSpy spy1(dock1->floatAction(), &QAction::toggled); QSignalSpy spy2(dock2->floatAction(), &QAction::toggled); QSignalSpy spy11(dock1, &DockWidgetBase::isFloatingChanged); QSignalSpy spy21(dock2, &DockWidgetBase::isFloatingChanged); dock1->addDockWidgetAsTab(dock2); QCOMPARE(spy1.count(), 1); // On earlier Qt versions this is flaky, but technically correct. // Windows can get hidden while being reparented and floating changes momentarily. QVERIFY(spy2.count() == 1 || spy2.count() == 3); QVERIFY(spy21.count() == 1 || spy21.count() == 3); QCOMPARE(spy11.count(), 1); QVERIFY(!dock1->isFloating()); QVERIFY(!dock2->isFloating()); QVERIFY(!dock2->floatAction()->isChecked()); QVERIFY(!dock1->floatAction()->isChecked()); delete dock1->window(); delete oldFw2->window(); } } void TestDocks::tst_raise() { // Tests DockWidget::raise(); EnsureTopLevelsDeleted e; auto dock1 = createDockWidget("1"); auto dock2 = createDockWidget("2"); auto fw2 = Tests::make_qpointer(dock2->window()); dock1->addDockWidgetAsTab(dock2); dock1->setAsCurrentTab(); QVERIFY(dock1->isCurrentTab()); QVERIFY(!dock2->isCurrentTab()); dock2->raise(); QVERIFY(!dock1->isCurrentTab()); QVERIFY(dock2->isCurrentTab()); if (qApp->platformName() != QLatin1String("offscreen")) { // offscreen qpa doesn't seem to keep Window Z. auto dock3 = createDockWidget("3"); dock3->window()->setGeometry(dock1->window()->geometry()); dock3->window()->setObjectName("3"); dock1->window()->setObjectName("1"); dock3->raise(); QTest::qWait(200); if (qApp->QGuiApplication::topLevelAt(dock3->window()->geometry().topLeft() + QPoint(50, 50)) != dock3->windowHandle()) { qDebug() << "Failing before raise" << qApp->widgetAt(dock3->window()->geometry().topLeft() + QPoint(50, 50))->window() << dock3->window() << dock1->window()->geometry() << dock3->window()->geometry(); QVERIFY(false); } dock1->raise(); QTest::qWait(200); QVERIFY(dock1->isCurrentTab()); if (qApp->QGuiApplication::topLevelAt(dock3->window()->geometry().topLeft() + QPoint(50, 50)) != dock1->windowHandle()) { qDebug() << "Failing after raise" << qApp->widgetAt(dock3->window()->geometry().topLeft() + QPoint(50, 50))->window() << dock1->window() << dock1->window()->geometry() << dock3->window()->geometry(); QVERIFY(false); } delete dock3->window(); } delete fw2; delete dock1->window(); } void TestDocks::tst_invalidLayoutAfterRestore() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto dock3 = createDockWidget("dock3", new QPushButton("three")); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; // Stack 1, 2, 3 m->addDockWidget(dock1, Location_OnLeft); m->addDockWidget(dock2, Location_OnRight); m->addDockWidget(dock3, Location_OnRight); const int oldContentsWidth = layout->width(); auto f1 = dock1->dptr()->frame(); dock3->close(); dock2->close(); dock1->close(); QVERIFY(Testing::waitForDeleted(f1)); dock3->show(); dock2->show(); dock1->show(); Testing::waitForEvent(m.get(), QEvent::LayoutRequest); // So MainWindow min size is updated Item *item1 = layout->itemForFrame(dock1->dptr()->frame()); Item *item3 = layout->itemForFrame(dock3->dptr()->frame()); Item *item4 = dropArea->centralFrame(); QCOMPARE(layout->count(), 4); QCOMPARE(layout->placeholderCount(), 0); // Detach dock2 QPointer f2 = dock2->dptr()->frame(); f2->detachTab(dock2); QVERIFY(!f2.data()); QTest::qWait(200); // Not sure why. Some event we're waiting for. TODO: Investigate auto fw2 = dock2->floatingWindow(); QCOMPARE(layout->minimumSize().width(), 2*Item::separatorThickness + item1->minSize().width() + item3->minSize().width() + item4->minSize().width()); // Drop left of dock3 layout->addWidget(fw2->dropArea(), Location_OnLeft, dock3->dptr()->frame()); QVERIFY(Testing::waitForDeleted(fw2)); QCOMPARE(layout->width(), oldContentsWidth); layout->checkSanity(); } void TestDocks::tst_dontCloseDockWidgetBeforeRestore() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two"), {}, DockWidgetBase::LayoutSaverOption::Skip); auto dock3 = createDockWidget("dock3", new QPushButton("three"), {}, DockWidgetBase::LayoutSaverOption::Skip); auto dock4 = createDockWidget("4", new QPushButton("4"), {}, {}, /*show=*/ false); m->addDockWidget(dock1, Location_OnBottom); m->addDockWidget(dock2, Location_OnBottom); // Dock #3 floats, while #1 and #2 are docked. dock3->setFloating(true); QVERIFY(dock3->isOpen()); QVERIFY(dock3->isFloating()); QVERIFY(!dock3->isInMainWindow()); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); dock3->close(); // Not open anymore QVERIFY(!dock3->isOpen()); QVERIFY(saver.restoreLayout(saved)); // #3 is still closed, the restore will skip it QVERIFY(!dock3->isOpen()); QVERIFY(!dock3->isInMainWindow()); auto dock5 = createDockWidget("5", new QPushButton("5"), {}, DockWidgetBase::LayoutSaverOption::Skip); dock4->show(); dock5->show(); QVERIFY(saver.restoreLayout(saved)); QVERIFY(!dock4->isOpen()); QVERIFY(dock5->isOpen()); // #5 is still open, it ignored restore } void TestDocks::tst_dontCloseDockWidgetBeforeRestore2() { // In this case we have a floating window with two dock widgets tabbed, both having LayoutSaverOption::Skip // Meaning the whole window should be skipped EnsureTopLevelsDeleted e; auto dock2 = createDockWidget("dock2", new QPushButton("two"), {}, DockWidgetBase::LayoutSaverOption::Skip); auto dock3 = createDockWidget("dock3", new QPushButton("three"), {}, DockWidgetBase::LayoutSaverOption::Skip); dock2->close(); dock3->close(); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); // This layout has 0 docks visible dock2->show(); dock3->show(); QVERIFY(saver.restoreLayout(saved)); QVERIFY(dock2->isVisible()); // They're still visible QVERIFY(dock3->isVisible()); // Now tab and restore again dock2->addDockWidgetAsTab(dock3); QVERIFY(saver.restoreLayout(saved)); QVERIFY(dock2->isOpen()); QVERIFY(dock3->isOpen()); QVERIFY(dock3->isVisible()); QCOMPARE(dock3->dptr()->frame(), dock2->dptr()->frame()); } void TestDocks::tst_dontCloseDockWidgetBeforeRestore3() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two"), {}, DockWidgetBase::LayoutSaverOption::Skip); dock1->close(); dock2->close(); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); // This layout has 0 docks visible m->addDockWidget(dock1, Location_OnBottom); m->addDockWidget(dock2, Location_OnBottom); QVERIFY(saver.restoreLayout(saved)); QVERIFY(!dock1->isOpen()); // Gets closed by the restore QVERIFY(dock2->isOpen()); // Dock2 remains open, it ignores restore QVERIFY(dock2->isFloating()); } void TestDocks::tst_dontCloseDockWidgetBeforeRestore4() { // Tests a case where the dock widget would get an invalid size. // Widgets which skip layout restore were be skipping LayoutSaver::onResize() EnsureTopLevelsDeleted e; auto m = createMainWindow({1000, 1000}, {}); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two"), {}, DockWidgetBase::LayoutSaverOption::Skip); m->addDockWidget(dock1, Location_OnBottom); m->addDockWidget(dock2, Location_OnBottom); QTest::qWait(100); dock1->close(); dock2->close(); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); QTest::qWait(100); dock2->show(); QVERIFY(saver.restoreLayout(saved)); QVERIFY(dock2->isOpen()); QTest::qWait(100); FloatingWindow *fw = dock2->floatingWindow(); DropArea *da = fw->dropArea(); QVERIFY(da->checkSanity()); QCOMPARE(da->size(), da->rootItem()->size()); QVERIFY(qAbs(fw->width() - da->width()) < 30); } void TestDocks::tst_closeOnlyCurrentTab() { { // Case of a floating window with tabs EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_CloseOnlyCurrentTab); auto dock1 = createDockWidget("1", new QPushButton("1")); auto dock2 = createDockWidget("2", new QPushButton("2")); auto dock3 = createDockWidget("3", new QPushButton("3")); /// Floating window with 3 tabs dock1->addDockWidgetAsTab(dock2); dock1->addDockWidgetAsTab(dock3); TitleBar *tb = dock1->titleBar(); QVERIFY(tb->isVisible()); dock1->setAsCurrentTab(); Frame *frame = dock1->dptr()->frame(); QCOMPARE(frame->currentIndex(), 0); tb->onCloseClicked(); QVERIFY(!dock1->isOpen()); QVERIFY(dock2->isOpen()); QVERIFY(dock3->isOpen()); } { // Case of a floating window with tabs EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_CloseOnlyCurrentTab); auto m = createMainWindow(); auto dock1 = createDockWidget("1", new QPushButton("1")); auto dock2 = createDockWidget("2", new QPushButton("2")); auto dock3 = createDockWidget("3", new QPushButton("3")); m->addDockWidget(dock1, Location_OnLeft); m->addDockWidget(dock2, Location_OnRight); dock2->addDockWidgetAsTab(dock3); Frame *frame = dock2->dptr()->frame(); QCOMPARE(frame->currentIndex(), 1); TitleBar *tb = frame->titleBar(); QVERIFY(tb->isVisible()); tb->onCloseClicked(); QVERIFY(!dock3->isOpen()); QVERIFY(dock2->isOpen()); QVERIFY(dock1->isOpen()); QCOMPARE(frame->dockWidgetCount(), 1); } } void TestDocks::tst_tabWidgetCurrentIndex() { EnsureTopLevelsDeleted e; auto dock1 = createDockWidget("1", new QPushButton("1")); auto dock2 = createDockWidget("2", new QPushButton("2")); auto dock3 = createDockWidget("3", new QPushButton("3")); auto fw2 = dock2->window(); auto fw3 = dock3->window(); DockWidgetBase *currentDw = nullptr; auto frame = dock1->dptr()->frame(); connect(frame, &Frame::currentDockWidgetChanged, this, [¤tDw] (DockWidgetBase *dw){ currentDw = dw; }); QCOMPARE(frame->tabWidget()->currentIndex(), 0); dock1->addDockWidgetAsTab(dock2); QCOMPARE(frame->tabWidget()->currentIndex(), 1); QCOMPARE(frame->currentDockWidget(), currentDw); QCOMPARE(dock2, currentDw); dock2->close(); QCOMPARE(frame->tabWidget()->currentIndex(), 0); QCOMPARE(dock1, currentDw); delete fw2; delete fw3; delete dock2; delete dock1->window(); } void TestDocks::tst_doubleClickTabToDetach() { EnsureTopLevelsDeleted e; auto dock1 = createDockWidget("1", new QPushButton("1")); auto dock2 = createDockWidget("2", new QPushButton("2")); auto fw2 = dock2->window(); dock1->addDockWidgetAsTab(dock2); auto frame = dock1->dptr()->frame(); QCOMPARE(frame->currentIndex(), 1); auto tb = frame->tabWidget()->asWidget(); Tests::doubleClickOn(tb->mapToGlobal(QPoint(20, 20)), frame->window()->windowHandle()); QVERIFY(dock1->isFloating()); QVERIFY(dock2->isFloating()); QVERIFY(dock1->floatingWindow() != dock2->floatingWindow()); delete fw2; delete dock1->window(); delete dock2->window(); } void TestDocks::tst_addingOptionHiddenTabbed() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(501, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new QPushButton("1")); auto dock2 = createDockWidget("2", new QPushButton("2")); m->addDockWidget(dock1, Location_OnTop); QCOMPARE(dock1->dptr()->frame()->dockWidgetCount(), 1); dock1->addDockWidgetAsTab(dock2, InitialVisibilityOption::StartHidden); QCOMPARE(dock1->dptr()->frame()->dockWidgetCount(), 1); dock2->show(); QCOMPARE(dock1->dptr()->frame()->dockWidgetCount(), 2); QVERIFY(dock1->dptr()->frame() == dock2->dptr()->frame()); } void TestDocks::tst_flagDoubleClick() { { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_DoubleClickMaximizes); auto m = createMainWindow(QSize(500, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new QPushButton("1")); auto dock2 = createDockWidget("2", new QPushButton("2")); m->addDockWidget(dock1, Location_OnTop); FloatingWindow *fw2 = dock2->floatingWindow(); QVERIFY(!fw2->isMaximized()); TitleBar *t2 = dock2->titleBar(); QPoint pos = t2->mapToGlobal(QPoint(5, 5)); Tests::doubleClickOn(pos, t2); QVERIFY(fw2->isMaximized()); delete fw2; TitleBar *t1 = dock1->titleBar(); QVERIFY(!t1->isFloating()); pos = t1->mapToGlobal(QPoint(5, 5)); Tests::doubleClickOn(pos, t1); QVERIFY(t1->isFloating()); QVERIFY(!dock1->window()->isMaximized()); delete dock1->window(); } { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(500, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnTop); TitleBar *t1 = dock1->titleBar(); QVERIFY(!t1->isFloating()); QPoint pos = t1->mapToGlobal(QPoint(5, 5)); Tests::doubleClickOn(pos, t1); QVERIFY(t1->isFloating()); QVERIFY(dock1->isFloating()); QVERIFY(!dock1->window()->isMaximized()); pos = t1->mapToGlobal(QPoint(5, 5)); Tests::doubleClickOn(pos, t1); QVERIFY(!dock1->isFloating()); } } void TestDocks::tst_maxSizedHonouredAfterRemoved() { EnsureTopLevelsDeleted e; auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None); auto dock1 = new DockWidgetType("dock1"); dock1->show(); auto w = new MyWidget("foo"); w->setMinimumSize(120, 100); w->setMaximumSize(300, 150); dock1->setWidget(w); m1->dropArea()->addMultiSplitter(dock1->floatingWindow()->multiSplitter(), Location_OnLeft); auto dock2 = new DockWidgetType("dock2"); dock2->show(); m1->dropArea()->addMultiSplitter(dock2->floatingWindow()->multiSplitter(), Location_OnTop); auto root = m1->multiSplitter()->rootItem(); // Wait 1 event loop so we get layout invalidated and get max-size constraints QTest::qWait(10); auto sep = root->separators().constFirst(); root->requestEqualSize(sep); // Since we're not calling honourMaxSizes() after a widget changes its max size afterwards yet const int sepMin = root->minPosForSeparator_global(sep); const int sepMax = root->maxPosForSeparator_global(sep); QVERIFY(sep->position() >= sepMin); QVERIFY(sep->position() <= sepMax); auto dock3 = new DockWidgetType("dock3"); dock3->show(); m1->dropArea()->addMultiSplitter(dock3->floatingWindow()->multiSplitter(), Location_OnBottom); dock1->setFloating(true); m1->dropArea()->addMultiSplitter(dock1->floatingWindow()->multiSplitter(), Location_OnBottom, dock2->dptr()->frame()); // Close dock2 and check if dock1's max-size is still honoured dock2->close(); QTest::qWait(100); // wait for the resize, so dock1 gets taller" QVERIFY(dock1->dptr()->frame()->height() <= dock1->dptr()->frame()->maxSizeHint().height()); delete dock2; } void TestDocks::tst_addDockWidgetAsTabToDockWidget() { EnsureTopLevelsDeleted e; { // Dock into a non-morphed floating dock widget auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); dock1->addDockWidgetAsTab(dock2); auto window1 = dock1->window(); auto window2 = dock2->window(); QCOMPARE(window1, window2); QCOMPARE(dock1->dptr()->frame(), dock2->dptr()->frame()); QCOMPARE(dock1->dptr()->frame()->dockWidgetCount(), 2); dock1->deleteLater(); dock2->deleteLater(); Testing::waitForDeleted(dock2); } { // Dock into a morphed dock widget auto dock1 = createDockWidget("dock1", new QPushButton("one")); dock1->dptr()->morphIntoFloatingWindow(); auto dock2 = createDockWidget("dock2", new QPushButton("two")); dock1->addDockWidgetAsTab(dock2); auto window1 = dock1->window(); auto window2 = dock2->window(); QCOMPARE(window1, window2); QCOMPARE(dock1->dptr()->frame(), dock2->dptr()->frame()); QCOMPARE(dock1->dptr()->frame()->dockWidgetCount(), 2); dock1->deleteLater(); dock2->deleteLater(); Testing::waitForDeleted(dock2); } { // Dock a morphed dock widget into a morphed dock widget auto dock1 = createDockWidget("dock1", new QPushButton("one")); dock1->dptr()->morphIntoFloatingWindow(); auto dock2 = createDockWidget("dock2", new QPushButton("two")); dock2->dptr()->morphIntoFloatingWindow(); auto originalWindow2 = Tests::make_qpointer(dock2->window()); dock1->addDockWidgetAsTab(dock2); auto window1 = dock1->window(); auto window2 = dock2->window(); QCOMPARE(window1, window2); QCOMPARE(dock1->dptr()->frame(), dock2->dptr()->frame()); QCOMPARE(dock1->dptr()->frame()->dockWidgetCount(), 2); Testing::waitForDeleted(originalWindow2); QVERIFY(!originalWindow2); dock1->deleteLater(); dock2->deleteLater(); Testing::waitForDeleted(dock2); } { // Dock to an already docked widget auto m = createMainWindow(); auto dropArea = m->dropArea(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); nestDockWidget(dock1, dropArea, nullptr, KDDockWidgets::Location_OnLeft); auto dock2 = createDockWidget("dock2", new QPushButton("two")); dock1->addDockWidgetAsTab(dock2); QCOMPARE(dock1->window(), m.get()); QCOMPARE(dock2->window(), m.get()); QCOMPARE(dock1->dptr()->frame(), dock2->dptr()->frame()); QCOMPARE(dock1->dptr()->frame()->dockWidgetCount(), 2); } } void TestDocks::tst_closeTabHidesDockWidget() { // Tests that closing some tabbed dock widgets will hide them // QtQuick had a bug where they would still be visible EnsureTopLevelsDeleted e; auto dock1 = createDockWidget("doc1", Qt::green); auto dock2 = createDockWidget("doc2", Qt::green); auto dock3 = createDockWidget("doc3", Qt::green); dock1->addDockWidgetAsTab(dock2); dock1->addDockWidgetAsTab(dock3); dock1->forceClose(); QVERIFY(!dock1->isOpen()); QVERIFY(!dock1->isVisible()); dock2->forceClose(); QVERIFY(!dock2->isOpen()); QVERIFY(!dock2->isVisible()); dock3->forceClose(); QVERIFY(!dock3->isOpen()); QVERIFY(!dock3->isVisible()); } void TestDocks::tst_close() { EnsureTopLevelsDeleted e; // 1.0 Call QWidget::close() on QDockWidget auto dock1 = createDockWidget("doc1", Qt::green); QAction *toggleAction = dock1->toggleAction(); QVERIFY(toggleAction->isChecked()); QVERIFY(dock1->close()); QVERIFY(!dock1->isVisible()); QVERIFY(!dock1->window()->isVisible()); QCOMPARE(dock1->window(), dock1); QVERIFY(!toggleAction->isChecked()); // 1.1 Reshow with show() dock1->show(); auto fw = dock1->floatingWindow(); QVERIFY(fw); QVERIFY(toggleAction->isChecked()); QVERIFY(dock1->isVisible()); QCOMPARE(dock1->window(), fw); QVERIFY(toggleAction->isChecked()); // 1.2 Reshow with toggleAction instead QVERIFY(dock1->close()); QVERIFY(!toggleAction->isChecked()); QVERIFY(!dock1->isVisible()); toggleAction->setChecked(true); QVERIFY(dock1->isVisible()); // 1.3 Use hide() instead auto fw1 = dock1->floatingWindow(); QVERIFY(fw1); dock1->close(); // TODO: Hide doesn't delete the FloatingWindow QVERIFY(Testing::waitForDeleted(fw1)); QVERIFY(!dock1->isVisible()); QVERIFY(!dock1->window()->isVisible()); QCOMPARE(dock1->window(), dock1); QVERIFY(!toggleAction->isChecked()); // 1.4 close a FloatingWindow, via DockWidget::close QPointer window = dock1->dptr()->morphIntoFloatingWindow(); QPointer frame1 = dock1->dptr()->frame(); QVERIFY(dock1->isVisible()); QVERIFY(dock1->window()->isVisible()); QVERIFY(frame1->QWidgetAdapter::isVisible()); QCOMPARE(dock1->window(), window.data()); QVERIFY(dock1->close()); QVERIFY(!dock1->dptr()->frame()); QVERIFY(Testing::waitForDeleted(frame1)); QVERIFY(Testing::waitForDeleted(window)); // 1.5 close a FloatingWindow, via FloatingWindow::close dock1->show(); window = dock1->dptr()->morphIntoFloatingWindow(); frame1 = dock1->dptr()->frame(); QVERIFY(dock1->isVisible()); QVERIFY(dock1->window()->isVisible()); QVERIFY(frame1->QWidgetAdapter::isVisible()); QCOMPARE(dock1->window(), window.data()); QVERIFY(window->close()); QVERIFY(!dock1->dptr()->frame()); QVERIFY(Testing::waitForDeleted(frame1)); QVERIFY(Testing::waitForDeleted(window)); // TODO: 1.6 Test FloatingWindow with two frames // TODO: 1.7 Test Frame with two tabs // 1.8 Check if space is reclaimed after closing left dock DockWidgetBase *centralDock; DockWidgetBase *leftDock; DockWidgetBase *rightDock; auto mainwindow = createSimpleNestedMainWindow(¢ralDock, &leftDock, &rightDock); auto da = mainwindow->dropArea(); QVERIFY(da->checkSanity()); QCOMPARE(leftDock->dptr()->frame()->QWidgetAdapter::x(), 0); QCOMPARE(centralDock->dptr()->frame()->QWidgetAdapter::x(), leftDock->dptr()->frame()->QWidgetAdapter::geometry().right() + Item::separatorThickness + 1); QCOMPARE(rightDock->dptr()->frame()->QWidgetAdapter::x(), centralDock->dptr()->frame()->QWidgetAdapter::geometry().right() + Item::separatorThickness + 1); leftDock->close(); QTest::qWait(250); // TODO: wait for some signal QCOMPARE(centralDock->dptr()->frame()->QWidgetAdapter::x(), 0); QCOMPARE(rightDock->dptr()->frame()->QWidgetAdapter::x(), centralDock->dptr()->frame()->QWidgetAdapter::geometry().right() + Item::separatorThickness + 1); rightDock->close(); QTest::qWait(250); // TODO: wait for some signal QMargins margins = mainwindow->centerWidgetMargins(); QCOMPARE(centralDock->dptr()->frame()->width(), mainwindow->width() - 0 * 2 - margins.left() - margins.right()); delete leftDock; delete rightDock; delete centralDock; // 1.9 Close tabbed dock, side docks will maintain their position mainwindow = createSimpleNestedMainWindow(¢ralDock, &leftDock, &rightDock); const int leftX = leftDock->dptr()->frame()->QWidgetAdapter::x(); const int rightX = rightDock->dptr()->frame()->QWidgetAdapter::x(); centralDock->close(); QCOMPARE(leftDock->dptr()->frame()->QWidgetAdapter::x(), leftX); QCOMPARE(rightDock->dptr()->frame()->QWidgetAdapter::x(), rightX); delete leftDock; delete rightDock; delete centralDock; delete dock1; // 2. Test that closing the single frame of a main window doesn't close the main window itself { auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame QPointer mainWindowPtr = m.get(); dock1 = createDockWidget("hello", Qt::green); m->addDockWidget(dock1, Location_OnLeft); // 2.2 Closing should not close the main window dock1->close(); QVERIFY(mainWindowPtr.data()); delete dock1; } // 2.1 Test closing the frame instead { auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame QPointer mainWindowPtr = m.get(); dock1 = createDockWidget("hello", Qt::green); m->addDockWidget(dock1, Location_OnLeft); // 2.2 Closing should not close the main window dock1->dptr()->frame()->titleBar()->onCloseClicked(); QVERIFY(mainWindowPtr.data()); QVERIFY(mainWindowPtr->isVisible()); delete dock1; } // 2.2 Repeat, but with a central frame { auto m = createMainWindow(QSize(800, 500)); QPointer mainWindowPtr = m.get(); dock1 = createDockWidget("hello", Qt::green); m->addDockWidget(dock1, Location_OnLeft); // 2.2 Closing should not close the main window dock1->dptr()->frame()->titleBar()->onCloseClicked(); QVERIFY(mainWindowPtr.data()); QVERIFY(mainWindowPtr->isVisible()); delete dock1; } } void TestDocks::tst_propagateSizeHonoursMinSize() { // Here we dock a widget on the left size, and on the right side. // When docking the second one, the 1st one shouldn't be squeezed too much, as it has a min size EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("one")); auto dock2 = createDockWidget("dock2", new QPushButton("two")); auto dropArea = m->dropArea(); int min1 = widgetMinLength(dock1, Qt::Horizontal); int min2 = widgetMinLength(dock2, Qt::Horizontal); QVERIFY(dock1->width() >= min1); QVERIFY(dock2->width() >= min2); nestDockWidget(dock1, dropArea, nullptr, KDDockWidgets::Location_OnRight); nestDockWidget(dock2, dropArea, nullptr, KDDockWidgets::Location_OnLeft); // Calculate again, as the window frame has disappeared min1 = widgetMinLength(dock1, Qt::Horizontal); min2 = widgetMinLength(dock2, Qt::Horizontal); auto l = m->dropArea(); l->checkSanity(); if (dock1->width() < min1) { qDebug() << "\ndock1->width()=" << dock1->width() << "\nmin1=" << min1 << "\ndock min sizes=" << dock1->minimumWidth() << dock1->minimumSizeHint().width() << "\nframe1->width()=" << dock1->dptr()->frame()->width() << "\nframe1->min=" << widgetMinLength(dock1->dptr()->frame(), Qt::Horizontal); l->dumpLayout(); QVERIFY(false); } QVERIFY(dock2->width() >= min2); // Dock on top of center widget: m = createMainWindow(); dock1 = createDockWidget("one", new QTextEdit()); m->addDockWidgetAsTab(dock1); auto dock3 = createDockWidget("three", new QTextEdit()); m->addDockWidget(dock3, Location_OnTop); QVERIFY(m->dropArea()->checkSanity()); min1 = widgetMinLength(dock1, Qt::Vertical); QVERIFY(dock1->height() >= min1); } void TestDocks::tst_constraintsPropagateUp() { // Mostly for QtQuick, which doesn't have any layouts, so we need to make the propagation // Manually in DockWidgetQuick::minimumSize(), in FrameQuick, etc. EnsureTopLevelsDeleted e; const int minWidth = 500; const int minHeight = 400; const QSize minSz = { minWidth, minHeight }; auto guestWidget = new MyWidget2(QSize(minWidth, minHeight)); auto dock1 = createDockWidget("dock1", guestWidget); auto dock2= createDockWidget("dock2", new MyWidget2(QSize(minWidth, minHeight))); QCOMPARE(widgetMinLength(guestWidget, Qt::Vertical), minHeight); QCOMPARE(widgetMinLength(guestWidget, Qt::Horizontal), minWidth); QCOMPARE(dock1->minimumWidth(), minWidth); QCOMPARE(dock1->minimumHeight(), minHeight); QCOMPARE(dock1->minimumSize(), minSz); auto frame1 = dock1->dptr()->frame(); QVERIFY(qAbs(widgetMinLength(frame1, Qt::Horizontal) - minWidth) < 10); //10px for styling differences #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // Flaky with 5.9 QVERIFY(qAbs(widgetMinLength(frame1, Qt::Vertical) - (minHeight + frame1->nonContentsHeight())) < 10); //10px for styling differences #endif // Add dock2 side-by side, so the Frame now has a title bar. auto oldFw2 = dock2->window(); dock1->addDockWidgetToContainingWindow(dock2, Location_OnLeft); TitleBar *tb = dock1->titleBar(); QVERIFY(tb->isVisible()); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // Flaky with 5.9 QVERIFY(qAbs(widgetMinLength(frame1, Qt::Vertical) - (minHeight + frame1->nonContentsHeight())) < 10); #endif delete dock1->window(); delete oldFw2; } void TestDocks::tst_constraintsAfterPlaceholder() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(500, 500), MainWindowOption_None); const int minHeight = 400; auto dock1 = createDockWidget("dock1", new MyWidget2(QSize(400, minHeight))); auto dock2 = createDockWidget("dock2", new MyWidget2(QSize(400, minHeight))); auto dock3 = createDockWidget("dock3", new MyWidget2(QSize(400, minHeight))); auto dropArea = m->dropArea(); MultiSplitter *layout = dropArea; // Stack 3, 2, 1 m->addDockWidget(dock1, Location_OnTop); m->addDockWidget(dock2, Location_OnTop); m->addDockWidget(dock3, Location_OnTop); #ifdef KDDOCKWIDGETS_QTWIDGETS QVERIFY(Testing::waitForResize(m.get())); #endif QVERIFY(widgetMinLength(m.get(), Qt::Vertical) > minHeight * 3); // > since some vertical space is occupied by the separators // Now close dock1 and check again dock1->close(); Testing::waitForResize(dock2); Item *item2 = layout->itemForFrame(dock2->dptr()->frame()); Item *item3 = layout->itemForFrame(dock3->dptr()->frame()); QMargins margins = m->centerWidgetMargins(); const int expectedMinHeight = item2->minLength(Qt::Vertical) + item3->minLength(Qt::Vertical) + 1 * Item::separatorThickness + margins.top() + margins.bottom(); QCOMPARE(m->minimumSizeHint().height(), expectedMinHeight); dock1->deleteLater(); Testing::waitForDeleted(dock1); } void TestDocks::tst_dragBySingleTab() { // Tests dragging via a tab when there's only 1 tab, and we're using Flag_AlwaysShowTabs EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AlwaysShowTabs); auto dock1 = createDockWidget("dock1", new MyWidget2(QSize(400, 400))); dock1->show(); auto frame1 = dock1->dptr()->frame(); QPoint globalPressPos = dragPointForWidget(frame1, 0); TabBar *tabBar = frame1->tabWidget()->tabBar(); QVERIFY(tabBar); SetExpectedWarning sew("No window being dragged for"); // because dragging by tab does nothing in this case drag(tabBar->asWidget(), globalPressPos, QPoint(0, 0)); delete dock1; Testing::waitForDeleted(frame1); } void TestDocks::tst_dragByTabBar_data() { QTest::addColumn("documentMode"); QTest::addColumn("tabsAlwaysVisible"); QTest::newRow("false-false") << false << false; QTest::newRow("true-false") << true << false; QTest::newRow("false-true") << false << true; QTest::newRow("true-true") << true << true; } void TestDocks::tst_dragByTabBar() { QFETCH(bool, documentMode); QFETCH(bool, tabsAlwaysVisible); EnsureTopLevelsDeleted e; auto flags = KDDockWidgets::Config::self().flags() | KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible; if (tabsAlwaysVisible) flags |= KDDockWidgets::Config::Flag_AlwaysShowTabs; KDDockWidgets::Config::self().setFlags(flags); auto m = createMainWindow(); auto dropArea = m->dropArea(); auto dock1 = createDockWidget("dock1", new MyWidget2(QSize(400, 400))); auto dock2 = createDockWidget("dock2", new MyWidget2(QSize(400, 400))); auto dock3 = createDockWidget("dock3", new MyWidget2(QSize(400, 400))); m->addDockWidgetAsTab(dock1); m->resize(osWindowMinWidth(), 200); dock2->addDockWidgetAsTab(dock3); #if KDDOCKWIDGETS_QTWIDGETS if (documentMode) static_cast( static_cast(dock2->dptr()->frame())->tabWidget()->asWidget()) ->setDocumentMode(true); #else Q_UNUSED(documentMode); #endif auto fw = dock2->floatingWindow(); fw->move(m->pos() + QPoint(500, 500)); QVERIFY(fw->isVisible()); QVERIFY(!fw->titleBar()->isVisible()); dragFloatingWindowTo(fw, dropArea, DropIndicatorOverlayInterface::DropLocation_Right); } void TestDocks::tst_dock2FloatingWidgetsTabbed() { EnsureTopLevelsDeleted e; if (KDDockWidgets::usesNativeTitleBar()) return; // Unit-tests can't drag via tab, yet auto dock1 = createDockWidget("doc1", Qt::green); auto fw1 = dock1->floatingWindow(); fw1->setGeometry(QRect(500, 500, 400, 400)); QVERIFY(dock1); QPointer frame1 = dock1->dptr()->frame(); auto titlebar1 = fw1->titleBar(); auto dock2 = createDockWidget("doc2", Qt::red); QVERIFY(dock1->isFloating()); QVERIFY(dock2->isFloating()); drag(titlebar1, titlebar1->mapToGlobal(QPoint(5, 5)), dock2->window()->geometry().center(), ButtonAction_Press); // It morphed into a FloatingWindow QPointer frame2 = dock2->dptr()->frame(); if (!dock2->floatingWindow()) { qWarning() << "dock2->floatingWindow()=" << dock2->floatingWindow(); QVERIFY(false); } QVERIFY(frame2); QCOMPARE(frame2->dockWidgetCount(), 1); releaseOn(dock2->window()->geometry().center(), titlebar1); QCOMPARE(frame2->dockWidgetCount(), 2); // 2.2 Frame has 2 widgets when one is dropped QVERIFY(Testing::waitForDeleted(frame1)); // 2.3 Detach tab1 to empty space QPoint globalPressPos = dragPointForWidget(frame2.data(), 0); TabBar *tabBar = frame2->tabWidget()->tabBar(); QVERIFY(tabBar); drag(tabBar->asWidget(), globalPressPos, frame2->window()->geometry().bottomRight() + QPoint(10, 10)); QVERIFY(frame2->dockWidgetCount() == 1); QVERIFY(dock1->floatingWindow()); // 2.4 Drag the first dock over the second frame1 = dock1->dptr()->frame(); frame2 = dock2->dptr()->frame(); fw1 = dock1->floatingWindow(); globalPressPos = fw1->titleBar()->mapToGlobal(QPoint(100,5)); drag(fw1->titleBar(), globalPressPos, dock2->window()->geometry().center()); QCOMPARE(frame2->dockWidgetCount(), 2); // 2.5 Detach and drop to the same place, should tab again globalPressPos = dragPointForWidget(frame2.data(), 0); tabBar = frame2->tabWidget()->tabBar(); drag(tabBar->asWidget(), globalPressPos, dock2->window()->geometry().center()); QCOMPARE(frame2->dockWidgetCount(), 2); // 2.6 Drag the tabbed group over a 3rd floating window auto dock3 = createDockWidget("doc3", Qt::black); QTest::qWait(1000); // Test is flaky otherwise auto fw2 = dock2->floatingWindow(); drag(fw2->titleBar(), frame2->mapToGlobal(QPoint(10, 10)), dock3->window()->geometry().center()); QVERIFY(Testing::waitForDeleted(frame1)); QVERIFY(Testing::waitForDeleted(frame2)); QVERIFY(dock3->dptr()->frame()); QCOMPARE(dock3->dptr()->frame()->dockWidgetCount(), 3); auto fw3 = dock3->floatingWindow(); QVERIFY(fw3); QVERIFY(fw3->dropArea()->checkSanity()); // 2.7 Drop the window into a MainWindow { auto m = createMainWindow(); m->show(); m->setGeometry(QRect(500, 300, 300, 300)); QVERIFY(!dock3->isFloating()); auto fw3 = dock3->floatingWindow(); drag(fw3->titleBar(), dock3->window()->mapToGlobal(QPoint(10, 10)), m->geometry().center()); QVERIFY(!dock3->isFloating()); QVERIFY(dock3->window() == m.get()); QCOMPARE(dock3->dptr()->frame()->dockWidgetCount(), 3); QVERIFY(m->dropArea()->checkSanity()); delete dock1; delete dock2; delete dock3; QVERIFY(Testing::waitForDeleted(frame2)); QVERIFY(Testing::waitForDeleted(fw3)); } } void TestDocks::tst_deleteOnClose() { { EnsureTopLevelsDeleted e; // Tests that DockWidget::close() deletes itself if Option_DeleteOnClose is set QPointer dock1 = createDockWidget("1", new MyWidget2(QSize(400, 400)), DockWidgetBase::Option_DeleteOnClose); dock1->show(); dock1->close(); QVERIFY(Testing::waitForDeleted(dock1)); } { // Tests that if it's closed via LayoutSaver it's also destroyed when having Option_DeleteOnClose EnsureTopLevelsDeleted e; QPointer dock1 = createDockWidget("1", new MyWidget2(QSize(400, 400)), DockWidgetBase::Option_DeleteOnClose, {}, /*show=*/ false); QPointer dock2 = createDockWidget("2", new MyWidget2(QSize(400, 400)), {}, {}, /*show=*/ false); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); dock1->show(); dock2->show(); QVERIFY(dock1->isVisible()); QVERIFY(dock2->isVisible()); QVERIFY(saver.restoreLayout(saved)); QVERIFY(!dock1->isVisible()); QVERIFY(!dock2->isVisible()); QVERIFY(Testing::waitForDeleted(dock1)); QVERIFY(dock2.data()); delete dock2; } #ifdef KDDOCKWIDGETS_QTWIDGETS // QtQuick doesn't support autohide/pin/sidebar yet { // Tests that restoring the side-bar-overlay will call the users dock widget factory in case // the dock widget was deleted already EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); KDDockWidgets::Config::self().setDockWidgetFactoryFunc([](const QString &name) { return createDockWidget(name, new MyWidget2(QSize(400, 400)), DockWidgetBase::Option_DeleteOnClose); }); auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); QPointer dock1 = createDockWidget("1", new MyWidget2(QSize(400, 400)), DockWidgetBase::Option_DeleteOnClose); m->addDockWidget(dock1, Location_OnLeft); m->moveToSideBar(dock1); m->overlayOnSideBar(dock1); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); QVERIFY(dock1->isVisible()); dock1->close(); QVERIFY(Testing::waitForDeleted(dock1)); QVERIFY(!dock1.data()); saver.restoreLayout(saved); } #endif } void TestDocks::tst_toggleAction() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget("dock1", new MyWidget2(QSize(400, 400))); auto dock2 = createDockWidget("dock2", new MyWidget2(QSize(400, 400))); auto dock3 = createDockWidget("dock3", new MyWidget2(QSize(400, 400))); m->addDockWidget(dock1, Location_OnLeft); m->addDockWidget(dock2, Location_OnRight); m->addDockWidget(dock3, Location_OnRight); auto root = m->multiSplitter()->rootItem(); QCOMPARE(root->visibleCount_recursive(), 3); QVERIFY(dock2->toggleAction()->isChecked()); QPointer frame2 = dock2->dptr()->frame(); dock2->toggleAction()->toggle(); QVERIFY(!dock2->toggleAction()->isChecked()); QVERIFY(!dock2->isVisible()); QVERIFY(!dock2->isOpen()); QVERIFY(Testing::waitForDeleted(frame2)); QCOMPARE(root->visibleCount_recursive(), 2); } void TestDocks::tst_redocksToPreviousTabIndex() { // Checks that when reordering tabs with mouse, floating and redocking, they go back to their previous index EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AllowReorderTabs); auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock0 = createDockWidget("dock0", new MyWidget2(QSize(400, 400))); auto dock1 = createDockWidget("dock1", new MyWidget2(QSize(400, 400))); m->addDockWidget(dock0, Location_OnLeft); dock0->addDockWidgetAsTab(dock1); QCOMPARE(dock0->tabIndex(), 0); QCOMPARE(dock1->tabIndex(), 1); dock0->setFloating(true); QCOMPARE(dock1->tabIndex(), 0); dock0->setFloating(false); QCOMPARE(dock0->tabIndex(), 0); QCOMPARE(dock1->tabIndex(), 1); Frame *frame = dock0->dptr()->frame(); auto tb = dock0->dptr()->frame()->tabWidget()->tabBar(); tb->moveTabTo(0, 1); #ifdef KDDOCKWIDGETS_QTWIDGETS QCOMPARE(dock0->tabIndex(), 1); QCOMPARE(dock1->tabIndex(), 0); // Also detach via detachTab(), which is what is called when the user detaches with mouse frame->detachTab(dock0); dock0->setFloating(false); QCOMPARE(dock0->tabIndex(), 1); QCOMPARE(dock1->tabIndex(), 0); #else // An XFAIL so we remember to implement this QEXPECT_FAIL("", "TabBar::moveTabTo not implemented for QtQuick yet", Continue); QVERIFY(false); Q_UNUSED(frame); #endif } void TestDocks::tst_addMDIDockWidget() { EnsureTopLevelsDeleted e; // Test that adding a MDI dock widget doesn't produce any warning auto m = createMainWindow(QSize(800, 500), MainWindowOption_MDI); auto dock0 = createDockWidget("dock0", new MyWidget2(QSize(400, 400))); qobject_cast(m->layoutWidget())->addDockWidget(dock0, QPoint(0, 0), {}); } void TestDocks::tst_redockToMDIRestoresPosition() { // Tests that setFloating(false) puts the dock widget where it was before floating EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_MDI); auto dock0 = createDockWidget("dock0", new MyWidget2(QSize(400, 400))); auto layoutWidget = qobject_cast(m->layoutWidget()); const QPoint initialPoint = QPoint(500, 500); layoutWidget->addDockWidget(dock0, initialPoint, {}); Frame *frame = dock0->DockWidgetBase::d->frame(); QCOMPARE(frame->QWidgetAdapter::pos(), initialPoint); const QSize initialSize = frame->QWidgetAdapter::size(); dock0->setFloating(true); dock0->setFloating(false); frame = dock0->DockWidgetBase::d->frame(); QCOMPARE(frame->QWidgetAdapter::pos(), initialPoint); const QPoint anotherPos = QPoint(250, 250); dock0->setMDIPosition(anotherPos); dock0->setFloating(true); dock0->setFloating(false); frame = dock0->DockWidgetBase::d->frame(); Item *item = layoutWidget->itemForFrame(frame); QCOMPARE(item->pos(), anotherPos); QCOMPARE(item->geometry(), frame->QWidgetAdapter::geometry()); QCOMPARE(frame->QWidgetAdapter::pos(), anotherPos); QCOMPARE(frame->QWidgetAdapter::size(), initialSize); const QSize anotherSize = QSize(500, 500); dock0->setMDISize(anotherSize); QCOMPARE(frame->QWidgetAdapter::size(), anotherSize); item = layoutWidget->itemForFrame(frame); QCOMPARE(item->geometry(), frame->QWidgetAdapter::geometry()); } void TestDocks::tst_restoreWithNativeTitleBar() { #ifdef Q_OS_WIN // Other OS don't support this EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_NativeTitleBar); auto dock0 = createDockWidget("dock0", new MyWidget2(QSize(400, 400))); dock0->window()->move(100, 100); QVERIFY(!dock0->titleBar()->isVisible()); QVERIFY(!dock0->floatingWindow()->titleBar()->isVisible()); QVERIFY(!dock0->d->frame()->titleBar()->isVisible()); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); saver.restoreLayout(saved); QVERIFY(!dock0->titleBar()->isVisible()); QVERIFY(!dock0->floatingWindow()->titleBar()->isVisible()); QVERIFY(!dock0->d->frame()->titleBar()->isVisible()); #endif }