This commit is contained in:
Sergio Martins
2020-04-01 12:02:47 +01:00
parent 899ca6af6a
commit 1643f23612
24 changed files with 2119 additions and 2191 deletions

View File

@@ -44,7 +44,7 @@ add_subdirectory(src)
if (OPTION_DEVELOPER_MODE)
if (NOT OPTION_QTQUICK)
add_subdirectory(tests)
# add_subdirectory(tests)
endif()
# examples use <kddockwidget/ qualified includes so need to be installed first

View File

@@ -25,12 +25,10 @@ set(DOCKSLIBS_SRCS
private/ObjectViewer.cpp
private/DropIndicatorOverlayInterface.cpp
private/indicators/ClassicIndicators.cpp
private/indicators/AnimatedIndicators.cpp
# private/indicators/AnimatedIndicators.cpp
private/DropArea.cpp
private/multisplitter/Item.cpp
private/multisplitter/MultiSplitter.cpp
private/multisplitter/Anchor.cpp
private/multisplitter/AnchorGroup.cpp
private/multisplitter/Separator.cpp
private/multisplitter/MultiSplitterLayout.cpp
private/TabWidget.cpp
@@ -119,6 +117,9 @@ else()
set(IS_CLANG_BUILD FALSE)
endif()
add_subdirectory(private/multisplitter)
qt5_add_resources(RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/resources.qrc)
add_library(kddockwidgets SHARED ${DOCKSLIBS_SRCS} ${DOCKS_INSTALLABLE_INCLUDES} ${RESOURCES} ${RESOURCES_QUICK})

View File

@@ -32,6 +32,7 @@
#include "KDDockWidgets.h"
#include "QWidgetAdapter.h"
#include "LayoutSaver_p.h"
#include "multisplitter/Item_p.h"
#include <QVector>
#include <QWidget>

View File

@@ -32,14 +32,6 @@
namespace KDDockWidgets
{
enum Location {
Location_None,
Location_OnLeft, ///> Left docking location
Location_OnTop, ///> Top docking location
Location_OnRight, ///> Right docking location
Location_OnBottom ///> Bottom docking location
};
enum MainWindowOption {
MainWindowOption_None = 0, ///> No option set
MainWindowOption_HasCentralFrame = 1 ///> Makes the MainWindow always have a central frame, for tabbing documents
@@ -65,64 +57,8 @@ namespace KDDockWidgets
///< Loading layouts won't change the main window geometry and just use whatever the user has at the moment.
};
Q_DECLARE_FLAGS(RestoreOptions, RestoreOption)
///@internal
inline Location oppositeLocation(Location loc)
{
switch (loc) {
case Location_OnLeft:
return Location_OnRight;
case Location_OnTop:
return Location_OnBottom;
case Location_OnRight:
return Location_OnLeft;
case Location_OnBottom:
return Location_OnTop;
default:
Q_ASSERT(false);
return Location_None;
}
}
///@internal
inline Location adjacentLocation(Location loc)
{
switch (loc) {
case Location_OnLeft:
return Location_OnTop;
case Location_OnTop:
return Location_OnRight;
case Location_OnRight:
return Location_OnBottom;
case Location_OnBottom:
return Location_OnLeft;
default:
Q_ASSERT(false);
return Location_None;
}
}
///@internal
inline QString locationStr(Location loc)
{
switch (loc) {
case KDDockWidgets::Location_None:
return QStringLiteral("none");
case KDDockWidgets::Location_OnLeft:
return QStringLiteral("left");
case KDDockWidgets::Location_OnTop:
return QStringLiteral("top");
case KDDockWidgets::Location_OnRight:
return QStringLiteral("right");
case KDDockWidgets::Location_OnBottom:
return QStringLiteral("bottom");
}
return QString();
}
}
Q_DECLARE_METATYPE(KDDockWidgets::Location)
Q_DECLARE_OPERATORS_FOR_FLAGS(KDDockWidgets::FrameOptions)
#endif

View File

@@ -221,31 +221,6 @@ bool LayoutSaver::restoreLayout(const QByteArray &data)
if (data.isEmpty())
return true;
struct EnsureItemsAtCorrectPlace {
EnsureItemsAtCorrectPlace(LayoutSaver *ls)
: layoutSaver(ls)
{
}
~EnsureItemsAtCorrectPlace()
{
// When using RestoreOption_RelativeToMainWindow we'll have many rounding errors so the layout won't be exact.
// Make sure to run a relayout at the end
// (Using RAII to make sure it runs after Private::RAIIIsRestoring went out of scope, since "isRestoring= true" inhibits relayout
if (ensure) {
for (auto layout : DockRegistry::self()->layouts()) {
if (layoutSaver->d->matchesAffinity(layout->affinityName()))
layout->redistributeSpace();
}
}
}
bool ensure = false;
LayoutSaver *const layoutSaver;
};
EnsureItemsAtCorrectPlace ensureItemsAtCorrectPlace(this);
Private::RAIIIsRestoring isRestoring;
struct FrameCleanup {
@@ -327,9 +302,6 @@ bool LayoutSaver::restoreLayout(const QByteArray &data)
}
}
// our raii class will run when
ensureItemsAtCorrectPlace.ensure = d->m_restoreOptions & RestoreOption_RelativeToMainWindow;
return true;
}

View File

@@ -33,6 +33,7 @@
#include "KDDockWidgets.h"
#include "QWidgetAdapter.h"
#include "LayoutSaver_p.h"
#include "multisplitter/Item_p.h"
#include <QVector>

View File

@@ -231,24 +231,6 @@ DebugWindow::DebugWindow(QWidget *parent)
repaintWidgetRecursive(w);
});
button = new QPushButton(this);
button->setText(QStringLiteral("EnsureAnchorsBounded"));
layout->addWidget(button);
connect(button, &QPushButton::clicked, this, [] {
const auto layouts = DockRegistry::self()->layouts();
for (auto l : layouts)
l->ensureAnchorsBounded();
});
button = new QPushButton(this);
button->setText(QStringLiteral("RedistributeSpace"));
layout->addWidget(button);
connect(button, &QPushButton::clicked, this, [] {
const auto layouts = DockRegistry::self()->layouts();
for (auto l : layouts)
l->redistributeSpace();
});
button = new QPushButton(this);
button->setText(QStringLiteral("resize by 1x1"));
layout->addWidget(button);
@@ -260,24 +242,6 @@ DebugWindow::DebugWindow(QWidget *parent)
}
});
button = new QPushButton(this);
button->setText(QStringLiteral("PositionStaticAnchors()"));
layout->addWidget(button);
connect(button, &QPushButton::clicked, this, [] {
const auto layouts = DockRegistry::self()->layouts();
for (auto l : layouts)
l->positionStaticAnchors();
});
button = new QPushButton(this);
button->setText(QStringLiteral("UpdateAnchorFollowing"));
layout->addWidget(button);
connect(button, &QPushButton::clicked, this, [] {
const auto layouts = DockRegistry::self()->layouts();
for (auto l : layouts)
l->updateAnchorFollowing();
});
button = new QPushButton(this);
button->setText(QStringLiteral("Raise #0 (after 3s timeout)"));
layout->addWidget(button);

View File

@@ -24,6 +24,7 @@
#include "DebugWindow_p.h"
#include "LastPosition_p.h"
#include "multisplitter/MultiSplitterLayout_p.h"
#include "multisplitter/MultiSplitter_p.h"
#include "quick/QmlTypes.h"
#include <QPointer>
@@ -77,6 +78,25 @@ bool DockRegistry::isProcessingAppQuitEvent() const
return m_isProcessingAppQuitEvent;
}
MultiSplitterLayout *DockRegistry::layoutForItem(const Item *item) const
{
Item *root = item->root();
for (MultiSplitterLayout *layout : m_layouts) {
if (layout->rootItem() == root)
return layout;
}
return nullptr;
}
bool DockRegistry::itemIsInMainWindow(const Item *item) const
{
if (auto layout = layoutForItem(item))
return layout->multiSplitter()->isInMainWindow();
return false;
}
DockRegistry *DockRegistry::self()
{
static QPointer<DockRegistry> s_dockRegistry;

View File

@@ -137,6 +137,12 @@ public:
*/
bool isProcessingAppQuitEvent() const;
// TODO: docs
MultiSplitterLayout* layoutForItem(const Item *) const;
// TODO: docs
bool itemIsInMainWindow(const Item *) const;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:

View File

@@ -75,7 +75,7 @@ Frame *DropArea::frameContainingPos(QPoint globalPos) const
{
const ItemList &items = m_layout->items();
for (Item *item : items) {
auto frame = item->frame();
auto frame = static_cast<Frame*>(item->frame());
if (!frame || !frame->isVisible()) {
continue;
}
@@ -89,7 +89,7 @@ Frame *DropArea::frameContainingPos(QPoint globalPos) const
Item *DropArea::centralFrame() const
{
for (Item *item : m_layout->items()) {
if (auto f = item->frame()) {
if (auto f = static_cast<Frame*>(item->frame())) {
if (f->isCentralFrame())
return item;
}
@@ -142,7 +142,7 @@ void DropArea::addDockWidget(DockWidgetBase *dw, Location location, DockWidgetBa
void DropArea::debug_updateItemNamesForGammaray()
{
for (Item *item : m_layout->items()) {
if (auto frame = item->frame()) {
if (auto frame = static_cast<Frame*>(item->frame())) {
if (!frame->dockWidgets().isEmpty())
frame->setObjectName(frame->dockWidgets().at(0)->uniqueName());
}
@@ -152,9 +152,9 @@ void DropArea::debug_updateItemNamesForGammaray()
a->debug_updateItemNames();
}
bool DropArea::checkSanity(MultiSplitterLayout::AnchorSanityOption o)
bool DropArea::checkSanity()
{
return m_layout->checkSanity(o);
return m_layout->checkSanity();
}
bool DropArea::contains(DockWidgetBase *dw) const

View File

@@ -66,7 +66,7 @@ public:
void debug_updateItemNamesForGammaray();
bool checkSanity(MultiSplitterLayout::AnchorSanityOption o = MultiSplitterLayout::AnchorSanity_All);
bool checkSanity();
bool contains(DockWidgetBase *) const;
QString affinityName() const;

View File

@@ -25,6 +25,7 @@
#include "QWidgetAdapter.h"
#include "Frame_p.h"
#include "KDDockWidgets.h"
#include "multisplitter/Item_p.h"
namespace KDDockWidgets {

View File

@@ -46,7 +46,7 @@ void LastPosition::addPlaceholderItem(Item *placeholder)
if (containsPlaceholder(placeholder))
return;
if (placeholder->isInMainWindow()) {
if (DockRegistry::self()->itemIsInMainWindow(placeholder)) {
// 2. If we have a MainWindow placeholder we don't need nothing else
removePlaceholders();
} else {
@@ -82,7 +82,7 @@ Item *LastPosition::layoutItem() const
// In the future we might want to restore it to FloatingWindows.
for (const auto &itemref : m_placeholders) {
if (itemref->item->isInMainWindow())
if (DockRegistry::self()->itemIsInMainWindow(itemref->item))
return itemref->item;
}
@@ -101,7 +101,7 @@ bool LastPosition::containsPlaceholder(Item *item) const
void LastPosition::removePlaceholders(const MultiSplitterLayout *layout)
{
m_placeholders.erase(std::remove_if(m_placeholders.begin(), m_placeholders.end(), [layout] (const std::unique_ptr<ItemRef> &itemref) {
return itemref->item->layout() == layout;
return DockRegistry::self()->layoutForItem(itemref->item) == layout;
}), m_placeholders.end());
}
@@ -110,7 +110,7 @@ void LastPosition::removeNonMainWindowPlaceholders()
auto it = m_placeholders.begin();
while (it != m_placeholders.end()) {
ItemRef *itemref = it->get();
if (!itemref->item->isInMainWindow())
if (!DockRegistry::self()->itemIsInMainWindow(itemref->item))
it = m_placeholders.erase(it);
else
++it;
@@ -179,7 +179,7 @@ LayoutSaver::LastPosition LastPosition::serialize() const
LayoutSaver::Placeholder p;
Item *item = itemRef->item;
MultiSplitterLayout *layout = item->layout();
MultiSplitterLayout *layout = DockRegistry::self()->layoutForItem(item);
const int itemIndex = layout->items().indexOf(item);
auto fw = layout->multiSplitter()->floatingWindow();

View File

@@ -31,12 +31,15 @@
#include "multisplitter/Item_p.h"
#include "Logging_p.h"
#include "LayoutSaver_p.h"
#include "QWidgetAdapter.h"
#include <QPointer>
#include <memory>
namespace KDDockWidgets {
class MultiSplitterLayout;
// Just a RAII class so we don't forget to unref
struct ItemRef
{

View File

@@ -59,10 +59,6 @@ Anchor::~Anchor()
m_separatorWidget->deleteLater();
qCDebug(multisplittercreation) << "~Anchor; this=" << this << "; m_to=" << m_to << "; m_from=" << m_from;
m_layout->removeAnchor(this);
for (Item *item : items(Side1))
item->anchorGroup().setAnchor(nullptr, m_orientation, Side1);
for (Item *item : items(Side2))
item->anchorGroup().setAnchor(nullptr, m_orientation, Side2);
}
void Anchor::setFrom(Anchor *from)
@@ -200,57 +196,6 @@ Qt::Orientation Anchor::orientation() const
void Anchor::setPosition(int p, SetPositionOptions options)
{
qCDebug(anchors) << Q_FUNC_INFO << this << "; visible="
<< m_separatorWidget->isVisible() << "; p=" << p;
const int max = m_layout->length(orientation()) - Anchor::thickness(true);
const bool outOfBounds = max != -1 && (p < 0 || p > max);
if (outOfBounds) {
if (m_layout->isRestoringPlaceholder() || m_layout->isAddingItem() || m_layout->isResizing()) {
// Don't do anything here, it will call ensureAnchorsBounded() when finished
return;
} else if (!LayoutSaver::restoreInProgress()) {
m_layout->dumpDebug();
qWarning() << Q_FUNC_INFO << "Out of bounds position=" << p
<< "; oldPosition=" << position()
<< "; this=" << this
<< "; size=" << m_layout->size()
<< "; max=" << max
<< m_layout->multiSplitter()->window();
}
}
m_initialized = true;
if (position() == p) {
updateItemSizes();
return;
}
if (isVertical()) {
m_geometry.moveLeft(p);
} else {
m_geometry.moveTop(p);
}
/**
* If we're in the middle of a resize then remember the relative positions, so we can do
* a redistribution so that relatively all widgets occupy the same amount
*/
const bool recalculatePercentage = !(options & SetPositionOption_DontRecalculatePercentage) && !m_layout->isResizing();
m_separatorWidget->move(p);
if (recalculatePercentage) {
// We keep the percentage, so we don't constantly recalculate it during a resize, which introduces rounding errors
updatePositionPercentage();
}
// Note: Position can be slightly negative if the main window isn't big enougn to host the new size.
// In that case the window will be resized shortly after
//Q_ASSERT(p >= 0); - commented out, as it's normal
Q_EMIT positionChanged(position());
updateItemSizes();
}
void Anchor::updatePositionPercentage()

View File

@@ -1,468 +0,0 @@
/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AnchorGroup_p.h"
#include "Anchor_p.h"
#include "MultiSplitterLayout_p.h"
#include "MultiSplitter_p.h"
#include "Logging_p.h"
using namespace KDDockWidgets;
AnchorGroup::AnchorGroup(MultiSplitterLayout *l)
: layout(l)
{
}
int AnchorGroup::width() const
{
return right->position() - left->position();
}
int AnchorGroup::height() const
{
return bottom->position() - top->position();
}
bool AnchorGroup::containsAnchor(Anchor *anchor) const
{
return anchor == left || anchor == top || anchor == right || anchor == bottom;
}
bool AnchorGroup::containsAnchor(Anchor *anchor, Anchor::Side side) const
{
if (side == Anchor::Side1)
return anchor == left || anchor == top;
return anchor == right || anchor == bottom;
}
QSize AnchorGroup::availableSize() const
{
const int leftBound = left->isStatic() ? left->position()
: layout->boundPositionForAnchor(left, Anchor::Side1);
const int rightBound = right->isStatic() ? right->position()
: layout->boundPositionForAnchor(right, Anchor::Side2);
const int topBound = top->isStatic() ? top->position()
: layout->boundPositionForAnchor(top, Anchor::Side1);
const int bottomBound = bottom->isStatic() ? bottom->position()
: layout->boundPositionForAnchor(bottom, Anchor::Side2);
return QSize(rightBound - leftBound - left->thickness(),
bottomBound - topBound - top->thickness());
}
QSize AnchorGroup::itemSize() const
{
return QSize(right->position() - left->position() - left->thickness(),
bottom->position() - top->position() - top->thickness());
}
int AnchorGroup::itemSize(Qt::Orientation o) const
{
return o == Qt::Vertical ? itemSize().width()
: itemSize().height();
}
bool AnchorGroup::hasAvailableSizeFor(QSize needed, Qt::Orientation orientation) const
{
const QSize available = availableSize();
return orientation == Qt::Vertical ? available.width() >= needed.width()
: available.height() >= needed.height();
}
AnchorGroup AnchorGroup::outterGroup() const
{
AnchorGroup group(layout);
group.left = left->hasNonPlaceholderItems(Anchor::Side1) ? left
: left->findNearestAnchorWithItems(Anchor::Side1);
group.top = top->hasNonPlaceholderItems(Anchor::Side1) ? top
: top->findNearestAnchorWithItems(Anchor::Side1);
group.right = right->hasNonPlaceholderItems(Anchor::Side2) ? right
: right->findNearestAnchorWithItems(Anchor::Side2);
group.bottom = bottom->hasNonPlaceholderItems(Anchor::Side2) ? bottom
: bottom->findNearestAnchorWithItems(Anchor::Side2);
return group;
}
Anchor *AnchorGroup::oppositeAnchor(Anchor *a) const
{
if (a == left)
return right;
if (a == right)
return left;
if (a == top)
return bottom;
if (a == bottom)
return top;
return nullptr;
}
Anchor *AnchorGroup::createAnchorFrom(Location fromAnchorLocation, Item *relativeTo)
{
Anchor *other = anchor(fromAnchorLocation);
Q_ASSERT(other);
auto anchor = new Anchor(other->orientation(), other->m_layout);
if (anchor->isVertical()) {
anchor->setFrom(top);
anchor->setTo(bottom);
} else {
anchor->setFrom(left);
anchor->setTo(right);
}
if (relativeTo) {
if (other->containsItem(relativeTo, Anchor::Side1)) {
other->removeItem(relativeTo);
anchor->addItem(relativeTo, Anchor::Side1);
} else if (other->containsItem(relativeTo, Anchor::Side2)) {
other->removeItem(relativeTo);
anchor->addItem(relativeTo, Anchor::Side2);
} else {
Q_ASSERT(false);
}
} else {
auto other1 = other->m_side1Items;
auto other2 = other->m_side2Items;
other->removeAllItems();
anchor->addItems(other1, Anchor::Side1);
anchor->addItems(other2, Anchor::Side2);
}
return anchor;
}
Anchor *AnchorGroup::anchor(Location loc) const
{
switch (loc) {
case KDDockWidgets::Location_OnLeft:
return left;
case KDDockWidgets::Location_OnTop:
return top;
case KDDockWidgets::Location_OnRight:
return right;
case KDDockWidgets::Location_OnBottom:
return bottom;
default:
Q_ASSERT(false);
return nullptr;
}
}
Anchor *AnchorGroup::anchorAtDirection(Anchor::Side side, Qt::Orientation orientation) const
{
const bool isSide1 = side == Anchor::Side1;
if (orientation == Qt::Vertical) {
return isSide1 ? right : left;
} else {
return isSide1 ? bottom : top;
}
}
Anchor *AnchorGroup::anchorAtSide(Anchor::Side side, Qt::Orientation orientation) const
{
const bool isSide1 = side == Anchor::Side1;
if (orientation == Qt::Vertical) {
return isSide1 ? left: right;
} else {
return isSide1 ? top : bottom;
}
}
void AnchorGroup::setAnchor(Anchor *anchor, Location loc)
{
switch (loc) {
case KDDockWidgets::Location_OnLeft:
left = anchor;
break;
case KDDockWidgets::Location_OnTop:
top = anchor;
break;
case KDDockWidgets::Location_OnRight:
right = anchor;
break;
case KDDockWidgets::Location_OnBottom:
bottom = anchor;
break;
default:
Q_ASSERT(false);
}
}
bool AnchorGroup::anchorIsFollowingInwards(Anchor *anchor) const
{
if (!anchor)
return false;
if (anchor == left && left->findAnchor(left->endFollowee(), Anchor::Side2))
return true;
if (anchor == top && top->findAnchor(top->endFollowee(), Anchor::Side2))
return true;
if (anchor == right && right->findAnchor(right->endFollowee(), Anchor::Side1))
return true;
if (anchor == bottom && bottom->findAnchor(bottom->endFollowee(), Anchor::Side1))
return true;
return false;
}
QDebug AnchorGroup::debug(QDebug d) const
{
d << "AnchorGroup: this=" << ((void*)this) << "\n; top=" << top << "; left=" << left
<< "\n ; right=" << right << "; bottom=" << bottom
<< "\n ; valid=" << isValid()
<< anchorIsFollowingInwards(left) << anchorIsFollowingInwards(top)
<< anchorIsFollowingInwards(right) << anchorIsFollowingInwards(bottom)
<< (left ? left->followee() : nullptr)
<< "\n";
return d;
}
const Anchor::List AnchorGroup::anchorsFollowingInwards() const
{
Anchor::List result;
if (anchorIsFollowingInwards(left))
result.push_back(left);
if (anchorIsFollowingInwards(top))
result.push_back(top);
if (anchorIsFollowingInwards(right)) {
result.push_back(right);
Q_ASSERT(!result.contains(left));
}
if (anchorIsFollowingInwards(bottom)) {
result.push_back(bottom);
Q_ASSERT(!result.contains(top));
}
Q_ASSERT(result.size() <= 2);
return result;
}
const Anchor::List AnchorGroup::anchorsNotFollowingInwards() const
{
Anchor::List result = anchors();
for (Anchor *a : anchorsFollowingInwards())
result.removeOne(a);
return result;
}
const Anchor::List AnchorGroup::anchors() const
{
return { left, top, right, bottom };
}
Anchor::Side AnchorGroup::sideForAnchor(Anchor *a) const
{
if (a == left || a == top)
return Anchor::Side1;
return Anchor::Side2;
}
bool AnchorGroup::isStatic() const
{
return top->isStatic() && bottom->isStatic() && left->isStatic() && right->isStatic();
}
bool AnchorGroup::isStaticOrFollowsStatic() const
{
return top->isStaticOrFollowsStatic() && bottom->isStaticOrFollowsStatic()
&& left->isStaticOrFollowsStatic() && right->isStaticOrFollowsStatic();
}
void AnchorGroup::updateItemSizes()
{
// Sets the geometry of the items that are inside this group
left->updateItemSizes();
top->updateItemSizes();
right->updateItemSizes();
bottom->updateItemSizes();
}
void AnchorGroup::setAnchor(Anchor *a, Qt::Orientation orientation, Anchor::Side side)
{
const bool isSide1 = side == Anchor::Side1;
if (orientation == Qt::Vertical) {
if (isSide1)
right = a;
else
left = a;
} else {
if (isSide1)
bottom = a;
else
top = a;
}
}
Anchor *AnchorGroup::adjacentAnchor(Anchor *other) const
{
if (other == top)
return right;
if (other == right)
return bottom;
if (other == bottom)
return left;
if (other == left)
return top;
return nullptr;
}
QPair<Anchor *, Anchor *> AnchorGroup::adjacentAnchors(Anchor *anchor) const
{
if (anchor == left || anchor == right) {
return { top, bottom };
} else if (anchor == top || anchor == bottom) {
return { left, right };
} else {
return {};
}
}
void AnchorGroup::addItem(Item *item)
{
// Dropping a single dockwidget, without any nesting
left->addItem(item, Anchor::Side2);
top->addItem(item, Anchor::Side2);
right->addItem(item, Anchor::Side1);
bottom->addItem(item, Anchor::Side1);
}
void AnchorGroup::addItem(MultiSplitterLayout *sourceMultiSplitter)
{
// Here we rip all the widgets and anchors from the source multisplitter into the receiving multisplitter
// preserving the layout between source widgets. Then we delete the source splitter, as all its
// content has bene integrated into ours
// To prevent the source splitter from deleting the anchors once the widgets are reparented
sourceMultiSplitter->m_beingMergedIntoAnotherMultiSplitter = true;
// Reparent the widgets:
for (Item *sourceItem : sourceMultiSplitter->items()) {
sourceItem->setLayout(layout);
sourceItem->setVisible(true);
}
// Reparent the inner anchors, they're ours now
for (Anchor *anchor : sourceMultiSplitter->anchors()) {
if (!anchor->isStatic()) {
const qreal positionPercentage = anchor->positionPercentage();
anchor->setLayout(layout);
anchor->setVisible(true);
if (anchor->from()->isStatic()) {
if (anchor->isVertical()) {
anchor->setFrom(top);
} else {
anchor->setFrom(left);
}
}
if (anchor->to()->isStatic()) {
if (anchor->isVertical()) {
anchor->setTo(bottom);
} else {
anchor->setTo(right);
}
}
// And update their position
qreal newPos = 0;
if (anchor->isVertical()) {
newPos = left->position() + (width() * positionPercentage);
} else {
newPos = top->position() + (height() * positionPercentage);
}
const QPair<int,int> bounds = layout->boundPositionsForAnchor(anchor);
anchor->setPosition(qBound(bounds.first, static_cast<int>(newPos), bounds.second));
}
}
AnchorGroup sourceAnchorGroup = sourceMultiSplitter->staticAnchorGroup();
Q_ASSERT(sourceAnchorGroup.isValid());
top->consume(sourceAnchorGroup.top);
bottom->consume(sourceAnchorGroup.bottom);
left->consume(sourceAnchorGroup.left);
right->consume(sourceAnchorGroup.right);
delete sourceMultiSplitter->multiSplitter(); // Delete MultiSplitter and MultiSplitterLayout
}
void AnchorGroup::removeItem(Item *item)
{
left->removeItem(item);
right->removeItem(item);
bottom->removeItem(item);
top->removeItem(item);
if (left->isUnneeded()) {
layout->updateAnchorsFromTo(left, right);
const int leftPosition = left->position();
right->consume(left, Anchor::Side1);
if (!right->isUnneeded() && !right->isStatic()) {
// Make use of the extra space, so it's fair
right->setPosition(right->position() - ((right->position() - leftPosition) / 2));
}
}
if (right->isUnneeded()) {
layout->updateAnchorsFromTo(right, left);
left->consume(right, Anchor::Side2);
}
if (top->isUnneeded()) {
layout->updateAnchorsFromTo(top, bottom);
const int topPosition = top->position();
bottom->consume(top, Anchor::Side1);
if (!bottom->isUnneeded() && !bottom->isStatic()) {
// Make use of the extra space, so it's fair
bottom->setPosition(bottom->position() - ((bottom->position() - topPosition) / 2));
}
}
if (bottom->isUnneeded()) {
layout->updateAnchorsFromTo(bottom, top);
top->consume(bottom, Anchor::Side2);
}
}

View File

@@ -1,126 +0,0 @@
/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KD_MULTISPLITTER_ANCHORGROUP_P_H
#define KD_MULTISPLITTER_ANCHORGROUP_P_H
#include "docks_export.h"
#include "KDDockWidgets.h"
#include "Anchor_p.h"
#include <QDebug>
namespace KDDockWidgets {
class MultiSplitterLayout;
class Anchor;
class Item;
struct DOCKS_EXPORT_FOR_UNIT_TESTS AnchorGroup
{
///@brief contructs an invalid group
AnchorGroup() = default;
explicit AnchorGroup(MultiSplitterLayout *);
void addItem(Item *item);
void addItem(MultiSplitterLayout *);
void removeItem(Item *item);
bool isValid() const { return top && left && bottom && right; }
int width() const;
int height() const;
///@brief returns whether this group contains @p anchor
bool containsAnchor(Anchor *anchor) const;
///@brief returns whether this group contains @p anchor at Side @p side
///If side is Side1, then anchor must be equal to left or top, otherwise top or bottom
bool containsAnchor(Anchor *anchor, Anchor::Side side) const;
/**
* @brief Returns the max available size in this group
* This is the size of the widget when you push all anchors outwards
*/
QSize availableSize() const;
/**
* @brief Returns the size of an item that would be inside these 4 anchors
*/
QSize itemSize() const;
/**
* @brief Similar to @ref itemSize(), but returns the width if @p o is Qt::Vertical, otherwise
* the height
*/
int itemSize(Qt::Orientation o) const;
/**
* @brief Returns whether @ref availableSize is bigger or equal than @ref needed
*/
bool hasAvailableSizeFor(QSize needed, Qt::Orientation orientation) const;
/// Returns the group formed by the Anchors that actually have items on their outter side
AnchorGroup outterGroup() const;
Anchor *oppositeAnchor(Anchor*) const;
Anchor *createAnchorFrom(KDDockWidgets::Location fromAnchorLocation, Item *relativeTo);
void setAnchor(Anchor *a, Qt::Orientation orientation, Anchor::Side side);
Anchor *adjacentAnchor(Anchor*) const;
QPair<Anchor*,Anchor*> adjacentAnchors(Anchor*) const;
Anchor *anchor(KDDockWidgets::Location) const;
Anchor *anchorAtDirection(Anchor::Side side, Qt::Orientation orientation) const;
Anchor *anchorAtSide(Anchor::Side side, Qt::Orientation orientation) const;
void setAnchor(Anchor *anchor, KDDockWidgets::Location);
bool anchorIsFollowingInwards(Anchor*) const;
const Anchor::List anchorsFollowingInwards() const;
const Anchor::List anchorsNotFollowingInwards() const;
const Anchor::List anchors() const;
Anchor::Side sideForAnchor(Anchor*) const;
bool isStatic() const;
bool isStaticOrFollowsStatic() const;
void updateItemSizes();
Anchor *top = nullptr;
Anchor *left = nullptr;
Anchor *bottom = nullptr;
Anchor *right = nullptr;
MultiSplitterLayout *layout;
QDebug debug(QDebug d) const;
};
}
inline QDebug operator<< (QDebug d, KDDockWidgets::AnchorGroup *group)
{
// out-of-line as it needs to include MultiSplitterLayout
return group->debug(d);
}
#endif

View File

@@ -0,0 +1,15 @@
set(MULTISPLITTER_SRCS
Item.cpp
Item_p.h)
add_library(kddockwidgets_layouting ${MULTISPLITTER_SRCS})
target_link_libraries(kddockwidgets_layouting Qt5::Core Qt5::Widgets)
add_subdirectory(tests)
target_include_directories(kddockwidgets_layouting
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
@@ -18,177 +18,332 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KD_MULTISPLITTER_ITEM_P_H
#define KD_MULTISPLITTER_ITEM_P_H
#pragma once
#include "docks_export.h"
#include "Anchor_p.h"
#include "QWidgetAdapter.h"
#include "LayoutSaver_p.h"
#include <QRect>
#include <QObject>
#include <QVector>
#include <QRect>
#include <QVariant>
#include <QWidget> // TODO: remove
#include <memory>
class TestMultiSplitter;
/**
* @brief Implements an item that you put into a multi-splitter.
* For now it just wraps a KDDockWidgets::Frame, but could eventually be used in QML.
*/
namespace KDDockWidgets {
struct AnchorGroup;
class MultiSplitterLayout;
class Frame;
class DockWidgetBase;
class TestDocks;
struct GeometryDiff
{
explicit GeometryDiff(QRect oldGeo, QRect newGeo)
: leftDiff(newGeo.left() - oldGeo.left())
, topDiff(newGeo.top() - oldGeo.top())
, rightDiff(newGeo.right() - oldGeo.right())
, bottomDiff(newGeo.bottom() - oldGeo.bottom())
, onlyOneSideChanged([this]{
int numChanged = 0;
if (leftDiff != 0)
numChanged++;
if (topDiff != 0)
numChanged++;
if (rightDiff != 0)
numChanged++;
if (bottomDiff != 0)
numChanged++;
return numChanged == 1;
}()) // Lambda just so we can have onlyOneChanged as const
{
}
// Orientation of the Anchor that provoked the geometry diff
Qt::Orientation orientation() const
{
if (leftDiff || rightDiff)
return Qt::Vertical;
return Qt::Horizontal;
}
int delta() const
{
// Since we only use GeometryDiff when only 1 side changed, just sum them all
return leftDiff + rightDiff + topDiff + bottomDiff;
}
int signess() const
{
return delta() > 0 ? 1: -1;
}
const int leftDiff;
const int topDiff;
const int rightDiff;
const int bottomDiff;
const bool onlyOneSideChanged;
enum Location {
Location_None,
Location_OnLeft, ///> Left docking location
Location_OnTop, ///> Top docking location
Location_OnRight, ///> Right docking location
Location_OnBottom ///> Bottom docking location
};
class DOCKS_EXPORT_FOR_UNIT_TESTS Item : public QObject // clazy:exclude=ctor-missing-parent-argument
///@internal
inline Location oppositeLocation(Location loc)
{
switch (loc) {
case Location_OnLeft:
return Location_OnRight;
case Location_OnTop:
return Location_OnBottom;
case Location_OnRight:
return Location_OnLeft;
case Location_OnBottom:
return Location_OnTop;
default:
Q_ASSERT(false);
return Location_None;
}
}
///@internal
inline Location adjacentLocation(Location loc)
{
switch (loc) {
case Location_OnLeft:
return Location_OnTop;
case Location_OnTop:
return Location_OnRight;
case Location_OnRight:
return Location_OnBottom;
case Location_OnBottom:
return Location_OnLeft;
default:
Q_ASSERT(false);
return Location_None;
}
}
///@internal
inline QString locationStr(Location loc)
{
switch (loc) {
case KDDockWidgets::Location_None:
return QStringLiteral("none");
case KDDockWidgets::Location_OnLeft:
return QStringLiteral("left");
case KDDockWidgets::Location_OnTop:
return QStringLiteral("top");
case KDDockWidgets::Location_OnRight:
return QStringLiteral("right");
case KDDockWidgets::Location_OnBottom:
return QStringLiteral("bottom");
}
return QString();
}
enum Side {
Side1,
Side2
};
enum class GrowthStrategy {
BothSidesEqually
};
enum class SizingOption {
Calculate,
UseProvided
};
inline Qt::Orientation oppositeOrientation(Qt::Orientation o) {
return o == Qt::Vertical ? Qt::Horizontal
: Qt::Vertical;
}
inline int pos(QPoint p, Qt::Orientation o) {
return o == Qt::Vertical ? p.y()
: p.x();
}
inline int length(QSize sz, Qt::Orientation o) {
return o == Qt::Vertical ? sz.height()
: sz.width();
}
inline bool locationIsVertical(Location loc)
{
return loc == Location_OnTop || loc == Location_OnBottom;
}
inline bool locationIsHorizontal(Location loc)
{
return !locationIsVertical(loc);
}
inline bool locationIsSide1(Location loc)
{
return loc == Location_OnLeft || loc == Location_OnTop;
}
inline bool locationIsSide2(Location loc)
{
return loc == Location_OnRight || loc == Location_OnBottom;
}
inline QRect adjustedRect(QRect r, Qt::Orientation o, int p1, int p2)
{
if (o == Qt::Vertical) {
r.adjust(0, p1, 0, p2);
} else {
r.adjust(p1, 0, p2, 0);
}
return r;
}
inline Qt::Orientation orientationForLocation(Location loc)
{
switch (loc) {
case Location_OnLeft:
case Location_OnRight:
return Qt::Horizontal;
case Location_None:
case Location_OnTop:
case Location_OnBottom:
return Qt::Vertical;
}
return Qt::Vertical;
}
struct SizingInfo {
QSize minSize = QSize(40, 40); // TODO: Hardcoded
QSize maxSize = QSize(16777215, 16777215); // TODO: Not supported yet
QSize proposedSize;
bool isBeingInserted = false;
};
class ItemContainer;
class Item : public QObject
{
Q_OBJECT
Q_PROPERTY(bool isPlaceholder READ isPlaceholder NOTIFY isPlaceholderChanged)
Q_PROPERTY(int x READ x NOTIFY xChanged)
Q_PROPERTY(int y READ y NOTIFY yChanged)
Q_PROPERTY(int width READ width NOTIFY widthChanged)
Q_PROPERTY(int height READ height NOTIFY heightChanged)
Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged)
Q_PROPERTY(QSize minimumSize READ minimumSize NOTIFY minimumSizeChanged)
Q_PROPERTY(bool isContainer READ isContainer CONSTANT)
public:
typedef QVector<Item*> List;
/// @brief constructs a new layout item to show @p Frame in the layout @layout
/// @param frame This is never nullptr.
/// @param layout This is never nullptr.
explicit Item(Frame *frame, MultiSplitterLayout *layout);
explicit Item(ItemContainer *parent = nullptr);
/// @brief Constructor overload used when restoring a layout and the Item is a placeholder (no frame)
explicit Item(MultiSplitterLayout *layout);
bool isRoot() const;
/// @brief Destroys its frame too.
~Item() override;
bool isVertical() const;
bool isHorizontal() const;
static Item* deserialize(const LayoutSaver::Item &, MultiSplitterLayout *layout);
LayoutSaver::Item serialize() const;
virtual void insertItem(Item *item, Location, SizingOption = SizingOption::Calculate);
int x() const;
int y() const;
QPoint pos() const;
int position(Qt::Orientation) const;
QSize size() const;
int width() const;
int height() const;
bool isVisible() const;
void setVisible(bool);
void setGeometry(QRect);
void ensureMinSize(Qt::Orientation orientation, Anchor::Side);
void ensureMinSize(Qt::Orientation orientation);
void beginBlockPropagateGeo();
void endBlockPropagateGeo();
QSize size() const;
void setSize(QSize);
QPoint pos() const;
int pos(Qt::Orientation) const;
QRect geometry() const;
bool eventFilter(QObject *, QEvent *) override;
Frame* frame() const;
QWidgetOrQuick *window() const;
QWidgetOrQuick *parentWidget() const;
bool isContainer() const;
bool isWidget() const { return !isContainer(); }
MultiSplitterLayout *layout() const;
void setLayout(MultiSplitterLayout *w); // TODO: Make the widget children of this one?
/**
* Returns the width of the widget if orientation is Vertical, the height otherwise.
*/
Qt::Orientation orientation() const;
static int separatorThickness();
virtual bool checkSanity() const;
void setParentContainer(ItemContainer *parent); // TODO: Make private
ItemContainer *parentContainer() const;
void setPos(QPoint); // TODO: Make private
void setPos(int pos, Qt::Orientation);
int position(Qt::Orientation) const;
const ItemContainer *asContainer() const;
ItemContainer *asContainer();
void setMinSize(QSize);
void setMaxSize(QSize);
virtual QSize minSize() const;
virtual QSize maxSize() const;
virtual void resize(QSize newSize);
int minLength(Qt::Orientation) const;
void setLength(int length, Qt::Orientation);
int length(Qt::Orientation) const;
int minLength(Qt::Orientation orientation) const;
Anchor *anchorAtSide(Anchor::Side side, Qt::Orientation orientation) const;
Anchor *anchor(const GeometryDiff &) const;
AnchorGroup& anchorGroup();
const AnchorGroup& anchorGroup() const;
QSize minimumSize() const;
int availableLength(Qt::Orientation) const;
bool isPlaceholder() const;
void setIsPlaceholder(bool);
/**
* @brief Returns whether this item lives in a @ref MainWindow, as opposed to a @ref FloatingWindow
*/
bool isInMainWindow() const;
///@brief turns the placeholder into a normal Item again showing @p dockWidget
void restorePlaceholder(DockWidgetBase *dockWidget, int tabIndex);
void ref() {}
void unref() {}
///@brief turns the placeholder into a normal item again
/// This overload is called when the Frame has more than 1 tab, otherwise we just use the DockWidget overload
void restorePlaceholder(Frame *frame);
bool isVisible() const;
void setIsVisible(bool);
virtual void setGeometry_recursive(QRect rect);
Item* neighbour(Side) const;
/**
* @brief Checks if the minSize is correct.
* The parent widget got a QEvent::LayoutRequest, so the Frame might have changed its constraints.
*/
void onLayoutRequest() const;
virtual void dumpLayout(int level = 0);
void setGeometry(QRect rect);
SizingInfo m_sizingInfo;
int availableOnSide(Side, Qt::Orientation) const;
QSize missingSize() const;
bool isBeingInserted() const;
void setBeingInserted(bool);
ItemContainer *root() const;
QWidget *frame() const { return m_widget; } // TODO: rename
void setFrame(QWidget *w) { m_widget = w; } // TODO rename
QWidget *window() const {
return m_widget ? m_widget->window()
: nullptr;
}
void ref();
void unref();
int refCount() const; // for tests
Q_SIGNALS:
void frameChanged();
void geometryChanged();
void isPlaceholderChanged();
void minimumSizeChanged();
private:
friend KDDockWidgets::TestDocks;
QSize actualMinSize() const; // The min size, regardless if it's a placeholder or not, so we can save the actual value while LayoutSaver::saveLayout
void restoreSizes(QSize minSize, QRect geometry); // Just for LayoutSaver::restore
void xChanged();
void yChanged();
void widthChanged();
void heightChanged();
void visibleChanged(Item *thisItem, bool visible);
void minSizeChanged(Item *thisItem);
protected:
friend class ::TestMultiSplitter;
explicit Item(bool isContainer, ItemContainer *parent);
const bool m_isContainer;
Qt::Orientation m_orientation = Qt::Vertical;
class Private;
Private *const d;
ItemContainer *m_parent = nullptr;
QRect m_geometry;
private:
bool m_isVisible = false;
QWidget *m_widget = nullptr; // TODO: Make generic
};
class ItemContainer : public Item {
Q_OBJECT
Q_PROPERTY(QVariantList items READ items NOTIFY itemsChanged)
public:
explicit ItemContainer(ItemContainer *parent = nullptr);
void insertItem(Item *item, int index, bool growItem = true);
bool checkSanity() const override;
bool hasOrientation() const;
int numChildren() const;
int numVisibleChildren() const;
bool hasChildren() const;
bool hasVisibleChildren() const;
int indexOfChild(const Item *) const;
void removeItem(Item *);
bool isEmpty() const;
void setGeometry_recursive(QRect rect) override;
ItemContainer *convertChildToContainer(Item *leaf);
void insertItem(Item *item, Location, SizingOption = SizingOption::Calculate) override;
bool hasOrientationFor(Location) const;
Item::List children() const;
Item::List visibleChildren() const;
int usableLength() const;
bool hasSingleVisibleItem() const;
bool contains(Item *item) const;
void setChildren(const Item::List children);
QSize minSize() const override;
QSize maxSize() const override;
void resize(QSize newSize) override;
int length() const;
QRect rect() const;
QVariantList items() const;
void dumpLayout(int level = 0);
void updateChildPercentages();
void restorePlaceholder(Item *);
void growNeighbours(Item *side1Neighbour, Item *side2Neighbour);
void growItem(Item *, int amount, GrowthStrategy);
void growItem(Item *, int side1Growth, int side2Growth);
Item *neighbourFor(const Item *, Side) const;
Item *visibleNeighbourFor(const Item *item, Side side) const;
QSize availableSize() const;
int availableLength() const;
int neighboursLengthFor(const Item *item, Side, Qt::Orientation) const;
int neighboursLengthFor_recursive(const Item *item, Side, Qt::Orientation) const;
int neighboursMinLengthFor(const Item *item, Side, Qt::Orientation) const;
int neighboursMinLengthFor_recursive(const Item *item, Side, Qt::Orientation) const;
int neighbourSeparatorWaste(const Item *item, Side, Qt::Orientation) const;
int neighbourSeparatorWaste_recursive(const Item *item, Side, Qt::Orientation) const;
int availableOnSide(Item *child, Side) const;
QSize missingSizeFor(Item *item, Qt::Orientation) const;
void onChildMinSizeChanged(Item *child);
void onChildVisibleChanged(Item *child, bool visible);
QVector<int> availableLengthPerNeighbour(Item *item, Side) const;
static QVector<int> calculateSqueezes(QVector<int> availabilities, int needed);
QRect suggestedDropRect(Item *newItem, Item *relativeTo, Location) const;
void positionItems();
bool isResizing() const { return m_isResizing; }
Q_SIGNALS:
void itemsChanged();
public:
QVector<qreal> m_childPercentages;
Item::List m_children;
bool m_isResizing = false;
};
}
#endif
Q_DECLARE_METATYPE(KDDockWidgets::Location)

View File

@@ -44,20 +44,11 @@ using namespace KDDockWidgets;
const QString MultiSplitterLayout::s_magicMarker = QStringLiteral("bac9948e-5f1b-4271-acc5-07f1708e2611");
static Qt::Orientation anchorOrientationForLocation(Location l)
{
return (l == Location_OnLeft || l == Location_OnRight) ? Qt::Vertical
: Qt::Horizontal;
}
MultiSplitterLayout::MultiSplitterLayout(MultiSplitter *parent)
: QObject(parent)
, m_multiSplitter(parent)
, m_leftAnchor(new Anchor(Qt::Vertical, this, Anchor::Type_LeftStatic))
, m_topAnchor(new Anchor(Qt::Horizontal, this, Anchor::Type_TopStatic))
, m_rightAnchor(new Anchor(Qt::Vertical, this, Anchor::Type_RightStatic))
, m_bottomAnchor(new Anchor(Qt::Horizontal, this, Anchor::Type_BottomStatic))
, m_staticAnchorGroup(this)
, m_rootItem(new Item())
{
Q_ASSERT(parent);
@@ -70,30 +61,8 @@ MultiSplitterLayout::MultiSplitterLayout(MultiSplitter *parent)
Q_EMIT visibleWidgetCountChanged(visibleCount());
});
m_leftAnchor->setObjectName(QStringLiteral("left"));
m_rightAnchor->setObjectName(QStringLiteral("right"));
m_bottomAnchor->setObjectName(QStringLiteral("bottom"));
m_topAnchor->setObjectName(QStringLiteral("top"));
m_leftAnchor->setFrom(m_topAnchor);
m_leftAnchor->setTo(m_bottomAnchor);
m_rightAnchor->setFrom(m_topAnchor);
m_rightAnchor->setTo(m_bottomAnchor);
m_topAnchor->setFrom(m_leftAnchor);
m_topAnchor->setTo(m_rightAnchor);
m_bottomAnchor->setFrom(m_leftAnchor);
m_bottomAnchor->setTo(m_rightAnchor);
m_staticAnchorGroup.left = m_leftAnchor;
m_staticAnchorGroup.right = m_rightAnchor;
m_staticAnchorGroup.top = m_topAnchor;
m_staticAnchorGroup.bottom = m_bottomAnchor;
clear();
positionStaticAnchors();
// Initialize min size
updateSizeConstraints();
m_inCtor = false;
@@ -105,6 +74,7 @@ MultiSplitterLayout::~MultiSplitterLayout()
m_inDestructor = true;
const auto anchors = m_anchors;
qDeleteAll(anchors);
delete m_rootItem;
DockRegistry::self()->unregisterLayout(this);
}
@@ -230,7 +200,7 @@ void MultiSplitterLayout::addWidget(QWidgetOrQuick *w, Location location, Frame
<< "; relativeTo=" << relativeToWidget
<< "; size=" << size()
<< "; w.size=" << w->size()
<< "; w.min=" << KDDockWidgets::widgetMinLength(w, anchorOrientationForLocation(location))
<< "; w.min=" << widgetMinLength(w, orientationForLocation(location))
<< "; frame=" << frame
<< "; option=" << option;
@@ -246,157 +216,8 @@ void MultiSplitterLayout::addWidget(QWidgetOrQuick *w, Location location, Frame
return;
unrefOldPlaceholders(framesFrom(w));
//Item *relativeToItem = itemForFrame(relativeToWidget);
Item *relativeToItem = itemForFrame(relativeToWidget);
ensureEnoughSize(w, location, relativeToItem);
if (option & AddingOption_StartHidden) {
addAsPlaceholder(qobject_cast<DockWidgetBase*>(w), location, relativeToItem);
return;
}
Anchor *newAnchor = nullptr;
const QRect dropRect = rectForDrop(w, location, relativeToItem);
if (dropRect.size().isNull() || dropRect.x() < 0 || dropRect.y() < 0) {
qWarning() << Q_FUNC_INFO << "Invalid drop rect" << dropRect
<< "\n size=" << m_multiSplitter->size() << "; size="<< m_size
<< "\n location=" << location
<< "\n window=" << m_multiSplitter->window()
<< "\n this=" << this
<< "\n availableHeight=" << availableLengthForOrientation(Qt::Horizontal)
<< "\n availableWidth=" << availableLengthForOrientation(Qt::Vertical)
<< "\n widget.minSize=" << widgetMinLength(w, anchorOrientationForLocation(location));
return;
}
m_addingItem = true;
auto result = this->createTargetAnchorGroup(location, relativeToItem);
AnchorGroup targetAnchorGroup = result.first;
newAnchor = result.second;
if (newAnchor && !newAnchor->isFollowing()) {
const int anchorThickness = Anchor::thickness(/*static=*/false);
qCDebug(sizing) << "Drop rect" << dropRect;
int posForExistingAnchor = 0;
int posForNewAnchor = 0;
Anchor *existingAnchor = targetAnchorGroup.anchor(location);
Anchor *direction1Anchor = nullptr;
Anchor *direction2Anchor = nullptr;
switch (location) {
case Location_OnLeft:
posForExistingAnchor = dropRect.left() - existingAnchor->thickness();
posForNewAnchor = dropRect.right() + 1;
break;
case Location_OnTop:
posForExistingAnchor = dropRect.top() - existingAnchor->thickness();
posForNewAnchor = dropRect.bottom() + 1;
break;
case Location_OnBottom:
posForExistingAnchor = dropRect.bottom() + 1;
posForNewAnchor = dropRect.top() - anchorThickness;
break;
case Location_OnRight:
posForExistingAnchor = dropRect.right() + 1;
posForNewAnchor = dropRect.left() - anchorThickness;
break;
case Location_None:
Q_ASSERT(false);
break;
}
int delta1 = 0;
int delta2 = 0;
const int originalExistingAnchorPos = existingAnchor->position();
switch (location) {
case Location_OnLeft:
case Location_OnTop:
direction1Anchor = existingAnchor;
direction2Anchor = newAnchor;
std::tie(posForExistingAnchor, posForNewAnchor) = boundInterval(posForExistingAnchor, existingAnchor, posForNewAnchor, newAnchor);
delta1 = originalExistingAnchorPos - posForExistingAnchor;
delta2 = posForNewAnchor - posForExistingAnchor;
break;
case Location_OnRight:
case Location_OnBottom:
direction1Anchor = newAnchor;
direction2Anchor = existingAnchor;
std::tie(posForNewAnchor, posForExistingAnchor) = boundInterval(posForNewAnchor, newAnchor, posForExistingAnchor, existingAnchor);
delta1 = posForExistingAnchor - posForNewAnchor;
delta2 = posForExistingAnchor - originalExistingAnchorPos;
break;
case Location_None:
qWarning() << Q_FUNC_INFO << "Location can't be none";
return;
}
newAnchor->setPosition(posForNewAnchor);
if (posForExistingAnchor != originalExistingAnchorPos) {
if (existingAnchor->isStatic()) {
qWarning() << "Trying to move static anchor from" << originalExistingAnchorPos << "to"
<< posForExistingAnchor << "; location=" << location
<< "; dropRect=" << dropRect
<< "; existingAnchor=" << existingAnchor
<< "; size=" << m_size
<< "; Qt::WA_PendingResizeEvent=" << m_multiSplitter->testAttribute(Qt::WA_PendingResizeEvent)
<< "; Qt::WA_WState_Created=" << m_multiSplitter->testAttribute(Qt::WA_WState_Created);
}
existingAnchor->setPosition(posForExistingAnchor);
}
// Make sure not just the side1/side2 adjacent widgets are contributing space for our new widget
// the ones adjacents to the adjacents (recursive) must also give.
// The code would work fine without this, it's just that it wouldn't look fair.
propagateResize(delta1, direction1Anchor, Anchor::Side1);
propagateResize(delta2, direction2Anchor, Anchor::Side2);
}
if (newAnchor) {
// Also ensure the widget has a minimum size in the other direction. So, when adding to
// left/right, it will still have its minimum height honoured, and vice-versa.
QPair<Anchor*, Anchor*> adjacentAnchors = targetAnchorGroup.adjacentAnchors(newAnchor);
const int bound1 = boundPositionForAnchor(adjacentAnchors.first, Anchor::Side1);
const int bound2 = boundPositionForAnchor(adjacentAnchors.second, Anchor::Side2);
const Qt::Orientation otherOrientation = adjacentAnchors.first->orientation();
const int min = widgetMinLength(w, otherOrientation);
const int has = targetAnchorGroup.itemSize(otherOrientation);
const int needs = min - has;
if (needs > 0) {
const int pos1 = qMax(bound1, adjacentAnchors.first->position() - needs);
const int pos2 = pos1 + adjacentAnchors.first->thickness() + min;
Q_ASSERT(pos2 <= bound2);
adjacentAnchors.first->setPosition(pos1);
adjacentAnchors.second->setPosition(pos2);
}
}
auto sourceMultiSplitter = qobject_cast<MultiSplitter *>(w);
auto sourceMultiSplitterLayout = sourceMultiSplitter ? sourceMultiSplitter->multiSplitterLayout()
: nullptr;
if (sourceMultiSplitterLayout) {
auto items = sourceMultiSplitterLayout->items();
targetAnchorGroup.addItem(sourceMultiSplitterLayout);
addItems_internal(items);
} else {
Q_ASSERT(frame);
auto item = new Item(frame, this);
targetAnchorGroup.addItem(item);
addItems_internal(ItemList{ item });
}
updateAnchorFollowing();
m_addingItem = false;
maybeCheckSanity();
}
void MultiSplitterLayout::addItems_internal(const ItemList &items, bool updateConstraints, bool emitSignal)
@@ -1015,24 +836,6 @@ Anchor *MultiSplitterLayout::staticAnchor(Anchor::Type type) const
return nullptr;
}
Anchor *MultiSplitterLayout::staticAnchor(Anchor::Side side, Qt::Orientation orientation) const
{
if (orientation == Qt::Vertical) {
return side == Anchor::Side1 ? m_leftAnchor : m_rightAnchor;
} else {
return side == Anchor::Side1 ? m_topAnchor : m_bottomAnchor;
}
}
AnchorGroup MultiSplitterLayout::anchorsForPos(QPoint pos) const
{
Item *item = itemAt(pos);
if (!item)
return AnchorGroup(const_cast<MultiSplitterLayout *>(this));
return item->anchorGroup();
}
void MultiSplitterLayout::dumpDebug() const
{
Q_EMIT aboutToDumpDebug();
@@ -1193,51 +996,6 @@ Anchor::List MultiSplitterLayout::anchors(Qt::Orientation orientation, bool incl
return result;
}
Anchor *MultiSplitterLayout::newAnchor(AnchorGroup &group, Location location)
{
qCDebug(::anchors) << "MultiSplitterLayout::newAnchor" << location;
Anchor *newAnchor = nullptr;
Anchor *donor = nullptr;
switch (location) {
case Location_OnLeft:
donor = group.left;
newAnchor = Anchor::createFrom(donor);
group.right = newAnchor;
break;
case Location_OnTop:
donor = group.top;
newAnchor = Anchor::createFrom(donor);
group.bottom = newAnchor;
break;
case Location_OnRight:
donor = group.right;
newAnchor = Anchor::createFrom(donor);
group.left = newAnchor;
break;
case Location_OnBottom:
donor = group.bottom;
newAnchor = Anchor::createFrom(donor);
group.top = newAnchor;
break;
default:
qWarning() << "MultiSplitterLayout::newAnchor invalid location!";
return nullptr;
}
Q_ASSERT(newAnchor);
Q_ASSERT(donor);
Q_ASSERT(donor != newAnchor);
updateAnchorsFromTo(donor, newAnchor);
qCDebug(::anchors()) << newAnchor->hasNonPlaceholderItems(Anchor::Side1)
<< newAnchor->hasNonPlaceholderItems(Anchor::Side2)
<< newAnchor->side1Items() << newAnchor->side2Items()
<< "; donor" << donor
<< "; follows=" << newAnchor->followee();
return newAnchor;
}
void MultiSplitterLayout::blockItemPropagateGeo(bool block)
{
for (Item *item : m_items) {
@@ -1300,253 +1058,9 @@ QVector<DockWidgetBase *> MultiSplitterLayout::dockWidgets() const
return result;
}
QPair<AnchorGroup,Anchor*> MultiSplitterLayout::createTargetAnchorGroup(KDDockWidgets::Location location, Item *relativeToItem)
bool MultiSplitterLayout::checkSanity() const
{
const bool relativeToThis = relativeToItem == nullptr;
AnchorGroup group = relativeToThis ? staticAnchorGroup()
: anchorsForPos(relativeToItem->geometry().center());
if (!group.isValid()) {
qWarning() << Q_FUNC_INFO << "Invalid anchor group:" << group
<< "; staticAnchorGroup=" << staticAnchorGroup()
<< "; relativeTo=" << relativeToItem;
dumpDebug();
}
Anchor *newAnchor = nullptr;
if (relativeToThis) {
if (!isEmpty())
newAnchor = this->newAnchor(group, location);
} else {
newAnchor = group.createAnchorFrom(location, relativeToItem);
group.setAnchor(newAnchor, KDDockWidgets::oppositeLocation(location));
}
return { group, newAnchor };
}
bool MultiSplitterLayout::checkSanity(AnchorSanityOption options) const
{
if (m_inCtor || LayoutSaver::restoreInProgress())
return true;
auto check = [this, options] (Item *item, Qt::Orientation orientation) {
int numSide1 = 0;
int numSide2 = 0;
const auto &anchors = this->anchors(orientation, /*includeStatic=*/ true);
for (Anchor *anchor : anchors) {
if (anchor->containsItem(item, Anchor::Side1))
numSide1++;
if (anchor->containsItem(item, Anchor::Side2))
numSide2++;
}
if (numSide1 != 1 || numSide2 != 1) {
dumpDebug();
qWarning() << "MultiSplitterLayout::checkSanity:" << "Problem detected! while processing"
<< orientation << "anchors"
<< "; numSide1=" << numSide1
<< "; numSide2=" << numSide2;
for (Anchor *anchor : anchors) {
if (anchor->containsItem(item, Anchor::Side1))
qDebug() << "Anchor" << anchor << "contains said widget on side1";
if (anchor->containsItem(item, Anchor::Side2))
qDebug() << "Anchor" << anchor << "contains said widget on side2";
}
qWarning() << "MultiSplitterLayout::checkSanity:" << numSide1 << numSide2 << item
<< "\n" << m_topAnchor->items(Anchor::Side2)
<< "\n" << m_bottomAnchor->items(Anchor::Side1)
<< "\n" << m_leftAnchor->items(Anchor::Side2)
<< "\n" << m_rightAnchor->items(Anchor::Side1);
return false;
}
if ((options & AnchorSanity_WidgetInvalidSizes) && !item->isPlaceholder()) {
if (item->width() <= 0 || item->height() <= 0) {
dumpDebug();
qWarning() << "Invalid size for widget" << item << item->size() << "; isPlaceholder=" << item->isPlaceholder()
<< "; minSize=" << item->minimumSize();
return false;
}
}
return true;
};
if (!m_topAnchor || !m_leftAnchor || !m_rightAnchor || !m_bottomAnchor) {
qWarning() << Q_FUNC_INFO << "Invalid static anchors"
<< m_leftAnchor << m_topAnchor << m_rightAnchor << m_bottomAnchor;
return false;
}
if (m_topAnchor->position() != 0 || m_leftAnchor->position() != 0 ||
m_rightAnchor->position() != width() - m_rightAnchor->thickness() ||
m_bottomAnchor->position() != height() - m_bottomAnchor->thickness()) {
qWarning() << Q_FUNC_INFO << "Invalid anchor position"
<< " left=" << m_leftAnchor->position()
<< " top=" << m_topAnchor->position()
<< " right=" << m_rightAnchor->position()
<< " bottom=" << m_bottomAnchor->position()
<< "; size=" << m_size
<< "; min=" << m_minSize;
return false;
}
for (Anchor *anchor : qAsConst(m_anchors)) {
if (!anchor->isValid()) {
dumpDebug();
qWarning() << "invalid anchor" << anchor;
return false;
}
auto checkSides = [this, anchor] (Anchor::Side side) {
for (Item *item : anchor->items(side)) {
if (!contains(item)) {
dumpDebug();
qWarning() << "MultiSplitterLayout::checkSanity: Anchor has" << item << "but multi splitter does not";
return false;
}
}
return true;
};
if (!checkSides(Anchor::Side1) || !checkSides(Anchor::Side2))
return false;
if (anchor->isFollowing() && !qobject_cast<Anchor*>(anchor->followee())) {
qWarning() << "Anchor is following but followee was deleted already";
return false;
}
if (options & AnchorSanity_Followers) {
const bool hasItemsOnBothSides = anchor->hasNonPlaceholderItems(Anchor::Side1) && anchor->hasNonPlaceholderItems(Anchor::Side2);
if (!anchor->isStatic() && !anchor->isFollowing() && !hasItemsOnBothSides && anchorsFollowing(anchor).isEmpty()) {
qWarning() << "Non static anchor should have items on both sides unless it's following or being followed" << anchor;
}
}
if (!anchor->isFollowing() &&anchor->geometry() != anchor->separatorWidget()->geometry()) {
qWarning() << Q_FUNC_INFO << anchor << anchor->separatorWidget()
<< "Inconsistent anchor geometry" << anchor->geometry() << "; " << anchor->separatorWidget()->geometry();
return false;
}
if (options & AnchorSanity_Visibility) {
if (multiSplitter()->isVisible() && !anchor->isFollowing() && !anchor->separatorWidget()->isVisible()) {
qWarning() << Q_FUNC_INFO << "Anchor should be visible" << anchor;
return false;
}
}
}
for (Item *item : qAsConst(m_items)) {
if (!check(item, Qt::Vertical))
return false;
if (!check(item, Qt::Horizontal))
return false;
}
// Check that no widget intersects with an anchor
if (options & AnchorSanity_Intersections) {
for (Item *item: items()) {
for (Anchor *a : anchors()) {
if (!item->isPlaceholder() && item->geometry().intersects(a->geometry())) {
dumpDebug();
qWarning() << "MultiSplitterLayout::checkSanity: Widget" << item << "with rect" << item->geometry()
<< "Intersects anchor" << a << "with rect" << a->geometry()
<< "; a.visible|following|valid|unneeded=" << a->separatorWidget()->isVisible()<< a->isFollowing() << a->isValid() << a->isUnneeded();
return false;
}
}
}
}
if (options & AnchorSanity_WidgetGeometry) {
for (Item *item: items()) {
if (!item->isPlaceholder() && item->geometry() != item->frame()->geometry()) {
qWarning() << Q_FUNC_INFO << "Invalid geometry for item" << item << item->geometry() << item->frame()->geometry();
return false;
}
if (!item->anchorGroup().isValid()) {
qWarning() << Q_FUNC_INFO << "Invalid item group for item" << item->anchorGroup();
return false;
}
if (!item->isPlaceholder() && item->anchorGroup().itemSize() != item->size()) {
qWarning() << Q_FUNC_INFO << "Invaild item size="
<< item->size()
<< "group size="
<< item->anchorGroup().itemSize();
return false;
}
}
}
if (options & AnchorSanity_WidgetMinSizes) {
for (Item *item : items()) {
if (item->isPlaceholder())
continue;
const int minWidth = item->minLength(Qt::Vertical);
const int minHeight = item->minLength(Qt::Horizontal);
if (item->width() < minWidth) {
qWarning() << "MultiSplitterLayout::checkSanity: Widget has width=" << item->width()
<< "but minimum is" << minWidth
<< item;
return false;
}
if (item->height() < minHeight) {
qWarning() << "MultiSplitterLayout::checkSanity: Widget has height=" << item->height()
<< "but minimum is" << minHeight
<< item;
return false;
}
}
}
for (DockWidgetBase *dw : DockRegistry::self()->dockwidgets()) {
Frame *frame = dw->frame();
auto tabWidgetParent = frame ? frame->tabWidget() : nullptr;
const bool shouldBeChecked = dw->isVisible() || tabWidgetParent;
if (shouldBeChecked != dw->toggleAction()->isChecked()) {
qWarning() << Q_FUNC_INFO << "Invalid state for DockWidgetBase::toggleAction()"
<< dw->toggleAction()->isChecked();
return false;
}
}
/* TODO: uncomment when all tests pass
if (m_topAnchor->position() != 0 || m_leftAnchor->position() != 0) {
qWarning() << Q_FUNC_INFO << "Invalid top or left anchor position"
<< m_topAnchor->position() << m_leftAnchor->position();
return false;
}
if (m_rightAnchor->position() != m_size.width() - 1 || m_bottomAnchor->position() != m_size.height() - 1) {
qWarning() << Q_FUNC_INFO << "Invalid right or bottom anchor position"
<< m_rightAnchor->position() << m_bottomAnchor->position()
<< "; m_size=" << m_size;
return false;
}
*/
return true;
}
void MultiSplitterLayout::maybeCheckSanity()
{
#if defined(DOCKS_DEVELOPER_MODE)
if (!isRestoringPlaceholder() && !checkSanity(AnchorSanityOption(AnchorSanity_All & ~AnchorSanity_Visibility)))
qWarning() << Q_FUNC_INFO << "Sanity check failed";
#endif
return m_rootItem->checkSanity();
}
void MultiSplitterLayout::ensureHasAvailableSize(QSize needed)
@@ -1565,132 +1079,6 @@ void MultiSplitterLayout::ensureHasAvailableSize(QSize needed)
setSize(newSize);
}
void MultiSplitterLayout::restorePlaceholder(Item *item)
{
QScopedValueRollback<bool> restoring(m_restoringPlaceholder, true);
AnchorGroup anchorGroup = item->anchorGroup();
const QSize availableSize = this->availableSize();
const QSize hardcodedMinSize = MultiSplitterLayout::hardcodedMinimumSize();
const QSize widgetMinSize = { qMax(hardcodedMinSize.width(), KDDockWidgets::widgetMinLength(item->frame(), Qt::Vertical)),
qMax(hardcodedMinSize.height(), KDDockWidgets::widgetMinLength(item->frame(), Qt::Horizontal)) };
const QSize newSize = {qMax(qMin(item->length(Qt::Vertical), availableSize.width()), widgetMinSize.width()),
qMax(qMin(item->length(Qt::Horizontal), availableSize.height()), widgetMinSize.height()) };
// Our layout has enough size for the dock widget
ensureHasAvailableSize(newSize);
item->setIsPlaceholder(false);
item->beginBlockPropagateGeo();
updateSizeConstraints();
Anchor::List anchorsFollowing = anchorGroup.anchorsFollowingInwards();
if (anchorsFollowing.isEmpty()) {
// There's no separator to move, it means it's a static anchor group (layout is empty, so the anchors
// are the actual borders of the window
// dumpDebug();
Q_ASSERT(anchorGroup.isStaticOrFollowsStatic());
anchorGroup.updateItemSizes();
maybeCheckSanity();
item->endBlockPropagateGeo();
return;
}
clearAnchorsFollowing();
QHash<Anchor*,Anchor*> anchorsThatWillFollowOthers = anchorsShouldFollow();
if (!anchorsFollowing.contains(anchorGroup.top) && !anchorsFollowing.contains(anchorGroup.bottom)) {
anchorGroup.top->updateItemSizes();
anchorGroup.bottom->updateItemSizes();
}
if (!anchorsFollowing.contains(anchorGroup.left) && !anchorsFollowing.contains(anchorGroup.right)) {
anchorGroup.left->updateItemSizes();
anchorGroup.right->updateItemSizes();
}
for (Anchor *anchorFollowingInwards : anchorsFollowing) {
const Qt::Orientation orientation = anchorFollowingInwards->orientation();
Anchor *side1Anchor = anchorGroup.anchorAtSide(Anchor::Side1, orientation); // returns the left if vertical, otherwise top
Anchor *side2Anchor = anchorGroup.anchorAtSide(Anchor::Side2, orientation); // returns the right if vertical, otherwise bottom
if (anchorsThatWillFollowOthers.contains(side1Anchor)) {
Anchor *followee = anchorsThatWillFollowOthers.value(side1Anchor);
side1Anchor->setFollowee(followee);
side1Anchor = followee;
}
if (anchorsThatWillFollowOthers.contains(side2Anchor)) {
Anchor *followee = anchorsThatWillFollowOthers.value(side2Anchor);
side2Anchor->setFollowee(followee);
side2Anchor = followee;
}
const int oldPosition1 = side1Anchor->position();
const int oldPosition2 = side2Anchor->position();
const int boundPosition1 = side1Anchor->isStatic() ? side1Anchor->position()
: boundPositionForAnchor(side1Anchor, Anchor::Side1);
const int boundPosition2 = side2Anchor->isStatic() ? side2Anchor->position()
: boundPositionForAnchor(side2Anchor, Anchor::Side2);
const int newLength = anchorFollowingInwards->isVertical() ? newSize.width() : newSize.height();
// Let's try that each anchor contributes 50%, so that the widget appears centered
const int suggestedLength1 = qMin(newLength, qCeil(newLength / 2) + side1Anchor->thickness() + 1);
const int maxPos1 = boundPosition2 - newLength - side1Anchor->thickness();
const int newPosition1 = qMax(qMin(maxPos1, oldPosition1 - suggestedLength1), boundPosition1); // Honour the bound
const int newPosition2 = newPosition1 + side1Anchor->thickness() + newLength; // No need to check bound2, we have enough space afterall
qCDebug(placeholder) << Q_FUNC_INFO
<< "; oldPos1=" << oldPosition1
<< "; oldPos2=" << oldPosition2
<< "; newPosition1=" << newPosition1
<< "; newPosition2=" << newPosition2
<< "; bounds1=" << boundPosition1
<< "; bounds2=" << boundPosition2
<< "; item.geo=" << item->geometry()
<< "; newSize=" << newSize
<< "; side1Anchor=" << side1Anchor
<< "; side2Anchor=" << side2Anchor
<< side1Anchor->followee() << side2Anchor->followee()
<< "; anchorFollowing=" << anchorFollowingInwards
<< "; size=" << m_size
<< "; minSize=" << m_minSize
<< "; widgetMinSize=" << widgetMinSize
<< "; available_old=" << availableSize
<< "; available_new=" << availableLengthForOrientation(orientation)
<< "; item.size=" << item->size();
if (newPosition1 < boundPosition1 || newPosition2 > boundPosition2) {
qWarning() << Q_FUNC_INFO << "Out of bounds"
// << "bounds.anchor1=" << boundPositionsForAnchor(side1Anchor)
<< "bounds.anchor2=" << boundPositionsForAnchor(side2Anchor)
<< "; side1Anchor.thickness" << side1Anchor->thickness()
<< "; side2Anchor.thickness" << side2Anchor->thickness();
}
// We don't want item to resize the anchors while setting newPosition1, we already calculated it
if (side1Anchor->isStatic()) {
side1Anchor->updateItemSizes();
} else {
side1Anchor->setPosition(newPosition1);
}
if (side2Anchor->isStatic()) {
side2Anchor->updateItemSizes();
} else {
side2Anchor->setPosition(newPosition2);
}
}
item->endBlockPropagateGeo();
updateAnchorFollowing();
maybeCheckSanity();
}
void MultiSplitterLayout::unrefOldPlaceholders(const Frame::List &framesBeingAdded) const
{
for (Frame *frame : framesBeingAdded) {
@@ -1763,31 +1151,6 @@ void MultiSplitterLayout::setMinimumSize(QSize sz)
qCDebug(sizing) << Q_FUNC_INFO << "minSize = " << m_minSize;
}
void MultiSplitterLayout::updateAnchorsFromTo(Anchor *oldAnchor, Anchor *newAnchor)
{
// Update the from/to of other anchors
for (Anchor *other : qAsConst(m_anchors)) {
Q_ASSERT(other);
Q_ASSERT(other->isValid());
if (!other->isStatic() && other->orientation() != newAnchor->orientation()) {
if (other->to() == oldAnchor) {
other->setTo(newAnchor);
} else if (other->from() == oldAnchor) {
other->setFrom(newAnchor);
}
if (!other->isValid()) {
qDebug() << "MultiSplitterLayout::updateAnchorsFromTo: anchor is now invalid."
<< "\n old=" << oldAnchor
<< "\n new=" << newAnchor
<< "\n from=" << other->from()
<< "\n to=" << other->to()
<< "\n other=" << other;
}
}
}
}
void MultiSplitterLayout::clearAnchorsFollowing()
{
for (Anchor *anchor : qAsConst(m_anchors))
@@ -1894,6 +1257,11 @@ const ItemList MultiSplitterLayout::items() const
return m_items;
}
Item *MultiSplitterLayout::rootItem() const
{
return m_rootItem;
}
bool MultiSplitterLayout::eventFilter(QObject *o, QEvent *e)
{
if (m_inDestructor || e->spontaneous() || !m_multiSplitter)

View File

@@ -34,7 +34,6 @@
#include "../Frame_p.h"
#include "Anchor_p.h"
#include "AnchorGroup_p.h"
#include "docks_export.h"
#include "KDDockWidgets.h"
#include "Item_p.h"
@@ -45,7 +44,6 @@
namespace KDDockWidgets {
class MultiSplitter;
class Length;
namespace Debug {
class DebugWindow;
@@ -82,22 +80,6 @@ inline Anchor::Side sideForLocation(Location loc)
return Anchor::Side_None;
}
inline Qt::Orientation orientationForLocation(Location loc)
{
switch (loc) {
case KDDockWidgets::Location_OnLeft:
case KDDockWidgets::Location_OnRight:
return Qt::Vertical;
case KDDockWidgets::Location_OnTop:
case KDDockWidgets::Location_OnBottom:
return Qt::Horizontal;
default:
break;
}
return Qt::Vertical;
}
/**
* A MultiSplitter is like a QSplitter but supports mixing vertical and horizontal splitters in
* any combination.
@@ -221,6 +203,8 @@ public:
*/
const ItemList items() const;
Item* rootItem() const;
/**
* Called by the indicators, so they draw the drop rubber band at the correct place.
* The rect for the rubberband when dropping a widget at the specified location.
@@ -311,22 +295,7 @@ public:
bool validateInputs(QWidgetOrQuick *widget, KDDockWidgets::Location location, const Frame *relativeToFrame, AddingOption option) const;
// For debug/hardening
enum AnchorSanityOption {
AnchorSanity_Normal = 0,
AnchorSanity_Intersections = 1,
AnchorSanity_WidgetMinSizes = 2,
AnchorSanity_WidgetInvalidSizes = 4,
AnchorSanity_Followers = 8,
AnchorSanity_WidgetGeometry = 16,
AnchorSanity_Visibility = 32,
AnchorSanity_All = AnchorSanity_Intersections | AnchorSanity_WidgetMinSizes | AnchorSanity_WidgetInvalidSizes | AnchorSanity_Followers | AnchorSanity_WidgetGeometry | AnchorSanity_Visibility
};
Q_ENUM(AnchorSanityOption)
bool checkSanity(AnchorSanityOption o = AnchorSanity_All) const;
void maybeCheckSanity();
void restorePlaceholder(Item *item);
bool checkSanity() const;
/**
* @brief Removes unneeded placeholder items when adding new frames.
@@ -362,14 +331,6 @@ public:
*/
QVector<DockWidgetBase*> dockWidgets() const;
/**
* @brief Creates an AnchorGroup suited for adding a dockwidget to @location relative to @relativeToItem
*
* Returns the AnchorGroup and a new Anchor, if it was needed.
* If relativeTo is null then it returns the static anchor group.
*/
QPair<AnchorGroup, Anchor *> createTargetAnchorGroup(Location location, Item *relativeToItem);
struct Length {
Length() = default;
Length(int side1, int side2)
@@ -429,15 +390,10 @@ Q_SIGNALS:
public:
bool eventFilter(QObject *o, QEvent *e) override;
AnchorGroup anchorsForPos(QPoint pos) const;
AnchorGroup staticAnchorGroup() const;
Anchor::List anchors(Qt::Orientation, bool includeStatic = false, bool includePlaceholders = true) const;
Anchor *newAnchor(AnchorGroup &group, KDDockWidgets::Location location);
friend QDebug operator<<(QDebug d, const AnchorGroup &group);
static const QString s_magicMarker;
void ensureAnchorsBounded();
private:
friend struct AnchorGroup;
friend class Item;
friend class Anchor;
friend class TestDocks;
@@ -525,36 +481,6 @@ private:
*/
QSize availableSize() const;
/**
* @brief Increases the layout size if @ref availableSize is less than @needed
*/
void ensureHasAvailableSize(QSize needed);
/**
* Removes the widgets associated with oldAnchor and gives them to newAnchor.
* Called when removing a widget results in unneeded anchors.
*/
void updateAnchorsFromTo(Anchor *oldAnchor, Anchor *newAnchor);
void clearAnchorsFollowing();
void updateAnchorFollowing(const AnchorGroup &groupBeingRemoved = {});
QHash<Anchor *, Anchor *> anchorsShouldFollow() const;
/**
* Positions the static anchors at their correct places. Called when the MultiSplitter is resized.
* left and top anchor are at position 0, while right/bottom are at position= width/height.
* (Approx, due to styling margins and whatnot)
*/
void positionStaticAnchors();
/**
* When this MultiSplitter is resized, it gives or steals the less/extra space evenly through
* all widgets.
**/
void redistributeSpace();
void redistributeSpace(QSize oldSize, QSize newSize);
void redistributeSpace_recursive(Anchor *fromAnchor, int minAnchorPos);
/**
* Returns the width (if orientation = Horizontal), or height that is occupied by anchors.
* For example, an horizontal anchor has 2 or 3 px of width, so that's space that can't be
@@ -562,62 +488,32 @@ private:
*/
int wastedSpacing(Qt::Orientation) const;
/**
* Called by addWidget().
*
* When adding a widget to a layout, it will steal space from the widgets on the left (or top) (@p direction being Anchor::Side1),
* and from the widgets on the right (or bottom) (@p direction being Anchor::Side2).
*
* @param delta the amount of space we're stealing in the specified side
* @param fromAnchor The anchor we're starting from
* @param direction if we're going left/top (Side1) or right/bottom (Side2)
*/
void propagateResize(int delta, Anchor *fromAnchor, Anchor::Side direction);
// Helper function for propagateResize()
void collectPaths(QVector<Anchor::List> &paths, Anchor *fromAnchor, Anchor::Side direction);
// convenience for the unit-tests
// Moves the widget's bottom or right anchor, to resize it.
void resizeItem(Frame *frame, int newSize, Qt::Orientation);
void ensureItemsMinSize();
///@brief returns whether we're inside setSize();
bool isResizing() const { return m_resizing; }
bool isRestoringPlaceholder() const { return m_restoringPlaceholder; }
bool isAddingItem() const { return m_addingItem; }
QString affinityName() const;
MultiSplitter *const m_multiSplitter;
Anchor::List m_anchors;
Anchor *m_leftAnchor = nullptr;
Anchor *m_topAnchor = nullptr;
Anchor *m_rightAnchor = nullptr;
Anchor *m_bottomAnchor = nullptr;
ItemList m_items;
bool m_inCtor = true;
bool m_inDestructor = false;
bool m_beingMergedIntoAnotherMultiSplitter = false;
bool m_beingMergedIntoAnotherMultiSplitter = false; // TODO
bool m_restoringPlaceholder = false;
bool m_resizing = false;
bool m_addingItem = false;
QSize m_minSize = QSize(0, 0);
AnchorGroup m_staticAnchorGroup;
QPointer<Anchor> m_anchorBeingDragged;
QSize m_size;
Item *const m_rootItem;
};
inline QDebug operator<<(QDebug d, const AnchorGroup &group) {
d << "AnchorGroup: top=" << group.top << "; left=" << group.left
<< "; right=" << group.right << "; bottom=" << group.bottom;
return d;
}
/**
* Returns the widget's min-width if orientation is Vertical, the min-height otherwise.
*/

View File

@@ -0,0 +1,5 @@
find_package(Qt5Test)
add_executable(tst_multisplitter tst_multisplitter.cpp)
target_link_libraries(tst_multisplitter kddockwidgets_layouting Qt5::Test)

View File

@@ -0,0 +1,560 @@
/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Item.h"
#include <QtTest/QtTest>
#include <memory.h>
// TODO: namespace
using namespace KDDockWidgets;
static int st = Item::separatorThickness();
static std::unique_ptr<ItemContainer> createRoot() {
auto item = new ItemContainer();
item->setSize({ 1000, 1000 });
return std::unique_ptr<ItemContainer>(item);
}
static Item* createItem(const QString &objName) {
auto item = new Item();
item->setObjectName(objName);
return item;
}
class TestMultiSplitter : public QObject
{
Q_OBJECT
public Q_SLOTS:
void initTestCase()
{
}
private Q_SLOTS:
void tst_createRoot();
void tst_insertOne();
void tst_insertThreeSideBySide();
void tst_insertOnWidgetItem1();
void tst_insertOnWidgetItem2();
void tst_insertOnWidgetItem1DifferentOrientation();
void tst_insertOnWidgetItem2DifferentOrientation();
void tst_insertOnRootDifferentOrientation();
void tst_removeItem1();
void tst_removeItem2();
void tst_minSize();
void tst_resize();
void tst_resizeWithConstraints();
void tst_availableSize();
void tst_missingSize();
void tst_ensureEnoughSize();
};
void TestMultiSplitter::tst_createRoot()
{
auto root = createRoot();
QVERIFY(root->isRoot());
QVERIFY(!root->isWidget());
QVERIFY(root->isContainer());
QVERIFY(root->hasOrientation());
QCOMPARE(root->size(), QSize(1000, 1000));
QVERIFY(root->checkSanity());
}
void TestMultiSplitter::tst_insertOne()
{
auto root = createRoot();
auto item = createItem("1");
root->insertItem(item, Location_Top);
QCOMPARE(root->numChildren(), 1);
QVERIFY(item->isWidget());
QVERIFY(!item->isContainer());
QCOMPARE(root->size(), QSize(1000, 1000));
QCOMPARE(item->size(), root->size());
QCOMPARE(item->pos(), QPoint());
QCOMPARE(item->pos(), root->pos());
QVERIFY(root->hasChildren());
QVERIFY(root->checkSanity());
}
void TestMultiSplitter::tst_insertThreeSideBySide()
{
// Result is [1, 2, 3]
auto root = createRoot();
auto item1 = new Item();
auto item2 = new Item();
auto item3 = new Item();
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
root->insertItem(item3, Location_Right);
QVERIFY(root->checkSanity());
QCOMPARE(root->numChildren(), 3);
}
void TestMultiSplitter::tst_insertOnWidgetItem1()
{
// We insert into a widget item instead of in a container. It will insert in the container still
// Result is still [1, 2, 3]
auto root = createRoot();
auto item1 = new Item();
auto item2 = new Item();
auto item3 = new Item();
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
item2->insertItem(item3, Location_Right);
QVERIFY(item3->x() > item2->x());
QCOMPARE(item3->y(), item2->y());
QVERIFY(root->checkSanity());
QCOMPARE(root->numChildren(), 3);
}
void TestMultiSplitter::tst_insertOnWidgetItem2()
{
// Same, but result [1, 3, 2]
auto root = createRoot();
auto item1 = new Item();
auto item2 = new Item();
auto item3 = new Item();
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
item2->insertItem(item3, Location_Left);
QVERIFY(item1->x() < item3->x());
QVERIFY(item3->x() < item2->x());
QCOMPARE(item3->y(), item2->y());
QVERIFY(root->checkSanity());
QCOMPARE(root->numChildren(), 3);
}
void TestMultiSplitter::tst_insertOnWidgetItem1DifferentOrientation()
{
// Result [1, 2, |3 |]
// |3.1|
auto root = createRoot();
auto item1 = createItem("1");
auto item2 = createItem("2");
auto item3 = createItem("3");
auto item31 = createItem("3.2");
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
item2->insertItem(item3, Location_Right);
item3->insertItem(item31, Location_Bottom);
auto container3 = item3->parentContainer();
QVERIFY(container3->isContainer());
QVERIFY(container3 != root.get());
QVERIFY(root->isHorizontal());
QVERIFY(container3->isVertical());
QCOMPARE(root->numChildren(), 3);
QCOMPARE(container3->numChildren(), 2);
QVERIFY(item1->x() < item2->x());
QVERIFY(item3->parentContainer()->x() > item2->x());
QCOMPARE(item3->x(), 0);
QCOMPARE(item3->y(), item2->y());
QCOMPARE(item1->y(), item2->y());
QVERIFY(item31->y() >= item3->y());
QCOMPARE(item31->parentContainer(), container3);
QCOMPARE(item3->parentContainer(), container3);
QCOMPARE(container3->parentContainer(), root.get());
QCOMPARE(QPoint(0, 0), item3->pos());
QCOMPARE(container3->width(), item3->width());
QCOMPARE(container3->height(), item3->height() + st + item31->height());
QVERIFY(root->checkSanity());
}
void TestMultiSplitter::tst_insertOnWidgetItem2DifferentOrientation()
{
// Result [1, 2, |3 3.2|]
// |3.1 |
auto root = createRoot();
auto item1 = createItem("1");
auto item2 = createItem("2");
auto item3 = createItem("3");
auto item31 = createItem("3.1");
auto item32 = createItem("3.2");
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
item2->insertItem(item3, Location_Right);
item3->insertItem(item31, Location_Bottom);
auto container3Parent = item3->parentContainer();
item3->insertItem(item32, Location_Right);
auto container3 = item3->parentContainer();
QCOMPARE(container3->parentContainer(), container3Parent);
QVERIFY(container3->isContainer());
QVERIFY(container3 != root.get());
QVERIFY(root->isHorizontal());
QVERIFY(container3->isHorizontal());
QVERIFY(container3Parent->isVertical());
QCOMPARE(root->numChildren(), 3);
QCOMPARE(container3->numChildren(), 2);
QCOMPARE(container3Parent->numChildren(), 2);
QVERIFY(item1->x() < item2->x());
QCOMPARE(container3->pos(), QPoint(0, 0l));
QCOMPARE(item3->pos(), container3->pos());
QVERIFY(container3Parent->x() > item2->x());
QCOMPARE(item3->y(), item2->y());
QCOMPARE(item1->y(), item2->y());
QVERIFY(item31->y() >= item3->y());
QCOMPARE(item31->parentContainer(), container3Parent);
QCOMPARE(item3->parentContainer(), container3);
QCOMPARE(container3Parent->parentContainer(), root.get());
QCOMPARE(container3->pos(), item3->pos());
QCOMPARE(container3->width(), item3->width() + item32->width() + st);
QCOMPARE(container3->height(), item3->height());
QCOMPARE(container3Parent->height(), item3->height() + st + item31->height());
QVERIFY(root->checkSanity());
}
void TestMultiSplitter::tst_insertOnRootDifferentOrientation()
{
// [ 4 ]
// Result [1, 2, |3 3.2|]
// |3.1 |
auto root = createRoot();
auto item1 = createItem("1");
auto item2 = createItem("2");
auto item3 = createItem("3");
auto item31 = createItem("3.1");
auto item32 = createItem("3.2");
auto item4 = createItem("4");
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
item2->insertItem(item3, Location_Right);
item3->insertItem(item31, Location_Bottom);
item3->insertItem(item32, Location_Right);
root->insertItem(item4, Location_Top);
QCOMPARE(item4->parentContainer(), root.get());
QCOMPARE(item4->pos(), root->pos());
QCOMPARE(item4->width(), root->width());
QVERIFY(root->checkSanity());
}
void TestMultiSplitter::tst_removeItem1()
{
// [ 4 ]
// Result [1, 2, |3 3.2|]
// |3.1 |
auto root = createRoot();
auto item1 = createItem("1");
auto item2 = createItem("2");
auto item3 = createItem("3");
auto item31 = createItem("3.1");
auto item32 = createItem("3.2");
auto item4 = createItem("4");
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
item2->insertItem(item3, Location_Right);
item3->insertItem(item31, Location_Bottom);
item3->insertItem(item32, Location_Right);
root->insertItem(item4, Location_Top);
QCOMPARE(root->numChildren(), 2);
root->removeItem(item4);
QCOMPARE(root->numChildren(), 1);
auto c1 = item1->parentContainer();
QCOMPARE(c1->pos(), QPoint(0, 0));
QCOMPARE(c1->width(), root->width());
QCOMPARE(c1->height(), item1->height());
QCOMPARE(c1->height(), root->height());
const int item3and32Width = item3->width() + item32->width() + st;
root->removeItem(item32);
QCOMPARE(item3->width(), item3and32Width);
root->checkSanity();
root->removeItem(item31);
root->checkSanity();
QCOMPARE(item2->height(), item3->height());
QPointer<Item> c3 = item3->parentContainer();
root->removeItem(c3);
QVERIFY(c3.isNull());
}
void TestMultiSplitter::tst_removeItem2()
{
auto root = createRoot();
auto item1 = createItem("1");
auto item2 = createItem("2");
auto item3 = createItem("3");
auto item31 = createItem("3.1");
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
item2->insertItem(item3, Location_Right);
item3->insertItem(item31, Location_Bottom);
item31->parentContainer()->removeItem(item31);
item3->parentContainer()->removeItem(item3);
}
void TestMultiSplitter::tst_minSize()
{
auto root = createRoot();
auto item1 = createItem("1");
auto item2 = createItem("2");
auto item22 = createItem("2.2");
item1->m_sizingInfo.minSize = {101, 150};
item2->m_sizingInfo.minSize = {200, 300};
item22->m_sizingInfo.minSize = {100, 100};
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
item2->insertItem(item22, Location_Bottom);
QCOMPARE(item2->minSize(), QSize(200, 300));
QCOMPARE(item2->parentContainer()->minSize(), QSize(200, 300+100+st));
QCOMPARE(root->minSize(), QSize(101+200+st, 300 + 100 + st));
}
void TestMultiSplitter::tst_resize()
{
auto root = createRoot();
auto item1 = createItem("1");
auto item2 = createItem("2");
auto item3 = createItem("3");
auto item31 = createItem("31");
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
root->insertItem(item3, Location_Right);
const int item1Percentage = item1->width() / root->width();
const int item2Percentage = item1->width() / root->width();
const int item3Percentage = item1->width() / root->width();
// Now resize:
root->resize({2000, 505});
QVERIFY(item1Percentage - (1.0* item1->width() / root->width()) < 0.01);
QVERIFY(item2Percentage - (1.0* item2->width() / root->width()) < 0.01);
QVERIFY(item3Percentage - (1.0* item3->width() / root->width()) < 0.01);
QCOMPARE(root->width(), 2000);
QCOMPARE(root->height(), 505);
QCOMPARE(item1->height(), 505);
QCOMPARE(item2->height(), 505);
QCOMPARE(item3->height(), 505);
item3->insertItem(item31, Location_Bottom);
QVERIFY(root->checkSanity());
root->resize({2500, 505});
QVERIFY(root->checkSanity());
}
void TestMultiSplitter::tst_resizeWithConstraints()
{
{
// Test that resizing below minSize isn't permitted.
auto root = createRoot();
auto item1 = createItem("1");
item1->setMinSize(QSize(500, 500));
root->insertItem(item1, Location_Left);
QVERIFY(root->checkSanity());
root->resize(item1->minSize()); // Still fits
root->resize(item1->minSize() - QSize(1, 0)); // wouldn't fit
QCOMPARE(root->size(), item1->size()); // still has the old size
}
{
auto root = createRoot();
auto item1 = createItem("1");
auto item2 = createItem("2");
auto item3 = createItem("3");
root->resize(QSize(2000, 500));
item1->setMinSize(QSize(500, 500));
item2->setMinSize(QSize(500, 500));
item3->setMinSize(QSize(500, 500));
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
root->insertItem(item3, Location_Right);
QVERIFY(root->checkSanity());
// TODO: Resize further
}
}
void TestMultiSplitter::tst_availableSize()
{
auto root = createRoot();
QCOMPARE(root->availableSize(), QSize(1000, 1000));
QCOMPARE(root->minSize(), QSize(0, 0));
auto item1 = createItem("1");
auto item2 = createItem("2");
auto item3 = createItem("3");
item1->m_sizingInfo.minSize = {100, 100};
item2->m_sizingInfo.minSize = {100, 100};
item3->m_sizingInfo.minSize = {100, 100};
root->insertItem(item1, Location_Left);
QCOMPARE(root->availableSize(), QSize(900, 900));
QCOMPARE(root->minSize(), QSize(100, 100));
QCOMPARE(root->neighboursLengthFor(item1, Side1, Qt::Horizontal), 0);
QCOMPARE(root->neighboursLengthFor(item1, Side2, Qt::Horizontal), 0);
QCOMPARE(root->neighboursMinLengthFor(item1, Side1, Qt::Horizontal), 0);
QCOMPARE(root->neighboursMinLengthFor(item1, Side2, Qt::Horizontal), 0);
QCOMPARE(root->neighbourSeparatorWaste(item1, Side1, Qt::Vertical), 0);
QCOMPARE(root->neighbourSeparatorWaste(item1, Side2, Qt::Vertical), 0);
QCOMPARE(root->neighbourSeparatorWaste(item1, Side1, Qt::Horizontal), 0);
QCOMPARE(root->neighbourSeparatorWaste(item1, Side2, Qt::Horizontal), 0);
QCOMPARE(root->neighboursLengthFor_recursive(item1, Side1, Qt::Vertical), 0);
QCOMPARE(root->neighboursLengthFor_recursive(item1, Side2, Qt::Vertical), 0);
QCOMPARE(root->neighboursLengthFor_recursive(item1, Side1, Qt::Horizontal), 0);
QCOMPARE(root->neighboursLengthFor_recursive(item1, Side2, Qt::Horizontal), 0);
root->insertItem(item2, Location_Left);
QCOMPARE(root->availableSize(), QSize(800 - st, 900));
QCOMPARE(root->minSize(), QSize(200 + st, 100));
QCOMPARE(root->neighboursLengthFor(item1, Side1, Qt::Horizontal), item2->width());
QCOMPARE(root->neighboursLengthFor(item1, Side2, Qt::Horizontal), 0);
QCOMPARE(root->neighboursLengthFor(item2, Side1, Qt::Horizontal), 0);
QCOMPARE(root->neighboursLengthFor(item2, Side2, Qt::Horizontal), item1->width());
QCOMPARE(root->neighboursMinLengthFor(item1, Side1, Qt::Horizontal), item2->minSize().width());
QCOMPARE(root->neighboursMinLengthFor(item1, Side2, Qt::Horizontal), 0);
QCOMPARE(root->neighboursMinLengthFor(item2, Side1, Qt::Horizontal), 0);
QCOMPARE(root->neighboursMinLengthFor(item2, Side2, Qt::Horizontal), item1->minSize().width());
QCOMPARE(root->neighboursLengthFor_recursive(item1, Side1, Qt::Vertical), 0);
QCOMPARE(root->neighboursLengthFor_recursive(item1, Side2, Qt::Vertical), 0);
QCOMPARE(root->neighboursLengthFor_recursive(item1, Side1, Qt::Horizontal), item2->width());
QCOMPARE(root->neighboursLengthFor_recursive(item1, Side2, Qt::Horizontal), 0);
root->insertItem(item3, Location_Bottom);
QCOMPARE(root->availableSize(), QSize(800 - st, 800 - st));
QCOMPARE(root->minSize(), QSize(200 + st, 100 + 100 + st));
QCOMPARE(item3->parentContainer()->neighboursMinLengthFor(item3, Side1, Qt::Vertical), item1->minSize().height());
auto container2 = item2->parentContainer();
QCOMPARE(container2->neighboursLengthFor_recursive(item1, Side1, Qt::Vertical), 0);
QCOMPARE(container2->neighboursLengthFor_recursive(item1, Side2, Qt::Vertical), item3->height());
QCOMPARE(container2->neighboursLengthFor_recursive(item1, Side1, Qt::Horizontal), item2->width());
QCOMPARE(container2->neighboursLengthFor_recursive(item1, Side2, Qt::Horizontal), 0);
// More nesting
auto item4 = createItem("4");
auto item5 = createItem("5");
item3->insertItem(item4, Location_Right);
item4->insertItem(item5, Location_Bottom);
auto container4 = item4->parentContainer();
QCOMPARE(container4->neighboursLengthFor_recursive(item4, Side1, Qt::Vertical), item1->height());
QCOMPARE(container4->neighboursLengthFor_recursive(item4, Side2, Qt::Vertical), item5->height());
QCOMPARE(container4->neighboursLengthFor_recursive(item4, Side1, Qt::Horizontal), item3->width());
QCOMPARE(container4->neighboursLengthFor_recursive(item4, Side2, Qt::Horizontal), 0);
QCOMPARE(container4->neighboursLengthFor_recursive(item5, Side1, Qt::Vertical), item4->height() + item1->height());
QCOMPARE(container4->neighboursLengthFor_recursive(item5, Side2, Qt::Vertical), 0);
QCOMPARE(container4->neighboursLengthFor_recursive(item5, Side1, Qt::Horizontal), item3->width());
QCOMPARE(container4->neighboursLengthFor_recursive(item5, Side2, Qt::Horizontal), 0);
QCOMPARE(container4->neighbourSeparatorWaste(item4, Side1, Qt::Vertical), 0);
QCOMPARE(container4->neighbourSeparatorWaste(item4, Side2, Qt::Vertical), st);
QCOMPARE(container4->neighbourSeparatorWaste(item4, Side1, Qt::Horizontal), 0);
QCOMPARE(container4->neighbourSeparatorWaste(item4, Side2, Qt::Horizontal), 0);
QCOMPARE(container4->neighbourSeparatorWaste(item5, Side1, Qt::Vertical), st);
QCOMPARE(container4->neighbourSeparatorWaste(item5, Side2, Qt::Vertical), 0);
QCOMPARE(container4->neighbourSeparatorWaste(item5, Side1, Qt::Horizontal), 0);
QCOMPARE(container4->neighbourSeparatorWaste(item5, Side2, Qt::Horizontal), 0);
}
void TestMultiSplitter::tst_missingSize()
{
auto root = createRoot();
QCOMPARE(root->size(), QSize(1000, 1000));
QCOMPARE(root->availableSize(), QSize(1000, 1000));
Item *item1 = createItem("1");
item1->setMinSize({100, 100});
Item *item2 = createItem("2");
item2->setMinSize(root->size());
Item *item3 = createItem("3");
item3->setMinSize(root->size() + QSize(100, 200));
// Test empty root
QCOMPARE(root->missingSizeFor(item1, Qt::Vertical), QSize(0, 0));
QCOMPARE(root->missingSizeFor(item2, Qt::Vertical), QSize(0, 0));
QCOMPARE(root->missingSizeFor(item3, Qt::Vertical), QSize(100, 200));
// Test with an existing item
root->insertItem(item1, Location_Top);
QCOMPARE(root->missingSizeFor(item2, Qt::Vertical), item1->minSize() + QSize(0, st));
QCOMPARE(root->missingSizeFor(item3, Qt::Vertical), item1->minSize() + QSize(0, st) + QSize(100, 200));
}
void TestMultiSplitter::tst_ensureEnoughSize()
{
// Tests that the layout's size grows when the item being inserted wouldn't have enough space
auto root = createRoot(); /// 1000x1000
Item *item1 = createItem("1");
item1->setMinSize({2000, 500});
// Insert to empty layout:
root->insertItem(item1, Location_Left);
QCOMPARE(root->size(), QSize(2000, 1000));
QCOMPARE(item1->size(), QSize(2000, 1000));
QCOMPARE(item1->minSize(), root->minSize());
QVERIFY(root->checkSanity());
// Insert to non-empty layout
Item *item2 = createItem("2");
item2->setMinSize({2000, 2000});
root->insertItem(item2, Location_Right);
QVERIFY(root->checkSanity());
QCOMPARE(root->size(), QSize(item1->minSize().width() + item2->minSize().width() + st, item2->minSize().height()));
}
QTEST_MAIN(TestMultiSplitter)
#include "tst_multisplitter.moc"