Files
KDDockWidgets/tests/tst_docks.cpp
Sergio Martins 4e8a9ff8ed tests: fix a flaky test on Windows
The drop area doesn't fill the floating window, so the
center was off and the drop didn't happen

Additionally, disabled AeroSnap, for debugging purposes, so we can
actually see the window moving, otherwise it doesn't move, since
we can't fake a native drag. It's just a visual bonus, doesn't
affect the correctness.
2021-05-20 18:28:23 +01:00

7212 lines
247 KiB
C++

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