Files
KDDockWidgets/tests/qtwidgets/tst_qtwidgets.cpp
Sergio Martins c9fede2aab Remove the FloatingWindow window state overrides
Nothing is inheriting from controller now. Those methods are already
virtual in view, so just remove them from the controller
2022-06-25 17:41:03 +01:00

1568 lines
56 KiB
C++

/*
This file is part of KDDockWidgets.
SPDX-FileCopyrightText: 2019-2022 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.
*/
/// @file
/// @brief Here lives the tests that only apply to QtWidgets
/// either because they haven't been ported to QtQuick yet or because they are really
/// QtWidgets specific.
#include "Platform.h"
#include "kddockwidgets/KDDockWidgets.h"
#include "../utils.h"
#include "Config.h"
#include "LayoutSaver.h"
#include "LayoutSaver_p.h"
#include "WindowBeingDragged_p.h"
#include "kddockwidgets/ViewFactory.h"
#include "multisplitter/Item_p.h"
#include "kddockwidgets/controllers/DropArea.h"
#include "kddockwidgets/controllers/Separator.h"
#include "kddockwidgets/controllers/Group.h"
#include "kddockwidgets/controllers/DockWidget.h"
#include "kddockwidgets/controllers/MainWindow.h"
#include "kddockwidgets/controllers/SideBar.h"
#include "qtwidgets/views/MDIArea_qtwidgets.h"
#include "qtwidgets/views/MainWindow_qtwidgets.h"
#include "qtwidgets/views/ViewWrapper_qtwidgets.h"
#include "qtwidgets/views/DockWidget_qtwidgets.h"
#include "Platform.h"
#include <QObject>
#include <QPushButton>
#include <QMenuBar>
#include <QVBoxLayout>
#include <QtTest/QtTest>
using namespace KDDockWidgets;
using namespace KDDockWidgets::Controllers;
using namespace KDDockWidgets::Tests;
using namespace Layouting;
/// Helper function so we don't write such a big line everywhere
inline Controllers::DockWidget *newDockWidget(const QString &uniqueName,
DockWidgetOptions opts = {},
LayoutSaverOptions layoutSaverOptions = {})
{
return Config::self().viewFactory()->createDockWidget(uniqueName, opts, layoutSaverOptions)->asDockWidgetController();
}
inline Controllers::DockWidget *createDockWidget(const QString &name, QWidget *w,
DockWidgetOptions options = {},
LayoutSaverOptions layoutSaverOptions = {},
bool show = true,
const QString &affinityName = {})
{
w->setFocusPolicy(Qt::StrongFocus);
auto dock = newDockWidget(name, options, layoutSaverOptions);
dock->setAffinityName(affinityName);
dock->setGuestView(std::shared_ptr<View>(new Views::ViewWrapper_qtwidgets(w)));
dock->setObjectName(name);
dock->view()->setGeometry(QRect(0, 0, 400, 400));
if (show) {
dock->show();
dock->dptr()->morphIntoFloatingWindow();
dock->view()->activateWindow();
Q_ASSERT(dock->window());
if (Platform::instance()->tests_waitForWindowActive(dock->view()->window(), 1000)) {
return dock;
}
qWarning() << Q_FUNC_INFO << "Couldn't activate window";
return nullptr;
} else {
return dock;
}
};
class EmbeddedWindow : public QWidget
{
public:
explicit EmbeddedWindow(Controllers::MainWindow *m)
: mainWindow(m)
{
}
~EmbeddedWindow() override;
Controllers::MainWindow *const mainWindow;
};
EmbeddedWindow::~EmbeddedWindow() = default;
inline EmbeddedWindow *createEmbeddedMainWindow(QSize sz)
{
static int count = 0;
count++;
// Tests a MainWindow which isn't a top-level window, but is embedded in another window
auto mainwindow = createMainWindow(QSize(600, 600), MainWindowOption_HasCentralFrame).release();
auto window = new EmbeddedWindow(mainwindow);
auto lay = new QVBoxLayout(window);
lay->setContentsMargins(100, 100, 100, 100);
lay->addWidget(qobject_cast<QWidget *>(mainwindow->view()->asQObject()));
window->show();
window->resize(sz);
return window;
}
class TestQtWidgets : public QObject
{
Q_OBJECT
public Q_SLOTS:
void initTestCase();
void cleanupTestCase();
private Q_SLOTS:
// TODO: Port these to QtQuick
void tst_mainWindowAlwaysHasCentralWidget();
void tst_dockableMainWindows();
void tst_mdi_mixed_with_docking();
void tst_mdi_mixed_with_docking2();
void tst_mdi_mixed_with_docking_setMDISize();
void tst_deleteOnClose();
void tstCloseNestedMdi();
void tstCloseNestedMDIPropagates();
// But these are fine to be widget only:
void tst_tabsNotClickable();
void tst_embeddedMainWindow();
void tst_restoreEmbeddedMainWindow();
void tst_negativeAnchorPositionWhenEmbedded();
void tst_negativeAnchorPositionWhenEmbedded_data();
void tst_closeRemovesFromSideBar();
void tst_restoreSideBar();
void tst_toggleActionOnSideBar();
void tst_deleteOnCloseWhenOnSideBar();
void tst_sidebarOverlayShowsAutohide();
void tst_sidebarOverlayGetsHiddenOnClick();
void tst_floatRemovesFromSideBar();
void tst_overlayedGeometryIsSaved();
void tst_overlayCrash();
// And fix these
void tst_floatingWindowDeleted();
void tst_addToSmallMainWindow6();
void tst_minSizeChanges();
void tst_maxSizePropagates();
void tst_maxSizePropagates2();
void tst_maxSizedFloatingWindow();
void tst_restoreResizesLayout();
void tst_restoreNonRelativeFloatingWindowGeometry();
void tst_maxSizeHonouredWhenDropped();
void tst_fixedSizePolicy();
void tst_maxSizeHonouredWhenAnotherDropped();
void tst_addToHiddenMainWindow();
void tst_maximumSizePolicy();
void tst_complex();
void tst_restoreFloatingMaximizedState();
};
void TestQtWidgets::tst_tabsNotClickable()
{
// Well, not a great unit-test, as it's only repro when it's Windows sending the native event
// Can't repro with fabricated events. Uncomment the WAIT and test different configs manually
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_Default | KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible);
auto dock1 = createDockWidget("dock1", new QWidget());
auto dock2 = createDockWidget("dock2", new QWidget());
dock1->addDockWidgetAsTab(dock2);
auto group = dock1->dptr()->group();
QCOMPARE(group->currentIndex(), 1);
QTest::qWait(500); // wait for window to get proper geometry
const QPoint clickPoint = group->tabBar()->mapToGlobal(group->tabBar()->rectForTab(0).center());
QCursor::setPos(clickPoint); // Just for visual debug when needed
pressOn(clickPoint, group->tabBar()->view());
releaseOn(clickPoint, group->tabBar()->view());
// WAIT // Uncomment for MANUAL test. Also test by adding Flag_AlwaysShowTabs
QCOMPARE(group->currentIndex(), 0);
}
void TestQtWidgets::tst_mainWindowAlwaysHasCentralWidget()
{
EnsureTopLevelsDeleted e;
auto m = createMainWindow();
QWidget *central = qobject_cast<Views::MainWindow_qtwidgets *>(m->view()->asQObject())->centralWidget();
auto dropArea = m->dropArea();
QVERIFY(dropArea);
QPointer<Controllers::Group> centralFrame = dropArea->centralFrame()->asGroupController();
QVERIFY(central);
QVERIFY(dropArea);
QCOMPARE(dropArea->count(), 1);
QVERIFY(centralFrame);
QCOMPARE(centralFrame->dockWidgetCount(), 0);
// Add a tab
auto dock = createDockWidget("doc1");
m->addDockWidgetAsTab(dock);
QCOMPARE(dropArea->count(), 1);
QCOMPARE(centralFrame->dockWidgetCount(), 1);
qDebug() << "Central widget width=" << central->size() << "; mainwindow="
<< m->size();
// Detach tab
QPoint globalPressPos = dragPointForWidget(centralFrame.data(), 0);
auto tabBar = centralFrame->tabBar()->view();
QVERIFY(tabBar);
qDebug() << "Detaching tab from dropArea->size=" << dropArea->size() << "; dropArea=" << dropArea;
drag(tabBar, globalPressPos, m->geometry().bottomRight() + QPoint(30, 30));
QVERIFY(centralFrame);
QCOMPARE(dropArea->count(), 1);
QCOMPARE(centralFrame->dockWidgetCount(), 0);
QVERIFY(dropArea->checkSanity());
}
void TestQtWidgets::tst_dockableMainWindows()
{
EnsureTopLevelsDeleted e;
auto m1 = createMainWindow();
auto dock1 = createDockWidget("dock1", new QPushButton("foo"));
m1->addDockWidget(dock1, Location_OnTop);
auto m2 = new KDDockWidgets::Views::MainWindow_qtwidgets("mainwindow-dockable");
auto m2Container = createDockWidget("mainwindow-dw", ( View * )m2);
auto menubar = m2->menuBar();
menubar->addMenu("File");
menubar->addMenu("View");
menubar->addMenu("Help");
m2Container->show();
auto dock21 = createDockWidget("dock21", new QPushButton("foo"));
auto dock22 = createDockWidget("dock22", new QPushButton("foo"));
m2->mainWindow()->addDockWidget(dock21, Location_OnLeft);
m2->mainWindow()->addDockWidget(dock22, Location_OnRight);
auto fw = m2Container->floatingWindow();
Controllers::TitleBar *fwTitleBar = fw->titleBar();
QVERIFY(fw->hasSingleFrame());
QVERIFY(fw->hasSingleDockWidget());
// Check that the inner-inner dock widgets have a visible title-bar
QVERIFY(dock21->titleBar()->isVisible());
QVERIFY(dock22->titleBar()->isVisible());
QVERIFY(dock21->titleBar() != fwTitleBar);
QVERIFY(dock22->titleBar() != fwTitleBar);
const QPoint startPoint = fwTitleBar->mapToGlobal(QPoint(5, 5));
const QPoint destination = startPoint + QPoint(20, 20);
// Check that we don't get the "Refusing to itself" warning. not actually dropping anywhere
drag(fwTitleBar->view(), startPoint, destination);
// The FloatingWindow has a single DockWidget, so it shows the title bar, while the Frame doesn't
QVERIFY(fwTitleBar->isVisible());
QVERIFY(!m2Container->dptr()->group()->titleBar()->isVisible());
fw->dropArea()->addDockWidget(dock1, Location::Location_OnLeft, nullptr);
// Now the FloatingWindow has two dock widgets, so our main window dock widget also shows the title bar
QVERIFY(fwTitleBar->isVisible());
QVERIFY(m2Container->dptr()->group()->titleBar()->isVisible());
// Put it how it was, FloatingWindow is single dock again
auto group1 = dock1->dptr()->group();
dock1->close();
Platform::instance()->tests_waitForDeleted(group1);
QVERIFY(fwTitleBar->isVisible());
QVERIFY(!m2Container->dptr()->group()->titleBar()->isVisible());
// Repeat, but instead of closing dock1, we float it
fw->dropArea()->addDockWidget(dock1, Location::Location_OnLeft, nullptr);
QVERIFY(fwTitleBar->isVisible());
QVERIFY(m2Container->dptr()->group()->titleBar()->isVisible());
group1 = dock1->dptr()->group();
group1->titleBar()->onFloatClicked();
QVERIFY(fwTitleBar->isVisible());
QVERIFY(!m2Container->dptr()->group()->titleBar()->isVisible());
fw->dropArea()->addDockWidget(dock1, Location::Location_OnLeft, nullptr);
}
void TestQtWidgets::tst_mdi_mixed_with_docking()
{
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(500, 500), MainWindowOption_HasCentralWidget);
auto dock1 = createDockWidget("1", new QPushButton("1"));
m->addDockWidget(dock1, Location_OnBottom);
auto mdiArea = new Views::MDIArea_qtwidgets();
m->setPersistentCentralView(std::shared_ptr<View>(new Views::ViewWrapper_qtwidgets(mdiArea)));
auto mdiWidget1 = createDockWidget("mdi1", new QPushButton("mdi1"));
auto mdiWidget2 = createDockWidget("mdi2", new QPushButton("mdi12"));
mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));
Controllers::Group *groupMDI1 = mdiWidget1->d->group();
Controllers::Group *group1 = dock1->d->group();
QVERIFY(!group1->isMDI());
QVERIFY(groupMDI1->isMDI());
QVERIFY(!group1->mdiLayout());
QVERIFY(groupMDI1->mdiLayout());
QVERIFY(!dock1->titleBar()->isMDI());
auto tb1 = mdiWidget1->titleBar();
QVERIFY(tb1->isMDI());
QVERIFY(Platform::instance()->tests_waitForEvent(tb1->view(), QEvent::Show));
QVERIFY(tb1->isVisible());
// Press the float button
tb1->onFloatClicked();
QVERIFY(mdiWidget1->d->lastPosition()->isValid());
QVERIFY(mdiWidget1->titleBar()->isVisible());
QVERIFY(mdiWidget1->isFloating());
// Dock again, and check it went back
mdiWidget1->titleBar()->onFloatClicked();
QVERIFY(!mdiWidget1->isFloating());
}
void TestQtWidgets::tst_mdi_mixed_with_docking2()
{
// Here, the MDI dock widgets are themselves main windows which will show drop-indicators.
// It will be super nested: MainWindow -> MDI -> MainWindow
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
auto dock1 = createDockWidget("1", new QPushButton("1"));
m->addDockWidget(dock1, Location_OnBottom);
auto mdiArea = new Views::MDIArea_qtwidgets();
m->setPersistentCentralView(std::shared_ptr<View>(new Views::ViewWrapper_qtwidgets(mdiArea)));
auto createSheet = [](int id) -> Controllers::DockWidget * {
auto dock = newDockWidget(QStringLiteral("dw-sheet-%1").arg(id), DockWidgetOption_MDINestable);
auto btn = new QPushButton(QStringLiteral("Sheet %1").arg(id));
auto btnw = std::shared_ptr<View>(new Views::ViewWrapper_qtwidgets(btn));
dock->setGuestView(btnw);
dock->setTitle(QStringLiteral("Sheet %1").arg(id));
return dock;
};
auto mdiWidget1 = createSheet(1);
auto mdiWidget2 = createSheet(2);
auto mdiWidget3 = createSheet(3);
/*auto mdiWidget4 = createSheet(4);
auto mdiWidget5 = createSheet(5);
auto mdiWidget6 = createSheet(6);*/
mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));
Controllers::Group *group1 = mdiWidget1->d->group();
Controllers::Group *mdiFrame1 = group1->mdiFrame();
QPointer<Controllers::Group> group2 = mdiWidget2->d->group();
QPointer<Controllers::Group> mdiFrame2 = group2->mdiFrame();
QPointer<DropArea> dropArea2 = group2->mdiDropAreaWrapper();
QPointer<DropArea> dropArea1 = group1->mdiDropAreaWrapper();
dropArea1->addDockWidget(mdiWidget3, Location_OnLeft, nullptr);
QVERIFY(!group1->isMDI());
QVERIFY(group1->isMDIWrapper());
QVERIFY(group1->mdiDockWidgetWrapper());
QVERIFY(dropArea1->isMDIWrapper());
QVERIFY(dropArea2->isMDIWrapper());
QVERIFY(!mdiFrame1->isMDIWrapper());
QVERIFY(mdiFrame1->isMDI());
QVERIFY(!mdiWidget1->d->isMDIWrapper());
// Test title bars:
auto tb1 = mdiWidget1->titleBar();
auto mdiTb1 = mdiFrame1->titleBar();
auto mdiTb2 = mdiFrame2->titleBar();
Platform::instance()->tests_waitForEvent(mdiTb1, QEvent::Show);
QVERIFY(mdiTb1->isVisible());
QVERIFY(mdiTb2->isVisible());
QVERIFY(tb1->isVisible());
QCOMPARE(tb1->title(), QString("Sheet 1"));
QCOMPARE(mdiTb1->title(), QString("dockwidgets-unit-tests"));
QCOMPARE(mdiTb2->title(), QString("Sheet 2"));
QVERIFY(tb1 != mdiTb1);
QCOMPARE(mdiWidget2->titleBar(), mdiTb2);
// Test that closing will delete the wrappers:
mdiWidget2->close();
QVERIFY(!mdiWidget2->isOpen());
Platform::instance()->tests_waitForDeleted(dropArea2);
QVERIFY(dropArea2.isNull());
QVERIFY(!mdiFrame2);
mdiWidget1->close();
QVERIFY(!mdiWidget1->isOpen());
QTest::qWait(500); // wait some event loops to make sure there's no delete later. (There isn't, but a bug could introduce them)
QVERIFY(!dropArea1.isNull()); // Not deleted, as sheet3 is still there
QCOMPARE(dropArea1->visibleCount(), 1);
QVERIFY(mdiTb1->isVisible());
QCOMPARE(mdiWidget3->titleBar(), mdiTb1);
Controllers::Group *group3 = mdiWidget3->d->group();
QVERIFY(!group3->titleBar()->isVisible());
mdiWidget3->close();
QVERIFY(Platform::instance()->tests_waitForDeleted(dropArea1));
QVERIFY(!mdiWidget3->isOpen());
QVERIFY(dropArea1.isNull());
// Reopen everything again:
mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));
group1 = mdiWidget1->d->group();
mdiFrame1 = group1->mdiFrame();
dropArea1 = group1->mdiDropAreaWrapper();
dropArea1->addDockWidget(mdiWidget3, Location_OnLeft, nullptr);
// Test floating:
group2 = mdiWidget2->d->group();
QPointer<Controllers::DockWidget> dwWrapper2 = group2->mdiDockWidgetWrapper();
dropArea2 = group2->mdiDropAreaWrapper();
QVERIFY(mdiWidget2->isVisible());
QVERIFY(group2->isMDIWrapper());
QVERIFY(dwWrapper2->d->isMDIWrapper());
mdiFrame2 = group2->mdiFrame();
mdiWidget2->setFloating(true);
QVERIFY(mdiWidget2->isFloating());
QVERIFY(!mdiWidget2->d->group()->isMDI());
QVERIFY(!mdiWidget2->d->group()->isMDIWrapper());
QVERIFY(Platform::instance()->tests_waitForDeleted(mdiFrame2));
QVERIFY(dropArea2.isNull());
QVERIFY(dwWrapper2.isNull());
auto mdiFrames = mdiArea->groups();
QCOMPARE(mdiFrames.count(), 1);
mdiFrame1 = mdiFrames.first();
QVERIFY(mdiFrame1->isMDI());
QVERIFY(mdiFrame1->hasNestedMDIDockWidgets());
auto mdiTitleBar1 = mdiFrame1->titleBar();
QVERIFY(mdiFrame1->titleBar()->isVisible());
mdiTitleBar1->makeWindow();
QVERIFY(Platform::instance()->tests_waitForDeleted(mdiFrame1));
QCOMPARE(mdiArea->groups().size(), 0);
// Dock again:
mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));
group1 = mdiWidget1->d->group();
dropArea1 = group1->mdiDropAreaWrapper();
dropArea1->addDockWidget(mdiWidget3, Location_OnLeft, nullptr);
// Detach an internal dock widget by dragging
const QPoint globalSrc = mdiWidget1->mapToGlobal(QPoint(5, 5));
const QPoint globalDest = globalSrc + QPoint(100, 100);
drag(mdiWidget1->view(), globalDest);
QCOMPARE(mdiArea->groups().count(), 2);
auto mdiTitleBar = mdiArea->groups().first()->titleBar();
QVERIFY(mdiTitleBar->isVisible());
QVERIFY(!mdiWidget3->isFloating());
QVERIFY(mdiWidget3->d->lastPosition()->isValid());
mdiTitleBar->onFloatClicked();
QVERIFY(mdiWidget3->isFloating());
QVERIFY(Platform::instance()->tests_waitForDeleted(mdiArea->groups().constFirst()));
QCOMPARE(mdiArea->groups().size(), 1);
QVERIFY(!mdiWidget2->isFloating());
Controllers::Group *lastMdiFrame = mdiArea->groups().constFirst();
QVERIFY(lastMdiFrame->titleBar()->isVisible());
QVERIFY(!lastMdiFrame->titleBar()->isFloating());
lastMdiFrame->titleBar()->onFloatClicked();
QVERIFY(mdiWidget2->isFloating());
// put it in the MDI area again
mdiWidget2->titleBar()->onFloatClicked();
QVERIFY(!mdiWidget2->isFloating());
}
void TestQtWidgets::tst_mdi_mixed_with_docking_setMDISize()
{
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
auto dock1 = createDockWidget("1", new QPushButton("1"));
m->addDockWidget(dock1, Location_OnBottom);
auto mdiArea = new Views::MDIArea_qtwidgets();
m->setPersistentCentralView(std::shared_ptr<View>(new Views::ViewWrapper_qtwidgets(mdiArea)));
auto createSheet = [](int id) -> Controllers::DockWidget * {
auto dock = newDockWidget(QStringLiteral("dw-sheet-%1").arg(id), DockWidgetOption_MDINestable);
dock->setGuestView(std::shared_ptr<View>(new Views::ViewWrapper_qtwidgets(new QPushButton(QStringLiteral("Sheet %1").arg(id)))));
dock->setTitle(QStringLiteral("Sheet %1").arg(id));
return dock;
};
auto mdiWidget1 = createSheet(1);
auto mdiWidget2 = createSheet(2);
mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));
Controllers::Group *group1 = mdiArea->groups().at(0);
const QSize sz1 = group1->view()->size();
const QSize increment(200, 200);
QVERIFY(mdiWidget1->d->mdiLayout());
mdiWidget1->setMDISize(sz1 + increment);
QCOMPARE(group1->size(), sz1 + increment);
}
// No need to port to QtQuick
void TestQtWidgets::tst_floatingWindowDeleted()
{
// Tests a case where the empty floating dock widget wouldn't be deleted
// Doesn't repro QTBUG-83030 unfortunately, as we already have an event loop running
// but let's leave this here nonetheless
class MyMainWindow : public KDDockWidgets::Views::MainWindow_qtwidgets
{
public:
MyMainWindow()
: KDDockWidgets::Views::MainWindow_qtwidgets("tst_floatingWindowDeleted", MainWindowOption_None)
{
auto dock1 = newDockWidget(QStringLiteral("DockWidget #1"));
auto myWidget = new QWidget();
dock1->setGuestView(std::shared_ptr<View>(new Views::ViewWrapper_qtwidgets(myWidget)));
dock1->view()->resize(QSize(600, 600));
dock1->show();
auto dock2 = newDockWidget(QStringLiteral("DockWidget #2"));
myWidget = new QWidget();
dock2->setGuestView(std::shared_ptr<View>(new Views::ViewWrapper_qtwidgets(myWidget)));
dock2->view()->resize(QSize(600, 600));
dock2->show();
dock1->addDockWidgetAsTab(dock2);
}
};
MyMainWindow m;
}
void TestQtWidgets::tst_addToSmallMainWindow6()
{
EnsureTopLevelsDeleted e;
// Test test shouldn't spit any warnings
QWidget container;
auto lay = new QVBoxLayout(&container);
auto m = Platform::instance()->createMainWindow("MyMainWindow_tst_addToSmallMainWindow8", {},
MainWindowOption_None);
auto qmainwindow = qobject_cast<Views::MainWindow_qtwidgets *>(m->view()->asQObject());
lay->addWidget(qmainwindow);
container.resize(100, 100);
Platform::instance()->tests_waitForEvent(&container, QEvent::Resize);
container.show();
Platform::instance()->tests_waitForResize(m);
auto dock1 = createDockWidget("dock1", Platform::instance()->tests_createView({ true, {}, QSize(50, 240) }));
auto dock2 = createDockWidget("dock2", Platform::instance()->tests_createView({ true, {}, QSize(50, 240) }));
m->addDockWidget(dock1, KDDockWidgets::Location_OnBottom);
m->addDockWidget(dock2, KDDockWidgets::Location_OnBottom);
Platform::instance()->tests_waitForResize(m);
QVERIFY(m->dropArea()->checkSanity());
delete m;
}
void TestQtWidgets::tst_closeRemovesFromSideBar()
{
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None);
auto dw1 = newDockWidget(QStringLiteral("1"));
auto fw1 = dw1->window();
m1->addDockWidget(dw1, Location_OnBottom);
m1->moveToSideBar(dw1);
QVERIFY(!dw1->isOverlayed());
QVERIFY(!dw1->isVisible());
QVERIFY(dw1->isInSideBar());
Controllers::SideBar *sb = m1->sideBarForDockWidget(dw1);
QVERIFY(sb);
// Overlay it:
sb->toggleOverlay(dw1);
QVERIFY(dw1->isOverlayed());
QVERIFY(dw1->isVisible());
QCOMPARE(dw1->sideBarLocation(), sb->location());
QVERIFY(dw1->isInMainWindow());
QVERIFY(!dw1->isFloating());
// Close it while it's overlayed:
dw1->close();
QVERIFY(!dw1->isInMainWindow());
QVERIFY(!dw1->isOverlayed());
QVERIFY(!dw1->isVisible());
QCOMPARE(dw1->sideBarLocation(), SideBarLocation::None);
}
void TestQtWidgets::tst_restoreSideBar()
{
SideBarLocation loc;
QByteArray serialized; // serialization after having 1 sidebar visible
QByteArray beforeSideBarSerialized; // serialization without any sidebar visible
{
LayoutSaver saver;
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
auto dw1 = newDockWidget(QStringLiteral("1"));
auto fw1 = dw1->window();
m1->addDockWidget(dw1, Location_OnBottom);
beforeSideBarSerialized = saver.serializeLayout();
QVERIFY(!m1->anySideBarIsVisible());
m1->moveToSideBar(dw1);
QVERIFY(m1->anySideBarIsVisible());
QVERIFY(!dw1->isOverlayed());
QVERIFY(!dw1->isVisible());
loc = dw1->sideBarLocation();
QVERIFY(loc != SideBarLocation::None);
serialized = saver.serializeLayout();
m1.reset();
}
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
auto dw1 = newDockWidget(QStringLiteral("1"));
auto fw1 = dw1->window();
m1->addDockWidget(dw1, Location_OnBottom);
QVERIFY(!m1->anySideBarIsVisible());
QVERIFY(!dw1->isOverlayed());
QVERIFY(dw1->isVisible());
QVERIFY(!dw1->isFloating());
QVERIFY(dw1->isInMainWindow());
LayoutSaver restorer;
restorer.restoreLayout(serialized);
QVERIFY(!dw1->isOverlayed());
QVERIFY(!dw1->isVisible());
QVERIFY(!dw1->isInMainWindow());
QVERIFY(m1->anySideBarIsVisible());
QCOMPARE(loc, dw1->sideBarLocation());
restorer.restoreLayout(beforeSideBarSerialized);
QVERIFY(!dw1->isOverlayed());
QVERIFY(dw1->isVisible());
QVERIFY(!dw1->isFloating());
QVERIFY(dw1->isInMainWindow());
QVERIFY(!m1->anySideBarIsVisible());
}
void TestQtWidgets::tst_toggleActionOnSideBar()
{
// When a dock widget is in the sidebar and we use DockWidget::toggleAction() then it should
// toggle visibility without removing it from the sidebar
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
auto dw1 = newDockWidget("1");
m1->addDockWidget(dw1, Location_OnBottom);
dw1->moveToSideBar();
QVERIFY(!dw1->isVisible());
QVERIFY(!dw1->isOverlayed());
QVERIFY(dw1->isInSideBar());
QVERIFY(!dw1->isInMainWindow());
QAction *action = dw1->toggleAction();
action->trigger();
QVERIFY(dw1->isVisible());
QVERIFY(dw1->isOverlayed());
QVERIFY(dw1->isInMainWindow());
QVERIFY(dw1->isInSideBar());
action->trigger();
QVERIFY(!dw1->isOverlayed());
QVERIFY(!dw1->isInMainWindow());
QVERIFY(dw1->isInSideBar());
QVERIFY(!dw1->isInMainWindow());
}
void TestQtWidgets::tst_deleteOnCloseWhenOnSideBar()
{
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
auto m = createMainWindow(QSize(800, 500), MainWindowOption_None);
QPointer<Controllers::DockWidget> dock1 = createDockWidget("dock1", Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }), DockWidgetOption_DeleteOnClose);
m->addDockWidget(dock1, Location_OnLeft);
dock1->moveToSideBar();
QVERIFY(dock1);
QVERIFY(dock1->isInSideBar());
QTest::qWait(500);
QVERIFY(dock1);
}
void TestQtWidgets::tst_sidebarOverlayShowsAutohide()
{
// Tests that overlayed widgets show the "Disable auto-hide" button
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
auto dw1 = newDockWidget(QStringLiteral("1"));
m1->addDockWidget(dw1, Location_OnBottom);
QVERIFY(dw1->titleBar()->supportsAutoHideButton());
m1->moveToSideBar(dw1);
m1->overlayOnSideBar(dw1);
QVERIFY(dw1->isOverlayed());
auto titleBar = dw1->titleBar();
QVERIFY(titleBar->isVisible());
QVERIFY(titleBar->supportsAutoHideButton());
delete dw1;
}
void TestQtWidgets::tst_sidebarOverlayGetsHiddenOnClick()
{
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
{
// Case #1 click on another dockwidget should hide the overlay
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
auto dw1 = newDockWidget(QStringLiteral("1"));
auto dw2 = newDockWidget(QStringLiteral("2"));
m1->addDockWidget(dw1, Location_OnBottom);
m1->addDockWidget(dw2, Location_OnBottom);
m1->moveToSideBar(dw1);
m1->overlayOnSideBar(dw1);
QVERIFY(dw1->isOverlayed());
Tests::clickOn(dw2->mapToGlobal(dw2->rect().bottomLeft() + QPoint(5, -5)), dw2->view());
QVERIFY(!dw1->isOverlayed());
auto widget2 = Platform::instance()->tests_createView({ true });
dw2->setGuestView(widget2->asWrapper());
m1->overlayOnSideBar(dw1);
QVERIFY(dw1->isOverlayed());
Tests::clickOn(widget2->mapToGlobal(widget2->rect().bottomLeft() + QPoint(5, -5)), widget2);
QVERIFY(!dw1->isOverlayed());
m1.reset();
delete dw1;
}
{
// Case #1 click on empty main window space, should hide the overlay
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
auto dw1 = newDockWidget(QStringLiteral("1"));
m1->addDockWidget(dw1, Location_OnBottom);
m1->moveToSideBar(dw1);
m1->overlayOnSideBar(dw1);
QVERIFY(dw1->isOverlayed());
const QPoint localPt(100, 250);
Tests::clickOn(m1->mapToGlobal(m1->rect().topLeft() + localPt), m1->view()->childViewAt(localPt).get());
QVERIFY(!dw1->isOverlayed());
}
}
void TestQtWidgets::tst_floatRemovesFromSideBar()
{
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
auto dw1 = newDockWidget(QStringLiteral("1"));
m1->addDockWidget(dw1, Location_OnBottom);
m1->moveToSideBar(dw1);
m1->overlayOnSideBar(dw1);
QVERIFY(dw1->isOverlayed());
QVERIFY(!dw1->isFloating());
QVERIFY(dw1->isInMainWindow());
dw1->setFloating(true);
QVERIFY(!dw1->isOverlayed());
QVERIFY(dw1->isFloating());
QVERIFY(!dw1->isInMainWindow());
QCOMPARE(dw1->sideBarLocation(), SideBarLocation::None);
// Also test a crash I got
m1->addDockWidget(dw1, Location_OnBottom);
auto tb = dw1->titleBar();
QVERIFY(tb->isVisible());
tb->onFloatClicked();
}
void TestQtWidgets::tst_overlayedGeometryIsSaved()
{
// Tests that after resizing an overlayed widget, and then hide+show, its size is preserved
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
auto dw1 = newDockWidget(QStringLiteral("1"));
m1->addDockWidget(dw1, Location_OnBottom);
m1->moveToSideBar(dw1, SideBarLocation::North);
m1->overlayOnSideBar(dw1);
Controllers::Group *group = dw1->dptr()->group();
QVERIFY(group->isOverlayed());
QCOMPARE(dw1->sideBarLocation(), SideBarLocation::North);
QVERIFY(group->height() > 0);
const int newHeight = group->height() + 300;
group->view()->setHeight(newHeight);
m1->toggleOverlayOnSideBar(dw1);
m1->toggleOverlayOnSideBar(dw1);
group = dw1->dptr()->group();
QCOMPARE(group->height(), newHeight);
}
void TestQtWidgets::tst_overlayCrash()
{
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
auto dw1 = newDockWidget(QStringLiteral("1"));
m1->addDockWidget(dw1, Location_OnBottom);
auto dw2 = newDockWidget(QStringLiteral("2"));
m1->addDockWidget(dw2, Location_OnBottom);
m1->moveToSideBar(dw1);
m1->toggleOverlayOnSideBar(dw1);
dw1->close();
auto tb = dw2->titleBar();
QVERIFY(tb->isVisible());
pressOn(tb->mapToGlobal(QPoint(5, 5)), tb->view());
}
void TestQtWidgets::tst_embeddedMainWindow()
{
EnsureTopLevelsDeleted e;
// Tests a MainWindow which isn't a top-level window, but is embedded in another window
EmbeddedWindow *window = createEmbeddedMainWindow(QSize(800, 800));
auto dock1 = createDockWidget("1", new QPushButton("1"));
window->mainWindow->addDockWidget(dock1, Location_OnTop);
dock1->setFloating(true);
auto dropArea = window->mainWindow->dropArea();
auto fw = dock1->floatingWindow();
dragFloatingWindowTo(fw, dropArea, DropLocation_Left);
auto layout = dropArea;
QVERIFY(Platform::instance()->tests_waitForDeleted(fw));
QCOMPARE(layout->count(), 2); // 2, as it has the central group
QCOMPARE(layout->visibleCount(), 2);
layout->checkSanity();
delete window;
}
void TestQtWidgets::tst_restoreEmbeddedMainWindow()
{
EnsureTopLevelsDeleted e;
// Tests a MainWindow which isn't a top-level window, but is embedded in another window
EmbeddedWindow *window = createEmbeddedMainWindow(QSize(800, 800));
auto dock1 = createDockWidget("1", new QPushButton("1"));
window->mainWindow->addDockWidget(dock1, Location_OnTop);
const QPoint originalPos(250, 250);
const QSize originalSize = window->size();
window->move(originalPos);
LayoutSaver saver;
QByteArray saved = saver.serializeLayout();
QVERIFY(!saved.isEmpty());
window->resize(555, 555);
const QPoint newPos(500, 500);
window->move(newPos);
QVERIFY(saver.restoreLayout(saved));
QCOMPARE(window->pos(), originalPos);
QCOMPARE(window->size(), originalSize);
window->mainWindow->layout()->checkSanity();
delete window;
}
void TestQtWidgets::tst_negativeAnchorPositionWhenEmbedded_data()
{
QTest::addColumn<bool>("embedded");
QTest::newRow("false") << false;
QTest::newRow("true") << true;
}
void TestQtWidgets::tst_negativeAnchorPositionWhenEmbedded()
{
QFETCH(bool, embedded);
EnsureTopLevelsDeleted e;
MainWindow *m = nullptr;
if (embedded) {
auto em = createEmbeddedMainWindow(QSize(500, 500));
m = em->mainWindow;
} else {
m = createMainWindow(QSize(500, 500), MainWindowOption_HasCentralFrame).release();
m->view()->resize(QSize(500, 500));
m->show();
}
auto layout = m->multiSplitter();
auto w1 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
auto w2 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
auto d1 = createDockWidget("1", w1);
auto d2 = createDockWidget("2", w2);
auto d3 = createDockWidget("3", w2);
m->addDockWidget(d1, Location_OnLeft);
m->addDockWidget(d2, Location_OnLeft);
m->addDockWidget(d3, Location_OnLeft);
layout->checkSanity();
delete static_cast<Views::ViewWrapper_qtwidgets *>(m->window().get())->widget();
}
void TestQtWidgets::tst_restoreResizesLayout()
{
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(500, 500), MainWindowOption_None);
auto dock1 = createDockWidget("1", new QPushButton("1"));
m->addDockWidget(dock1, Location_OnLeft);
LayoutSaver saver;
QVERIFY(saver.saveToFile("layout_tst_restoreResizesLayout.json"));
// Now resize the window, and then restore. The layout should have the new size
auto layout = m->multiSplitter();
m->view()->resize(QSize(1050, 1050));
QCOMPARE(m->size(), QSize(1050, 1050));
LayoutSaver restorer(RestoreOption_RelativeToMainWindow);
QVERIFY(restorer.restoreFromFile("layout_tst_restoreResizesLayout.json"));
QVERIFY(layout->checkSanity());
QCOMPARE(m->dropArea()->size(), layout->layoutSize());
QVERIFY(layout->checkSanity());
}
void TestQtWidgets::tst_restoreNonRelativeFloatingWindowGeometry()
{
// Tests that disabling RelativeFloatingWindowGeometry works
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(500, 500), MainWindowOption_None);
auto dock1 = createDockWidget("1", new QPushButton("1"));
// Also test that invisible dock doesn't change size
auto dock2 = createDockWidget("2", new QPushButton("2"), {}, {}, /*show=*/false);
LayoutSaver saver(RestoreOption_RelativeToMainWindow);
saver.dptr()->m_restoreOptions.setFlag(InternalRestoreOption::RelativeFloatingWindowGeometry,
false);
const QByteArray saved = saver.serializeLayout();
const QSize floatingWindowSize = dock1->window()->size();
const QSize floatingWindowSize2 = dock2->window()->size();
m->view()->resize(QSize(m->width() * 2, m->height()));
saver.restoreLayout(saved);
QVERIFY(dock2->isFloating());
QVERIFY(!dock2->isOpen());
QCOMPARE(dock1->window()->size(), floatingWindowSize);
QCOMPARE(dock2->window()->size(), floatingWindowSize2);
}
void TestQtWidgets::tst_maximumSizePolicy()
{
EnsureTopLevelsDeleted e;
const int maxHeight = 250;
auto widget = Platform::instance()->tests_createView({ true, QSize(250, maxHeight), QSize(200, 200) });
widget->setSizePolicy(SizePolicy::Preferred, SizePolicy::Maximum);
auto dock1 = createDockWidget("dock1", widget);
dock1->show();
dock1->window()->resize(QSize(500, 500));
auto window1 = dock1->window();
dock1->close();
dock1->show();
auto oldFw2 = dock1->window();
const int tolerance = 50;
QVERIFY(dock1->window()->height() <= maxHeight + tolerance); // +tolerance as the floating window is a bit bigger, due to margins etc.
QVERIFY(dock1->height() <= maxHeight);
auto m1 = createMainWindow();
auto dock2 = createDockWidget("dock2", new QPushButton("foo"));
m1->addDockWidget(dock2, Location_OnTop);
m1->view()->resize(QSize(2000, 3000));
// Make the floating window big, and see if the suggested highlight is still small
dock1->window()->resize(QSize(dock1->width(), 800));
{
WindowBeingDragged wbd1(dock1->floatingWindow());
const QRect highlightRect = m1->multiSplitter()->rectForDrop(&wbd1, Location_OnBottom, nullptr);
QVERIFY(highlightRect.height() <= maxHeight + tolerance);
}
// Now drop it, and check too
m1->addDockWidget(dock1, Location_OnBottom);
QVERIFY(dock1->height() <= maxHeight);
}
void TestQtWidgets::tst_minSizeChanges()
{
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(600, 600), MainWindowOption_None);
m->show();
auto w1 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
auto w2 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
auto d1 = newDockWidget("1");
d1->setGuestView(w1->asWrapper());
auto d2 = newDockWidget("2");
d2->setGuestView(w2->asWrapper());
m->addDockWidget(d1, Location_OnTop);
m->addDockWidget(d2, Location_OnTop, nullptr, InitialVisibilityOption::StartHidden);
auto layout = m->multiSplitter();
// 1. d2 is a placeholder, let's change its min size before showing it
w2->setMinimumSize(QSize(800, 800));
d2->show();
Item *item2 = layout->itemForFrame(d2->dptr()->group());
QVERIFY(layout->checkSanity());
Platform::instance()->tests_waitForResize(m->view());
QVERIFY(item2->width() >= 800);
QVERIFY(item2->height() >= 800);
QVERIFY(m->height() >= 1200);
// 2. d1 is visible, let's change its min size
w1->setMinimumSize(QSize(800, 800));
Platform::instance()->tests_waitForResize(m->view());
layout->checkSanity();
QVERIFY(m->height() >= 1600);
// add a small one to the middle
auto w3 = Platform::instance()->tests_createView({ true, {}, QSize(100, 100) });
auto d3 = newDockWidget("3");
d3->setGuestView(w3->asWrapper());
m->addDockWidget(d3, Location_OnTop, d1);
}
void TestQtWidgets::tst_maxSizePropagates()
{
// Tests that the DockWidget gets the min and max size of its guest widget
EnsureTopLevelsDeleted e;
auto dock1 = newDockWidget("dock1");
auto w = Platform::instance()->tests_createView({ true, {}, QSize(200, 200) });
w->setMinimumSize(QSize(120, 120));
w->setMaximumSize(QSize(500, 500));
dock1->setGuestView(w->asWrapper());
dock1->show();
QCOMPARE(dock1->view()->minSize(), w->minSize());
QCOMPARE(dock1->view()->maximumSize(), w->maximumSize());
w->setMinimumSize(QSize(121, 121));
w->setMaximumSize(QSize(501, 501));
Platform::instance()->tests_waitForEvent(w, QEvent::LayoutRequest);
QCOMPARE(dock1->view()->minSize(), w->minSize());
QCOMPARE(dock1->view()->maximumSize(), w->maximumSize());
// Now let's see if our Frame also has proper size-constraints
Controllers::Group *group = dock1->dptr()->group();
QCOMPARE(group->view()->maxSizeHint().expandedTo(w->maximumSize()), group->view()->maxSizeHint());
}
void TestQtWidgets::tst_maxSizedFloatingWindow()
{
// Tests that FloatingWindows get a proper max-size, if its dock widget has one
EnsureTopLevelsDeleted e;
auto dock1 = newDockWidget("dock1");
auto dock2 = newDockWidget("dock2");
auto w = Platform::instance()->tests_createView({ true });
w->setMinimumSize(QSize(120, 100));
w->setMaximumSize(QSize(300, 300));
dock1->setGuestView(w->asWrapper());
dock1->show();
dock2->show();
auto window1 = dock1->window();
auto window2 = dock2->window();
Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest);
QVERIFY(window1->maximumSize().width() < 500);
QVERIFY(window1->maximumSize().height() < 500);
QVERIFY(window2->maximumSize().width() > 500);
QVERIFY(window2->maximumSize().height() > 500);
auto hasMax = [&window1] {
const QSize max = window1->maximumSize();
return max.width() < 500 && max.height() < 500;
};
// Adding side-by-side, we don't honour max size (yet)
dock1->addDockWidgetToContainingWindow(dock2, Location_OnBottom);
Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest);
QVERIFY(window1->maximumSize().width() > 500);
QVERIFY(window1->maximumSize().height() > 500);
// Close dw2, we have a single dock widget again, we honour max-size
dock2->close();
Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest);
QVERIFY(hasMax());
dock1->addDockWidgetAsTab(dock2);
Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest);
QVERIFY(!hasMax());
dock2->close();
Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest);
QVERIFY(hasMax());
}
void TestQtWidgets::tst_maxSizeHonouredWhenAnotherDropped()
{
// dock1 is docked, and has small max-height.
// When dropping dock2, which is small too, dock2 should occupy all the height except dock1's max-height
// i.e. dock2 should expand and eat all available space
EnsureTopLevelsDeleted e;
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None);
auto dock1 = newDockWidget("dock1");
auto w = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
w->setMinimumSize(QSize(120, 100));
w->setMaximumSize(QSize(300, 150));
dock1->setGuestView(w->asWrapper());
m1->addDockWidget(dock1, Location_OnLeft);
auto dock2 = newDockWidget("dock2");
m1->addDockWidget(dock2, Location_OnBottom);
auto root = m1->multiSplitter()->rootItem();
Controllers::Separator *separator = root->separators().constFirst();
const int min1 = root->minPosForSeparator_global(separator);
const int max2 = root->maxPosForSeparator_global(separator);
QVERIFY(separator->position() >= min1);
QVERIFY(separator->position() <= max2);
const int item1MaxHeight = dock1->dptr()->group()->view()->maxSizeHint().height();
QVERIFY(dock1->dptr()->group()->view()->height() <= item1MaxHeight);
root->dumpLayout();
QCOMPARE(dock2->dptr()->group()->view()->height(),
root->height() - item1MaxHeight - Item::separatorThickness);
}
void TestQtWidgets::tst_addToHiddenMainWindow()
{
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralFrame, {}, false);
auto w1 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
auto w2 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
auto d1 = createDockWidget("1", w1);
auto d2 = createDockWidget("2", w2);
m->addDockWidget(d1, Location_OnTop);
m->addDockWidget(d2, Location_OnTop);
QVERIFY(!m->isVisible());
d1->setFloating(true);
d2->setFloating(false);
m->layout()->checkSanity();
}
void TestQtWidgets::tst_maxSizePropagates2()
{
EnsureTopLevelsDeleted e;
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None);
auto dock1 = newDockWidget("dock1");
auto w = Platform::instance()->tests_createView({ true, {}, QSize(200, 200) });
w->setMinimumSize(QSize(120, 120));
w->setMaximumSize(QSize(300, 500));
dock1->setGuestView(w->asWrapper());
dock1->show();
auto dock2 = newDockWidget("dock2");
auto dock3 = newDockWidget("dock3");
auto dock4 = newDockWidget("dock4");
m1->addDockWidget(dock2, Location_OnLeft);
m1->addDockWidget(dock3, Location_OnRight);
m1->addDockWidget(dock4, Location_OnBottom, dock3);
m1->addDockWidget(dock1, Location_OnLeft, dock4);
Controllers::Group *group1 = dock1->dptr()->group();
Layouting::ItemBoxContainer *root = m1->multiSplitter()->rootItem();
Item *item1 = root->itemForWidget(group1->view());
auto vertSep1 = root->separators().constFirst();
const int min1 = root->minPosForSeparator_global(vertSep1);
ItemBoxContainer *container1 = item1->parentBoxContainer();
auto innerVertSep1 = container1->separators().constFirst();
const int minInnerSep = container1->minPosForSeparator_global(innerVertSep1);
const int maxInnerSep = container1->maxPosForSeparator_global(innerVertSep1);
root->requestSeparatorMove(vertSep1, -(vertSep1->position() - min1));
QVERIFY(group1->width() <= group1->view()->maxSizeHint().width());
container1->requestSeparatorMove(innerVertSep1, -(innerVertSep1->position() - minInnerSep));
QVERIFY(group1->width() <= group1->view()->maxSizeHint().width());
container1->requestSeparatorMove(innerVertSep1, maxInnerSep - innerVertSep1->position());
QVERIFY(group1->width() <= group1->view()->maxSizeHint().width());
}
void TestQtWidgets::tst_maxSizeHonouredWhenDropped()
{
EnsureTopLevelsDeleted e;
auto m1 = createMainWindow();
auto dock1 = newDockWidget("dock1");
auto dock2 = newDockWidget("dock2");
m1->addDockWidget(dock1, Location_OnTop);
m1->view()->resize(QSize(2000, 2000));
auto w2 = Platform::instance()->tests_createView({ true, {}, { 0, 0 } });
dock2->setGuestView(w2->asWrapper());
const int maxWidth = 200;
w2->setMaximumSize(QSize(maxWidth, 200));
m1->addDockWidget(dock2, Location_OnLeft);
const int droppedWidth = dock2->dptr()->group()->width();
QVERIFY(droppedWidth < maxWidth + 50); // +50 to cover any margins and waste by QTabWidget
// Try again, but now dropping a multisplitter
dock2->setFloating(true);
auto fw = dock2->floatingWindow();
m1->dropArea()->drop(fw->view(), Location_OnLeft, nullptr);
QCOMPARE(dock2->dptr()->group()->width(), droppedWidth);
}
void TestQtWidgets::tst_fixedSizePolicy()
{
// tests that KDDW also takes into account QSizePolicy::Fixed for calculating the max size hint.
// Since QPushButton for example doesn't set QWidget::maximumSize(), but instead uses sizeHint()
// + QSizePolicy::Fixed.
EnsureTopLevelsDeleted e;
auto button = new QPushButton("one");
auto dock1 = createDockWidget("dock1", button);
Controllers::Group *group = dock1->dptr()->group();
// Just a precondition from the test. If QPushButton ever changes, replace with a QWidget and set fixed size policy
QCOMPARE(button->sizePolicy().verticalPolicy(), QSizePolicy::Fixed);
const int buttonMaxHeight = button->sizeHint().height();
QCOMPARE(dock1->view()->sizeHint(), button->sizeHint());
QCOMPARE(dock1->view()->verticalSizePolicy(), SizePolicy(button->sizePolicy().verticalPolicy()));
QCOMPARE(dock1->view()->horizontalSizePolicy(), SizePolicy(button->sizePolicy().horizontalPolicy()));
QCOMPARE(group->view()->maxSizeHint().height(), qMax(buttonMaxHeight, Layouting::Item::hardcodedMinimumSize.height()));
}
void TestQtWidgets::tst_restoreFloatingMaximizedState()
{
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_TitleBarHasMaximizeButton);
auto dock1 = createDockWidget("dock1", Platform::instance()->tests_createView({ true }));
const QRect originalNormalGeometry = dock1->floatingWindow()->view()->normalGeometry();
dock1->floatingWindow()->view()->showMaximized();
qDebug() << originalNormalGeometry;
QCOMPARE(dock1->floatingWindow()->view()->window()->windowState(), Qt::WindowMaximized);
LayoutSaver saver;
const QByteArray saved = saver.serializeLayout();
saver.restoreLayout(saved);
QCOMPARE(dock1->floatingWindow()->view()->window()->windowState(), Qt::WindowMaximized);
QCOMPARE(dock1->floatingWindow()->view()->normalGeometry(), originalNormalGeometry);
dock1->floatingWindow()->view()->showNormal();
QCOMPARE(dock1->floatingWindow()->view()->normalGeometry(), originalNormalGeometry);
}
void TestQtWidgets::tst_complex()
{
// Tests some anchors out of bounds I got
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(600, 500), MainWindowOption_None);
auto layout = m->multiSplitter();
m->view()->resize(QSize(3266, 2239));
m->show();
Controllers::DockWidget::List docks;
QVector<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 = Platform::instance()->tests_createView({ true, {}, minSizes.at(i) });
auto dw = newDockWidget(QString::number(i));
dw->setGuestView(widget->asWrapper());
docks << dw;
}
for (int i = 0; i < num; ++i) {
m->addDockWidget(docks[i], locations[i], nullptr, options[i]);
layout->checkSanity();
docks[i]->setFloating(floatings[i]);
layout->checkSanity();
}
m->show();
// Cleanup
qDeleteAll(docks);
qDeleteAll(DockRegistry::self()->groups());
}
void TestQtWidgets::tst_deleteOnClose()
{
// QtQuick doesn't support autohide/pin/sidebar yet.
// Tests that restoring the side-bar-overlay will call the users dock widget factory in case
// the dock widget was deleted already
EnsureTopLevelsDeleted e;
KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
KDDockWidgets::Config::self().setDockWidgetFactoryFunc([](const QString &name) {
return createDockWidget(name, Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }), DockWidgetOption_DeleteOnClose);
});
auto m = createMainWindow(QSize(800, 500), MainWindowOption_None);
QPointer<Controllers::DockWidget> dock1 = createDockWidget("1", Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }), DockWidgetOption_DeleteOnClose);
m->addDockWidget(dock1, Location_OnLeft);
m->moveToSideBar(dock1);
m->overlayOnSideBar(dock1);
LayoutSaver saver;
const QByteArray saved = saver.serializeLayout();
QVERIFY(dock1->isVisible());
dock1->close();
QVERIFY(Platform::instance()->tests_waitForDeleted(dock1));
QVERIFY(!dock1.data());
saver.restoreLayout(saved);
}
void TestQtWidgets::tstCloseNestedMdi()
{
// Tests a bug where closing a mdi dock widget would close its main window too
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
QPointer<Controllers::MainWindow> p = m.get();
auto mdi = new KDDockWidgets::Views::MDIArea_qtwidgets();
m->setPersistentCentralView(mdi->asWrapper());
auto dock1 = new KDDockWidgets::Views::DockWidget_qtwidgets(QStringLiteral("MyDock1"));
dock1->setWidget(new QPushButton("1"));
mdi->addDockWidget(dock1, {});
dock1->dockWidget()->titleBar()->onCloseClicked();
QVERIFY(p);
QVERIFY(m->isVisible());
}
void TestQtWidgets::tstCloseNestedMDIPropagates()
{
auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
QPointer<Controllers::MainWindow> p = m.get();
auto mdi = new KDDockWidgets::Views::MDIArea_qtwidgets();
m->setPersistentCentralView(mdi->asWrapper());
auto dock1 = new KDDockWidgets::Views::DockWidget_qtwidgets(QStringLiteral("MyDock1"));
auto nonClosableWidget = Platform::instance()->tests_createNonClosableView();
dock1->dockWidget()->setGuestView(nonClosableWidget->asWrapper());
mdi->addDockWidget(dock1, {});
auto dock2 = new KDDockWidgets::Views::DockWidget_qtwidgets(QStringLiteral("MyDock2"));
auto nonClosableWidget2 = Platform::instance()->tests_createNonClosableView();
dock2->dockWidget()->setGuestView(nonClosableWidget2->asWrapper());
dock2->show();
Platform::instance()->tests_waitForEvent(dock1->controller(), QEvent::Show);
QVERIFY(dock1->isVisible());
QVERIFY(dock2->isVisible());
m->close();
QVERIFY(dock2->isVisible());
QVERIFY(dock1->isVisible());
}
void TestQtWidgets::initTestCase()
{
KDDockWidgets::Platform::instance()->installMessageHandler();
}
void TestQtWidgets::cleanupTestCase()
{
KDDockWidgets::Platform::instance()->uninstallMessageHandler();
}
int main(int argc, char *argv[])
{
// Might be disabled by env var
const auto frontends = Platform::instance()->frontendTypes();
if (std::find(frontends.cbegin(), frontends.cend(), FrontendType::QtWidgets) == frontends.cend())
return 0;
Platform::tests_initPlatform(argc, argv, FrontendType::QtWidgets);
TestQtWidgets test;
const int exitCode = QTest::qExec(&test, argc, argv);
Platform::tests_deinitPlatform();
return exitCode;
}
#include "tst_qtwidgets.moc"