WIP: Anchor.cpp now builds

This commit is contained in:
Sergio Martins
2020-04-01 15:19:22 +01:00
parent 1643f23612
commit 76eb54e86f
5 changed files with 71 additions and 1371 deletions

View File

@@ -38,18 +38,15 @@
using namespace KDDockWidgets;
bool Anchor::s_isResizing = false;
const QString Anchor::s_magicMarker = QStringLiteral("e520c60e-cf5d-4a30-b1a7-588d2c569851");
Anchor::Anchor(Qt::Orientation orientation, MultiSplitterLayout *multiSplitter, Type type)
Anchor::Anchor(Qt::Orientation orientation, MultiSplitterLayout *multiSplitter)
: QObject(multiSplitter->multiSplitter())
, m_orientation(orientation)
, m_type(type)
, m_layout(multiSplitter)
, m_separatorWidget(Config::self().frameworkWidgetFactory()->createSeparator(this, multiSplitter->multiSplitter()))
, m_lazyResize(Config::self().flags() & Config::Flag_LazyResize)
, m_lazyResizeRubberBand(m_lazyResize ? new QRubberBand(QRubberBand::Line, multiSplitter->multiSplitter()) : nullptr)
{
multiSplitter->insertAnchor(this);
connect(this, &QObject::objectNameChanged, m_separatorWidget, &QObject::setObjectName);
}
@@ -58,7 +55,6 @@ Anchor::~Anchor()
m_separatorWidget->setEnabled(false);
m_separatorWidget->deleteLater();
qCDebug(multisplittercreation) << "~Anchor; this=" << this << "; m_to=" << m_to << "; m_from=" << m_from;
m_layout->removeAnchor(this);
}
void Anchor::setFrom(Anchor *from)
@@ -122,47 +118,6 @@ void Anchor::setGeometry(QRect r)
}
}
void Anchor::updateItemSizes()
{
if (!m_initialized) {
// setPosition() hasn't been called yet, don't bother
return;
}
if (LayoutSaver::restoreInProgress()) {
// Nothing to do. The LayoutSaver is setting up the whole layout.
return;
}
qCDebug(anchors) << Q_FUNC_INFO << this << "; o=" << orientation();
int position = this->position() + m_positionOffset;
for (Item *item : qAsConst(m_side2Items)) {
QRect geo = item->geometry();
const QPoint topLeft = isVertical() ? QPoint(position + thickness(), item->y())
: QPoint(item->x(), position + thickness());
geo.setTopLeft(topLeft);
if (!item->isPlaceholder())
item->setGeometry(geo);
}
position = this->position() - m_positionOffset;
for (Item *item : qAsConst(m_side1Items)) {
QRect geo = item->geometry();
// -1 as the widget is right next to the anchor, and not on top
const QPoint bottomRight = isVertical() ? QPoint(position - 1, geo.bottom())
: QPoint(geo.right(), position - 1);
geo.setBottomRight(bottomRight);
if (!item->isPlaceholder()) {
item->setGeometry(geo);
}
}
}
void Anchor::debug_updateItemNames()
{
// I call this in the unit-tests, when running them on gammaray
@@ -194,23 +149,10 @@ Qt::Orientation Anchor::orientation() const
return m_orientation;
}
void Anchor::setPosition(int p, SetPositionOptions options)
void Anchor::setPosition(int)
{
}
void Anchor::updatePositionPercentage()
{
const int layoutLength = m_layout->length(m_orientation);
m_positionPercentage = (position() * 1.0) / layoutLength;
if (position() > layoutLength) {
// This warning makes the unit-tests fail if some invalid m_positionPercentage ever appears.
// Bug fixed now though.
qWarning() << Q_FUNC_INFO << "Weird position percentage" << m_positionPercentage
<< position() << layoutLength;
}
}
int Anchor::position() const
{
const QPoint topLeft = m_geometry.topLeft();
@@ -225,48 +167,6 @@ void Anchor::setVisible(bool v)
}
}
int Anchor::minPosition() const
{
const int smallestSqueeze = smallestAvailableItemSqueeze(Side1);
return position() - smallestSqueeze;
}
int Anchor::smallestAvailableItemSqueeze(Anchor::Side side) const
{
int smallest = 0;
bool firstElement = true;
for (Item *item : items(side)) {
const int length = item->length(m_orientation);
const int minLength = item->minLength(m_orientation);
const int availableSqueeze = length - minLength;
if (availableSqueeze < smallest || firstElement) {
smallest = availableSqueeze;
firstElement = false;
}
}
return smallest;
}
void Anchor::ensureBounded()
{
// TODO: Probably delete this unused method. It was used in the old days before discovering it
// was flawed: Separators being in between bounds doesn't imply that all min sizes are being
// Honoured. Use MultiSplitterLayout::ensureItemsMinSize() instead
if (!isStatic() && !isFollowing()) {
const QPair<int,int> bounds = m_layout->boundPositionsForAnchor(this);
if (position() < bounds.first) {
setPosition(bounds.first);
} else if (position() > bounds.second) {
setPosition(bounds.second);
}
}
for (Item *item : items(Side2)) {
item->anchorAtSide(Side2, orientation())->ensureBounded();
}
}
int Anchor::length() const
{
Q_ASSERT(m_to);
@@ -285,46 +185,7 @@ int Anchor::thickness() const
: m_separatorWidget->height();
}
bool Anchor::hasItems(Anchor::Side side) const
{
switch (side) {
case Side1:
return !m_side1Items.isEmpty();
case Side2:
return !m_side2Items.isEmpty();
default:
Q_ASSERT(false);
return false;
}
}
bool Anchor::onlyHasPlaceholderItems(Anchor::Side side) const
{
auto &items = side == Side1 ? m_side1Items
: m_side2Items;
for (Item *item : items) {
if (!item->isPlaceholder())
return false;
}
return true;
}
bool Anchor::hasNonPlaceholderItems(Anchor::Side side) const
{
auto &items = side == Side1 ? m_side1Items
: m_side2Items;
for (Item *item : items) {
if (!item->isPlaceholder())
return true;
}
return false;
}
bool Anchor::containsItem(const Item *item, Anchor::Side side) const
bool Anchor::containsItem(const Item *item, Side side) const
{
switch (side) {
case Side1:
@@ -337,15 +198,7 @@ bool Anchor::containsItem(const Item *item, Anchor::Side side) const
}
}
bool Anchor::isStaticOrFollowsStatic() const
{
if (isStatic())
return true;
return m_followee && m_followee->isStaticOrFollowsStatic();
}
const ItemList Anchor::items(Anchor::Side side) const
const ItemList Anchor::items(Side side) const
{
switch (side) {
case Side1:
@@ -358,296 +211,28 @@ const ItemList Anchor::items(Anchor::Side side) const
}
}
void Anchor::consume(Anchor *other)
{
QPointer<Anchor> otherp = other; // Just to check if it wasn't deleted meanwhile. Which doesn't happen, but we silence a clang-tidy warning this way.
consume(other, Side1);
if (otherp)
consume(other, Side2);
}
void Anchor::consume(Anchor *other, Side side)
{
auto items = other->items(side);
other->removeItems(side);
addItems(items, side);
if (other->isUnneeded()) {
// Before deleting an unneeded anchor, we must check if there's anchors following it, and make them follow us instead
Anchor::List anchorsFollowingOther = m_layout->anchorsFollowing(other);
for (Anchor *follower : anchorsFollowingOther) {
if (follower != this)
follower->setFollowee(this);
}
delete other;
}
}
void Anchor::swapItems(Anchor *other)
{
auto other1 = other->m_side1Items;
auto other2 = other->m_side2Items;
auto my1 = m_side1Items;
auto my2 = m_side2Items;
removeAllItems();
other->removeAllItems();
other->addItems(my1, Side1);
other->addItems(my2, Side2);
addItems(other1, Side1);
addItems(other2, Side1);
}
void Anchor::removeAllItems()
{
removeItems(Side1);
removeItems(Side2);
}
/** static */
Anchor *Anchor::createFrom(Anchor *other, Item *relativeTo)
{
Q_ASSERT(other);
auto anchor = new Anchor(other->orientation(), other->m_layout);
anchor->setFrom(other->m_from);
anchor->setTo(other->m_to);
if (relativeTo) {
if (other->containsItem(relativeTo, Side1)) {
other->removeItem(relativeTo);
anchor->addItem(relativeTo, Side1);
} else if (other->containsItem(relativeTo, Side2)) {
other->removeItem(relativeTo);
anchor->addItem(relativeTo, Side2);
} else {
Q_ASSERT(false);
}
} else {
auto other1 = other->m_side1Items;
auto other2 = other->m_side2Items;
other->removeAllItems();
anchor->addItems(other1, Side1);
anchor->addItems(other2, Side2);
}
return anchor;
}
void Anchor::setPositionOffset(int value)
{
if (value != m_positionOffset) {
m_positionOffset = value;
updateItemSizes();
}
}
bool Anchor::isBeingDragged() const
{
return m_layout->anchorBeingDragged() == this;
}
int Anchor::cumulativeMinLength(Anchor::Side side) const
{
if (isStatic() && isEmpty()) {
// There's no widget, but minimum is the space occupied by left+right anchors (or top+bottom).
const int staticAnchorThickness = Anchor::thickness(/*static=*/true);
if ((side == Side2 && (m_type & (Type_LeftStatic | Type_TopStatic))) ||
(side == Side1 && (m_type & (Type_RightStatic | Type_BottomStatic))))
return 2 * staticAnchorThickness;
}
const CumulativeMin result = cumulativeMinLength_recursive(side);
const int numNonStaticAnchors = result.numItems >= 2 ? result.numItems - 1
: 0;
int r = Anchor::thickness(isStatic()) + Anchor::thickness(true)
+ numNonStaticAnchors*Anchor::thickness(false)
+ result.minLength;
return r;
}
Anchor::CumulativeMin Anchor::cumulativeMinLength_recursive(Anchor::Side side) const
{
const auto items = this->items(side);
CumulativeMin result = { 0, 0 };
for (auto item : items) {
Anchor *oppositeAnchor = item->anchorAtSide(side, orientation());
if (!oppositeAnchor) {
// Shouldn't happen. But don't assert as this might be being called from a dumpDebug()
qWarning() << Q_FUNC_INFO << "Null opposite anchor";
return {0, 0};
}
CumulativeMin candidateMin = { 0, 0 };
if (!item->isPlaceholder()) {
candidateMin.numItems++;
candidateMin.minLength = item->minLength(orientation());
}
candidateMin += oppositeAnchor->cumulativeMinLength_recursive(side);
if (candidateMin.minLength >= result.minLength) {
result = candidateMin;
}
}
return result;
}
void Anchor::setFollowee(Anchor *followee)
{
Q_ASSERT(this != followee);
if (m_followee == followee)
return;
qCDebug(placeholder) << Q_FUNC_INFO << "follower="
<< this << "; followee=" << followee;
if (m_followee) {
disconnect(m_followee, &Anchor::positionChanged, this, &Anchor::onFolloweePositionChanged);
disconnect(m_followee, &Anchor::thicknessChanged, this, &Anchor::setThickness);
disconnect(m_followeeDestroyedConnection);
}
m_followee = followee;
setThickness();
if (m_followee) {
Q_ASSERT(orientation() == m_followee->orientation());
setVisible(false);
setPosition(m_followee->position());
connect(m_followee, &Anchor::positionChanged, this, &Anchor::onFolloweePositionChanged);
connect(m_followee, &Anchor::thicknessChanged, this, &Anchor::setThickness);
m_followeeDestroyedConnection = connect(m_followee, &QObject::destroyed, this, [this] {
setFollowee(nullptr);
});
} else {
setVisible(true);
}
Q_EMIT followeeChanged();
}
const Anchor::List Anchor::followers() const
{
Anchor::List result;
for (Anchor *a : m_layout->anchors()) {
if (a->followee() == this)
result.push_back(a);
}
return result;
}
Anchor *Anchor::endFollowee() const
{
Anchor *a = m_followee;
while (a) {
if (!a->followee())
return a;
a = a->followee();
}
return nullptr;
}
bool Anchor::findAnchor(Anchor *anchor, Anchor::Side side) const
{
if (!anchor)
return false;
Q_ASSERT(anchor != this);
Q_ASSERT(anchor->orientation() == orientation());
for (Item *item : items(side)) {
Anchor *a = item->anchorAtSide(side, orientation());
if (anchor == a)
return true;
if (a->findAnchor(anchor, side))
return true;
}
return false;
}
Anchor *Anchor::findNearestAnchorWithItems(Anchor::Side side) const
{
Anchor *candidate = nullptr;
for (Item *item : items(side)) {
Anchor *a = item->anchorAtSide(side, orientation());
if (!a->hasNonPlaceholderItems(side))
a = a->findNearestAnchorWithItems(side);
if (!candidate || (side == Side1 && a->position() > candidate->position()) || (side == Side2 && a->position() < candidate->position()) ) {
candidate = a;
}
}
if (!candidate)
candidate = m_layout->staticAnchor(side, orientation());
Q_ASSERT(candidate->isStatic() || candidate->hasNonPlaceholderItems(side));
return candidate;
}
void Anchor::clear()
{
m_side1Items.clear();
m_side2Items.clear();
}
void Anchor::onFolloweePositionChanged(int pos)
{
Q_ASSERT(isFollowing());
setPosition(pos);
}
int Anchor::thickness(bool staticAnchor)
{
return Config::self().separatorThickness(staticAnchor);
}
void Anchor::setLayout(MultiSplitterLayout *layout)
{
m_layout->removeAnchor(this);
m_layout = layout;
setParent(layout->multiSplitter());
m_separatorWidget->setParent(layout->multiSplitter());
m_layout->insertAnchor(this);
m_layout->setAnchorBeingDragged(nullptr);
}
Separator *Anchor::separatorWidget() const
{
return m_separatorWidget;
}
void Anchor::setThickness()
{
const int value = isFollowing() ? m_followee->thickness()
: thickness(isStatic());
const int oldValue = thickness();
if (value != oldValue) {
if (isVertical()) {
m_separatorWidget->setFixedWidth(value);
m_geometry.setWidth(value);
} else {
m_separatorWidget->setFixedHeight(value);
m_geometry.setHeight(value);
}
Q_EMIT thicknessChanged();
}
}
void Anchor::setLazyPosition(int pos)
{
if (m_lazyPosition != pos) {
@@ -669,57 +254,6 @@ int Anchor::position(QPoint p) const
return isVertical() ? p.x() : p.y();
}
void Anchor::addItem(Item *item, Anchor::Side side)
{
Q_ASSERT(side != Side_None);
auto &items = (side == Side1) ? m_side1Items : m_side2Items;
if (!items.contains(item)) {
items << item;
item->anchorGroup().setAnchor(this, orientation(), side);
Q_EMIT itemsChanged(side);
updateItemSizes();
}
}
void Anchor::addItems(const ItemList &list, Side side)
{
for (Item *item : list)
addItem(item, side);
}
void Anchor::removeItem(Item *item)
{
if (m_side1Items.removeOne(item)) {
item->anchorGroup().setAnchor(nullptr, orientation(), Side1);
Q_EMIT itemsChanged(Side1);
} else {
if (m_side2Items.removeOne(item)) {
item->anchorGroup().setAnchor(nullptr, orientation(), Side2);
Q_EMIT itemsChanged(Side2);
}
}
}
void Anchor::removeItems(Side side)
{
const auto &items = this->items(side);
for (Item *item : items)
removeItem(item);
}
Anchor::Side Anchor::oppositeSide(Side side)
{
switch (side) {
case Side1:
return Side2;
case Side2:
return Side1;
default:
Q_ASSERT(false);
return Side_None;
}
}
void Anchor::onMousePress()
{
s_isResizing = true;
@@ -743,9 +277,9 @@ void Anchor::onMouseReleased()
m_layout->setAnchorBeingDragged(nullptr);
}
void Anchor::onMouseMoved(QPoint pt)
void Anchor::onMouseMoved(QPoint)
{
if (!isBeingDragged() || isStatic())
if (!isBeingDragged())
return;
if (!(qApp->mouseButtons() & Qt::LeftButton)) {
@@ -764,8 +298,8 @@ void Anchor::onMouseMoved(QPoint pt)
}
#endif
const int positionToGoTo = position(pt);
auto bounds = m_layout->boundPositionsForAnchor(this);
// const int positionToGoTo = position(pt);
/*auto bounds = m_layout->boundPositionsForAnchor(this);
if (positionToGoTo < bounds.first || positionToGoTo > bounds.second) {
// qDebug() << "Out of bounds" << bounds.first << bounds.second << positionToGoTo << "; currentPos" << position() << "; window size" << window()->size();
@@ -779,7 +313,7 @@ void Anchor::onMouseMoved(QPoint pt)
if (m_lazyResize)
setLazyPosition(positionToGoTo);
else
setPosition(positionToGoTo);
setPosition(positionToGoTo);*/
}
void Anchor::onWidgetMoved(int p)
@@ -795,62 +329,3 @@ bool Anchor::isResizing()
{
return s_isResizing;
}
Anchor *Anchor::deserialize(const LayoutSaver::Anchor &a, MultiSplitterLayout *layout)
{
auto anchor = new Anchor(Qt::Orientation(a.orientation), layout, Anchor::Type(a.type));
anchor->setObjectName(a.objectName);
anchor->setGeometry(a.geometry);
anchor->m_positionPercentage = a.positionPercentage;
anchor->setProperty("indexFrom", a.indexOfFrom);
anchor->setProperty("indexTo", a.indexOfTo);
anchor->setProperty("indexFolowee", a.indexOfFollowee);
ItemList side1Items;
ItemList side2Items;
const ItemList allItems = layout->items();
side1Items.reserve(a.side1Items.size());
for (int index : qAsConst(a.side1Items)) {
side1Items.push_back(allItems.at(index));
}
side2Items.reserve(a.side2Items.size());
for (int index : qAsConst(a.side2Items)) {
side2Items.push_back(allItems.at(index));
}
anchor->m_side1Items = side1Items;
anchor->m_side2Items = side2Items;
anchor->m_initialized = true;
return anchor;
}
LayoutSaver::Anchor Anchor::serialize() const
{
LayoutSaver::Anchor a;
const Anchor::List allAnchors = m_layout->anchors();
const ItemList allItems = m_layout->items();
a.objectName = objectName();
a.type = type();
a.geometry = geometry();
a.orientation = orientation();
a.indexOfFrom = allAnchors.indexOf(from());
a.indexOfTo = allAnchors.indexOf(to());
a.indexOfFollowee = followee() ? allAnchors.indexOf(followee()) : -1;
a.positionPercentage = m_positionPercentage;
a.side1Items.clear();
a.side1Items.reserve(this->side1Items().size());
for (Item *item : this->side1Items())
a.side1Items.push_back(allItems.indexOf(item));
a.side2Items.clear();
a.side2Items.reserve(this->side2Items().size());
for (Item *item : this->side2Items())
a.side2Items.push_back(allItems.indexOf(item));
return a;
}

View File

@@ -23,6 +23,7 @@
#include "docks_export.h"
#include "LayoutSaver_p.h"
#include "Item_p.h"
#include <QObject>
#include <QPointer>
@@ -96,40 +97,11 @@ class DOCKS_EXPORT_FOR_UNIT_TESTS Anchor : public QObject // clazy:exclude=ctor-
Q_PROPERTY(Anchor* to READ to WRITE setTo NOTIFY toChanged)
Q_PROPERTY(int position READ position WRITE setPosition NOTIFY positionChanged)
Q_PROPERTY(Qt::Orientation orientation READ orientation CONSTANT)
Q_PROPERTY(Anchor *followee READ followee NOTIFY followeeChanged)
public:
///@brief represents the Anchor type
///An anchor can be of 2 types:
/// - Normal: Anchor that can be resized via mouse
/// - static: this is the top, left, right, bottom borders of the main window. They are called static because they don't move.
enum Type {
Type_None = 0, ///< The anchor is normal, and can be resized.
Type_LeftStatic = 1, ///< The anchor is static and represents the left mainwindow margin
Type_RightStatic = 2, ///< The anchor is static and represents the right mainwindow margin
Type_TopStatic = 4, ///< The anchor is static and represents the top mainwindow margin
Type_BottomStatic = 8, ///< The anchor is static and represents the bottom mainwindow margin
Type_Static = Type_TopStatic | Type_LeftStatic | Type_RightStatic | Type_BottomStatic ///< The anchor is static, one of the 4 previous ones
};
Q_ENUM(Type)
enum Side {
Side_None = 0,
Side1,
Side2
};
Q_ENUM(Side)
enum SetPositionOption {
SetPositionOption_None = 0,
SetPositionOption_DontRecalculatePercentage = 1
};
Q_DECLARE_FLAGS(SetPositionOptions, SetPositionOption)
typedef QVector<Anchor *> List;
explicit Anchor(Qt::Orientation orientation, MultiSplitterLayout *multiSplitter, Type = Type_None);
explicit Anchor(Qt::Orientation orientation, MultiSplitterLayout *multiSplitter);
~Anchor() override;
static Anchor* deserialize(const LayoutSaver::Anchor &, MultiSplitterLayout *layout);
LayoutSaver::Anchor serialize() const;
void setFrom(Anchor *);
Anchor *from() const { return m_from; }
@@ -141,14 +113,11 @@ public:
void removeItem(Item *w);
void removeItems(Side);
bool isVertical() const { return m_orientation == Qt::Vertical; }
void setPosition(int p, SetPositionOptions = SetPositionOption_None);
void setPosition(int);
void updatePositionPercentage();
int position() const;
void setVisible(bool);
qreal positionPercentage() const { return m_positionPercentage; }
void ensureBounded();
/**
* @brief Sets the new layout. Called when we're dropping a source layout into a target one.
@@ -159,21 +128,6 @@ public:
///@brief returns the separator widget
Separator* separatorWidget() const;
/**
* Returns how far left or top an anchor can go and still respecting it's Side1 widgets min-size.
* This function doesn't count with shifting other anchors, for that use MultiSplitterLayout::boundPositionsForAnchor()
* which is is recursive and returns the bounds after simulating that intermediary anchors to the left/top were
* also resized (each still respecting widgets min sizes though).
*/
int minPosition() const;
/**
* A squeeze is a widget's width (or height for horizontal anchors) minus its minimum width.
* This function iterates through all widgets of the specified side and returns the minimum
* available squeeze.
*/
int smallestAvailableItemSqueeze(Anchor::Side) const;
/**
* @brief The length of this anchor. The distance between @ref from and @ref to.
* @return the anchor's length
@@ -191,122 +145,39 @@ public:
*/
int thickness() const;
/**
* @brief Checks if this Anchor is static.
* @return true if this Anchor is static.
*/
bool isStatic() const { return m_type & Type_Static; }
bool isUnneeded() const { return !isStatic() && (!hasItems(Side1) || !hasItems(Side2)); }
bool isEmpty() const { return !hasItems(Side1) && !hasItems(Side2); }
bool hasItems(Side) const;
bool hasNonPlaceholderItems(Side) const;
bool onlyHasPlaceholderItems(Anchor::Side side) const;
/**
* @brief Returns whether this Anchor should follow another one. That happens if one of it's side is empty or only has placeholders
* Also, it can't be a static anchor.
*/
bool shouldFollow() const { return !isStatic() && (onlyHasPlaceholderItems(Side1) || onlyHasPlaceholderItems(Side2)); }
bool containsItem(const Item *w, Side side) const;
bool isStaticOrFollowsStatic() const;
const ItemList items(Side side) const;
const ItemList side1Items() const { return m_side1Items; }
const ItemList side2Items() const { return m_side2Items; }
void consume(Anchor *other);
void consume(Anchor *other, Side);
void swapItems(Anchor *other);
void removeAllItems();
static Anchor *createFrom(Anchor *other, Item *relativeTo = nullptr);
void setPositionOffset(int);
bool isBeingDragged() const;
Type type() const { return m_type; }
int cumulativeMinLength(Anchor::Side side) const;
/**
* @brief Makes this separator follow another one. This one will be made invisible.
* Used when the item in the layout is just a placeholder remembering a previous dock widget position.
* Pass nullptr do make it not follow and visible again.
*/
void setFollowee(Anchor *);
/**
* @brief getter for the followee
*/
Anchor *followee() const { return m_followee; }
/**
* @brief Returns the list of anchors following this one.
*/
const List followers() const;
/**
* @brief Returns the last followee in the chain.
*/
Anchor *endFollowee() const;
/**
* @brief Recursively looks for an anchor in the whole layout but only looking at side @p side
*
* This allows us to know if there's an anchor on the top or left of us (side1) or right or bottom
* (side2), in the whole layout.
*
* Returns false if @p anchor is nullptr
*/
bool findAnchor(Anchor *anchor, Side side) const;
/**
* @brief Returns the nearest Anchor with non-placeholder items on side @p side
* If nothing is found then returns the static anchor on that side
*/
Anchor *findNearestAnchorWithItems(Side side) const;
///@brief removes the side1 and side2 items. Doesn't delete them
void clear();
static int thickness(bool staticAnchor);
static Anchor::Side oppositeSide(Side side);
void onFolloweePositionChanged(int pos);
bool isFollowing() const { return m_followee != nullptr; }
void onMousePress();
void onMouseReleased();
void onMouseMoved(QPoint pt);
void onWidgetMoved(int p);
///@brief Returns whether we're dragging a separator. Can be useful for the app to stop other work while we're not in the final size
static bool isResizing();
private:
struct CumulativeMin {
int minLength;
int numItems;
CumulativeMin& operator+=(CumulativeMin other) {
minLength += other.minLength;
numItems += other.numItems;
return *this;
}
};
CumulativeMin cumulativeMinLength_recursive(Anchor::Side side) const;
void setThickness();
void setLazyPosition(int);
Q_SIGNALS:
void positionChanged(int pos);
void itemsChanged(Anchor::Side);
void itemsChanged(Side);
void fromChanged();
void toChanged();
void debug_itemNamesChanged();
void followeeChanged();
void thicknessChanged();
public:
int position(QPoint) const;
@@ -323,28 +194,20 @@ public:
ItemList m_side2Items;
QPointer<Anchor> m_from;// QPointer just so we can assert. They should never be null.
QPointer<Anchor> m_to;
const Type m_type;
qreal m_positionPercentage = 0.0; // Should be between 0 and 1
// Only set when anchor is moved through mouse. Side1 if going towards left or top, Side2 otherwise.
Side m_lastMoveDirection = Side_None;
Side m_lastMoveDirection = Side1;
MultiSplitterLayout *m_layout = nullptr;
bool m_showingSide1Rubberband = false;
bool m_showingSide2Rubberband = false;
bool m_initialized = false;
static bool s_isResizing;
static const QString s_magicMarker; // Just to validate serialize is symmetric to deserialize
// For when being animated. They are not displayed at their pos, but with an offset.
int m_positionOffset = 0;
QString m_debug_side1ItemNames;
QString m_debug_side2ItemNames;
Separator *const m_separatorWidget;
QRect m_geometry;
Anchor *m_followee = nullptr;
QMetaObject::Connection m_followeeDestroyedConnection;
const bool m_lazyResize;
int m_lazyPosition = 0;
QRubberBand *const m_lazyResizeRubberBand;

View File

@@ -538,276 +538,13 @@ int MultiSplitterLayout::placeholderCount() const
return count() - visibleCount();
}
void MultiSplitterLayout::removeAnchor(Anchor *anchor)
{
if (!m_inDestructor)
m_anchors.removeOne(anchor);
}
QPair<int, int> MultiSplitterLayout::boundPositionsForAnchor(Anchor *anchor) const
{
if (anchor->isStatic()) {
if (anchor == m_leftAnchor || anchor == m_topAnchor) {
return {0, 0};
} else if (anchor == m_rightAnchor || anchor == m_bottomAnchor) {
const int max = length(anchor->orientation()) - Anchor::thickness(true);
return {max, max};
}
}
if (anchor->isFollowing())
anchor = anchor->endFollowee();
const int minSide1Length = anchor->cumulativeMinLength(Anchor::Side1);
const int minSide2Length = anchor->cumulativeMinLength(Anchor::Side2);
const int length = anchor->isVertical() ? width() : height();
const int bound1 = qMax(0, minSide1Length - anchor->thickness());
const int bound2 = qMax(0, length - minSide2Length);
if (bound2 < bound1) {
qWarning() << Q_FUNC_INFO << "Invalid bounds"
<< "; bound1=" << bound1
<< "; bound2=" << bound2
<< "; layout.size=" << size()
<< "; layout.min=" << minimumSize()
<< "; anchor=" << anchor
<< "; orientation=" << anchor->orientation()
<< "; minSide1Length=" << minSide1Length
<< "; minSide2Length=" << minSide2Length
<< "; side1=" << anchor->side1Items()
<< "; side2=" << anchor->side2Items()
<< "; followee=" << anchor->followee()
<< "; thickness=" << anchor->thickness();
}
return { bound1, bound2 };
}
QHash<Anchor *, QPair<int, int> > MultiSplitterLayout::boundPositionsForAllAnchors() const
{
QHash<Anchor *, QPair<int, int> > result;
for (Anchor *anchor : m_anchors)
result.insert(anchor, boundPositionsForAnchor(anchor));
return result;
}
int MultiSplitterLayout::boundPositionForAnchor(Anchor *anchor, Anchor::Side direction) const
{
auto bounds = boundPositionsForAnchor(anchor);
return direction == Anchor::Side1 ? bounds.first
: bounds.second;
}
MultiSplitterLayout::Length MultiSplitterLayout::availableLengthForDrop(Location location, const Item *relativeTo) const
{
Length result;
const bool relativeToThis = relativeTo == nullptr;
AnchorGroup anchors = relativeToThis ? staticAnchorGroup()
: relativeTo->anchorGroup();
Anchor *anchor = nullptr;
int thisLength = 0;
switch (location) {
case KDDockWidgets::Location_None:
qWarning() << "MultiSplitterLayout::availableLengthForDrop invalid location for dropping";
return result;
case KDDockWidgets::Location_OnLeft:
anchor = anchors.left;
thisLength = width();
break;
case KDDockWidgets::Location_OnTop:
anchor = anchors.top;
thisLength = height();
break;
case KDDockWidgets::Location_OnRight:
anchor = anchors.right;
thisLength = width();
break;
case KDDockWidgets::Location_OnBottom:
anchor = anchors.bottom;
thisLength = height();
break;
}
anchor = anchor->isFollowing() ? anchor->endFollowee() : anchor;
const int minForAlreadyOccupied1 = anchor->cumulativeMinLength(Anchor::Side1) - anchor->thickness(); // TODO: Check if this is correct, we're discounting the anchor twice
const int minForAlreadyOccupied2 = anchor->cumulativeMinLength(Anchor::Side2) - anchor->thickness();
const int side1AvailableLength = anchor->position() - minForAlreadyOccupied1;
const int side2AvailableLength = thisLength - (anchor->position() + anchor->thickness()) - minForAlreadyOccupied2;
const bool needsNewAnchor = hasVisibleItems(); // If a new anchor is needed then we need space for the drag handle and such.
const int newAnchorThickness = needsNewAnchor ? Anchor::thickness(/*static=*/false) : 0;
// This useless space doesn't belong to side1 or side2 specifically. So account for it separately.
const int unusableSpace = newAnchorThickness;
const int usableLength = qMax(0, side1AvailableLength + side2AvailableLength - unusableSpace);
if (usableLength > 0) {
qreal factor = (side1AvailableLength * 1.0) / (side1AvailableLength + side2AvailableLength);
result.side1Length = int(qRound(usableLength * factor)); // rounding not really needed, but makes things more fair probably
result.side2Length = usableLength - result.side1Length;
}
qCDebug(sizing) << Q_FUNC_INFO
<< "; available=" << result.length() << result.side1Length << result.side2Length
<< "; side1AvailableLength=" << side1AvailableLength
<< "; side2AvailableLength=" << side2AvailableLength
<< "; minForAlreadyOccupied1=" << minForAlreadyOccupied1
<< "; minForAlreadyOccupied2=" << minForAlreadyOccupied2
<< "; thisLength=" << thisLength
<< "; anchorPos=" << anchor->position()
<< "; unusableSpace=" << unusableSpace;
return result;
}
int MultiSplitterLayout::availableLengthForOrientation(Qt::Orientation orientation) const
{
Length l = availableLengthForDrop(orientation == Qt::Vertical ? Location_OnLeft
: Location_OnTop, nullptr);
return l.length();
}
QSize MultiSplitterLayout::availableSize() const
{
return { availableLengthForOrientation(Qt::Vertical), availableLengthForOrientation(Qt::Horizontal) };
}
/*
* Returns the width or height the widget will get when dropped.
*/
MultiSplitterLayout::Length MultiSplitterLayout::lengthForDrop(const QWidgetOrQuick *widget, Location location,
const Item *relativeTo) const
{
Q_ASSERT(location != Location_None);
const Qt::Orientation anchorOrientation = anchorOrientationForLocation(location);
const int widgetCurrentLength = widgetLength(widget, anchorOrientation);
Length available = availableLengthForDrop(location, relativeTo);
const int requiredAtLeast = widgetMinLength(widget, anchorOrientation);
if (available.length() < requiredAtLeast) {
qCDebug(sizing) << Q_FUNC_INFO
<< "\n Not enough space. available=" << available.length()
<< "; required=" << requiredAtLeast
<< "; m_size=" << m_size;
return {};
}
const int suggestedLength = qMin(widgetCurrentLength, int(0.4 * length(anchorOrientation)));
available.setLength(qBound(requiredAtLeast, suggestedLength, available.length()));
qCDebug(sizing) << "MultiSplitterLayout::lengthForDrop length=" << available.length()
<< "; s1=" << available.side1Length << "; s2="<< available.side2Length
<< "; relativeTo=" << relativeTo
<< "; relativeTo.geo=" << (relativeTo ? relativeTo->geometry() : QRect())
<< "; widgetCurrentLength=" << widgetCurrentLength;
return available;
}
QRect MultiSplitterLayout::rectForDrop(MultiSplitterLayout::Length lfd, Location location, QRect relativeToRect) const
{
QRect result;
const int widgetLength = lfd.length();
const int newAnchorThickness = isEmpty() ? 0 : Anchor::thickness(/*static=*/false);
const int side1Length = lfd.side1Length;
const int staticAnchorThickness = Anchor::thickness(/**static=*/true);
switch (location) {
case Location_OnLeft:
result = QRect(qMax(0, relativeToRect.x() - side1Length), relativeToRect.y(),
widgetLength, relativeToRect.height());
break;
case Location_OnTop:
result = QRect(relativeToRect.x(), qMax(0, relativeToRect.y() - side1Length),
relativeToRect.width(), widgetLength);
break;
case Location_OnRight:
result = QRect(qMin(relativeToRect.right() + 1 - side1Length + newAnchorThickness,
width() - widgetLength - staticAnchorThickness), relativeToRect.y(), widgetLength, relativeToRect.height());
break;
case Location_OnBottom:
result = QRect(relativeToRect.x(), qMin(relativeToRect.bottom() + 1 - side1Length + newAnchorThickness,
height() - widgetLength - staticAnchorThickness),
relativeToRect.width(), widgetLength);
break;
default:
break;
}
qCDebug(sizing) << "MultiSplitterLayout::rectForDrop rect=" << result
<< "; result.bottomRight=" << result.bottomRight()
<< "; location=" << location
<< "; s1=" << side1Length
<< "; relativeToRect.bottomRight=" << relativeToRect.bottomRight();
return result;
}
QRect MultiSplitterLayout::rectForDrop(const QWidgetOrQuick *widgetBeingDropped, Location location,
const Item *relativeTo) const
{
Q_ASSERT(widgetBeingDropped);
Length lfd = lengthForDrop(widgetBeingDropped, location, relativeTo);
const bool needsMoreSpace = lfd.isNull();
if (needsMoreSpace) {
// This is the case with the drop indicators. If there's not enough space let's still
// draw some indicator drop. The window will resize to accommodate the drop.
lfd.side1Length = INDICATOR_MINIMUM_LENGTH / 2;
lfd.side2Length = INDICATOR_MINIMUM_LENGTH - lfd.side1Length;
}
const int staticAnchorThickness = Anchor::thickness(/**static=*/true);
const bool relativeToThis = relativeTo == nullptr;
const QRect relativeToRect = relativeToThis ? m_multiSplitter->rect().adjusted(staticAnchorThickness, staticAnchorThickness,
-staticAnchorThickness, -staticAnchorThickness)
: relativeTo->geometry();
// This function is split in two just so we can unit-test the math in the second one, which is more involved
QRect result = rectForDrop(lfd, location, relativeToRect);
return result;
}
void MultiSplitterLayout::setAnchorBeingDragged(Anchor *anchor)
{
m_anchorBeingDragged = anchor;
}
Anchor::List MultiSplitterLayout::anchorsFollowing(Anchor *followee) const
{
if (!followee)
return {};
Anchor::List followers;
for (Anchor *a : m_anchors) {
if (a->followee() == followee)
followers.push_back(a);
}
return followers;
}
int MultiSplitterLayout::numAchorsFollowing() const
{
int count = 0;
for (Anchor *a : m_anchors) {
if (a->isFollowing())
count++;
}
return count;
}
int MultiSplitterLayout::numVisibleAnchors() const
{
int count = 0;
@@ -819,193 +556,15 @@ int MultiSplitterLayout::numVisibleAnchors() const
return count;
}
Anchor *MultiSplitterLayout::staticAnchor(Anchor::Type type) const
{
if (type == Anchor::Type_TopStatic)
return m_topAnchor;
if (type == Anchor::Type_BottomStatic)
return m_bottomAnchor;
if (type == Anchor::Type_LeftStatic)
return m_leftAnchor;
if (type == Anchor::Type_RightStatic)
return m_rightAnchor;
return nullptr;
}
void MultiSplitterLayout::dumpDebug() const
{
Q_EMIT aboutToDumpDebug();
qDebug() << Q_FUNC_INFO << "m_size=" << m_size
<< "; minimumSize=" << minimumSize()
<< "; parentWidget.size=" << multiSplitter()->size()
<< "; window=" << multiSplitter()->window()
<< "; window.size=" << multiSplitter()->window()->size();
qDebug() << "Items:";
for (auto item : items()) {
qDebug() <<" " << item
<< "; min.width=" << item->minLength(Qt::Vertical)
<< "; min.height=" << item->minLength(Qt::Horizontal)
<< "; geometry=" << item->geometry()
<< "; isPlaceholder=" << item->isPlaceholder()
<< "; refCount=" << item->refCount();
if (Frame *frame = item->frame())
frame->dumpDebug();
}
qDebug() << "Anchors:";
for (Anchor *anchor : m_anchors) {
auto side1Widgets = anchor->items(Anchor::Side1);
auto side2Widgets = anchor->items(Anchor::Side2);
auto bounds = anchor->isStatic() ? QPair<int, int>() : boundPositionsForAnchor(anchor);
qDebug() << "\n " << anchor
<< "; side1=" << side1Widgets
<< "; side2=" << side2Widgets
<< "; pos=" << anchor->position()
<< "; sepWidget.pos=" << (anchor->isVertical() ? anchor->separatorWidget()->x()
: anchor->separatorWidget()->y())
<< "; sepWidget.visible=" << anchor->separatorWidget()->isVisible()
<< "; geo=" << anchor->geometry()
<< "; sep.geo=" << anchor->separatorWidget()->geometry()
<< "; bounds=" << bounds
<< "; orientation=" << anchor->orientation()
<< "; isFollowing=" << anchor->isFollowing()
<< "; followee=" << anchor->followee()
<< "; from=" << ((void*)anchor->from())
<< "; to=" << ((void*)anchor->to())
<< "; positionPercentage=" << anchor->positionPercentage();
}
qDebug() << "Num Frame:" << Frame::dbg_numFrames();
qDebug() << "Num FloatingWindow:" << FloatingWindow::dbg_numFrames();
}
void MultiSplitterLayout::positionStaticAnchors()
{
qCDebug(sizing) << Q_FUNC_INFO;
m_leftAnchor->setPosition(0);
m_topAnchor->setPosition(0);
m_bottomAnchor->setPosition(height() - m_bottomAnchor->thickness());
m_rightAnchor->setPosition(width() - m_rightAnchor->thickness());
}
void MultiSplitterLayout::redistributeSpace()
{
positionStaticAnchors();
redistributeSpace_recursive(m_leftAnchor, 0);
redistributeSpace_recursive(m_topAnchor, 0);
}
void MultiSplitterLayout::redistributeSpace(QSize oldSize, QSize newSize)
{
positionStaticAnchors();
if (oldSize == newSize || !oldSize.isValid() || !newSize.isValid())
return;
qCDebug(sizing) << Q_FUNC_INFO << "old=" << oldSize << "; new=" << newSize;
const bool widthChanged = oldSize.width() != newSize.width();
const bool heightChanged = oldSize.height() != newSize.height();
if (widthChanged)
redistributeSpace_recursive(m_leftAnchor, 0);
if (heightChanged)
redistributeSpace_recursive(m_topAnchor, 0);
}
void MultiSplitterLayout::redistributeSpace_recursive(Anchor *fromAnchor, int minAnchorPos)
{
for (Item *item : fromAnchor->items(Anchor::Side2)) {
Anchor *nextAnchor = item->anchorAtSide(Anchor::Side2, fromAnchor->orientation());
if (nextAnchor->isStatic())
continue;
// We use the minPos of the Anchor that had non-placeholder items on its side1.
if (nextAnchor->hasNonPlaceholderItems(Anchor::Side1))
minAnchorPos = nextAnchor->minPosition();
if (nextAnchor->hasNonPlaceholderItems(Anchor::Side2) && !nextAnchor->isFollowing()) {
const int newPosition = int(nextAnchor->positionPercentage() * length(nextAnchor->orientation()));
// But don't let the anchor go out of bounds, it must respect its widgets min sizes
auto bounds = boundPositionsForAnchor(nextAnchor);
// For the bounding, use Anchor::minPosition, as we're not making the anchors on the left/top shift, which boundsPositionsForAnchor() assumes.
const int newPositionBounded = qMax(bounds.first, qBound(minAnchorPos, newPosition, bounds.second));
qCDebug(sizing) << Q_FUNC_INFO << nextAnchor << "bounds.first=" << bounds.first
<< "; newPosition=" << newPosition
<< "; bounds.first=" << bounds.first
<< "; bounds.second=" << bounds.second
<< "; newPositionBounded=" << newPositionBounded
<< "; oldPosition=" << nextAnchor->position()
<< "; size=" << m_size
<< "; nextAnchor.minPosition=" << minAnchorPos;
nextAnchor->setPosition(newPositionBounded, Anchor::SetPositionOption_DontRecalculatePercentage);
}
redistributeSpace_recursive(nextAnchor, minAnchorPos);
}
}
void MultiSplitterLayout::updateSizeConstraints()
{
const int minH = m_topAnchor->cumulativeMinLength(Anchor::Side2);
const int minW = m_leftAnchor->cumulativeMinLength(Anchor::Side2);
const QSize newMinSize = QSize(minW, minH);
const QSize newMinSize = m_rootItem->minSize();
qCDebug(sizing) << Q_FUNC_INFO << "Updating size constraints from" << m_minSize
<< "to" << newMinSize;
setMinimumSize(newMinSize);
}
int MultiSplitterLayout::wastedSpacing(Qt::Orientation orientation) const
{
// Wasted spacing due to using splitters:
int numAnchors = 0;
for (Anchor *anchor : m_anchors) {
if (anchor->orientation() == orientation)
numAnchors++;
}
return (2 * Anchor::thickness(/*static=*/ true)) +
((numAnchors - 2) * Anchor::thickness(/*static=*/ false)); // 2 of the anchors are always static
}
AnchorGroup MultiSplitterLayout::staticAnchorGroup() const
{
return m_staticAnchorGroup;
}
Anchor::List MultiSplitterLayout::anchors(Qt::Orientation orientation, bool includeStatic,
bool includePlaceholders) const
{
Anchor::List result;
for (Anchor *anchor : m_anchors) {
if ((includeStatic || !anchor->isStatic()) && (includePlaceholders || !anchor->isFollowing()) && anchor->orientation() == orientation)
result << anchor;
}
return result;
}
void MultiSplitterLayout::blockItemPropagateGeo(bool block)
{
for (Item *item : m_items) {
if (block)
item->beginBlockPropagateGeo();
else
item->endBlockPropagateGeo();
}
}
void MultiSplitterLayout::emitVisibleWidgetCountChanged()
{
if (!m_inDestructor)
@@ -1040,7 +599,7 @@ Frame::List MultiSplitterLayout::frames() const
Frame::List result;
for (Item *item : m_items) {
if (Frame *f = item->frame())
if (auto f = static_cast<Frame*>(item->frame()))
result.push_back(f);
}
@@ -1063,22 +622,6 @@ bool MultiSplitterLayout::checkSanity() const
return m_rootItem->checkSanity();
}
void MultiSplitterLayout::ensureHasAvailableSize(QSize needed)
{
const QSize availableSize = this->availableSize();
qCDebug(placeholder) << Q_FUNC_INFO << "; needed=" << needed << availableSize;
const int deltaWidth = needed.width() > availableSize.width() ? (needed.width() - availableSize.width())
: 0;
const int deltaHeight = needed.height() > availableSize.height() ? (needed.height() - availableSize.height())
: 0;
const QSize newSize = size() + QSize(deltaWidth, deltaHeight);
setSize(newSize);
}
void MultiSplitterLayout::unrefOldPlaceholders(const Frame::List &framesBeingAdded) const
{
for (Frame *frame : framesBeingAdded) {
@@ -1151,107 +694,6 @@ void MultiSplitterLayout::setMinimumSize(QSize sz)
qCDebug(sizing) << Q_FUNC_INFO << "minSize = " << m_minSize;
}
void MultiSplitterLayout::clearAnchorsFollowing()
{
for (Anchor *anchor : qAsConst(m_anchors))
anchor->setFollowee(nullptr);
}
void MultiSplitterLayout::updateAnchorFollowing(const AnchorGroup &groupBeingRemoved)
{
clearAnchorsFollowing();
QHash<Anchor *, int> newPositionsWhenGroupRemoved;
for (Anchor *anchor : qAsConst(m_anchors)) {
if (anchor->isStatic())
continue;
if (anchor->onlyHasPlaceholderItems(Anchor::Side2)) {
Anchor *toFollow = anchor->findNearestAnchorWithItems(Anchor::Side2);
if (toFollow->followee() != anchor) {
if (!toFollow->isStatic() && groupBeingRemoved.containsAnchor(anchor, Anchor::Side1)) {
// A group is being removed, instead of simply shifting the left/top anchor all the way, let's make it use half the space
if (toFollow->onlyHasPlaceholderItems(Anchor::Side1)) { // Means it can move!
const int delta = toFollow->position() - anchor->position() - anchor->thickness();
const int halfDelta = int(delta / 2.0);
if (halfDelta > 0) {
newPositionsWhenGroupRemoved.insert(toFollow, toFollow->position() - halfDelta);
}
}
}
anchor->setFollowee(toFollow);
}
} else if (anchor->onlyHasPlaceholderItems(Anchor::Side1)) {
Anchor *toFollow = anchor->findNearestAnchorWithItems(Anchor::Side1);
if (toFollow->followee() != anchor) {
if (!toFollow->isStatic() && groupBeingRemoved.containsAnchor(anchor, Anchor::Side2)) {
// A group is being removed, instead of simply shifting the right/bottom anchor all the way, let's make it use half the space
if (toFollow->onlyHasPlaceholderItems(Anchor::Side2)) { // Means it can move!
const int delta = anchor->position() - toFollow->position() - toFollow->thickness();
const int halfDelta = int(delta / 2.0);
if (halfDelta > 0) {
newPositionsWhenGroupRemoved.insert(toFollow, toFollow->position() + halfDelta);
}
}
}
anchor->setFollowee(toFollow);
}
}
}
for (auto it = newPositionsWhenGroupRemoved.begin(), end = newPositionsWhenGroupRemoved.end(); it != end; ++it) {
Anchor *anchorToShift = it.key();
const int newPosition = it.value();
const Anchor::Side sideToShiftTo = newPosition < anchorToShift->position() ? Anchor::Side1
: Anchor::Side2;
bool doShift = true;
for (Anchor *follower : anchorToShift->followers()) {
if (follower->hasNonPlaceholderItems(sideToShiftTo) && !groupBeingRemoved.containsAnchor(follower, sideToShiftTo)) {
doShift = false;
break;
}
}
if (doShift && !anchorToShift->isFollowing())
anchorToShift->setPosition(newPosition);
}
updateSizeConstraints();
ensureAnchorsBounded();
}
QHash<Anchor*, Anchor*> MultiSplitterLayout::anchorsShouldFollow() const
{
QHash<Anchor*, Anchor*> followers;
for (Anchor *anchor : m_anchors) {
if (anchor->isStatic())
continue;
if (anchor->onlyHasPlaceholderItems(Anchor::Side2)) {
Anchor *toFollow = anchor->findNearestAnchorWithItems(Anchor::Side2);
if (followers.value(toFollow) != anchor)
followers.insert(anchor, toFollow);
} else if (anchor->onlyHasPlaceholderItems(Anchor::Side1)) {
Anchor *toFollow = anchor->findNearestAnchorWithItems(Anchor::Side1);
if (followers.value(toFollow) != anchor)
followers.insert(anchor, toFollow);
}
}
return followers;
}
void MultiSplitterLayout::insertAnchor(Anchor *anchor)
{
m_anchors.append(anchor);
}
const ItemList MultiSplitterLayout::items() const
{
return m_items;
@@ -1314,10 +756,6 @@ bool MultiSplitterLayout::deserialize(const LayoutSaver::MultiSplitterLayout &ms
}
}
m_staticAnchorGroup.left = m_leftAnchor;
m_staticAnchorGroup.top = m_topAnchor;
m_staticAnchorGroup.right = m_rightAnchor;
m_staticAnchorGroup.bottom = m_bottomAnchor;
m_items.clear(); // Now properly set the items, which installs needed event filters, etc.
addItems_internal(items, false, false); // Add the items only after we have the static anchors set

View File

@@ -58,28 +58,6 @@ inline int widgetLength(const T *w, Qt::Orientation orientation)
return (orientation == Qt::Vertical) ? w->width() : w->height();
}
inline int lengthFromSize(QSize sz, Qt::Orientation orientation)
{
return orientation == Qt::Vertical ? sz.width()
: sz.height();
}
inline Anchor::Side sideForLocation(Location loc)
{
switch (loc) {
case KDDockWidgets::Location_OnLeft:
case KDDockWidgets::Location_OnTop:
return Anchor::Side1;
case KDDockWidgets::Location_OnRight:
case KDDockWidgets::Location_OnBottom:
return Anchor::Side2;
default:
break;
}
return Anchor::Side_None;
}
/**
* A MultiSplitter is like a QSplitter but supports mixing vertical and horizontal splitters in
* any combination.
@@ -223,22 +201,9 @@ public:
///@brief returns list of separators
const Anchor::List anchors() const { return m_anchors; }
/**
* @brief Returns the list of anchors that are following @p followee
*/
Anchor::List anchorsFollowing(Anchor *followee) const;
///@brief returns the number of anchors that are following others, just for tests.
int numAchorsFollowing() const;
///@brief returns the number of anchors that are following others, just for tests.
int numVisibleAnchors() const;
///@brief returns either the left, top, right or bottom separator, depending on the @p type
Anchor *staticAnchor(Anchor::Type type) const;
Anchor *staticAnchor(Anchor::Side side, Qt::Orientation orientation) const;
///@brief a function that all code paths adding Items will call.
///It's mostly for code reuse, so we don't duplicate what's done here. But it's also nice to
///have a central place that we know will be called
@@ -421,47 +386,6 @@ private:
void emitVisibleWidgetCountChanged();
/**
* @brief Returns the size that the widget will get when dropped at this specific location.
*
* When location is Left or Right then the length represents a width, otherwise an height.
* This function is also called to know the size of the rubberband when hovering over a location.
*/
MultiSplitterLayout::Length lengthForDrop(const QWidgetOrQuick *widget, KDDockWidgets::Location location,
const Item *relativeTo) const;
/**
* @brief Ensures that this layout's size is enough for dropping @p widget to @p location,
* relative to @p relativeToItem.
*
* It may increase size or do notying, never decrease.
*/
void ensureEnoughSize(const QWidgetOrQuick *widget, KDDockWidgets::Location location,
const Item *relativeToItem);
void insertAnchor(Anchor *);
void removeAnchor(Anchor *);
/**
* Returns the min or max position that an anchor can go to (due to minimum size restriction on the widgets).
* For example, if the anchor is vertical and direction is Side1 then it returns the minimum x
* that the anchor can have. If direction is Side2 then it returns the maximum width. If horizontal
* then the height.
*/
int boundPositionForAnchor(Anchor *, Anchor::Side direction) const;
/**
* Similar to boundPositionForAnchor, but returns both the min and the max width (or height)
*/
QPair<int, int> boundPositionsForAnchor(Anchor *) const;
/**
* @brief similar to @ref boundPositionsForAnchor but returns for all anchors
*/
QHash<Anchor*, QPair<int,int>> boundPositionsForAllAnchors() const;
/** Returns how much is available for the new drop. It already counts with the space for new anchor that will be created.
* So it returns this layout's width() (or height), minus the minimum-sizes of all widgets, minus the thickness of all anchors
* minus the thickness of the anchor that would be created.

View File

@@ -18,7 +18,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Item.h"
#include "Item_p.h"
#include <QtTest/QtTest>
#include <memory.h>
@@ -83,7 +83,7 @@ void TestMultiSplitter::tst_insertOne()
{
auto root = createRoot();
auto item = createItem("1");
root->insertItem(item, Location_Top);
root->insertItem(item, Location_OnTop);
QCOMPARE(root->numChildren(), 1);
QVERIFY(item->isWidget());
QVERIFY(!item->isContainer());
@@ -104,9 +104,9 @@ void TestMultiSplitter::tst_insertThreeSideBySide()
auto item2 = new Item();
auto item3 = new Item();
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
root->insertItem(item3, Location_Right);
root->insertItem(item1, Location_OnLeft);
root->insertItem(item2, Location_OnRight);
root->insertItem(item3, Location_OnRight);
QVERIFY(root->checkSanity());
QCOMPARE(root->numChildren(), 3);
@@ -121,9 +121,9 @@ void TestMultiSplitter::tst_insertOnWidgetItem1()
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);
root->insertItem(item1, Location_OnLeft);
root->insertItem(item2, Location_OnRight);
item2->insertItem(item3, Location_OnRight);
QVERIFY(item3->x() > item2->x());
QCOMPARE(item3->y(), item2->y());
@@ -140,9 +140,9 @@ void TestMultiSplitter::tst_insertOnWidgetItem2()
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);
root->insertItem(item1, Location_OnLeft);
root->insertItem(item2, Location_OnRight);
item2->insertItem(item3, Location_OnLeft);
QVERIFY(item1->x() < item3->x());
QVERIFY(item3->x() < item2->x());
@@ -162,10 +162,10 @@ void TestMultiSplitter::tst_insertOnWidgetItem1DifferentOrientation()
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);
root->insertItem(item1, Location_OnLeft);
root->insertItem(item2, Location_OnRight);
item2->insertItem(item3, Location_OnRight);
item3->insertItem(item31, Location_OnBottom);
auto container3 = item3->parentContainer();
QVERIFY(container3->isContainer());
@@ -204,12 +204,12 @@ void TestMultiSplitter::tst_insertOnWidgetItem2DifferentOrientation()
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);
root->insertItem(item1, Location_OnLeft);
root->insertItem(item2, Location_OnRight);
item2->insertItem(item3, Location_OnRight);
item3->insertItem(item31, Location_OnBottom);
auto container3Parent = item3->parentContainer();
item3->insertItem(item32, Location_Right);
item3->insertItem(item32, Location_OnRight);
auto container3 = item3->parentContainer();
QCOMPARE(container3->parentContainer(), container3Parent);
@@ -256,12 +256,12 @@ void TestMultiSplitter::tst_insertOnRootDifferentOrientation()
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);
root->insertItem(item1, Location_OnLeft);
root->insertItem(item2, Location_OnRight);
item2->insertItem(item3, Location_OnRight);
item3->insertItem(item31, Location_OnBottom);
item3->insertItem(item32, Location_OnRight);
root->insertItem(item4, Location_OnTop);
QCOMPARE(item4->parentContainer(), root.get());
QCOMPARE(item4->pos(), root->pos());
@@ -283,12 +283,12 @@ void TestMultiSplitter::tst_removeItem1()
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);
root->insertItem(item1, Location_OnLeft);
root->insertItem(item2, Location_OnRight);
item2->insertItem(item3, Location_OnRight);
item3->insertItem(item31, Location_OnBottom);
item3->insertItem(item32, Location_OnRight);
root->insertItem(item4, Location_OnTop);
QCOMPARE(root->numChildren(), 2);
root->removeItem(item4);
@@ -322,10 +322,10 @@ void TestMultiSplitter::tst_removeItem2()
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);
root->insertItem(item1, Location_OnLeft);
root->insertItem(item2, Location_OnRight);
item2->insertItem(item3, Location_OnRight);
item3->insertItem(item31, Location_OnBottom);
item31->parentContainer()->removeItem(item31);
item3->parentContainer()->removeItem(item3);
}
@@ -341,9 +341,9 @@ void TestMultiSplitter::tst_minSize()
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);
root->insertItem(item1, Location_OnLeft);
root->insertItem(item2, Location_OnRight);
item2->insertItem(item22, Location_OnBottom);
QCOMPARE(item2->minSize(), QSize(200, 300));
QCOMPARE(item2->parentContainer()->minSize(), QSize(200, 300+100+st));
@@ -359,9 +359,9 @@ void TestMultiSplitter::tst_resize()
auto item3 = createItem("3");
auto item31 = createItem("31");
root->insertItem(item1, Location_Left);
root->insertItem(item2, Location_Right);
root->insertItem(item3, Location_Right);
root->insertItem(item1, Location_OnLeft);
root->insertItem(item2, Location_OnRight);
root->insertItem(item3, Location_OnRight);
const int item1Percentage = item1->width() / root->width();
const int item2Percentage = item1->width() / root->width();
@@ -379,7 +379,7 @@ void TestMultiSplitter::tst_resize()
QCOMPARE(item2->height(), 505);
QCOMPARE(item3->height(), 505);
item3->insertItem(item31, Location_Bottom);
item3->insertItem(item31, Location_OnBottom);
QVERIFY(root->checkSanity());
root->resize({2500, 505});
@@ -394,7 +394,7 @@ void TestMultiSplitter::tst_resizeWithConstraints()
auto root = createRoot();
auto item1 = createItem("1");
item1->setMinSize(QSize(500, 500));
root->insertItem(item1, Location_Left);
root->insertItem(item1, Location_OnLeft);
QVERIFY(root->checkSanity());
root->resize(item1->minSize()); // Still fits
@@ -411,9 +411,9 @@ void TestMultiSplitter::tst_resizeWithConstraints()
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);
root->insertItem(item1, Location_OnLeft);
root->insertItem(item2, Location_OnRight);
root->insertItem(item3, Location_OnRight);
QVERIFY(root->checkSanity());
// TODO: Resize further
@@ -433,7 +433,7 @@ void TestMultiSplitter::tst_availableSize()
item2->m_sizingInfo.minSize = {100, 100};
item3->m_sizingInfo.minSize = {100, 100};
root->insertItem(item1, Location_Left);
root->insertItem(item1, Location_OnLeft);
QCOMPARE(root->availableSize(), QSize(900, 900));
QCOMPARE(root->minSize(), QSize(100, 100));
QCOMPARE(root->neighboursLengthFor(item1, Side1, Qt::Horizontal), 0);
@@ -450,7 +450,7 @@ void TestMultiSplitter::tst_availableSize()
QCOMPARE(root->neighboursLengthFor_recursive(item1, Side1, Qt::Horizontal), 0);
QCOMPARE(root->neighboursLengthFor_recursive(item1, Side2, Qt::Horizontal), 0);
root->insertItem(item2, Location_Left);
root->insertItem(item2, Location_OnLeft);
QCOMPARE(root->availableSize(), QSize(800 - st, 900));
QCOMPARE(root->minSize(), QSize(200 + st, 100));
QCOMPARE(root->neighboursLengthFor(item1, Side1, Qt::Horizontal), item2->width());
@@ -467,7 +467,7 @@ void TestMultiSplitter::tst_availableSize()
QCOMPARE(root->neighboursLengthFor_recursive(item1, Side1, Qt::Horizontal), item2->width());
QCOMPARE(root->neighboursLengthFor_recursive(item1, Side2, Qt::Horizontal), 0);
root->insertItem(item3, Location_Bottom);
root->insertItem(item3, Location_OnBottom);
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());
@@ -481,8 +481,8 @@ void TestMultiSplitter::tst_availableSize()
// More nesting
auto item4 = createItem("4");
auto item5 = createItem("5");
item3->insertItem(item4, Location_Right);
item4->insertItem(item5, Location_Bottom);
item3->insertItem(item4, Location_OnRight);
item4->insertItem(item5, Location_OnBottom);
auto container4 = item4->parentContainer();
QCOMPARE(container4->neighboursLengthFor_recursive(item4, Side1, Qt::Vertical), item1->height());
@@ -526,7 +526,7 @@ void TestMultiSplitter::tst_missingSize()
QCOMPARE(root->missingSizeFor(item3, Qt::Vertical), QSize(100, 200));
// Test with an existing item
root->insertItem(item1, Location_Top);
root->insertItem(item1, Location_OnTop);
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));
}
@@ -541,7 +541,7 @@ void TestMultiSplitter::tst_ensureEnoughSize()
// Insert to empty layout:
root->insertItem(item1, Location_Left);
root->insertItem(item1, Location_OnLeft);
QCOMPARE(root->size(), QSize(2000, 1000));
QCOMPARE(item1->size(), QSize(2000, 1000));
QCOMPARE(item1->minSize(), root->minSize());
@@ -550,7 +550,7 @@ void TestMultiSplitter::tst_ensureEnoughSize()
// Insert to non-empty layout
Item *item2 = createItem("2");
item2->setMinSize({2000, 2000});
root->insertItem(item2, Location_Right);
root->insertItem(item2, Location_OnRight);
QVERIFY(root->checkSanity());
QCOMPARE(root->size(), QSize(item1->minSize().width() + item2->minSize().width() + st, item2->minSize().height()));
}