/*
  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(Views::ViewWrapper_qtwidgets::create(w));
    dock->setObjectName(name);
    dock->view()->setGeometry(QRect(0, 0, 400, 400));
    if (show) {
        dock->show();
        dock->dptr()->morphIntoFloatingWindow();
        dock->view()->activateWindow();
        Q_ASSERT(dock->window());
        if (Platform::instance()->tests_waitForWindowActive(dock->view()->window(), 1000)) {
            return dock;
        }
        qWarning() << Q_FUNC_INFO << "Couldn't activate window";
        return nullptr;
    } else {
        return dock;
    }
};

class EmbeddedWindow : public QWidget
{
public:
    explicit EmbeddedWindow(Controllers::MainWindow *m)
        : mainWindow(m)
    {
    }

    ~EmbeddedWindow() override;

    Controllers::MainWindow *const mainWindow;
};
EmbeddedWindow::~EmbeddedWindow() = default;


inline EmbeddedWindow *createEmbeddedMainWindow(QSize sz)
{
    static int count = 0;
    count++;
    // Tests a MainWindow which isn't a top-level window, but is embedded in another window
    auto mainwindow = createMainWindow(QSize(600, 600), MainWindowOption_HasCentralFrame).release();

    auto window = new EmbeddedWindow(mainwindow);

    auto lay = new QVBoxLayout(window);
    lay->setContentsMargins(100, 100, 100, 100);
    lay->addWidget(dynamic_cast<QWidget *>(mainwindow->view()));

    window->show();
    window->resize(sz);
    return window;
}

class TestQtWidgets : public QObject
{
    Q_OBJECT

public Q_SLOTS:
    void initTestCase();
    void cleanupTestCase();

private Q_SLOTS:
    // TODO: Port these to QtQuick
    void tst_mainWindowAlwaysHasCentralWidget();
    void tst_dockableMainWindows();
    void tst_mdi_mixed_with_docking();
    void tst_mdi_mixed_with_docking2();
    void tst_mdi_mixed_with_docking_setMDISize();
    void tst_deleteOnClose();
    void tstCloseNestedMdi();
    void tstCloseNestedMDIPropagates();

    // But these are fine to be widget only:
    void tst_tabsNotClickable();
    void tst_embeddedMainWindow();
    void tst_restoreEmbeddedMainWindow();
    void tst_negativeAnchorPositionWhenEmbedded();
    void tst_negativeAnchorPositionWhenEmbedded_data();
    void tst_closeRemovesFromSideBar();
    void tst_restoreSideBar();
    void tst_toggleActionOnSideBar();
    void tst_deleteOnCloseWhenOnSideBar();
    void tst_sidebarOverlayShowsAutohide();
    void tst_sidebarOverlayGetsHiddenOnClick();
    void tst_floatRemovesFromSideBar();
    void tst_overlayedGeometryIsSaved();
    void tst_overlayCrash();

    // And fix these
    void tst_floatingWindowDeleted();
    void tst_addToSmallMainWindow6();
    void tst_minSizeChanges();
    void tst_maxSizePropagates();
    void tst_maxSizePropagates2();
    void tst_maxSizedFloatingWindow();
    void tst_restoreResizesLayout();
    void tst_restoreNonRelativeFloatingWindowGeometry();
    void tst_maxSizeHonouredWhenDropped();
    void tst_fixedSizePolicy();
    void tst_maxSizeHonouredWhenAnotherDropped();
    void tst_addToHiddenMainWindow();
    void tst_maximumSizePolicy();
    void tst_complex();
    void tst_restoreFloatingMaximizedState();
};

void TestQtWidgets::tst_tabsNotClickable()
{
    // Well, not a great unit-test, as it's only repro when it's Windows sending the native event
    // Can't repro with fabricated events. Uncomment the WAIT and test different configs manually
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_Default | KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible);

    auto dock1 = createDockWidget("dock1", new QWidget());
    auto dock2 = createDockWidget("dock2", new QWidget());
    dock1->addDockWidgetAsTab(dock2);

    auto group = dock1->dptr()->group();
    QCOMPARE(group->currentIndex(), 1);

    QTest::qWait(500); // wait for window to get proper geometry

    const QPoint clickPoint = group->tabBar()->mapToGlobal(group->tabBar()->rectForTab(0).center());
    QCursor::setPos(clickPoint); // Just for visual debug when needed

    pressOn(clickPoint, group->tabBar()->view());
    releaseOn(clickPoint, group->tabBar()->view());

    // WAIT // Uncomment for MANUAL test. Also test by adding Flag_AlwaysShowTabs

    QCOMPARE(group->currentIndex(), 0);
}

void TestQtWidgets::tst_mainWindowAlwaysHasCentralWidget()
{
    EnsureTopLevelsDeleted e;

    auto m = createMainWindow();

    QWidget *central = dynamic_cast<Views::MainWindow_qtwidgets *>(m->view())->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(Views::ViewWrapper_qtwidgets::create(mdiArea));

    auto mdiWidget1 = createDockWidget("mdi1", new QPushButton("mdi1"));
    auto mdiWidget2 = createDockWidget("mdi2", new QPushButton("mdi12"));

    mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
    mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));

    Controllers::Group *groupMDI1 = mdiWidget1->d->group();
    Controllers::Group *group1 = dock1->d->group();
    QVERIFY(!group1->isMDI());
    QVERIFY(groupMDI1->isMDI());
    QVERIFY(!group1->mdiLayout());
    QVERIFY(groupMDI1->mdiLayout());

    QVERIFY(!dock1->titleBar()->isMDI());

    auto tb1 = mdiWidget1->titleBar();
    QVERIFY(tb1->isMDI());
    QVERIFY(Platform::instance()->tests_waitForEvent(tb1->view(), QEvent::Show));
    QVERIFY(tb1->isVisible());

    // Press the float button
    tb1->onFloatClicked();

    QVERIFY(mdiWidget1->d->lastPosition()->isValid());
    QVERIFY(mdiWidget1->titleBar()->isVisible());
    QVERIFY(mdiWidget1->isFloating());

    // Dock again, and check it went back
    mdiWidget1->titleBar()->onFloatClicked();
    QVERIFY(!mdiWidget1->isFloating());
}

void TestQtWidgets::tst_mdi_mixed_with_docking2()
{
    // Here, the MDI dock widgets are themselves main windows which will show drop-indicators.
    // It will be super nested: MainWindow -> MDI -> MainWindow

    EnsureTopLevelsDeleted e;
    auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
    auto dock1 = createDockWidget("1", new QPushButton("1"));

    m->addDockWidget(dock1, Location_OnBottom);

    auto mdiArea = new Views::MDIArea_qtwidgets();

    m->setPersistentCentralView(Views::ViewWrapper_qtwidgets::create(mdiArea));


    auto createSheet = [](int id) -> Controllers::DockWidget * {
        auto dock = newDockWidget(QStringLiteral("dw-sheet-%1").arg(id), DockWidgetOption_MDINestable);
        auto btn = new QPushButton(QStringLiteral("Sheet %1").arg(id));
        dock->setGuestView(Views::ViewWrapper_qtwidgets::create(btn));
        dock->setTitle(QStringLiteral("Sheet %1").arg(id));

        return dock;
    };

    auto mdiWidget1 = createSheet(1);
    auto mdiWidget2 = createSheet(2);
    auto mdiWidget3 = createSheet(3);
    /*auto mdiWidget4 = createSheet(4);
    auto mdiWidget5 = createSheet(5);
    auto mdiWidget6 = createSheet(6);*/


    mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
    mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));

    Controllers::Group *group1 = mdiWidget1->d->group();
    Controllers::Group *mdiFrame1 = group1->mdiFrame();

    QPointer<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(Views::ViewWrapper_qtwidgets::create(mdiArea));

    auto createSheet = [](int id) -> Controllers::DockWidget * {
        auto dock = newDockWidget(QStringLiteral("dw-sheet-%1").arg(id), DockWidgetOption_MDINestable);
        dock->setGuestView(Views::ViewWrapper_qtwidgets::create(new QPushButton(QStringLiteral("Sheet %1").arg(id))));
        dock->setTitle(QStringLiteral("Sheet %1").arg(id));

        return dock;
    };

    auto mdiWidget1 = createSheet(1);
    auto mdiWidget2 = createSheet(2);

    mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
    mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));

    Controllers::Group *group1 = mdiArea->groups().at(0);

    const QSize sz1 = group1->view()->size();
    const QSize increment(200, 200);

    QVERIFY(mdiWidget1->d->mdiLayout());
    mdiWidget1->setMDISize(sz1 + increment);

    QCOMPARE(group1->size(), sz1 + increment);
}

// No need to port to QtQuick
void TestQtWidgets::tst_floatingWindowDeleted()
{
    // Tests a case where the empty floating dock widget wouldn't be deleted
    // Doesn't repro QTBUG-83030 unfortunately, as we already have an event loop running
    // but let's leave this here nonetheless
    class MyMainWindow : public KDDockWidgets::Views::MainWindow_qtwidgets
    {
    public:
        MyMainWindow()
            : KDDockWidgets::Views::MainWindow_qtwidgets("tst_floatingWindowDeleted", MainWindowOption_None)
        {
            auto dock1 = newDockWidget(QStringLiteral("DockWidget #1"));
            auto myWidget = new QWidget();
            dock1->setGuestView(Views::ViewWrapper_qtwidgets::create((myWidget)));
            dock1->view()->resize(QSize(600, 600));
            dock1->show();

            auto dock2 = newDockWidget(QStringLiteral("DockWidget #2"));
            myWidget = new QWidget();
            dock2->setGuestView(Views::ViewWrapper_qtwidgets::create(myWidget));
            dock2->view()->resize(QSize(600, 600));
            dock2->show();

            dock1->addDockWidgetAsTab(dock2);
        }
    };

    MyMainWindow m;
}

void TestQtWidgets::tst_addToSmallMainWindow6()
{
    EnsureTopLevelsDeleted e;
    // Test test shouldn't spit any warnings

    QWidget container;
    auto lay = new QVBoxLayout(&container);
    auto m = Platform::instance()->createMainWindow("MyMainWindow_tst_addToSmallMainWindow8", {},
                                                    MainWindowOption_None);
    auto qmainwindow = dynamic_cast<Views::MainWindow_qtwidgets *>(m->view());
    lay->addWidget(qmainwindow);
    container.resize(100, 100);
    Platform::instance()->tests_waitForEvent(&container, QEvent::Resize);
    container.show();
    Platform::instance()->tests_waitForResize(m);
    auto dock1 = createDockWidget("dock1", Platform::instance()->tests_createView({ true, {}, QSize(50, 240) }));
    auto dock2 = createDockWidget("dock2", Platform::instance()->tests_createView({ true, {}, QSize(50, 240) }));
    m->addDockWidget(dock1, KDDockWidgets::Location_OnBottom);
    m->addDockWidget(dock2, KDDockWidgets::Location_OnBottom);
    Platform::instance()->tests_waitForResize(m);
    QVERIFY(m->dropArea()->checkSanity());
    delete m;
}

void TestQtWidgets::tst_closeRemovesFromSideBar()
{
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None);
    auto dw1 = newDockWidget(QStringLiteral("1"));
    auto fw1 = dw1->window();
    m1->addDockWidget(dw1, Location_OnBottom);
    m1->moveToSideBar(dw1);

    QVERIFY(!dw1->isOverlayed());
    QVERIFY(!dw1->isVisible());
    QVERIFY(dw1->isInSideBar());

    Controllers::SideBar *sb = m1->sideBarForDockWidget(dw1);
    QVERIFY(sb);

    // Overlay it:
    sb->toggleOverlay(dw1);
    QVERIFY(dw1->isOverlayed());
    QVERIFY(dw1->isVisible());
    QCOMPARE(dw1->sideBarLocation(), sb->location());
    QVERIFY(dw1->isInMainWindow());
    QVERIFY(!dw1->isFloating());

    // Close it while it's overlayed:
    dw1->close();
    QVERIFY(!dw1->isInMainWindow());
    QVERIFY(!dw1->isOverlayed());
    QVERIFY(!dw1->isVisible());
    QCOMPARE(dw1->sideBarLocation(), SideBarLocation::None);
}

void TestQtWidgets::tst_restoreSideBar()
{
    SideBarLocation loc;
    QByteArray serialized; // serialization after having 1 sidebar visible
    QByteArray beforeSideBarSerialized; // serialization without any sidebar visible

    {
        LayoutSaver saver;
        EnsureTopLevelsDeleted e;
        KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
        auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
        auto dw1 = newDockWidget(QStringLiteral("1"));
        auto fw1 = dw1->window();
        m1->addDockWidget(dw1, Location_OnBottom);
        beforeSideBarSerialized = saver.serializeLayout();
        QVERIFY(!m1->anySideBarIsVisible());
        m1->moveToSideBar(dw1);
        QVERIFY(m1->anySideBarIsVisible());

        QVERIFY(!dw1->isOverlayed());
        QVERIFY(!dw1->isVisible());
        loc = dw1->sideBarLocation();
        QVERIFY(loc != SideBarLocation::None);

        serialized = saver.serializeLayout();

        m1.reset();
    }

    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = newDockWidget(QStringLiteral("1"));
    auto fw1 = dw1->window();
    m1->addDockWidget(dw1, Location_OnBottom);
    QVERIFY(!m1->anySideBarIsVisible());
    QVERIFY(!dw1->isOverlayed());
    QVERIFY(dw1->isVisible());
    QVERIFY(!dw1->isFloating());
    QVERIFY(dw1->isInMainWindow());

    LayoutSaver restorer;
    restorer.restoreLayout(serialized);

    QVERIFY(!dw1->isOverlayed());
    QVERIFY(!dw1->isVisible());
    QVERIFY(!dw1->isInMainWindow());
    QVERIFY(m1->anySideBarIsVisible());

    QCOMPARE(loc, dw1->sideBarLocation());

    restorer.restoreLayout(beforeSideBarSerialized);
    QVERIFY(!dw1->isOverlayed());
    QVERIFY(dw1->isVisible());
    QVERIFY(!dw1->isFloating());
    QVERIFY(dw1->isInMainWindow());
    QVERIFY(!m1->anySideBarIsVisible());
}

void TestQtWidgets::tst_toggleActionOnSideBar()
{
    // When a dock widget is in the sidebar and we use DockWidget::toggleAction() then it should
    // toggle visibility without removing it from the sidebar

    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = newDockWidget("1");
    m1->addDockWidget(dw1, Location_OnBottom);
    dw1->moveToSideBar();

    QVERIFY(!dw1->isVisible());
    QVERIFY(!dw1->isOverlayed());
    QVERIFY(dw1->isInSideBar());
    QVERIFY(!dw1->isInMainWindow());

    QAction *action = dw1->toggleAction();
    action->trigger();

    QVERIFY(dw1->isVisible());
    QVERIFY(dw1->isOverlayed());
    QVERIFY(dw1->isInMainWindow());

    QVERIFY(dw1->isInSideBar());
    action->trigger();

    QVERIFY(!dw1->isOverlayed());
    QVERIFY(!dw1->isInMainWindow());

    QVERIFY(dw1->isInSideBar());
    QVERIFY(!dw1->isInMainWindow());
}

void TestQtWidgets::tst_deleteOnCloseWhenOnSideBar()
{
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
    auto m = createMainWindow(QSize(800, 500), MainWindowOption_None);
    QPointer<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->itemForView(group1->view());
    auto vertSep1 = root->separators().constFirst();
    const int min1 = root->minPosForSeparator_global(vertSep1);

    ItemBoxContainer *container1 = item1->parentBoxContainer();
    auto innerVertSep1 = container1->separators().constFirst();
    const int minInnerSep = container1->minPosForSeparator_global(innerVertSep1);
    const int maxInnerSep = container1->maxPosForSeparator_global(innerVertSep1);

    root->requestSeparatorMove(vertSep1, -(vertSep1->position() - min1));
    QVERIFY(group1->width() <= group1->view()->maxSizeHint().width());

    container1->requestSeparatorMove(innerVertSep1, -(innerVertSep1->position() - minInnerSep));
    QVERIFY(group1->width() <= group1->view()->maxSizeHint().width());

    container1->requestSeparatorMove(innerVertSep1, maxInnerSep - innerVertSep1->position());
    QVERIFY(group1->width() <= group1->view()->maxSizeHint().width());
}

void TestQtWidgets::tst_maxSizeHonouredWhenDropped()
{
    EnsureTopLevelsDeleted e;
    auto m1 = createMainWindow();
    auto dock1 = newDockWidget("dock1");
    auto dock2 = newDockWidget("dock2");
    m1->addDockWidget(dock1, Location_OnTop);
    m1->view()->resize(QSize(2000, 2000));

    auto w2 = Platform::instance()->tests_createView({ true, {}, { 0, 0 } });
    dock2->setGuestView(w2->asWrapper());
    const int maxWidth = 200;
    w2->setMaximumSize(QSize(maxWidth, 200));
    m1->addDockWidget(dock2, Location_OnLeft);
    const int droppedWidth = dock2->dptr()->group()->width();
    QVERIFY(droppedWidth < maxWidth + 50); // +50 to cover any margins and waste by QTabWidget

    // Try again, but now dropping a multisplitter
    dock2->setFloating(true);
    auto fw = dock2->floatingWindow();

    m1->dropArea()->drop(fw->view(), Location_OnLeft, nullptr);
    QCOMPARE(dock2->dptr()->group()->width(), droppedWidth);
}

void TestQtWidgets::tst_fixedSizePolicy()
{
    // tests that KDDW also takes into account QSizePolicy::Fixed for calculating the max size hint.
    // Since QPushButton for example doesn't set QWidget::maximumSize(), but instead uses sizeHint()
    // + QSizePolicy::Fixed.
    EnsureTopLevelsDeleted e;
    auto button = new QPushButton("one");
    auto dock1 = createDockWidget("dock1", button);
    Controllers::Group *group = dock1->dptr()->group();

    // Just a precondition from the test. If QPushButton ever changes, replace with a QWidget and set fixed size policy
    QCOMPARE(button->sizePolicy().verticalPolicy(), QSizePolicy::Fixed);

    const int buttonMaxHeight = button->sizeHint().height();

    QCOMPARE(dock1->view()->sizeHint(), button->sizeHint());
    QCOMPARE(dock1->view()->verticalSizePolicy(), SizePolicy(button->sizePolicy().verticalPolicy()));
    QCOMPARE(dock1->view()->horizontalSizePolicy(), SizePolicy(button->sizePolicy().horizontalPolicy()));

    QCOMPARE(group->view()->maxSizeHint().height(), qMax(buttonMaxHeight, Layouting::Item::hardcodedMinimumSize.height()));
}

void TestQtWidgets::tst_restoreFloatingMaximizedState()
{
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_TitleBarHasMaximizeButton);
    auto dock1 = createDockWidget("dock1", Platform::instance()->tests_createView({ true }));
    const QRect originalNormalGeometry = dock1->floatingWindow()->view()->normalGeometry();
    dock1->floatingWindow()->view()->showMaximized();
    qDebug() << originalNormalGeometry;

    QCOMPARE(dock1->floatingWindow()->view()->window()->windowState(), WindowState::Maximized);

    LayoutSaver saver;
    const QByteArray saved = saver.serializeLayout();

    saver.restoreLayout(saved);
    QCOMPARE(dock1->floatingWindow()->view()->window()->windowState(), WindowState::Maximized);

    QCOMPARE(dock1->floatingWindow()->view()->normalGeometry(), originalNormalGeometry);

    dock1->floatingWindow()->view()->showNormal();
    QCOMPARE(dock1->floatingWindow()->view()->normalGeometry(), originalNormalGeometry);
}

void TestQtWidgets::tst_complex()
{
    // Tests some anchors out of bounds I got

    EnsureTopLevelsDeleted e;
    auto m = createMainWindow(QSize(600, 500), MainWindowOption_None);
    auto layout = m->multiSplitter();
    m->view()->resize(QSize(3266, 2239));
    m->show();

    Controllers::DockWidget::List docks;

    QVector<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"
