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