Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qlayoutengine.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qlayout.h"
6#include "private/qlayoutengine_p.h"
7
8#include "qlist.h"
9#include "qwidget.h"
10
11#include <qvarlengtharray.h>
12#include <qdebug.h>
13
14#include <algorithm>
15
17
18//#define QLAYOUT_EXTRA_DEBUG
19
21static inline Fixed64 toFixed(int i) { return (Fixed64)i * 256; }
22static inline int fRound(Fixed64 i) {
23 return (i % 256 < 128) ? i / 256 : 1 + i / 256;
24}
25
26/*
27 This is the main workhorse of the QGridLayout. It portions out
28 available space to the chain's children.
29
30 The calculation is done in fixed point: "fixed" variables are
31 scaled by a factor of 256.
32
33 If the layout runs "backwards" (i.e. RightToLeft or Up) the layout
34 is computed mirror-reversed, and it's the caller's responsibility
35 do reverse the values before use.
36
37 chain contains input and output parameters describing the geometry.
38 count is the count of items in the chain; pos and space give the
39 interval (relative to parentWidget topLeft).
40*/
41void qGeomCalc(QList<QLayoutStruct> &chain, int start, int count, int pos, int space, int spacer)
42{
43 int cHint = 0;
44 int cMin = 0;
45 int sumStretch = 0;
46 int sumSpacing = 0;
47 int expandingCount = 0;
48
49 bool allEmptyNonstretch = true;
50 int pendingSpacing = -1;
51 int spacerCount = 0;
52 int i;
53
54 for (i = start; i < start + count; i++) {
55 QLayoutStruct *data = &chain[i];
56
57 data->done = false;
58 cHint += data->smartSizeHint();
59 cMin += data->minimumSize;
60 sumStretch += data->stretch;
61 if (!data->empty) {
62 /*
63 Using pendingSpacing, we ensure that the spacing for the last
64 (non-empty) item is ignored.
65 */
66 if (pendingSpacing >= 0) {
67 sumSpacing += pendingSpacing;
68 ++spacerCount;
69 }
70 pendingSpacing = data->effectiveSpacer(spacer);
71 }
72 if (data->expansive)
73 expandingCount++;
74 allEmptyNonstretch = allEmptyNonstretch && data->empty && !data->expansive && data->stretch <= 0;
75 }
76
77 int extraspace = 0;
78
79 if (space < cMin + sumSpacing) {
80 /*
81 Less space than minimumSize; take from the biggest first
82 */
83
84 int minSize = cMin + sumSpacing;
85
86 // shrink the spacers proportionally
87 if (spacer >= 0) {
88 spacer = minSize > 0 ? spacer * space / minSize : 0;
89 sumSpacing = spacer * spacerCount;
90 }
91
92 QVarLengthArray<int, 32> minimumSizes;
93 minimumSizes.reserve(count);
94
95 for (i = start; i < start + count; i++)
96 minimumSizes << chain.at(i).minimumSize;
97
98 std::sort(minimumSizes.begin(), minimumSizes.end());
99
100 int space_left = space - sumSpacing;
101
102 int sum = 0;
103 int idx = 0;
104 int space_used=0;
105 int current = 0;
106 while (idx < count && space_used < space_left) {
107 current = minimumSizes.at(idx);
108 space_used = sum + current * (count - idx);
109 sum += current;
110 ++idx;
111 }
112 --idx;
113 int deficit = space_used - space_left;
114
115 int items = count - idx;
116 /*
117 * If we truncate all items to "current", we would get "deficit" too many pixels. Therefore, we have to remove
118 * deficit/items from each item bigger than maxval. The actual value to remove is deficitPerItem + remainder/items
119 * "rest" is the accumulated error from using integer arithmetic.
120 */
121 int deficitPerItem = deficit/items;
122 int remainder = deficit % items;
123 int maxval = current - deficitPerItem;
124
125 int rest = 0;
126 for (i = start; i < start + count; i++) {
127 int maxv = maxval;
128 rest += remainder;
129 if (rest >= items) {
130 maxv--;
131 rest-=items;
132 }
133 QLayoutStruct *data = &chain[i];
134 data->size = qMin(data->minimumSize, maxv);
135 data->done = true;
136 }
137 } else if (space < cHint + sumSpacing) {
138 /*
139 Less space than smartSizeHint(), but more than minimumSize.
140 Currently take space equally from each, as in Qt 2.x.
141 Commented-out lines will give more space to stretchier
142 items.
143 */
144 int n = count;
145 int space_left = space - sumSpacing;
146 int overdraft = cHint - space_left;
147
148 // first give to the fixed ones:
149 for (i = start; i < start + count; i++) {
150 QLayoutStruct *data = &chain[i];
151 if (!data->done
152 && data->minimumSize >= data->smartSizeHint()) {
153 data->size = data->smartSizeHint();
154 data->done = true;
155 space_left -= data->smartSizeHint();
156 // sumStretch -= data->stretch;
157 n--;
158 }
159 }
160 bool finished = n == 0;
161 while (!finished) {
162 finished = true;
163 Fixed64 fp_over = toFixed(overdraft);
164 Fixed64 fp_w = 0;
165
166 for (i = start; i < start+count; i++) {
167 QLayoutStruct *data = &chain[i];
168 if (data->done)
169 continue;
170 // if (sumStretch <= 0)
171 fp_w += fp_over / n;
172 // else
173 // fp_w += (fp_over * data->stretch) / sumStretch;
174 int w = fRound(fp_w);
175 data->size = data->smartSizeHint() - w;
176 fp_w -= toFixed(w); // give the difference to the next
177 if (data->size < data->minimumSize) {
178 data->done = true;
179 data->size = data->minimumSize;
180 finished = false;
181 overdraft -= data->smartSizeHint() - data->minimumSize;
182 // sumStretch -= data->stretch;
183 n--;
184 break;
185 }
186 }
187 }
188 } else { // extra space
189 int n = count;
190 int space_left = space - sumSpacing;
191 // first give to the fixed ones, and handle non-expansiveness
192 for (i = start; i < start + count; i++) {
193 QLayoutStruct *data = &chain[i];
194 if (!data->done
195 && (data->maximumSize <= data->smartSizeHint()
196 || (!allEmptyNonstretch && data->empty &&
197 !data->expansive && data->stretch == 0))) {
198 data->size = data->smartSizeHint();
199 data->done = true;
200 space_left -= data->size;
201 sumStretch -= data->stretch;
202 if (data->expansive)
203 expandingCount--;
204 n--;
205 }
206 }
207 extraspace = space_left;
208
209 /*
210 Do a trial distribution and calculate how much it is off.
211 If there are more deficit pixels than surplus pixels, give
212 the minimum size items what they need, and repeat.
213 Otherwise give to the maximum size items, and repeat.
214
215 Paul Olav Tvete has a wonderful mathematical proof of the
216 correctness of this principle, but unfortunately this
217 comment is too small to contain it.
218 */
219 int surplus, deficit;
220 do {
221 surplus = deficit = 0;
222 Fixed64 fp_space = toFixed(space_left);
223 Fixed64 fp_w = 0;
224 for (i = start; i < start + count; i++) {
225 QLayoutStruct *data = &chain[i];
226 if (data->done)
227 continue;
228 extraspace = 0;
229 if (sumStretch > 0) {
230 fp_w += (fp_space * data->stretch) / sumStretch;
231 } else if (expandingCount > 0) {
232 fp_w += (fp_space * (data->expansive ? 1 : 0)) / expandingCount;
233 } else {
234 fp_w += fp_space * 1 / n;
235 }
236 int w = fRound(fp_w);
237 data->size = w;
238 fp_w -= toFixed(w); // give the difference to the next
239 if (w < data->smartSizeHint()) {
240 deficit += data->smartSizeHint() - w;
241 } else if (w > data->maximumSize) {
242 surplus += w - data->maximumSize;
243 }
244 }
245 if (deficit > 0 && surplus <= deficit) {
246 // give to the ones that have too little
247 for (i = start; i < start+count; i++) {
248 QLayoutStruct *data = &chain[i];
249 if (!data->done && data->size < data->smartSizeHint()) {
250 data->size = data->smartSizeHint();
251 data->done = true;
252 space_left -= data->smartSizeHint();
253 sumStretch -= data->stretch;
254 if (data->expansive)
255 expandingCount--;
256 n--;
257 }
258 }
259 }
260 if (surplus > 0 && surplus >= deficit) {
261 // take from the ones that have too much
262 for (i = start; i < start + count; i++) {
263 QLayoutStruct *data = &chain[i];
264 if (!data->done && data->size > data->maximumSize) {
265 data->size = data->maximumSize;
266 data->done = true;
267 space_left -= data->maximumSize;
268 sumStretch -= data->stretch;
269 if (data->expansive)
270 expandingCount--;
271 n--;
272 }
273 }
274 }
275 } while (n > 0 && surplus != deficit);
276 if (n == 0)
277 extraspace = space_left;
278 }
279
280 /*
281 As a last resort, we distribute the unwanted space equally
282 among the spacers (counting the start and end of the chain). We
283 could, but don't, attempt a sub-pixel allocation of the extra
284 space.
285 */
286 int extra = extraspace / (spacerCount + 2);
287 int p = pos + extra;
288 for (i = start; i < start+count; i++) {
289 QLayoutStruct *data = &chain[i];
290 data->pos = p;
291 p += data->size;
292 if (!data->empty)
293 p += data->effectiveSpacer(spacer) + extra;
294 }
295
296#ifdef QLAYOUT_EXTRA_DEBUG
297 qDebug() << "qGeomCalc" << "start" << start << "count" << count << "pos" << pos
298 << "space" << space << "spacer" << spacer;
299 for (i = start; i < start + count; ++i) {
300 qDebug() << i << ':' << chain[i].minimumSize << chain[i].smartSizeHint()
301 << chain[i].maximumSize << "stretch" << chain[i].stretch
302 << "empty" << chain[i].empty << "expansive" << chain[i].expansive
303 << "spacing" << chain[i].spacing;
304 qDebug() << "result pos" << chain[i].pos << "size" << chain[i].size;
305 }
306#endif
307}
308
309Q_WIDGETS_EXPORT QSize qSmartMinSize(const QSize &sizeHint, const QSize &minSizeHint,
310 const QSize &minSize, const QSize &maxSize,
311 const QSizePolicy &sizePolicy)
312{
313 QSize s(0, 0);
314
315 if (sizePolicy.horizontalPolicy() != QSizePolicy::Ignored) {
316 if (sizePolicy.horizontalPolicy() & QSizePolicy::ShrinkFlag)
317 s.setWidth(minSizeHint.width());
318 else
319 s.setWidth(qMax(sizeHint.width(), minSizeHint.width()));
320 }
321
322 if (sizePolicy.verticalPolicy() != QSizePolicy::Ignored) {
323 if (sizePolicy.verticalPolicy() & QSizePolicy::ShrinkFlag) {
324 s.setHeight(minSizeHint.height());
325 } else {
326 s.setHeight(qMax(sizeHint.height(), minSizeHint.height()));
327 }
328 }
329
330 s = s.boundedTo(maxSize);
331 if (minSize.width() > 0)
332 s.setWidth(minSize.width());
333 if (minSize.height() > 0)
334 s.setHeight(minSize.height());
335
336 return s.expandedTo(QSize(0,0));
337}
338
339Q_WIDGETS_EXPORT QSize qSmartMinSize(const QWidgetItem *i)
340{
341 QWidget *w = i->widget();
342 return qSmartMinSize(w->sizeHint(), w->minimumSizeHint(),
343 w->minimumSize(), w->maximumSize(),
344 w->sizePolicy());
345}
346
347Q_WIDGETS_EXPORT QSize qSmartMinSize(const QWidget *w)
348{
349 return qSmartMinSize(w->sizeHint(), w->minimumSizeHint(),
350 w->minimumSize(), w->maximumSize(),
351 w->sizePolicy());
352}
353
354Q_WIDGETS_EXPORT QSize qSmartMaxSize(const QSize &sizeHint,
355 const QSize &minSize, const QSize &maxSize,
356 const QSizePolicy &sizePolicy, Qt::Alignment align)
357{
358 if (align & Qt::AlignHorizontal_Mask && align & Qt::AlignVertical_Mask)
359 return QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX);
360 QSize s = maxSize;
361 QSize hint = sizeHint.expandedTo(minSize);
362 if (s.width() == QWIDGETSIZE_MAX && !(align & Qt::AlignHorizontal_Mask))
363 if (!(sizePolicy.horizontalPolicy() & QSizePolicy::GrowFlag))
364 s.setWidth(hint.width());
365
366 if (s.height() == QWIDGETSIZE_MAX && !(align & Qt::AlignVertical_Mask))
367 if (!(sizePolicy.verticalPolicy() & QSizePolicy::GrowFlag))
368 s.setHeight(hint.height());
369
370 if (align & Qt::AlignHorizontal_Mask)
371 s.setWidth(QLAYOUTSIZE_MAX);
372 if (align & Qt::AlignVertical_Mask)
373 s.setHeight(QLAYOUTSIZE_MAX);
374 return s;
375}
376
377Q_WIDGETS_EXPORT QSize qSmartMaxSize(const QWidgetItem *i, Qt::Alignment align)
378{
379 QWidget *w = i->widget();
380 return qSmartMaxSize(w->sizeHint().expandedTo(w->minimumSizeHint()), w->minimumSize(), w->maximumSize(),
381 w->sizePolicy(), align);
382}
383
384Q_WIDGETS_EXPORT QSize qSmartMaxSize(const QWidget *w, Qt::Alignment align)
385{
386 return qSmartMaxSize(w->sizeHint().expandedTo(w->minimumSizeHint()), w->minimumSize(), w->maximumSize(),
387 w->sizePolicy(), align);
388}
389
390Q_WIDGETS_EXPORT int qSmartSpacing(const QLayout *layout, QStyle::PixelMetric pm)
391{
392 QObject *parent = layout->parent();
393 if (!parent) {
394 return -1;
395 } else if (parent->isWidgetType()) {
396 QWidget *pw = static_cast<QWidget *>(parent);
397 return pw->style()->pixelMetric(pm, nullptr, pw);
398 } else {
399 return static_cast<QLayout *>(parent)->spacing();
400 }
401}
402
403QT_END_NAMESPACE
\inmodule QtCore
Definition qsize.h:26
void qGeomCalc(QList< QLayoutStruct > &chain, int start, int count, int pos, int space, int spacer)
static int fRound(Fixed64 i)
static Fixed64 toFixed(int i)
QT_BEGIN_NAMESPACE typedef qint64 Fixed64
Q_WIDGETS_EXPORT int qSmartSpacing(const QLayout *layout, QStyle::PixelMetric pm)
#define QWIDGETSIZE_MAX
Definition qwidget.h:922