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