Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qgraphicsanchorlayout_p.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtWidgets/qwidget.h>
7#include <QtWidgets/qapplication.h>
8#include <QtCore/qstack.h>
9
10#ifdef QT_DEBUG
11#include <QtCore/qfile.h>
12#endif
13
14#include <numeric>
15
17
18using namespace Qt::StringLiterals;
19
20// To ensure that all variables inside the simplex solver are non-negative,
21// we limit the size of anchors in the interval [-limit, limit]. Then before
22// sending them to the simplex solver we add "limit" as an offset, so that
23// they are actually calculated in the interval [0, 2 * limit]
24// To avoid numerical errors in platforms where we use single precision,
25// we use a tighter limit for the variables range.
26const qreal g_offset = (sizeof(qreal) == sizeof(double)) ? QWIDGETSIZE_MAX : QWIDGETSIZE_MAX / 32;
27
29 : QObjectPrivate(version), layoutPrivate(nullptr), data(nullptr),
30 sizePolicy(QSizePolicy::Fixed), preferredSize(0),
31 hasSize(true)
32{
33}
34
36{
37 if (data) {
38 // The QGraphicsAnchor was already deleted at this moment. We must clean
39 // the dangling pointer to avoid double deletion in the AnchorData dtor.
40 data->graphicsAnchor = nullptr;
41
43 }
44}
45
47{
48 if (sizePolicy != policy) {
50 layoutPrivate->q_func()->invalidate();
51 }
52}
53
55{
56 if (!data) {
57 qWarning("QGraphicsAnchor::setSpacing: The anchor does not exist.");
58 return;
59 }
60
61 if (hasSize && (preferredSize == value))
62 return;
63
64 // The anchor has an user-defined size
65 hasSize = true;
67
68 layoutPrivate->q_func()->invalidate();
69}
70
72{
73 if (!data) {
74 qWarning("QGraphicsAnchor::setSpacing: The anchor does not exist.");
75 return;
76 }
77
78 // Return to standard direction
79 hasSize = false;
80
81 layoutPrivate->q_func()->invalidate();
82}
83
85{
86 if (!data) {
87 qWarning("QGraphicsAnchor::setSpacing: The anchor does not exist.");
88 return 0;
89 }
90
91 return preferredSize;
92}
93
94
96 qreal minSizeHint, qreal prefSizeHint, qreal maxSizeHint,
97 qreal *minSize, qreal *prefSize,
98 qreal *maxSize)
99{
100 // minSize, prefSize and maxSize are initialized
101 // with item's preferred Size: this is QSizePolicy::Fixed.
102 //
103 // Then we check each flag to find the resultant QSizePolicy,
104 // according to the following table:
105 //
106 // constant value
107 // QSizePolicy::Fixed 0
108 // QSizePolicy::Minimum GrowFlag
109 // QSizePolicy::Maximum ShrinkFlag
110 // QSizePolicy::Preferred GrowFlag | ShrinkFlag
111 // QSizePolicy::Ignored GrowFlag | ShrinkFlag | IgnoreFlag
112
114 *minSize = minSizeHint;
115 else
116 *minSize = prefSizeHint;
117
119 *maxSize = maxSizeHint;
120 else
121 *maxSize = prefSizeHint;
122
123 // Note that these two initializations are affected by the previous flags
125 *prefSize = *minSize;
126 else
127 *prefSize = prefSizeHint;
128}
129
131{
132 if (graphicsAnchor) {
133 // Remove reference to ourself to avoid double removal in
134 // QGraphicsAnchorPrivate dtor.
136
137 delete graphicsAnchor;
138 }
139}
140
141
143{
145 qreal minSizeHint;
146 qreal prefSizeHint;
147 qreal maxSizeHint;
148
149 if (item) {
150 // It is an internal anchor, fetch size information from the item
151 if (isLayoutAnchor) {
152 minSize = 0;
153 prefSize = 0;
155 if (isCenterAnchor)
156 maxSize /= 2;
157
160 return;
161 } else {
162 if (!isVertical) {
167 } else {
172 }
173
174 if (isCenterAnchor) {
175 minSizeHint /= 2;
176 prefSizeHint /= 2;
177 maxSizeHint /= 2;
178 }
179 }
180 } else {
181 // It is a user-created anchor, fetch size information from the associated QGraphicsAnchor
184
185 // Policy, min and max sizes are straightforward
186 policy = anchorPrivate->sizePolicy;
187 minSizeHint = 0;
188 maxSizeHint = QWIDGETSIZE_MAX;
189
190 // Preferred Size
191 if (anchorPrivate->hasSize) {
192 // Anchor has user-defined size
193 prefSizeHint = anchorPrivate->preferredSize;
194 } else if (styleInfo) {
195 // Fetch size information from style
197 qreal s = styleInfo->defaultSpacing(orient);
198 if (s < 0) {
201 s = styleInfo->perItemSpacing(controlTypeFrom, controlTypeTo, orient);
202
203 // ### Currently we do not support negative anchors inside the graph.
204 // To avoid those being created by a negative style spacing, we must
205 // make this test.
206 if (s < 0)
207 s = 0;
208 }
209 prefSizeHint = s;
210 } else {
211 prefSizeHint = 0;
212 }
213 }
214
215 // Fill minSize, prefSize and maxSize based on policy and sizeHints
216 applySizePolicy(policy, minSizeHint, prefSizeHint, maxSizeHint,
218
221
222 // Set the anchor effective sizes to preferred.
223 //
224 // Note: The idea here is that all items should remain at their
225 // preferred size unless where that's impossible. In cases where
226 // the item is subject to restrictions (anchored to the layout
227 // edges, for instance), the simplex solver will be run to
228 // recalculate and override the values we set here.
232}
233
235{
239
240 if (secondForward()) {
244 } else {
248 }
249
252}
253
254/*
255 \internal
256
257 Initialize the parallel anchor size hints using the sizeHint information from
258 its children.
259
260 Note that parallel groups can lead to unfeasibility, so during calculation, we can
261 find out one unfeasibility. Because of that this method return boolean. This can't
262 happen in sequential, so there the method is void.
263 */
265{
266 // Normalize second child sizes.
267 // A negative anchor of sizes min, minPref, pref, maxPref and max, is equivalent
268 // to a forward anchor of sizes -max, -maxPref, -pref, -minPref, -min
269 qreal secondMin;
270 qreal secondMinPref;
271 qreal secondPref;
272 qreal secondMaxPref;
273 qreal secondMax;
274
275 if (secondForward()) {
276 secondMin = secondEdge->minSize;
277 secondMinPref = secondEdge->minPrefSize;
278 secondPref = secondEdge->prefSize;
279 secondMaxPref = secondEdge->maxPrefSize;
280 secondMax = secondEdge->maxSize;
281 } else {
282 secondMin = -secondEdge->maxSize;
283 secondMinPref = -secondEdge->maxPrefSize;
284 secondPref = -secondEdge->prefSize;
285 secondMaxPref = -secondEdge->minPrefSize;
286 secondMax = -secondEdge->minSize;
287 }
288
289 minSize = qMax(firstEdge->minSize, secondMin);
290 maxSize = qMin(firstEdge->maxSize, secondMax);
291
292 // This condition means that the maximum size of one anchor being simplified is smaller than
293 // the minimum size of the other anchor. The consequence is that there won't be a valid size
294 // for this parallel setup.
295 if (minSize > maxSize) {
296 return false;
297 }
298
299 // Preferred size calculation
300 // The calculation of preferred size is done as follows:
301 //
302 // 1) Check whether one of the child anchors is the layout structural anchor
303 // If so, we can simply copy the preferred information from the other child,
304 // after bounding it to our minimum and maximum sizes.
305 // If not, then we proceed with the actual calculations.
306 //
307 // 2) The whole algorithm for preferred size calculation is based on the fact
308 // that, if a given anchor cannot remain at its preferred size, it'd rather
309 // grow than shrink.
310 //
311 // What happens though is that while this affirmative is true for simple
312 // anchors, it may not be true for sequential anchors that have one or more
313 // reversed anchors inside it. That happens because when a sequential anchor
314 // grows, any reversed anchors inside it may be required to shrink, something
315 // we try to avoid, as said above.
316 //
317 // To overcome this, besides their actual preferred size "prefSize", each anchor
318 // exports what we call "minPrefSize" and "maxPrefSize". These two values define
319 // a surrounding interval where, if required to move, the anchor would rather
320 // remain inside.
321 //
322 // For standard anchors, this area simply represents the region between
323 // prefSize and maxSize, which makes sense since our first affirmation.
324 // For composed anchors, these values are calculated as to reduce the global
325 // "damage", that is, to reduce the total deviation and the total amount of
326 // anchors that had to shrink.
327
329 prefSize = qBound(minSize, secondPref, maxSize);
330 minPrefSize = qBound(minSize, secondMinPref, maxSize);
331 maxPrefSize = qBound(minSize, secondMaxPref, maxSize);
332 } else if (secondEdge->isLayoutAnchor) {
336 } else {
337 // Calculate the intersection between the "preferred" regions of each child
338 const qreal lowerBoundary =
339 qBound(minSize, qMax(firstEdge->minPrefSize, secondMinPref), maxSize);
340 const qreal upperBoundary =
341 qBound(minSize, qMin(firstEdge->maxPrefSize, secondMaxPref), maxSize);
342 const qreal prefMean =
343 qBound(minSize, (firstEdge->prefSize + secondPref) / 2, maxSize);
344
345 if (lowerBoundary < upperBoundary) {
346 // If there is an intersection between the two regions, this intersection
347 // will be used as the preferred region of the parallel anchor itself.
348 // The preferred size will be the bounded average between the two preferred
349 // sizes.
350 prefSize = qBound(lowerBoundary, prefMean, upperBoundary);
351 minPrefSize = lowerBoundary;
352 maxPrefSize = upperBoundary;
353 } else {
354 // If there is no intersection, we have to attribute "damage" to at least
355 // one of the children. The minimum total damage is achieved in points
356 // inside the region that extends from (1) the upper boundary of the lower
357 // region to (2) the lower boundary of the upper region.
358 // Then, we expose this region as _our_ preferred region and once again,
359 // use the bounded average as our preferred size.
360 prefSize = qBound(upperBoundary, prefMean, lowerBoundary);
361 minPrefSize = upperBoundary;
362 maxPrefSize = lowerBoundary;
363 }
364 }
365
366 // See comment in AnchorData::refreshSizeHints() about sizeAt* values
370
371 return true;
372}
373
382 qreal minPref, qreal pref,
383 qreal maxPref, qreal max)
384{
386 qreal lower;
387 qreal upper;
388
389 if (value < minPref) {
391 lower = min;
392 upper = minPref;
393 } else if (value < pref) {
395 lower = minPref;
396 upper = pref;
397 } else if (value < maxPref) {
399 lower = pref;
400 upper = maxPref;
401 } else {
403 lower = maxPref;
404 upper = max;
405 }
406
407 qreal progress;
408 if (upper == lower) {
409 progress = 0;
410 } else {
411 progress = (value - lower) / (upper - lower);
412 }
413
414 return qMakePair(interval, progress);
415}
416
418 qreal min, qreal minPref, qreal pref, qreal maxPref, qreal max)
419{
420 qreal lower = 0;
421 qreal upper = 0;
422
423 switch (factor.first) {
425 lower = min;
426 upper = minPref;
427 break;
429 lower = minPref;
430 upper = pref;
431 break;
433 lower = pref;
434 upper = maxPref;
435 break;
437 lower = maxPref;
438 upper = max;
439 break;
440 }
441
442 return lower + factor.second * (upper - lower);
443}
444
446{
447 // Band here refers if the value is in the Minimum To Preferred
448 // band (the lower band) or the Preferred To Maximum (the upper band).
449
456
457 // XXX This is not safe if Vertex simplification takes place after the sequential
458 // anchor is created. In that case, "prev" will be a group-vertex, different from
459 // "from" or "to", that _contains_ one of them.
460 AnchorVertex *prev = from;
461
462 for (int i = 0; i < m_edges.size(); ++i) {
463 AnchorData *e = m_edges.at(i);
464
465 const bool edgeIsForward = (e->from == prev);
466 if (edgeIsForward) {
467 e->sizeAtMinimum = interpolate(minFactor, e->minSize, e->minPrefSize,
468 e->prefSize, e->maxPrefSize, e->maxSize);
469 e->sizeAtPreferred = interpolate(prefFactor, e->minSize, e->minPrefSize,
470 e->prefSize, e->maxPrefSize, e->maxSize);
471 e->sizeAtMaximum = interpolate(maxFactor, e->minSize, e->minPrefSize,
472 e->prefSize, e->maxPrefSize, e->maxSize);
473 prev = e->to;
474 } else {
475 Q_ASSERT(prev == e->to);
476 e->sizeAtMinimum = interpolate(minFactor, e->maxSize, e->maxPrefSize,
477 e->prefSize, e->minPrefSize, e->minSize);
478 e->sizeAtPreferred = interpolate(prefFactor, e->maxSize, e->maxPrefSize,
479 e->prefSize, e->minPrefSize, e->minSize);
480 e->sizeAtMaximum = interpolate(maxFactor, e->maxSize, e->maxPrefSize,
481 e->prefSize, e->minPrefSize, e->minSize);
482 prev = e->from;
483 }
484
485 e->updateChildrenSizes();
486 }
487}
488
490{
491 minSize = 0;
492 prefSize = 0;
493 maxSize = 0;
494 minPrefSize = 0;
495 maxPrefSize = 0;
496
497 AnchorVertex *prev = from;
498
499 for (int i = 0; i < m_edges.size(); ++i) {
500 AnchorData *edge = m_edges.at(i);
501
502 const bool edgeIsForward = (edge->from == prev);
503 if (edgeIsForward) {
504 minSize += edge->minSize;
505 prefSize += edge->prefSize;
506 maxSize += edge->maxSize;
507 minPrefSize += edge->minPrefSize;
508 maxPrefSize += edge->maxPrefSize;
509 prev = edge->to;
510 } else {
511 Q_ASSERT(prev == edge->to);
512 minSize -= edge->maxSize;
513 prefSize -= edge->prefSize;
514 maxSize -= edge->minSize;
515 minPrefSize -= edge->maxPrefSize;
516 maxPrefSize -= edge->minPrefSize;
517 prev = edge->from;
518 }
519 }
520
521 // See comment in AnchorData::refreshSizeHints() about sizeAt* values
525}
526
527#ifdef QT_DEBUG
528void AnchorData::dump(int indent) {
529 if (type == Parallel) {
530 qDebug("%*s type: parallel:", indent, "");
531 ParallelAnchorData *p = static_cast<ParallelAnchorData *>(this);
532 p->firstEdge->dump(indent+2);
533 p->secondEdge->dump(indent+2);
534 } else if (type == Sequential) {
535 SequentialAnchorData *s = static_cast<SequentialAnchorData *>(this);
536 int kids = s->m_edges.count();
537 qDebug("%*s type: sequential(%d):", indent, "", kids);
538 for (int i = 0; i < kids; ++i) {
539 s->m_edges.at(i)->dump(indent+2);
540 }
541 } else {
542 qDebug("%*s type: Normal:", indent, "");
543 }
544}
545
546#endif
547
549{
550 // Calculate
551 QSet<AnchorData *> cPositives;
552 QSet<AnchorData *> cNegatives;
553 QSet<AnchorData *> intersection;
554
555 cPositives = positives + path.negatives;
556 cNegatives = negatives + path.positives;
557
558 intersection = cPositives & cNegatives;
559
560 cPositives -= intersection;
561 cNegatives -= intersection;
562
563 // Fill
566 for (i = cPositives.begin(); i != cPositives.end(); ++i)
567 c->variables.insert(*i, 1.0);
568
569 for (i = cNegatives.begin(); i != cNegatives.end(); ++i)
570 c->variables.insert(*i, -1.0);
571
572 return c;
573}
574
575#ifdef QT_DEBUG
576QString GraphPath::toString() const
577{
579 string += "Path: "_L1;
580
581 for (AnchorData *edge : positives)
582 string += QString::fromLatin1(" (+++) %1").arg(edge->toString());
583
584 for (AnchorData *edge : negatives)
585 string += QString::fromLatin1(" (---) %1").arg(edge->toString());
586
587 return string;
588}
589#endif
590
592 : calculateGraphCacheDirty(true), styleInfoDirty(true)
593{
594}
595
597{
598 switch (edge) {
599 case Qt::AnchorLeft:
600 edge = Qt::AnchorRight;
601 break;
602 case Qt::AnchorRight:
603 edge = Qt::AnchorLeft;
604 break;
605 case Qt::AnchorTop:
606 edge = Qt::AnchorBottom;
607 break;
608 case Qt::AnchorBottom:
609 edge = Qt::AnchorTop;
610 break;
611 default:
612 break;
613 }
614 return edge;
615}
616
617
632{
633 const Qt::Orientation orientation = newAnchor->isVertical ? Qt::Vertical : Qt::Horizontal;
635 *feasible = true;
636
637 // If already exists one anchor where newAnchor is supposed to be, we create a parallel
638 // anchor.
639 if (AnchorData *oldAnchor = g.takeEdge(newAnchor->from, newAnchor->to)) {
640 ParallelAnchorData *parallel = new ParallelAnchorData(oldAnchor, newAnchor);
641
642 // The parallel anchor will "replace" its children anchors in
643 // every center constraint that they appear.
644
645 // ### If the dependent (center) anchors had reference(s) to their constraints, we
646 // could avoid traversing all the itemCenterConstraints.
648
649 AnchorData *children[2] = { oldAnchor, newAnchor };
650 QList<QSimplexConstraint *> *childrenConstraints[2] = { &parallel->m_firstConstraints,
651 &parallel->m_secondConstraints };
652
653 for (int i = 0; i < 2; ++i) {
654 AnchorData *child = children[i];
655 QList<QSimplexConstraint *> *childConstraints = childrenConstraints[i];
656
657 // We need to fix the second child constraints if the parallel group will have the
658 // opposite direction of the second child anchor. For the point of view of external
659 // entities, this anchor was reversed. So if at some point we say that the parallel
660 // has a value of 20, this mean that the second child (when reversed) will be
661 // assigned -20.
662 const bool needsReverse = i == 1 && !parallel->secondForward();
663
664 if (!child->isCenterAnchor)
665 continue;
666
667 parallel->isCenterAnchor = true;
668
669 for (int j = 0; j < constraints.size(); ++j) {
671 if (c->variables.contains(child)) {
672 childConstraints->append(c);
673 qreal v = c->variables.take(child);
674 if (needsReverse)
675 v *= -1;
676 c->variables.insert(parallel, v);
677 }
678 }
679 }
680
681 // At this point we can identify that the parallel anchor is not feasible, e.g. one
682 // anchor minimum size is bigger than the other anchor maximum size.
683 *feasible = parallel->calculateSizeHints();
684 newAnchor = parallel;
685 }
686
687 g.createEdge(newAnchor->from, newAnchor->to, newAnchor);
688 return newAnchor;
689}
690
702 const QList<AnchorVertex *> &vertices, AnchorVertex *after)
703{
704#if defined(QT_DEBUG) && 0
705 QString strVertices;
706 for (int i = 0; i < vertices.count(); ++i) {
707 strVertices += QString::fromLatin1("%1 - ").arg(vertices.at(i)->toString());
708 }
709 QString strPath = QString::fromLatin1("%1 - %2%3").arg(before->toString(), strVertices, after->toString());
710 qDebug("simplifying [%s] to [%s - %s]", qPrintable(strPath), qPrintable(before->toString()), qPrintable(after->toString()));
711#endif
712
713 AnchorVertex *prev = before;
715 edges.reserve(vertices.size() + 1);
716
717 const int numVertices = vertices.size();
718 edges.reserve(numVertices + 1);
719 // Take from the graph, the edges that will be simplificated
720 for (int i = 0; i < numVertices; ++i) {
721 AnchorVertex *next = vertices.at(i);
722 AnchorData *ad = graph->takeEdge(prev, next);
723 Q_ASSERT(ad);
724 edges.append(ad);
725 prev = next;
726 }
727
728 // Take the last edge (not covered in the loop above)
729 AnchorData *ad = graph->takeEdge(vertices.last(), after);
730 Q_ASSERT(ad);
731 edges.append(ad);
732
733 // Create sequence
734 SequentialAnchorData *sequence = new SequentialAnchorData(vertices, edges);
735 sequence->from = before;
736 sequence->to = after;
737
738 sequence->calculateSizeHints();
739
740 return sequence;
741}
742
781{
782 if (items.isEmpty())
783 return true;
784
785#if defined(QT_DEBUG) && 0
786 qDebug("Simplifying Graph for %s",
787 orientation == Horizontal ? "Horizontal" : "Vertical");
788
789 static int count = 0;
790 if (orientation == Horizontal) {
791 count++;
792 dumpGraph(QString::fromLatin1("%1-full").arg(count));
793 }
794#endif
795
796 // Vertex simplification
797 if (!simplifyVertices(orientation)) {
798 restoreVertices(orientation);
799 return false;
800 }
801
802 // Anchor simplification
803 bool dirty;
804 bool feasible = true;
805 do {
806 dirty = simplifyGraphIteration(orientation, &feasible);
807 } while (dirty && feasible);
808
809 // Note that if we are not feasible, we fallback and make sure that the graph is fully restored
810 if (!feasible) {
811 restoreSimplifiedGraph(orientation);
812 restoreVertices(orientation);
813 return false;
814 }
815
816#if defined(QT_DEBUG) && 0
817 dumpGraph(QString::fromLatin1("%1-simplified-%2").arg(count).arg(
818 QString::fromLatin1(orientation == Horizontal ? "Horizontal" : "Vertical")));
819#endif
820
821 return true;
822}
823
825{
827 if (data->from == oldV) {
828 data->from = newV;
829 other = data->to;
830 } else {
831 data->to = newV;
832 other = data->from;
833 }
834 return other;
835}
836
838 AnchorVertex *newV, const QList<AnchorData *> &edges)
839{
841 bool feasible = true;
842
843 for (int i = 0; i < edges.size(); ++i) {
844 AnchorData *ad = edges[i];
845 AnchorVertex *otherV = replaceVertex_helper(ad, oldV, newV);
846
847#if defined(QT_DEBUG)
848 ad->name = QString::fromLatin1("%1 --to--> %2").arg(ad->from->toString(), ad->to->toString());
849#endif
850
851 bool newFeasible;
852 AnchorData *newAnchor = addAnchorMaybeParallel(ad, &newFeasible);
853 feasible &= newFeasible;
854
855 if (newAnchor != ad) {
856 // A parallel was created, we mark that in the list of anchors created by vertex
857 // simplification. This is needed because we want to restore them in a separate step
858 // from the restoration of anchor simplification.
859 anchorsFromSimplifiedVertices[orientation].append(newAnchor);
860 }
861
862 g.takeEdge(oldV, otherV);
863 }
864
865 return feasible;
866}
867
872{
875
876 // We'll walk through vertices
878 stack.push(layoutFirstVertex[orientation]);
879 QSet<AnchorVertex *> visited;
880
881 while (!stack.isEmpty()) {
882 AnchorVertex *v = stack.pop();
883 visited.insert(v);
884
885 // Each adjacent of 'v' is a possible vertex to be merged. So we traverse all of
886 // them. Since once a merge is made, we might add new adjacents, and we don't want to
887 // pass two times through one adjacent. The 'index' is used to track our position.
888 QList<AnchorVertex *> adjacents = g.adjacentVertices(v);
889 int index = 0;
890
891 while (index < adjacents.size()) {
892 AnchorVertex *next = adjacents.at(index);
893 index++;
894
895 AnchorData *data = g.edgeData(v, next);
896 const bool bothLayoutVertices = v->m_item == q && next->m_item == q;
897 const bool zeroSized = !data->minSize && !data->maxSize;
898
899 if (!bothLayoutVertices && zeroSized) {
900
901 // Create a new vertex pair, note that we keep a list of those vertices so we can
902 // easily process them when restoring the graph.
904 simplifiedVertices[orientation].append(newV);
905
906 // Collect the anchors of both vertices, the new vertex pair will take their place
907 // in those anchors
908 const QList<AnchorVertex *> &vAdjacents = g.adjacentVertices(v);
909 const QList<AnchorVertex *> &nextAdjacents = g.adjacentVertices(next);
910
911 for (int i = 0; i < vAdjacents.size(); ++i) {
912 AnchorVertex *adjacent = vAdjacents.at(i);
913 if (adjacent != next) {
914 AnchorData *ad = g.edgeData(v, adjacent);
915 newV->m_firstAnchors.append(ad);
916 }
917 }
918
919 for (int i = 0; i < nextAdjacents.size(); ++i) {
920 AnchorVertex *adjacent = nextAdjacents.at(i);
921 if (adjacent != v) {
922 AnchorData *ad = g.edgeData(next, adjacent);
923 newV->m_secondAnchors.append(ad);
924
925 // We'll also add new vertices to the adjacent list of the new 'v', to be
926 // created as a vertex pair and replace the current one.
927 if (!adjacents.contains(adjacent))
928 adjacents.append(adjacent);
929 }
930 }
931
932 // ### merge this loop into the ones that calculated m_firstAnchors/m_secondAnchors?
933 // Make newV take the place of v and next
934 bool feasible = replaceVertex(orientation, v, newV, newV->m_firstAnchors);
935 feasible &= replaceVertex(orientation, next, newV, newV->m_secondAnchors);
936
937 // Update the layout vertex information if one of the vertices is a layout vertex.
938 AnchorVertex *layoutVertex = nullptr;
939 if (v->m_item == q)
940 layoutVertex = v;
941 else if (next->m_item == q)
942 layoutVertex = next;
943
944 if (layoutVertex) {
945 // Layout vertices always have m_item == q...
946 newV->m_item = q;
947 changeLayoutVertex(orientation, layoutVertex, newV);
948 }
949
950 g.takeEdge(v, next);
951
952 // If a non-feasibility is found, we leave early and cancel the simplification
953 if (!feasible)
954 return false;
955
956 v = newV;
957 visited.insert(newV);
958
959 } else if (!visited.contains(next) && !stack.contains(next)) {
960 // If the adjacent is not fit for merge and it wasn't visited by the outermost
961 // loop, we add it to the stack.
962 stack.push(next);
963 }
964 }
965 }
966
967 return true;
968}
969
985 bool *feasible)
986{
989
990 QSet<AnchorVertex *> visited;
992 stack.push(qMakePair(static_cast<AnchorVertex *>(nullptr), layoutFirstVertex[orientation]));
993 QList<AnchorVertex *> candidates;
994
995 // Walk depth-first, in the stack we store start of the candidate sequence (beforeSequence)
996 // and the vertex to be visited.
997 while (!stack.isEmpty()) {
999 AnchorVertex *beforeSequence = pair.first;
1000 AnchorVertex *v = pair.second;
1001
1002 // The basic idea is to determine whether we found an end of sequence,
1003 // if that's the case, we stop adding vertices to the candidate list
1004 // and do a simplification step.
1005 //
1006 // A vertex can trigger an end of sequence if
1007 // (a) it is a layout vertex, we don't simplify away the layout vertices;
1008 // (b) it does not have exactly 2 adjacents;
1009 // (c) its next adjacent is already visited (a cycle in the graph).
1010 // (d) the next anchor is a center anchor.
1011
1012 const QList<AnchorVertex *> &adjacents = g.adjacentVertices(v);
1013 const bool isLayoutVertex = v->m_item == q;
1014 AnchorVertex *afterSequence = v;
1015 bool endOfSequence = false;
1016
1017 //
1018 // Identify the end cases.
1019 //
1020
1021 // Identifies cases (a) and (b)
1022 endOfSequence = isLayoutVertex || adjacents.size() != 2;
1023
1024 if (!endOfSequence) {
1025 // This is a tricky part. We peek at the next vertex to find out whether
1026 //
1027 // - we already visited the next vertex (c);
1028 // - the next anchor is a center (d).
1029 //
1030 // Those are needed to identify the remaining end of sequence cases. Note that unlike
1031 // (a) and (b), we preempt the end of sequence by looking into the next vertex.
1032
1033 // Peek at the next vertex
1034 AnchorVertex *after;
1035 if (candidates.isEmpty())
1036 after = (beforeSequence == adjacents.last() ? adjacents.first() : adjacents.last());
1037 else
1038 after = (candidates.constLast() == adjacents.last() ? adjacents.first() : adjacents.last());
1039
1040 // ### At this point we assumed that candidates will not contain 'after', this may not hold
1041 // when simplifying FLOATing anchors.
1042 Q_ASSERT(!candidates.contains(after));
1043
1044 const AnchorData *data = g.edgeData(v, after);
1045 Q_ASSERT(data);
1046 const bool cycleFound = visited.contains(after);
1047
1048 // Now cases (c) and (d)...
1049 endOfSequence = cycleFound || data->isCenterAnchor;
1050
1051 if (!endOfSequence) {
1052 // If it's not an end of sequence, then the vertex didn't trigger neither of the
1053 // previously three cases, so it can be added to the candidates list.
1054 candidates.append(v);
1055 } else if (cycleFound && (beforeSequence != after)) {
1056 afterSequence = after;
1057 candidates.append(v);
1058 }
1059 }
1060
1061 //
1062 // Add next non-visited vertices to the stack.
1063 //
1064 for (int i = 0; i < adjacents.size(); ++i) {
1065 AnchorVertex *next = adjacents.at(i);
1066 if (visited.contains(next))
1067 continue;
1068
1069 // If current vertex is an end of sequence, and it'll reset the candidates list. So
1070 // the next vertices will build candidates lists with the current vertex as 'before'
1071 // vertex. If it's not an end of sequence, we keep the original 'before' vertex,
1072 // since we are keeping the candidates list.
1073 if (endOfSequence)
1074 stack.push(qMakePair(v, next));
1075 else
1076 stack.push(qMakePair(beforeSequence, next));
1077 }
1078
1079 visited.insert(v);
1080
1081 if (!endOfSequence || candidates.isEmpty())
1082 continue;
1083
1084 //
1085 // Create a sequence for (beforeSequence, candidates, afterSequence).
1086 //
1087
1088 // One restriction we have is to not simplify half of an anchor and let the other half
1089 // unsimplified. So we remove center edges before and after the sequence.
1090 const AnchorData *firstAnchor = g.edgeData(beforeSequence, candidates.constFirst());
1091 if (firstAnchor->isCenterAnchor) {
1092 beforeSequence = candidates.constFirst();
1093 candidates.remove(0);
1094
1095 // If there's not candidates to be simplified, leave.
1096 if (candidates.isEmpty())
1097 continue;
1098 }
1099
1100 const AnchorData *lastAnchor = g.edgeData(candidates.constLast(), afterSequence);
1101 if (lastAnchor->isCenterAnchor) {
1102 afterSequence = candidates.constLast();
1103 candidates.remove(candidates.size() - 1);
1104
1105 if (candidates.isEmpty())
1106 continue;
1107 }
1108
1109 //
1110 // Add the sequence to the graph.
1111 //
1112
1113 AnchorData *sequence = createSequence(&g, beforeSequence, candidates, afterSequence);
1114
1115 // If 'beforeSequence' and 'afterSequence' already had an anchor between them, we'll
1116 // create a parallel anchor between the new sequence and the old anchor.
1117 bool newFeasible;
1118 AnchorData *newAnchor = addAnchorMaybeParallel(sequence, &newFeasible);
1119
1120 if (!newFeasible) {
1121 *feasible = false;
1122 return false;
1123 }
1124
1125 // When a new parallel anchor is create in the graph, we finish the iteration and return
1126 // true to indicate a new iteration is needed. This happens because a parallel anchor
1127 // changes the number of adjacents one vertex has, possibly opening up oportunities for
1128 // building candidate lists (when adjacents == 2).
1129 if (newAnchor != sequence)
1130 return true;
1131
1132 // If there was no parallel simplification, we'll keep walking the graph. So we clear the
1133 // candidates list to start again.
1134 candidates.clear();
1135 }
1136
1137 return false;
1138}
1139
1141{
1142 const Qt::Orientation orientation = edge->isVertical ? Qt::Vertical : Qt::Horizontal;
1143#if 0
1144 static const char *anchortypes[] = {"Normal",
1145 "Sequential",
1146 "Parallel"};
1147 qDebug("Restoring %s edge.", anchortypes[int(edge->type)]);
1148#endif
1149
1150 Graph<AnchorVertex, AnchorData> &g = graph[orientation];
1151
1152 if (edge->type == AnchorData::Normal) {
1153 g.createEdge(edge->from, edge->to, edge);
1154
1155 } else if (edge->type == AnchorData::Sequential) {
1156 SequentialAnchorData *sequence = static_cast<SequentialAnchorData *>(edge);
1157
1158 for (int i = 0; i < sequence->m_edges.size(); ++i) {
1159 AnchorData *data = sequence->m_edges.at(i);
1161 }
1162
1163 delete sequence;
1164
1165 } else if (edge->type == AnchorData::Parallel) {
1166
1167 // Skip parallel anchors that were created by vertex simplification, they will be processed
1168 // later, when restoring vertex simplification.
1169 // ### we could improve this check bit having a bit inside 'edge'
1170 if (anchorsFromSimplifiedVertices[orientation].contains(edge))
1171 return;
1172
1173 ParallelAnchorData* parallel = static_cast<ParallelAnchorData*>(edge);
1175
1176 // ### Because of the way parallel anchors are created in the anchor simplification
1177 // algorithm, we know that one of these will be a sequence, so it'll be safe if the other
1178 // anchor create an edge between the same vertices as the parallel.
1180 || parallel->secondEdge->type == AnchorData::Sequential);
1183
1184 delete parallel;
1185 }
1186}
1187
1189{
1190 if (!parallel->isCenterAnchor)
1191 return;
1192
1193 for (int i = 0; i < parallel->m_firstConstraints.size(); ++i) {
1195 qreal v = c->variables[parallel];
1196 c->variables.remove(parallel);
1197 c->variables.insert(parallel->firstEdge, v);
1198 }
1199
1200 // When restoring, we might have to revert constraints back. See comments on
1201 // addAnchorMaybeParallel().
1202 const bool needsReverse = !parallel->secondForward();
1203
1204 for (int i = 0; i < parallel->m_secondConstraints.size(); ++i) {
1206 qreal v = c->variables[parallel];
1207 if (needsReverse)
1208 v *= -1;
1209 c->variables.remove(parallel);
1210 c->variables.insert(parallel->secondEdge, v);
1211 }
1212}
1213
1215{
1216#if 0
1217 qDebug("Restoring Simplified Graph for %s",
1218 orientation == Horizontal ? "Horizontal" : "Vertical");
1219#endif
1220
1221 // Restore anchor simplification
1222 Graph<AnchorVertex, AnchorData> &g = graph[orientation];
1223 QList<QPair<AnchorVertex *, AnchorVertex *>> connections = g.connections();
1224 for (int i = 0; i < connections.size(); ++i) {
1225 AnchorVertex *v1 = connections.at(i).first;
1226 AnchorVertex *v2 = connections.at(i).second;
1227 AnchorData *edge = g.edgeData(v1, v2);
1228
1229 // We restore only sequential anchors and parallels that were not created by
1230 // vertex simplification.
1231 if (edge->type == AnchorData::Sequential
1232 || (edge->type == AnchorData::Parallel &&
1233 !anchorsFromSimplifiedVertices[orientation].contains(edge))) {
1234
1235 g.takeEdge(v1, v2);
1237 }
1238 }
1239
1240 restoreVertices(orientation);
1241}
1242
1244{
1246
1247 Graph<AnchorVertex, AnchorData> &g = graph[orientation];
1248 QList<AnchorVertexPair *> &toRestore = simplifiedVertices[orientation];
1249
1250 // Since we keep a list of parallel anchors and vertices that were created during vertex
1251 // simplification, we can now iterate on those lists instead of traversing the graph
1252 // recursively.
1253
1254 // First, restore the constraints changed when we created parallel anchors. Note that this
1255 // works at this point because the constraints doesn't depend on vertex information and at
1256 // this point it's always safe to identify whether the second child is forward or backwards.
1257 // In the next step, we'll change the anchors vertices so that would not be possible anymore.
1258 QList<AnchorData *> &parallelAnchors = anchorsFromSimplifiedVertices[orientation];
1259
1260 for (int i = parallelAnchors.size() - 1; i >= 0; --i) {
1261 ParallelAnchorData *parallel = static_cast<ParallelAnchorData *>(parallelAnchors.at(i));
1263 }
1264
1265 // Then, we will restore the vertices in the inverse order of creation, this way we ensure that
1266 // the vertex being restored was not wrapped by another simplification.
1267 for (int i = toRestore.size() - 1; i >= 0; --i) {
1268 AnchorVertexPair *pair = toRestore.at(i);
1269 QList<AnchorVertex *> adjacents = g.adjacentVertices(pair);
1270
1271 // Restore the removed edge, this will also restore both vertices 'first' and 'second' to
1272 // the graph structure.
1273 AnchorVertex *first = pair->m_first;
1274 AnchorVertex *second = pair->m_second;
1275 g.createEdge(first, second, pair->m_removedAnchor);
1276
1277 // Restore the anchors for the first child vertex
1278 for (int j = 0; j < pair->m_firstAnchors.size(); ++j) {
1279 AnchorData *ad = pair->m_firstAnchors.at(j);
1280 Q_ASSERT(ad->from == pair || ad->to == pair);
1281
1282 replaceVertex_helper(ad, pair, first);
1283 g.createEdge(ad->from, ad->to, ad);
1284 }
1285
1286 // Restore the anchors for the second child vertex
1287 for (int j = 0; j < pair->m_secondAnchors.size(); ++j) {
1288 AnchorData *ad = pair->m_secondAnchors.at(j);
1289 Q_ASSERT(ad->from == pair || ad->to == pair);
1290
1291 replaceVertex_helper(ad, pair, second);
1292 g.createEdge(ad->from, ad->to, ad);
1293 }
1294
1295 for (int j = 0; j < adjacents.size(); ++j) {
1296 g.takeEdge(pair, adjacents.at(j));
1297 }
1298
1299 // The pair simplified a layout vertex, so place back the correct vertex in the variable
1300 // that track layout vertices
1301 if (pair->m_item == q) {
1302 AnchorVertex *layoutVertex = first->m_item == q ? first : second;
1303 Q_ASSERT(layoutVertex->m_item == q);
1304 changeLayoutVertex(orientation, pair, layoutVertex);
1305 }
1306
1307 delete pair;
1308 }
1309 qDeleteAll(parallelAnchors);
1310 parallelAnchors.clear();
1311 toRestore.clear();
1312}
1313
1316{
1318}
1319
1330{
1333
1334 // Horizontal
1335 AnchorData *data = new AnchorData;
1338 data->maxSize = QWIDGETSIZE_MAX;
1339
1340 // Save a reference to layout vertices
1344
1345 // Vertical
1346 data = new AnchorData;
1349 data->maxSize = QWIDGETSIZE_MAX;
1350
1351 // Save a reference to layout vertices
1355}
1356
1358{
1360
1363
1368}
1369
1371{
1372 items.append(item);
1373
1374 // Create horizontal and vertical internal anchors for the item and
1375 // refresh its size hint / policy values.
1376 AnchorData *data = new AnchorData;
1378 data->refreshSizeHints();
1379
1380 data = new AnchorData;
1382 data->refreshSizeHints();
1383}
1384
1398{
1400
1401 Qt::Orientation orientation;
1402 switch (centerEdge) {
1404 orientation = Qt::Horizontal;
1405 break;
1407 orientation = Qt::Vertical;
1408 break;
1409 default:
1410 // Don't create center edges unless needed
1411 return;
1412 }
1413
1414 // Check if vertex already exists
1415 if (internalVertex(item, centerEdge))
1416 return;
1417
1418 // Orientation code
1419 Qt::AnchorPoint firstEdge;
1420 Qt::AnchorPoint lastEdge;
1421
1422 if (orientation == Qt::Horizontal) {
1423 firstEdge = Qt::AnchorLeft;
1424 lastEdge = Qt::AnchorRight;
1425 } else {
1426 firstEdge = Qt::AnchorTop;
1427 lastEdge = Qt::AnchorBottom;
1428 }
1429
1430 AnchorVertex *first = internalVertex(item, firstEdge);
1431 AnchorVertex *last = internalVertex(item, lastEdge);
1432 Q_ASSERT(first && last);
1433
1434 // Create new anchors
1436
1437 AnchorData *data = new AnchorData;
1438 c->variables.insert(data, 1.0);
1439 addAnchor_helper(item, firstEdge, item, centerEdge, data);
1440 data->isCenterAnchor = true;
1441 data->dependency = AnchorData::Master;
1442 data->refreshSizeHints();
1443
1444 data = new AnchorData;
1445 c->variables.insert(data, -1.0);
1446 addAnchor_helper(item, centerEdge, item, lastEdge, data);
1447 data->isCenterAnchor = true;
1448 data->dependency = AnchorData::Slave;
1449 data->refreshSizeHints();
1450
1451 itemCenterConstraints[orientation].append(c);
1452
1453 // Remove old one
1455
1456 if (item == q) {
1457 layoutCentralVertex[orientation] = internalVertex(q, centerEdge);
1458 }
1459}
1460
1463 bool substitute)
1464{
1466
1467 Qt::Orientation orientation;
1468 switch (centerEdge) {
1470 orientation = Qt::Horizontal;
1471 break;
1473 orientation = Qt::Vertical;
1474 break;
1475 default:
1476 // Don't remove edges that not the center ones
1477 return;
1478 }
1479
1480 // Orientation code
1481 Qt::AnchorPoint firstEdge;
1482 Qt::AnchorPoint lastEdge;
1483
1484 if (orientation == Qt::Horizontal) {
1485 firstEdge = Qt::AnchorLeft;
1486 lastEdge = Qt::AnchorRight;
1487 } else {
1488 firstEdge = Qt::AnchorTop;
1489 lastEdge = Qt::AnchorBottom;
1490 }
1491
1492 AnchorVertex *center = internalVertex(item, centerEdge);
1493 if (!center)
1494 return;
1495 AnchorVertex *first = internalVertex(item, firstEdge);
1496
1497 Q_ASSERT(first);
1498 Q_ASSERT(center);
1499
1500 Graph<AnchorVertex, AnchorData> &g = graph[orientation];
1501
1502
1503 AnchorData *oldData = g.edgeData(first, center);
1504 // Remove center constraint
1505 for (int i = itemCenterConstraints[orientation].size() - 1; i >= 0; --i) {
1506 if (itemCenterConstraints[orientation].at(i)->variables.contains(oldData)) {
1507 delete itemCenterConstraints[orientation].takeAt(i);
1508 break;
1509 }
1510 }
1511
1512 if (substitute) {
1513 // Create the new anchor that should substitute the left-center-right anchors.
1514 AnchorData *data = new AnchorData;
1515 addAnchor_helper(item, firstEdge, item, lastEdge, data);
1516 data->refreshSizeHints();
1517
1518 // Remove old anchors
1519 removeAnchor_helper(first, center);
1520 removeAnchor_helper(center, internalVertex(item, lastEdge));
1521
1522 } else {
1523 // this is only called from removeAnchors()
1524 // first, remove all non-internal anchors
1525 QList<AnchorVertex*> adjacents = g.adjacentVertices(center);
1526 for (int i = 0; i < adjacents.size(); ++i) {
1527 AnchorVertex *v = adjacents.at(i);
1528 if (v->m_item != item) {
1529 removeAnchor_helper(center, internalVertex(v->m_item, v->m_edge));
1530 }
1531 }
1532 // when all non-internal anchors is removed it will automatically merge the
1533 // center anchor into a left-right (or top-bottom) anchor. We must also delete that.
1534 // by this time, the center vertex is deleted and merged into a non-centered internal anchor
1536 }
1537
1538 if (item == q) {
1539 layoutCentralVertex[orientation] = nullptr;
1540 }
1541}
1542
1543
1545 Qt::Orientation orientation)
1546{
1547 // Remove the item center constraints associated to this item
1548 // ### This is a temporary solution. We should probably use a better
1549 // data structure to hold items and/or their associated constraints
1550 // so that we can remove those easily
1551
1555 AnchorVertex *center = internalVertex(item, orientation == Qt::Horizontal ?
1558
1559 // Skip if no center constraints exist
1560 if (!center)
1561 return;
1562
1563 Q_ASSERT(first);
1564 AnchorData *internalAnchor = graph[orientation].edgeData(first, center);
1565
1566 // Look for our anchor in all item center constraints, then remove it
1567 for (int i = 0; i < itemCenterConstraints[orientation].size(); ++i) {
1568 if (itemCenterConstraints[orientation].at(i)->variables.contains(internalAnchor)) {
1569 delete itemCenterConstraints[orientation].takeAt(i);
1570 break;
1571 }
1572 }
1573}
1574
1588 Qt::AnchorPoint firstEdge,
1589 QGraphicsLayoutItem *secondItem,
1590 Qt::AnchorPoint secondEdge,
1591 qreal *spacing)
1592{
1594 if ((firstItem == nullptr) || (secondItem == nullptr)) {
1595 qWarning("QGraphicsAnchorLayout::addAnchor(): "
1596 "Cannot anchor NULL items");
1597 return nullptr;
1598 }
1599
1600 if (firstItem == secondItem) {
1601 qWarning("QGraphicsAnchorLayout::addAnchor(): "
1602 "Cannot anchor the item to itself");
1603 return nullptr;
1604 }
1605
1606 if (edgeOrientation(secondEdge) != edgeOrientation(firstEdge)) {
1607 qWarning("QGraphicsAnchorLayout::addAnchor(): "
1608 "Cannot anchor edges of different orientations");
1609 return nullptr;
1610 }
1611
1612 const QGraphicsLayoutItem *parentWidget = q->parentLayoutItem();
1613 if (firstItem == parentWidget || secondItem == parentWidget) {
1614 qWarning("QGraphicsAnchorLayout::addAnchor(): "
1615 "You cannot add the parent of the layout to the layout.");
1616 return nullptr;
1617 }
1618
1619 // In QGraphicsAnchorLayout, items are represented in its internal
1620 // graph as four anchors that connect:
1621 // - Left -> HCenter
1622 // - HCenter-> Right
1623 // - Top -> VCenter
1624 // - VCenter -> Bottom
1625
1626 // Ensure that the internal anchors have been created for both items.
1627 if (firstItem != q && !items.contains(firstItem)) {
1628 createItemEdges(firstItem);
1629 addChildLayoutItem(firstItem);
1630 }
1631 if (secondItem != q && !items.contains(secondItem)) {
1632 createItemEdges(secondItem);
1633 addChildLayoutItem(secondItem);
1634 }
1635
1636 // Create center edges if needed
1637 createCenterAnchors(firstItem, firstEdge);
1638 createCenterAnchors(secondItem, secondEdge);
1639
1640 // Use heuristics to find out what the user meant with this anchor.
1641 correctEdgeDirection(firstItem, firstEdge, secondItem, secondEdge);
1642
1643 AnchorData *data = new AnchorData;
1644 QGraphicsAnchor *graphicsAnchor = acquireGraphicsAnchor(data);
1645
1646 addAnchor_helper(firstItem, firstEdge, secondItem, secondEdge, data);
1647
1648 if (spacing) {
1649 graphicsAnchor->setSpacing(*spacing);
1650 } else {
1651 // If firstItem or secondItem is the layout itself, the spacing will default to 0.
1652 // Otherwise, the following matrix is used (questionmark means that the spacing
1653 // is queried from the style):
1654 // from
1655 // to Left HCenter Right
1656 // Left 0 0 ?
1657 // HCenter 0 0 0
1658 // Right ? 0 0
1659 if (firstItem == q
1660 || secondItem == q
1662 || oppositeEdge(firstEdge) != secondEdge) {
1663 graphicsAnchor->setSpacing(0);
1664 } else {
1665 graphicsAnchor->unsetSpacing();
1666 }
1667 }
1668
1669 return graphicsAnchor;
1670}
1671
1672/*
1673 \internal
1674
1675 This method adds an AnchorData to the internal graph. It is responsible for doing
1676 the boilerplate part of such task.
1677
1678 If another AnchorData exists between the mentioned vertices, it is deleted and
1679 the new one is inserted.
1680*/
1682 Qt::AnchorPoint firstEdge,
1683 QGraphicsLayoutItem *secondItem,
1684 Qt::AnchorPoint secondEdge,
1686{
1688
1689 const Qt::Orientation orientation = edgeOrientation(firstEdge);
1690
1691 // Create or increase the reference count for the related vertices.
1692 AnchorVertex *v1 = addInternalVertex(firstItem, firstEdge);
1693 AnchorVertex *v2 = addInternalVertex(secondItem, secondEdge);
1694
1695 // Remove previous anchor
1696 if (graph[orientation].edgeData(v1, v2)) {
1698 }
1699
1700 // If its an internal anchor, set the associated item
1701 if (firstItem == secondItem)
1702 data->item = firstItem;
1703
1704 data->isVertical = orientation == Qt::Vertical;
1705
1706 // Create a bi-directional edge in the sense it can be transversed both
1707 // from v1 or v2. "data" however is shared between the two references
1708 // so we still know that the anchor direction is from 1 to 2.
1709 data->from = v1;
1710 data->to = v2;
1711#ifdef QT_DEBUG
1712 data->name = QString::fromLatin1("%1 --to--> %2").arg(v1->toString(), v2->toString());
1713#endif
1714 // ### bit to track internal anchors, since inside AnchorData methods
1715 // we don't have access to the 'q' pointer.
1716 data->isLayoutAnchor = (data->item == q);
1717
1718 graph[orientation].createEdge(v1, v2, data);
1719}
1720
1722 Qt::AnchorPoint firstEdge,
1723 QGraphicsLayoutItem *secondItem,
1724 Qt::AnchorPoint secondEdge)
1725{
1726 // Do not expose internal anchors
1727 if (firstItem == secondItem)
1728 return nullptr;
1729
1730 const Qt::Orientation orientation = edgeOrientation(firstEdge);
1731 AnchorVertex *v1 = internalVertex(firstItem, firstEdge);
1732 AnchorVertex *v2 = internalVertex(secondItem, secondEdge);
1733
1734 QGraphicsAnchor *graphicsAnchor = nullptr;
1735
1736 AnchorData *data = graph[orientation].edgeData(v1, v2);
1737 if (data) {
1738 // We could use "acquireGraphicsAnchor" here, but to avoid a regression where
1739 // an internal anchor was wrongly exposed, I want to ensure no new
1740 // QGraphicsAnchor instances are created by this call.
1741 // This assumption must hold because anchors are either user-created (and already
1742 // have their public object created), or they are internal (and must not reach
1743 // this point).
1744 Q_ASSERT(data->graphicsAnchor);
1745 graphicsAnchor = data->graphicsAnchor;
1746 }
1747 return graphicsAnchor;
1748}
1749
1757 AnchorVertex *secondVertex)
1758{
1760
1761 // Save references to items while it's safe to assume the vertices exist
1762 QGraphicsLayoutItem *firstItem = firstVertex->m_item;
1763 QGraphicsLayoutItem *secondItem = secondVertex->m_item;
1764
1765 // Delete the anchor (may trigger deletion of center vertices)
1766 removeAnchor_helper(firstVertex, secondVertex);
1767
1768 // Ensure no dangling pointer is left behind
1769 firstVertex = secondVertex = nullptr;
1770
1771 // Checking if the item stays in the layout or not
1772 bool keepFirstItem = false;
1773 bool keepSecondItem = false;
1774
1776 int refcount = -1;
1777
1778 if (firstItem != q) {
1779 for (int i = Qt::AnchorLeft; i <= Qt::AnchorBottom; ++i) {
1780 v = m_vertexList.value(qMakePair(firstItem, static_cast<Qt::AnchorPoint>(i)));
1781 if (v.first) {
1783 refcount = 2;
1784 else
1785 refcount = 1;
1786
1787 if (v.second > refcount) {
1788 keepFirstItem = true;
1789 break;
1790 }
1791 }
1792 }
1793 } else
1794 keepFirstItem = true;
1795
1796 if (secondItem != q) {
1797 for (int i = Qt::AnchorLeft; i <= Qt::AnchorBottom; ++i) {
1798 v = m_vertexList.value(qMakePair(secondItem, static_cast<Qt::AnchorPoint>(i)));
1799 if (v.first) {
1801 refcount = 2;
1802 else
1803 refcount = 1;
1804
1805 if (v.second > refcount) {
1806 keepSecondItem = true;
1807 break;
1808 }
1809 }
1810 }
1811 } else
1812 keepSecondItem = true;
1813
1814 if (!keepFirstItem)
1815 q->removeAt(items.indexOf(firstItem));
1816
1817 if (!keepSecondItem)
1818 q->removeAt(items.indexOf(secondItem));
1819
1820 // Removing anchors invalidates the layout
1821 q->invalidate();
1822}
1823
1824/*
1825 \internal
1826
1827 Implements the low level "removeAnchor" feature. Called by
1828 private methods.
1829*/
1831{
1832 Q_ASSERT(v1 && v2);
1833
1834 // Remove edge from graph
1835 const Qt::Orientation o = edgeOrientation(v1->m_edge);
1836 graph[o].removeEdge(v1, v2);
1837
1838 // Decrease vertices reference count (may trigger a deletion)
1839 removeInternalVertex(v1->m_item, v1->m_edge);
1840 removeInternalVertex(v2->m_item, v2->m_edge);
1841}
1842
1844 Qt::AnchorPoint edge)
1845{
1848
1849 if (!v.first) {
1850 Q_ASSERT(v.second == 0);
1851 v.first = new AnchorVertex(item, edge);
1852 }
1853 v.second++;
1854 m_vertexList.insert(pair, v);
1855 return v.first;
1856}
1857
1865 Qt::AnchorPoint edge)
1866{
1869
1870 if (!v.first) {
1871 qWarning("This item with this edge is not in the graph");
1872 return;
1873 }
1874
1875 v.second--;
1876 if (v.second == 0) {
1877 // Remove reference and delete vertex
1878 m_vertexList.remove(pair);
1879 delete v.first;
1880 } else {
1881 // Update reference count
1882 m_vertexList.insert(pair, v);
1883
1884 if ((v.second == 2) &&
1885 ((edge == Qt::AnchorHorizontalCenter) ||
1886 (edge == Qt::AnchorVerticalCenter))) {
1887 removeCenterAnchors(item, edge, true);
1888 }
1889 }
1890}
1891
1893{
1894 if (AnchorVertex *v = internalVertex(item, edge)) {
1896 const auto allVertices = g.adjacentVertices(v);
1897 for (auto *v2 : allVertices) {
1898 g.removeEdge(v, v2);
1900 removeInternalVertex(v2->m_item, v2->m_edge);
1901 }
1902 }
1903}
1904
1906{
1907 // remove the center anchor first!!
1911
1915}
1916
1942 Qt::AnchorPoint &firstEdge,
1943 QGraphicsLayoutItem *&secondItem,
1944 Qt::AnchorPoint &secondEdge)
1945{
1947
1948 if ((firstItem != q) && (secondItem != q)) {
1949 // If connection is between widgets (not the layout itself)
1950 // Ensure that "right-edges" sit to the left of "left-edges".
1951 if (firstEdge < secondEdge) {
1952 qSwap(firstItem, secondItem);
1953 qSwap(firstEdge, secondEdge);
1954 }
1955 } else if (firstItem == q) {
1956 // If connection involves the right or bottom of a layout, ensure
1957 // the layout is the second item.
1958 if ((firstEdge == Qt::AnchorRight) || (firstEdge == Qt::AnchorBottom)) {
1959 qSwap(firstItem, secondItem);
1960 qSwap(firstEdge, secondEdge);
1961 }
1962 } else if ((secondEdge != Qt::AnchorRight) && (secondEdge != Qt::AnchorBottom)) {
1963 // If connection involves the left, center or top of layout, ensure
1964 // the layout is the first item.
1965 qSwap(firstItem, secondItem);
1966 qSwap(firstEdge, secondEdge);
1967 }
1968}
1969
1971{
1972 if (styleInfoDirty) {
1973 Q_Q(const QGraphicsAnchorLayout);
1974 //### Fix this if QGV ever gets support for Metal style or different Aqua sizes.
1975 QWidget *wid = nullptr;
1976
1978 while (parent && parent->isLayout()) {
1980 }
1981 QGraphicsWidget *w = nullptr;
1982 if (parent) {
1984 if (parentItem && parentItem->isWidget())
1985 w = static_cast<QGraphicsWidget*>(parentItem);
1986 }
1987
1988 QStyle *style = w ? w->style() : QApplication::style();
1989 cachedStyleInfo = QLayoutStyleInfo(style, wid);
1992
1993 styleInfoDirty = false;
1994 }
1995 return cachedStyleInfo;
1996}
1997
2006{
2008 return;
2012}
2013
2014// ### Maybe getGraphParts could return the variables when traversing, at least
2015// for trunk...
2017{
2018 QSet<AnchorData *> variableSet;
2019 for (int i = 0; i < constraints.size(); ++i) {
2020 const QSimplexConstraint *c = constraints.at(i);
2021 for (auto it = c->variables.cbegin(), end = c->variables.cend(); it != end; ++it)
2022 variableSet.insert(static_cast<AnchorData *>(it.key()));
2023 }
2024 return variableSet.values();
2025}
2026
2051{
2052#if defined(QT_DEBUG) || defined(QT_BUILD_INTERNAL)
2053 lastCalculationUsedSimplex[orientation] = false;
2054#endif
2055
2056 static bool simplificationEnabled = qEnvironmentVariableIsEmpty("QT_ANCHORLAYOUT_NO_SIMPLIFICATION");
2057
2058 // Reset the nominal sizes of each anchor based on the current item sizes
2059 refreshAllSizeHints(orientation);
2060
2061 // Simplify the graph
2062 if (simplificationEnabled && !simplifyGraph(orientation)) {
2063 qWarning("QGraphicsAnchorLayout: anchor setup is not feasible.");
2064 graphHasConflicts[orientation] = true;
2065 return;
2066 }
2067
2068 // Traverse all graph edges and store the possible paths to each vertex
2069 findPaths(orientation);
2070
2071 // From the paths calculated above, extract the constraints that the current
2072 // anchor setup impose, to our Linear Programming problem.
2073 constraintsFromPaths(orientation);
2074
2075 // Split the constraints and anchors into groups that should be fed to the
2076 // simplex solver independently. Currently we find two groups:
2077 //
2078 // 1) The "trunk", that is, the set of anchors (items) that are connected
2079 // to the two opposite sides of our layout, and thus need to stretch in
2080 // order to fit in the current layout size.
2081 //
2082 // 2) The floating or semi-floating anchors (items) that are those which
2083 // are connected to only one (or none) of the layout sides, thus are not
2084 // influenced by the layout size.
2085 const auto parts = getGraphParts(orientation);
2086
2087 // Now run the simplex solver to calculate Minimum, Preferred and Maximum sizes
2088 // of the "trunk" set of constraints and variables.
2089 // ### does trunk always exist? empty = trunk is the layout left->center->right
2090 const QList<AnchorData *> trunkVariables = getVariables(parts.trunkConstraints);
2091
2092 // For minimum and maximum, use the path between the two layout sides as the
2093 // objective function.
2094 AnchorVertex *v = layoutLastVertex[orientation];
2095 GraphPath trunkPath = graphPaths[orientation].value(v);
2096
2097 bool feasible = calculateTrunk(orientation, trunkPath, parts.trunkConstraints, trunkVariables);
2098
2099 // For the other parts that not the trunk, solve only for the preferred size
2100 // that is the size they will remain at, since they are not stretched by the
2101 // layout.
2102
2103 if (feasible && !parts.nonTrunkConstraints.isEmpty()) {
2104 const QList<AnchorData *> partVariables = getVariables(parts.nonTrunkConstraints);
2105 Q_ASSERT(!partVariables.isEmpty());
2106 feasible = calculateNonTrunk(parts.nonTrunkConstraints, partVariables);
2107 }
2108
2109 // Propagate the new sizes down the simplified graph, ie. tell the
2110 // group anchors to set their children anchors sizes.
2111 updateAnchorSizes(orientation);
2112
2113 graphHasConflicts[orientation] = !feasible;
2114
2115 // Clean up our data structures. They are not needed anymore since
2116 // distribution uses just interpolation.
2117 qDeleteAll(constraints[orientation]);
2118 constraints[orientation].clear();
2119 graphPaths[orientation].clear(); // ###
2120
2121 if (simplificationEnabled)
2122 restoreSimplifiedGraph(orientation);
2123}
2124
2132static void shiftConstraints(const QList<QSimplexConstraint *> &constraints, qreal amount)
2133{
2134 for (int i = 0; i < constraints.size(); ++i) {
2135 QSimplexConstraint *c = constraints.at(i);
2136 const qreal multiplier = std::accumulate(c->variables.cbegin(), c->variables.cend(), qreal(0));
2137 c->constant += multiplier * amount;
2138 }
2139}
2140
2148 const QList<QSimplexConstraint *> &constraints,
2149 const QList<AnchorData *> &variables)
2150{
2151 bool feasible = true;
2152 bool needsSimplex = !constraints.isEmpty();
2153
2154#if 0
2155 qDebug("Simplex %s for trunk of %s", needsSimplex ? "used" : "NOT used",
2156 orientation == Qt::Horizontal ? "Horizontal" : "Vertical");
2157#endif
2158
2159 if (needsSimplex) {
2160
2161 QList<QSimplexConstraint *> sizeHintConstraints = constraintsFromSizeHints(variables);
2162 QList<QSimplexConstraint *> allConstraints = constraints + sizeHintConstraints;
2163
2164 shiftConstraints(allConstraints, g_offset);
2165
2166 // Solve min and max size hints
2167 qreal min, max;
2168 feasible = solveMinMax(allConstraints, path, &min, &max);
2169
2170 if (feasible) {
2171 solvePreferred(constraints, variables);
2172
2173 // Calculate and set the preferred size for the layout,
2174 // from the edge sizes that were calculated above.
2175 qreal pref(0.0);
2176 for (const AnchorData *ad : path.positives)
2177 pref += ad->sizeAtPreferred;
2178 for (const AnchorData *ad : path.negatives)
2179 pref -= ad->sizeAtPreferred;
2180
2181 sizeHints[orientation][Qt::MinimumSize] = min;
2182 sizeHints[orientation][Qt::PreferredSize] = pref;
2183 sizeHints[orientation][Qt::MaximumSize] = max;
2184 }
2185
2186 qDeleteAll(sizeHintConstraints);
2188
2189 } else {
2190 // No Simplex is necessary because the path was simplified all the way to a single
2191 // anchor.
2192 Q_ASSERT(path.positives.size() == 1);
2193 Q_ASSERT(path.negatives.size() == 0);
2194
2195 AnchorData *ad = *path.positives.cbegin();
2196 ad->sizeAtMinimum = ad->minSize;
2197 ad->sizeAtPreferred = ad->prefSize;
2198 ad->sizeAtMaximum = ad->maxSize;
2199
2200 sizeHints[orientation][Qt::MinimumSize] = ad->sizeAtMinimum;
2201 sizeHints[orientation][Qt::PreferredSize] = ad->sizeAtPreferred;
2202 sizeHints[orientation][Qt::MaximumSize] = ad->sizeAtMaximum;
2203 }
2204
2205#if defined(QT_DEBUG) || defined(QT_BUILD_INTERNAL)
2206 lastCalculationUsedSimplex[orientation] = needsSimplex;
2207#endif
2208
2209 return feasible;
2210}
2211
2216 const QList<AnchorData *> &variables)
2217{
2219 bool feasible = solvePreferred(constraints, variables);
2220
2221 if (feasible) {
2222 // Propagate size at preferred to other sizes. Semi-floats always will be
2223 // in their sizeAtPreferred.
2224 for (int j = 0; j < variables.size(); ++j) {
2225 AnchorData *ad = variables.at(j);
2226 Q_ASSERT(ad);
2229 }
2230 }
2231
2233 return feasible;
2234}
2235
2243{
2244 Graph<AnchorVertex, AnchorData> &g = graph[orientation];
2245 QList<QPair<AnchorVertex *, AnchorVertex *>> vertices = g.connections();
2246
2247 QLayoutStyleInfo styleInf = styleInfo();
2248 for (int i = 0; i < vertices.size(); ++i) {
2249 AnchorData *data = g.edgeData(vertices.at(i).first, vertices.at(i).second);
2250 data->refreshSizeHints(&styleInf);
2251 }
2252}
2253
2265{
2267
2268 QSet<AnchorData *> visited;
2269
2270 AnchorVertex *root = layoutFirstVertex[orientation];
2271
2272 graphPaths[orientation].insert(root, GraphPath());
2273
2274 const auto adjacentVertices = graph[orientation].adjacentVertices(root);
2275 for (AnchorVertex *v : adjacentVertices)
2276 queue.enqueue(qMakePair(root, v));
2277
2278 while(!queue.isEmpty()) {
2280 AnchorData *edge = graph[orientation].edgeData(pair.first, pair.second);
2281
2282 if (visited.contains(edge))
2283 continue;
2284
2285 visited.insert(edge);
2286 GraphPath current = graphPaths[orientation].value(pair.first);
2287
2288 if (edge->from == pair.first)
2289 current.positives.insert(edge);
2290 else
2291 current.negatives.insert(edge);
2292
2293 graphPaths[orientation].insert(pair.second, current);
2294
2295 const auto adjacentVertices = graph[orientation].adjacentVertices(pair.second);
2296 for (AnchorVertex *v : adjacentVertices)
2297 queue.enqueue(qMakePair(pair.second, v));
2298 }
2299
2300 // We will walk through every reachable items (non-float) store them in a temporary set.
2301 // We them create a set of all items and subtract the non-floating items from the set in
2302 // order to get the floating items. The floating items is then stored in m_floatItems
2303 identifyFloatItems(visited, orientation);
2304}
2305
2317{
2318 const auto vertices = graphPaths[orientation].uniqueKeys();
2319 for (AnchorVertex *vertex : vertices) {
2320 int valueCount = graphPaths[orientation].count(vertex);
2321 if (valueCount == 1)
2322 continue;
2323
2324 QList<GraphPath> pathsToVertex = graphPaths[orientation].values(vertex);
2325 for (int i = 1; i < valueCount; ++i) {
2326 constraints[orientation] += \
2327 pathsToVertex[0].constraint(pathsToVertex.at(i));
2328 }
2329 }
2330}
2331
2336{
2337 Graph<AnchorVertex, AnchorData> &g = graph[orientation];
2338 const QList<QPair<AnchorVertex *, AnchorVertex *>> &vertices = g.connections();
2339
2340 for (int i = 0; i < vertices.size(); ++i) {
2341 AnchorData *ad = g.edgeData(vertices.at(i).first, vertices.at(i).second);
2342 ad->updateChildrenSizes();
2343 }
2344}
2345
2354{
2355 if (anchors.isEmpty())
2357
2358 // Look for the layout edge. That can be either the first half in case the
2359 // layout is split in two, or the whole layout anchor.
2360 const Qt::Orientation orient = anchors.first()->isVertical ? Qt::Vertical : Qt::Horizontal;
2361 AnchorData *layoutEdge = nullptr;
2362 if (layoutCentralVertex[orient]) {
2363 layoutEdge = graph[orient].edgeData(layoutFirstVertex[orient], layoutCentralVertex[orient]);
2364 } else {
2365 layoutEdge = graph[orient].edgeData(layoutFirstVertex[orient], layoutLastVertex[orient]);
2366 }
2367
2368 // If maxSize is less then "infinite", that means there are other anchors
2369 // grouped together with this one. We can't ignore its maximum value so we
2370 // set back the variable to NULL to prevent the continue condition from being
2371 // satisfied in the loop below.
2372 const qreal expectedMax = layoutCentralVertex[orient] ? QWIDGETSIZE_MAX / 2 : QWIDGETSIZE_MAX;
2373 qreal actualMax;
2374 if (layoutEdge->from == layoutFirstVertex[orient]) {
2375 actualMax = layoutEdge->maxSize;
2376 } else {
2377 actualMax = -layoutEdge->minSize;
2378 }
2379 if (actualMax != expectedMax) {
2380 layoutEdge = nullptr;
2381 }
2382
2383 // For each variable, create constraints based on size hints
2384 QList<QSimplexConstraint *> anchorConstraints;
2385 bool unboundedProblem = true;
2386 for (int i = 0; i < anchors.size(); ++i) {
2387 AnchorData *ad = anchors.at(i);
2388
2389 // Anchors that have their size directly linked to another one don't need constraints
2390 // For exammple, the second half of an item has exactly the same size as the first half
2391 // thus constraining the latter is enough.
2392 if (ad->dependency == AnchorData::Slave)
2393 continue;
2394
2395 // To use negative variables inside simplex, we shift them so the minimum negative value is
2396 // mapped to zero before solving. To make sure that it works, we need to guarantee that the
2397 // variables are all inside a certain boundary.
2398 qreal boundedMin = qBound(-g_offset, ad->minSize, g_offset);
2399 qreal boundedMax = qBound(-g_offset, ad->maxSize, g_offset);
2400
2401 if ((boundedMin == boundedMax) || qFuzzyCompare(boundedMin, boundedMax)) {
2403 c->variables.insert(ad, 1.0);
2404 c->constant = boundedMin;
2406 anchorConstraints += c;
2407 unboundedProblem = false;
2408 } else {
2410 c->variables.insert(ad, 1.0);
2411 c->constant = boundedMin;
2413 anchorConstraints += c;
2414
2415 // We avoid adding restrictions to the layout internal anchors. That's
2416 // to prevent unnecessary fair distribution from happening due to this
2417 // artificial restriction.
2418 if (ad == layoutEdge)
2419 continue;
2420
2421 c = new QSimplexConstraint;
2422 c->variables.insert(ad, 1.0);
2423 c->constant = boundedMax;
2425 anchorConstraints += c;
2426 unboundedProblem = false;
2427 }
2428 }
2429
2430 // If no upper boundary restriction was added, add one to avoid unbounded problem
2431 if (unboundedProblem) {
2433 c->variables.insert(layoutEdge, 1.0);
2434 // The maximum size that the layout can take
2435 c->constant = g_offset;
2437 anchorConstraints += c;
2438 }
2439
2440 return anchorConstraints;
2441}
2442
2448{
2450
2451 Q_ASSERT(layoutFirstVertex[orientation] && layoutLastVertex[orientation]);
2452
2453 AnchorData *edgeL1 = nullptr;
2454 AnchorData *edgeL2 = nullptr;
2455
2456 // The layout may have a single anchor between Left and Right or two half anchors
2457 // passing through the center
2458 if (layoutCentralVertex[orientation]) {
2459 edgeL1 = graph[orientation].edgeData(layoutFirstVertex[orientation], layoutCentralVertex[orientation]);
2460 edgeL2 = graph[orientation].edgeData(layoutCentralVertex[orientation], layoutLastVertex[orientation]);
2461 } else {
2462 edgeL1 = graph[orientation].edgeData(layoutFirstVertex[orientation], layoutLastVertex[orientation]);
2463 }
2464
2465 result.nonTrunkConstraints = constraints[orientation] + itemCenterConstraints[orientation];
2466
2467 QSet<QSimplexVariable *> trunkVariables;
2468
2469 trunkVariables += edgeL1;
2470 if (edgeL2)
2471 trunkVariables += edgeL2;
2472
2473 bool dirty;
2474 auto end = result.nonTrunkConstraints.end();
2475 do {
2476 dirty = false;
2477
2478 auto isMatch = [&result, &trunkVariables](QSimplexConstraint *c) -> bool {
2479 bool match = false;
2480
2481 // Check if this constraint have some overlap with current
2482 // trunk variables...
2483 for (QSimplexVariable *ad : std::as_const(trunkVariables)) {
2484 if (c->variables.contains(ad)) {
2485 match = true;
2486 break;
2487 }
2488 }
2489
2490 // If so, we add it to trunk, and erase it from the
2491 // remaining constraints.
2492 if (match) {
2493 result.trunkConstraints += c;
2494 for (auto jt = c->variables.cbegin(), end = c->variables.cend(); jt != end; ++jt)
2495 trunkVariables.insert(jt.key());
2496 return true;
2497 } else {
2498 // Note that we don't erase the constraint if it's not
2499 // a match, since in a next iteration of a do-while we
2500 // can pass on it again and it will be a match.
2501 //
2502 // For example: if trunk share a variable with
2503 // remainingConstraints[1] and it shares with
2504 // remainingConstraints[0], we need a second iteration
2505 // of the do-while loop to match both.
2506 return false;
2507 }
2508 };
2509 const auto newEnd = std::remove_if(result.nonTrunkConstraints.begin(), end, isMatch);
2510 dirty = newEnd != end;
2511 end = newEnd;
2512 } while (dirty);
2513
2514 result.nonTrunkConstraints.erase(end, result.nonTrunkConstraints.end());
2515
2516 return result;
2517}
2518
2525{
2526 QSet<QGraphicsLayoutItem *> nonFloating;
2527
2528 for (const AnchorData *ad : visited)
2529 identifyNonFloatItems_helper(ad, &nonFloating);
2530
2531 QSet<QGraphicsLayoutItem *> floatItems;
2532 for (QGraphicsLayoutItem *item : std::as_const(items)) {
2533 if (!nonFloating.contains(item))
2534 floatItems.insert(item);
2535 }
2536 m_floatItems[orientation] = std::move(floatItems);
2537}
2538
2539
2548{
2550
2551 switch(ad->type) {
2552 case AnchorData::Normal:
2553 if (ad->item && ad->item != q)
2554 nonFloatingItemsIdentifiedSoFar->insert(ad->item);
2555 break;
2557 foreach (const AnchorData *d, static_cast<const SequentialAnchorData *>(ad)->m_edges)
2558 identifyNonFloatItems_helper(d, nonFloatingItemsIdentifiedSoFar);
2559 break;
2561 identifyNonFloatItems_helper(static_cast<const ParallelAnchorData *>(ad)->firstEdge, nonFloatingItemsIdentifiedSoFar);
2562 identifyNonFloatItems_helper(static_cast<const ParallelAnchorData *>(ad)->secondEdge, nonFloatingItemsIdentifiedSoFar);
2563 break;
2564 }
2565}
2566
2574{
2576 AnchorVertex *firstH, *secondH, *firstV, *secondV;
2577
2578 qreal top;
2579 qreal left;
2580 qreal right;
2581
2582 q->getContentsMargins(&left, &top, &right, nullptr);
2583 const Qt::LayoutDirection visualDir = visualDirection();
2584 if (visualDir == Qt::RightToLeft)
2585 qSwap(left, right);
2586
2587 left += geom.left();
2588 top += geom.top();
2589 right = geom.right() - right;
2590
2591 for (QGraphicsLayoutItem *item : std::as_const(items)) {
2592 QRectF newGeom;
2593 QSizeF itemPreferredSize = item->effectiveSizeHint(Qt::PreferredSize);
2595 newGeom.setLeft(0);
2596 newGeom.setRight(itemPreferredSize.width());
2597 } else {
2600
2601 if (visualDir == Qt::LeftToRight) {
2602 newGeom.setLeft(left + firstH->distance);
2603 newGeom.setRight(left + secondH->distance);
2604 } else {
2605 newGeom.setLeft(right - secondH->distance);
2606 newGeom.setRight(right - firstH->distance);
2607 }
2608 }
2609
2611 newGeom.setTop(0);
2612 newGeom.setBottom(itemPreferredSize.height());
2613 } else {
2616
2617 newGeom.setTop(top + firstV->distance);
2618 newGeom.setBottom(top + secondV->distance);
2619 }
2620
2621 item->setGeometry(newGeom);
2622 }
2623}
2624
2632{
2634 QSet<AnchorVertex *> visited;
2635
2636 // Get root vertex
2637 AnchorVertex *root = layoutFirstVertex[orientation];
2638
2639 root->distance = 0;
2640 visited.insert(root);
2641
2642 // Add initial edges to the queue
2643 const auto adjacentVertices = graph[orientation].adjacentVertices(root);
2644 for (AnchorVertex *v : adjacentVertices)
2645 queue.enqueue(qMakePair(root, v));
2646
2647 // Do initial calculation required by "interpolateEdge()"
2648 setupEdgesInterpolation(orientation);
2649
2650 // Traverse the graph and calculate vertex positions
2651 while (!queue.isEmpty()) {
2653 AnchorData *edge = graph[orientation].edgeData(pair.first, pair.second);
2654
2655 if (visited.contains(pair.second))
2656 continue;
2657
2658 visited.insert(pair.second);
2659 interpolateEdge(pair.first, edge);
2660
2661 QList<AnchorVertex *> adjacents = graph[orientation].adjacentVertices(pair.second);
2662 for (int i = 0; i < adjacents.size(); ++i) {
2663 if (!visited.contains(adjacents.at(i)))
2664 queue.enqueue(qMakePair(pair.second, adjacents.at(i)));
2665 }
2666 }
2667}
2668
2677 Qt::Orientation orientation)
2678{
2680
2681 qreal current;
2682 current = (orientation == Qt::Horizontal) ? q->contentsRect().width() : q->contentsRect().height();
2683
2685 result = getFactor(current,
2686 sizeHints[orientation][Qt::MinimumSize],
2687 sizeHints[orientation][Qt::PreferredSize],
2688 sizeHints[orientation][Qt::PreferredSize],
2689 sizeHints[orientation][Qt::PreferredSize],
2690 sizeHints[orientation][Qt::MaximumSize]);
2691
2692 interpolationInterval[orientation] = result.first;
2693 interpolationProgress[orientation] = result.second;
2694}
2695
2712{
2713 const Qt::Orientation orientation = edge->isVertical ? Qt::Vertical : Qt::Horizontal;
2714 const QPair<Interval, qreal> factor(interpolationInterval[orientation],
2715 interpolationProgress[orientation]);
2716
2717 qreal edgeDistance = interpolate(factor, edge->sizeAtMinimum, edge->sizeAtPreferred,
2718 edge->sizeAtPreferred, edge->sizeAtPreferred,
2719 edge->sizeAtMaximum);
2720
2721 Q_ASSERT(edge->from == base || edge->to == base);
2722
2723 // Calculate the distance for the vertex opposite to the base
2724 if (edge->from == base) {
2725 edge->to->distance = base->distance + edgeDistance;
2726 } else {
2727 edge->from->distance = base->distance - edgeDistance;
2728 }
2729}
2730
2732 const GraphPath &path, qreal *min, qreal *max)
2733{
2734 QSimplex simplex;
2735 bool feasible = simplex.setConstraints(constraints);
2736 if (feasible) {
2737 // Obtain the objective constraint
2738 QSimplexConstraint objective;
2740 for (iter = path.positives.constBegin(); iter != path.positives.constEnd(); ++iter)
2741 objective.variables.insert(*iter, 1.0);
2742
2743 for (iter = path.negatives.constBegin(); iter != path.negatives.constEnd(); ++iter)
2744 objective.variables.insert(*iter, -1.0);
2745
2746 const qreal objectiveOffset = (path.positives.size() - path.negatives.size()) * g_offset;
2747 simplex.setObjective(&objective);
2748
2749 // Calculate minimum values
2750 *min = simplex.solveMin() - objectiveOffset;
2751
2752 // Save sizeAtMinimum results
2754 for (int i = 0; i < variables.size(); ++i) {
2755 AnchorData *ad = static_cast<AnchorData *>(variables.at(i));
2756 ad->sizeAtMinimum = ad->result - g_offset;
2757 }
2758
2759 // Calculate maximum values
2760 *max = simplex.solveMax() - objectiveOffset;
2761
2762 // Save sizeAtMaximum results
2763 for (int i = 0; i < variables.size(); ++i) {
2764 AnchorData *ad = static_cast<AnchorData *>(variables.at(i));
2765 ad->sizeAtMaximum = ad->result - g_offset;
2766 }
2767 }
2768 return feasible;
2769}
2770
2771enum slackType { Grower = -1, Shrinker = 1 };
2773 qreal interval, slackType type)
2774{
2776 sizeConstraint->variables.insert(slack, type);
2777
2779 limit->variables.insert(slack, 1.0);
2781 limit->constant = interval;
2782
2783 return qMakePair(slack, limit);
2784}
2785
2787 const QList<AnchorData *> &variables)
2788{
2789 QList<QSimplexConstraint *> preferredConstraints;
2790 QList<QSimplexVariable *> preferredVariables;
2791 QSimplexConstraint objective;
2792
2793 // Fill the objective coefficients for this variable. In the
2794 // end the objective function will be
2795 //
2796 // z = n * (A_shrinker_hard + A_grower_hard + B_shrinker_hard + B_grower_hard + ...) +
2797 // (A_shrinker_soft + A_grower_soft + B_shrinker_soft + B_grower_soft + ...)
2798 //
2799 // where n is the number of variables that have
2800 // slacks. Note that here we use the number of variables
2801 // as coefficient, this is to mark the "shrinker slack
2802 // variable" less likely to get value than the "grower
2803 // slack variable".
2804
2805 // This will fill the values for the structural constraints
2806 // and we now fill the values for the slack constraints (one per variable),
2807 // which have this form (the constant A_pref was set when creating the slacks):
2808 //
2809 // A + A_shrinker_hard + A_shrinker_soft - A_grower_hard - A_grower_soft = A_pref
2810 //
2811 for (int i = 0; i < variables.size(); ++i) {
2812 AnchorData *ad = variables.at(i);
2813
2814 // The layout original structure anchors are not relevant in preferred size calculation
2815 if (ad->isLayoutAnchor)
2816 continue;
2817
2818 // By default, all variables are equal to their preferred size. If they have room to
2819 // grow or shrink, such flexibility will be added by the additional variables below.
2820 QSimplexConstraint *sizeConstraint = new QSimplexConstraint;
2821 preferredConstraints += sizeConstraint;
2822 sizeConstraint->variables.insert(ad, 1.0);
2823 sizeConstraint->constant = ad->prefSize + g_offset;
2824
2825 // Can easily shrink
2827 const qreal softShrinkInterval = ad->prefSize - ad->minPrefSize;
2828 if (softShrinkInterval) {
2829 slack = createSlack(sizeConstraint, softShrinkInterval, Shrinker);
2830 preferredVariables += slack.first;
2831 preferredConstraints += slack.second;
2832
2833 // Add to objective with ratio == 1 (soft)
2834 objective.variables.insert(slack.first, 1.0);
2835 }
2836
2837 // Can easily grow
2838 const qreal softGrowInterval = ad->maxPrefSize - ad->prefSize;
2839 if (softGrowInterval) {
2840 slack = createSlack(sizeConstraint, softGrowInterval, Grower);
2841 preferredVariables += slack.first;
2842 preferredConstraints += slack.second;
2843
2844 // Add to objective with ratio == 1 (soft)
2845 objective.variables.insert(slack.first, 1.0);
2846 }
2847
2848 // Can shrink if really necessary
2849 const qreal hardShrinkInterval = ad->minPrefSize - ad->minSize;
2850 if (hardShrinkInterval) {
2851 slack = createSlack(sizeConstraint, hardShrinkInterval, Shrinker);
2852 preferredVariables += slack.first;
2853 preferredConstraints += slack.second;
2854
2855 // Add to objective with ratio == N (hard)
2856 objective.variables.insert(slack.first, variables.size());
2857 }
2858
2859 // Can grow if really necessary
2860 const qreal hardGrowInterval = ad->maxSize - ad->maxPrefSize;
2861 if (hardGrowInterval) {
2862 slack = createSlack(sizeConstraint, hardGrowInterval, Grower);
2863 preferredVariables += slack.first;
2864 preferredConstraints += slack.second;
2865
2866 // Add to objective with ratio == N (hard)
2867 objective.variables.insert(slack.first, variables.size());
2868 }
2869 }
2870
2871 QSimplex *simplex = new QSimplex;
2872 bool feasible = simplex->setConstraints(constraints + preferredConstraints);
2873 if (feasible) {
2874 simplex->setObjective(&objective);
2875
2876 // Calculate minimum values
2877 simplex->solveMin();
2878
2879 // Save sizeAtPreferred results
2880 for (int i = 0; i < variables.size(); ++i) {
2881 AnchorData *ad = variables.at(i);
2882 ad->sizeAtPreferred = ad->result - g_offset;
2883 }
2884 }
2885
2886 // Make sure we delete the simplex solver -before- we delete the
2887 // constraints used by it.
2888 delete simplex;
2889
2890 // Delete constraints and variables we created.
2891 qDeleteAll(preferredConstraints);
2892 qDeleteAll(preferredVariables);
2893
2894 return feasible;
2895}
2896
2905{
2907 that->calculateGraphs();
2908
2909 bool floatConflict = !m_floatItems[Qt::Horizontal].isEmpty() || !m_floatItems[Qt::Vertical].isEmpty();
2910
2912}
2913
2914#ifdef QT_DEBUG
2915void QGraphicsAnchorLayoutPrivate::dumpGraph(const QString &name)
2916{
2917 QFile file(QString::fromLatin1("anchorlayout.%1.dot").arg(name));
2919 qWarning("Could not write to %ls", qUtf16Printable(file.fileName()));
2920
2921 QString str = QString::fromLatin1("digraph anchorlayout {\nnode [shape=\"rect\"]\n%1}");
2922 QString dotContents = graph[Qt::Horizontal].serializeToDot();
2923 dotContents += graph[Qt::Vertical].serializeToDot();
2924 file.write(str.arg(dotContents).toLocal8Bit());
2925
2926 file.close();
2927}
2928#endif
2929
EdgeData * takeEdge(Vertex *first, Vertex *second)
Definition qgraph_p.h:149
static QStyle * style()
Returns the application's style object.
void close() override
Calls QFileDevice::flush() and closes the file.
\inmodule QtCore
Definition qfile.h:93
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
QString fileName() const override
Returns the name set by setFileName() or to the QFile constructors.
Definition qfile.cpp:277
bool solvePreferred(const QList< QSimplexConstraint * > &constraints, const QList< AnchorData * > &variables)
QHVContainer< AnchorVertex * > layoutLastVertex
void removeAnchor_helper(AnchorVertex *v1, AnchorVertex *v2)
QList< QSimplexConstraint * > constraintsFromSizeHints(const QList< AnchorData * > &anchors)
QHVContainer< Interval > interpolationInterval
QGraphicsAnchor * addAnchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge, qreal *spacing=nullptr)
QHVContainer< Graph< AnchorVertex, AnchorData > > graph
QHVContainer< QList< QSimplexConstraint * > > constraints
void setupEdgesInterpolation(Qt::Orientation orientation)
QHVContainer< AnchorVertex * > layoutFirstVertex
QHash< QPair< QGraphicsLayoutItem *, Qt::AnchorPoint >, QPair< AnchorVertex *, int > > m_vertexList
bool replaceVertex(Qt::Orientation orientation, AnchorVertex *oldV, AnchorVertex *newV, const QList< AnchorData * > &edges)
void updateAnchorSizes(Qt::Orientation orientation)
bool calculateNonTrunk(const QList< QSimplexConstraint * > &constraints, const QList< AnchorData * > &variables)
static Qt::AnchorPoint oppositeEdge(Qt::AnchorPoint edge)
bool calculateTrunk(Qt::Orientation orientation, const GraphPath &trunkPath, const QList< QSimplexConstraint * > &constraints, const QList< AnchorData * > &variables)
QHVContainer< QList< AnchorData * > > anchorsFromSimplifiedVertices
bool solveMinMax(const QList< QSimplexConstraint * > &constraints, const GraphPath &path, qreal *min, qreal *max)
QHVContainer< QList< AnchorVertexPair * > > simplifiedVertices
AnchorData * addAnchorMaybeParallel(AnchorData *newAnchor, bool *feasible)
void removeCenterConstraints(QGraphicsLayoutItem *item, Qt::Orientation orientation)
static Qt::AnchorPoint pickEdge(Qt::AnchorPoint edge, Qt::Orientation orientation)
void removeInternalVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge)
bool simplifyGraph(Qt::Orientation orientation)
void refreshAllSizeHints(Qt::Orientation orientation)
QHVContainer< QMultiHash< AnchorVertex *, GraphPath > > graphPaths
static Qt::Orientation edgeOrientation(Qt::AnchorPoint edge) noexcept
void findPaths(Qt::Orientation orientation)
AnchorVertex * addInternalVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge)
void createCenterAnchors(QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge)
QGraphicsAnchor * acquireGraphicsAnchor(AnchorData *data)
QHVContainer< qreal > interpolationProgress
void restoreSimplifiedGraph(Qt::Orientation orientation)
bool simplifyGraphIteration(Qt::Orientation orientation, bool *feasible)
void restoreSimplifiedConstraints(ParallelAnchorData *parallel)
void removeCenterAnchors(QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge, bool substitute=true)
void calculateVertexPositions(Qt::Orientation orientation)
void changeLayoutVertex(Qt::Orientation orientation, AnchorVertex *oldV, AnchorVertex *newV)
void removeAnchors(QGraphicsLayoutItem *item)
QHVContainer< QList< QSimplexConstraint * > > itemCenterConstraints
static constexpr Qt::Orientation Horizontal
void setItemsGeometries(const QRectF &geom)
QHVContainer< QSet< QGraphicsLayoutItem * > > m_floatItems
void removeAnchor(AnchorVertex *firstVertex, AnchorVertex *secondVertex)
QHVContainer< AnchorVertex * > layoutCentralVertex
void restoreVertices(Qt::Orientation orientation)
GraphParts getGraphParts(Qt::Orientation orientation)
QList< QGraphicsLayoutItem * > items
void interpolateEdge(AnchorVertex *base, AnchorData *edge)
void identifyNonFloatItems_helper(const AnchorData *ad, QSet< QGraphicsLayoutItem * > *nonFloatingItemsIdentifiedSoFar)
QGraphicsAnchor * getAnchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge)
bool simplifyVertices(Qt::Orientation orientation)
void restoreSimplifiedAnchor(AnchorData *edge)
void identifyFloatItems(const QSet< AnchorData * > &visited, Qt::Orientation orientation)
AnchorVertex * internalVertex(const QPair< QGraphicsLayoutItem *, Qt::AnchorPoint > &itemEdge) const
void constraintsFromPaths(Qt::Orientation orientation)
QHVContainer< std::array< qreal, 3 > > sizeHints
void correctEdgeDirection(QGraphicsLayoutItem *&firstItem, Qt::AnchorPoint &firstEdge, QGraphicsLayoutItem *&secondItem, Qt::AnchorPoint &secondEdge)
void addAnchor_helper(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge, AnchorData *data)
void removeVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge)
void createItemEdges(QGraphicsLayoutItem *item)
The QGraphicsAnchorLayout class provides a layout where one can anchor widgets together in Graphics V...
void setSizePolicy(QSizePolicy::Policy policy)
QGraphicsAnchorPrivate(int version=QObjectPrivateVersion)
static QGraphicsAnchorPrivate * get(QGraphicsAnchor *q)
QGraphicsAnchorLayoutPrivate * layoutPrivate
The QGraphicsAnchor class represents an anchor between two items in a QGraphicsAnchorLayout.
void setSpacing(qreal spacing)
The QGraphicsItem class is the base class for all graphical items in a QGraphicsScene.
bool isWidget() const
QGraphicsItem * parentItem() const
The QGraphicsLayoutItem class can be inherited to allow your custom items to be managed by layouts.
QGraphicsItem * graphicsItem() const
Returns the QGraphicsItem that this layout item represents.
QSizeF effectiveSizeHint(Qt::SizeHint which, const QSizeF &constraint=QSizeF()) const
Returns the effective size hint for this QGraphicsLayoutItem.
bool isLayout() const
Returns true if this QGraphicsLayoutItem is a layout (e.g., is inherited by an object that arranges o...
QGraphicsLayoutItem * parentLayoutItem() const
Returns the parent of this QGraphicsLayoutItem, or \nullptr if there is no parent,...
QSizePolicy sizePolicy() const
Returns the current size policy.
void addChildLayoutItem(QGraphicsLayoutItem *item)
Qt::LayoutDirection visualDirection() const
The QGraphicsWidget class is the base class for all widget items in a QGraphicsScene.
bool remove(const Key &key)
Removes the item that has the key from the hash.
Definition qhash.h:956
T value(const Key &key) const noexcept
Definition qhash.h:1044
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1283
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
void setDefaultSpacing(Qt::Orientation o, qreal spacing)
qreal perItemSpacing(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, Qt::Orientation orientation) const
qreal defaultSpacing(Qt::Orientation o) const
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
T & first()
Definition qlist.h:628
T & last()
Definition qlist.h:631
const T & constLast() const noexcept
Definition qlist.h:633
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
void remove(qsizetype i, qsizetype n=1)
Definition qlist.h:787
qsizetype count() const noexcept
Definition qlist.h:387
const T & constFirst() const noexcept
Definition qlist.h:630
void reserve(qsizetype size)
Definition qlist.h:746
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
\inmodule QtCore
Definition qqueue.h:14
\inmodule QtCore\reentrant
Definition qrect.h:483
constexpr void setBottom(qreal pos) noexcept
Sets the bottom edge of the rectangle to the given finite y coordinate.
Definition qrect.h:670
constexpr void setRight(qreal pos) noexcept
Sets the right edge of the rectangle to the given finite x coordinate.
Definition qrect.h:664
constexpr void setLeft(qreal pos) noexcept
Sets the left edge of the rectangle to the given finite x coordinate.
Definition qrect.h:661
constexpr void setTop(qreal pos) noexcept
Sets the top edge of the rectangle to the given finite y coordinate.
Definition qrect.h:667
constexpr qreal left() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:496
constexpr qreal top() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:497
constexpr qreal right() const noexcept
Returns the x-coordinate of the rectangle's right edge.
Definition qrect.h:498
Definition qset.h:18
QList< T > values() const
Definition qset.h:297
iterator begin()
Definition qset.h:136
iterator end()
Definition qset.h:140
bool contains(const T &value) const
Definition qset.h:71
const_iterator cbegin() const noexcept
Definition qset.h:138
iterator insert(const T &value)
Definition qset.h:155
bool setConstraints(const QList< QSimplexConstraint * > &constraints)
void setObjective(QSimplexConstraint *objective)
qreal solveMax()
qreal solveMin()
\inmodule QtCore
Definition qsize.h:207
constexpr qreal width() const noexcept
Returns the width.
Definition qsize.h:321
constexpr qreal height() const noexcept
Returns the height.
Definition qsize.h:324
The QSizePolicy class is a layout attribute describing horizontal and vertical resizing policy.
Definition qsizepolicy.h:18
constexpr Policy verticalPolicy() const noexcept
Returns the vertical component of the size policy.
Definition qsizepolicy.h:67
ControlType controlType() const noexcept
constexpr Policy horizontalPolicy() const noexcept
Returns the horizontal component of the size policy.
Definition qsizepolicy.h:66
Policy
This enum describes the various per-dimension sizing types used when constructing a QSizePolicy.
Definition qsizepolicy.h:29
\inmodule QtCore
Definition qstack.h:13
T pop()
Removes the top item from the stack and returns it.
Definition qstack.h:18
void push(const T &t)
Adds element t to the top of the stack.
Definition qstack.h:17
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5710
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8606
QByteArray toLocal8Bit() const &
Definition qstring.h:567
The QStyle class is an abstract base class that encapsulates the look and feel of a GUI.
Definition qstyle.h:29
The QWidget class is the base class of all user interface objects.
Definition qwidget.h:99
QSimplexConstraint * constraint(const GraphPath &path) const
QString str
[2]
qreal spacing
qSwap(pi, e)
qDeleteAll(list.begin(), list.end())
double e
QSet< QString >::iterator it
short next
Definition keywords.cpp:445
Combined button and popup list for selecting options.
LayoutDirection
@ LeftToRight
@ RightToLeft
Orientation
Definition qnamespace.h:97
@ Horizontal
Definition qnamespace.h:98
@ Vertical
Definition qnamespace.h:99
AnchorPoint
@ AnchorRight
@ AnchorVerticalCenter
@ AnchorBottom
@ AnchorTop
@ AnchorHorizontalCenter
@ AnchorLeft
@ MaximumSize
@ PreferredSize
@ MinimumSize
std::pair< T1, T2 > QPair
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter * iter
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:287
static QPair< QGraphicsAnchorLayoutPrivate::Interval, qreal > getFactor(qreal value, qreal min, qreal minPref, qreal pref, qreal maxPref, qreal max)
static QPair< QSimplexVariable *, QSimplexConstraint * > createSlack(QSimplexConstraint *sizeConstraint, qreal interval, slackType type)
static AnchorData * createSequence(Graph< AnchorVertex, AnchorData > *graph, AnchorVertex *before, const QList< AnchorVertex * > &vertices, AnchorVertex *after)
static qreal interpolate(const QPair< QGraphicsAnchorLayoutPrivate::Interval, qreal > &factor, qreal min, qreal minPref, qreal pref, qreal maxPref, qreal max)
const qreal g_offset
static void applySizePolicy(QSizePolicy::Policy policy, qreal minSizeHint, qreal prefSizeHint, qreal maxSizeHint, qreal *minSize, qreal *prefSize, qreal *maxSize)
static AnchorVertex * replaceVertex_helper(AnchorData *data, AnchorVertex *oldV, AnchorVertex *newV)
static void shiftConstraints(const QList< QSimplexConstraint * > &constraints, qreal amount)
QList< AnchorData * > getVariables(const QList< QSimplexConstraint * > &constraints)
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
static bool contains(const QJsonArray &haystack, unsigned needle)
Definition qopengl.cpp:116
GLint GLfloat GLfloat GLfloat v2
GLsizei const GLfloat * v
[13]
GLfloat GLfloat GLfloat w
[0]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLuint GLuint end
GLdouble GLdouble GLdouble GLdouble top
GLenum GLenum GLsizei count
GLdouble GLdouble right
GLint left
GLenum type
GLint GLfloat GLfloat v1
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLboolean GLboolean g
GLuint name
GLint first
const GLubyte * c
GLint limit
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
GLdouble s
[6]
Definition qopenglext.h:235
GLfloat GLfloat p
[1]
GLsizei const GLchar *const * string
[0]
Definition qopenglext.h:694
constexpr decltype(auto) qMakePair(T1 &&value1, T2 &&value2) noexcept(noexcept(std::make_pair(std::forward< T1 >(value1), std::forward< T2 >(value2))))
Definition qpair.h:19
QQuickAnchors * anchors(QQuickItem *item)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
SSL_CTX int(*) void arg)
#define qPrintable(string)
Definition qstring.h:1391
#define qUtf16Printable(string)
Definition qstring.h:1403
#define v1
Q_CORE_EXPORT bool qEnvironmentVariableIsEmpty(const char *varName) noexcept
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
double qreal
Definition qtypes.h:92
#define QWIDGETSIZE_MAX
Definition qwidget.h:930
QFile file
[0]
QObject::connect nullptr
QVBoxLayout * layout
QQueue< int > queue
[0]
QSharedPointer< T > other(t)
[5]
QGraphicsItem * item
QLayoutItem * child
[0]
QAction * at
QSizePolicy policy
char * toString(const MyType &t)
[31]
qsizetype indexOf(const AT &t, qsizetype from=0) const noexcept
Definition qlist.h:955
bool contains(const AT &t) const noexcept
Definition qlist.h:44
QHash< QSimplexVariable *, qreal > variables
Definition qsimplex_p.h:57
void refreshSizeHints(const QLayoutStyleInfo *styleInfo=nullptr)