Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qgridlayoutengine.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// Qt-Security score:significant reason:default
4
5#include "qglobal.h"
6
9
10#include <QtDebug>
11#include <QtCore/qmath.h>
12
14
15using namespace Qt::StringLiterals;
16
17#define LAYOUTITEMSIZE_MAX (1 << 24)
18
19template<typename T>
20static void insertOrRemoveItems(QList<T> &items, int index, int delta)
21{
22 int count = items.size();
23 if (index < count) {
24 if (delta > 0) {
25 items.insert(index, delta, T());
26 } else if (delta < 0) {
27 items.remove(index, qMin(-delta, count - index));
28 }
29 }
30}
31
32static qreal growthFactorBelowPreferredSize(qreal desired, qreal sumAvailable, qreal sumDesired)
33{
34 Q_ASSERT(sumDesired != 0.0);
35 return desired * qPow(sumAvailable / sumDesired, desired / sumDesired);
36}
37
38static qreal fixedDescent(qreal descent, qreal ascent, qreal targetSize)
39{
40 if (descent < 0.0)
41 return -1.0;
42
43 Q_ASSERT(descent >= 0.0);
44 Q_ASSERT(ascent >= 0.0);
45 Q_ASSERT(targetSize >= ascent + descent);
46
47 qreal extra = targetSize - (ascent + descent);
48 return descent + (extra / 2.0);
49}
50
51static qreal compare(const QGridLayoutBox &box1, const QGridLayoutBox &box2, int which)
52{
53 qreal size1 = box1.q_sizes(which);
54 qreal size2 = box2.q_sizes(which);
55
56 if (which == MaximumSize) {
57 return size2 - size1;
58 } else {
59 return size1 - size2;
60 }
61}
62
63void QGridLayoutBox::add(const QGridLayoutBox &other, int stretch, qreal spacing)
64{
65 Q_ASSERT(q_minimumDescent < 0.0);
66
67 q_minimumSize += other.q_minimumSize + spacing;
68 q_preferredSize += other.q_preferredSize + spacing;
69 q_maximumSize += ((stretch == 0) ? other.q_preferredSize : other.q_maximumSize) + spacing;
70}
71
72void QGridLayoutBox::combine(const QGridLayoutBox &other)
73{
74 q_minimumDescent = qMax(q_minimumDescent, other.q_minimumDescent);
75 q_minimumAscent = qMax(q_minimumAscent, other.q_minimumAscent);
76
77 q_minimumSize = qMax(q_minimumAscent + q_minimumDescent,
78 qMax(q_minimumSize, other.q_minimumSize));
79 qreal maxMax;
80 if (q_maximumSize == FLT_MAX && other.q_maximumSize != FLT_MAX)
81 maxMax = other.q_maximumSize;
82 else if (other.q_maximumSize == FLT_MAX && q_maximumSize != FLT_MAX)
83 maxMax = q_maximumSize;
84 else
85 maxMax = qMax(q_maximumSize, other.q_maximumSize);
86
87 q_maximumSize = qMax(q_minimumSize, maxMax);
88 q_preferredSize = qBound(q_minimumSize, qMax(q_preferredSize, other.q_preferredSize),
89 q_maximumSize);
90}
91
92void QGridLayoutBox::normalize()
93{
94 q_maximumSize = qMax(qreal(0.0), q_maximumSize);
95 q_minimumSize = qBound(qreal(0.0), q_minimumSize, q_maximumSize);
96 q_preferredSize = qBound(q_minimumSize, q_preferredSize, q_maximumSize);
97 q_minimumDescent = qMin(q_minimumDescent, q_minimumSize);
98
99 Q_ASSERT((q_minimumDescent < 0.0) == (q_minimumAscent < 0.0));
100}
101
102#ifdef QGRIDLAYOUTENGINE_DEBUG
103void QGridLayoutBox::dump(int indent) const
104{
105 qDebug("%*sBox (%g <= %g <= %g [%g/%g])", indent, "", q_minimumSize, q_preferredSize,
106 q_maximumSize, q_minimumAscent, q_minimumDescent);
107}
108#endif
109
110bool operator==(const QGridLayoutBox &box1, const QGridLayoutBox &box2)
111{
112 for (int i = 0; i < NSizes; ++i) {
113 if (box1.q_sizes(i) != box2.q_sizes(i))
114 return false;
115 }
116 return box1.q_minimumDescent == box2.q_minimumDescent
117 && box1.q_minimumAscent == box2.q_minimumAscent;
118}
119
120void QGridLayoutRowData::reset(int count)
121{
122 ignore.fill(false, count);
123 boxes.fill(QGridLayoutBox(), count);
124 multiCellMap.clear();
125 stretches.fill(0, count);
126 spacings.fill(0.0, count);
127 hasIgnoreFlag = false;
128}
129
130void QGridLayoutRowData::distributeMultiCells(const QGridLayoutRowInfo &rowInfo, bool snapToPixelGrid)
131{
132 MultiCellMap::const_iterator i = multiCellMap.constBegin();
133 for (; i != multiCellMap.constEnd(); ++i) {
134 int start = i.key().first;
135 int span = i.key().second;
136 int end = start + span;
137 const QGridLayoutBox &box = i.value().q_box;
138 int stretch = i.value().q_stretch;
139
140 QGridLayoutBox totalBox = this->totalBox(start, end);
141 QVarLengthArray<QGridLayoutBox> extras(span);
142 QVarLengthArray<qreal> dummy(span);
143 QVarLengthArray<qreal> newSizes(span);
144
145 for (int j = 0; j < NSizes; ++j) {
146 qreal extra = compare(box, totalBox, j);
147 if (extra > 0.0) {
148 calculateGeometries(start, end, box.q_sizes(j), dummy.data(), newSizes.data(),
149 nullptr, totalBox, rowInfo, snapToPixelGrid);
150
151 for (int k = 0; k < span; ++k)
152 extras[k].q_sizes(j) = newSizes[k];
153 }
154 }
155
156 for (int k = 0; k < span; ++k) {
157 boxes[start + k].combine(extras[k]);
158 if (stretch != 0)
159 stretches[start + k] = qMax(stretches[start + k], stretch);
160 }
161 }
162 multiCellMap.clear();
163}
164namespace {
165
166// does not return int
167static inline qreal qround(qreal f)
168{
169 return std::floor(f + qreal(0.5));
170}
171
172}
173void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSize, qreal *positions,
174 qreal *sizes, qreal *descents,
175 const QGridLayoutBox &totalBox,
176 const QGridLayoutRowInfo &rowInfo, bool snapToPixelGrid)
177{
178 Q_ASSERT(end > start);
179
180 targetSize = qMax(totalBox.q_minimumSize, targetSize);
181
182 int n = end - start;
183 QVarLengthArray<qreal> newSizes(n);
184 QVarLengthArray<qreal> factors(n);
185 qreal sumFactors = 0.0;
186 int sumStretches = 0;
187 qreal sumAvailable;
188
189 for (int i = 0; i < n; ++i) {
190 const int stretch = stretches.at(start + i);
191 if (stretch > 0)
192 sumStretches += stretch;
193 }
194
195 if (targetSize < totalBox.q_preferredSize) {
196 stealBox(start, end, MinimumSize, positions, sizes);
197
198 sumAvailable = targetSize - totalBox.q_minimumSize;
199 if (sumAvailable > 0.0) {
200 const qreal totalBox_preferredSize = qMin(totalBox.q_preferredSize, qreal(LAYOUTITEMSIZE_MAX));
201 qreal sumDesired = totalBox_preferredSize - totalBox.q_minimumSize;
202
203 for (int i = 0; i < n; ++i) {
204 if (ignore.testBit(start + i)) {
205 factors[i] = 0.0;
206 continue;
207 }
208
209 const QGridLayoutBox &box = boxes.at(start + i);
210 const qreal box_preferredSize = qMin(box.q_preferredSize, qreal(LAYOUTITEMSIZE_MAX));
211 qreal desired = box_preferredSize - box.q_minimumSize;
212 factors[i] = growthFactorBelowPreferredSize(desired, sumAvailable, sumDesired);
213 sumFactors += factors[i];
214 }
215
216 for (int i = 0; i < n; ++i) {
217 Q_ASSERT(sumFactors > 0.0);
218 qreal delta = sumAvailable * factors[i] / sumFactors;
219 newSizes[i] = sizes[i] + delta;
220 }
221 }
222 } else {
223 bool isLargerThanMaximum = (targetSize > totalBox.q_maximumSize);
224 if (isLargerThanMaximum) {
225 stealBox(start, end, MaximumSize, positions, sizes);
226 sumAvailable = targetSize - totalBox.q_maximumSize;
227 } else {
228 stealBox(start, end, PreferredSize, positions, sizes);
229 sumAvailable = targetSize - totalBox.q_preferredSize;
230 }
231
232 if (sumAvailable > 0.0) {
233 qreal sumCurrentAvailable = sumAvailable;
234 bool somethingHasAMaximumSize = false;
235
236 qreal sumSizes = 0.0;
237 for (int i = 0; i < n; ++i)
238 sumSizes += sizes[i];
239
240 for (int i = 0; i < n; ++i) {
241 if (ignore.testBit(start + i)) {
242 newSizes[i] = 0.0;
243 factors[i] = 0.0;
244 continue;
245 }
246
247 const QGridLayoutBox &box = boxes.at(start + i);
248 qreal boxSize;
249
250 qreal desired;
251 if (isLargerThanMaximum) {
252 boxSize = box.q_maximumSize;
253 desired = rowInfo.boxes.value(start + i).q_maximumSize - boxSize;
254 } else {
255 boxSize = box.q_preferredSize;
256 desired = box.q_maximumSize - boxSize;
257 }
258 if (desired == 0.0) {
259 newSizes[i] = sizes[i];
260 factors[i] = 0.0;
261 } else {
262 Q_ASSERT(desired > 0.0);
263
264 int stretch = stretches[start + i];
265 if (sumStretches == 0) {
266 if (hasIgnoreFlag || sizes[i] == 0.0) {
267 factors[i] = (stretch < 0) ? 1.0 : 0.0;
268 } else {
269 factors[i] = (stretch < 0) ? sizes[i] : 0.0;
270 }
271 } else if (stretch == sumStretches) {
272 factors[i] = 1.0;
273 } else if (stretch <= 0) {
274 factors[i] = 0.0;
275 } else {
276 qreal ultimateSize;
277 qreal ultimateSumSizes;
278 qreal x = ((stretch * sumSizes)
279 - (sumStretches * boxSize))
280 / (sumStretches - stretch);
281 if (x >= 0.0) {
282 ultimateSize = boxSize + x;
283 ultimateSumSizes = sumSizes + x;
284 } else {
285 ultimateSize = boxSize;
286 ultimateSumSizes = (sumStretches * boxSize)
287 / stretch;
288 }
289
290 /*
291 We multiply these by 1.5 to give some space for a smooth transition
292 (at the expense of the stretch factors, which are not fully respected
293 during the transition).
294 */
295 ultimateSize = ultimateSize * 3 / 2;
296 ultimateSumSizes = ultimateSumSizes * 3 / 2;
297
298 qreal beta = ultimateSumSizes - sumSizes;
299 if (!beta) {
300 factors[i] = 1;
301 } else {
302 qreal alpha = qMin(sumCurrentAvailable, beta);
303 qreal ultimateFactor = (stretch * ultimateSumSizes / sumStretches)
304 - (boxSize);
305 qreal transitionalFactor = sumCurrentAvailable * (ultimateSize - boxSize) / beta;
306
307 factors[i] = ((alpha * ultimateFactor)
308 + ((beta - alpha) * transitionalFactor)) / beta;
309 }
310
311 }
312 sumFactors += factors[i];
313 if (desired < sumCurrentAvailable)
314 somethingHasAMaximumSize = true;
315
316 newSizes[i] = -1.0;
317 }
318 }
319
320 bool keepGoing = somethingHasAMaximumSize;
321 while (keepGoing) {
322 //sumCurrentAvailable is so large that something *might* reach its maximum size
323 keepGoing = false;
324
325 for (int i = 0; i < n; ++i) {
326 if (newSizes[i] >= 0.0)
327 continue;
328
329 const QList<QGridLayoutBox> &rBoxes = isLargerThanMaximum ? rowInfo.boxes : boxes;
330 const QGridLayoutBox &box = rBoxes.value(start + i);
331 qreal maxBoxSize = box.q_maximumSize;
332
333 if (snapToPixelGrid)
334 maxBoxSize = qMax(box.q_minimumSize, std::floor(maxBoxSize));
335
336 qreal avail = sumCurrentAvailable * factors[i] / sumFactors;
337 if (sizes[i] + avail >= maxBoxSize) {
338 newSizes[i] = maxBoxSize;
339 sumCurrentAvailable -= maxBoxSize - sizes[i];
340 sumFactors -= factors[i];
341 keepGoing = (sumCurrentAvailable > 0.0);
342 if (!keepGoing)
343 break;
344 }
345 }
346 }
347 for (int i = 0; i < n; ++i) {
348 if (newSizes[i] < 0.0) {
349 qreal delta = (sumFactors == 0.0) ? 0.0
350 : sumCurrentAvailable * factors[i] / sumFactors;
351 newSizes[i] = sizes[i] + delta;
352 }
353 }
354 }
355 }
356
357 if (sumAvailable > 0) {
358 qreal offset = 0;
359 for (int i = 0; i < n; ++i) {
360 qreal delta = newSizes[i] - sizes[i];
361 positions[i] += offset;
362 sizes[i] += delta;
363 offset += delta;
364 }
365
366#if 0 // some "pixel allocation"
367 int surplus = targetSize - (positions[n - 1] + sizes[n - 1]);
368 Q_ASSERT(surplus >= 0 && surplus <= n);
369
370 int prevSurplus = -1;
371 while (surplus > 0 && surplus != prevSurplus) {
372 prevSurplus = surplus;
373
374 int offset = 0;
375 for (int i = 0; i < n; ++i) {
376 const QGridLayoutBox &box = boxes.at(start + i);
377 int delta = (!ignore.testBit(start + i) && surplus > 0
378 && factors[i] > 0 && sizes[i] < box.q_maximumSize)
379 ? 1 : 0;
380
381 positions[i] += offset;
382 sizes[i] += delta;
383 offset += delta;
384 surplus -= delta;
385 }
386 }
387 Q_ASSERT(surplus == 0);
388#endif
389 }
390 if (snapToPixelGrid) {
391 for (int i = 0; i < n; ++i) {
392 const qreal oldpos = positions[i];
393 positions[i] = qround(oldpos);
394 const qreal delta = positions[i] - oldpos;
395 sizes[i] -= delta;
396 if (i > 0)
397 sizes[i - 1] += delta;
398 }
399
400 sizes[n - 1] = targetSize - positions[n - 1];
401 // This loop serves two purposes:
402 // 1. round off the small epsilons produced by the above loop.
403 // 2. avoid that the above loop didn't make the cell width smaller than its minimum constraint.
404 for (int i = 0; i < n; ++i) {
405 const QGridLayoutBox &box = boxes.at(start + i);
406 sizes[i] = qMax(box.q_minimumSize, qround(sizes[i]));
407 }
408 }
409
410 if (descents) {
411 for (int i = 0; i < n; ++i) {
412 if (ignore.testBit(start + i))
413 continue;
414 const QGridLayoutBox &box = boxes.at(start + i);
415 descents[i] = fixedDescent(box.q_minimumDescent, box.q_minimumAscent, sizes[i]);
416 }
417 }
418}
419
421{
422 QGridLayoutBox result;
423 if (start < end) {
424 result.q_maximumSize = 0.0;
425 qreal nextSpacing = 0.0;
426 for (int i = start; i < end; ++i) {
427 if (ignore.testBit(i))
428 continue;
429 result.add(boxes.at(i), stretches.at(i), nextSpacing);
430 nextSpacing = spacings.at(i);
431 }
432 }
433 return result;
434}
435
436void QGridLayoutRowData::stealBox(int start, int end, int which, qreal *positions, qreal *sizes)
437{
438 qreal offset = 0.0;
439 qreal nextSpacing = 0.0;
440
441 for (int i = start; i < end; ++i) {
442 qreal avail = 0.0;
443
444 if (!ignore.testBit(i)) {
445 const QGridLayoutBox &box = boxes.at(i);
446 avail = box.q_sizes(which);
447 offset += nextSpacing;
448 nextSpacing = spacings.at(i);
449 }
450
451 *positions++ = offset;
452 *sizes++ = avail;
453 offset += avail;
454 }
455}
456
457#ifdef QGRIDLAYOUTENGINE_DEBUG
458void QGridLayoutRowData::dump(int indent) const
459{
460 qDebug("%*sData", indent, "");
461
462 for (int i = 0; i < ignore.count(); ++i) {
463 qDebug("%*s Row %d (stretch %d, spacing %g)", indent, "", i, stretches.at(i),
464 spacings.at(i));
465 if (ignore.testBit(i))
466 qDebug("%*s Ignored", indent, "");
467 boxes.at(i).dump(indent + 2);
468 }
469
470 MultiCellMap::const_iterator it = multiCellMap.constBegin();
471 while (it != multiCellMap.constEnd()) {
472 qDebug("%*s Multi-cell entry <%d, %d> (stretch %d)", indent, "", it.key().first,
473 it.key().second, it.value().q_stretch);
474 it.value().q_box.dump(indent + 2);
475 ++it;
476 }
477}
478#endif
479
480QGridLayoutItem::QGridLayoutItem(int row, int column, int rowSpan, int columnSpan,
481 Qt::Alignment alignment)
482 : q_firstRows{column, row},
483 q_rowSpans{columnSpan, rowSpan},
484 q_stretches{-1, -1},
485 q_alignment(alignment)
486{
487}
488
489int QGridLayoutItem::firstRow(Qt::Orientation orientation) const
490{
491 return q_firstRows[orientation];
492}
493
494int QGridLayoutItem::firstColumn(Qt::Orientation orientation) const
495{
496 return q_firstRows.transposed()[orientation];
497}
498
499int QGridLayoutItem::lastRow(Qt::Orientation orientation) const
500{
501 return firstRow(orientation) + rowSpan(orientation) - 1;
502}
503
504int QGridLayoutItem::lastColumn(Qt::Orientation orientation) const
505{
506 return firstColumn(orientation) + columnSpan(orientation) - 1;
507}
508
509int QGridLayoutItem::rowSpan(Qt::Orientation orientation) const
510{
511 return q_rowSpans[orientation];
512}
513
514int QGridLayoutItem::columnSpan(Qt::Orientation orientation) const
515{
516 return q_rowSpans.transposed()[orientation];
517}
518
519void QGridLayoutItem::setFirstRow(int row, Qt::Orientation orientation)
520{
521 q_firstRows[orientation] = row;
522}
523
524void QGridLayoutItem::setRowSpan(int rowSpan, Qt::Orientation orientation)
525{
526 q_rowSpans[orientation] = rowSpan;
527}
528
529int QGridLayoutItem::stretchFactor(Qt::Orientation orientation) const
530{
531 int stretch = q_stretches[orientation];
532 if (stretch >= 0)
533 return stretch;
534
535 QLayoutPolicy::Policy policy = sizePolicy(orientation);
536
537 if (policy & QLayoutPolicy::ExpandFlag) {
538 return 1;
539 } else if (policy & QLayoutPolicy::GrowFlag) {
540 return -1; // because we max it up
541 } else {
542 return 0;
543 }
544}
545
546void QGridLayoutItem::setStretchFactor(int stretch, Qt::Orientation orientation)
547{
548 Q_ASSERT(stretch >= 0); // ### deal with too big stretches
549 q_stretches[orientation] = stretch;
550}
551
552QLayoutPolicy::ControlTypes QGridLayoutItem::controlTypes(LayoutSide /*side*/) const
553{
554 return QLayoutPolicy::DefaultType;
555}
556
557QGridLayoutBox QGridLayoutItem::box(Qt::Orientation orientation, bool snapToPixelGrid, qreal constraint) const
558{
559 QGridLayoutBox result;
560 QLayoutPolicy::Policy policy = sizePolicy(orientation);
561
562 if (orientation == Qt::Horizontal) {
563 QSizeF constraintSize(-1.0, constraint);
564
565 result.q_preferredSize = sizeHint(Qt::PreferredSize, constraintSize).width();
566
567 if (policy & QLayoutPolicy::ShrinkFlag) {
568 result.q_minimumSize = sizeHint(Qt::MinimumSize, constraintSize).width();
569 } else {
570 result.q_minimumSize = result.q_preferredSize;
571 }
572 if (snapToPixelGrid)
573 result.q_minimumSize = qCeil(result.q_minimumSize);
574
575 if (policy & (QLayoutPolicy::GrowFlag | QLayoutPolicy::ExpandFlag)) {
576 result.q_maximumSize = sizeHint(Qt::MaximumSize, constraintSize).width();
577 } else {
578 result.q_maximumSize = result.q_preferredSize;
579 }
580 } else {
581 QSizeF constraintSize(constraint, -1.0);
582
583 result.q_preferredSize = sizeHint(Qt::PreferredSize, constraintSize).height();
584
585 if (policy & QLayoutPolicy::ShrinkFlag) {
586 result.q_minimumSize = sizeHint(Qt::MinimumSize, constraintSize).height();
587 } else {
588 result.q_minimumSize = result.q_preferredSize;
589 }
590 if (snapToPixelGrid)
591 result.q_minimumSize = qCeil(result.q_minimumSize);
592
593 if (policy & (QLayoutPolicy::GrowFlag | QLayoutPolicy::ExpandFlag)) {
594 result.q_maximumSize = sizeHint(Qt::MaximumSize, constraintSize).height();
595 } else {
596 result.q_maximumSize = result.q_preferredSize;
597 }
598
599 if (alignment() & Qt::AlignBaseline) {
600 result.q_minimumDescent = sizeHint(Qt::MinimumDescent, constraintSize).height();
601 if (result.q_minimumDescent != -1.0) {
602 const qreal minSizeHint = sizeHint(Qt::MinimumSize, constraintSize).height();
603 result.q_minimumDescent -= (minSizeHint - result.q_minimumSize);
604 result.q_minimumAscent = result.q_minimumSize - result.q_minimumDescent;
605 }
606 }
607 }
608 if (policy & QLayoutPolicy::IgnoreFlag)
609 result.q_preferredSize = result.q_minimumSize;
610
611 return result;
612}
613
614QRectF QGridLayoutItem::geometryWithin(qreal x, qreal y, qreal width, qreal height,
615 qreal rowDescent, Qt::Alignment align, bool snapToPixelGrid) const
616{
617 const qreal cellWidth = width;
618 const qreal cellHeight = height;
619
620 QSizeF size = effectiveMaxSize(QSizeF(-1,-1));
621 if (hasDynamicConstraint()) {
622 if (dynamicConstraintOrientation() == Qt::Vertical) {
623 if (size.width() > cellWidth)
624 size = effectiveMaxSize(QSizeF(cellWidth, -1));
625 } else if (size.height() > cellHeight) {
626 size = effectiveMaxSize(QSizeF(-1, cellHeight));
627 }
628 }
629 size = size.boundedTo(QSizeF(cellWidth, cellHeight));
630 width = size.width();
631 height = size.height();
632
633 switch (align & Qt::AlignHorizontal_Mask) {
634 case Qt::AlignHCenter:
635 x += (cellWidth - width)/2;
636 break;
637 case Qt::AlignRight:
638 x += cellWidth - width;
639 break;
640 default:
641 break;
642 }
643
644 switch (align & Qt::AlignVertical_Mask) {
645 case Qt::AlignVCenter:
646 y += (cellHeight - height)/2;
647 break;
648 case Qt::AlignBottom:
649 y += cellHeight - height;
650 break;
651 case Qt::AlignBaseline: {
652 width = qMin(effectiveMaxSize(QSizeF(-1,-1)).width(), width);
653 QGridLayoutBox vBox = box(Qt::Vertical, snapToPixelGrid);
654 const qreal descent = vBox.q_minimumDescent;
655 const qreal ascent = vBox.q_minimumSize - descent;
656 y += (cellHeight - rowDescent - ascent);
657 height = ascent + descent;
658 break; }
659 default:
660 break;
661 }
662 return QRectF(x, y, width, height);
663}
664
665void QGridLayoutItem::transpose()
666{
667 q_firstRows.transpose();
668 q_rowSpans.transpose();
669 q_stretches.transpose();
670}
671
672void QGridLayoutItem::insertOrRemoveRows(int row, int delta, Qt::Orientation orientation)
673{
674 int oldFirstRow = firstRow(orientation);
675 if (oldFirstRow >= row) {
676 setFirstRow(oldFirstRow + delta, orientation);
677 } else if (lastRow(orientation) >= row) {
678 setRowSpan(rowSpan(orientation) + delta, orientation);
679 }
680}
681/*!
682 \internal
683 returns the effective maximumSize, will take the sizepolicy into
684 consideration. (i.e. if sizepolicy does not have QLayoutPolicy::Grow, then
685 maxSizeHint will be the preferredSize)
686 Note that effectiveSizeHint does not take sizePolicy into consideration,
687 (since it only evaluates the hints, as the name implies)
688*/
689QSizeF QGridLayoutItem::effectiveMaxSize(const QSizeF &constraint) const
690{
691 QSizeF size = constraint;
692 bool vGrow = (sizePolicy(Qt::Vertical) & QLayoutPolicy::GrowFlag) == QLayoutPolicy::GrowFlag;
693 bool hGrow = (sizePolicy(Qt::Horizontal) & QLayoutPolicy::GrowFlag) == QLayoutPolicy::GrowFlag;
694 if (!vGrow || !hGrow) {
695 QSizeF pref = sizeHint(Qt::PreferredSize, constraint);
696 if (!vGrow)
697 size.setHeight(pref.height());
698 if (!hGrow)
699 size.setWidth(pref.width());
700 }
701
702 if (!size.isValid()) {
703 QSizeF maxSize = sizeHint(Qt::MaximumSize, size);
704 if (size.width() == -1)
705 size.setWidth(maxSize.width());
706 if (size.height() == -1)
707 size.setHeight(maxSize.height());
708 }
709 return size;
710}
711
712#ifdef QGRIDLAYOUTENGINE_DEBUG
713void QGridLayoutItem::dump(int indent) const
714{
715 qDebug("%*s (%d, %d) %d x %d", indent, "", firstRow(), firstColumn(), //###
716 rowSpan(), columnSpan());
717
718 if (q_stretches[Qt::Horizontal] >= 0)
719 qDebug("%*s Horizontal stretch: %d", indent, "", q_stretches[Qt::Horizontal]);
720 if (q_stretches[Qt::Vertical] >= 0)
721 qDebug("%*s Vertical stretch: %d", indent, "", q_stretches[Qt::Vertical]);
722 if (q_alignment != 0)
723 qDebug("%*s Alignment: %x", indent, "", uint(q_alignment));
724 qDebug("%*s Horizontal size policy: %x Vertical size policy: %x",
725 indent, "", (unsigned int)sizePolicy(Qt::Horizontal), (unsigned int)sizePolicy(Qt::Vertical));
726}
727#endif
728
729void QGridLayoutRowInfo::insertOrRemoveRows(int row, int delta)
730{
731 count += delta;
732
733 insertOrRemoveItems(stretches, row, delta);
734 insertOrRemoveItems(spacings, row, delta);
735 insertOrRemoveItems(alignments, row, delta);
736 insertOrRemoveItems(boxes, row, delta);
737}
738
739#ifdef QGRIDLAYOUTENGINE_DEBUG
740void QGridLayoutRowInfo::dump(int indent) const
741{
742 qDebug("%*sInfo (count: %d)", indent, "", count);
743 for (int i = 0; i < count; ++i) {
744 QString message;
745
746 if (stretches.value(i).value() >= 0)
747 message += QString::fromLatin1(" stretch %1").arg(stretches.value(i).value());
748 if (spacings.value(i).value() >= 0.0)
749 message += QString::fromLatin1(" spacing %1").arg(spacings.value(i).value());
750 if (alignments.value(i) != 0)
751 message += QString::fromLatin1(" alignment %1").arg(int(alignments.value(i)), 16);
752
753 if (!message.isEmpty() || boxes.value(i) != QGridLayoutBox()) {
754 qDebug("%*s Row %d:%s", indent, "", i, qPrintable(message));
755 if (boxes.value(i) != QGridLayoutBox())
756 boxes.value(i).dump(indent + 1);
757 }
758 }
759}
760#endif
761
762QGridLayoutEngine::QGridLayoutEngine(Qt::Alignment defaultAlignment, bool snapToPixelGrid)
763{
764 m_visualDirection = Qt::LeftToRight;
765 m_defaultAlignment = defaultAlignment;
766 m_snapToPixelGrid = snapToPixelGrid;
767 m_uniformCellWidths = false;
768 m_uniformCellHeights = false;
769 invalidate();
770}
771
772int QGridLayoutEngine::rowCount(Qt::Orientation orientation) const
773{
774 return q_infos[orientation].count;
775}
776
777int QGridLayoutEngine::columnCount(Qt::Orientation orientation) const
778{
779 return q_infos.transposed()[orientation].count;
780}
781
782int QGridLayoutEngine::itemCount() const
783{
784 return q_items.size();
785}
786
787QGridLayoutItem *QGridLayoutEngine::itemAt(int index) const
788{
789 Q_ASSERT(index >= 0 && index < itemCount());
790 return q_items.at(index);
791}
792
793int QGridLayoutEngine::effectiveFirstRow(Qt::Orientation orientation) const
794{
795 ensureEffectiveFirstAndLastRows();
796 return q_cachedEffectiveFirstRows[orientation];
797}
798
799int QGridLayoutEngine::effectiveLastRow(Qt::Orientation orientation) const
800{
801 ensureEffectiveFirstAndLastRows();
802 return q_cachedEffectiveLastRows[orientation];
803}
804
805void QGridLayoutEngine::setSpacing(qreal spacing, Qt::Orientations orientations)
806{
807 if (orientations & Qt::Horizontal)
808 q_defaultSpacings[Qt::Horizontal].setUserValue(spacing);
809 if (orientations & Qt::Vertical)
810 q_defaultSpacings[Qt::Vertical].setUserValue(spacing);
811
812 invalidate();
813}
814
815qreal QGridLayoutEngine::spacing(Qt::Orientation orientation, const QAbstractLayoutStyleInfo *styleInfo) const
816{
817 if (!q_defaultSpacings[orientation].isUser()) {
818 qreal defaultSpacing = styleInfo->spacing(orientation);
819 q_defaultSpacings[orientation].setCachedValue(defaultSpacing);
820 }
821 return q_defaultSpacings[orientation].value();
822}
823
824void QGridLayoutEngine::setRowSpacing(int row, qreal spacing, Qt::Orientation orientation)
825{
826 Q_ASSERT(row >= 0);
827
828 QGridLayoutRowInfo &rowInfo = q_infos[orientation];
829 if (row >= rowInfo.spacings.size())
830 rowInfo.spacings.resize(row + 1);
831 if (spacing >= 0)
832 rowInfo.spacings[row].setUserValue(spacing);
833 else
834 rowInfo.spacings[row] = QLayoutParameter<qreal>();
835 invalidate();
836}
837
838qreal QGridLayoutEngine::rowSpacing(int row, Qt::Orientation orientation) const
839{
840 QLayoutParameter<qreal> spacing = q_infos[orientation].spacings.value(row);
841 if (!spacing.isDefault())
842 return spacing.value();
843 return q_defaultSpacings[orientation].value();
844}
845
846void QGridLayoutEngine::setRowStretchFactor(int row, int stretch, Qt::Orientation orientation)
847{
848 Q_ASSERT(row >= 0);
849 Q_ASSERT(stretch >= 0);
850
851 maybeExpandGrid(row, -1, orientation);
852
853 QGridLayoutRowInfo &rowInfo = q_infos[orientation];
854 if (row >= rowInfo.stretches.size())
855 rowInfo.stretches.resize(row + 1);
856 rowInfo.stretches[row].setUserValue(stretch);
857}
858
859int QGridLayoutEngine::rowStretchFactor(int row, Qt::Orientation orientation) const
860{
861 QStretchParameter stretch = q_infos[orientation].stretches.value(row);
862 if (!stretch.isDefault())
863 return stretch.value();
864 return 0;
865}
866
867void QGridLayoutEngine::setRowSizeHint(Qt::SizeHint which, int row, qreal size,
868 Qt::Orientation orientation)
869{
870 Q_ASSERT(row >= 0);
871 Q_ASSERT(size >= 0.0);
872
873 maybeExpandGrid(row, -1, orientation);
874
875 QGridLayoutRowInfo &rowInfo = q_infos[orientation];
876 if (row >= rowInfo.boxes.size())
877 rowInfo.boxes.resize(row + 1);
878 rowInfo.boxes[row].q_sizes(which) = size;
879}
880
881qreal QGridLayoutEngine::rowSizeHint(Qt::SizeHint which, int row, Qt::Orientation orientation) const
882{
883 return q_infos[orientation].boxes.value(row).q_sizes(which);
884}
885
886bool QGridLayoutEngine::uniformCellWidths() const
887{
888 return m_uniformCellWidths;
889}
890
891void QGridLayoutEngine::setUniformCellWidths(bool uniformCellWidths)
892{
893 if (m_uniformCellWidths == uniformCellWidths)
894 return;
895
896 m_uniformCellWidths = uniformCellWidths;
897 invalidate();
898}
899
900bool QGridLayoutEngine::uniformCellHeights() const
901{
902 return m_uniformCellHeights;
903}
904
905void QGridLayoutEngine::setUniformCellHeights(bool uniformCellHeights)
906{
907 if (m_uniformCellHeights == uniformCellHeights)
908 return;
909
910 m_uniformCellHeights = uniformCellHeights;
911 invalidate();
912}
913
914void QGridLayoutEngine::setRowAlignment(int row, Qt::Alignment alignment,
915 Qt::Orientation orientation)
916{
917 Q_ASSERT(row >= 0);
918
919 maybeExpandGrid(row, -1, orientation);
920
921 QGridLayoutRowInfo &rowInfo = q_infos[orientation];
922 if (row >= rowInfo.alignments.size())
923 rowInfo.alignments.resize(row + 1);
924 rowInfo.alignments[row] = alignment;
925}
926
927Qt::Alignment QGridLayoutEngine::rowAlignment(int row, Qt::Orientation orientation) const
928{
929 Q_ASSERT(row >= 0);
930 return q_infos[orientation].alignments.value(row);
931}
932
933Qt::Alignment QGridLayoutEngine::effectiveAlignment(const QGridLayoutItem *layoutItem) const
934{
935 Qt::Alignment align = layoutItem->alignment();
936 if (!(align & Qt::AlignVertical_Mask)) {
937 // no vertical alignment, respect the row alignment
938 int y = layoutItem->firstRow();
939 align |= (rowAlignment(y, Qt::Vertical) & Qt::AlignVertical_Mask);
940 if (!(align & Qt::AlignVertical_Mask))
941 align |= (m_defaultAlignment & Qt::AlignVertical_Mask);
942 }
943 if (!(align & Qt::AlignHorizontal_Mask)) {
944 // no horizontal alignment, respect the column alignment
945 int x = layoutItem->firstColumn();
946 align |= (rowAlignment(x, Qt::Horizontal) & Qt::AlignHorizontal_Mask);
947 }
948
949 return align;
950}
951
952/*!
953 \internal
954 The \a index is only used by QGraphicsLinearLayout to ensure that itemAt() reflects the order
955 of visual arrangement. Strictly speaking it does not have to, but most people expect it to.
956 (And if it didn't we would have to add itemArrangedAt(int index) or something..)
957 */
958void QGridLayoutEngine::insertItem(QGridLayoutItem *item, int index)
959{
960 maybeExpandGrid(item->lastRow(), item->lastColumn());
961
962 if (index < 0 || index >= q_items.size())
963 q_items.append(item);
964 else
965 q_items.insert(index, item);
966
967 for (int i = item->firstRow(); i <= item->lastRow(); ++i) {
968 for (int j = item->firstColumn(); j <= item->lastColumn(); ++j) {
969 const auto existingItem = itemAt(i, j);
970 if (existingItem) {
971 qWarning("QGridLayoutEngine::addItem: Can't add %s at cell (%d, %d) because it's already taken by %s",
972 qPrintable(item->toString()), i, j, qPrintable(existingItem->toString()));
973 }
974 setItemAt(i, j, item);
975 }
976 }
977}
978
979void QGridLayoutEngine::addItem(QGridLayoutItem *item)
980{
981 insertItem(item, -1);
982}
983
984void QGridLayoutEngine::removeItem(QGridLayoutItem *item)
985{
986 Q_ASSERT(q_items.contains(item));
987
988 invalidate();
989
990 for (int i = item->firstRow(); i <= item->lastRow(); ++i) {
991 for (int j = item->firstColumn(); j <= item->lastColumn(); ++j) {
992 if (itemAt(i, j) == item)
993 setItemAt(i, j, nullptr);
994 }
995 }
996
997 q_items.removeAll(item);
998}
999
1000
1001QGridLayoutItem *QGridLayoutEngine::itemAt(int row, int column, Qt::Orientation orientation) const
1002{
1003 if (orientation == Qt::Horizontal)
1004 qSwap(row, column);
1005 if (uint(row) >= uint(rowCount()) || uint(column) >= uint(columnCount()))
1006 return nullptr;
1007 return q_grid.at((row * internalGridColumnCount()) + column);
1008}
1009
1010void QGridLayoutEngine::invalidate()
1011{
1012 q_cachedEffectiveFirstRows = {-1, -1};
1013 q_cachedEffectiveLastRows = {-1, -1};
1014
1015 q_totalBoxCachedConstraints = {NotCached, NotCached};
1016
1017 q_cachedSize = QSizeF();
1018 q_cachedConstraintOrientation = UnknownConstraint;
1019}
1020
1021static void visualRect(QRectF *geom, Qt::LayoutDirection dir, const QRectF &contentsRect)
1022{
1023 if (dir == Qt::RightToLeft)
1024 geom->moveRight(contentsRect.right() - (geom->left() - contentsRect.left()));
1025}
1026
1027void QGridLayoutEngine::setGeometries(const QRectF &contentsGeometry, const QAbstractLayoutStyleInfo *styleInfo)
1028{
1029 if (rowCount() < 1 || columnCount() < 1)
1030 return;
1031
1032 ensureGeometries(contentsGeometry.size(), styleInfo);
1033
1034 for (int i = q_items.size() - 1; i >= 0; --i) {
1035 QGridLayoutItem *item = q_items.at(i);
1036
1037 qreal x = q_xx.at(item->firstColumn());
1038 qreal y = q_yy.at(item->firstRow());
1039 qreal width = q_widths.at(item->lastColumn());
1040 qreal height = q_heights.at(item->lastRow());
1041
1042 if (item->columnSpan() != 1)
1043 width += q_xx.at(item->lastColumn()) - x;
1044 if (item->rowSpan() != 1)
1045 height += q_yy.at(item->lastRow()) - y;
1046
1047 const Qt::Alignment align = effectiveAlignment(item);
1048 QRectF geom = item->geometryWithin(contentsGeometry.x() + x, contentsGeometry.y() + y,
1049 width, height, q_descents.at(item->lastRow()), align, m_snapToPixelGrid);
1050 if (m_snapToPixelGrid) {
1051 // x and y should already be rounded, but the call to geometryWithin() above might
1052 // result in a geom with x,y at half-pixels (due to centering within the cell)
1053 // QRectF may change the width as it wants to maintain the right edge. In this
1054 // case the width need to be preserved.
1055 geom.moveLeft(qround(geom.x()));
1056 // Do not snap baseline aligned items, since that might cause the baselines to not be aligned.
1057 if (align != Qt::AlignBaseline)
1058 geom.moveTop(qround(geom.y()));
1059 }
1060 visualRect(&geom, visualDirection(), contentsGeometry);
1061 item->setGeometry(geom);
1062 }
1063}
1064
1065// ### candidate for deletion
1066QRectF QGridLayoutEngine::cellRect(const QRectF &contentsGeometry, int row, int column, int rowSpan,
1067 int columnSpan, const QAbstractLayoutStyleInfo *styleInfo) const
1068{
1069 if (uint(row) >= uint(rowCount()) || uint(column) >= uint(columnCount())
1070 || rowSpan < 1 || columnSpan < 1)
1071 return QRectF();
1072
1073 ensureGeometries(contentsGeometry.size(), styleInfo);
1074
1075 int lastColumn = qMin(column + columnSpan, columnCount()) - 1;
1076 int lastRow = qMin(row + rowSpan, rowCount()) - 1;
1077
1078 qreal x = q_xx[column];
1079 qreal y = q_yy[row];
1080 qreal width = q_widths[lastColumn];
1081 qreal height = q_heights[lastRow];
1082
1083 if (columnSpan != 1)
1084 width += q_xx[lastColumn] - x;
1085 if (rowSpan != 1)
1086 height += q_yy[lastRow] - y;
1087
1088 return QRectF(contentsGeometry.x() + x, contentsGeometry.y() + y, width, height);
1089}
1090
1091QSizeF QGridLayoutEngine::sizeHint(Qt::SizeHint which, const QSizeF &constraint,
1092 const QAbstractLayoutStyleInfo *styleInfo) const
1093{
1094
1095
1096 if (hasDynamicConstraint() && rowCount() > 0 && columnCount() > 0) {
1097 QHVContainer<QGridLayoutBox> sizehint_totalBoxes;
1098 bool sizeHintCalculated = false;
1099 if (constraintOrientation() == Qt::Vertical) {
1100 //We have items whose height depends on their width
1101 if (constraint.width() >= 0) {
1102 ensureColumnAndRowData(&q_columnData, &sizehint_totalBoxes[Qt::Horizontal], nullptr, nullptr, Qt::Horizontal, styleInfo);
1103 QList<qreal> sizehint_xx;
1104 QList<qreal> sizehint_widths;
1105
1106 sizehint_xx.resize(columnCount());
1107 sizehint_widths.resize(columnCount());
1108 qreal width = constraint.width();
1109 //Calculate column widths and positions, and put results in q_xx.data() and q_widths.data() so that we can use this information as
1110 //constraints to find the row heights
1111 q_columnData.calculateGeometries(0, columnCount(), width, sizehint_xx.data(), sizehint_widths.data(),
1112 nullptr, sizehint_totalBoxes[Qt::Horizontal], q_infos[Qt::Horizontal], m_snapToPixelGrid);
1113 ensureColumnAndRowData(&q_rowData, &sizehint_totalBoxes[Qt::Vertical], sizehint_xx.data(), sizehint_widths.data(), Qt::Vertical, styleInfo);
1114 sizeHintCalculated = true;
1115 }
1116 } else {
1117 if (constraint.height() >= 0) {
1118 //We have items whose width depends on their height
1119 ensureColumnAndRowData(&q_rowData, &sizehint_totalBoxes[Qt::Vertical], nullptr, nullptr, Qt::Vertical, styleInfo);
1120 QList<qreal> sizehint_yy;
1121 QList<qreal> sizehint_heights;
1122
1123 sizehint_yy.resize(rowCount());
1124 sizehint_heights.resize(rowCount());
1125 qreal height = constraint.height();
1126 //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() so that we can use this information as
1127 //constraints to find the column widths
1128 q_rowData.calculateGeometries(0, rowCount(), height, sizehint_yy.data(), sizehint_heights.data(),
1129 nullptr, sizehint_totalBoxes[Qt::Vertical], q_infos[Qt::Vertical], m_snapToPixelGrid);
1130 ensureColumnAndRowData(&q_columnData, &sizehint_totalBoxes[Qt::Horizontal], sizehint_yy.data(), sizehint_heights.data(), Qt::Horizontal, styleInfo);
1131 sizeHintCalculated = true;
1132 }
1133 }
1134 if (sizeHintCalculated)
1135 return QSizeF{sizehint_totalBoxes[Qt::Horizontal].q_sizes(which),
1136 sizehint_totalBoxes[Qt::Vertical].q_sizes(which)};
1137 }
1138
1139 //No items with height for width, so it doesn't matter which order we do these in
1140 ensureColumnAndRowData(&q_columnData, &q_totalBoxes[Qt::Horizontal], nullptr, nullptr, Qt::Horizontal, styleInfo);
1141 ensureColumnAndRowData(&q_rowData, &q_totalBoxes[Qt::Vertical], nullptr, nullptr, Qt::Vertical, styleInfo);
1142 return QSizeF(q_totalBoxes[Qt::Horizontal].q_sizes(which), q_totalBoxes[Qt::Vertical].q_sizes(which));
1143}
1144
1145QLayoutPolicy::ControlTypes QGridLayoutEngine::controlTypes(LayoutSide side) const
1146{
1147 Qt::Orientation orientation = (side == Top || side == Bottom) ? Qt::Vertical : Qt::Horizontal;
1148 int row = (side == Top || side == Left) ? effectiveFirstRow(orientation)
1149 : effectiveLastRow(orientation);
1150 QLayoutPolicy::ControlTypes result;
1151
1152 for (int column = columnCount(orientation) - 1; column >= 0; --column) {
1153 if (QGridLayoutItem *item = itemAt(row, column, orientation))
1154 result |= item->controlTypes(side);
1155 }
1156 return result;
1157}
1158
1159void QGridLayoutEngine::transpose()
1160{
1161 invalidate();
1162
1163 for (int i = q_items.size() - 1; i >= 0; --i)
1164 q_items.at(i)->transpose();
1165
1166 q_defaultSpacings.transpose();
1167 q_infos.transpose();
1168
1169 regenerateGrid();
1170}
1171
1172void QGridLayoutEngine::setVisualDirection(Qt::LayoutDirection direction)
1173{
1174 m_visualDirection = direction;
1175}
1176
1177Qt::LayoutDirection QGridLayoutEngine::visualDirection() const
1178{
1179 return m_visualDirection;
1180}
1181
1182#ifdef QGRIDLAYOUTENGINE_DEBUG
1183void QGridLayoutEngine::dump(int indent) const
1184{
1185 qDebug("%*sEngine", indent, "");
1186
1187 qDebug("%*s Items (%lld)", indent, "", q_items.count());
1188 int i;
1189 for (i = 0; i < q_items.count(); ++i)
1190 q_items.at(i)->dump(indent + 2);
1191
1192 qDebug("%*s Grid (%d x %d)", indent, "", internalGridRowCount(),
1193 internalGridColumnCount());
1194 for (int row = 0; row < internalGridRowCount(); ++row) {
1195 QString message = "[ "_L1;
1196 for (int column = 0; column < internalGridColumnCount(); ++column) {
1197 message += QString::number(q_items.indexOf(itemAt(row, column))).rightJustified(3);
1198 message += u' ';
1199 }
1200 message += u']';
1201 qDebug("%*s %s", indent, "", qPrintable(message));
1202 }
1203
1204 if (q_defaultSpacings[Qt::Horizontal].value() >= 0.0 || q_defaultSpacings[Qt::Vertical].value() >= 0.0)
1205 qDebug("%*s Default spacings: %g %g", indent, "",
1206 q_defaultSpacings[Qt::Horizontal].value(),
1207 q_defaultSpacings[Qt::Vertical].value());
1208
1209 qDebug("%*s Column and row info", indent, "");
1210 q_infos[Qt::Horizontal].dump(indent + 2);
1211 q_infos[Qt::Vertical].dump(indent + 2);
1212
1213 qDebug("%*s Column and row data", indent, "");
1214 q_columnData.dump(indent + 2);
1215 q_rowData.dump(indent + 2);
1216
1217 qDebug("%*s Geometries output", indent, "");
1218 QList<qreal> *cellPos = &q_yy;
1219 for (int pass = 0; pass < 2; ++pass) {
1220 QString message;
1221 for (i = 0; i < cellPos->count(); ++i) {
1222 message += (message.isEmpty() ? "["_L1 : ", "_L1);
1223 message += QString::number(cellPos->at(i));
1224 }
1225 message += u']';
1226 qDebug("%*s %s %s", indent, "", (pass == 0 ? "rows:" : "columns:"), qPrintable(message));
1227 cellPos = &q_xx;
1228 }
1229}
1230#endif
1231
1232void QGridLayoutEngine::maybeExpandGrid(int row, int column, Qt::Orientation orientation)
1233{
1234 invalidate(); // ### move out of here?
1235
1236 if (orientation == Qt::Horizontal)
1237 qSwap(row, column);
1238
1239 if (row < rowCount() && column < columnCount())
1240 return;
1241
1242 int oldGridRowCount = internalGridRowCount();
1243 int oldGridColumnCount = internalGridColumnCount();
1244
1245 q_infos[Qt::Vertical].count = qMax(row + 1, rowCount());
1246 q_infos[Qt::Horizontal].count = qMax(column + 1, columnCount());
1247
1248 int newGridRowCount = internalGridRowCount();
1249 int newGridColumnCount = internalGridColumnCount();
1250
1251 int newGridSize = newGridRowCount * newGridColumnCount;
1252 if (newGridSize != q_grid.size()) {
1253 q_grid.resize(newGridSize);
1254
1255 if (newGridColumnCount != oldGridColumnCount) {
1256 for (int i = oldGridRowCount - 1; i >= 1; --i) {
1257 for (int j = oldGridColumnCount - 1; j >= 0; --j) {
1258 int oldIndex = (i * oldGridColumnCount) + j;
1259 int newIndex = (i * newGridColumnCount) + j;
1260
1261 Q_ASSERT(newIndex > oldIndex);
1262 q_grid[newIndex] = q_grid[oldIndex];
1263 q_grid[oldIndex] = nullptr;
1264 }
1265 }
1266 }
1267 }
1268}
1269
1270void QGridLayoutEngine::regenerateGrid()
1271{
1272 q_grid.fill(nullptr);
1273
1274 for (int i = q_items.size() - 1; i >= 0; --i) {
1275 QGridLayoutItem *item = q_items.at(i);
1276
1277 for (int j = item->firstRow(); j <= item->lastRow(); ++j) {
1278 for (int k = item->firstColumn(); k <= item->lastColumn(); ++k) {
1279 setItemAt(j, k, item);
1280 }
1281 }
1282 }
1283}
1284
1285void QGridLayoutEngine::setItemAt(int row, int column, QGridLayoutItem *item)
1286{
1287 Q_ASSERT(row >= 0 && row < rowCount());
1288 Q_ASSERT(column >= 0 && column < columnCount());
1289 q_grid[(row * internalGridColumnCount()) + column] = item;
1290}
1291
1292void QGridLayoutEngine::insertOrRemoveRows(int row, int delta, Qt::Orientation orientation)
1293{
1294 int oldRowCount = rowCount(orientation);
1295 Q_ASSERT(uint(row) <= uint(oldRowCount));
1296
1297 invalidate();
1298
1299 // appending rows (or columns) is easy
1300 if (row == oldRowCount && delta > 0) {
1301 maybeExpandGrid(oldRowCount + delta - 1, -1, orientation);
1302 return;
1303 }
1304
1305 q_infos[orientation].insertOrRemoveRows(row, delta);
1306
1307 for (int i = q_items.size() - 1; i >= 0; --i)
1308 q_items.at(i)->insertOrRemoveRows(row, delta, orientation);
1309
1310 q_grid.resize(internalGridRowCount() * internalGridColumnCount());
1311 regenerateGrid();
1312}
1313
1314void QGridLayoutEngine::fillRowData(QGridLayoutRowData *rowData,
1315 const qreal *colPositions, const qreal *colSizes,
1316 Qt::Orientation orientation,
1317 const QAbstractLayoutStyleInfo *styleInfo) const
1318{
1319 const int ButtonMask = QLayoutPolicy::ButtonBox | QLayoutPolicy::PushButton;
1320 const QGridLayoutRowInfo &rowInfo = q_infos[orientation];
1321 const QGridLayoutRowInfo &columnInfo = q_infos.other(orientation);
1322 LayoutSide top = (orientation == Qt::Vertical) ? Top : Left;
1323 LayoutSide bottom = (orientation == Qt::Vertical) ? Bottom : Right;
1324
1325 const QLayoutParameter<qreal> &defaultSpacing = q_defaultSpacings[orientation];
1326 qreal innerSpacing = styleInfo->spacing(orientation);
1327 if (innerSpacing >= 0.0)
1328 defaultSpacing.setCachedValue(innerSpacing);
1329
1330 for (int row = 0; row < rowInfo.count; ++row) {
1331 bool rowIsEmpty = true;
1332 bool rowIsIdenticalToPrevious = (row > 0);
1333
1334 for (int column = 0; column < columnInfo.count; ++column) {
1335 QGridLayoutItem *item = itemAt(row, column, orientation);
1336
1337 if (rowIsIdenticalToPrevious && item != itemAt(row - 1, column, orientation))
1338 rowIsIdenticalToPrevious = false;
1339
1340 if (item && !item->isEmpty())
1341 rowIsEmpty = false;
1342 }
1343
1344 if ((rowIsEmpty || rowIsIdenticalToPrevious)
1345 && rowInfo.spacings.value(row).isDefault()
1346 && rowInfo.stretches.value(row).isDefault()
1347 && rowInfo.boxes.value(row) == QGridLayoutBox())
1348 rowData->ignore.setBit(row, true);
1349
1350 if (rowInfo.spacings.value(row).isUser()) {
1351 rowData->spacings[row] = rowInfo.spacings.at(row).value();
1352 } else if (!defaultSpacing.isDefault()) {
1353 rowData->spacings[row] = defaultSpacing.value();
1354 }
1355
1356 rowData->stretches[row] = rowInfo.stretches.value(row).value();
1357 }
1358
1359 struct RowAdHocData {
1360 int q_row;
1361 unsigned int q_hasButtons : 8;
1362 unsigned int q_hasNonButtons : 8;
1363
1364 inline RowAdHocData() : q_row(-1), q_hasButtons(false), q_hasNonButtons(false) {}
1365 inline void init(int row) {
1366 this->q_row = row;
1367 q_hasButtons = false;
1368 q_hasNonButtons = false;
1369 }
1370 inline bool hasOnlyButtons() const { return q_hasButtons && !q_hasNonButtons; }
1371 inline bool hasOnlyNonButtons() const { return q_hasNonButtons && !q_hasButtons; }
1372 };
1373 RowAdHocData lastRowAdHocData;
1374 RowAdHocData nextToLastRowAdHocData;
1375 RowAdHocData nextToNextToLastRowAdHocData;
1376
1377 rowData->hasIgnoreFlag = false;
1378 for (int row = 0; row < rowInfo.count; ++row) {
1379 if (rowData->ignore.testBit(row))
1380 continue;
1381
1382 QGridLayoutBox &rowBox = rowData->boxes[row];
1383 if (styleInfo->isWindow()) {
1384 nextToNextToLastRowAdHocData = nextToLastRowAdHocData;
1385 nextToLastRowAdHocData = lastRowAdHocData;
1386 lastRowAdHocData.init(row);
1387 }
1388
1389 bool userRowStretch = rowInfo.stretches.value(row).isUser();
1390 int &rowStretch = rowData->stretches[row];
1391
1392 bool hasIgnoreFlag = true;
1393 for (int column = 0; column < columnInfo.count; ++column) {
1394 QGridLayoutItem *item = itemAt(row, column, orientation);
1395 if (item) {
1396 int itemRow = item->firstRow(orientation);
1397 int itemColumn = item->firstColumn(orientation);
1398
1399 if (itemRow == row && itemColumn == column) {
1400 int itemStretch = item->stretchFactor(orientation);
1401 if (!(item->sizePolicy(orientation) & QLayoutPolicy::IgnoreFlag))
1402 hasIgnoreFlag = false;
1403 int itemRowSpan = item->rowSpan(orientation);
1404
1405 int effectiveRowSpan = 1;
1406 for (int i = 1; i < itemRowSpan; ++i) {
1407 if (!rowData->ignore.testBit(i + itemRow))
1408 ++effectiveRowSpan;
1409 }
1410
1411 QGridLayoutBox *box;
1412 if (effectiveRowSpan == 1) {
1413 box = &rowBox;
1414 if (!userRowStretch && itemStretch != 0)
1415 rowStretch = qMax(rowStretch, itemStretch);
1416 } else {
1417 QGridLayoutMultiCellData &multiCell =
1418 rowData->multiCellMap[std::pair(row, itemRowSpan)];
1419 box = &multiCell.q_box;
1420 multiCell.q_stretch = itemStretch;
1421 }
1422 // Items with constraints need to be passed the constraint
1423 if (colSizes && colPositions && item->hasDynamicConstraint() && orientation == item->dynamicConstraintOrientation()) {
1424 /* Get the width of the item by summing up the widths of the columns that it spans.
1425 * We need to have already calculated the widths of the columns by calling
1426 * q_columns->calculateGeometries() before hand and passing the value in the colSizes
1427 * and colPositions parameters.
1428 * The variable name is still colSizes even when it actually has the row sizes
1429 */
1430 qreal length = colSizes[item->lastColumn(orientation)];
1431 if (item->columnSpan(orientation) != 1)
1432 length += colPositions[item->lastColumn(orientation)] - colPositions[item->firstColumn(orientation)];
1433 box->combine(item->box(orientation, m_snapToPixelGrid, length));
1434 } else {
1435 box->combine(item->box(orientation, m_snapToPixelGrid));
1436 }
1437
1438 if (effectiveRowSpan == 1) {
1439 QLayoutPolicy::ControlTypes controls = item->controlTypes(top);
1440 if (controls & ButtonMask)
1441 lastRowAdHocData.q_hasButtons = true;
1442 if (controls & ~ButtonMask)
1443 lastRowAdHocData.q_hasNonButtons = true;
1444 }
1445 }
1446 }
1447 }
1448 if (row < rowInfo.boxes.size()) {
1449 QGridLayoutBox rowBoxInfo = rowInfo.boxes.at(row);
1450 rowBoxInfo.normalize();
1451 rowBox.q_minimumSize = qMax(rowBox.q_minimumSize, rowBoxInfo.q_minimumSize);
1452 rowBox.q_maximumSize = qMax(rowBox.q_minimumSize,
1453 (rowBoxInfo.q_maximumSize != FLT_MAX ?
1454 rowBoxInfo.q_maximumSize : rowBox.q_maximumSize));
1455 rowBox.q_preferredSize = qBound(rowBox.q_minimumSize,
1456 qMax(rowBox.q_preferredSize, rowBoxInfo.q_preferredSize),
1457 rowBox.q_maximumSize);
1458 }
1459 if (hasIgnoreFlag)
1460 rowData->hasIgnoreFlag = true;
1461 }
1462
1463 /*
1464 Heuristic: Detect button boxes that don't use QLayoutPolicy::ButtonBox.
1465 This is somewhat ad hoc but it usually does the trick.
1466 */
1467 bool lastRowIsButtonBox = (lastRowAdHocData.hasOnlyButtons()
1468 && nextToLastRowAdHocData.hasOnlyNonButtons());
1469 bool lastTwoRowsIsButtonBox = (lastRowAdHocData.hasOnlyButtons()
1470 && nextToLastRowAdHocData.hasOnlyButtons()
1471 && nextToNextToLastRowAdHocData.hasOnlyNonButtons()
1472 && orientation == Qt::Vertical);
1473
1474 if (defaultSpacing.isDefault()) {
1475 int prevRow = -1;
1476 for (int row = 0; row < rowInfo.count; ++row) {
1477 if (rowData->ignore.testBit(row))
1478 continue;
1479
1480 if (prevRow != -1 && !rowInfo.spacings.value(prevRow).isUser()) {
1481 qreal &rowSpacing = rowData->spacings[prevRow];
1482 for (int column = 0; column < columnInfo.count; ++column) {
1483 QGridLayoutItem *item1 = itemAt(prevRow, column, orientation);
1484 QGridLayoutItem *item2 = itemAt(row, column, orientation);
1485
1486 if (item1 && item2 && item1 != item2) {
1487 QLayoutPolicy::ControlTypes controls1 = item1->controlTypes(bottom);
1488 QLayoutPolicy::ControlTypes controls2 = item2->controlTypes(top);
1489
1490 if (controls2 & QLayoutPolicy::PushButton) {
1491 if ((row == nextToLastRowAdHocData.q_row && lastTwoRowsIsButtonBox)
1492 || (row == lastRowAdHocData.q_row && lastRowIsButtonBox)) {
1493 controls2 &= ~QLayoutPolicy::PushButton;
1494 controls2 |= QLayoutPolicy::ButtonBox;
1495 }
1496 }
1497
1498 qreal spacing = styleInfo->combinedLayoutSpacing(controls1, controls2,
1499 orientation);
1500 if (orientation == Qt::Horizontal) {
1501 qreal width1 = rowData->boxes.at(prevRow).q_minimumSize;
1502 qreal width2 = rowData->boxes.at(row).q_minimumSize;
1503 QRectF rect1 = item1->geometryWithin(0.0, 0.0, width1, FLT_MAX, -1.0, effectiveAlignment(item1), m_snapToPixelGrid);
1504 QRectF rect2 = item2->geometryWithin(0.0, 0.0, width2, FLT_MAX, -1.0, effectiveAlignment(item2), m_snapToPixelGrid);
1505 spacing -= (width1 - (rect1.x() + rect1.width())) + rect2.x();
1506 } else {
1507 const QGridLayoutBox &box1 = rowData->boxes.at(prevRow);
1508 const QGridLayoutBox &box2 = rowData->boxes.at(row);
1509 qreal height1 = box1.q_minimumSize;
1510 qreal height2 = box2.q_minimumSize;
1511 qreal rowDescent1 = fixedDescent(box1.q_minimumDescent,
1512 box1.q_minimumAscent, height1);
1513 qreal rowDescent2 = fixedDescent(box2.q_minimumDescent,
1514 box2.q_minimumAscent, height2);
1515 QRectF rect1 = item1->geometryWithin(0.0, 0.0, FLT_MAX, height1,
1516 rowDescent1, effectiveAlignment(item1), m_snapToPixelGrid);
1517 QRectF rect2 = item2->geometryWithin(0.0, 0.0, FLT_MAX, height2,
1518 rowDescent2, effectiveAlignment(item2), m_snapToPixelGrid);
1519 spacing -= (height1 - (rect1.y() + rect1.height())) + rect2.y();
1520 }
1521 rowSpacing = qMax(spacing, rowSpacing);
1522 }
1523 }
1524 }
1525 prevRow = row;
1526 }
1527 } else if (lastRowIsButtonBox || lastTwoRowsIsButtonBox) {
1528 /*
1529 Even for styles that define a uniform spacing, we cheat a
1530 bit and use the window margin as the spacing. This
1531 significantly improves the look of dialogs.
1532 */
1533 int prevRow = lastRowIsButtonBox ? nextToLastRowAdHocData.q_row
1534 : nextToNextToLastRowAdHocData.q_row;
1535 if (!defaultSpacing.isUser() && !rowInfo.spacings.value(prevRow).isUser()) {
1536 qreal windowMargin = styleInfo->windowMargin(orientation);
1537 qreal &rowSpacing = rowData->spacings[prevRow];
1538 rowSpacing = qMax(windowMargin, rowSpacing);
1539 }
1540 }
1541
1542 if (rowData->boxes.size() > 1 &&
1543 ((orientation == Qt::Horizontal && m_uniformCellWidths) ||
1544 (orientation == Qt::Vertical && m_uniformCellHeights))) {
1545 qreal averagePreferredSize = 0.;
1546 qreal minimumMaximumSize = std::numeric_limits<qreal>::max();
1547 qreal maximumMinimumSize = 0.;
1548 for (const auto &box : rowData->boxes) {
1549 averagePreferredSize += box.q_preferredSize;
1550 minimumMaximumSize = qMin(minimumMaximumSize, box.q_maximumSize);
1551 maximumMinimumSize = qMax(maximumMinimumSize, box.q_minimumSize);
1552 }
1553 averagePreferredSize /= rowData->boxes.size();
1554 minimumMaximumSize = qMax(minimumMaximumSize, maximumMinimumSize);
1555 averagePreferredSize = qBound(maximumMinimumSize, averagePreferredSize, minimumMaximumSize);
1556 for (auto &box : rowData->boxes) {
1557 box.q_preferredSize = averagePreferredSize;
1558 box.q_minimumSize = maximumMinimumSize;
1559 box.q_maximumSize = minimumMaximumSize;
1560 }
1561 }
1562}
1563
1564void QGridLayoutEngine::ensureEffectiveFirstAndLastRows() const
1565{
1566 if (q_cachedEffectiveFirstRows[Qt::Horizontal] == -1 && !q_items.isEmpty()) {
1567 int rowCount = this->rowCount();
1568 int columnCount = this->columnCount();
1569
1570 q_cachedEffectiveFirstRows = {columnCount, rowCount};
1571 q_cachedEffectiveLastRows = {-1, -1};
1572
1573 for (int i = q_items.size() - 1; i >= 0; --i) {
1574 const QGridLayoutItem *item = q_items.at(i);
1575
1576 for (Qt::Orientation o : {Qt::Horizontal, Qt::Vertical}) {
1577 if (item->firstRow(o) < q_cachedEffectiveFirstRows[o])
1578 q_cachedEffectiveFirstRows[o] = item->firstRow(o);
1579 if (item->lastRow(o) > q_cachedEffectiveLastRows[o])
1580 q_cachedEffectiveLastRows[o] = item->lastRow(o);
1581 }
1582 }
1583 }
1584}
1585
1586void QGridLayoutEngine::ensureColumnAndRowData(QGridLayoutRowData *rowData, QGridLayoutBox *totalBox,
1587 const qreal *colPositions, const qreal *colSizes,
1588 Qt::Orientation orientation,
1589 const QAbstractLayoutStyleInfo *styleInfo) const
1590{
1591 const int cc = columnCount(orientation);
1592
1593 const qreal constraint = (colPositions && colSizes && hasDynamicConstraint()) ? (colPositions[cc - 1] + colSizes[cc - 1]) : qreal(CachedWithNoConstraint);
1594 qreal &cachedConstraint = q_totalBoxCachedConstraints[orientation];
1595 if (cachedConstraint == constraint) {
1596 if (totalBox != &q_totalBoxes[orientation])
1597 *totalBox = q_totalBoxes[orientation];
1598 return;
1599 }
1600 rowData->reset(rowCount(orientation));
1601 fillRowData(rowData, colPositions, colSizes, orientation, styleInfo);
1602 const QGridLayoutRowInfo &rowInfo = q_infos[orientation];
1603 rowData->distributeMultiCells(rowInfo, m_snapToPixelGrid);
1604 *totalBox = rowData->totalBox(0, rowCount(orientation));
1605
1606 if (totalBox != &q_totalBoxes[orientation])
1607 q_totalBoxes[orientation] = *totalBox;
1608
1609 cachedConstraint = constraint;
1610}
1611
1612/**
1613 returns false if the layout has contradicting constraints (i.e. some items with a horizontal
1614 constraint and other items with a vertical constraint)
1615 */
1616bool QGridLayoutEngine::ensureDynamicConstraint() const
1617{
1618 if (q_cachedConstraintOrientation == UnknownConstraint) {
1619 for (int i = q_items.size() - 1; i >= 0; --i) {
1620 QGridLayoutItem *item = q_items.at(i);
1621 if (item->hasDynamicConstraint()) {
1622 Qt::Orientation itemConstraintOrientation = item->dynamicConstraintOrientation();
1623 if (q_cachedConstraintOrientation == UnknownConstraint) {
1624 q_cachedConstraintOrientation = itemConstraintOrientation;
1625 } else if (q_cachedConstraintOrientation != itemConstraintOrientation) {
1626 q_cachedConstraintOrientation = UnfeasibleConstraint;
1627 qWarning("QGridLayoutEngine: Unfeasible, cannot mix horizontal and"
1628 " vertical constraint in the same layout");
1629 return false;
1630 }
1631 }
1632 }
1633 if (q_cachedConstraintOrientation == UnknownConstraint)
1634 q_cachedConstraintOrientation = NoConstraint;
1635 }
1636 return true;
1637}
1638
1639bool QGridLayoutEngine::hasDynamicConstraint() const
1640{
1641 if (!ensureDynamicConstraint())
1642 return false;
1643 return q_cachedConstraintOrientation != NoConstraint;
1644}
1645
1646/*
1647 * return value is only valid if hasConstraint() returns \c true
1648 */
1649Qt::Orientation QGridLayoutEngine::constraintOrientation() const
1650{
1651 (void)ensureDynamicConstraint();
1652 return (Qt::Orientation)q_cachedConstraintOrientation;
1653}
1654
1655void QGridLayoutEngine::ensureGeometries(const QSizeF &size,
1656 const QAbstractLayoutStyleInfo *styleInfo) const
1657{
1658 if (q_cachedSize == size)
1659 return;
1660
1661 q_cachedSize = size;
1662
1663 q_xx.resize(columnCount());
1664 q_widths.resize(columnCount());
1665 q_yy.resize(rowCount());
1666 q_heights.resize(rowCount());
1667 q_descents.resize(rowCount());
1668
1669 if (constraintOrientation() != Qt::Horizontal) {
1670 //We might have items whose height depends on their width (HFW)
1671 ensureColumnAndRowData(&q_columnData, &q_totalBoxes[Qt::Horizontal], nullptr, nullptr, Qt::Horizontal, styleInfo);
1672 //Calculate column widths and positions, and put results in q_xx.data() and q_widths.data() so that we can use this information as
1673 //constraints to find the row heights
1674 q_columnData.calculateGeometries(0, columnCount(), size.width(), q_xx.data(), q_widths.data(),
1675 nullptr, q_totalBoxes[Qt::Horizontal], q_infos[Qt::Horizontal], m_snapToPixelGrid);
1676 ensureColumnAndRowData(&q_rowData, &q_totalBoxes[Qt::Vertical], q_xx.data(), q_widths.data(), Qt::Vertical, styleInfo);
1677 //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data()
1678 q_rowData.calculateGeometries(0, rowCount(), size.height(), q_yy.data(), q_heights.data(),
1679 q_descents.data(), q_totalBoxes[Qt::Vertical], q_infos[Qt::Vertical], m_snapToPixelGrid);
1680 } else {
1681 //We have items whose width depends on their height (WFH)
1682 ensureColumnAndRowData(&q_rowData, &q_totalBoxes[Qt::Vertical], nullptr, nullptr, Qt::Vertical, styleInfo);
1683 //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() so that we can use this information as
1684 //constraints to find the column widths
1685 q_rowData.calculateGeometries(0, rowCount(), size.height(), q_yy.data(), q_heights.data(),
1686 q_descents.data(), q_totalBoxes[Qt::Vertical], q_infos[Qt::Vertical], m_snapToPixelGrid);
1687 ensureColumnAndRowData(&q_columnData, &q_totalBoxes[Qt::Horizontal], q_yy.data(), q_heights.data(), Qt::Horizontal, styleInfo);
1688 //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data()
1689 q_columnData.calculateGeometries(0, columnCount(), size.width(), q_xx.data(), q_widths.data(),
1690 nullptr, q_totalBoxes[Qt::Horizontal], q_infos[Qt::Horizontal], m_snapToPixelGrid);
1691 }
1692}
1693
1694QT_END_NAMESPACE
friend bool operator==(const QByteArray::FromBase64Result &lhs, const QByteArray::FromBase64Result &rhs) noexcept
Returns true if lhs and rhs are equal, otherwise returns false.
Definition qbytearray.h:801
QGridLayoutBox totalBox(int start, int end) const
void stealBox(int start, int end, int which, qreal *positions, qreal *sizes)
void calculateGeometries(int start, int end, qreal targetSize, qreal *positions, qreal *sizes, qreal *descents, const QGridLayoutBox &totalBox, const QGridLayoutRowInfo &rowInfo, bool snapToPixelGrid)
void distributeMultiCells(const QGridLayoutRowInfo &rowInfo, bool snapToPixelGrid)
void insertOrRemoveRows(int row, int delta)
static void insertOrRemoveItems(QList< T > &items, int index, int delta)
static qreal growthFactorBelowPreferredSize(qreal desired, qreal sumAvailable, qreal sumDesired)
static void visualRect(QRectF *geom, Qt::LayoutDirection dir, const QRectF &contentsRect)
static qreal fixedDescent(qreal descent, qreal ascent, qreal targetSize)
#define LAYOUTITEMSIZE_MAX
static qreal compare(const QGridLayoutBox &box1, const QGridLayoutBox &box2, int which)
@ MaximumSize