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