/* This file is part of KDDockWidgets. SPDX-FileCopyrightText: 2019-2022 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. */ /// @file /// @brief Here lives the tests that only apply to QtWidgets /// either because they haven't been ported to QtQuick yet or because they are really /// QtWidgets specific. #include "Platform.h" #include "kddockwidgets/KDDockWidgets.h" #include "../utils.h" #include "Config.h" #include "LayoutSaver.h" #include "LayoutSaver_p.h" #include "WindowBeingDragged_p.h" #include "kddockwidgets/ViewFactory.h" #include "multisplitter/Item_p.h" #include "kddockwidgets/controllers/DropArea.h" #include "kddockwidgets/controllers/Separator.h" #include "kddockwidgets/controllers/Group.h" #include "kddockwidgets/controllers/DockWidget.h" #include "kddockwidgets/controllers/MainWindow.h" #include "kddockwidgets/controllers/SideBar.h" #include "qtwidgets/views/MDIArea_qtwidgets.h" #include "qtwidgets/views/MainWindow_qtwidgets.h" #include "qtwidgets/views/ViewWrapper_qtwidgets.h" #include "qtwidgets/views/DockWidget_qtwidgets.h" #include "Platform.h" #include #include #include #include #include using namespace KDDockWidgets; using namespace KDDockWidgets::Controllers; using namespace KDDockWidgets::Tests; using namespace Layouting; /// Helper function so we don't write such a big line everywhere inline Controllers::DockWidget *newDockWidget(const QString &uniqueName, DockWidgetOptions opts = {}, LayoutSaverOptions layoutSaverOptions = {}) { return Config::self().viewFactory()->createDockWidget(uniqueName, opts, layoutSaverOptions)->asDockWidgetController(); } inline Controllers::DockWidget *createDockWidget(const QString &name, QWidget *w, DockWidgetOptions options = {}, LayoutSaverOptions layoutSaverOptions = {}, bool show = true, const QString &affinityName = {}) { w->setFocusPolicy(Qt::StrongFocus); auto dock = newDockWidget(name, options, layoutSaverOptions); dock->setAffinityName(affinityName); dock->setGuestView(Views::ViewWrapper_qtwidgets::create(w)); dock->setObjectName(name); dock->view()->setGeometry(QRect(0, 0, 400, 400)); if (show) { dock->show(); dock->dptr()->morphIntoFloatingWindow(); dock->view()->activateWindow(); Q_ASSERT(dock->window()); if (Platform::instance()->tests_waitForWindowActive(dock->view()->window(), 1000)) { return dock; } qWarning() << Q_FUNC_INFO << "Couldn't activate window"; return nullptr; } else { return dock; } }; class EmbeddedWindow : public QWidget { public: explicit EmbeddedWindow(Controllers::MainWindow *m) : mainWindow(m) { } ~EmbeddedWindow() override; Controllers::MainWindow *const mainWindow; }; EmbeddedWindow::~EmbeddedWindow() = default; inline EmbeddedWindow *createEmbeddedMainWindow(QSize sz) { static int count = 0; count++; // Tests a MainWindow which isn't a top-level window, but is embedded in another window auto mainwindow = createMainWindow(QSize(600, 600), MainWindowOption_HasCentralFrame).release(); auto window = new EmbeddedWindow(mainwindow); auto lay = new QVBoxLayout(window); lay->setContentsMargins(100, 100, 100, 100); lay->addWidget(dynamic_cast(mainwindow->view())); window->show(); window->resize(sz); return window; } class TestQtWidgets : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase(); void cleanupTestCase(); private Q_SLOTS: // TODO: Port these to QtQuick void tst_mainWindowAlwaysHasCentralWidget(); void tst_dockableMainWindows(); void tst_mdi_mixed_with_docking(); void tst_mdi_mixed_with_docking2(); void tst_mdi_mixed_with_docking_setMDISize(); void tst_deleteOnClose(); void tstCloseNestedMdi(); void tstCloseNestedMDIPropagates(); // But these are fine to be widget only: void tst_tabsNotClickable(); void tst_embeddedMainWindow(); void tst_restoreEmbeddedMainWindow(); void tst_negativeAnchorPositionWhenEmbedded(); void tst_negativeAnchorPositionWhenEmbedded_data(); void tst_closeRemovesFromSideBar(); void tst_restoreSideBar(); void tst_toggleActionOnSideBar(); void tst_deleteOnCloseWhenOnSideBar(); void tst_sidebarOverlayShowsAutohide(); void tst_sidebarOverlayGetsHiddenOnClick(); void tst_floatRemovesFromSideBar(); void tst_overlayedGeometryIsSaved(); void tst_overlayCrash(); // And fix these void tst_floatingWindowDeleted(); void tst_addToSmallMainWindow6(); void tst_minSizeChanges(); void tst_maxSizePropagates(); void tst_maxSizePropagates2(); void tst_maxSizedFloatingWindow(); void tst_restoreResizesLayout(); void tst_restoreNonRelativeFloatingWindowGeometry(); void tst_maxSizeHonouredWhenDropped(); void tst_fixedSizePolicy(); void tst_maxSizeHonouredWhenAnotherDropped(); void tst_addToHiddenMainWindow(); void tst_maximumSizePolicy(); void tst_complex(); void tst_restoreFloatingMaximizedState(); }; void TestQtWidgets::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 group = dock1->dptr()->group(); QCOMPARE(group->currentIndex(), 1); QTest::qWait(500); // wait for window to get proper geometry const QPoint clickPoint = group->tabBar()->mapToGlobal(group->tabBar()->rectForTab(0).center()); QCursor::setPos(clickPoint); // Just for visual debug when needed pressOn(clickPoint, group->tabBar()->view()); releaseOn(clickPoint, group->tabBar()->view()); // WAIT // Uncomment for MANUAL test. Also test by adding Flag_AlwaysShowTabs QCOMPARE(group->currentIndex(), 0); } void TestQtWidgets::tst_mainWindowAlwaysHasCentralWidget() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); QWidget *central = dynamic_cast(m->view())->centralWidget(); auto dropArea = m->dropArea(); QVERIFY(dropArea); QPointer centralFrame = dropArea->centralFrame()->asGroupController(); QVERIFY(central); QVERIFY(dropArea); QCOMPARE(dropArea->count(), 1); QVERIFY(centralFrame); QCOMPARE(centralFrame->dockWidgetCount(), 0); // Add a tab auto dock = createDockWidget("doc1"); 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); auto tabBar = centralFrame->tabBar()->view(); QVERIFY(tabBar); qDebug() << "Detaching tab from dropArea->size=" << dropArea->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()); } void TestQtWidgets::tst_dockableMainWindows() { EnsureTopLevelsDeleted e; auto m1 = createMainWindow(); auto dock1 = createDockWidget("dock1", new QPushButton("foo")); m1->addDockWidget(dock1, Location_OnTop); auto m2 = new KDDockWidgets::Views::MainWindow_qtwidgets("mainwindow-dockable"); auto m2Container = createDockWidget("mainwindow-dw", ( View * )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->mainWindow()->addDockWidget(dock21, Location_OnLeft); m2->mainWindow()->addDockWidget(dock22, Location_OnRight); auto fw = m2Container->floatingWindow(); Controllers::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->view(), 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()->group()->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()->group()->titleBar()->isVisible()); // Put it how it was, FloatingWindow is single dock again auto group1 = dock1->dptr()->group(); dock1->close(); Platform::instance()->tests_waitForDeleted(group1); QVERIFY(fwTitleBar->isVisible()); QVERIFY(!m2Container->dptr()->group()->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()->group()->titleBar()->isVisible()); group1 = dock1->dptr()->group(); group1->titleBar()->onFloatClicked(); QVERIFY(fwTitleBar->isVisible()); QVERIFY(!m2Container->dptr()->group()->titleBar()->isVisible()); fw->dropArea()->addDockWidget(dock1, Location::Location_OnLeft, nullptr); } void TestQtWidgets::tst_mdi_mixed_with_docking() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(500, 500), MainWindowOption_HasCentralWidget); auto dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnBottom); auto mdiArea = new Views::MDIArea_qtwidgets(); m->setPersistentCentralView(Views::ViewWrapper_qtwidgets::create(mdiArea)); auto mdiWidget1 = createDockWidget("mdi1", new QPushButton("mdi1")); auto mdiWidget2 = createDockWidget("mdi2", new QPushButton("mdi12")); mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10)); mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50)); Controllers::Group *groupMDI1 = mdiWidget1->d->group(); Controllers::Group *group1 = dock1->d->group(); QVERIFY(!group1->isMDI()); QVERIFY(groupMDI1->isMDI()); QVERIFY(!group1->mdiLayout()); QVERIFY(groupMDI1->mdiLayout()); QVERIFY(!dock1->titleBar()->isMDI()); auto tb1 = mdiWidget1->titleBar(); QVERIFY(tb1->isMDI()); QVERIFY(Platform::instance()->tests_waitForEvent(tb1->view(), QEvent::Show)); QVERIFY(tb1->isVisible()); // Press the float button tb1->onFloatClicked(); QVERIFY(mdiWidget1->d->lastPosition()->isValid()); QVERIFY(mdiWidget1->titleBar()->isVisible()); QVERIFY(mdiWidget1->isFloating()); // Dock again, and check it went back mdiWidget1->titleBar()->onFloatClicked(); QVERIFY(!mdiWidget1->isFloating()); } void TestQtWidgets::tst_mdi_mixed_with_docking2() { // Here, the MDI dock widgets are themselves main windows which will show drop-indicators. // It will be super nested: MainWindow -> MDI -> MainWindow EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget); auto dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnBottom); auto mdiArea = new Views::MDIArea_qtwidgets(); m->setPersistentCentralView(Views::ViewWrapper_qtwidgets::create(mdiArea)); auto createSheet = [](int id) -> Controllers::DockWidget * { auto dock = newDockWidget(QStringLiteral("dw-sheet-%1").arg(id), DockWidgetOption_MDINestable); auto btn = new QPushButton(QStringLiteral("Sheet %1").arg(id)); dock->setGuestView(Views::ViewWrapper_qtwidgets::create(btn)); dock->setTitle(QStringLiteral("Sheet %1").arg(id)); return dock; }; auto mdiWidget1 = createSheet(1); auto mdiWidget2 = createSheet(2); auto mdiWidget3 = createSheet(3); /*auto mdiWidget4 = createSheet(4); auto mdiWidget5 = createSheet(5); auto mdiWidget6 = createSheet(6);*/ mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10)); mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50)); Controllers::Group *group1 = mdiWidget1->d->group(); Controllers::Group *mdiFrame1 = group1->mdiFrame(); QPointer group2 = mdiWidget2->d->group(); QPointer mdiFrame2 = group2->mdiFrame(); QPointer dropArea2 = group2->mdiDropAreaWrapper(); QPointer dropArea1 = group1->mdiDropAreaWrapper(); dropArea1->addDockWidget(mdiWidget3, Location_OnLeft, nullptr); QVERIFY(!group1->isMDI()); QVERIFY(group1->isMDIWrapper()); QVERIFY(group1->mdiDockWidgetWrapper()); QVERIFY(dropArea1->isMDIWrapper()); QVERIFY(dropArea2->isMDIWrapper()); QVERIFY(!mdiFrame1->isMDIWrapper()); QVERIFY(mdiFrame1->isMDI()); QVERIFY(!mdiWidget1->d->isMDIWrapper()); // Test title bars: auto tb1 = mdiWidget1->titleBar(); auto mdiTb1 = mdiFrame1->titleBar(); auto mdiTb2 = mdiFrame2->titleBar(); Platform::instance()->tests_waitForEvent(mdiTb1, QEvent::Show); QVERIFY(mdiTb1->isVisible()); QVERIFY(mdiTb2->isVisible()); QVERIFY(tb1->isVisible()); QCOMPARE(tb1->title(), QString("Sheet 1")); QCOMPARE(mdiTb1->title(), QString("dockwidgets-unit-tests")); QCOMPARE(mdiTb2->title(), QString("Sheet 2")); QVERIFY(tb1 != mdiTb1); QCOMPARE(mdiWidget2->titleBar(), mdiTb2); // Test that closing will delete the wrappers: mdiWidget2->close(); QVERIFY(!mdiWidget2->isOpen()); Platform::instance()->tests_waitForDeleted(dropArea2); QVERIFY(dropArea2.isNull()); QVERIFY(!mdiFrame2); mdiWidget1->close(); QVERIFY(!mdiWidget1->isOpen()); QTest::qWait(500); // wait some event loops to make sure there's no delete later. (There isn't, but a bug could introduce them) QVERIFY(!dropArea1.isNull()); // Not deleted, as sheet3 is still there QCOMPARE(dropArea1->visibleCount(), 1); QVERIFY(mdiTb1->isVisible()); QCOMPARE(mdiWidget3->titleBar(), mdiTb1); Controllers::Group *group3 = mdiWidget3->d->group(); QVERIFY(!group3->titleBar()->isVisible()); mdiWidget3->close(); QVERIFY(Platform::instance()->tests_waitForDeleted(dropArea1)); QVERIFY(!mdiWidget3->isOpen()); QVERIFY(dropArea1.isNull()); // Reopen everything again: mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10)); mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50)); group1 = mdiWidget1->d->group(); mdiFrame1 = group1->mdiFrame(); dropArea1 = group1->mdiDropAreaWrapper(); dropArea1->addDockWidget(mdiWidget3, Location_OnLeft, nullptr); // Test floating: group2 = mdiWidget2->d->group(); QPointer dwWrapper2 = group2->mdiDockWidgetWrapper(); dropArea2 = group2->mdiDropAreaWrapper(); QVERIFY(mdiWidget2->isVisible()); QVERIFY(group2->isMDIWrapper()); QVERIFY(dwWrapper2->d->isMDIWrapper()); mdiFrame2 = group2->mdiFrame(); mdiWidget2->setFloating(true); QVERIFY(mdiWidget2->isFloating()); QVERIFY(!mdiWidget2->d->group()->isMDI()); QVERIFY(!mdiWidget2->d->group()->isMDIWrapper()); QVERIFY(Platform::instance()->tests_waitForDeleted(mdiFrame2)); QVERIFY(dropArea2.isNull()); QVERIFY(dwWrapper2.isNull()); auto mdiFrames = mdiArea->groups(); QCOMPARE(mdiFrames.count(), 1); mdiFrame1 = mdiFrames.first(); QVERIFY(mdiFrame1->isMDI()); QVERIFY(mdiFrame1->hasNestedMDIDockWidgets()); auto mdiTitleBar1 = mdiFrame1->titleBar(); QVERIFY(mdiFrame1->titleBar()->isVisible()); mdiTitleBar1->makeWindow(); QVERIFY(Platform::instance()->tests_waitForDeleted(mdiFrame1)); QCOMPARE(mdiArea->groups().size(), 0); // Dock again: mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10)); mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50)); group1 = mdiWidget1->d->group(); dropArea1 = group1->mdiDropAreaWrapper(); dropArea1->addDockWidget(mdiWidget3, Location_OnLeft, nullptr); // Detach an internal dock widget by dragging const QPoint globalSrc = mdiWidget1->mapToGlobal(QPoint(5, 5)); const QPoint globalDest = globalSrc + QPoint(100, 100); drag(mdiWidget1->view(), globalDest); QCOMPARE(mdiArea->groups().count(), 2); auto mdiTitleBar = mdiArea->groups().first()->titleBar(); QVERIFY(mdiTitleBar->isVisible()); QVERIFY(!mdiWidget3->isFloating()); QVERIFY(mdiWidget3->d->lastPosition()->isValid()); mdiTitleBar->onFloatClicked(); QVERIFY(mdiWidget3->isFloating()); QVERIFY(Platform::instance()->tests_waitForDeleted(mdiArea->groups().constFirst())); QCOMPARE(mdiArea->groups().size(), 1); QVERIFY(!mdiWidget2->isFloating()); Controllers::Group *lastMdiFrame = mdiArea->groups().constFirst(); QVERIFY(lastMdiFrame->titleBar()->isVisible()); QVERIFY(!lastMdiFrame->titleBar()->isFloating()); lastMdiFrame->titleBar()->onFloatClicked(); QVERIFY(mdiWidget2->isFloating()); // put it in the MDI area again mdiWidget2->titleBar()->onFloatClicked(); QVERIFY(!mdiWidget2->isFloating()); } void TestQtWidgets::tst_mdi_mixed_with_docking_setMDISize() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget); auto dock1 = createDockWidget("1", new QPushButton("1")); m->addDockWidget(dock1, Location_OnBottom); auto mdiArea = new Views::MDIArea_qtwidgets(); m->setPersistentCentralView(Views::ViewWrapper_qtwidgets::create(mdiArea)); auto createSheet = [](int id) -> Controllers::DockWidget * { auto dock = newDockWidget(QStringLiteral("dw-sheet-%1").arg(id), DockWidgetOption_MDINestable); dock->setGuestView(Views::ViewWrapper_qtwidgets::create(new QPushButton(QStringLiteral("Sheet %1").arg(id)))); dock->setTitle(QStringLiteral("Sheet %1").arg(id)); return dock; }; auto mdiWidget1 = createSheet(1); auto mdiWidget2 = createSheet(2); mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10)); mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50)); Controllers::Group *group1 = mdiArea->groups().at(0); const QSize sz1 = group1->view()->size(); const QSize increment(200, 200); QVERIFY(mdiWidget1->d->mdiLayout()); mdiWidget1->setMDISize(sz1 + increment); QCOMPARE(group1->size(), sz1 + increment); } // No need to port to QtQuick void TestQtWidgets::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 nonetheless class MyMainWindow : public KDDockWidgets::Views::MainWindow_qtwidgets { public: MyMainWindow() : KDDockWidgets::Views::MainWindow_qtwidgets("tst_floatingWindowDeleted", MainWindowOption_None) { auto dock1 = newDockWidget(QStringLiteral("DockWidget #1")); auto myWidget = new QWidget(); dock1->setGuestView(Views::ViewWrapper_qtwidgets::create((myWidget))); dock1->view()->resize(QSize(600, 600)); dock1->show(); auto dock2 = newDockWidget(QStringLiteral("DockWidget #2")); myWidget = new QWidget(); dock2->setGuestView(Views::ViewWrapper_qtwidgets::create(myWidget)); dock2->view()->resize(QSize(600, 600)); dock2->show(); dock1->addDockWidgetAsTab(dock2); } }; MyMainWindow m; } void TestQtWidgets::tst_addToSmallMainWindow6() { EnsureTopLevelsDeleted e; // Test test shouldn't spit any warnings QWidget container; auto lay = new QVBoxLayout(&container); auto m = Platform::instance()->createMainWindow("MyMainWindow_tst_addToSmallMainWindow8", {}, MainWindowOption_None); auto qmainwindow = dynamic_cast(m->view()); lay->addWidget(qmainwindow); container.resize(100, 100); Platform::instance()->tests_waitForEvent(&container, QEvent::Resize); container.show(); Platform::instance()->tests_waitForResize(m); auto dock1 = createDockWidget("dock1", Platform::instance()->tests_createView({ true, {}, QSize(50, 240) })); auto dock2 = createDockWidget("dock2", Platform::instance()->tests_createView({ true, {}, QSize(50, 240) })); m->addDockWidget(dock1, KDDockWidgets::Location_OnBottom); m->addDockWidget(dock2, KDDockWidgets::Location_OnBottom); Platform::instance()->tests_waitForResize(m); QVERIFY(m->dropArea()->checkSanity()); delete m; } void TestQtWidgets::tst_closeRemovesFromSideBar() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None); auto dw1 = newDockWidget(QStringLiteral("1")); auto fw1 = dw1->window(); m1->addDockWidget(dw1, Location_OnBottom); m1->moveToSideBar(dw1); QVERIFY(!dw1->isOverlayed()); QVERIFY(!dw1->isVisible()); QVERIFY(dw1->isInSideBar()); Controllers::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); } void TestQtWidgets::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 = newDockWidget(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(); m1.reset(); } EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1"); auto dw1 = newDockWidget(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()); } void TestQtWidgets::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 = newDockWidget("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 TestQtWidgets::tst_deleteOnCloseWhenOnSideBar() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); QPointer dock1 = createDockWidget("dock1", Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }), DockWidgetOption_DeleteOnClose); m->addDockWidget(dock1, Location_OnLeft); dock1->moveToSideBar(); QVERIFY(dock1); QVERIFY(dock1->isInSideBar()); QTest::qWait(500); QVERIFY(dock1); } void TestQtWidgets::tst_sidebarOverlayShowsAutohide() { // Tests that overlayed widgets show the "Disable auto-hide" button EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1"); auto dw1 = newDockWidget(QStringLiteral("1")); m1->addDockWidget(dw1, Location_OnBottom); QVERIFY(dw1->titleBar()->supportsAutoHideButton()); m1->moveToSideBar(dw1); m1->overlayOnSideBar(dw1); QVERIFY(dw1->isOverlayed()); auto titleBar = dw1->titleBar(); QVERIFY(titleBar->isVisible()); QVERIFY(titleBar->supportsAutoHideButton()); delete dw1; } void TestQtWidgets::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 = newDockWidget(QStringLiteral("1")); auto dw2 = newDockWidget(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->view()); QVERIFY(!dw1->isOverlayed()); auto widget2 = Platform::instance()->tests_createView({ true }); dw2->setGuestView(widget2->asWrapper()); m1->overlayOnSideBar(dw1); QVERIFY(dw1->isOverlayed()); Tests::clickOn(widget2->mapToGlobal(widget2->rect().bottomLeft() + QPoint(5, -5)), widget2); QVERIFY(!dw1->isOverlayed()); m1.reset(); 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 = newDockWidget(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->view()->childViewAt(localPt).get()); QVERIFY(!dw1->isOverlayed()); } } void TestQtWidgets::tst_floatRemovesFromSideBar() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1"); auto dw1 = newDockWidget(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 TestQtWidgets::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 = newDockWidget(QStringLiteral("1")); m1->addDockWidget(dw1, Location_OnBottom); m1->moveToSideBar(dw1, SideBarLocation::North); m1->overlayOnSideBar(dw1); Controllers::Group *group = dw1->dptr()->group(); QVERIFY(group->isOverlayed()); QCOMPARE(dw1->sideBarLocation(), SideBarLocation::North); QVERIFY(group->height() > 0); const int newHeight = group->height() + 300; group->view()->setHeight(newHeight); m1->toggleOverlayOnSideBar(dw1); m1->toggleOverlayOnSideBar(dw1); group = dw1->dptr()->group(); QCOMPARE(group->height(), newHeight); } void TestQtWidgets::tst_overlayCrash() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport); auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1"); auto dw1 = newDockWidget(QStringLiteral("1")); m1->addDockWidget(dw1, Location_OnBottom); auto dw2 = newDockWidget(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->view()); } void TestQtWidgets::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, DropLocation_Left); auto layout = dropArea; QVERIFY(Platform::instance()->tests_waitForDeleted(fw)); QCOMPARE(layout->count(), 2); // 2, as it has the central group QCOMPARE(layout->visibleCount(), 2); layout->checkSanity(); delete window; } void TestQtWidgets::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->layout()->checkSanity(); delete window; } void TestQtWidgets::tst_negativeAnchorPositionWhenEmbedded_data() { QTest::addColumn("embedded"); QTest::newRow("false") << false; QTest::newRow("true") << true; } void TestQtWidgets::tst_negativeAnchorPositionWhenEmbedded() { QFETCH(bool, embedded); EnsureTopLevelsDeleted e; MainWindow *m = nullptr; if (embedded) { auto em = createEmbeddedMainWindow(QSize(500, 500)); m = em->mainWindow; } else { m = createMainWindow(QSize(500, 500), MainWindowOption_HasCentralFrame).release(); m->view()->resize(QSize(500, 500)); m->show(); } auto layout = m->multiSplitter(); auto w1 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }); auto w2 = Platform::instance()->tests_createView({ true, {}, 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 static_cast(m->window().get())->widget(); } void TestQtWidgets::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->view()->resize(QSize(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()->size(), layout->layoutSize()); QVERIFY(layout->checkSanity()); } void TestQtWidgets::tst_restoreNonRelativeFloatingWindowGeometry() { // Tests that disabling RelativeFloatingWindowGeometry works EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(500, 500), MainWindowOption_None); auto dock1 = createDockWidget("1", new QPushButton("1")); // Also test that invisible dock doesn't change size auto dock2 = createDockWidget("2", new QPushButton("2"), {}, {}, /*show=*/false); LayoutSaver saver(RestoreOption_RelativeToMainWindow); saver.dptr()->m_restoreOptions.setFlag(InternalRestoreOption::RelativeFloatingWindowGeometry, false); const QByteArray saved = saver.serializeLayout(); const QSize floatingWindowSize = dock1->window()->size(); const QSize floatingWindowSize2 = dock2->window()->size(); m->view()->resize(QSize(m->width() * 2, m->height())); saver.restoreLayout(saved); QVERIFY(dock2->isFloating()); QVERIFY(!dock2->isOpen()); QCOMPARE(dock1->window()->size(), floatingWindowSize); QCOMPARE(dock2->window()->size(), floatingWindowSize2); } void TestQtWidgets::tst_maximumSizePolicy() { EnsureTopLevelsDeleted e; const int maxHeight = 250; auto widget = Platform::instance()->tests_createView({ true, QSize(250, maxHeight), QSize(200, 200) }); widget->setSizePolicy(SizePolicy::Preferred, SizePolicy::Maximum); auto dock1 = createDockWidget("dock1", widget); dock1->show(); dock1->window()->resize(QSize(500, 500)); auto window1 = dock1->window(); dock1->close(); dock1->show(); auto oldFw2 = dock1->window(); const int tolerance = 50; QVERIFY(dock1->window()->height() <= maxHeight + tolerance); // +tolerance 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->view()->resize(QSize(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 + tolerance); } // Now drop it, and check too m1->addDockWidget(dock1, Location_OnBottom); QVERIFY(dock1->height() <= maxHeight); } void TestQtWidgets::tst_minSizeChanges() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(600, 600), MainWindowOption_None); m->show(); auto w1 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }); auto w2 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }); auto d1 = newDockWidget("1"); d1->setGuestView(w1->asWrapper()); auto d2 = newDockWidget("2"); d2->setGuestView(w2->asWrapper()); 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()->group()); QVERIFY(layout->checkSanity()); Platform::instance()->tests_waitForResize(m->view()); 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)); Platform::instance()->tests_waitForResize(m->view()); layout->checkSanity(); QVERIFY(m->height() >= 1600); // add a small one to the middle auto w3 = Platform::instance()->tests_createView({ true, {}, QSize(100, 100) }); auto d3 = newDockWidget("3"); d3->setGuestView(w3->asWrapper()); m->addDockWidget(d3, Location_OnTop, d1); } void TestQtWidgets::tst_maxSizePropagates() { // Tests that the DockWidget gets the min and max size of its guest widget EnsureTopLevelsDeleted e; auto dock1 = newDockWidget("dock1"); auto w = Platform::instance()->tests_createView({ true, {}, QSize(200, 200) }); w->setMinimumSize(QSize(120, 120)); w->setMaximumSize(QSize(500, 500)); dock1->setGuestView(w->asWrapper()); dock1->show(); QCOMPARE(dock1->view()->minSize(), w->minSize()); QCOMPARE(dock1->view()->maximumSize(), w->maximumSize()); w->setMinimumSize(QSize(121, 121)); w->setMaximumSize(QSize(501, 501)); Platform::instance()->tests_waitForEvent(w, QEvent::LayoutRequest); QCOMPARE(dock1->view()->minSize(), w->minSize()); QCOMPARE(dock1->view()->maximumSize(), w->maximumSize()); // Now let's see if our Frame also has proper size-constraints Controllers::Group *group = dock1->dptr()->group(); QCOMPARE(group->view()->maxSizeHint().expandedTo(w->maximumSize()), group->view()->maxSizeHint()); } void TestQtWidgets::tst_maxSizedFloatingWindow() { // Tests that FloatingWindows get a proper max-size, if its dock widget has one EnsureTopLevelsDeleted e; auto dock1 = newDockWidget("dock1"); auto dock2 = newDockWidget("dock2"); auto w = Platform::instance()->tests_createView({ true }); w->setMinimumSize(QSize(120, 100)); w->setMaximumSize(QSize(300, 300)); dock1->setGuestView(w->asWrapper()); dock1->show(); dock2->show(); auto window1 = dock1->window(); auto window2 = dock2->window(); Platform::instance()->tests_waitForEvent(window1.get(), 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); Platform::instance()->tests_waitForEvent(window1.get(), 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(); Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest); QVERIFY(hasMax()); dock1->addDockWidgetAsTab(dock2); Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest); QVERIFY(!hasMax()); dock2->close(); Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest); QVERIFY(hasMax()); } void TestQtWidgets::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 = newDockWidget("dock1"); auto w = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }); w->setMinimumSize(QSize(120, 100)); w->setMaximumSize(QSize(300, 150)); dock1->setGuestView(w->asWrapper()); m1->addDockWidget(dock1, Location_OnLeft); auto dock2 = newDockWidget("dock2"); m1->addDockWidget(dock2, Location_OnBottom); auto root = m1->multiSplitter()->rootItem(); Controllers::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()->group()->view()->maxSizeHint().height(); QVERIFY(dock1->dptr()->group()->view()->height() <= item1MaxHeight); root->dumpLayout(); QCOMPARE(dock2->dptr()->group()->view()->height(), root->height() - item1MaxHeight - Item::separatorThickness); } void TestQtWidgets::tst_addToHiddenMainWindow() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralFrame, {}, false); auto w1 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }); auto w2 = Platform::instance()->tests_createView({ true, {}, 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->layout()->checkSanity(); } void TestQtWidgets::tst_maxSizePropagates2() { EnsureTopLevelsDeleted e; auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None); auto dock1 = newDockWidget("dock1"); auto w = Platform::instance()->tests_createView({ true, {}, QSize(200, 200) }); w->setMinimumSize(QSize(120, 120)); w->setMaximumSize(QSize(300, 500)); dock1->setGuestView(w->asWrapper()); dock1->show(); auto dock2 = newDockWidget("dock2"); auto dock3 = newDockWidget("dock3"); auto dock4 = newDockWidget("dock4"); m1->addDockWidget(dock2, Location_OnLeft); m1->addDockWidget(dock3, Location_OnRight); m1->addDockWidget(dock4, Location_OnBottom, dock3); m1->addDockWidget(dock1, Location_OnLeft, dock4); Controllers::Group *group1 = dock1->dptr()->group(); Layouting::ItemBoxContainer *root = m1->multiSplitter()->rootItem(); Item *item1 = root->itemForView(group1->view()); 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(group1->width() <= group1->view()->maxSizeHint().width()); container1->requestSeparatorMove(innerVertSep1, -(innerVertSep1->position() - minInnerSep)); QVERIFY(group1->width() <= group1->view()->maxSizeHint().width()); container1->requestSeparatorMove(innerVertSep1, maxInnerSep - innerVertSep1->position()); QVERIFY(group1->width() <= group1->view()->maxSizeHint().width()); } void TestQtWidgets::tst_maxSizeHonouredWhenDropped() { EnsureTopLevelsDeleted e; auto m1 = createMainWindow(); auto dock1 = newDockWidget("dock1"); auto dock2 = newDockWidget("dock2"); m1->addDockWidget(dock1, Location_OnTop); m1->view()->resize(QSize(2000, 2000)); auto w2 = Platform::instance()->tests_createView({ true, {}, { 0, 0 } }); dock2->setGuestView(w2->asWrapper()); const int maxWidth = 200; w2->setMaximumSize(QSize(maxWidth, 200)); m1->addDockWidget(dock2, Location_OnLeft); const int droppedWidth = dock2->dptr()->group()->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->view(), Location_OnLeft, nullptr); QCOMPARE(dock2->dptr()->group()->width(), droppedWidth); } void TestQtWidgets::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); Controllers::Group *group = dock1->dptr()->group(); // 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->view()->sizeHint(), button->sizeHint()); QCOMPARE(dock1->view()->verticalSizePolicy(), SizePolicy(button->sizePolicy().verticalPolicy())); QCOMPARE(dock1->view()->horizontalSizePolicy(), SizePolicy(button->sizePolicy().horizontalPolicy())); QCOMPARE(group->view()->maxSizeHint().height(), qMax(buttonMaxHeight, Layouting::Item::hardcodedMinimumSize.height())); } void TestQtWidgets::tst_restoreFloatingMaximizedState() { EnsureTopLevelsDeleted e; KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_TitleBarHasMaximizeButton); auto dock1 = createDockWidget("dock1", Platform::instance()->tests_createView({ true })); const QRect originalNormalGeometry = dock1->floatingWindow()->view()->normalGeometry(); dock1->floatingWindow()->view()->showMaximized(); qDebug() << originalNormalGeometry; QCOMPARE(dock1->floatingWindow()->view()->window()->windowState(), WindowState::Maximized); LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); saver.restoreLayout(saved); QCOMPARE(dock1->floatingWindow()->view()->window()->windowState(), WindowState::Maximized); QCOMPARE(dock1->floatingWindow()->view()->normalGeometry(), originalNormalGeometry); dock1->floatingWindow()->view()->showNormal(); QCOMPARE(dock1->floatingWindow()->view()->normalGeometry(), originalNormalGeometry); } void TestQtWidgets::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->view()->resize(QSize(3266, 2239)); m->show(); Controllers::DockWidget::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 = Platform::instance()->tests_createView({ true, {}, minSizes.at(i) }); auto dw = newDockWidget(QString::number(i)); dw->setGuestView(widget->asWrapper()); 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()->groups()); } void TestQtWidgets::tst_deleteOnClose() { // 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, Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }), DockWidgetOption_DeleteOnClose); }); auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); QPointer dock1 = createDockWidget("1", Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }), DockWidgetOption_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(Platform::instance()->tests_waitForDeleted(dock1)); QVERIFY(!dock1.data()); saver.restoreLayout(saved); } void TestQtWidgets::tstCloseNestedMdi() { // Tests a bug where closing a mdi dock widget would close its main window too EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget); QPointer p = m.get(); auto mdi = new KDDockWidgets::Views::MDIArea_qtwidgets(); m->setPersistentCentralView(mdi->asWrapper()); auto dock1 = new KDDockWidgets::Views::DockWidget_qtwidgets(QStringLiteral("MyDock1")); dock1->setWidget(new QPushButton("1")); mdi->addDockWidget(dock1, {}); dock1->dockWidget()->titleBar()->onCloseClicked(); QVERIFY(p); QVERIFY(m->isVisible()); } void TestQtWidgets::tstCloseNestedMDIPropagates() { auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget); QPointer p = m.get(); auto mdi = new KDDockWidgets::Views::MDIArea_qtwidgets(); m->setPersistentCentralView(mdi->asWrapper()); auto dock1 = new KDDockWidgets::Views::DockWidget_qtwidgets(QStringLiteral("MyDock1")); auto nonClosableWidget = Platform::instance()->tests_createNonClosableView(); dock1->dockWidget()->setGuestView(nonClosableWidget->asWrapper()); mdi->addDockWidget(dock1, {}); auto dock2 = new KDDockWidgets::Views::DockWidget_qtwidgets(QStringLiteral("MyDock2")); auto nonClosableWidget2 = Platform::instance()->tests_createNonClosableView(); dock2->dockWidget()->setGuestView(nonClosableWidget2->asWrapper()); dock2->show(); Platform::instance()->tests_waitForEvent(dock1->controller(), QEvent::Show); QVERIFY(dock1->isVisible()); QVERIFY(dock2->isVisible()); m->close(); QVERIFY(dock2->isVisible()); QVERIFY(dock1->isVisible()); } void TestQtWidgets::initTestCase() { KDDockWidgets::Platform::instance()->installMessageHandler(); } void TestQtWidgets::cleanupTestCase() { KDDockWidgets::Platform::instance()->uninstallMessageHandler(); } int main(int argc, char *argv[]) { // Might be disabled by env var const auto frontends = Platform::instance()->frontendTypes(); if (std::find(frontends.cbegin(), frontends.cend(), FrontendType::QtWidgets) == frontends.cend()) return 0; Platform::tests_initPlatform(argc, argv, FrontendType::QtWidgets); TestQtWidgets test; const int exitCode = QTest::qExec(&test, argc, argv); Platform::tests_deinitPlatform(); return exitCode; } #include "tst_qtwidgets.moc"