WIP
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "KDDockWidgets.h"
|
||||
#include "QWidgetAdapter.h"
|
||||
#include "LayoutSaver_p.h"
|
||||
#include "multisplitter/Item_p.h"
|
||||
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "KDDockWidgets.h"
|
||||
#include "QWidgetAdapter.h"
|
||||
#include "LayoutSaver_p.h"
|
||||
#include "multisplitter/Item_p.h"
|
||||
|
||||
#include <QVector>
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "QWidgetAdapter.h"
|
||||
#include "Frame_p.h"
|
||||
#include "KDDockWidgets.h"
|
||||
#include "multisplitter/Item_p.h"
|
||||
|
||||
namespace KDDockWidgets {
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
15
src/private/multisplitter/CMakeLists.txt
Normal file
15
src/private/multisplitter/CMakeLists.txt
Normal 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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
5
src/private/multisplitter/tests/CMakeLists.txt
Normal file
5
src/private/multisplitter/tests/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
find_package(Qt5Test)
|
||||
|
||||
|
||||
add_executable(tst_multisplitter tst_multisplitter.cpp)
|
||||
target_link_libraries(tst_multisplitter kddockwidgets_layouting Qt5::Test)
|
||||
560
src/private/multisplitter/tests/tst_multisplitter.cpp
Normal file
560
src/private/multisplitter/tests/tst_multisplitter.cpp
Normal 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"
|
||||
Reference in New Issue
Block a user