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

© 2019-2022 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 Mon Mar 7 2022 02:01:20 for KDDockWidgets API Documentation by doxygen 1.8.20