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

© 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 by doxygen 1.9.8