KDDockWidgets API Documentation 1.7
Loading...
Searching...
No Matches
Item.cpp
Go to the documentation of this file.
1/*
2 This file is part of KDDockWidgets.
3
4 SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5 Author: Sérgio Martins <sergio.martins@kdab.com>
6
7 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
8
9 Contact KDAB at <info@kdab.com> for commercial licensing options.
10*/
11
12#include "Item_p.h"
13#include "Separator_p.h"
14#include "MultiSplitterConfig.h"
15#include "Widget.h"
16#include "ItemFreeContainer_p.h"
17
18#include <QEvent>
19#include <QDebug>
20#include <QScopedValueRollback>
21#include <QTimer>
22#include <QGuiApplication>
23#include <QScreen>
24#include <algorithm>
25
26#ifdef Q_CC_MSVC
27#pragma warning(push)
28#pragma warning(disable : 4138)
29#pragma warning(disable : 4244)
30#pragma warning(disable : 4457)
31#pragma warning(disable : 4702)
32#endif
33
34// clazy:excludeall=missing-typeinfo,old-style-connect
35
36using namespace Layouting;
37using namespace KDDockWidgets;
38
39int Layouting::Item::separatorThickness = 5;
40
41// There are the defaults. They can be changed by the user via Config.h API.
42QSize Layouting::Item::hardcodedMinimumSize = QSize(80, 90);
43QSize Layouting::Item::hardcodedMaximumSize = QSize(16777215, 16777215);
44
45bool Layouting::ItemBoxContainer::s_inhibitSimplify = false;
46
48{
49 return loc == Location_OnTop || loc == Location_OnBottom;
50}
51
52inline bool locationIsSide1(Location loc)
53{
54 return loc == Location_OnLeft || loc == Location_OnTop;
55}
56
58{
59 switch (loc) {
60 case Location_OnLeft:
62 return Qt::Horizontal;
63 case Location_None:
64 case Location_OnTop:
66 return Qt::Vertical;
67 }
68
69 return Qt::Vertical;
70}
71
77
78inline QRect adjustedRect(QRect r, Qt::Orientation o, int p1, int p2)
79{
80 if (o == Qt::Vertical) {
81 r.adjust(0, p1, 0, p2);
82 } else {
83 r.adjust(p1, 0, p2, 0);
84 }
85
86 return r;
87}
88
89namespace Layouting {
90struct LengthOnSide
91{
92 int length = 0;
93 int minLength = 0;
94
95 int available() const
96 {
97 return qMax(0, length - minLength);
98 }
99
100 int missing() const
101 {
102 return qMax(0, minLength - length);
103 }
104};
105
106}
107
108ItemBoxContainer *Item::root() const
109{
110 return m_parent ? m_parent->root()
111 : const_cast<ItemBoxContainer *>(qobject_cast<const ItemBoxContainer *>(this));
112}
113
114QRect Item::mapToRoot(QRect r) const
115{
116 const QPoint topLeft = mapToRoot(r.topLeft());
117 r.moveTopLeft(topLeft);
118 return r;
119}
120
121QPoint Item::mapToRoot(QPoint p) const
122{
123 if (isRoot())
124 return p;
125
126 return p + parentContainer()->mapToRoot(pos());
127}
128
129int Item::mapToRoot(int p, Qt::Orientation o) const
130{
131 if (o == Qt::Vertical)
132 return mapToRoot(QPoint(0, p)).y();
133 return mapToRoot(QPoint(p, 0)).x();
134}
135
136QPoint Item::mapFromRoot(QPoint p) const
137{
138 const Item *it = this;
139 while (it) {
140 p = p - it->pos();
141 it = it->parentContainer();
142 }
143
144 return p;
145}
146
147QRect Item::mapFromRoot(QRect r) const
148{
149 const QPoint topLeft = mapFromRoot(r.topLeft());
150 r.moveTopLeft(topLeft);
151 return r;
152}
153
154QPoint Item::mapFromParent(QPoint p) const
155{
156 if (isRoot())
157 return p;
158
159 return p - pos();
160}
161
162int Item::mapFromRoot(int p, Qt::Orientation o) const
163{
164 if (o == Qt::Vertical)
165 return mapFromRoot(QPoint(0, p)).y();
166 return mapFromRoot(QPoint(p, 0)).x();
167}
168
169QObject *Item::guestAsQObject() const
170{
171 return m_guest ? m_guest->asQObject() : nullptr;
172}
173
174void Item::setGuestWidget(Widget *guest)
175{
176 Q_ASSERT(!guest || !m_guest);
177 QObject *newWidget = guest ? guest->asQObject() : nullptr;
178 QObject *oldWidget = guestAsQObject();
179
180 if (oldWidget) {
181 oldWidget->removeEventFilter(this);
182 disconnect(oldWidget, nullptr, this, nullptr);
183 }
184
185 m_guest = guest;
186
187 if (m_guest) {
188 m_guest->setParent(m_hostWidget);
189 m_guest->setLayoutItem(this);
190 newWidget->installEventFilter(this);
191
192 {
193 QScopedValueRollback<bool> guard(m_isSettingGuest, true);
194 setMinSize(guest->minSize());
195 setMaxSizeHint(guest->maxSizeHint());
196 }
197
198 connect(newWidget, &QObject::objectNameChanged, this, &Item::updateObjectName);
199 connect(newWidget, &QObject::destroyed, this, &Item::onWidgetDestroyed);
200 connect(newWidget, SIGNAL(layoutInvalidated()), this, SLOT(onWidgetLayoutRequested()));
201
202 if (m_sizingInfo.geometry.isEmpty()) {
203 // Use the widgets geometry, but ensure it's at least hardcodedMinimumSize
204 QRect widgetGeo = m_guest->geometry();
205 widgetGeo.setSize(
206 widgetGeo.size().expandedTo(minSize()).expandedTo(Item::hardcodedMinimumSize));
207 setGeometry(mapFromRoot(widgetGeo));
208 } else {
209 updateWidgetGeometries();
210 }
211 }
212
213 updateObjectName();
214}
215
216void Item::updateWidgetGeometries()
217{
218 if (m_guest) {
219 m_guest->setGeometry(mapToRoot(rect()));
220 }
221}
222
223QVariantMap Item::toVariantMap() const
224{
225 QVariantMap result;
226
227 result[QStringLiteral("sizingInfo")] = m_sizingInfo.toVariantMap();
228 result[QStringLiteral("isVisible")] = m_isVisible;
229 result[QStringLiteral("isContainer")] = isContainer();
230 result[QStringLiteral("objectName")] = objectName();
231 if (m_guest)
232 result[QStringLiteral("guestId")] = m_guest->id(); // just for coorelation purposes when restoring
233
234 return result;
235}
236
237void Item::fillFromVariantMap(const QVariantMap &map, const QHash<QString, Widget *> &widgets)
238{
239 m_sizingInfo.fromVariantMap(map[QStringLiteral("sizingInfo")].toMap());
240 m_isVisible = map[QStringLiteral("isVisible")].toBool();
241 setObjectName(map[QStringLiteral("objectName")].toString());
242
243 const QString guestId = map.value(QStringLiteral("guestId")).toString();
244 if (!guestId.isEmpty()) {
245 if (Widget *guest = widgets.value(guestId)) {
246 setGuestWidget(guest);
247 m_guest->setParent(hostWidget());
248 } else if (hostWidget()) {
249 qWarning() << Q_FUNC_INFO << "Couldn't find frame to restore for" << this;
250 }
251 }
252}
253
254Item *Item::createFromVariantMap(Widget *hostWidget, ItemContainer *parent,
255 const QVariantMap &map, const QHash<QString, Widget *> &widgets)
256{
257 auto item = new Item(hostWidget, parent);
258 item->fillFromVariantMap(map, widgets);
259 return item;
260}
261
262void Item::ref()
263{
264 m_refCount++;
265}
266
267void Item::unref()
268{
269 Q_ASSERT(m_refCount > 0);
270 m_refCount--;
271 if (m_refCount == 0) {
272 Q_ASSERT(!isRoot());
273 parentContainer()->removeItem(this);
274 }
275}
276
277int Item::refCount() const
278{
279 return m_refCount;
280}
281
282Widget *Item::hostWidget() const
283{
284 return m_hostWidget;
285}
286
287QObject *Item::host() const
288{
289 return m_hostWidget ? m_hostWidget->asQObject()
290 : nullptr;
291}
292
293void Item::restore(Widget *guest)
294{
295 if (isVisible() || guestAsQObject()) {
296 qWarning() << Q_FUNC_INFO << "Hitting assert. visible="
297 << isVisible() << "; guest=" << guestAsQObject();
298 Q_ASSERT(false);
299 }
300
301 if (isContainer()) {
302 qWarning() << Q_FUNC_INFO << "Containers can't be restored";
303 } else {
304 setGuestWidget(guest);
305 parentContainer()->restore(this);
306
307 // When we restore to previous positions, we only still from the immediate neighbours.
308 // It's consistent with closing an item, it also only grows the immediate neighbours
309 // By passing ImmediateNeighboursFirst we can hide/show an item multiple times and it
310 // uses the same place
311 }
312}
313
314QVector<int> Item::pathFromRoot() const
315{
316 // Returns the list of indexes to get to this item, starting from the root container
317 // Example [0, 1, 3] would mean that the item is the 4th child of the 2nd child of the 1st child of root
318 // [] would mean 'this' is the root item
319 // [0] would mean the 1st child of root
320
321 QVector<int> path;
322 path.reserve(10); // random big number, good to bootstrap it
323
324 const Item *it = this;
325 while (it) {
326 if (auto p = it->parentContainer()) {
327 const auto index = p->childItems().indexOf(const_cast<Item *>(it));
328 path.prepend(index);
329 it = p;
330 } else {
331 break;
332 }
333 }
334
335 return path;
336}
337
338void Item::setHostWidget(Widget *host)
339{
340 if (m_hostWidget != host) {
341 m_hostWidget = host;
342 if (m_guest) {
343 m_guest->setParent(host);
344 m_guest->setVisible(true);
345 updateWidgetGeometries();
346 }
347 }
348}
349
350void Item::setSize_recursive(QSize newSize, ChildrenResizeStrategy)
351{
352 setSize(newSize);
353}
354
355QSize Item::missingSize() const
356{
357 QSize missing = minSize() - this->size();
358 missing.setWidth(qMax(missing.width(), 0));
359 missing.setHeight(qMax(missing.height(), 0));
360
361 return missing;
362}
363
364bool Item::isBeingInserted() const
365{
366 return m_sizingInfo.isBeingInserted;
367}
368
369void Item::setBeingInserted(bool is)
370{
371 m_sizingInfo.isBeingInserted = is;
372
373 // Trickle up the hierarchy too, as the parent might be hidden due to not having visible children
374 if (auto parent = parentContainer()) {
375 if (is) {
376 if (!parent->hasVisibleChildren())
377 parent->setBeingInserted(true);
378 } else {
379 parent->setBeingInserted(false);
380 }
381 }
382}
383
384void Item::setParentContainer(ItemContainer *parent)
385{
386 if (parent == m_parent)
387 return;
388
389 if (m_parent) {
390 disconnect(this, &Item::minSizeChanged, m_parent, &ItemContainer::onChildMinSizeChanged);
391 disconnect(this, &Item::visibleChanged, m_parent, &ItemContainer::onChildVisibleChanged);
392 Q_EMIT visibleChanged(this, false);
393 }
394
395 if (auto c = asContainer()) {
396 const bool ceasingToBeRoot = !m_parent && parent;
397 if (ceasingToBeRoot && !c->hasVisibleChildren()) {
398 // Was root but is not root anymore. So, if empty, then it has an empty rect too.
399 // Only root can have a non-empty rect without having children
400 c->setGeometry({});
401 }
402 }
403
404 m_parent = parent;
405 connectParent(parent); // Reused by the ctor too
406
407 QObject::setParent(parent);
408}
409
410void Item::connectParent(ItemContainer *parent)
411{
412 if (parent) {
413 connect(this, &Item::minSizeChanged, parent, &ItemContainer::onChildMinSizeChanged);
414 connect(this, &Item::visibleChanged, parent, &ItemContainer::onChildVisibleChanged);
415
416 setHostWidget(parent->hostWidget());
417 updateWidgetGeometries();
418
419 Q_EMIT visibleChanged(this, isVisible());
420 }
421}
422
423ItemContainer *Item::parentContainer() const
424{
425 return m_parent;
426}
427
428ItemBoxContainer *Item::parentBoxContainer() const
429{
430 return qobject_cast<ItemBoxContainer *>(m_parent);
431}
432
433const ItemContainer *Item::asContainer() const
434{
435 return qobject_cast<const ItemContainer *>(this);
436}
437
438ItemContainer *Item::asContainer()
439{
440 return qobject_cast<ItemContainer *>(this);
441}
442
443ItemBoxContainer *Item::asBoxContainer()
444{
445 return qobject_cast<ItemBoxContainer *>(this);
446}
447
448void Item::setMinSize(QSize sz)
449{
450 if (sz != m_sizingInfo.minSize) {
451 m_sizingInfo.minSize = sz;
452 Q_EMIT minSizeChanged(this);
453 if (!m_isSettingGuest)
454 setSize_recursive(size().expandedTo(sz));
455 }
456}
457
458void Item::setMaxSizeHint(QSize sz)
459{
460 if (sz != m_sizingInfo.maxSizeHint) {
461 m_sizingInfo.maxSizeHint = sz;
462 Q_EMIT maxSizeChanged(this);
463 }
464}
465
466QSize Item::minSize() const
467{
468 return m_sizingInfo.minSize;
469}
470
471QSize Item::maxSizeHint() const
472{
473 return m_sizingInfo.maxSizeHint.boundedTo(hardcodedMaximumSize);
474}
475
476void Item::setPos(QPoint pos)
477{
478 QRect geo = m_sizingInfo.geometry;
479 geo.moveTopLeft(pos);
480 setGeometry(geo);
481}
482
483void Item::setPos(int pos, Qt::Orientation o)
484{
485 if (o == Qt::Vertical) {
486 setPos({ x(), pos });
487 } else {
488 setPos({ pos, y() });
489 }
490}
491
492int Item::pos(Qt::Orientation o) const
493{
494 return o == Qt::Vertical ? y() : x();
495}
496
497int Item::x() const
498{
499 return m_sizingInfo.geometry.x();
500}
501
502int Item::y() const
503{
504 return m_sizingInfo.geometry.y();
505}
506
507int Item::width() const
508{
509 return m_sizingInfo.geometry.width();
510}
511
512int Item::height() const
513{
514 return m_sizingInfo.geometry.height();
515}
516
517QSize Item::size() const
518{
519 return m_sizingInfo.geometry.size();
520}
521
522void Item::setSize(QSize sz)
523{
524 QRect newGeo = m_sizingInfo.geometry;
525 newGeo.setSize(sz);
526 setGeometry(newGeo);
527}
528
529QPoint Item::pos() const
530{
531 return m_sizingInfo.geometry.topLeft();
532}
533
534QRect Item::geometry() const
535{
536 return isBeingInserted() ? QRect()
537 : m_sizingInfo.geometry;
538}
539
540QRect Item::rect() const
541{
542 return QRect(0, 0, width(), height());
543}
544
545bool Item::isContainer() const
546{
547 return m_isContainer;
548}
549
550int Item::minLength(Qt::Orientation o) const
551{
552 return Layouting::length(minSize(), o);
553}
554
555int Item::maxLengthHint(Qt::Orientation o) const
556{
557 return Layouting::length(maxSizeHint(), o);
558}
559
560void Item::setLength(int length, Qt::Orientation o)
561{
562 Q_ASSERT(length > 0);
563 if (o == Qt::Vertical) {
564 const int w = qMax(width(), hardcodedMinimumSize.width());
565 setSize(QSize(w, length));
566 } else {
567 const int h = qMax(height(), hardcodedMinimumSize.height());
568 setSize(QSize(length, h));
569 }
570}
571
572void Item::setLength_recursive(int length, Qt::Orientation o)
573{
574 setLength(length, o);
575}
576
577int Item::length(Qt::Orientation o) const
578{
579 return Layouting::length(size(), o);
580}
581
582int Item::availableLength(Qt::Orientation o) const
583{
584 return length(o) - minLength(o);
585}
586
587bool Item::isPlaceholder() const
588{
589 return !isVisible();
590}
591
592bool Item::isVisible(bool excludeBeingInserted) const
593{
594 return m_isVisible && !(excludeBeingInserted && isBeingInserted());
595}
596
597void Item::setIsVisible(bool is)
598{
599 if (is != m_isVisible) {
600 m_isVisible = is;
601 Q_EMIT visibleChanged(this, is);
602 }
603
604 if (is && m_guest) {
605 m_guest->setGeometry(mapToRoot(rect()));
606 m_guest->setVisible(true); // TODO: Only set visible when apply*() ?
607 }
608
609 updateObjectName();
610}
611
612void Item::setGeometry_recursive(QRect rect)
613{
614 // Recursiveness doesn't apply for non-container items
615 setGeometry(rect);
616}
617
618bool Item::checkSanity()
619{
620 if (!root())
621 return true;
622
623 if (minSize().width() > width() || minSize().height() > height()) {
624 root()->dumpLayout();
625 qWarning() << Q_FUNC_INFO << "Size constraints not honoured" << this
626 << "; min=" << minSize() << "; size=" << size();
627 return false;
628 }
629
630 if (m_guest) {
631 if (m_guest->parent() != hostWidget()->asQObject()) {
632 if (root())
633 root()->dumpLayout();
634 qWarning() << Q_FUNC_INFO << "Unexpected parent for our guest. guest.parent="
635 << m_guest->parent() << "; host=" << hostWidget()->asQObject()
636 << "; guest.asObj=" << m_guest->asQObject()
637 << "; this=" << this
638 << "; item.parentContainer=" << parentContainer()
639 << "; item.root.parent=" << (root() ? root()->parent() : nullptr);
640 return false;
641 }
642
643 if (false && !m_guest->isVisible() && (!m_guest->parent() || m_guest->parentWidget()->isVisible())) {
644 // TODO: if guest is explicitly hidden we're not hiding the item yet
645 qWarning() << Q_FUNC_INFO << "Guest widget isn't visible" << this
646 << m_guest->asQObject();
647 return false;
648 }
649
650 if (m_guest->geometry() != mapToRoot(rect())) {
651 root()->dumpLayout();
652 auto d = qWarning();
653 d << Q_FUNC_INFO << "Guest widget doesn't have correct geometry. has"
654 << "guest.global=" << m_guest->geometry()
655 << "; item.local=" << geometry()
656 << "; item.global=" << mapToRoot(rect())
657 << this;
658 m_guest->dumpDebug(d);
659
660 return false;
661 }
662 }
663
664 return true;
665}
666
667bool Item::isMDI() const
668{
669 return qobject_cast<ItemFreeContainer *>(parentContainer()) != nullptr;
670}
671
672void Item::setGeometry(QRect rect)
673{
674 QRect &m_geometry = m_sizingInfo.geometry;
675
676 if (rect != m_geometry) {
677 const QRect oldGeo = m_geometry;
678
679 m_geometry = rect;
680
681 if (rect.isEmpty()) {
682 // Just a sanity check...
683 ItemContainer *c = asContainer();
684 if (c) {
685 if (c->hasVisibleChildren()) {
686 if (auto r = root())
687 r->dumpLayout();
688 Q_ASSERT(false);
689 }
690 } else {
691 qWarning() << Q_FUNC_INFO << "Empty rect";
692 }
693 }
694
695 const QSize minSz = minSize();
696 if (rect.width() < minSz.width() || rect.height() < minSz.height()) {
697 if (auto r = root())
698 r->dumpLayout();
699 qWarning() << Q_FUNC_INFO << this << "Constraints not honoured."
700 << "sz=" << rect.size() << "; min=" << minSz
701 << ": parent=" << parentContainer();
702 }
703
704 Q_EMIT geometryChanged();
705
706 if (oldGeo.x() != x())
707 Q_EMIT xChanged();
708 if (oldGeo.y() != y())
709 Q_EMIT yChanged();
710 if (oldGeo.width() != width())
711 Q_EMIT widthChanged();
712 if (oldGeo.height() != height())
713 Q_EMIT heightChanged();
714
715 updateWidgetGeometries();
716 }
717}
718
719void Item::dumpLayout(int level)
720{
721 QString indent;
722 indent.fill(QLatin1Char(' '), level);
723
724 auto dbg = qDebug().noquote();
725
726 dbg << indent << "- Widget: " << objectName()
727 << m_sizingInfo.geometry // << "r=" << m_geometry.right() << "b=" << m_geometry.bottom()
728 << "; min=" << minSize();
729
730 if (maxSizeHint() != hardcodedMaximumSize)
731 dbg << "; max=" << maxSizeHint();
732
733 if (!isVisible())
734 dbg << QStringLiteral(";hidden;");
735
736 if (m_guest && geometry() != m_guest->geometry()) {
737 dbg << "; guest geometry=" << m_guest->geometry();
738 }
739
740 if (m_sizingInfo.isBeingInserted)
741 dbg << QStringLiteral(";beingInserted;");
742
743 dbg << this << "; guest=" << guestAsQObject();
744}
745
746Item::Item(Widget *hostWidget, ItemContainer *parent)
747 : QObject(parent)
748 , m_isContainer(false)
749 , m_parent(parent)
750 , m_hostWidget(hostWidget)
751{
752 connectParent(parent);
753}
754
755Item::Item(bool isContainer, Widget *hostWidget, ItemContainer *parent)
756 : QObject(parent)
757 , m_isContainer(isContainer)
758 , m_parent(parent)
759 , m_hostWidget(hostWidget)
760{
761 connectParent(parent);
762}
763
764Item::~Item()
765{
766}
767
768bool Item::eventFilter(QObject *widget, QEvent *e)
769{
770 if (e->type() != QEvent::ParentChange)
771 return false;
772
773 QObject *host = hostWidget() ? hostWidget()->asQObject() : nullptr;
774 if (widget->parent() != host) {
775 // Frame was detached into floating window. Turn into placeholder
776 Q_ASSERT(isVisible());
777 turnIntoPlaceholder();
778 }
779
780 return false;
781}
782
783
784void Item::turnIntoPlaceholder()
785{
786 Q_ASSERT(!isContainer());
787
788 // Turning into placeholder just means hiding it. So we can show it again in its original position.
789 // Call removeItem() so we share the code for making the neighbours grow into the space that becomes available
790 // after hiding this one
791 parentContainer()->removeItem(this, /*hardDelete=*/false);
792}
793
794void Item::updateObjectName()
795{
796 if (isContainer())
797 return;
798
799 if (auto w = guestAsQObject()) {
800 setObjectName(w->objectName().isEmpty() ? QStringLiteral("widget") : w->objectName());
801 } else if (!isVisible()) {
802 setObjectName(QStringLiteral("hidden"));
803 } else if (!m_guest) {
804 setObjectName(QStringLiteral("null"));
805 } else {
806 setObjectName(QStringLiteral("empty"));
807 }
808}
809
810void Item::onWidgetDestroyed()
811{
812 m_guest = nullptr;
813
814 if (m_refCount) {
815 turnIntoPlaceholder();
816 } else if (!isRoot()) {
817 parentContainer()->removeItem(this);
818 }
819}
820
821void Item::onWidgetLayoutRequested()
822{
823 if (Widget *w = guestWidget()) {
824 if (w->size() != size() && !isMDI()) { // for MDI we allow user/manual arbitrary resize with mouse
825 qDebug() << Q_FUNC_INFO << "TODO: Not implemented yet. Widget can't just decide to resize yet"
826 << w->size()
827 << size()
828 << m_sizingInfo.geometry
829 << m_sizingInfo.isBeingInserted;
830 }
831
832 if (w->minSize() != minSize()) {
833 setMinSize(m_guest->minSize());
834 }
835
836 setMaxSizeHint(w->maxSizeHint());
837 }
838}
839
840bool Item::isRoot() const
841{
842 return m_parent == nullptr;
843}
844
845LayoutBorderLocations Item::adjacentLayoutBorders() const
846{
847 if (isRoot()) {
848 return LayoutBorderLocation_All;
849 }
850
851 ItemBoxContainer *c = parentBoxContainer();
852 if (!c)
853 return LayoutBorderLocation_None;
854
855 const int indexInParent = c->indexOfVisibleChild(this);
856 const int numVisibleChildren = c->numVisibleChildren();
857 const bool isFirst = indexInParent == 0;
858 const bool isLast = indexInParent == numVisibleChildren - 1;
859 if (indexInParent == -1)
860 return LayoutBorderLocation_None;
861
862 LayoutBorderLocations locations = LayoutBorderLocation_None;
863 if (c->isRoot()) {
864 if (c->isVertical()) {
865 locations |= LayoutBorderLocation_West;
866 locations |= LayoutBorderLocation_East;
867
868 if (isFirst)
869 locations |= LayoutBorderLocation_North;
870 if (isLast)
871 locations |= LayoutBorderLocation_South;
872 } else {
873 locations |= LayoutBorderLocation_North;
874 locations |= LayoutBorderLocation_South;
875
876 if (isFirst)
877 locations |= LayoutBorderLocation_West;
878 if (isLast)
879 locations |= LayoutBorderLocation_East;
880 }
881 } else {
882 const LayoutBorderLocations parentBorders = c->adjacentLayoutBorders();
883 if (c->isVertical()) {
884 if (parentBorders & LayoutBorderLocation_West)
885 locations |= LayoutBorderLocation_West;
886
887 if (parentBorders & LayoutBorderLocation_East)
888 locations |= LayoutBorderLocation_East;
889
890 if (isFirst && (parentBorders & LayoutBorderLocation_North))
891 locations |= LayoutBorderLocation_North;
892
893 if (isLast && (parentBorders & LayoutBorderLocation_South))
894 locations |= LayoutBorderLocation_South;
895
896 } else {
897 if (parentBorders & LayoutBorderLocation_North)
898 locations |= LayoutBorderLocation_North;
899
900 if (parentBorders & LayoutBorderLocation_South)
901 locations |= LayoutBorderLocation_South;
902
903 if (isFirst && (parentBorders & LayoutBorderLocation_West))
904 locations |= LayoutBorderLocation_West;
905
906 if (isLast && (parentBorders & LayoutBorderLocation_East))
907 locations |= LayoutBorderLocation_East;
908 }
909 }
910
911 return locations;
912}
913
914int Item::visibleCount_recursive() const
915{
916 return isVisible() ? 1 : 0;
917}
918
919struct ItemBoxContainer::Private
920{
921 Private(ItemBoxContainer *qq)
922 : q(qq)
923 {
924 }
925
926 ~Private()
927 {
928 qDeleteAll(m_separators);
929 m_separators.clear();
930 }
931
932 int defaultLengthFor(Item *item, InitialOption option) const;
933 bool isOverflowing() const;
934 void relayoutIfNeeded();
935 const Item *itemFromPath(const QVector<int> &path) const;
936 void resizeChildren(QSize oldSize, QSize newSize, SizingInfo::List &sizes, ChildrenResizeStrategy);
937 void honourMaxSizes(SizingInfo::List &sizes);
938 void scheduleCheckSanity() const;
939 Separator *neighbourSeparator(const Item *item, Side, Qt::Orientation) const;
940 Separator *neighbourSeparator_recursive(const Item *item, Side, Qt::Orientation) const;
941 void updateWidgets_recursive();
943 QVector<int> requiredSeparatorPositions() const;
944 void updateSeparators();
945 void deleteSeparators();
946 Separator *separatorAt(int p) const;
947 QVector<double> childPercentages() const;
948 bool isDummy() const;
949 void deleteSeparators_recursive();
950 void updateSeparators_recursive();
951 QSize minSize(const Item::List &items) const;
952 int excessLength() const;
953
954 mutable bool m_checkSanityScheduled = false;
956 bool m_convertingItemToContainer = false;
957 bool m_blockUpdatePercentages = false;
958 bool m_isDeserializing = false;
959 bool m_isSimplifying = false;
960 Qt::Orientation m_orientation = Qt::Vertical;
961 ItemBoxContainer *const q;
962};
963
964ItemBoxContainer::ItemBoxContainer(Widget *hostWidget, ItemContainer *parent)
965 : ItemContainer(hostWidget, parent)
966 , d(new Private(this))
967{
968 Q_ASSERT(parent);
969}
970
971ItemBoxContainer::ItemBoxContainer(Widget *hostWidget)
972 : ItemContainer(hostWidget, /*parentContainer=*/nullptr)
973 , d(new Private(this))
974{
975}
976
977ItemBoxContainer::~ItemBoxContainer()
978{
979 delete d;
980}
981
982int ItemBoxContainer::numSideBySide_recursive(Qt::Orientation o) const
983{
984 int num = 0;
985 if (d->m_orientation == o) {
986 // Example: Container is horizontal and we want to know how many layouted horizontally
987 for (Item *child : m_children) {
988 if (ItemBoxContainer *container = child->asBoxContainer()) {
989 num += container->numSideBySide_recursive(o);
990 } else if (!child->isPlaceholder()) {
991 num++;
992 }
993 }
994 } else {
995 // Example: Container is vertical and we want to know how many layouted horizontally
996 for (Item *child : m_children) {
997 if (ItemBoxContainer *container = child->asBoxContainer()) {
998 num = qMax(num, container->numSideBySide_recursive(o));
999 } else if (!child->isPlaceholder()) {
1000 num = qMax(num, 1);
1001 }
1002 }
1003 }
1004
1005 return num;
1006}
1007
1008bool ItemBoxContainer::checkSanity()
1009{
1010 d->m_checkSanityScheduled = false;
1011
1012 if (!hostWidget()) {
1014 return true;
1015 }
1016
1017 if (!Item::checkSanity())
1018 return false;
1019
1020 if (numChildren() == 0 && !isRoot()) {
1021 qWarning() << Q_FUNC_INFO << "Container is empty. Should be deleted";
1022 return false;
1023 }
1024
1025 if (d->m_orientation != Qt::Vertical && d->m_orientation != Qt::Horizontal) {
1026 qWarning() << Q_FUNC_INFO << "Invalid orientation" << d->m_orientation << this;
1027 return false;
1028 }
1029
1030 // Check that the geometries don't overlap
1031 int expectedPos = 0;
1032 const auto children = childItems();
1033 for (Item *item : children) {
1034 if (!item->isVisible())
1035 continue;
1036 const int pos = Layouting::pos(item->pos(), d->m_orientation);
1037 if (expectedPos != pos) {
1038 root()->dumpLayout();
1039 qWarning() << Q_FUNC_INFO << "Unexpected pos" << pos << "; expected=" << expectedPos
1040 << "; for item=" << item
1041 << "; isContainer=" << item->isContainer();
1042 return false;
1043 }
1044
1045 expectedPos = pos + Layouting::length(item->size(), d->m_orientation) + separatorThickness;
1046 }
1047
1048 const int h1 = Layouting::length(size(), oppositeOrientation(d->m_orientation));
1049 for (Item *item : children) {
1050 if (item->parentContainer() != this) {
1051 qWarning() << "Invalid parent container for" << item
1052 << "; is=" << item->parentContainer() << "; expected=" << this;
1053 return false;
1054 }
1055
1056 if (item->parent() != this) {
1057 qWarning() << "Invalid QObject parent for" << item
1058 << "; is=" << item->parent() << "; expected=" << this;
1059 return false;
1060 }
1061
1062 if (item->isVisible()) {
1063 // Check the children height (if horizontal, and vice-versa)
1064 const int h2 = Layouting::length(item->size(), oppositeOrientation(d->m_orientation));
1065 if (h1 != h2) {
1066 root()->dumpLayout();
1067 qWarning() << Q_FUNC_INFO << "Invalid size for item." << item
1068 << "Container.length=" << h1 << "; item.length=" << h2;
1069 return false;
1070 }
1071
1072 if (!rect().contains(item->geometry())) {
1073 root()->dumpLayout();
1074 qWarning() << Q_FUNC_INFO << "Item geo is out of bounds. item=" << item << "; geo="
1075 << item->geometry() << "; parent.rect=" << rect();
1076 return false;
1077 }
1078 }
1079
1080 if (!item->checkSanity())
1081 return false;
1082 }
1083
1084 const Item::List visibleChildren = this->visibleChildren();
1085 const bool isEmptyRoot = isRoot() && visibleChildren.isEmpty();
1086 if (!isEmptyRoot) {
1087 auto occupied = qMax(0, Item::separatorThickness * (visibleChildren.size() - 1));
1088 for (Item *item : visibleChildren) {
1089 occupied += item->length(d->m_orientation);
1090 }
1091
1092 if (occupied != length()) {
1093 root()->dumpLayout();
1094 qWarning() << Q_FUNC_INFO << "Unexpected length. Expected=" << occupied
1095 << "; got=" << length() << "; this=" << this;
1096 return false;
1097 }
1098
1099 const QVector<double> percentages = d->childPercentages();
1100 const double totalPercentage = std::accumulate(percentages.begin(), percentages.end(), 0.0);
1101 const double expectedPercentage = visibleChildren.isEmpty() ? 0.0 : 1.0;
1102 if (!qFuzzyCompare(totalPercentage, expectedPercentage)) {
1103 root()->dumpLayout();
1104 qWarning() << Q_FUNC_INFO << "Percentages don't add up"
1105 << totalPercentage << percentages
1106 << this;
1107 const_cast<ItemBoxContainer *>(this)->d->updateSeparators_recursive();
1108 qWarning() << Q_FUNC_INFO << d->childPercentages();
1109 return false;
1110 }
1111 }
1112
1113 const auto numVisibleChildren = visibleChildren.size();
1114 if (d->m_separators.size() != qMax(0, numVisibleChildren - 1)) {
1115 root()->dumpLayout();
1116 qWarning() << Q_FUNC_INFO << "Unexpected number of separators" << d->m_separators.size()
1117 << numVisibleChildren;
1118 return false;
1119 }
1120
1121 const QSize expectedSeparatorSize = isVertical() ? QSize(width(), Item::separatorThickness)
1122 : QSize(Item::separatorThickness, height());
1123
1124 const int pos2 = Layouting::pos(mapToRoot(QPoint(0, 0)), oppositeOrientation(d->m_orientation));
1125
1126 for (int i = 0; i < d->m_separators.size(); ++i) {
1127 Separator *separator = d->m_separators.at(i);
1128 Item *item = visibleChildren.at(i);
1129 const int expectedSeparatorPos = mapToRoot(item->m_sizingInfo.edge(d->m_orientation) + 1, d->m_orientation);
1130
1131 if (separator->host() != host()) {
1132 qWarning() << Q_FUNC_INFO << "Invalid host widget for separator"
1133 << separator->host() << host() << this;
1134 return false;
1135 }
1136
1137 if (separator->parentContainer() != this) {
1138 qWarning() << Q_FUNC_INFO << "Invalid parent container for separator"
1139 << separator->parentContainer() << separator << this;
1140 return false;
1141 }
1142
1143 if (separator->position() != expectedSeparatorPos) {
1144 root()->dumpLayout();
1145 qWarning() << Q_FUNC_INFO << "Unexpected separator position" << separator->position()
1146 << "; expected=" << expectedSeparatorPos
1147 << separator << "; this=" << this;
1148 return false;
1149 }
1150
1151 Widget *separatorWidget = separator->asWidget();
1152 if (separatorWidget->geometry().size() != expectedSeparatorSize) {
1153 qWarning() << Q_FUNC_INFO << "Unexpected separator size" << separatorWidget->geometry().size()
1154 << "; expected=" << expectedSeparatorSize
1155 << separator << "; this=" << this;
1156 return false;
1157 }
1158
1159 const int separatorPos2 = Layouting::pos(separatorWidget->geometry().topLeft(), oppositeOrientation(d->m_orientation));
1160 if (Layouting::pos(separatorWidget->geometry().topLeft(), oppositeOrientation(d->m_orientation)) != pos2) {
1161 root()->dumpLayout();
1162 qWarning() << Q_FUNC_INFO << "Unexpected position pos2=" << separatorPos2
1163 << "; expected=" << pos2
1164 << separator << "; this=" << this;
1165 return false;
1166 }
1167
1168 if (separator->host() != host()) {
1169 qWarning() << Q_FUNC_INFO << "Unexpected host widget in separator"
1170 << separator->host() << "; expected=" << host();
1171 return false;
1172 }
1173
1174 // Check that the separator bounds are correct. We can't always honour widget's max-size constraints, so only honour min-size
1175 const int separatorMinPos = minPosForSeparator_global(separator, /*honourMax=*/false);
1176 const int separatorMaxPos = maxPosForSeparator_global(separator, /*honourMax=*/false);
1177 const int separatorPos = separator->position();
1178 if (separatorPos < separatorMinPos || separatorPos > separatorMaxPos || separatorMinPos < 0 || separatorMaxPos <= 0) {
1179 root()->dumpLayout();
1180 qWarning() << Q_FUNC_INFO << "Invalid bounds for separator, pos="
1181 << separatorPos << "; min=" << separatorMinPos
1182 << "; max=" << separatorMaxPos
1183 << separator;
1184 return false;
1185 }
1186 }
1187
1188#ifdef DOCKS_DEVELOPER_MODE
1189 // Can cause slowdown, so just use it in developer mode.
1190 if (isRoot()) {
1191 if (!asBoxContainer()->test_suggestedRect())
1192 return false;
1193 }
1194#endif
1195
1196 return true;
1197}
1198
1199void ItemBoxContainer::Private::scheduleCheckSanity() const
1200{
1201 if (!m_checkSanityScheduled) {
1202 m_checkSanityScheduled = true;
1203 QTimer::singleShot(0, q->root(), &ItemBoxContainer::checkSanity);
1204 }
1205}
1206
1207bool ItemBoxContainer::hasOrientation() const
1208{
1209 return isVertical() || isHorizontal();
1210}
1211
1212int ItemBoxContainer::indexOfVisibleChild(const Item *item) const
1213{
1214 const Item::List items = visibleChildren();
1215 return items.indexOf(const_cast<Item *>(item));
1216}
1217
1218void ItemBoxContainer::restore(Item *child)
1219{
1220 restoreChild(child, NeighbourSqueezeStrategy::ImmediateNeighboursFirst);
1221}
1222
1223void ItemBoxContainer::removeItem(Item *item, bool hardRemove)
1224{
1225 Q_ASSERT(!item->isRoot());
1226
1227 if (!contains(item)) {
1228 // Not ours, ask parent
1229 item->parentContainer()->removeItem(item, hardRemove);
1230 return;
1231 }
1232
1233 Item *side1Item = visibleNeighbourFor(item, Side1);
1234 Item *side2Item = visibleNeighbourFor(item, Side2);
1235
1236 const bool isContainer = item->isContainer();
1237 const bool wasVisible = !isContainer && item->isVisible();
1238
1239 if (hardRemove) {
1240 m_children.removeOne(item);
1241 delete item;
1242 if (!isContainer)
1243 Q_EMIT root()->numItemsChanged();
1244 } else {
1245 item->setIsVisible(false);
1246 item->setGuestWidget(nullptr);
1247
1248 if (!wasVisible && !isContainer) {
1249 // Was already hidden
1250 return;
1251 }
1252 }
1253
1254 if (wasVisible) {
1255 Q_EMIT root()->numVisibleItemsChanged(root()->numVisibleChildren());
1256 }
1257
1258 if (isEmpty()) {
1259 // Empty container is useless, delete it
1260 if (auto p = parentContainer())
1261 p->removeItem(this, /*hardDelete=*/true);
1262 } else if (!hasVisibleChildren()) {
1263 if (auto p = parentContainer()) {
1264 p->removeItem(this, /*hardDelete=*/false);
1265 setGeometry(QRect());
1266 }
1267 } else {
1268 // Neighbours will occupy the space of the deleted item
1269 growNeighbours(side1Item, side2Item);
1270 Q_EMIT itemsChanged();
1271
1272 updateSizeConstraints();
1273 d->updateSeparators_recursive();
1274 }
1275}
1276
1277void ItemBoxContainer::setGeometry_recursive(QRect rect)
1278{
1279 setPos(rect.topLeft());
1280
1281 // Call resize, which is recursive and will resize the children too
1282 setSize_recursive(rect.size());
1283}
1284
1285ItemBoxContainer *ItemBoxContainer::convertChildToContainer(Item *leaf)
1286{
1287 QScopedValueRollback<bool> converting(d->m_convertingItemToContainer, true);
1288
1289 const auto index = m_children.indexOf(leaf);
1290 Q_ASSERT(index != -1);
1291 auto container = new ItemBoxContainer(hostWidget(), this);
1292 container->setParentContainer(nullptr);
1293 container->setParentContainer(this);
1294
1295 insertItem(container, index, DefaultSizeMode::NoDefaultSizeMode);
1296 m_children.removeOne(leaf);
1297 container->setGeometry(leaf->geometry());
1298 container->insertItem(leaf, Location_OnTop, DefaultSizeMode::NoDefaultSizeMode);
1299 Q_EMIT itemsChanged();
1300 d->updateSeparators_recursive();
1301
1302 return container;
1303}
1304
1306void ItemBoxContainer::insertItemRelativeTo(Item *item, Item *relativeTo,
1308{
1309 Q_ASSERT(item != relativeTo);
1310
1311 if (auto asContainer = relativeTo->asBoxContainer()) {
1312 asContainer->insertItem(item, loc, option);
1313 return;
1314 }
1315
1316 item->setIsVisible(!option.startsHidden());
1317 Q_ASSERT(!(option.startsHidden() && item->isContainer()));
1318
1319 ItemBoxContainer *parent = relativeTo->parentBoxContainer();
1320 if (!parent) {
1321 qWarning() << Q_FUNC_INFO << "This method should only be called for box containers"
1322 << item->parent();
1323 return;
1324 }
1325
1326 if (parent->hasOrientationFor(loc)) {
1327 const bool locIsSide1 = locationIsSide1(loc);
1328 auto indexInParent = parent->childItems().indexOf(relativeTo);
1329 if (!locIsSide1)
1330 indexInParent++;
1331
1332 const Qt::Orientation orientation = orientationForLocation(loc);
1333 if (orientation != parent->orientation()) {
1334 Q_ASSERT(parent->visibleChildren().size() == 1);
1335 // This is the case where the container only has one item, so it's both vertical and horizontal
1336 // Now its orientation gets defined
1337 parent->setOrientation(orientation);
1338 }
1339
1340 parent->insertItem(item, indexInParent, option);
1341 } else {
1342 ItemBoxContainer *container = parent->convertChildToContainer(relativeTo);
1343 container->insertItem(item, loc, option);
1344 }
1345}
1346
1347void ItemBoxContainer::insertItem(Item *item, Location loc,
1348 KDDockWidgets::InitialOption initialOption)
1349{
1350 Q_ASSERT(item != this);
1351 if (contains(item)) {
1352 qWarning() << Q_FUNC_INFO << "Item already exists";
1353 return;
1354 }
1355
1356 item->setIsVisible(!initialOption.startsHidden());
1357 Q_ASSERT(!(initialOption.startsHidden() && item->isContainer()));
1358
1359 const Qt::Orientation locOrientation = orientationForLocation(loc);
1360
1361 if (hasOrientationFor(loc)) {
1362 if (m_children.size() == 1) {
1363 // 2 items is the minimum to know which orientation we're layedout
1364 d->m_orientation = locOrientation;
1365 }
1366
1367 const auto index = locationIsSide1(loc) ? 0 : m_children.size();
1368 insertItem(item, index, initialOption);
1369 } else {
1370 // Inserting directly in a container ? Only if it's root.
1371 Q_ASSERT(isRoot());
1372 auto container = new ItemBoxContainer(hostWidget(), this);
1373 container->setGeometry(rect());
1374 container->setChildren(m_children, d->m_orientation);
1375 m_children.clear();
1376 setOrientation(oppositeOrientation(d->m_orientation));
1377 insertItem(container, 0, DefaultSizeMode::NoDefaultSizeMode);
1378
1379 // Now we have the correct orientation, we can insert
1380 insertItem(item, loc, initialOption);
1381
1382 if (!container->hasVisibleChildren())
1383 container->setGeometry(QRect());
1384 }
1385
1386 d->updateSeparators_recursive();
1387 d->scheduleCheckSanity();
1388}
1389
1390void ItemBoxContainer::onChildMinSizeChanged(Item *child)
1391{
1392 if (d->m_convertingItemToContainer || d->m_isDeserializing || !child->isVisible()) {
1393 // Don't bother our parents, we're converting
1394 return;
1395 }
1396
1397 updateSizeConstraints();
1398
1399 if (child->isBeingInserted())
1400 return;
1401
1402 if (numVisibleChildren() == 1 && child->isVisible()) {
1403 // The easy case. Child is alone in the layout, occupies everything.
1404 child->setGeometry(rect());
1405 updateChildPercentages();
1406 return;
1407 }
1408
1409 const QSize missingForChild = child->missingSize();
1410 if (!missingForChild.isNull()) {
1411 // Child has some growing to do. It will grow left and right equally, (and top-bottom), as needed.
1412 growItem(child, Layouting::length(missingForChild, d->m_orientation), GrowthStrategy::BothSidesEqually, NeighbourSqueezeStrategy::AllNeighbours);
1413 }
1414
1415 updateChildPercentages();
1416}
1417
1418void ItemBoxContainer::updateSizeConstraints()
1419{
1420 const QSize missingSize = this->missingSize();
1421 if (!missingSize.isNull()) {
1422 if (isRoot()) {
1423 // Resize the whole layout
1424 setSize_recursive(size() + missingSize);
1425 }
1426 }
1427
1428 // Our min-size changed, notify our parent, and so on until it reaches root()
1429 Q_EMIT minSizeChanged(this);
1430}
1431
1432void ItemBoxContainer::onChildVisibleChanged(Item *, bool visible)
1433{
1434 if (d->m_isDeserializing || isInSimplify())
1435 return;
1436
1437 const int numVisible = numVisibleChildren();
1438 if (visible && numVisible == 1) {
1439 // Child became visible and there's only 1 visible child. Meaning there were 0 visible before.
1440 Q_EMIT visibleChanged(this, true);
1441 } else if (!visible && numVisible == 0) {
1442 Q_EMIT visibleChanged(this, false);
1443 }
1444}
1445
1446QRect ItemBoxContainer::suggestedDropRect(const Item *item, const Item *relativeTo, Location loc) const
1447{
1448 // Returns the drop rect. This is the geometry used by the rubber band when you hover over an indicator.
1449 // It's calculated by copying the layout and inserting the item into the dummy/invisible copy
1450 // The we see which geometry the item got. This way the returned geometry is always what the item will get
1451 // if you drop it.
1452 // One exception is if the window doesn't have enough space and it would grow. In this case
1453 // we fall back to something reasonable
1454
1455
1456 if (relativeTo && !relativeTo->parentContainer()) {
1457 qWarning() << Q_FUNC_INFO << "No parent container";
1458 return {};
1459 }
1460
1461 if (relativeTo && relativeTo->parentContainer() != this) {
1462 qWarning() << Q_FUNC_INFO << "Called on the wrong container";
1463 return {};
1464 }
1465
1466 if (relativeTo && !relativeTo->isVisible()) {
1467 qWarning() << Q_FUNC_INFO << "relative to isn't visible";
1468 return {};
1469 }
1470
1471 if (loc == Location_None) {
1472 qWarning() << Q_FUNC_INFO << "Invalid location";
1473 return {};
1474 }
1475
1476 const QSize availableSize = root()->availableSize();
1477 const QSize minSize = item->minSize();
1478 const bool isEmpty = !root()->hasVisibleChildren();
1479 const int extraWidth = (isEmpty || locationIsVertical(loc)) ? 0 : Item::separatorThickness;
1480 const int extraHeight = (isEmpty || !locationIsVertical(loc)) ? 0 : Item::separatorThickness;
1481 const bool windowNeedsGrowing = availableSize.width() < minSize.width() + extraWidth || availableSize.height() < minSize.height() + extraHeight;
1482
1483 if (windowNeedsGrowing)
1484 return suggestedDropRectFallback(item, relativeTo, loc);
1485
1486 const QVariantMap rootSerialized = root()->toVariantMap();
1487 ItemBoxContainer rootCopy(nullptr);
1488 rootCopy.fillFromVariantMap(rootSerialized, {});
1489
1490 if (relativeTo)
1491 relativeTo = rootCopy.d->itemFromPath(relativeTo->pathFromRoot());
1492
1493 const QVariantMap itemSerialized = item->toVariantMap();
1494 auto itemCopy = new Item(nullptr);
1495 itemCopy->fillFromVariantMap(itemSerialized, {});
1496
1497 if (relativeTo) {
1498 auto r = const_cast<Item *>(relativeTo);
1499 ItemBoxContainer::insertItemRelativeTo(itemCopy, r, loc, DefaultSizeMode::FairButFloor);
1500 } else {
1501 rootCopy.insertItem(itemCopy, loc, DefaultSizeMode::FairButFloor);
1502 }
1503
1504 if (rootCopy.size() != root()->size()) {
1505 // Doesn't happen
1506 qWarning() << Q_FUNC_INFO << "The root copy grew ?!" << rootCopy.size() << root()->size()
1507 << loc;
1508 return suggestedDropRectFallback(item, relativeTo, loc);
1509 }
1510
1511 return itemCopy->mapToRoot(itemCopy->rect());
1512}
1513
1514QRect ItemBoxContainer::suggestedDropRectFallback(const Item *item, const Item *relativeTo, Location loc) const
1515{
1516 const QSize minSize = item->minSize();
1517 const int itemMin = Layouting::length(minSize, d->m_orientation);
1518 const int available = availableLength() - Item::separatorThickness;
1519 if (relativeTo) {
1520 int suggestedPos = 0;
1521 const QRect relativeToGeo = relativeTo->geometry();
1522 const int suggestedLength = relativeTo->length(orientationForLocation(loc)) / 2;
1523 switch (loc) {
1524 case Location_OnLeft:
1525 suggestedPos = relativeToGeo.x();
1526 break;
1527 case Location_OnTop:
1528 suggestedPos = relativeToGeo.y();
1529 break;
1530 case Location_OnRight:
1531 suggestedPos = relativeToGeo.right() - suggestedLength + 1;
1532 break;
1533 case Location_OnBottom:
1534 suggestedPos = relativeToGeo.bottom() - suggestedLength + 1;
1535 break;
1536 default:
1537 Q_ASSERT(false);
1538 }
1539
1540 QRect rect;
1542 rect.setTopLeft(QPoint(relativeTo->x(), suggestedPos));
1543 rect.setSize(QSize(relativeTo->width(), suggestedLength));
1544 } else {
1545 rect.setTopLeft(QPoint(suggestedPos, relativeTo->y()));
1546 rect.setSize(QSize(suggestedLength, relativeTo->height()));
1547 }
1548
1549 return mapToRoot(rect);
1550 } else if (isRoot()) {
1551 // Relative to the window itself
1552 QRect rect = this->rect();
1553 const int oneThird = length() / 3;
1554 const int suggestedLength = qMax(qMin(available, oneThird), itemMin);
1555
1556 switch (loc) {
1557 case Location_OnLeft:
1558 rect.setWidth(suggestedLength);
1559 break;
1560 case Location_OnTop:
1561 rect.setHeight(suggestedLength);
1562 break;
1563 case Location_OnRight:
1564 rect.adjust(rect.width() - suggestedLength, 0, 0, 0);
1565 break;
1566 case Location_OnBottom:
1567 rect.adjust(0, rect.bottom() - suggestedLength, 0, 0);
1568 break;
1569 case Location_None:
1570 return {};
1571 }
1572
1573 return rect;
1574
1575 } else {
1576 qWarning() << Q_FUNC_INFO << "Shouldn't happen";
1577 }
1578
1579 return {};
1580}
1581
1582void ItemBoxContainer::positionItems()
1583{
1584 SizingInfo::List sizes = this->sizes();
1585 positionItems(/*by-ref=*/sizes);
1586 applyPositions(sizes);
1587
1588 d->updateSeparators_recursive();
1589}
1590
1591void ItemBoxContainer::positionItems_recursive()
1592{
1593 positionItems();
1594 for (Item *item : qAsConst(m_children)) {
1595 if (item->isVisible()) {
1596 if (auto c = item->asBoxContainer())
1597 c->positionItems_recursive();
1598 }
1599 }
1600}
1601
1602void ItemBoxContainer::applyPositions(const SizingInfo::List &sizes)
1603{
1604 const Item::List items = visibleChildren();
1605 const auto count = items.size();
1606 Q_ASSERT(count == sizes.size());
1607 for (int i = 0; i < count; ++i) {
1608 Item *item = items.at(i);
1609 const SizingInfo &sizing = sizes[i];
1610 if (sizing.isBeingInserted) {
1611 continue;
1612 }
1613
1615 // If the layout is horizontal, the item will have the height of the container. And vice-versa
1616 item->setLength_recursive(sizing.length(oppositeOrientation), oppositeOrientation);
1617
1618 item->setPos(sizing.geometry.topLeft());
1619 }
1620}
1621
1622Qt::Orientation ItemBoxContainer::orientation() const
1623{
1624 return d->m_orientation;
1625}
1626
1627void ItemBoxContainer::positionItems(SizingInfo::List &sizes)
1628{
1629 int nextPos = 0;
1630 const auto count = sizes.count();
1632 for (auto i = 0; i < count; ++i) {
1633 SizingInfo &sizing = sizes[i];
1634 if (sizing.isBeingInserted) {
1635 nextPos += Item::separatorThickness;
1636 continue;
1637 }
1638
1639 // If the layout is horizontal, the item will have the height of the container. And vice-versa
1640 const int oppositeLength = Layouting::length(size(), oppositeOrientation);
1641 sizing.setLength(oppositeLength, oppositeOrientation);
1642 sizing.setPos(0, oppositeOrientation);
1643
1644 sizing.setPos(nextPos, d->m_orientation);
1645 nextPos += sizing.length(d->m_orientation) + Item::separatorThickness;
1646 }
1647}
1648
1649void ItemBoxContainer::clear()
1650{
1651 for (Item *item : qAsConst(m_children)) {
1652 if (ItemBoxContainer *container = item->asBoxContainer())
1653 container->clear();
1654
1655 delete item;
1656 }
1657 m_children.clear();
1658 d->deleteSeparators();
1659}
1660
1661Item *ItemBoxContainer::itemAt(QPoint p) const
1662{
1663 for (Item *item : qAsConst(m_children)) {
1664 if (item->isVisible() && item->geometry().contains(p))
1665 return item;
1666 }
1667
1668 return nullptr;
1669}
1670
1671Item *ItemBoxContainer::itemAt_recursive(QPoint p) const
1672{
1673 if (Item *item = itemAt(p)) {
1674 if (auto c = item->asBoxContainer()) {
1675 return c->itemAt_recursive(c->mapFromParent(p));
1676 } else {
1677 return item;
1678 }
1679 }
1680
1681 return nullptr;
1682}
1683
1684void ItemBoxContainer::setHostWidget(Widget *host)
1685{
1686 Item::setHostWidget(host);
1687 d->deleteSeparators_recursive();
1688 for (Item *item : qAsConst(m_children)) {
1689 item->setHostWidget(host);
1690 }
1691
1692 d->updateSeparators_recursive();
1693}
1694
1695void ItemBoxContainer::setIsVisible(bool)
1696{
1697 // no-op for containers, visibility is calculated
1698}
1699
1700bool ItemBoxContainer::isVisible(bool excludeBeingInserted) const
1701{
1702 return hasVisibleChildren(excludeBeingInserted);
1703}
1704
1705void ItemBoxContainer::setLength_recursive(int length, Qt::Orientation o)
1706{
1707 QSize sz = size();
1708 if (o == Qt::Vertical) {
1709 sz.setHeight(length);
1710 } else {
1711 sz.setWidth(length);
1712 }
1713
1714 setSize_recursive(sz);
1715}
1716
1717void ItemBoxContainer::insertItem(Item *item, int index, InitialOption option)
1718{
1719 if (option.sizeMode != DefaultSizeMode::NoDefaultSizeMode) {
1721 const int suggestedLength = d->defaultLengthFor(item, option);
1722 item->setLength_recursive(suggestedLength, d->m_orientation);
1723 }
1724
1725 m_children.insert(index, item);
1726 item->setParentContainer(this);
1727
1728 Q_EMIT itemsChanged();
1729
1730 if (!d->m_convertingItemToContainer && item->isVisible())
1731 restoreChild(item);
1732
1733 const bool shouldEmitVisibleChanged = item->isVisible();
1734
1735 if (!d->m_convertingItemToContainer && !s_inhibitSimplify)
1736 simplify();
1737
1738 if (shouldEmitVisibleChanged)
1739 Q_EMIT root()->numVisibleItemsChanged(root()->numVisibleChildren());
1740 Q_EMIT root()->numItemsChanged();
1741}
1742
1743bool ItemBoxContainer::hasOrientationFor(Location loc) const
1744{
1745 if (m_children.size() <= 1)
1746 return true;
1747
1748 return d->m_orientation == orientationForLocation(loc);
1749}
1750
1751int ItemBoxContainer::usableLength() const
1752{
1753 const Item::List children = visibleChildren();
1754 const auto numVisibleChildren = children.size();
1755
1756 if (children.size() <= 1)
1757 return Layouting::length(size(), d->m_orientation);
1758
1759 const int separatorWaste = separatorThickness * (numVisibleChildren - 1);
1760 return length() - separatorWaste;
1761}
1762
1763void ItemBoxContainer::setChildren(const List &children, Qt::Orientation o)
1764{
1765 m_children = children;
1766 for (Item *item : children)
1767 item->setParentContainer(this);
1768
1769 setOrientation(o);
1770}
1771
1772void ItemBoxContainer::setOrientation(Qt::Orientation o)
1773{
1774 if (o != d->m_orientation) {
1775 d->m_orientation = o;
1776 d->updateSeparators_recursive();
1777 }
1778}
1779
1780QSize ItemBoxContainer::Private::minSize(const Item::List &items) const
1781{
1782 int minW = 0;
1783 int minH = 0;
1784 int numVisible = 0;
1785 if (!q->m_children.isEmpty()) {
1786 for (Item *item : items) {
1787 if (!(item->isVisible() || item->isBeingInserted()))
1788 continue;
1789 numVisible++;
1790 if (q->isVertical()) {
1791 minW = qMax(minW, item->minSize().width());
1792 minH += item->minSize().height();
1793 } else {
1794 minH = qMax(minH, item->minSize().height());
1795 minW += item->minSize().width();
1796 }
1797 }
1798
1799 const int separatorWaste = qMax(0, (numVisible - 1) * separatorThickness);
1800 if (q->isVertical())
1801 minH += separatorWaste;
1802 else
1803 minW += separatorWaste;
1804 }
1805
1806 return QSize(minW, minH);
1807}
1808
1809QSize ItemBoxContainer::minSize() const
1810{
1811 return d->minSize(m_children);
1812}
1813
1814QSize ItemBoxContainer::maxSizeHint() const
1815{
1816 int maxW = isVertical() ? hardcodedMaximumSize.width() : 0;
1817 int maxH = isVertical() ? 0 : hardcodedMaximumSize.height();
1818
1819 const Item::List visibleChildren = this->visibleChildren(/*includeBeingInserted=*/false);
1820 if (!visibleChildren.isEmpty()) {
1821 for (Item *item : visibleChildren) {
1822 if (item->isBeingInserted())
1823 continue;
1824 const QSize itemMaxSz = item->maxSizeHint();
1825 const int itemMaxWidth = itemMaxSz.width();
1826 const int itemMaxHeight = itemMaxSz.height();
1827 if (isVertical()) {
1828 maxW = qMin(maxW, itemMaxWidth);
1829 maxH = qMin(maxH + itemMaxHeight, hardcodedMaximumSize.height());
1830 } else {
1831 maxH = qMin(maxH, itemMaxHeight);
1832 maxW = qMin(maxW + itemMaxWidth, hardcodedMaximumSize.width());
1833 }
1834 }
1835
1836 const auto separatorWaste = (visibleChildren.size() - 1) * separatorThickness;
1837 if (isVertical()) {
1838 maxH = qMin(maxH + separatorWaste, hardcodedMaximumSize.height());
1839 } else {
1840 maxW = qMin(maxW + separatorWaste, hardcodedMaximumSize.width());
1841 }
1842 }
1843
1844 if (maxW == 0)
1845 maxW = hardcodedMaximumSize.width();
1846
1847 if (maxH == 0)
1848 maxH = hardcodedMaximumSize.height();
1849
1850 return QSize(maxW, maxH).expandedTo(d->minSize(visibleChildren));
1851}
1852
1853void ItemBoxContainer::Private::resizeChildren(QSize oldSize, QSize newSize, SizingInfo::List &childSizes,
1854 ChildrenResizeStrategy strategy)
1855{
1856 // This container is being resized to @p newSize, so we must resize our children too, based
1857 // on @p strategy.
1858 // The new sizes are applied to @p childSizes, which will be applied to the widgets when we're done
1859
1860 const QVector<double> childPercentages = this->childPercentages();
1861 const auto count = childSizes.count();
1862 const bool widthChanged = oldSize.width() != newSize.width();
1863 const bool heightChanged = oldSize.height() != newSize.height();
1864 const bool lengthChanged = (q->isVertical() && heightChanged) || (q->isHorizontal() && widthChanged);
1865 const int totalNewLength = q->usableLength();
1866
1867 if (strategy == ChildrenResizeStrategy::Percentage) {
1868 // In this strategy mode, each children will preserve its current relative size. So, if a child
1869 // is occupying 50% of this container, then it will still occupy that after the container resize
1870
1871 int remaining = totalNewLength;
1872 for (int i = 0; i < count; ++i) {
1873 const bool isLast = i == count - 1;
1874
1875 SizingInfo &itemSize = childSizes[i];
1876
1877 const qreal childPercentage = childPercentages.at(i);
1878 const int newItemLength = lengthChanged ? (isLast ? remaining
1879 : int(childPercentage * totalNewLength))
1880 : itemSize.length(m_orientation);
1881
1882 if (newItemLength <= 0) {
1883 q->root()->dumpLayout();
1884 qWarning() << Q_FUNC_INFO << "Invalid resize newItemLength=" << newItemLength;
1885 Q_ASSERT(false);
1886 return;
1887 }
1888
1889 remaining = remaining - newItemLength;
1890
1891 if (q->isVertical()) {
1892 itemSize.geometry.setSize({ q->width(), newItemLength });
1893 } else {
1894 itemSize.geometry.setSize({ newItemLength, q->height() });
1895 }
1896 }
1897 } else if (strategy == ChildrenResizeStrategy::Side1SeparatorMove || strategy == ChildrenResizeStrategy::Side2SeparatorMove) {
1898 int remaining = Layouting::length(newSize - oldSize, m_orientation); // This is how much we need to give to children (when growing the container), or to take from them when shrinking the container
1899 const bool isGrowing = remaining > 0;
1900 remaining = qAbs(remaining); // Easier to deal in positive numbers
1901
1902 // We're resizing the container, and need to decide if we start resizing the 1st children or in reverse order.
1903 // If the separator is being dragged left or top, then isSide1SeparatorMove is true.
1904 // If isSide1SeparatorMove is true and we're growing, then it means this container is on the right/bottom of the separator,
1905 // so should resize its first children first. Same logic for the other 3 cases
1906
1907 const bool isSide1SeparatorMove = strategy == ChildrenResizeStrategy::Side1SeparatorMove;
1908 bool resizeHeadFirst = false;
1909 if (isGrowing && isSide1SeparatorMove) {
1910 resizeHeadFirst = true;
1911 } else if (isGrowing && !isSide1SeparatorMove) {
1912 resizeHeadFirst = false;
1913 } else if (!isGrowing && isSide1SeparatorMove) {
1914 resizeHeadFirst = false;
1915 } else if (!isGrowing && !isSide1SeparatorMove) {
1916 resizeHeadFirst = true;
1917 }
1918
1919 for (int i = 0; i < count; i++) {
1920 const auto index = resizeHeadFirst ? i : count - 1 - i;
1921
1922 SizingInfo &size = childSizes[index];
1923
1924 if (isGrowing) {
1925 // Since we don't honour item max-size yet, it can just grow all it wants
1926 size.incrementLength(remaining, m_orientation);
1927 remaining = 0; // and we're done, the first one got everything
1928 } else {
1929 const int availableToGive = size.availableLength(m_orientation);
1930 const int took = qMin(availableToGive, remaining);
1931 size.incrementLength(-took, m_orientation);
1932 remaining -= took;
1933 }
1934
1935 if (remaining == 0)
1936 break;
1937 }
1938 }
1939 honourMaxSizes(childSizes);
1940}
1941
1942void ItemBoxContainer::Private::honourMaxSizes(SizingInfo::List &sizes)
1943{
1944 // Reduces the size of all children that are bigger than max-size.
1945 // Assuming there's widgets that are willing to grow to occupy that space.
1946
1947 int amountNeededToShrink = 0;
1948 int amountAvailableToGrow = 0;
1949 QVector<int> indexesOfShrinkers;
1950 QVector<int> indexesOfGrowers;
1951
1952 for (int i = 0; i < sizes.count(); ++i) {
1953 SizingInfo &info = sizes[i];
1954 const int neededToShrink = info.neededToShrink(m_orientation);
1955 const int availableToGrow = info.availableToGrow(m_orientation);
1956
1957 if (neededToShrink > 0) {
1958 amountNeededToShrink += neededToShrink;
1959 indexesOfShrinkers.push_back(i); // clazy:exclude=reserve-candidates
1960 } else if (availableToGrow > 0) {
1961 amountAvailableToGrow = qMin(amountAvailableToGrow + availableToGrow, q->length());
1962 indexesOfGrowers.push_back(i); // clazy:exclude=reserve-candidates
1963 }
1964 }
1965
1966 // Don't grow more than what's needed
1967 amountAvailableToGrow = qMin(amountNeededToShrink, amountAvailableToGrow);
1968
1969 // Don't shrink more than what's available to grow
1970 amountNeededToShrink = qMin(amountAvailableToGrow, amountNeededToShrink);
1971
1972 if (amountNeededToShrink == 0 || amountAvailableToGrow == 0)
1973 return;
1974
1975 // We gathered who needs to shrink and who can grow, now try to do it evenly so that all
1976 // growers participate, and not just one giving everything.
1977
1978 // Do the growing:
1979 while (amountAvailableToGrow > 0) {
1980 // Each grower will grow a bit (round-robin)
1981 auto toGrow = qMax(1, amountAvailableToGrow / indexesOfGrowers.size());
1982
1983 for (auto it = indexesOfGrowers.begin(); it != indexesOfGrowers.end();) {
1984 const int index = *it;
1985 SizingInfo &sizing = sizes[index];
1986 const auto grew = qMin(sizing.availableToGrow(m_orientation), toGrow);
1987 sizing.incrementLength(grew, m_orientation);
1988 amountAvailableToGrow -= grew;
1989
1990 if (amountAvailableToGrow == 0) {
1991 // We're done growing
1992 break;
1993 }
1994
1995 if (sizing.availableToGrow(m_orientation) == 0) {
1996 // It's no longer a grower
1997 it = indexesOfGrowers.erase(it);
1998 } else {
1999 it++;
2000 }
2001 }
2002 }
2003
2004 // Do the shrinking:
2005 while (amountNeededToShrink > 0) {
2006 // Each shrinker will shrink a bit (round-robin)
2007 auto toShrink = qMax(1, amountNeededToShrink / indexesOfShrinkers.size());
2008
2009 for (auto it = indexesOfShrinkers.begin(); it != indexesOfShrinkers.end();) {
2010 const int index = *it;
2011 SizingInfo &sizing = sizes[index];
2012 const auto shrunk = qMin(sizing.neededToShrink(m_orientation), toShrink);
2013 sizing.incrementLength(-shrunk, m_orientation);
2014 amountNeededToShrink -= shrunk;
2015
2016 if (amountNeededToShrink == 0) {
2017 // We're done shrinking
2018 break;
2019 }
2020
2021 if (sizing.neededToShrink(m_orientation) == 0) {
2022 // It's no longer a shrinker
2023 it = indexesOfShrinkers.erase(it);
2024 } else {
2025 it++;
2026 }
2027 }
2028 }
2029}
2030
2031void ItemBoxContainer::setSize_recursive(QSize newSize, ChildrenResizeStrategy strategy)
2032{
2033 QScopedValueRollback<bool> block(d->m_blockUpdatePercentages, true);
2034
2035 const QSize minSize = this->minSize();
2036 if (newSize.width() < minSize.width() || newSize.height() < minSize.height()) {
2037 root()->dumpLayout();
2038 qWarning() << Q_FUNC_INFO << "New size doesn't respect size constraints"
2039 << "; new=" << newSize
2040 << "; min=" << minSize
2041 << this;
2042 return;
2043 }
2044 if (newSize == size())
2045 return;
2046
2047 const QSize oldSize = size();
2048 setSize(newSize);
2049
2050 const Item::List children = visibleChildren();
2051 const auto count = children.size();
2052 SizingInfo::List childSizes = sizes();
2053
2054 // #1 Since we changed size, also resize out children.
2055 // But apply them to our SizingInfo::List first before setting actual Item/QWidget geometries
2056 // Because we need step #2 where we ensure min sizes for each item are respected. We could
2057 // calculate and do everything in a single-step, but we already have the code for #2 in growItem()
2058 // so doing it in 2 steps will reuse much logic.
2059
2060
2061 // the sizes:
2062 d->resizeChildren(oldSize, newSize, /*by-ref*/ childSizes, strategy);
2063
2064 // the positions:
2065 positionItems(/*by-ref*/ childSizes);
2066
2067 // #2 Adjust sizes so that each item has at least Item::minSize.
2068 for (int i = 0; i < count; ++i) {
2069 SizingInfo &size = childSizes[i];
2070 const int missing = size.missingLength(d->m_orientation);
2071 if (missing > 0)
2072 growItem(i, childSizes, missing, GrowthStrategy::BothSidesEqually, NeighbourSqueezeStrategy::AllNeighbours);
2073 }
2074
2075 // #3 Sizes are now correct and honour min/max sizes. So apply them to our Items
2076 applyGeometries(childSizes, strategy);
2077}
2078
2079int ItemBoxContainer::length() const
2080{
2081 return isVertical() ? height() : width();
2082}
2083
2084void ItemBoxContainer::dumpLayout(int level)
2085{
2086 if (level == 0 && hostWidget()) {
2087
2088 const auto screens = qApp->screens();
2089 for (auto screen : screens) {
2090 qDebug().noquote() << "Screen" << screen->geometry() << screen->availableGeometry()
2091 << "; drp=" << screen->devicePixelRatio();
2092 }
2093
2094 hostWidget()->dumpDebug(qDebug().noquote());
2095 }
2096
2097 QString indent;
2098 indent.fill(QLatin1Char(' '), level);
2099 const QString beingInserted = m_sizingInfo.isBeingInserted ? QStringLiteral("; beingInserted;")
2100 : QString();
2101 const QString visible = !isVisible() ? QStringLiteral(";hidden;")
2102 : QString();
2103
2104 const QString typeStr = isRoot() ? QStringLiteral("* Root: ")
2105 : QStringLiteral("* Layout: ");
2106
2107 {
2108 auto dbg = qDebug().noquote();
2109 dbg << indent << typeStr << d->m_orientation
2110 << m_sizingInfo.geometry /*<< "r=" << m_geometry.right() << "b=" << m_geometry.bottom()*/
2111 << "; min=" << minSize()
2112 << "; this=" << this << beingInserted << visible
2113 << "; %=" << d->childPercentages();
2114
2115 if (maxSizeHint() != Item::hardcodedMaximumSize)
2116 dbg << "; max=" << maxSizeHint();
2117 }
2118
2119 int i = 0;
2120 for (Item *item : qAsConst(m_children)) {
2121 item->dumpLayout(level + 1);
2122 if (item->isVisible()) {
2123 if (i < d->m_separators.size()) {
2124 auto separator = d->m_separators.at(i);
2125 qDebug().noquote() << indent << " - Separator: "
2126 << "local.geo=" << mapFromRoot(separator->asWidget()->geometry())
2127 << "global.geo=" << separator->asWidget()->geometry()
2128 << separator;
2129 }
2130 ++i;
2131 }
2132 }
2133}
2134
2135void ItemBoxContainer::updateChildPercentages()
2136{
2137 if (root()->d->m_blockUpdatePercentages)
2138 return;
2139
2140 const int usable = usableLength();
2141 for (Item *item : qAsConst(m_children)) {
2142 if (item->isVisible() && !item->isBeingInserted()) {
2143 item->m_sizingInfo.percentageWithinParent = (1.0 * item->length(d->m_orientation)) / usable;
2144 } else {
2145 item->m_sizingInfo.percentageWithinParent = 0.0;
2146 }
2147 }
2148}
2149
2150void ItemBoxContainer::updateChildPercentages_recursive()
2151{
2152 updateChildPercentages();
2153 for (Item *item : qAsConst(m_children)) {
2154 if (auto c = item->asBoxContainer())
2155 c->updateChildPercentages_recursive();
2156 }
2157}
2158
2159QVector<double> ItemBoxContainer::Private::childPercentages() const
2160{
2161 QVector<double> percentages;
2162 percentages.reserve(q->m_children.size());
2163
2164 for (Item *item : qAsConst(q->m_children)) {
2165 if (item->isVisible() && !item->isBeingInserted())
2166 percentages << item->m_sizingInfo.percentageWithinParent;
2167 }
2168
2169 return percentages;
2170}
2171
2172void ItemBoxContainer::restoreChild(Item *item, NeighbourSqueezeStrategy neighbourSqueezeStrategy)
2173{
2174 Q_ASSERT(contains(item));
2175
2176 const bool hadVisibleChildren = hasVisibleChildren(/*excludeBeingInserted=*/true);
2177
2178 item->setIsVisible(true);
2179 item->setBeingInserted(true);
2180
2181 const int excessLength = d->excessLength();
2182
2183 if (!hadVisibleChildren) {
2184 // This container was hidden and will now be restored too, since a child was restored
2185 if (auto c = parentBoxContainer()) {
2186 setSize(item->size()); // give it a decent size. Same size as the item being restored makes sense
2187 c->restoreChild(this, neighbourSqueezeStrategy);
2188 }
2189 }
2190
2191 // Make sure root() is big enough to respect all item's min-sizes
2192 updateSizeConstraints();
2193
2194 item->setBeingInserted(false);
2195
2196 if (numVisibleChildren() == 1) {
2197 // The easy case. Child is alone in the layout, occupies everything.
2198 item->setGeometry_recursive(rect());
2199 d->updateSeparators_recursive();
2200 return;
2201 }
2202
2203 const int available = availableToSqueezeOnSide(item, Side1) + availableToSqueezeOnSide(item, Side2) - Item::separatorThickness;
2204
2205 const int max = qMin(available, item->maxLengthHint(d->m_orientation));
2206 const int min = item->minLength(d->m_orientation);
2207
2208 /*
2209 * Regarding the excessLength:
2210 * The layout bigger than its own max-size. The new item will get more (if it can), to counter that excess.
2211 * There's just 1 case where we have excess length: A layout with items with max-size, but the layout can't be smaller due to min-size constraints of the higher level layouts, in the nesting hierarchy.
2212 * The excess goes away when inserting a widget that can grow indefinitely, it eats all the current excess.
2213 */
2214 const int proposed = qMax(Layouting::length(item->size(), d->m_orientation), excessLength - Item::separatorThickness);
2215 const int newLength = qBound(min, proposed, max);
2216
2217 Q_ASSERT(item->isVisible());
2218
2219 // growItem() will make it grow by the same amount it steals from the neighbours, so we can't start the growing without zeroing it
2220 if (isVertical()) {
2221 item->m_sizingInfo.geometry.setHeight(0);
2222 } else {
2223 item->m_sizingInfo.geometry.setWidth(0);
2224 }
2225
2226 growItem(item, newLength, GrowthStrategy::BothSidesEqually, neighbourSqueezeStrategy, /*accountForNewSeparator=*/true);
2227 d->updateSeparators_recursive();
2228}
2229
2230void ItemBoxContainer::updateWidgetGeometries()
2231{
2232 for (Item *item : qAsConst(m_children))
2233 item->updateWidgetGeometries();
2234}
2235
2236int ItemBoxContainer::oppositeLength() const
2237{
2238 return isVertical() ? width()
2239 : height();
2240}
2241
2242void ItemBoxContainer::requestSeparatorMove(Separator *separator, int delta)
2243{
2244 const auto separatorIndex = d->m_separators.indexOf(separator);
2245 if (separatorIndex == -1) {
2246 // Doesn't happen
2247 qWarning() << Q_FUNC_INFO << "Unknown separator" << separator << this;
2248 root()->dumpLayout();
2249 return;
2250 }
2251
2252 if (delta == 0)
2253 return;
2254
2255 const int min = minPosForSeparator_global(separator);
2256 const int pos = separator->position();
2257 const int max = maxPosForSeparator_global(separator);
2258
2259 if ((pos + delta < min && delta < 0) || // pos can be smaller than min, as long as we're making the distane to minPos smaller, same for max.
2260 (pos + delta > max && delta > 0)) { // pos can be bigger than max already and going left/up (negative delta, which is fine), just don't increase if further
2261 root()->dumpLayout();
2262 qWarning() << "Separator would have gone out of bounds"
2263 << "; separators=" << separator
2264 << "; min=" << min << "; pos=" << pos
2265 << "; max=" << max << "; delta=" << delta;
2266 return;
2267 }
2268
2269 const Side moveDirection = delta < 0 ? Side1 : Side2;
2270 const Item::List children = visibleChildren();
2271 if (children.size() <= separatorIndex) {
2272 // Doesn't happen
2273 qWarning() << Q_FUNC_INFO << "Not enough children for separator index" << separator
2274 << this << separatorIndex;
2275 root()->dumpLayout();
2276 return;
2277 }
2278
2279 int remainingToTake = qAbs(delta);
2280 int tookLocally = 0;
2281
2282 Item *side1Neighbour = children[separatorIndex];
2283 Item *side2Neighbour = children[separatorIndex + 1];
2284
2285 Side nextSeparatorDirection = moveDirection;
2286
2287 if (moveDirection == Side1) {
2288 // Separator is moving left (or top if horizontal)
2289 const int availableSqueeze1 = availableToSqueezeOnSide(side2Neighbour, Side1);
2290 const int availableGrow2 = availableToGrowOnSide(side1Neighbour, Side2);
2291
2292 // This is the available within our container, which we can use without bothering other separators
2293 tookLocally = qMin(availableSqueeze1, remainingToTake);
2294 tookLocally = qMin(tookLocally, availableGrow2);
2295
2296 if (tookLocally != 0) {
2297 growItem(side2Neighbour, tookLocally, GrowthStrategy::Side1Only,
2298 NeighbourSqueezeStrategy::ImmediateNeighboursFirst, false,
2299 ChildrenResizeStrategy::Side1SeparatorMove);
2300 }
2301
2302 if (availableGrow2 == tookLocally)
2303 nextSeparatorDirection = Side2;
2304
2305 } else {
2306
2307 const int availableSqueeze2 = availableToSqueezeOnSide(side1Neighbour, Side2);
2308 const int availableGrow1 = availableToGrowOnSide(side2Neighbour, Side1);
2309
2310 // Separator is moving right (or bottom if horizontal)
2311 tookLocally = qMin(availableSqueeze2, remainingToTake);
2312 tookLocally = qMin(tookLocally, availableGrow1);
2313
2314 if (tookLocally != 0) {
2315 growItem(side1Neighbour, tookLocally, GrowthStrategy::Side2Only,
2316 NeighbourSqueezeStrategy::ImmediateNeighboursFirst, false,
2317 ChildrenResizeStrategy::Side2SeparatorMove);
2318 }
2319
2320 if (availableGrow1 == tookLocally)
2321 nextSeparatorDirection = Side1;
2322 }
2323
2324 remainingToTake -= tookLocally;
2325
2326 if (remainingToTake > 0) {
2327 // Go up the hierarchy and move the next separator on the left
2328 if (Q_UNLIKELY(isRoot())) {
2329 // Doesn't happen
2330 qWarning() << Q_FUNC_INFO << "Not enough space to move separator"
2331 << this;
2332 } else {
2333 Separator *nextSeparator = parentBoxContainer()->d->neighbourSeparator_recursive(this, nextSeparatorDirection, d->m_orientation);
2334 if (!nextSeparator) {
2335 // Doesn't happen
2336 qWarning() << Q_FUNC_INFO << "nextSeparator is null, report a bug";
2337 return;
2338 }
2339
2340 // nextSeparator might not belong to parentContainer(), due to different orientation
2341 const int remainingDelta = moveDirection == Side1 ? -remainingToTake : remainingToTake;
2342 nextSeparator->parentContainer()->requestSeparatorMove(nextSeparator, remainingDelta);
2343 }
2344 }
2345}
2346
2347void ItemBoxContainer::requestEqualSize(Separator *separator)
2348{
2349 const auto separatorIndex = d->m_separators.indexOf(separator);
2350 if (separatorIndex == -1) {
2351 // Doesn't happen
2352 qWarning() << Q_FUNC_INFO << "Separator not found" << separator;
2353 return;
2354 }
2355
2356 const Item::List children = visibleChildren();
2357 Item *side1Item = children.at(separatorIndex);
2358 Item *side2Item = children.at(separatorIndex + 1);
2359
2360 const int length1 = side1Item->length(d->m_orientation);
2361 const int length2 = side2Item->length(d->m_orientation);
2362
2363 if (qAbs(length1 - length2) <= 1) {
2364 // items already have the same length, nothing to do.
2365 // We allow for a difference of 1px, since you can't split that.
2366
2367 // But if at least 1 item is bigger than its max-size, don't bail out early, as then they don't deserve equal sizes.
2368 if (!(side1Item->m_sizingInfo.isPastMax(d->m_orientation) || side2Item->m_sizingInfo.isPastMax(d->m_orientation))) {
2369 return;
2370 }
2371 }
2372
2373 const int newLength = (length1 + length2) / 2;
2374
2375 int delta = 0;
2376 if (length1 < newLength) {
2377 // Let's move separator to the right
2378 delta = newLength - length1;
2379 } else if (length2 < newLength) {
2380 // or left.
2381 delta = -(newLength - length2); // negative, since separator is going left
2382 }
2383
2384 // Do some bounds checking, to respect min-size and max-size
2385 const int min = minPosForSeparator_global(separator, true);
2386 const int max = maxPosForSeparator_global(separator, true);
2387 const int newPos = qBound(min, separator->position() + delta, max);
2388
2389 // correct the delta
2390 delta = newPos - separator->position();
2391
2392 if (delta != 0)
2393 requestSeparatorMove(separator, delta);
2394}
2395
2396void ItemBoxContainer::layoutEqually()
2397{
2398 SizingInfo::List childSizes = sizes();
2399 if (!childSizes.isEmpty()) {
2400 layoutEqually(childSizes);
2401 applyGeometries(childSizes);
2402 }
2403}
2404
2405void ItemBoxContainer::layoutEqually(SizingInfo::List &sizes)
2406{
2407 const auto numItems = sizes.count();
2408 QVector<int> satisfiedIndexes;
2409 satisfiedIndexes.reserve(numItems);
2410
2411 auto lengthToGive = length() - (d->m_separators.size() * Item::separatorThickness);
2412
2413 // clear the sizes before we start distributing
2414 for (SizingInfo &size : sizes) {
2415 size.setLength(0, d->m_orientation);
2416 }
2417
2418 while (satisfiedIndexes.count() < sizes.count()) {
2419 const auto remainingItems = sizes.count() - satisfiedIndexes.count();
2420 auto suggestedToGive = qMax(1, lengthToGive / remainingItems);
2421 const auto oldLengthToGive = lengthToGive;
2422
2423 for (int i = 0; i < numItems; ++i) {
2424 if (satisfiedIndexes.contains(i))
2425 continue;
2426
2427 SizingInfo &size = sizes[i];
2428 if (size.availableToGrow(d->m_orientation) <= 0) {
2429 // Was already satisfied from the beginning
2430 satisfiedIndexes.push_back(i);
2431 continue;
2432 }
2433
2434 // Bound the max length. Our max can't be bigger than the remaining space.
2435 // The layout's min length minus our own min length is the amount of space that we
2436 // need to guarantee. We can't go larger and overwrite that
2437
2438 const auto othersMissing = // The size that the others are missing to satisfy their
2439 // minimum length
2440 std::accumulate(sizes.constBegin(), sizes.constEnd(), 0,
2441 [this](size_t sum, const SizingInfo &sz) {
2442 return int(sum) + sz.missingLength(d->m_orientation);
2443 })
2444 - size.missingLength(d->m_orientation);
2445
2446 const auto maxLength =
2447 qMin(size.length(d->m_orientation) + lengthToGive - othersMissing,
2448 size.maxLengthHint(d->m_orientation));
2449
2450 const auto newItemLenght =
2451 qBound(size.minLength(d->m_orientation),
2452 size.length(d->m_orientation) + suggestedToGive, maxLength);
2453 const auto toGive = newItemLenght - size.length(d->m_orientation);
2454
2455 if (toGive == 0) {
2456 Q_ASSERT(false);
2457 satisfiedIndexes.push_back(i);
2458 } else {
2459 lengthToGive -= toGive;
2460 size.incrementLength(toGive, d->m_orientation);
2461 if (size.availableToGrow(d->m_orientation) <= 0) {
2462 satisfiedIndexes.push_back(i);
2463 }
2464 if (lengthToGive == 0)
2465 return;
2466
2467 if (lengthToGive < 0) {
2468 qWarning() << Q_FUNC_INFO << "Breaking infinite loop";
2469 return;
2470 }
2471 }
2472 }
2473
2474 if (oldLengthToGive == lengthToGive) {
2475 // Nothing happened, we can't satisfy more items, due to min/max constraints
2476 return;
2477 }
2478 }
2479}
2480
2481void ItemBoxContainer::layoutEqually_recursive()
2482{
2483 layoutEqually();
2484 for (Item *item : qAsConst(m_children)) {
2485 if (item->isVisible()) {
2486 if (auto c = item->asBoxContainer())
2487 c->layoutEqually_recursive();
2488 }
2489 }
2490}
2491
2492Item *ItemBoxContainer::visibleNeighbourFor(const Item *item, Side side) const
2493{
2494 // Item might not be visible, so use m_children instead of visibleChildren()
2495 const auto index = m_children.indexOf(const_cast<Item *>(item));
2496
2497 if (side == Side1) {
2498 for (auto i = index - 1; i >= 0; --i) {
2499 Item *child = m_children.at(i);
2500 if (child->isVisible())
2501 return child;
2502 }
2503 } else {
2504 for (auto i = index + 1; i < m_children.size(); ++i) {
2505 Item *child = m_children.at(i);
2506 if (child->isVisible())
2507 return child;
2508 }
2509 }
2510
2511 return nullptr;
2512}
2513
2514QSize ItemBoxContainer::availableSize() const
2515{
2516 return size() - this->minSize();
2517}
2518
2519int ItemBoxContainer::availableLength() const
2520{
2521 return isVertical() ? availableSize().height()
2522 : availableSize().width();
2523}
2524
2525LengthOnSide ItemBoxContainer::lengthOnSide(const SizingInfo::List &sizes, int fromIndex,
2526 Side side, Qt::Orientation o) const
2527{
2528 if (fromIndex < 0)
2529 return {};
2530
2531 const auto count = sizes.count();
2532 if (fromIndex >= count)
2533 return {};
2534
2535 int start = 0;
2536 int end = -1;
2537 if (side == Side1) {
2538 start = 0;
2539 end = fromIndex;
2540 } else {
2541 start = fromIndex;
2542 end = count - 1;
2543 }
2544
2545 LengthOnSide result;
2546 for (int i = start; i <= end; ++i) {
2547 const SizingInfo &size = sizes.at(i);
2548 result.length += size.length(o);
2549 result.minLength += size.minLength(o);
2550 }
2551
2552 return result;
2553}
2554
2555int ItemBoxContainer::neighboursLengthFor(const Item *item, Side side, Qt::Orientation o) const
2556{
2557 const Item::List children = visibleChildren();
2558 const auto index = children.indexOf(const_cast<Item *>(item));
2559 if (index == -1) {
2560 qWarning() << Q_FUNC_INFO << "Couldn't find item" << item;
2561 return 0;
2562 }
2563
2564 if (o == d->m_orientation) {
2565 int neighbourLength = 0;
2566 int start = 0;
2567 int end = -1;
2568 if (side == Side1) {
2569 start = 0;
2570 end = index - 1;
2571 } else {
2572 start = index + 1;
2573 end = children.size() - 1;
2574 }
2575
2576 for (int i = start; i <= end; ++i)
2577 neighbourLength += children.at(i)->length(d->m_orientation);
2578
2579 return neighbourLength;
2580 } else {
2581 // No neighbours in the other orientation. Each container is bidimensional.
2582 return 0;
2583 }
2584}
2585
2586int ItemBoxContainer::neighboursLengthFor_recursive(const Item *item, Side side, Qt::Orientation o) const
2587{
2588 return neighboursLengthFor(item, side, o) + (isRoot() ? 0 : parentBoxContainer()->neighboursLengthFor_recursive(this, side, o));
2589}
2590
2591int ItemBoxContainer::neighboursMinLengthFor(const Item *item, Side side, Qt::Orientation o) const
2592{
2593 const Item::List children = visibleChildren();
2594 const auto index = children.indexOf(const_cast<Item *>(item));
2595 if (index == -1) {
2596 qWarning() << Q_FUNC_INFO << "Couldn't find item" << item;
2597 return 0;
2598 }
2599
2600 if (o == d->m_orientation) {
2601 int neighbourMinLength = 0;
2602 int start = 0;
2603 int end = -1;
2604 if (side == Side1) {
2605 start = 0;
2606 end = index - 1;
2607 } else {
2608 start = index + 1;
2609 end = children.size() - 1;
2610 }
2611
2612 for (int i = start; i <= end; ++i)
2613 neighbourMinLength += children.at(i)->minLength(d->m_orientation);
2614
2615 return neighbourMinLength;
2616 } else {
2617 // No neighbours here
2618 return 0;
2619 }
2620}
2621
2622int ItemBoxContainer::neighboursMaxLengthFor(const Item *item, Side side, Qt::Orientation o) const
2623{
2624 const Item::List children = visibleChildren();
2625 const auto index = children.indexOf(const_cast<Item *>(item));
2626 if (index == -1) {
2627 qWarning() << Q_FUNC_INFO << "Couldn't find item" << item;
2628 return 0;
2629 }
2630
2631 if (o == d->m_orientation) {
2632 int neighbourMaxLength = 0;
2633 int start = 0;
2634 int end = -1;
2635 if (side == Side1) {
2636 start = 0;
2637 end = index - 1;
2638 } else {
2639 start = index + 1;
2640 end = children.size() - 1;
2641 }
2642
2643 for (int i = start; i <= end; ++i)
2644 neighbourMaxLength = qMin(Layouting::length(root()->size(), d->m_orientation), neighbourMaxLength + children.at(i)->maxLengthHint(d->m_orientation));
2645
2646 return neighbourMaxLength;
2647 } else {
2648 // No neighbours here
2649 return 0;
2650 }
2651}
2652
2653int ItemBoxContainer::availableToSqueezeOnSide(const Item *child, Side side) const
2654{
2655 const int length = neighboursLengthFor(child, side, d->m_orientation);
2656 const int min = neighboursMinLengthFor(child, side, d->m_orientation);
2657
2658 const int available = length - min;
2659 if (available < 0) {
2660 root()->dumpLayout();
2661 Q_ASSERT(false);
2662 }
2663 return available;
2664}
2665
2666int ItemBoxContainer::availableToGrowOnSide(const Item *child, Side side) const
2667{
2668 const int length = neighboursLengthFor(child, side, d->m_orientation);
2669 const int max = neighboursMaxLengthFor(child, side, d->m_orientation);
2670
2671 return max - length;
2672}
2673
2674int ItemBoxContainer::availableToSqueezeOnSide_recursive(const Item *child, Side side, Qt::Orientation orientation) const
2675{
2676 if (orientation == d->m_orientation) {
2677 const int available = availableToSqueezeOnSide(child, side);
2678 return isRoot() ? available
2679 : (available + parentBoxContainer()->availableToSqueezeOnSide_recursive(this, side, orientation));
2680 } else {
2681 return isRoot() ? 0
2682 : parentBoxContainer()->availableToSqueezeOnSide_recursive(this, side, orientation);
2683 }
2684}
2685
2686int ItemBoxContainer::availableToGrowOnSide_recursive(const Item *child, Side side, Qt::Orientation orientation) const
2687{
2688 if (orientation == d->m_orientation) {
2689 const int available = availableToGrowOnSide(child, side);
2690 return isRoot() ? available
2691 : (available + parentBoxContainer()->availableToGrowOnSide_recursive(this, side, orientation));
2692 } else {
2693 return isRoot() ? 0
2694 : parentBoxContainer()->availableToGrowOnSide_recursive(this, side, orientation);
2695 }
2696}
2697
2698void ItemBoxContainer::growNeighbours(Item *side1Neighbour, Item *side2Neighbour)
2699{
2700 if (!side1Neighbour && !side2Neighbour)
2701 return;
2702
2703 SizingInfo::List childSizes = sizes();
2704
2705 if (side1Neighbour && side2Neighbour) {
2706 const int index1 = indexOfVisibleChild(side1Neighbour);
2707 const int index2 = indexOfVisibleChild(side2Neighbour);
2708
2709 if (index1 == -1 || index2 == -1 || index1 >= childSizes.count() || index2 >= childSizes.count()) {
2710 // Doesn't happen
2711 qWarning() << Q_FUNC_INFO << "Invalid indexes" << index1 << index2 << childSizes.count();
2712 return;
2713 }
2714
2715 // Give half/half to each neighbour
2716 QRect &geo1 = childSizes[index1].geometry;
2717 QRect &geo2 = childSizes[index2].geometry;
2718
2719 if (isVertical()) {
2720 const int available = geo2.y() - geo1.bottom() - separatorThickness;
2721 geo1.setHeight(geo1.height() + available / 2);
2722 geo2.setTop(geo1.bottom() + separatorThickness + 1);
2723 } else {
2724 const int available = geo2.x() - geo1.right() - separatorThickness;
2725 geo1.setWidth(geo1.width() + available / 2);
2726 geo2.setLeft(geo1.right() + separatorThickness + 1);
2727 }
2728
2729 } else if (side1Neighbour) {
2730 const int index1 = indexOfVisibleChild(side1Neighbour);
2731 if (index1 == -1 || index1 >= childSizes.count()) {
2732 // Doesn't happen
2733 qWarning() << Q_FUNC_INFO << "Invalid indexes" << index1 << childSizes.count();
2734 return;
2735 }
2736
2737 // Grow all the way to the right (or bottom if vertical)
2738 QRect &geo = childSizes[index1].geometry;
2739 if (isVertical()) {
2740 geo.setBottom(rect().bottom());
2741 } else {
2742 geo.setRight(rect().right());
2743 }
2744 } else if (side2Neighbour) {
2745 const int index2 = indexOfVisibleChild(side2Neighbour);
2746 if (index2 == -1 || index2 >= childSizes.count()) {
2747 // Doesn't happen
2748 qWarning() << Q_FUNC_INFO << "Invalid indexes" << index2 << childSizes.count();
2749 return;
2750 }
2751
2752 // Grow all the way to the left (or top if vertical)
2753 QRect &geo = childSizes[index2].geometry;
2754 if (isVertical()) {
2755 geo.setTop(0);
2756 } else {
2757 geo.setLeft(0);
2758 }
2759 }
2760
2761 d->honourMaxSizes(childSizes);
2762 positionItems(/*by-ref*/ childSizes);
2763 applyGeometries(childSizes);
2764}
2765
2766void ItemBoxContainer::growItem(int index, SizingInfo::List &sizes, int missing,
2767 GrowthStrategy growthStrategy,
2768 NeighbourSqueezeStrategy neighbourSqueezeStrategy,
2769 bool accountForNewSeparator)
2770{
2771 int toSteal = missing; // The amount that neighbours of @p index will shrink
2772 if (accountForNewSeparator)
2773 toSteal += Item::separatorThickness;
2774
2775 Q_ASSERT(index != -1);
2776 if (toSteal == 0)
2777 return;
2778
2779 // #1. Grow our item
2780 SizingInfo &sizingInfo = sizes[index];
2781 sizingInfo.setOppositeLength(oppositeLength(), d->m_orientation);
2782 const bool isFirst = index == 0;
2783 const bool isLast = index == sizes.count() - 1;
2784
2785 int side1Growth = 0;
2786 int side2Growth = 0;
2787
2788 if (growthStrategy == GrowthStrategy::BothSidesEqually) {
2789 sizingInfo.setLength(sizingInfo.length(d->m_orientation) + missing, d->m_orientation);
2790 const auto count = sizes.count();
2791 if (count == 1) {
2792 // There's no neighbours to push, we're alone. Occupy the full container
2793 sizingInfo.incrementLength(missing, d->m_orientation);
2794 return;
2795 }
2796
2797 // #2. Now shrink the neighbors by the same amount. Calculate how much to shrink from each side
2798 const LengthOnSide side1Length = lengthOnSide(sizes, index - 1, Side1, d->m_orientation);
2799 const LengthOnSide side2Length = lengthOnSide(sizes, index + 1, Side2, d->m_orientation);
2800
2801 int available1 = side1Length.available();
2802 int available2 = side2Length.available();
2803
2804 if (toSteal > available1 + available2) {
2805 root()->dumpLayout();
2806 Q_ASSERT(false);
2807 }
2808
2809 while (toSteal > 0) {
2810 if (available1 == 0) {
2811 Q_ASSERT(available2 >= toSteal);
2812 side2Growth += toSteal;
2813 break;
2814 } else if (available2 == 0) {
2815 Q_ASSERT(available1 >= toSteal);
2816 side1Growth += toSteal;
2817 break;
2818 }
2819
2820 const int toTake = qMax(1, toSteal / 2);
2821 const int took1 = qMin(toTake, available1);
2822 toSteal -= took1;
2823 available1 -= took1;
2824 side1Growth += took1;
2825 if (toSteal == 0)
2826 break;
2827
2828 const int took2 = qMin(toTake, available2);
2829 toSteal -= took2;
2830 side2Growth += took2;
2831 available2 -= took2;
2832 }
2833 shrinkNeighbours(index, sizes, side1Growth, side2Growth, neighbourSqueezeStrategy);
2834 } else if (growthStrategy == GrowthStrategy::Side1Only) {
2835 side1Growth = qMin(missing, sizingInfo.availableToGrow(d->m_orientation));
2836 sizingInfo.setLength(sizingInfo.length(d->m_orientation) + side1Growth, d->m_orientation);
2837 if (side1Growth > 0)
2838 shrinkNeighbours(index, sizes, side1Growth, /*side2Growth=*/0, neighbourSqueezeStrategy);
2839 if (side1Growth < missing) {
2840 missing = missing - side1Growth;
2841
2842 if (isLast) {
2843 // Doesn't happen
2844 qWarning() << Q_FUNC_INFO << "No more items to grow";
2845 } else {
2846 growItem(index + 1, sizes, missing, growthStrategy, neighbourSqueezeStrategy, accountForNewSeparator);
2847 }
2848 }
2849
2850 } else if (growthStrategy == GrowthStrategy::Side2Only) {
2851 side2Growth = qMin(missing, sizingInfo.availableToGrow(d->m_orientation));
2852 sizingInfo.setLength(sizingInfo.length(d->m_orientation) + side2Growth, d->m_orientation);
2853
2854 if (side2Growth > 0)
2855 shrinkNeighbours(index, sizes, /*side1Growth=*/0, side2Growth, neighbourSqueezeStrategy);
2856 if (side2Growth < missing) {
2857 missing = missing - side2Growth;
2858
2859 if (isFirst) {
2860 // Doesn't happen
2861 qWarning() << Q_FUNC_INFO << "No more items to grow";
2862 } else {
2863 growItem(index - 1, sizes, missing, growthStrategy, neighbourSqueezeStrategy, accountForNewSeparator);
2864 }
2865 }
2866 }
2867}
2868
2869void ItemBoxContainer::growItem(Item *item, int amount, GrowthStrategy growthStrategy,
2870 NeighbourSqueezeStrategy neighbourSqueezeStrategy,
2871 bool accountForNewSeparator,
2872 ChildrenResizeStrategy childResizeStrategy)
2873{
2874 const Item::List items = visibleChildren();
2875 const auto index = items.indexOf(item);
2876 SizingInfo::List sizes = this->sizes();
2877
2878 growItem(index, /*by-ref=*/sizes, amount, growthStrategy, neighbourSqueezeStrategy, accountForNewSeparator);
2879
2880 applyGeometries(sizes, childResizeStrategy);
2881}
2882
2883void ItemBoxContainer::applyGeometries(const SizingInfo::List &sizes, ChildrenResizeStrategy strategy)
2884{
2885 const Item::List items = visibleChildren();
2886 const auto count = items.size();
2887 Q_ASSERT(count == sizes.size());
2888
2889 for (int i = 0; i < count; ++i) {
2890 Item *item = items.at(i);
2891 item->setSize_recursive(sizes[i].geometry.size(), strategy);
2892 }
2893
2894 positionItems();
2895}
2896
2897SizingInfo::List ItemBoxContainer::sizes(bool ignoreBeingInserted) const
2898{
2899 const Item::List children = visibleChildren(ignoreBeingInserted);
2900 SizingInfo::List result;
2901 result.reserve(children.count());
2902 for (Item *item : children) {
2903 if (item->isContainer()) {
2904 // Containers have virtual min/maxSize methods, and don't really fill in these properties
2905 // So fill them here
2906 item->m_sizingInfo.minSize = item->minSize();
2907 item->m_sizingInfo.maxSizeHint = item->maxSizeHint();
2908 }
2909 result << item->m_sizingInfo;
2910 }
2911
2912 return result;
2913}
2914
2915QVector<int> ItemBoxContainer::calculateSqueezes(SizingInfo::List::ConstIterator begin, // clazy:exclude=function-args-by-ref
2916 SizingInfo::List::ConstIterator end, int needed, // clazy:exclude=function-args-by-ref
2917 NeighbourSqueezeStrategy strategy, bool reversed) const
2918{
2919 QVector<int> availabilities;
2920 for (auto it = begin; it < end; ++it) {
2921 availabilities << it->availableLength(d->m_orientation);
2922 }
2923
2924 const auto count = availabilities.count();
2925
2926 QVector<int> squeezes;
2927 squeezes.resize(count);
2928 std::fill(squeezes.begin(), squeezes.end(), 0);
2929
2930 int missing = needed;
2931
2932 if (strategy == NeighbourSqueezeStrategy::AllNeighbours) {
2933 while (missing > 0) {
2934 const int numDonors = std::count_if(availabilities.cbegin(), availabilities.cend(), [](int num) {
2935 return num > 0;
2936 });
2937
2938 if (numDonors == 0) {
2939 root()->dumpLayout();
2940 Q_ASSERT(false);
2941 return {};
2942 }
2943
2944 int toTake = missing / numDonors;
2945 if (toTake == 0)
2946 toTake = missing;
2947
2948 for (int i = 0; i < count; ++i) {
2949 const int available = availabilities.at(i);
2950 if (available == 0)
2951 continue;
2952 const int took = qMin(missing, qMin(toTake, available));
2953 availabilities[i] -= took;
2954 missing -= took;
2955 squeezes[i] += took;
2956 if (missing == 0)
2957 break;
2958 }
2959 }
2960 } else if (strategy == NeighbourSqueezeStrategy::ImmediateNeighboursFirst) {
2961 for (int i = 0; i < count; i++) {
2962 const auto index = reversed ? count - 1 - i : i;
2963
2964 const int available = availabilities.at(index);
2965 if (available > 0) {
2966 const int took = qMin(missing, available);
2967 missing -= took;
2968 squeezes[index] += took;
2969 }
2970
2971 if (missing == 0)
2972 break;
2973 }
2974 }
2975
2976 if (missing < 0) {
2977 // Doesn't really happen
2978 qWarning() << Q_FUNC_INFO << "Missing is negative" << missing
2979 << squeezes;
2980 }
2981
2982 return squeezes;
2983}
2984
2985void ItemBoxContainer::shrinkNeighbours(int index, SizingInfo::List &sizes, int side1Amount,
2986 int side2Amount, NeighbourSqueezeStrategy strategy)
2987{
2988 Q_ASSERT(side1Amount > 0 || side2Amount > 0);
2989 Q_ASSERT(side1Amount >= 0 && side2Amount >= 0); // never negative
2990
2991 if (side1Amount > 0) {
2992 auto begin = sizes.cbegin();
2993 auto end = sizes.cbegin() + index;
2994 const bool reversed = strategy == NeighbourSqueezeStrategy::ImmediateNeighboursFirst;
2995 const QVector<int> squeezes = calculateSqueezes(begin, end, side1Amount, strategy, reversed);
2996 for (int i = 0; i < squeezes.size(); ++i) {
2997 const int squeeze = squeezes.at(i);
2998 SizingInfo &sizing = sizes[i];
2999 // setSize() or setGeometry() have the same effect here, we don't care about the position yet. That's done in positionItems()
3000 sizing.setSize(adjustedRect(sizing.geometry, d->m_orientation, 0, -squeeze).size());
3001 }
3002 }
3003
3004 if (side2Amount > 0) {
3005 auto begin = sizes.cbegin() + index + 1;
3006 auto end = sizes.cend();
3007
3008 const QVector<int> squeezes = calculateSqueezes(begin, end, side2Amount, strategy);
3009 for (int i = 0; i < squeezes.size(); ++i) {
3010 const int squeeze = squeezes.at(i);
3011 SizingInfo &sizing = sizes[i + index + 1];
3012 sizing.setSize(adjustedRect(sizing.geometry, d->m_orientation, squeeze, 0).size());
3013 }
3014 }
3015}
3016
3017QVector<int> ItemBoxContainer::Private::requiredSeparatorPositions() const
3018{
3019 const int numSeparators = qMax(0, q->numVisibleChildren() - 1);
3020 QVector<int> positions;
3021 positions.reserve(numSeparators);
3022
3023 for (Item *item : qAsConst(q->m_children)) {
3024 if (positions.size() == numSeparators)
3025 break;
3026
3027 if (item->isVisible()) {
3028 const int localPos = item->m_sizingInfo.edge(m_orientation) + 1;
3029 positions << q->mapToRoot(localPos, m_orientation);
3030 }
3031 }
3032
3033 return positions;
3034}
3035
3036void ItemBoxContainer::Private::updateSeparators()
3037{
3038 if (!q->hostWidget())
3039 return;
3040
3041 const QVector<int> positions = requiredSeparatorPositions();
3042 const auto requiredNumSeparators = positions.size();
3043
3044 const bool numSeparatorsChanged = requiredNumSeparators != m_separators.size();
3045 if (numSeparatorsChanged) {
3046 // Instead of just creating N missing ones at the end of the list, let's minimize separators
3047 // having their position changed, to minimize flicker
3048 Separator::List newSeparators;
3049 newSeparators.reserve(requiredNumSeparators);
3050
3051 for (int position : positions) {
3052 Separator *separator = separatorAt(position);
3053 if (separator) {
3054 // Already existing, reuse
3055 newSeparators.push_back(separator);
3056 m_separators.removeOne(separator);
3057 } else {
3058 separator = Config::self().createSeparator(q->hostWidget());
3059 separator->init(q, m_orientation);
3060 newSeparators.push_back(separator);
3061 }
3062 }
3063
3064 // delete what remained, which is unused
3065 deleteSeparators();
3066
3067 m_separators = newSeparators;
3068 }
3069
3070 // Update their positions:
3071 const int pos2 = q->isVertical() ? q->mapToRoot(QPoint(0, 0)).x()
3072 : q->mapToRoot(QPoint(0, 0)).y();
3073
3074 int i = 0;
3075 for (int position : positions) {
3076 m_separators.at(i)->setGeometry(position, pos2, q->oppositeLength());
3077 i++;
3078 }
3079
3080 q->updateChildPercentages();
3081}
3082
3083void ItemBoxContainer::Private::deleteSeparators()
3084{
3085 qDeleteAll(m_separators);
3086 m_separators.clear();
3087}
3088
3089void ItemBoxContainer::Private::deleteSeparators_recursive()
3090{
3091 deleteSeparators();
3092
3093 // recurse into the children:
3094 for (Item *item : qAsConst(q->m_children)) {
3095 if (auto c = item->asBoxContainer())
3096 c->d->deleteSeparators_recursive();
3097 }
3098}
3099
3100void ItemBoxContainer::Private::updateSeparators_recursive()
3101{
3102 updateSeparators();
3103
3104 // recurse into the children:
3105 const Item::List items = q->visibleChildren();
3106 for (Item *item : items) {
3107 if (auto c = item->asBoxContainer())
3108 c->d->updateSeparators_recursive();
3109 }
3110}
3111
3112int ItemBoxContainer::Private::excessLength() const
3113{
3114 // Returns how much bigger this layout is than its max-size
3115 return qMax(0, Layouting::length(q->size(), m_orientation) - q->maxLengthHint(m_orientation));
3116}
3117
3118void ItemBoxContainer::simplify()
3119{
3120 // Removes unneeded nesting. For example, a vertical layout doesn't need to have vertical layouts
3121 // inside. It can simply have the contents of said sub-layouts
3122
3123 QScopedValueRollback<bool> isInSimplify(d->m_isSimplifying, true);
3124
3125 Item::List newChildren;
3126 newChildren.reserve(m_children.size() + 20); // over-reserve a bit
3127
3128 for (Item *child : qAsConst(m_children)) {
3129 if (ItemBoxContainer *childContainer = child->asBoxContainer()) {
3130 childContainer->simplify(); // recurse down the hierarchy
3131
3132 if (childContainer->orientation() == d->m_orientation || childContainer->m_children.size() == 1) {
3133 // This sub-container is redundant, as it has the same orientation as its parent
3134 // Cannibalize it.
3135 for (Item *child2 : childContainer->childItems()) {
3136 child2->setParentContainer(this);
3137 newChildren.push_back(child2);
3138 }
3139
3140 delete childContainer;
3141 } else {
3142 newChildren.push_back(child);
3143 }
3144 } else {
3145 newChildren.push_back(child);
3146 }
3147 }
3148
3149 if (m_children != newChildren) {
3150 m_children = newChildren;
3151 positionItems();
3152 updateChildPercentages();
3153 }
3154}
3155
3156Separator *ItemBoxContainer::Private::separatorAt(int p) const
3157{
3158 for (Separator *separator : m_separators) {
3159 if (separator->position() == p)
3160 return separator;
3161 }
3162
3163 return nullptr;
3164}
3165
3166bool ItemBoxContainer::isVertical() const
3167{
3168 return d->m_orientation == Qt::Vertical;
3169}
3170
3171bool ItemBoxContainer::isHorizontal() const
3172{
3173 return d->m_orientation == Qt::Horizontal;
3174}
3175
3176int ItemBoxContainer::indexOf(Separator *separator) const
3177{
3178 return d->m_separators.indexOf(separator);
3179}
3180
3181bool ItemBoxContainer::isInSimplify() const
3182{
3183 if (d->m_isSimplifying)
3184 return true;
3185
3186 auto p = parentBoxContainer();
3187 return p && p->isInSimplify();
3188}
3189
3190int ItemBoxContainer::minPosForSeparator(Separator *separator, bool honourMax) const
3191{
3192 const int globalMin = minPosForSeparator_global(separator, honourMax);
3193 return mapFromRoot(globalMin, d->m_orientation);
3194}
3195
3196int ItemBoxContainer::maxPosForSeparator(Separator *separator, bool honourMax) const
3197{
3198 const int globalMax = maxPosForSeparator_global(separator, honourMax);
3199 return mapFromRoot(globalMax, d->m_orientation);
3200}
3201
3202int ItemBoxContainer::minPosForSeparator_global(Separator *separator, bool honourMax) const
3203{
3204 const int separatorIndex = indexOf(separator);
3205 Q_ASSERT(separatorIndex != -1);
3206
3207 const Item::List children = visibleChildren();
3208 Q_ASSERT(separatorIndex + 1 < children.size());
3209 Item *item2 = children.at(separatorIndex + 1);
3210
3211 const int availableToSqueeze = availableToSqueezeOnSide_recursive(item2, Side1, d->m_orientation);
3212
3213 if (honourMax) {
3214 // We can drag the separator left just as much as it doesn't violate max-size constraints of Side2
3215 Item *item1 = children.at(separatorIndex);
3216 const int availabletoGrow = availableToGrowOnSide_recursive(item1, Side2, d->m_orientation);
3217 return separator->position() - qMin(availabletoGrow, availableToSqueeze);
3218 }
3219
3220 return separator->position() - availableToSqueeze;
3221}
3222
3223int ItemBoxContainer::maxPosForSeparator_global(Separator *separator, bool honourMax) const
3224{
3225 const int separatorIndex = indexOf(separator);
3226 Q_ASSERT(separatorIndex != -1);
3227
3228 const Item::List children = visibleChildren();
3229 Item *item1 = children.at(separatorIndex);
3230
3231 const int availableToSqueeze = availableToSqueezeOnSide_recursive(item1, Side2, d->m_orientation);
3232
3233 if (honourMax) {
3234 // We can drag the separator right just as much as it doesn't violate max-size constraints of Side1
3235 Item *item2 = children.at(separatorIndex + 1);
3236 const int availabletoGrow = availableToGrowOnSide_recursive(item2, Side1, d->m_orientation);
3237 return separator->position() + qMin(availabletoGrow, availableToSqueeze);
3238 }
3239
3240 return separator->position() + availableToSqueeze;
3241}
3242
3243QVariantMap ItemBoxContainer::toVariantMap() const
3244{
3245 QVariantMap result = Item::toVariantMap();
3246
3247 QVariantList childrenV;
3248 childrenV.reserve(m_children.size());
3249 for (Item *child : qAsConst(m_children)) {
3250 childrenV.push_back(child->toVariantMap());
3251 }
3252
3253 result[QStringLiteral("children")] = childrenV;
3254 result[QStringLiteral("orientation")] = d->m_orientation;
3255
3256 return result;
3257}
3258
3259void ItemBoxContainer::fillFromVariantMap(const QVariantMap &map,
3260 const QHash<QString, Widget *> &widgets)
3261{
3262 QScopedValueRollback<bool> deserializing(d->m_isDeserializing, true);
3263
3264 Item::fillFromVariantMap(map, widgets);
3265 const QVariantList childrenV = map[QStringLiteral("children")].toList();
3266 d->m_orientation = Qt::Orientation(map[QStringLiteral("orientation")].toInt());
3267
3268 for (const QVariant &childV : childrenV) {
3269 const QVariantMap childMap = childV.toMap();
3270 const bool isContainer = childMap.value(QStringLiteral("isContainer")).toBool();
3271 Item *child = isContainer ? new ItemBoxContainer(hostWidget(), this)
3272 : new Item(hostWidget(), this);
3273 child->fillFromVariantMap(childMap, widgets);
3274 m_children.push_back(child);
3275 }
3276
3277 if (isRoot()) {
3278 updateChildPercentages_recursive();
3279 if (hostWidget()) {
3280 d->updateSeparators_recursive();
3281 d->updateWidgets_recursive();
3282 }
3283
3284 d->relayoutIfNeeded();
3285 positionItems_recursive();
3286
3287 Q_EMIT minSizeChanged(this);
3288#ifdef DOCKS_DEVELOPER_MODE
3289 if (!checkSanity())
3290 qWarning() << Q_FUNC_INFO << "Resulting layout is invalid";
3291#endif
3292 }
3293}
3294
3295bool ItemBoxContainer::Private::isDummy() const
3296{
3297 return q->hostWidget() == nullptr;
3298}
3299
3300#ifdef DOCKS_DEVELOPER_MODE
3301bool ItemBoxContainer::test_suggestedRect()
3302{
3303 auto itemToDrop = new Item(hostWidget());
3304
3305 const Item::List children = visibleChildren();
3306 for (Item *relativeTo : children) {
3307 if (auto c = relativeTo->asBoxContainer()) {
3308 c->test_suggestedRect();
3309 } else {
3312 const QRect rect = suggestedDropRect(itemToDrop, relativeTo, loc);
3313 rects.insert(loc, rect);
3314 if (rect.isEmpty()) {
3315 qWarning() << Q_FUNC_INFO << "Empty rect";
3316 return false;
3317 } else if (!root()->rect().contains(rect)) {
3318 root()->dumpLayout();
3319 qWarning() << Q_FUNC_INFO << "Suggested rect is out of bounds" << rect
3320 << "; loc=" << loc << "; relativeTo=" << relativeTo;
3321 return false;
3322 }
3323 }
3324 if (rects.value(Location_OnBottom).y() <= rects.value(Location_OnTop).y() || rects.value(Location_OnRight).x() <= rects.value(Location_OnLeft).x()) {
3325 root()->dumpLayout();
3326 qWarning() << Q_FUNC_INFO << "Invalid suggested rects" << rects
3327 << this << "; relativeTo=" << relativeTo;
3328 return false;
3329 }
3330 }
3331 }
3332
3333 delete itemToDrop;
3334 return true;
3335}
3336#endif
3337
3338QVector<Separator *> ItemBoxContainer::separators_recursive() const
3339{
3340 Layouting::Separator::List separators = d->m_separators;
3341
3342 for (Item *item : qAsConst(m_children)) {
3343 if (auto c = item->asBoxContainer())
3344 separators << c->separators_recursive();
3345 }
3346
3347 return separators;
3348}
3349
3350QVector<Separator *> ItemBoxContainer::separators() const
3351{
3352 return d->m_separators;
3353}
3354
3355bool ItemBoxContainer::Private::isOverflowing() const
3356{
3357 // This never returns true, unless when loading a buggy layout
3358 // or if QWidgets now have bigger min-size
3359
3360 int contentsLength = 0;
3361 int numVisible = 0;
3362 for (Item *item : qAsConst(q->m_children)) {
3363 if (item->isVisible()) {
3364 contentsLength += item->length(m_orientation);
3365 numVisible++;
3366 }
3367 }
3368
3369 contentsLength += qMax(0, Item::separatorThickness * (numVisible - 1));
3370 return contentsLength > q->length();
3371}
3372
3373void ItemBoxContainer::Private::relayoutIfNeeded()
3374{
3375 // Checks all the child containers if they have the correct min-size, recursively.
3376 // When loading a layout from disk the min-sizes for the host QWidgets might have changed, so we
3377 // need to adjust
3378
3379 if (!q->missingSize().isNull())
3380 q->setSize_recursive(q->minSize());
3381
3382 if (isOverflowing()) {
3383 const QSize size = q->size();
3384 q->m_sizingInfo.setSize(size + QSize(1, 1)); // Just so setSize_recursive() doesn't bail out
3385 q->setSize_recursive(size);
3386 q->updateChildPercentages();
3387 }
3388
3389 // Let's see our children too:
3390 for (Item *item : qAsConst(q->m_children)) {
3391 if (item->isVisible()) {
3392 if (auto c = item->asBoxContainer())
3393 c->d->relayoutIfNeeded();
3394 }
3395 }
3396}
3397
3398const Item *ItemBoxContainer::Private::itemFromPath(const QVector<int> &path) const
3399{
3400 const ItemBoxContainer *container = q;
3401
3402 for (int i = 0; i < path.size(); ++i) {
3403 const int index = path[i];
3404 const bool isLast = i == path.size() - 1;
3405 if (index < 0 || index >= container->m_children.size()) {
3406 // Doesn't happen
3407 q->root()->dumpLayout();
3408 qWarning() << Q_FUNC_INFO << "Invalid index" << index
3409 << this << path << q->isRoot();
3410 return nullptr;
3411 }
3412
3413 if (isLast) {
3414 return container->m_children.at(index);
3415 } else {
3416 container = container->m_children.at(index)->asBoxContainer();
3417 if (!container) {
3418 qWarning() << Q_FUNC_INFO << "Invalid index" << path;
3419 return nullptr;
3420 }
3421 }
3422 }
3423
3424 return q;
3425}
3426
3427Separator *ItemBoxContainer::Private::neighbourSeparator(const Item *item, Side side, Qt::Orientation orientation) const
3428{
3429 Item::List children = q->visibleChildren();
3430 const auto itemIndex = children.indexOf(const_cast<Item *>(item));
3431 if (itemIndex == -1) {
3432 qWarning() << Q_FUNC_INFO << "Item not found" << item
3433 << this;
3434 q->root()->dumpLayout();
3435 return nullptr;
3436 }
3437
3438 if (orientation != q->orientation()) {
3439 // Go up
3440 if (q->isRoot()) {
3441 return nullptr;
3442 } else {
3443 return q->parentBoxContainer()->d->neighbourSeparator(q, side, orientation);
3444 }
3445 }
3446
3447 const auto separatorIndex = side == Side1 ? itemIndex - 1
3448 : itemIndex;
3449
3450 if (separatorIndex < 0 || separatorIndex >= m_separators.size())
3451 return nullptr;
3452
3453 return m_separators[separatorIndex];
3454}
3455
3456Separator *ItemBoxContainer::Private::neighbourSeparator_recursive(const Item *item, Side side,
3457 Qt::Orientation orientation) const
3458{
3459 Separator *separator = neighbourSeparator(item, side, orientation);
3460 if (separator)
3461 return separator;
3462
3463 if (!q->parentContainer())
3464 return nullptr;
3465
3466 return q->parentBoxContainer()->d->neighbourSeparator_recursive(q, side, orientation);
3467}
3468
3469void ItemBoxContainer::Private::updateWidgets_recursive()
3470{
3471 for (Item *item : qAsConst(q->m_children)) {
3472 if (auto c = item->asBoxContainer()) {
3473 c->d->updateWidgets_recursive();
3474 } else {
3475 if (item->isVisible()) {
3476 if (Widget *widget = item->guestWidget()) {
3477 widget->setGeometry(q->mapToRoot(item->geometry()));
3478 widget->setVisible(true);
3479 } else {
3480 qWarning() << Q_FUNC_INFO << "visible item doesn't have a guest"
3481 << item;
3482 }
3483 }
3484 }
3485 }
3486}
3487
3488SizingInfo::SizingInfo()
3489 : minSize(Layouting::Item::hardcodedMinimumSize)
3490 , maxSizeHint(Layouting::Item::hardcodedMaximumSize)
3491{
3492}
3493
3494void SizingInfo::setOppositeLength(int l, Qt::Orientation o)
3495{
3496 setLength(l, oppositeOrientation(o));
3497}
3498
3499QVariantMap SizingInfo::toVariantMap() const
3500{
3501 QVariantMap result;
3502 result[QStringLiteral("geometry")] = rectToMap(geometry);
3503 result[QStringLiteral("minSize")] = sizeToMap(minSize);
3504 result[QStringLiteral("maxSize")] = sizeToMap(maxSizeHint);
3505 return result;
3506}
3507
3508void SizingInfo::fromVariantMap(const QVariantMap &map)
3509{
3510 *this = SizingInfo(); // reset any non-important fields to their default
3511 geometry = mapToRect(map[QStringLiteral("geometry")].toMap());
3512 minSize = mapToSize(map[QStringLiteral("minSize")].toMap());
3513 maxSizeHint = mapToSize(map[QStringLiteral("maxSize")].toMap());
3514}
3515
3516int ItemBoxContainer::Private::defaultLengthFor(Item *item, InitialOption option) const
3517{
3518 int result = 0;
3519
3520 if (option.hasPreferredLength(m_orientation) && option.sizeMode != DefaultSizeMode::NoDefaultSizeMode) {
3521 result = option.preferredLength(m_orientation);
3522 } else {
3523 switch (option.sizeMode) {
3524 case DefaultSizeMode::NoDefaultSizeMode:
3525 break;
3526 case DefaultSizeMode::Fair: {
3527 const int numVisibleChildren = q->numVisibleChildren() + 1; // +1 so it counts with @p item too, which we're adding
3528 const int usableLength = q->length() - (Item::separatorThickness * (numVisibleChildren - 1));
3529 result = usableLength / numVisibleChildren;
3530 break;
3531 }
3532 case DefaultSizeMode::FairButFloor: {
3533 int length = defaultLengthFor(item, DefaultSizeMode::Fair);
3534 result = qMin(length, item->length(m_orientation));
3535 break;
3536 }
3537 case DefaultSizeMode::ItemSize:
3538 result = item->length(m_orientation);
3539 break;
3540 }
3541 }
3542
3543 result = qMax(item->minLength(m_orientation), result); // bound with max-size too
3544 return result;
3545}
3546
3547struct ItemContainer::Private
3548{
3549 Private(ItemContainer *qq)
3550 : q(qq)
3551 {
3552 ( void )Config::self(); // Ensure Config ctor runs, as it registers qml types
3553 }
3554
3555 ~Private()
3556 {
3557 }
3558 ItemContainer *const q;
3559};
3560
3561ItemContainer::ItemContainer(Widget *hostWidget, ItemContainer *parent)
3562 : Item(true, hostWidget, parent)
3563 , d(new Private(this))
3564{
3565 connect(this, &Item::xChanged, this, [this] {
3566 for (Item *item : qAsConst(m_children)) {
3567 Q_EMIT item->xChanged();
3568 }
3569 });
3570
3571 connect(this, &Item::yChanged, this, [this] {
3572 for (Item *item : qAsConst(m_children)) {
3573 Q_EMIT item->yChanged();
3574 }
3575 });
3576}
3577
3578ItemContainer::ItemContainer(Widget *hostWidget)
3579 : Item(true, hostWidget, nullptr)
3580 , d(new Private(this))
3581{
3582}
3583
3584ItemContainer::~ItemContainer()
3585{
3586 delete d;
3587}
3588
3589const Item::List ItemContainer::childItems() const
3590{
3591 return m_children;
3592}
3593
3594bool ItemContainer::hasChildren() const
3595{
3596 return !m_children.isEmpty();
3597}
3598
3599bool ItemContainer::hasVisibleChildren(bool excludeBeingInserted) const
3600{
3601 for (Item *item : qAsConst(m_children)) {
3602 if (item->isVisible(excludeBeingInserted))
3603 return true;
3604 }
3605
3606 return false;
3607}
3608
3609int ItemContainer::numChildren() const
3610{
3611 return m_children.size();
3612}
3613
3614int ItemContainer::numVisibleChildren() const
3615{
3616 int num = 0;
3617 for (Item *child : qAsConst(m_children)) {
3618 if (child->isVisible())
3619 num++;
3620 }
3621 return num;
3622}
3623
3624bool ItemContainer::isEmpty() const
3625{
3626 return m_children.isEmpty();
3627}
3628
3629bool ItemContainer::hasSingleVisibleItem() const
3630{
3631 return numVisibleChildren() == 1;
3632}
3633
3634bool ItemContainer::contains(const Item *item) const
3635{
3636 return m_children.contains(const_cast<Item *>(item));
3637}
3638
3639Item *ItemContainer::itemForObject(const QObject *o) const
3640{
3641 for (Item *item : qAsConst(m_children)) {
3642 if (item->isContainer()) {
3643 if (Item *result = item->asContainer()->itemForObject(o))
3644 return result;
3645 } else if (auto guest = item->guestWidget()) {
3646 if (guest && guest->asQObject() == o)
3647 return item;
3648 }
3649 }
3650
3651 return nullptr;
3652}
3653
3654Item *ItemContainer::itemForWidget(const Widget *w) const
3655{
3656 for (Item *item : qAsConst(m_children)) {
3657 if (item->isContainer()) {
3658 if (Item *result = item->asContainer()->itemForWidget(w))
3659 return result;
3660 } else if (item->guestWidget() == w) {
3661 return item;
3662 }
3663 }
3664
3665 return nullptr;
3666}
3667
3668Item::List ItemContainer::visibleChildren(bool includeBeingInserted) const
3669{
3670 Item::List items;
3671 items.reserve(m_children.size());
3672 for (Item *item : qAsConst(m_children)) {
3673 if (includeBeingInserted) {
3674 if (item->isVisible() || item->isBeingInserted())
3675 items << item;
3676 } else {
3677 if (item->isVisible() && !item->isBeingInserted())
3678 items << item;
3679 }
3680 }
3681
3682 return items;
3683}
3684
3685Item::List ItemContainer::items_recursive() const
3686{
3687 Item::List items;
3688 items.reserve(30); // sounds like a good upper number to minimize allocations
3689 for (Item *item : qAsConst(m_children)) {
3690 if (auto c = item->asContainer()) {
3691 items << c->items_recursive();
3692 } else {
3693 items << item;
3694 }
3695 }
3696
3697 return items;
3698}
3699
3700bool ItemContainer::contains_recursive(const Item *item) const
3701{
3702 for (Item *it : qAsConst(m_children)) {
3703 if (it == item) {
3704 return true;
3705 } else if (it->isContainer()) {
3706 if (it->asContainer()->contains_recursive(item))
3707 return true;
3708 }
3709 }
3710
3711 return false;
3712}
3713
3714
3715int ItemContainer::visibleCount_recursive() const
3716{
3717 int count = 0;
3718 for (Item *item : qAsConst(m_children)) {
3719 count += item->visibleCount_recursive();
3720 }
3721
3722 return count;
3723}
3724
3725int ItemContainer::count_recursive() const
3726{
3727 int count = 0;
3728 for (Item *item : qAsConst(m_children)) {
3729 if (auto c = item->asContainer()) {
3730 count += c->count_recursive();
3731 } else {
3732 count++;
3733 }
3734 }
3735
3736 return count;
3737}
3738
3739#ifdef Q_CC_MSVC
3740#pragma warning(pop)
3741#endif
bool locationIsSide1(Location loc)
Definition Item.cpp:52
QRect adjustedRect(QRect r, Qt::Orientation o, int p1, int p2)
Definition Item.cpp:78
Qt::Orientation oppositeOrientation(Qt::Orientation o)
Definition Item.cpp:72
Qt::Orientation orientationForLocation(Location loc)
Definition Item.cpp:57
bool locationIsVertical(Location loc)
Definition Item.cpp:47
An abstraction/wrapper around QWidget, QtQuickItem or anything else.
static Config & self()
returns the singleton Config instance
Definition Config.cpp:84
An abstraction/wrapper around QWidget, QtQuickItem or anything else.
Definition Widget.h:77
virtual void setParent(Widget *)=0
virtual QDebug & dumpDebug(QDebug &) const =0
virtual QRect geometry() const =0
QObject * asQObject() const
Definition Widget.h:122
virtual QSize minSize() const =0
virtual QSize maxSizeHint() const =0
@ Location_OnTop
‍Left docking location
@ Location_OnRight
‍Top docking location
@ Location_OnBottom
‍Right docking location
QEvent::Type type() const const
QHash::iterator insert(const Key &key, const T &value)
const T value(const Key &key) const const
void destroyed(QObject *obj)
void installEventFilter(QObject *filterObj)
void objectNameChanged(const QString &objectName)
QObject * parent() const const
void removeEventFilter(QObject *obj)
void setParent(QObject *parent)
int y() const const
void adjust(int dx1, int dy1, int dx2, int dy2)
int bottom() const const
int height() const const
bool isEmpty() const const
void moveTopLeft(const QPoint &position)
int right() const const
void setBottom(int y)
void setHeight(int height)
void setLeft(int x)
void setRight(int x)
void setSize(const QSize &size)
void setTop(int y)
void setTopLeft(const QPoint &position)
void setWidth(int width)
QSize size() const const
QPoint topLeft() const const
int width() const const
int x() const const
int y() const const
QSize boundedTo(const QSize &otherSize) const const
QSize expandedTo(const QSize &otherSize) const const
int height() const const
bool isNull() const const
void setHeight(int height)
void setWidth(int width)
int width() const const
QString & fill(QChar ch, int size)
bool isEmpty() const const
Orientation
QTextStream & right(QTextStream &stream)
const T & at(int i) const const
QVector::iterator begin()
QVector::const_iterator cbegin() const const
QVector::const_iterator cend() const const
bool contains(const T &value) const const
int count(const T &value) const const
QVector::iterator end()
QVector::iterator erase(QVector::iterator begin, QVector::iterator end)
int indexOf(const T &value, int from) const const
int length() const const
void prepend(T &&value)
void push_back(const T &value)
void reserve(int size)
void resize(int size)
int size() const const
Struct describing the preferred dock widget size and visibility when adding it to a layout.
int preferredLength(Qt::Orientation o) const
bool hasPreferredLength(Qt::Orientation o) const

© 2019-2023 Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDDockWidgets
Advanced Dock Widget Framework for Qt
https://www.kdab.com/development-resources/qt-tools/kddockwidgets/
Generated on Wed Nov 1 2023 00:02:31 for KDDockWidgets API Documentation by doxygen 1.9.8