diff --git a/src/private/multisplitter/Anchor.cpp b/src/private/multisplitter/Anchor.cpp index 77b1a4e3..17adbd4d 100644 --- a/src/private/multisplitter/Anchor.cpp +++ b/src/private/multisplitter/Anchor.cpp @@ -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 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 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; -} diff --git a/src/private/multisplitter/Anchor_p.h b/src/private/multisplitter/Anchor_p.h index c4f61498..988941c7 100644 --- a/src/private/multisplitter/Anchor_p.h +++ b/src/private/multisplitter/Anchor_p.h @@ -23,6 +23,7 @@ #include "docks_export.h" #include "LayoutSaver_p.h" +#include "Item_p.h" #include #include @@ -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 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 m_from;// QPointer just so we can assert. They should never be null. QPointer 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; diff --git a/src/private/multisplitter/MultiSplitterLayout.cpp b/src/private/multisplitter/MultiSplitterLayout.cpp index c30fa2be..2fe8687e 100644 --- a/src/private/multisplitter/MultiSplitterLayout.cpp +++ b/src/private/multisplitter/MultiSplitterLayout.cpp @@ -538,276 +538,13 @@ int MultiSplitterLayout::placeholderCount() const return count() - visibleCount(); } -void MultiSplitterLayout::removeAnchor(Anchor *anchor) -{ - if (!m_inDestructor) - m_anchors.removeOne(anchor); -} -QPair 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 > MultiSplitterLayout::boundPositionsForAllAnchors() const -{ - QHash > 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() : 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(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 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 MultiSplitterLayout::anchorsShouldFollow() const -{ - QHash 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 diff --git a/src/private/multisplitter/MultiSplitterLayout_p.h b/src/private/multisplitter/MultiSplitterLayout_p.h index 68e1f295..373c3c3f 100644 --- a/src/private/multisplitter/MultiSplitterLayout_p.h +++ b/src/private/multisplitter/MultiSplitterLayout_p.h @@ -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 boundPositionsForAnchor(Anchor *) const; - - /** - * @brief similar to @ref boundPositionsForAnchor but returns for all anchors - */ - QHash> 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. diff --git a/src/private/multisplitter/tests/tst_multisplitter.cpp b/src/private/multisplitter/tests/tst_multisplitter.cpp index 105b1e87..234f030b 100644 --- a/src/private/multisplitter/tests/tst_multisplitter.cpp +++ b/src/private/multisplitter/tests/tst_multisplitter.cpp @@ -18,7 +18,7 @@ along with this program. If not, see . */ -#include "Item.h" +#include "Item_p.h" #include #include @@ -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())); }