/* This file is part of KDDockWidgets. Copyright (C) 2018-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: SĂ©rgio Martins This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // 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 #include "DockWidget.h" #include "MainWindow.h" #include "FloatingWindow_p.h" #include "DockRegistry_p.h" #include "Frame_p.h" #include "DropArea_p.h" #include "TitleBar_p.h" #include "WindowBeingDragged_p.h" #include "Utils_p.h" #include "LayoutSaver.h" #include "TabWidget_p.h" #include "multisplitter/MultiSplitterWidget_p.h" #include "LastPosition_p.h" #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN # include #endif #define STATIC_ANCHOR_LENGTH 1 #define ANCHOR_LENGTH 5 #define WAIT QTest::qWait(5000000); using namespace KDDockWidgets; static bool s_pauseBeforePress = false; // for debugging static bool s_pauseBeforeMove = false; // for debugging #define DEBUGGING_PAUSE_DURATION 5000 // 5 seconds extern quintptr Q_CORE_EXPORT qtHookData[]; struct WidgetResize { int length; Qt::Orientation orientation; QWidget *w; }; typedef QVector WidgetResizes; Q_DECLARE_METATYPE(WidgetResize) struct DockDescriptor { Location loc; int relativeToIndex; QPointer createdDock; }; Q_DECLARE_METATYPE(DockDescriptor) struct MultiSplitterSetup { QSize size; QWidgetList widgets; QWidgetList relativeTos; WidgetResizes widgetResizes; QVector locations; }; Q_DECLARE_METATYPE(MultiSplitterSetup) class EmbeddedWindow : public QWidget { public: explicit EmbeddedWindow(MainWindow *m) : mainWindow(m) { } MainWindow *const mainWindow; }; struct ExpectedAvailableSize // struct for testing MultiSplitterLayout::availableLengthForDrop() { KDDockWidgets::Location location; QWidget *relativeTo; int side1ExpectedSize; int side2ExpectedSize; int totalAvailable; }; typedef QVector ExpectedAvailableSizes; Q_DECLARE_METATYPE(ExpectedAvailableSize) struct ExpectedRectForDrop // struct for testing MultiSplitterLayout::availableLengthForDrop() { QWidget *widgetToDrop; KDDockWidgets::Location location; Frame *relativeTo; QRect expectedRect; }; typedef QVector ExpectedRectsForDrop; Q_DECLARE_METATYPE(ExpectedRectForDrop) 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 int osWindowMinHeight() { #ifdef Q_OS_WIN return GetSystemMetrics(SM_CYMIN); #else return 100; // Some random value for our windows. It's only important on Windows #endif }*/ namespace KDDockWidgets { enum ButtonAction { ButtonAction_None, ButtonAction_Press = 1, ButtonAction_Release = 2 }; Q_DECLARE_FLAGS(ButtonActions, ButtonAction) static QtMessageHandler s_original = nullptr; static bool isGammaray() { static bool is = qtHookData[3] != 0; return is; } namespace { class EventFilter : public QObject { public: EventFilter() {} bool eventFilter(QObject *, QEvent *e) { if (e->type() == QEvent::Resize) m_gotResize = true; return false; } bool m_gotResize = false; }; class WidgetWithMinSize : public QWidget { public: WidgetWithMinSize(QSize minSize) { m_minSize = minSize; } QSize minimumSizeHint() const override { return m_minSize; } QSize m_minSize; }; } static QWidget *createWidget(int minLength, const QString &objname = QString()) { auto w = new WidgetWithMinSize(QSize(minLength, minLength)); w->setObjectName(objname); return w; } void fatalWarningsMessageHandler(QtMsgType t, const QMessageLogContext &context, const QString &msg) { s_original(t, context, msg); if (t == QtWarningMsg) { if (QLatin1String(context.category) == QLatin1String("qt.qpa.xcb")) return; if (msg.contains(QLatin1String("QSocketNotifier: Invalid socket")) || msg.contains(QLatin1String("QWindowsWindow::setGeometry")) || msg.contains(QLatin1String("This plugin does not support")) || msg.contains(QLatin1String("Note that Qt no longer ships fonts")) || msg.contains(QLatin1String("Another dock KDDockWidgets::DockWidget"))) return; if (!isGammaray() && !qEnvironmentVariableIsSet("NO_FATAL")) qFatal("Got a warning, category=%s", context.category); } } struct EnsureTopLevelsDeleted { EnsureTopLevelsDeleted() : m_initialNumWindows(qApp->topLevelWidgets().size()) { } ~EnsureTopLevelsDeleted() { if (qApp->topLevelWidgets().size() != m_initialNumWindows) { qWarning() << "There's still top-level widgets present!" << qApp->topLevelWidgets() << m_initialNumWindows; } } const int m_initialNumWindows; }; class TestDocks : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase() { qputenv("KDDOCKWIDGETS_SHOW_DEBUG_WINDOW", ""); qApp->setOrganizationName(QStringLiteral("KDAB")); qApp->setApplicationName(QStringLiteral("dockwidgets-unit-tests")); s_original = qInstallMessageHandler(fatalWarningsMessageHandler); } public: static void nestDockWidget(DockWidget *dock, DropArea *dropArea, Frame *relativeTo, KDDockWidgets::Location location); private Q_SLOTS: void tst_simple1(); void tst_simple2(); void tst_shutdown(); void tst_mainWindowAlwaysHasCentralWidget(); void tst_createFloatingWindow(); void tst_dock2FloatingWidgetsTabbed(); void tst_close(); void tst_closeAllDockWidgets(); void tst_dockDockWidgetNested(); void tst_dockFloatingWindowNested(); void tst_anchorsFromTo(); void tst_dockWindowWithTwoSideBySideFramesIntoCenter(); void tst_dockWindowWithTwoSideBySideFramesIntoLeft(); void tst_dockWindowWithTwoSideBySideFramesIntoRight(); void tst_posAfterLeftDetach(); void tst_propagateMinSize(); void tst_dockInternal(); void tst_propagateSizeHonoursMinSize(); void tst_addDockWidgetAsTabToDockWidget(); void tst_addDockWidgetToMainWindow(); // Tests MainWindow::addDockWidget(); void tst_addDockWidgetToContainingWindow(); void tst_addToSmallMainWindow(); void tst_fairResizeAfterRemoveWidget(); void tst_notClosable(); void tst_maximizeAndRestore(); void tst_propagateResize2(); void tst_availableLengthForDrop_data(); void tst_availableLengthForDrop(); void tst_constraintsAfterPlaceholder(); void tst_rectForDrop_data(); void tst_rectForDrop(); void tst_crash(); // tests some crash I got void tst_setFloatingWhenWasTabbed(); void tst_setFloatingWhenSideBySide(); void tst_setVisibleFalseWhenSideBySide(); void tst_refUnrefItem(); void tst_addAndReadd(); void tst_placeholderCount(); void tst_availableLengthForOrientation(); void tst_setAstCurrentTab(); void tst_closeShowWhenNoCentralFrame(); void tst_placeholderDisappearsOnReadd(); void tst_placeholdersAreRemovedPropertly(); void tst_embeddedMainWindow(); void tst_toggleMiddleDockCrash(); // tests some crash I got void tst_28NestedWidgets(); void tst_28NestedWidgets_data(); void tst_invalidPlaceholderPosition_data(); void tst_invalidPlaceholderPosition(); void tst_invalidAnchorGroup(); void tst_resizeViaAnchorsAfterPlaceholderCreation(); void tst_negativeAnchorPosition(); private: void tst_restoreEmpty(); // TODO. Disabled for now, save/restore needs to support placeholders void tst_restoreCrash(); // TODO. Disabled for now, save/restore needs to support placeholders std::unique_ptr createMultiSplitterFromSetup(MultiSplitterSetup setup, QHash &frameMap) const; }; } static std::unique_ptr createMainWindow(QSize sz = {600, 600}, MainWindowOptions options = MainWindowOption_HasCentralFrame) { auto ptr = std::unique_ptr(new MainWindow(QStringLiteral("MyMainWindow"), options)); ptr->show(); ptr->resize(sz); return ptr; } static EmbeddedWindow *createEmbeddedMainWindow(QSize sz) { // Tests a MainWindow which isn't a top-level window, but is embedded in another window auto mainwindow = new MainWindow(QStringLiteral("MyMainWindow")); auto window = new EmbeddedWindow(mainwindow); auto lay = new QVBoxLayout(window); lay->setContentsMargins(100, 100, 100, 100); lay->addWidget(mainwindow); window->show(); window->resize(sz); return window; } namespace { class MyWidget : public QWidget { public: explicit MyWidget(const QString &, QColor c) : QWidget() , c(c) { qDebug() << "MyWidget" << this; } ~MyWidget() override { qDebug() << "~MyWidget" << this; } void paintEvent(QPaintEvent *) override { QPainter p(this); p.fillRect(rect(), c); } QColor c; }; class MyWidget2 : public QWidget { public: explicit MyWidget2(QSize minSz = QSize(1,1)) : m_minSz(minSz) { } QSize sizeHint() const override { return m_minSz; } QSize minimumSizeHint() const override { return m_minSz; } QSize m_minSz; }; } bool waitForDeleted(QObject *o, int timeout = 2000) { if (!o) return true; QPointer ptr = o; QTime time; time.start(); while (ptr && time.elapsed() < timeout) { qApp->processEvents(); QTest::qWait(50); } const bool wasDeleted = !ptr; return wasDeleted; } bool waitForResize(QWidget *w, int timeout = 2000) { EventFilter filter; w->installEventFilter(&filter); QTime time; time.start(); while (!filter.m_gotResize && time.elapsed() < timeout) { qApp->processEvents(); QTest::qWait(50); } return filter.m_gotResize; } static QTabBar *tabBarForFrame(Frame *f) { return f->findChild(QString(), Qt::FindChildrenRecursively); } void moveMouseTo(QPoint globalDest, QWidget *receiver) { QPoint globalSrc(receiver->mapToGlobal(QPoint(5, 5))); QPointer receiverP = receiver; while (globalSrc != globalDest) { if (globalSrc.x() < globalDest.x()) { globalSrc.setX(globalSrc.x() + 1); } else if (globalSrc.x() > globalDest.x()) { globalSrc.setX(globalSrc.x() - 1); } if (globalSrc.y() < globalDest.y()) { globalSrc.setY(globalSrc.y() + 1); } else if (globalSrc.y() > globalDest.y()) { globalSrc.setY(globalSrc.y() - 1); } QCursor::setPos(globalSrc); // Since some code uses QCursor::pos() QMouseEvent ev(QEvent::MouseMove, receiver->mapFromGlobal(globalSrc), receiver->window()->mapFromGlobal(globalSrc), globalSrc, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); if (!receiverP) { qWarning() << "Receiver was deleted"; return; } qApp->sendEvent(receiver, &ev); QTest::qWait(2); } } static void pressOn(QPoint globalPos, QWidget *receiver) { QCursor::setPos(globalPos); QMouseEvent ev(QEvent::MouseButtonPress, receiver->mapFromGlobal(globalPos), receiver->window()->mapFromGlobal(globalPos), globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); qApp->sendEvent(receiver, &ev); } void releaseOn(QPoint globalPos, QWidget *receiver) { QMouseEvent ev(QEvent::MouseButtonRelease, receiver->mapFromGlobal(globalPos), receiver->window()->mapFromGlobal(globalPos), globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); qApp->sendEvent(receiver, &ev); } static void drag(QWidget *sourceWidget, QPoint pressGlobalPos, QPoint globalDest, ButtonActions buttonActions = ButtonActions(ButtonAction_Press) | ButtonAction_Release) { if (buttonActions & ButtonAction_Press) { if (s_pauseBeforePress) QTest::qWait(DEBUGGING_PAUSE_DURATION); pressOn(pressGlobalPos, sourceWidget); } sourceWidget->window()->activateWindow(); if (s_pauseBeforeMove) QTest::qWait(DEBUGGING_PAUSE_DURATION); qDebug() << "Moving sourceWidget to" << globalDest << "; sourceWidget->size=" << sourceWidget->size(); moveMouseTo(globalDest, sourceWidget); qDebug() << "Arrived at" << QCursor::pos(); pressGlobalPos = sourceWidget->mapToGlobal(QPoint(10, 10)); if (buttonActions & ButtonAction_Release) releaseOn(globalDest, sourceWidget); } static void drag(QWidget *sourceWidget, QPoint globalDest, ButtonActions buttonActions = ButtonActions(ButtonAction_Press) | ButtonAction_Release) { Q_ASSERT(sourceWidget && sourceWidget->isVisible()); TitleBar *titleBar = nullptr; if (auto dock = qobject_cast(sourceWidget)) { titleBar = dock->titleBar(); if (!titleBar->isVisible()) { if (auto frame = dock->frame()) { titleBar = frame->titleBar(); } } } else if (auto fw = qobject_cast(sourceWidget)) { titleBar = fw->titleBar(); } Q_ASSERT(titleBar && titleBar->isVisible()); const QPoint pressGlobalPos = titleBar->mapToGlobal(QPoint(6, 6)); drag(titleBar, pressGlobalPos, globalDest, buttonActions); } static void dragFloatingWindowTo(FloatingWindow *fw, QPoint globalDest, ButtonActions buttonActions = ButtonActions(ButtonAction_Press) | ButtonAction_Release) { auto sourceTitleBar = fw->actualTitleBar(); Q_ASSERT(sourceTitleBar && sourceTitleBar->isVisible()); drag(sourceTitleBar, sourceTitleBar->mapToGlobal(QPoint(10, 10)), globalDest, buttonActions); } static void dragFloatingWindowTo(FloatingWindow *fw, DropArea *target, DropIndicatorOverlayInterface::DropLocation dropLocation) { auto sourceTitleBar = fw->actualTitleBar(); // First we drag over it, so the drop indicators appear: drag(sourceTitleBar, sourceTitleBar->mapToGlobal(QPoint(10, 10)), target->window()->mapToGlobal(QPoint(50, 50)), ButtonAction_Press); // Now we drag over the drop indicator and only then release mouse: DropIndicatorOverlayInterface *dropIndicatorOverlay = target->dropIndicatorOverlay(); const QPoint dropPoint = dropIndicatorOverlay->posForIndicator(dropLocation); drag(sourceTitleBar, QPoint(), dropPoint, ButtonAction_Release); } DockWidget *createDockWidget(const QString &name, QWidget *w, DockWidget::Options options = {}) { auto dock = new DockWidget(name, options); dock->setWidget(w); dock->setObjectName(name); dock->setGeometry(0, 0, 400, 400); dock->show(); dock->activateWindow(); Q_ASSERT(dock->window()); Q_ASSERT(dock->windowHandle()); if (QTest::qWaitForWindowActive(dock->window()->windowHandle(), 200)) { qDebug() << dock->window(); return dock; } return nullptr; }; Frame* createFrameWithWidget(const QString &name, MultiSplitterWidget *parent, int minLength = -1) { QWidget *w = createWidget(minLength, name); auto dw = new DockWidget(name); dw->setWidget(w); auto frame = new Frame(parent); frame->addWidget(dw); return frame; } DockWidget *createDockWidget(const QString &name, QColor color) { return createDockWidget(name, new MyWidget(name, color)); }; FloatingWindow *createFloatingWindow() { static int count = 0; count++; auto dock = createDockWidget(QStringLiteral("docfw %1").arg(count), Qt::green); return dock->morphIntoFloatingWindow(); } void TestDocks::tst_createFloatingWindow() { EnsureTopLevelsDeleted e; auto dock = createDockWidget(QStringLiteral("doc1"), Qt::green); QVERIFY(dock); QVERIFY(dock->isFloating()); QVERIFY(dock->name() == QLatin1String("doc1")); // 1.0 objectName() is inherited QCOMPARE(dock->window(), dock); dock->morphIntoFloatingWindow(); QPointer window = qobject_cast(dock->window()); QVERIFY(window); // 1.1 DockWidget creates a FloatingWindow and is reparented QVERIFY(window->dropArea()->checkSanity()); delete dock; QVERIFY(waitForDeleted(window)); // 1.2 Floating Window is destroyed when DockWidget is destroyed QVERIFY(!window); } void TestDocks::nestDockWidget(DockWidget *dock, DropArea *dropArea, Frame *relativeTo, KDDockWidgets::Location location) { auto frame = new Frame(); frame->addWidget(dock); dock->frame()->setObjectName(dock->objectName()); qDebug() << "Adding widget" << frame << "; min width=" << widgetMinLength(frame, Qt::Vertical) << "; min height=" << widgetMinLength(frame, Qt::Horizontal); dropArea->multiSplitter()->addWidget(frame, location, relativeTo); QVERIFY(dropArea->checkSanity()); qDebug() << "Size after adding: " << frame->size(); } DockWidget *createAndNestDockWidget(DropArea *dropArea, Frame *relativeTo, KDDockWidgets::Location location) { static int count = 0; count++; const QString name = QStringLiteral("dock%1").arg(count); auto dock = createDockWidget(name, Qt::red); dock->setObjectName(name); TestDocks::nestDockWidget(dock, dropArea, relativeTo, location); dropArea->checkSanity(); return dock; } std::unique_ptr createSimpleNestedMainWindow(DockWidget * *centralDock, DockWidget * *leftDock, DockWidget * *rightDock) { auto window = createMainWindow({900, 500}); *centralDock = createDockWidget(QStringLiteral("centralDock"), Qt::green); window->addDockWidgetAsTab(*centralDock); QWidget *central = window->centralWidget(); auto dropArea = qobject_cast(central); *leftDock = createAndNestDockWidget(dropArea, nullptr, KDDockWidgets::Location_OnLeft); *rightDock = createAndNestDockWidget(dropArea, nullptr, KDDockWidgets::Location_OnRight); return window; } void TestDocks::tst_dock2FloatingWidgetsTabbed() { EnsureTopLevelsDeleted e; if (KDDockWidgets::supportsNativeTitleBar()) return; // Unit-tests can't drag via tab, yet auto dock1 = createDockWidget(QStringLiteral("doc1"), Qt::green); dock1->window()->setGeometry(500, 500, 400, 400); QVERIFY(dock1); QPointer frame1 = dock1->frame(); QVERIFY(!frame1); // Doesn't have frame yet auto titlebar1 = dock1->titleBar(); auto dock2 = createDockWidget(QStringLiteral("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->frame(); if (!qobject_cast(dock2->window())) { qWarning() << "dock2->window()=" << dock2->window(); 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(waitForDeleted(frame1)); // 2.3 Detach tab1 to empty space QPoint globalPressPos = frame2->dragPointForWidget(0); QTabBar *tabBar = tabBarForFrame(frame2); QVERIFY(tabBar); drag(tabBar, globalPressPos, frame2->window()->geometry().bottomRight() + QPoint(10, 10)); QVERIFY(frame2->dockWidgetCount() == 1); QVERIFY(qobject_cast(dock1->window())); // 2.4 Drag the first dock over the second frame1 = dock1->frame(); frame2 = dock2->frame(); drag(dock1, frame1->mapToGlobal(QPoint(10, 10)), dock2->window()->geometry().center()); QCOMPARE(frame2->dockWidgetCount(), 2); // 2.5 Detach and drop to the same place, should tab again globalPressPos = frame2->dragPointForWidget(0); tabBar = tabBarForFrame(frame2); drag(tabBar, globalPressPos, dock2->window()->geometry().center()); QCOMPARE(frame2->dockWidgetCount(), 2); // 2.6 Drag the tabbed group over a 3rd floating window auto dock3 = createDockWidget(QStringLiteral("doc3"), Qt::black); QTest::qWait(1000); // Test is flaky otherwise drag(frame2->window(), frame2->mapToGlobal(QPoint(10, 10)), dock3->window()->geometry().center()); QVERIFY(waitForDeleted(frame1)); QVERIFY(waitForDeleted(frame2)); QVERIFY(dock3->frame()); QCOMPARE(dock3->frame()->dockWidgetCount(), 3); auto fw3 = qobject_cast(dock3->window()); QVERIFY(fw3); QVERIFY(fw3->dropArea()->checkSanity()); // 2.7 Drop the window into a MainWindow { MainWindow m(QStringLiteral("MyMainWindow")); m.show(); m.setGeometry(500, 300, 300, 300); QVERIFY(!dock3->isFloating()); drag(dock3->window(), dock3->window()->mapToGlobal(QPoint(10, 10)), m.geometry().center()); QVERIFY(!dock3->isFloating()); QVERIFY(qobject_cast(dock3->window()) == &m); QCOMPARE(dock3->frame()->dockWidgetCount(), 3); QVERIFY(qobject_cast(m.centralWidget())->checkSanity()); delete dock1; delete dock2; delete dock3; QVERIFY(waitForDeleted(frame2)); QVERIFY(waitForDeleted(fw3)); } } void TestDocks::tst_close() { EnsureTopLevelsDeleted e; // 1.0 Call QWidget::close() on QDockWidget auto dock1 = createDockWidget(QStringLiteral("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(); QVERIFY(toggleAction->isChecked()); QVERIFY(dock1->isVisible()); QCOMPARE(dock1->window(), dock1); 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 dock1->hide(); 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->morphIntoFloatingWindow(); QPointer frame1 = dock1->frame(); QVERIFY(dock1->isVisible()); QVERIFY(dock1->window()->isVisible()); QVERIFY(frame1->isVisible()); QCOMPARE(dock1->window(), window.data()); QVERIFY(dock1->close()); QVERIFY(!dock1->frame()); QVERIFY(waitForDeleted(frame1)); QVERIFY(waitForDeleted(window)); // 1.5 close a FloatingWindow, via FloatingWindow::close dock1->show(); QCOMPARE(dock1->window(), dock1); window = dock1->morphIntoFloatingWindow(); frame1 = dock1->frame(); QVERIFY(dock1->isVisible()); QVERIFY(dock1->window()->isVisible()); QVERIFY(frame1->isVisible()); QCOMPARE(dock1->window(), window.data()); QVERIFY(window->close()); QVERIFY(!dock1->frame()); QVERIFY(waitForDeleted(frame1)); QVERIFY(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 DockWidget *centralDock; DockWidget *leftDock; DockWidget *rightDock; auto mainwindow = createSimpleNestedMainWindow(¢ralDock, &leftDock, &rightDock); auto da = qobject_cast(mainwindow->centralWidget()); QVERIFY(da->checkSanity()); QCOMPARE(leftDock->frame()->x(), STATIC_ANCHOR_LENGTH); // 1 = static anchor thickness QCOMPARE(centralDock->frame()->x(), leftDock->frame()->geometry().right() + ANCHOR_LENGTH + 1); QCOMPARE(rightDock->frame()->x(), centralDock->frame()->geometry().right() + ANCHOR_LENGTH + 1); leftDock->close(); QTest::qWait(250); // TODO: wait for some signal QCOMPARE(centralDock->frame()->x(), STATIC_ANCHOR_LENGTH); QCOMPARE(rightDock->frame()->x(), centralDock->frame()->geometry().right() + ANCHOR_LENGTH + 1); rightDock->close(); QTest::qWait(250); // TODO: wait for some signal QCOMPARE(centralDock->frame()->width(), mainwindow->width() - STATIC_ANCHOR_LENGTH*2); 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->frame()->x(); const int rightX = rightDock->frame()->x(); centralDock->close(); QCOMPARE(leftDock->frame()->x(), leftX); QCOMPARE(rightDock->frame()->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(QStringLiteral("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(QStringLiteral("hello"), Qt::green); m->addDockWidget(dock1, Location_OnLeft); // 2.2 Closing should not close the main window dock1->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(QStringLiteral("hello"), Qt::green); m->addDockWidget(dock1, Location_OnLeft); // 2.2 Closing should not close the main window dock1->frame()->titleBar()->onCloseClicked(); QVERIFY(mainWindowPtr.data()); QVERIFY(mainWindowPtr->isVisible()); delete dock1; } } 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_anchorsFromTo() { EnsureTopLevelsDeleted e; DockWidget *centralDock; DockWidget *leftDock; DockWidget *rightDock; auto mainwindow = createSimpleNestedMainWindow(¢ralDock, &leftDock, &rightDock); auto dropArea = qobject_cast(mainwindow->centralWidget()); QVERIFY(dropArea->checkSanity()); auto nonStaticAnchors = dropArea->nonStaticAnchors(); AnchorGroup staticAnchors = dropArea->multiSplitter()->staticAnchorGroup(); QVERIFY(staticAnchors.isValid()); QCOMPARE(nonStaticAnchors.size(), 2); for (Anchor *anchor : nonStaticAnchors) { QCOMPARE(anchor->orientation(), Qt::Vertical); QCOMPARE(anchor->from(), staticAnchors.top); QCOMPARE(anchor->to(), staticAnchors.bottom); } qDebug() << "Adding the bottom one"; QVERIFY(dropArea->checkSanity()); DockWidget *bottom = createAndNestDockWidget(dropArea, nullptr, KDDockWidgets::Location_OnBottom); QVERIFY(dropArea->checkSanity()); nonStaticAnchors = dropArea->nonStaticAnchors(); auto horizAnchors = dropArea->multiSplitter()->anchors(Qt::Horizontal); auto vertAnchors = dropArea->multiSplitter()->anchors(Qt::Vertical); QCOMPARE(nonStaticAnchors.size(), 3); QCOMPARE(horizAnchors.size(), 1); QCOMPARE(vertAnchors.size(), 2); for (Anchor *anchor : horizAnchors) { QCOMPARE(anchor->orientation(), Qt::Horizontal); QCOMPARE(anchor->from(), staticAnchors.left); QCOMPARE(anchor->to(), staticAnchors.right); } for (Anchor *anchor : vertAnchors) { QCOMPARE(anchor->orientation(), Qt::Vertical); QCOMPARE(anchor->from(), staticAnchors.top); QCOMPARE(anchor->to(), horizAnchors.at(0)); } // Float bottom, check if horizontal anchor is deleted, and from/to updated Anchor *follower = horizAnchors.at(0); auto window = bottom->frame()->titleBar()->makeWindow(); QVERIFY(dropArea->checkSanity()); QVERIFY(qobject_cast(window->window())); QVERIFY(follower->isFollowing()); nonStaticAnchors = dropArea->nonStaticAnchors(); horizAnchors = dropArea->multiSplitter()->anchors(Qt::Horizontal, false, false); vertAnchors = dropArea->multiSplitter()->anchors(Qt::Vertical, false, false); QCOMPARE(nonStaticAnchors.size(), 2); QCOMPARE(horizAnchors.size(), 0); QCOMPARE(vertAnchors.size(), 2); for (Anchor *anchor : qAsConst(vertAnchors)) { QCOMPARE(anchor->orientation(), Qt::Vertical); if (!anchor->isValid()) { qDebug() << "anchors:" << anchor->to() << anchor->from(); QVERIFY(false); } QCOMPARE(anchor->from(), staticAnchors.top); QCOMPARE(anchor->to(), follower); } mainwindow.reset(); delete window->window(); { // Test a case where the to wasn't correct auto m = createMainWindow({400, 400}); DropArea *dropArea = qobject_cast(m->centralWidget()); auto dock = createAndNestDockWidget(dropArea, nullptr, KDDockWidgets::Location_OnRight); createAndNestDockWidget(dropArea, dock->frame(), KDDockWidgets::Location_OnBottom); const auto anchors = dropArea->nonStaticAnchors(); QCOMPARE(anchors.size(), 2); QCOMPARE(anchors[1]->orientation(), Qt::Horizontal); QCOMPARE(anchors[1]->to()->objectName(), QStringLiteral("right")); QCOMPARE(anchors[1]->from(), anchors[0]); } } void TestDocks::tst_dockWindowWithTwoSideBySideFramesIntoCenter() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto fw = createFloatingWindow(); auto dock2 = createDockWidget(QStringLiteral("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(waitForDeleted(fw)); delete fw2; } void TestDocks::tst_dockWindowWithTwoSideBySideFramesIntoLeft() { EnsureTopLevelsDeleted e; auto fw = createFloatingWindow(); fw->setObjectName(QStringLiteral("fw1")); auto dock2 = createDockWidget(QStringLiteral("doc2"), Qt::red); nestDockWidget(dock2, fw->dropArea(), nullptr, KDDockWidgets::Location_OnLeft); QCOMPARE(fw->frames().size(), 2); auto fw2 = createFloatingWindow(); fw2->setObjectName(QStringLiteral("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); auto anchors = fw2->dropArea()->nonStaticAnchors(); QCOMPARE(anchors.size(), 2); QCOMPARE(anchors[0]->orientation(), Qt::Vertical); QCOMPARE(anchors[1]->orientation(), Qt::Vertical); QCOMPARE(anchors[0]->from()->objectName(), QStringLiteral("top")); QCOMPARE(anchors[0]->to()->objectName(), QStringLiteral("bottom")); QCOMPARE(anchors[1]->from()->objectName(), QStringLiteral("top")); QCOMPARE(anchors[1]->to()->objectName(), QStringLiteral("bottom")); QVERIFY(anchors[1]->position() < anchors[0]->position()); fw2->dropArea()->debug_updateItemNamesForGammaray(); QVERIFY(fw2->dropArea()->checkSanity()); fw2->deleteLater(); waitForDeleted(fw2); } void TestDocks::tst_dockWindowWithTwoSideBySideFramesIntoRight() { EnsureTopLevelsDeleted e; auto fw = createFloatingWindow(); auto dock2 = createDockWidget(QStringLiteral("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); auto anchors = fw2->dropArea()->nonStaticAnchors(); QCOMPARE(anchors.size(), 2); QCOMPARE(anchors[0]->orientation(), Qt::Vertical); QCOMPARE(anchors[1]->orientation(), Qt::Horizontal); QCOMPARE(anchors[0]->from()->objectName(), QStringLiteral("top")); QCOMPARE(anchors[0]->to()->objectName(), QStringLiteral("bottom")); QCOMPARE(anchors[1]->from(), anchors[0]); QCOMPARE(anchors[1]->to()->objectName(), QStringLiteral("right")); QVERIFY(anchors[1]->position() > 0); QVERIFY(anchors[1]->position() < fw2->height()); QVERIFY(fw2->dropArea()->checkSanity()); fw2->deleteLater(); waitForDeleted(fw2); } void TestDocks::tst_posAfterLeftDetach() { { EnsureTopLevelsDeleted e; auto fw = createFloatingWindow(); auto dock2 = createDockWidget(QStringLiteral("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(); waitForDeleted(fw); } { EnsureTopLevelsDeleted e; auto fw = createFloatingWindow(); auto dock2 = createDockWidget(QStringLiteral("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->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(); waitForDeleted(fw); } } void TestDocks::tst_shutdown() { EnsureTopLevelsDeleted e; auto dock = createDockWidget(QStringLiteral("doc1"), Qt::green); auto m = createMainWindow(); m->show(); QVERIFY(QTest::qWaitForWindowActive(m->windowHandle())); delete dock; } void TestDocks::tst_mainWindowAlwaysHasCentralWidget() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); QVERIFY(dropArea); QPointer centralFrame = static_cast(dropArea->centralFrame()->frame()); QVERIFY(central); QVERIFY(dropArea); QCOMPARE(dropArea->count(), 1); QVERIFY(centralFrame); QCOMPARE(centralFrame->dockWidgetCount(), 0); // Add a tab auto dock = createDockWidget(QStringLiteral("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 = centralFrame->dragPointForWidget(0); QTabBar *tabBar = tabBarForFrame(centralFrame); 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()); delete dock->window(); } void TestDocks::tst_propagateMinSize() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); auto dock3 = createDockWidget(QStringLiteral("dock3"), new QPushButton(QStringLiteral("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_dockInternal() { /** * Here we dock relative to an existing widget, and not to the drop-area. */ EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); Frame *centralWidget = dropArea->multiSplitter()->items()[0]->frame(); nestDockWidget(dock1, dropArea, centralWidget, KDDockWidgets::Location_OnRight); QVERIFY(dock1->width() < dropArea->width() - centralWidget->width()); } void TestDocks::tst_closeAllDockWidgets() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("one"))); auto dock3 = createDockWidget(QStringLiteral("dock3"), new QPushButton(QStringLiteral("one"))); auto dock4 = createDockWidget(QStringLiteral("dock4"), new QPushButton(QStringLiteral("one"))); auto dock5 = createDockWidget(QStringLiteral("dock5"), new QPushButton(QStringLiteral("one"))); auto dock6 = createDockWidget(QStringLiteral("dock6"), new QPushButton(QStringLiteral("one"))); QPointer fw = dock3->morphIntoFloatingWindow(); qDebug() << "Nesting1"; nestDockWidget(dock4, dropArea, nullptr, KDDockWidgets::Location_OnRight); qDebug() << "Nesting2"; nestDockWidget(dock5, dropArea, nullptr, KDDockWidgets::Location_OnTop); qDebug() << "Nesting3 fw size is" << fw->dropArea()->size(); const int oldFWHeight = fw->height(); nestDockWidget(dock6, fw->dropArea(), nullptr, KDDockWidgets::Location_OnTop); QVERIFY(oldFWHeight <= fw->height()); qDebug() << "Nesting done"; QCOMPARE(fw->frames().size(), 2); QCOMPARE(dock1->window(), dock1); QCOMPARE(dock2->window(), dock2); QCOMPARE(dock3->window(), fw.data()); QCOMPARE(dock4->window(), m.get()); QCOMPARE(dock5->window(), m.get()); QCOMPARE(dock6->window(), fw.data()); qDebug() << "closeAllDockWidgets"; DockRegistry::self()->closeAllDockWidgets(); qDebug() << "closeAllDockWidgets done"; 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_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(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); int min1 = widgetMinLength(dock1, Qt::Vertical); int min2 = widgetMinLength(dock2, Qt::Vertical); 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::Vertical); min2 = widgetMinLength(dock2, Qt::Vertical); if (dock1->width() < min1) { qDebug() << "\ndock1->width()=" << dock1->width() << "\nmin1=" << min1 << "\ndock min sizes=" << dock1->minimumWidth() << dock1->minimumSizeHint().width() << "\nframe1->width()=" << dock1->frame()->width() << "\nframe1->min=" << widgetMinLength(dock1->frame(), Qt::Vertical); QVERIFY(false); } QVERIFY(dock2->width() >= min2); // Dock on top of center widget: m = createMainWindow(); dock1 = createDockWidget(QStringLiteral("one"), new QTextEdit()); m->addDockWidgetAsTab(dock1); auto dock3 = createDockWidget(QStringLiteral("three"), new QTextEdit()); m->addDockWidget(dock3, Location_OnTop); QVERIFY(qobject_cast(m->centralWidget())->checkSanity()); min1 = widgetMinLength(dock1, Qt::Horizontal); QVERIFY(dock1->height() >= min1); } void TestDocks::tst_restoreEmpty() { EnsureTopLevelsDeleted e; // Create a main window, with a left dock, save it to disk. auto m = createMainWindow(); LayoutSaver saver; QVERIFY(saver.saveToDisk()); saver.restoreFromDisk(); } void TestDocks::tst_restoreCrash() { EnsureTopLevelsDeleted e; { // Create a main window, with a left dock, save it to disk. auto m = createMainWindow(); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); nestDockWidget(dock1, dropArea, nullptr, KDDockWidgets::Location_OnLeft); LayoutSaver saver; QVERIFY(saver.saveToDisk()); } // Restore qDebug() << Q_FUNC_INFO << "Restoring"; auto m = createMainWindow(); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); QVERIFY(dock1->isFloating()); QVERIFY(dropArea->checkSanity()); LayoutSaver saver; saver.restoreFromDisk(); QVERIFY(dropArea->checkSanity()); QVERIFY(!dock1->isFloating()); } void TestDocks::tst_addDockWidgetAsTabToDockWidget() { EnsureTopLevelsDeleted e; { // Dock into a non-morphed floating dock widget auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); dock1->addDockWidgetAsTab(dock2); QWidget *window1 = dock1->window(); QWidget *window2 = dock2->window(); QCOMPARE(window1, window2); QCOMPARE(dock1->frame(), dock2->frame()); QCOMPARE(dock1->frame()->dockWidgetCount(), 2); delete dock1; delete dock2; } { // Dock into a morphed dock widget auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); dock1->morphIntoFloatingWindow(); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); dock1->addDockWidgetAsTab(dock2); QWidget *window1 = dock1->window(); QWidget *window2 = dock2->window(); QCOMPARE(window1, window2); QCOMPARE(dock1->frame(), dock2->frame()); QCOMPARE(dock1->frame()->dockWidgetCount(), 2); delete dock1; delete dock2; } { // Dock a morphed dock widget into a morphed dock widget auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); dock1->morphIntoFloatingWindow(); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); dock2->morphIntoFloatingWindow(); QPointer originalWindow2 = dock2->window(); dock1->addDockWidgetAsTab(dock2); QWidget *window1 = dock1->window(); QWidget *window2 = dock2->window(); QCOMPARE(window1, window2); QCOMPARE(dock1->frame(), dock2->frame()); QCOMPARE(dock1->frame()->dockWidgetCount(), 2); waitForDeleted(originalWindow2); QVERIFY(!originalWindow2); delete dock1; delete dock2; } { // Dock to an already docked widget auto m = createMainWindow(); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); nestDockWidget(dock1, dropArea, nullptr, KDDockWidgets::Location_OnLeft); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); dock1->addDockWidgetAsTab(dock2); QCOMPARE(dock1->window(), m.get()); QCOMPARE(dock2->window(), m.get()); QCOMPARE(dock1->frame(), dock2->frame()); QCOMPARE(dock1->frame()->dockWidgetCount(), 2); } } void TestDocks::tst_addDockWidgetToMainWindow() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); m->addDockWidget(dock1, Location_OnRight, nullptr); m->addDockWidget(dock2, Location_OnTop, dock1); QVERIFY(qobject_cast(m->centralWidget())->checkSanity()); QCOMPARE(dock1->window(), m.get()); QCOMPARE(dock2->window(), m.get()); QVERIFY(dock1->frame()->y() > dock2->frame()->y()); QCOMPARE(dock1->frame()->x(), dock2->frame()->x()); } void TestDocks::tst_addDockWidgetToContainingWindow() { EnsureTopLevelsDeleted e; auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); auto dock3 = createDockWidget(QStringLiteral("dock3"), new QPushButton(QStringLiteral("three"))); dock1->addDockWidgetToContainingWindow(dock2, Location_OnRight); dock1->addDockWidgetToContainingWindow(dock3, Location_OnTop, dock2); QCOMPARE(dock1->window(), dock2->window()); QCOMPARE(dock2->window(), dock3->window()); QVERIFY(dock3->frame()->y() < dock2->frame()->y()); QVERIFY(dock1->frame()->x() < dock2->frame()->x()); QCOMPARE(dock2->frame()->x(), dock3->frame()->x()); QWidget *window = dock1->window(); delete dock1; delete dock2; delete dock3; waitForDeleted(window); } void TestDocks::tst_addToSmallMainWindow() { // Add a dock widget which is bigger than the main window. // Check that the dock widget gets smaller EnsureTopLevelsDeleted e; qDebug() << "Test 1"; { auto m = createMainWindow(); auto dock1 = createDockWidget(QStringLiteral("dock1"), new MyWidget2()); auto dock2 = createDockWidget(QStringLiteral("dock2"), new MyWidget2()); auto dock3 = createDockWidget(QStringLiteral("dock3"), new MyWidget2()); auto dock4 = createDockWidget(QStringLiteral("dock4"), new MyWidget2()); const int mainWindowLength = 400; m->resize(mainWindowLength, mainWindowLength); dock1->resize(800, 800); dock2->resize(800, 800); dock3->resize(800, 800); // Add as tabbed: m->addDockWidgetAsTab(dock1); QCOMPARE(m->height(), mainWindowLength); QVERIFY(dock1->height() < mainWindowLength); QVERIFY(dock1->width() < mainWindowLength); //Add in area: m->addDockWidget(dock2, Location_OnLeft); m->addDockWidget(dock3, Location_OnTop, dock2); qDebug() << "Adding bottom one"; m->addDockWidget(dock4, Location_OnBottom); auto dropArea = qobject_cast(m->centralWidget()); dropArea->debug_updateItemNamesForGammaray(); QVERIFY(dropArea->checkSanity()); QVERIFY(dock2->width() < mainWindowLength); QVERIFY(dock3->height() < m->height()); QVERIFY(dock4->height() < m->height()); } qDebug() << "Test 2"; { auto m = createMainWindow(); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); auto dock1 = createDockWidget(QStringLiteral("dock1"), new MyWidget2(QSize(100, 100))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new MyWidget2(QSize(100, 100))); m->addDockWidgetAsTab(dock1); m->resize(osWindowMinWidth(), 200); waitForResize(m.get()); qDebug() << "Adding dock2 to Right. window size=" << m->size(); QVERIFY(m->width() == osWindowMinWidth()); m->addDockWidget(dock2, KDDockWidgets::Location_OnRight); qDebug() << "Waiting for resize. window size2=" << m->size(); QVERIFY(waitForResize(m.get())); qDebug() << "window size3=" << m->size(); QVERIFY(dropArea->multiSplitter()->contentsWidth() > osWindowMinWidth()); QCOMPARE(dropArea->multiSplitter()->contentsWidth(), m->width()); qDebug() << "New size: " << m->width() << dropArea->multiSplitter()->contentsWidth() << dropArea->minimumSize(); QVERIFY(qobject_cast(m->centralWidget())->checkSanity()); } qDebug() << "Test 3"; { auto m = createMainWindow(); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); auto dock1 = createDockWidget(QStringLiteral("dock1"), new MyWidget2()); auto dock2 = createDockWidget(QStringLiteral("dock2"), new MyWidget2()); m->addDockWidgetAsTab(dock1); m->resize(osWindowMinWidth(), 200); QTest::qWait(200); QVERIFY(m->width() == osWindowMinWidth()); auto fw = dock2->morphIntoFloatingWindow(); QVERIFY(fw->isVisible()); QVERIFY(dropArea->checkSanity(MultiSplitterLayout::AnchorSanity_Intersections)); dragFloatingWindowTo(fw, dropArea, DropIndicatorOverlayInterface::DropLocation_Right); QVERIFY(qobject_cast(m->centralWidget())->checkSanity()); delete fw; } qDebug() << "Test 4"; { auto m = createMainWindow(QSize(100, 100), MainWindowOption_None); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); auto dock1 = createDockWidget(QStringLiteral("dock1"), new MyWidget2(QSize(50, 50))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new MyWidget2(QSize(50, 50))); m->addDockWidget(dock1, KDDockWidgets::Location_OnBottom); waitForResize(m.get()); qDebug() << "Size=" << m->size(); m->addDockWidget(dock2, KDDockWidgets::Location_OnBottom); waitForResize(m.get()); const int frame2MinHeight = KDDockWidgets::widgetMinLength(dock2->frame(), Qt::Horizontal); QCOMPARE(dropArea->height(), dock1->frame()->height() + frame2MinHeight + Anchor::thickness(true)*2 + Anchor::thickness(false)); } qDebug() << "Test 5"; { // Test test shouldn't spit any warnings MainWindow m(QStringLiteral("MyMainWindow"), MainWindowOption_None); auto dock1 = createDockWidget(QStringLiteral("dock1"), new MyWidget2(QSize(50, 240))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new MyWidget2(QSize(50, 240))); m.addDockWidget(dock1, KDDockWidgets::Location_OnBottom); m.addDockWidget(dock2, KDDockWidgets::Location_OnBottom); waitForResize(&m); qDebug() << m.size(); } qDebug() << "Test 8"; { // Test test shouldn't spit any warnings QWidget container; auto lay = new QVBoxLayout(&container); MainWindow m(QStringLiteral("MyMainWindow"), MainWindowOption_None); lay->addWidget(&m); container.resize(100, 100); waitForResize(&container); container.show(); waitForResize(&m); auto dock1 = createDockWidget(QStringLiteral("dock1"), new MyWidget2(QSize(50, 240))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new MyWidget2(QSize(50, 240))); m.addDockWidget(dock1, KDDockWidgets::Location_OnBottom); m.addDockWidget(dock2, KDDockWidgets::Location_OnBottom); waitForResize(&m); } } 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; DockWidget *dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); DockWidget *dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); DockWidget *dock3 = createDockWidget(QStringLiteral("dock3"), new QPushButton(QStringLiteral("three"))); dock1->addDockWidgetToContainingWindow(dock2, Location_OnRight); dock1->addDockWidgetToContainingWindow(dock3, Location_OnRight, dock2); auto fw = qobject_cast(dock1->window()); QPointer frame2= dock2->frame(); const int oldWidth1 = dock1->frame()->width(); const int oldWidth2 = dock2->frame()->width(); const int oldWidth3 = dock3->frame()->width(); MultiSplitterLayout *layout = fw->dropArea()->multiSplitter(); QCOMPARE(layout->count(), 3); QCOMPARE(layout->visibleCount(), 3); QCOMPARE(layout->placeholderCount(), 0); delete dock2; QVERIFY(waitForResize(dock1)); QVERIFY(!frame2); QCOMPARE(layout->count(), 2); QCOMPARE(layout->visibleCount(), 2); QCOMPARE(layout->placeholderCount(), 0); const int delta1 = (dock1->frame()->width() - oldWidth1); const int delta3 = (dock3->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 QWidget *window = dock1->window(); window->deleteLater(); waitForDeleted(window); } void TestDocks::tst_notClosable() { EnsureTopLevelsDeleted e; { auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one")), DockWidget::Option_NotClosable); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); QVERIFY(dock1->titleBar()->isVisible()); QWidget *close1 = dock1->titleBar()->closeButton(); QWidget *close2 = dock2->titleBar()->closeButton(); QVERIFY(!close1->isEnabled()); QVERIFY(close2->isEnabled()); dock1->addDockWidgetAsTab(dock2); auto fw = qobject_cast(dock1->window()); QVERIFY(fw); QWidget *closeFW = fw->titleBar()->closeButton(); QWidget *closeFrame = fw->frames().at(0)->titleBar()->closeButton(); QVERIFY(!close1->isVisible()); QVERIFY(!close2->isVisible()); QVERIFY(!closeFW->isVisible()); QVERIFY(closeFrame->isVisible()); QVERIFY(!closeFrame->isEnabled()); auto window = dock1->window(); window->deleteLater(); waitForDeleted(window); } { // Now dock dock1 into dock1 instead auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one")), DockWidget::Option_NotClosable); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); QVERIFY(dock1->titleBar()->isVisible()); QWidget *close1 = dock1->titleBar()->closeButton(); QWidget *close2 = dock2->titleBar()->closeButton(); QVERIFY(!close1->isEnabled()); QVERIFY(close2->isEnabled()); dock2->morphIntoFloatingWindow(); dock2->addDockWidgetAsTab(dock1); auto fw = qobject_cast(dock1->window()); QVERIFY(fw); QWidget *closeFW = fw->titleBar()->closeButton(); QWidget *closeFrame = fw->frames().at(0)->titleBar()->closeButton(); QVERIFY(!close1->isVisible()); QVERIFY(!close2->isVisible()); QVERIFY(!closeFW->isVisible()); QVERIFY(closeFrame->isVisible()); QVERIFY(!closeFrame->isEnabled()); auto window = dock2->window(); window->deleteLater(); waitForDeleted(window); } } void TestDocks::tst_maximizeAndRestore() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); m->addDockWidget(dock2, KDDockWidgets::Location_OnRight); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); QVERIFY(dropArea->checkSanity()); m->showMaximized(); waitForResize(m.get()); QVERIFY(dropArea->checkSanity()); qDebug() << "About to show normal"; m->showNormal(); waitForResize(m.get()); QVERIFY(dropArea->checkSanity()); } void TestDocks::tst_propagateResize2() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); m->addDockWidget(dock1, KDDockWidgets::Location_OnTop); m->addDockWidget(dock2, KDDockWidgets::Location_OnRight, dock1); auto dock3 = createDockWidget(QStringLiteral("dock3"), new QPushButton(QStringLiteral("three"))); auto dock4 = createDockWidget(QStringLiteral("dock4"), new QPushButton(QStringLiteral("four"))); m->addDockWidget(dock3, KDDockWidgets::Location_OnBottom); m->addDockWidget(dock4, KDDockWidgets::Location_OnRight, dock3); auto dock5 = createDockWidget(QStringLiteral("dock5"), new QPushButton(QStringLiteral("five"))); m->addDockWidget(dock5, KDDockWidgets::Location_OnLeft); QWidget *central = m->centralWidget(); auto dropArea = qobject_cast(central); dropArea->checkSanity(); } std::unique_ptr TestDocks::createMultiSplitterFromSetup(MultiSplitterSetup setup, QHash &frameMap) const { auto widget = std::unique_ptr(new MultiSplitterWidget()); auto layout = widget->multiSplitter(); widget->show(); layout->setContentsSize(setup.size); const int count = setup.widgets.size(); for (int i = 0; i < count; ++i) { auto frame = new Frame(widget.get()); auto dock = new DockWidget(QStringLiteral("foo")); dock->setWidget(setup.widgets[i]); frame->addWidget(dock); frameMap.insert(setup.widgets[i], frame); qDebug() << "Min size=" << KDDockWidgets::widgetMinLength(frame, Qt::Horizontal) << KDDockWidgets::widgetMinLength(dock, Qt::Horizontal); layout->addWidget(frame, setup.locations[i], frameMap.value(setup.relativeTos[i])); } for (WidgetResize wr : setup.widgetResizes) { qDebug() << "Resizing widget"; Frame *frame = frameMap.value(wr.w); layout->resizeItem(frame, wr.length, wr.orientation); if (widgetLength(frame, wr.orientation) != wr.length) { qDebug() << widgetLength(wr.w, wr.orientation) << wr.length << widgetLength(frame, wr.orientation); Q_ASSERT(false); } } return widget; } void TestDocks::tst_availableLengthForDrop_data() { QTest::addColumn("multisplitterSetup"); QTest::addColumn("expectedAvailableSizes"); const int staticAnchorThickness = Anchor::thickness(/*static=*/true); const int anchorThickness = Anchor::thickness(/*static=*/false); const int multispitterlength = 500; QSize minFrameSize; { // Frame has a bit bigger min size than the widget it hosts, since it has space for a tab bar and titlebar Frame frame; const int w1MinLength = 100; QWidget *w1 = createWidget(w1MinLength); auto dock = new DockWidget(QStringLiteral("foo")); dock->setWidget(w1); frame.addWidget(dock); minFrameSize = frame.minimumSizeHint(); } { ExpectedAvailableSizes availableSizes; MultiSplitterSetup setup; setup.size = QSize(multispitterlength, multispitterlength); int totalAvailable = multispitterlength - 2*staticAnchorThickness; int expected2 = totalAvailable; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnTop, nullptr, 0, expected2, totalAvailable }; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnLeft, nullptr, 0, expected2, totalAvailable }; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnRight, nullptr, expected2, 0, totalAvailable }; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnBottom, nullptr, expected2, 0, totalAvailable }; QTest::newRow("empty") << setup << availableSizes; } { ExpectedAvailableSizes availableSizes; MultiSplitterSetup setup; setup.size = QSize(multispitterlength, multispitterlength); const int w1MinLength = 100; QWidget *w1 = createWidget(w1MinLength); setup.widgets << w1; setup.relativeTos << nullptr; setup.locations << KDDockWidgets::Location_OnLeft; const int totalAvailable_vert = multispitterlength - 2*staticAnchorThickness - anchorThickness - minFrameSize.height(); const int expected2_vert = totalAvailable_vert; const int totalAvailable_horiz = multispitterlength - 2*staticAnchorThickness - anchorThickness - minFrameSize.width(); const int expected2_horiz = totalAvailable_horiz; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnTop, nullptr, 0, expected2_vert, totalAvailable_vert }; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnLeft, nullptr, 0, expected2_horiz, totalAvailable_horiz }; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnRight, nullptr, expected2_horiz, 0, totalAvailable_horiz }; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnBottom, nullptr, expected2_vert, 0, totalAvailable_vert }; QTest::newRow("one_existing-outter") << setup << availableSizes; } { ExpectedAvailableSizes availableSizes; MultiSplitterSetup setup; setup.size = QSize(multispitterlength, multispitterlength); const int w1MinLength = 100; QWidget *w1 = createWidget(w1MinLength); setup.widgets << w1; setup.relativeTos << nullptr; setup.locations << KDDockWidgets::Location_OnLeft; const int totalAvailable_vert = multispitterlength - 2*staticAnchorThickness - anchorThickness - minFrameSize.height(); const int expected2_vert = totalAvailable_vert; const int totalAvailable_horiz = multispitterlength - 2*staticAnchorThickness - anchorThickness - minFrameSize.width(); const int expected2_horiz = totalAvailable_horiz; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnTop, w1, 0, expected2_vert, totalAvailable_vert }; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnLeft, w1, 0, expected2_horiz, totalAvailable_horiz }; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnRight, w1, expected2_horiz, 0, totalAvailable_horiz }; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnBottom, w1, expected2_vert, 0, totalAvailable_vert }; QTest::newRow("one_existing-inner") << setup << availableSizes; } { ExpectedAvailableSizes availableSizes; MultiSplitterSetup setup; setup.size = QSize(multispitterlength, multispitterlength); const int w1MinLength = 100; QWidget *w1 = createWidget(w1MinLength, QStringLiteral("w1")); QWidget *w2 = createWidget(w1MinLength, QStringLiteral("w2")); QWidget *w3 = createWidget(w1MinLength, QStringLiteral("w3")); setup.widgets << w1 << w2 << w3; setup.relativeTos << nullptr << nullptr << nullptr; setup.locations << KDDockWidgets::Location_OnBottom << KDDockWidgets::Location_OnBottom << KDDockWidgets::Location_OnBottom; setup.widgetResizes << WidgetResize{ 150, Qt::Horizontal, w1 }; setup.widgetResizes << WidgetResize{ 150, Qt::Horizontal, w2 }; const int totalAvailable = multispitterlength - 2*staticAnchorThickness - 2*anchorThickness -3*minFrameSize.height() - anchorThickness; int expected2 = totalAvailable; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnTop, nullptr, 0, expected2, totalAvailable }; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnBottom, w3, expected2, 0, totalAvailable }; int expected1 = 15; expected2 = totalAvailable - expected1; availableSizes << ExpectedAvailableSize{ KDDockWidgets::Location_OnBottom, w1, expected1, expected2, totalAvailable }; QTest::newRow("another") << setup << availableSizes; } //---------------------------------------------------------------------------------------------- } void TestDocks::tst_availableLengthForDrop() { QFETCH(MultiSplitterSetup, multisplitterSetup); QFETCH(ExpectedAvailableSizes, expectedAvailableSizes); QHash frameMap; auto multisplitterWidget = createMultiSplitterFromSetup(multisplitterSetup, frameMap); auto layout = multisplitterWidget->multiSplitter(); int i = 0; for (ExpectedAvailableSize expectedSize : expectedAvailableSizes) { expectedSize.relativeTo = expectedSize.relativeTo == nullptr ? multisplitterWidget->parentWidget() : expectedSize.relativeTo; auto available = layout->availableLengthForDrop(expectedSize.location, layout->itemForFrame(frameMap.value(expectedSize.relativeTo))); // qDebug() << available.length() << "; i=" << i; QCOMPARE(available.length(), expectedSize.totalAvailable); if (available.side1Length != expectedSize.side1ExpectedSize) { layout->dumpDebug(); qDebug() << "loc=" << expectedSize.location << "; relativeTo=" << expectedSize.relativeTo; QCOMPARE(available.side1Length, expectedSize.side1ExpectedSize); } QCOMPARE(available.side2Length, expectedSize.side2ExpectedSize); ++i; } } void TestDocks::tst_constraintsAfterPlaceholder() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(500, 500), MainWindowOption_None); const int minHeight = 400; auto dock1 = createDockWidget(QStringLiteral("dock1"), new MyWidget2(QSize(400, minHeight))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new MyWidget2(QSize(400, minHeight))); auto dock3 = createDockWidget(QStringLiteral("dock2"), new MyWidget2(QSize(400, minHeight))); m->addDockWidget(dock1, Location_OnTop); m->addDockWidget(dock2, Location_OnTop); m->addDockWidget(dock3, Location_OnTop); QVERIFY(m->minimumSizeHint().height() > minHeight * 3); // > since some vertical space is occupied by the separators // Now close dock1 and check again dock1->close(); waitForResize(dock2); const int expectedMinHeight = KDDockWidgets::widgetMinLength(dock2->frame(), Qt::Horizontal) + KDDockWidgets::widgetMinLength(dock3->frame(), Qt::Horizontal) + 2 * Anchor::thickness(true) + 1 * Anchor::thickness(false); QCOMPARE(m->minimumSizeHint().height(), expectedMinHeight); dock1->deleteLater(); waitForDeleted(dock1); } void TestDocks::tst_rectForDrop_data() { QTest::addColumn("multisplitterSetup"); QTest::addColumn("expectedRects"); const int staticAnchorThickness = Anchor::thickness(/*static=*/true); const int multispitterlength = 500; { MultiSplitterSetup setup; ExpectedRectsForDrop rects; QWidget * widgetToDrop = createWidget(100, QStringLiteral("w1")); widgetToDrop->resize(200, 200); const int expectedLength = 200; // this 200 will change when the initial length algoritm changes; Maybe just call MultiSplitterLayout::LengthForDrop() directly here rects << ExpectedRectForDrop {widgetToDrop, KDDockWidgets::Location_OnLeft, nullptr, QRect(1, 1, expectedLength, multispitterlength - staticAnchorThickness*2) }; rects << ExpectedRectForDrop {widgetToDrop, KDDockWidgets::Location_OnTop, nullptr, QRect(1, 1, multispitterlength - staticAnchorThickness*2, expectedLength) }; rects << ExpectedRectForDrop {widgetToDrop, KDDockWidgets::Location_OnRight, nullptr, QRect(299, 1, expectedLength, multispitterlength - staticAnchorThickness*2) }; rects << ExpectedRectForDrop {widgetToDrop, KDDockWidgets::Location_OnBottom, nullptr, QRect(1, 299, multispitterlength - staticAnchorThickness*2, expectedLength) }; setup.size = QSize(multispitterlength, multispitterlength); QTest::newRow("empty") << setup << rects; } } void TestDocks::tst_rectForDrop() { QFETCH(MultiSplitterSetup, multisplitterSetup); QFETCH(ExpectedRectsForDrop, expectedRects); QHash frameMap; auto multisplitterWidget = createMultiSplitterFromSetup(multisplitterSetup, frameMap); auto layout = multisplitterWidget->multiSplitter(); qDebug() << "Created with contentsSize=" << layout->contentsWidth() << layout->contentsHeight()<< multisplitterSetup.size; for (ExpectedRectForDrop expected : expectedRects) { QRect actualRect = layout->rectForDrop(expected.widgetToDrop, expected.location, layout->itemForFrame(expected.relativeTo)); layout->dumpDebug(); QCOMPARE(actualRect, expected.expectedRect); expected.widgetToDrop->deleteLater(); } } void TestDocks::tst_crash() { EnsureTopLevelsDeleted e; MultiSplitterWidget msw; auto layout = msw.multiSplitter(); layout->setContentsSize(QSize(800, 316)); layout->parentWidget()->show(); auto f1 = createFrameWithWidget(QStringLiteral("w1"), &msw, 200); auto f2 = createFrameWithWidget(QStringLiteral("w2"), &msw, 100); auto f3 = createFrameWithWidget(QStringLiteral("w3"), &msw, 100); layout->addWidget(f3, KDDockWidgets::Location_OnBottom); layout->addWidget(f2, KDDockWidgets::Location_OnTop, f3); layout->addWidget(f1, KDDockWidgets::Location_OnTop, f2); layout->resizeItem(f1, 308, Qt::Horizontal); auto f4 = createFrameWithWidget(QStringLiteral("w4"), &msw, 105); // side1 has 108pixels available, which doesn't fit the 5px for the new anchor, + 105 for the widget. Side2 must catter for the 5px. layout->addWidget(f4, KDDockWidgets::Location_OnBottom, f1); } 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(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("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->frame()->m_tabWidget->count(), 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()); LastPosition *pos2 = dock2->lastPosition(); QCOMPARE(pos2->m_tabIndex, 1); QVERIFY(pos2->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(QStringLiteral("dock3"), new QPushButton(QStringLiteral("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->frame()->m_tabWidget->indexOf(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->frame()->m_tabWidget->count(), 1); // Cleanup m->addDockWidgetAsTab(dock2); m->addDockWidgetAsTab(dock3); m->deleteLater(); auto window = m.release(); 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(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); m->addDockWidget(dock2, KDDockWidgets::Location_OnRight); QPointer frame1 = dock1->frame(); dock1->setFloating(true); QVERIFY(dock1->isFloating()); auto fw = qobject_cast(dock1->window()); QVERIFY(fw); //2. Put it back, via setFloating(). It should return to its place. dock1->setFloating(false); QVERIFY(!dock1->isFloating()); QVERIFY(!dock1->isTabbed()); waitForDeleted(fw); } void TestDocks::tst_setVisibleFalseWhenSideBySide() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); m->addDockWidget(dock2, KDDockWidgets::Location_OnRight); const QRect oldGeo = dock1->geometry(); QWidget *oldParent = dock1->parentWidget(); // 1. Just toggle visibility and check that stuff remained sane dock1->setVisible(false); QVERIFY(!dock1->isTabbed()); QVERIFY(!dock1->isFloating()); dock1->setVisible(true); QVERIFY(!dock1->isTabbed()); QVERIFY(!dock1->isFloating()); QCOMPARE(dock1->geometry(), oldGeo); QCOMPARE(dock1->parentWidget(), oldParent); // 2. Check that the parent frame also is hidden now dock1->setVisible(false); QVERIFY(!dock1->frame()->isVisible()); // Cleanup m->deleteLater(); auto window = m.release(); waitForDeleted(window); } void TestDocks::tst_simple1() { // Simply create a MainWindow EnsureTopLevelsDeleted e; auto m = createMainWindow(); } void TestDocks::tst_simple2() { // Simply create a MainWindow, and dock something on top EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dw = createDockWidget(QStringLiteral("dw"), new QPushButton(QStringLiteral("dw"))); m->addDockWidget(dw, KDDockWidgets::Location_OnTop); } void TestDocks::tst_refUnrefItem() { EnsureTopLevelsDeleted e; auto m = createMainWindow(); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("1"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("2"))); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); m->addDockWidget(dock2, KDDockWidgets::Location_OnRight); auto dropArea = qobject_cast(m->centralWidget()); auto layout = dropArea->multiSplitter(); QPointer frame1 = dock1->frame(); QPointer frame2 = dock2->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; 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(QStringLiteral("dock3"), new QPushButton(QStringLiteral("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(); waitForDeleted(frame2); QVERIFY(dock2); QVERIFY(item2.data()); QCOMPARE(item2->refCount(), 1); QCOMPARE(dock2->lastPosition()->layoutItem(), item2.data()); delete dock2; QVERIFY(!item2.data()); QCOMPARE(layout->count(), 1); layout->dumpDebug(); // 4. Move a closed dock widget from one mainwindow to another // It should delete its old placeholder auto dock4 = createDockWidget(QStringLiteral("dock4"), new QPushButton(QStringLiteral("4"))); m->addDockWidget(dock4, KDDockWidgets::Location_OnLeft); QPointer frame4 = dock4->frame(); QPointer item4 = layout->itemForFrame(frame4); dock4->close(); waitForDeleted(frame4); QCOMPARE(item4->refCount(), 1); QVERIFY(item4->isPlaceholder()); auto m2 = createMainWindow(); m2->addDockWidget(dock4, KDDockWidgets::Location_OnLeft); QVERIFY(!item4.data()); } 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(QStringLiteral("dock1"), new QPushButton(QStringLiteral("1"))); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); dock1->setFloating(true); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); dock1->frame()->titleBar()->makeWindow(); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); dock1->frame()->titleBar()->makeWindow(); auto fw = qobject_cast(dock1->window()); QVERIFY(fw); auto dropArea = qobject_cast(m->centralWidget()); dragFloatingWindowTo(fw, dropArea, DropIndicatorOverlayInterface::DropLocation_OutterRight); dock1->frame()->titleBar()->makeWindow(); //Cleanup delete dock1; waitForDeleted(fw); } 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(QStringLiteral("1"), new QPushButton(QStringLiteral("1"))); auto dock2 = createDockWidget(QStringLiteral("2"), new QPushButton(QStringLiteral("2"))); auto dropArea = qobject_cast(m->centralWidget()); auto layout = dropArea->multiSplitter(); 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 = qobject_cast(dock1->window()); layout->dumpDebug(); 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 = qobject_cast(dock1->window()); m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft); QCOMPARE(layout->count(), 2); QCOMPARE(layout->visibleCount(), 2); QCOMPARE(layout->placeholderCount(), 0); 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 = qobject_cast(m->centralWidget()); MultiSplitterLayout *layout = dropArea->multiSplitter(); int availableWidth = layout->availableLengthForOrientation(Qt::Vertical); int availableHeight = layout->availableLengthForOrientation(Qt::Horizontal); QCOMPARE(availableWidth, layout->contentsWidth() - 2 * Anchor::thickness(true)); QCOMPARE(availableHeight, layout->contentsHeight() - 2 *Anchor::thickness(true)); //2. Now do the same, but we have some widget docked auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("1"))); m->addDockWidget(dock1, Location_OnLeft); const int dock1MinWidth = widgetMinLength(dock1->frame(), Qt::Vertical); const int dock1MinHeight = widgetMinLength(dock1->frame(), Qt::Horizontal); availableWidth = layout->availableLengthForOrientation(Qt::Vertical); availableHeight = layout->availableLengthForOrientation(Qt::Horizontal); QCOMPARE(availableWidth, layout->contentsWidth() - 2 * Anchor::thickness(true) - Anchor::thickness(false) - dock1MinWidth); QCOMPARE(availableHeight, layout->contentsHeight() - 2 *Anchor::thickness(true) - Anchor::thickness(false) - dock1MinHeight); } void TestDocks::tst_setAstCurrentTab() { EnsureTopLevelsDeleted e; // Tests DockWidget::setAsCurrentTab() and DockWidget::isCurrentTab() // 1. a single dock widget is current, by definition auto dock1 = createDockWidget(QStringLiteral("1"), new QPushButton(QStringLiteral("1"))); QVERIFY(dock1->isCurrentTab()); // 2. Tab dock2 to the group, dock2 is current now auto dock2 = createDockWidget(QStringLiteral("2"), new QPushButton(QStringLiteral("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 = qobject_cast(dock1->window()); QVERIFY(fw); delete dock1; delete dock2; waitForDeleted(fw); } 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(QStringLiteral("1"), new QPushButton(QStringLiteral("1"))); m->addDockWidget(dock1, Location_OnLeft); dock1->close(); QVERIFY(!dock1->frame()); QVERIFY(!waitForDeleted(dock1)); // It was being deleted due to a bug QVERIFY(dock1); dock1->show(); } 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 auto dropArea = qobject_cast(m->centralWidget()); MultiSplitterLayout *layout = dropArea->multiSplitter(); QPointer dock1 = createDockWidget(QStringLiteral("1"), new QPushButton(QStringLiteral("1"))); m->addDockWidget(dock1, Location_OnLeft); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); layout->dumpDebug(); dock1->setFloating(true); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 1); dock1->morphIntoFloatingWindow(); auto fw = qobject_cast(dock1->window()); layout->addMultiSplitter(fw->dropArea(), Location_OnRight ); layout->dumpDebug(); QCOMPARE(layout->placeholderCount(), 0); QCOMPARE(layout->count(), 1); layout->dumpDebug(); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); // The dock1 should occupy the entire width QCOMPARE(dock1->frame()->width(), layout->contentsWidth() - 2 * Anchor::thickness(true)); QVERIFY(waitForDeleted(fw)); } void TestDocks::tst_placeholdersAreRemovedPropertly() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame auto dropArea = qobject_cast(m->centralWidget()); MultiSplitterLayout *layout = dropArea->multiSplitter(); QPointer dock1 = createDockWidget(QStringLiteral("1"), new QPushButton(QStringLiteral("1"))); QPointer dock2 = createDockWidget(QStringLiteral("1"), new QPushButton(QStringLiteral("1"))); 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->anchors().size(), 5); QCOMPARE(layout->count(), 2); QCOMPARE(layout->placeholderCount(), 1); layout->removeItem(item); QCOMPARE(layout->anchors().size(), 4); 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); QPointer window1 = dock1->window(); delete dock1; QCOMPARE(layout->anchors().size(), 4); QCOMPARE(layout->count(), 1); QCOMPARE(layout->placeholderCount(), 0); // Cleanup waitForDeleted(window1); } void TestDocks::tst_embeddedMainWindow() { // 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(QStringLiteral("1"), new QPushButton(QStringLiteral("1"))); window->mainWindow->addDockWidget(dock1, Location_OnTop); dock1->setFloating(true); auto dropArea = qobject_cast(window->mainWindow->centralWidget()); auto fw = qobject_cast(dock1->window()); dragFloatingWindowTo(fw, dropArea, DropIndicatorOverlayInterface::DropLocation_OutterLeft); auto layout = dropArea->multiSplitter(); QVERIFY(waitForDeleted(fw)); QCOMPARE(layout->count(), 2); // 2, as it has the central frame QCOMPARE(layout->visibleCount(), 2); } void TestDocks::tst_toggleMiddleDockCrash() { EnsureTopLevelsDeleted e; auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); // Remove central frame auto dropArea = qobject_cast(m->centralWidget()); MultiSplitterLayout *layout = dropArea->multiSplitter(); QPointer dock1 = createDockWidget(QStringLiteral("1"), new QPushButton(QStringLiteral("1"))); QPointer dock2 = createDockWidget(QStringLiteral("2"), new QPushButton(QStringLiteral("2"))); QPointer dock3 = createDockWidget(QStringLiteral("3"), new QPushButton(QStringLiteral("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->frame(); dock2->close(); QVERIFY(waitForDeleted(frame)); QCOMPARE(layout->count(), 3); QCOMPARE(layout->placeholderCount(), 1); QVERIFY(layout->checkSanity()); QCOMPARE(layout->numAchorsFollowing(), 1); qDebug() << "Dock1.min=" << KDDockWidgets::widgetMinLength(dock1->frame(), Qt::Vertical); qDebug() << "Dock3.min=" << KDDockWidgets::widgetMinLength(dock3->frame(), Qt::Vertical); dock2->show(); } 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(QStringLiteral("1"), new QPushButton(QStringLiteral("1"))); auto dock2 = createDockWidget(QStringLiteral("2"), new QPushButton(QStringLiteral("2"))); auto dock3 = createDockWidget(QStringLiteral("3"), new QPushButton(QStringLiteral("3"))); auto dropArea = qobject_cast(m->centralWidget()); MultiSplitterLayout *layout = dropArea->multiSplitter(); // Stack: 1, 2, 3 vertically m->addDockWidget(dock3, Location_OnTop); m->addDockWidget(dock2, Location_OnTop); m->addDockWidget(dock1, Location_OnTop); auto frame1 = dock1->frame(); auto frame2 = dock2->frame(); auto frame3 = dock3->frame(); QCOMPARE(frame1->y(), 1); // Close 1 dock1->close(); waitForResize(frame2); // Check that frame2 moved up to y=1 QCOMPARE(frame2->y(), 1); QCOMPARE(layout->numAchorsFollowing(), 1); layout->dumpDebug(); // Close 2 dock2->close(); waitForResize(dock3); layout->dumpDebug(); QVERIFY(layout->checkSanity()); QCOMPARE(layout->count(), 3); QCOMPARE(layout->placeholderCount(), 2); QCOMPARE(layout->numAchorsFollowing(), 2); // Check that frame3 moved up to y=1 QCOMPARE(frame3->y(), 1); // Now restore: auto toRestore1 = restore1First ? dock1 : dock2; auto toRestore2 = restore1First ? dock2 : dock1; toRestore1->show(); toRestore2->show(); waitForResize(frame3); QVERIFY(layout->checkSanity()); QCOMPARE(layout->count(), 3); QCOMPARE(layout->placeholderCount(), 0); QCOMPARE(layout->numAchorsFollowing(), 0); dock1->deleteLater(); dock2->deleteLater(); QVERIFY(waitForDeleted(dock2)); } void TestDocks::tst_28NestedWidgets_data() { QTest::addColumn>("docksToCreate"); QTest::addColumn>("docksToHide"); QVector docks = { {Location_OnLeft, -1, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnTop, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnLeft, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnBottom, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr } }; //QTest::newRow("28") << docks << QVector{11, 0}; docks = { {Location_OnLeft, -1, nullptr }, {Location_OnBottom, -1, nullptr }, {Location_OnBottom, -1, nullptr }, {Location_OnBottom, -1, nullptr }, {Location_OnBottom, -1, nullptr }, {Location_OnBottom, -1, nullptr }, {Location_OnTop, -1, nullptr }, {Location_OnRight, -1, nullptr }, }; // 2. Produced valgrind invalid reads while adding //QTest::newRow("valgrind") << docks << QVector{}; docks = { {Location_OnLeft, -1, nullptr }, {Location_OnBottom, -1, nullptr }, {Location_OnTop, -1, nullptr }, {Location_OnRight, -1, nullptr }, }; //QTest::newRow("bug_when_closing") << docks << QVector{}; // Q_ASSERT(!isSquashed()) docks = { {Location_OnLeft, -1, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnBottom, -1, nullptr }, }; //QTest::newRow("bug_when_closing2") << docks << QVector{}; // Tests for void KDDockWidgets::Anchor::setPosition(int, KDDockWidgets::Anchor::SetPositionOptions) Negative position -69 docks = { {Location_OnLeft, -1, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnBottom, 0, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnTop, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnLeft, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnBottom, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr }, {Location_OnRight, -1, nullptr } }; QVector docksToHide; for (int i = 0; i < 28; ++i) { if (i != 16 && i != 17 && i != 18 && i != 27) docksToHide << i; } QTest::newRow("bug_with_holes") << docks << docksToHide; } 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); int i = 0; for (DockDescriptor &desc : docksToCreate) { desc.createdDock = createDockWidget(QStringLiteral("%1").arg(i), new QPushButton(QStringLiteral("%1").arg(i))); DockWidget *relativeTo = nullptr; if (desc.relativeToIndex != -1) relativeTo = docksToCreate.at(desc.relativeToIndex).createdDock; m->addDockWidget(desc.createdDock, desc.loc, relativeTo); ++i; } for (int i : docksToHide) { docksToCreate.at(i).createdDock->close(); QTest::qWait(200); } for (int i : docksToHide) { docksToCreate.at(i).createdDock->deleteLater(); QVERIFY(waitForDeleted(docksToCreate.at(i).createdDock)); } // And hide the remaining ones i = 0; for (auto dock : docksToCreate) { if (dock.createdDock && dock.createdDock->isVisible()) { qDebug() << "Closing" << i << dock.createdDock->title(); 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; } // Cleanup for (auto dock : DockRegistry::self()->dockwidgets()) { qDebug() << "Deleting" << dock->title(); dock->deleteLater(); QVERIFY(waitForDeleted(dock)); } } void TestDocks::tst_invalidAnchorGroup() { // Tests a bug I got. Should not warn. EnsureTopLevelsDeleted e; { auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); QPointer fw = dock2->morphIntoFloatingWindow(); nestDockWidget(dock1, fw->dropArea(), nullptr, KDDockWidgets::Location_OnTop); dock1->close(); waitForResize(dock2); auto layout = fw->dropArea()->multiSplitter(); layout->dumpDebug(); dock2->close(); dock1->deleteLater(); dock2->deleteLater(); waitForDeleted(dock1); } { // Stack 1, 2, 3, close 2, close 1 auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); auto dock3 = createDockWidget(QStringLiteral("dock3"), new QPushButton(QStringLiteral("three"))); m->addDockWidget(dock3, Location_OnTop); m->addDockWidget(dock2, Location_OnTop); m->addDockWidget(dock1, Location_OnTop); dock2->close(); dock1->close(); dock1->deleteLater(); dock2->deleteLater(); waitForDeleted(dock1); } } void TestDocks::tst_resizeViaAnchorsAfterPlaceholderCreation() { EnsureTopLevelsDeleted e; // Stack 1, 2, 3, close 2, close 2 { auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); auto dock3 = createDockWidget(QStringLiteral("dock3"), new QPushButton(QStringLiteral("three"))); m->addDockWidget(dock3, Location_OnTop); m->addDockWidget(dock2, Location_OnTop); m->addDockWidget(dock1, Location_OnTop); dock2->close(); waitForResize(dock3); auto dropArea = qobject_cast(m->centralWidget()); MultiSplitterLayout *layout = dropArea->multiSplitter(); QCOMPARE(layout->numVisibleAnchors(), 5); // Cleanup: dock2->deleteLater(); waitForDeleted(dock2); } { auto m = createMainWindow(QSize(800, 500), MainWindowOption_None); auto dock1 = createDockWidget(QStringLiteral("dock1"), new QPushButton(QStringLiteral("one"))); auto dock2 = createDockWidget(QStringLiteral("dock2"), new QPushButton(QStringLiteral("two"))); auto dock3 = createDockWidget(QStringLiteral("dock3"), new QPushButton(QStringLiteral("three"))); auto dock4 = createDockWidget(QStringLiteral("dock4"), new QPushButton(QStringLiteral("four"))); m->addDockWidget(dock1, Location_OnRight); m->addDockWidget(dock2, Location_OnRight); m->addDockWidget(dock3, Location_OnRight); m->addDockWidget(dock4, Location_OnRight); auto dropArea = qobject_cast(m->centralWidget()); MultiSplitterLayout *layout = dropArea->multiSplitter(); Item *item1 = layout->itemForFrame(dock1->frame()); Item *item2 = layout->itemForFrame(dock2->frame()); Item *item3 = layout->itemForFrame(dock3->frame()); Item *item4 = layout->itemForFrame(dock4->frame()); Anchor *anchor = item1->anchorGroup().right; int boundToTheRight = layout->boundPositionForAnchor(anchor, Anchor::Side2); int expectedBoundToTheRight = layout->contentsSize().width() - Anchor::thickness(true) - 3*Anchor::thickness(false) - KDDockWidgets::widgetMinLength(dock2->frame(), Qt::Vertical) - KDDockWidgets::widgetMinLength(dock3->frame(), Qt::Vertical) - KDDockWidgets::widgetMinLength(dock4->frame(), Qt::Vertical); QCOMPARE(boundToTheRight, expectedBoundToTheRight); qDebug() << "boundToRight="<< boundToTheRight; dock3->close(); waitForResize(dock2); QVERIFY(!item1->isPlaceholder()); QVERIFY(!item2->isPlaceholder()); QVERIFY(item3->isPlaceholder()); QVERIFY(!item4->isPlaceholder()); QCOMPARE(item1->anchorGroup().right, item2->anchorGroup().left); QCOMPARE(item2->anchorGroup().right, item3->anchorGroup().left); QCOMPARE(item3->anchorGroup().right, item4->anchorGroup().left); boundToTheRight = layout->boundPositionForAnchor(anchor, Anchor::Side2); expectedBoundToTheRight = layout->contentsSize().width() - Anchor::thickness(true) - 2*Anchor::thickness(false) - KDDockWidgets::widgetMinLength(dock2->frame(), Qt::Vertical) - KDDockWidgets::widgetMinLength(dock4->frame(), Qt::Vertical); qDebug() << "after close boundToRight="<< boundToTheRight << "; anchor=" << anchor; QCOMPARE(boundToTheRight, expectedBoundToTheRight); anchor->setPosition(boundToTheRight); layout->checkSanity(); dock3->deleteLater(); waitForDeleted(dock3); } } void TestDocks::tst_negativeAnchorPosition() { // Tests that we don't hit: // void KDDockWidgets::Anchor::setPosition(int, KDDockWidgets::Anchor::SetPositionOptions) Negative position auto m = createMainWindow(QSize(1000, 800)); 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); auto dropArea = qobject_cast(m->centralWidget()); MultiSplitterLayout *layout = dropArea->multiSplitter(); auto d1 = createDockWidget(QStringLiteral("1"), w1); auto d2 = createDockWidget(QStringLiteral("2"), w2); auto d3 = createDockWidget(QStringLiteral("3"), w3); m->addDockWidgetAsTab(d1); m->addDockWidget(d2, Location_OnTop); m->addDockWidget(d3, Location_OnTop); const int minHeight = layout->minimumSize().height(); const int min1 = KDDockWidgets::widgetMinLength(d1->frame(), Qt::Horizontal); const int min2 = KDDockWidgets::widgetMinLength(d2->frame(), Qt::Horizontal); const int min3 = KDDockWidgets::widgetMinLength(d3->frame(), Qt::Horizontal); qDebug() << "MinSizes=" << min1 << min2 << min3 << minHeight; d2->close(); waitForResize(d3); d2->show(); // Should not result in negative anchor positions (Test will fail due to a qWarning) waitForResize(d3); d2->close(); waitForResize(d3); // Now resize the Window, after removing middle one const int availableToShrink = layout->contentsSize().height() - layout->minimumSize().height(); layout->setContentLength(Qt::Horizontal, layout->contentsLength(Qt::Horizontal) - availableToShrink); // Should not warn about negative sizes } // QTest::qWait(50000) QTEST_MAIN(KDDockWidgets::TestDocks) #include "tst_docks.moc"