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#ifndef QGRIDLAYOUTENGINE_P_H
5#define QGRIDLAYOUTENGINE_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists for the convenience
12// of the graphics view layout classes. This header
13// file may change from version to version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <QtGui/private/qtguiglobal_p.h>
19
20#include <QtCore/qalgorithms.h>
21#include <QtCore/qbitarray.h>
22#include <QtCore/qlist.h>
23#include <QtCore/qmap.h>
24#include <QtCore/qpair.h>
25#include <QtCore/qsize.h>
26#include <QtCore/qrect.h>
27
28#include <float.h>
29#include "qlayoutpolicy_p.h"
30#include "qabstractlayoutstyleinfo_p.h"
31
32// #define QGRIDLAYOUTENGINE_DEBUG
33
34QT_BEGIN_NAMESPACE
35
36class QStyle;
37class QWidget;
38
39// ### deal with Descent in a similar way
40enum {
41 MinimumSize = Qt::MinimumSize,
42 PreferredSize = Qt::PreferredSize,
43 MaximumSize = Qt::MaximumSize,
44 NSizes
45};
46
47// do not reorder
48enum LayoutSide {
49 Left,
50 Top,
51 Right,
52 Bottom
53};
54
55enum {
56 NoConstraint,
57 HorizontalConstraint, // Width depends on the height
58 VerticalConstraint, // Height depends on the width
59 UnknownConstraint, // need to update cache
60 UnfeasibleConstraint // not feasible, it be has some items with Vertical and others with Horizontal constraints
61};
62
63/*
64 Minimal container to store Qt::Orientation-discriminated values.
65
66 The salient feature is the indexing operator, which takes
67 Qt::Orientation (and assumes it's passed only Qt::Horizontal or Qt::Vertical).
68*/
69template <typename T>
70class QHVContainer {
71 T m_data[2];
72
73 static_assert(Qt::Horizontal == 0x1);
74 static_assert(Qt::Vertical == 0x2);
75 static constexpr int map(Qt::Orientation o) noexcept
76 {
77 return int(o) - 1;
78 }
79 static constexpr int mapOther(Qt::Orientation o) noexcept
80 {
81 return 2 - int(o);
82 }
83public:
84 constexpr QHVContainer(const T &h, const T &v)
85 noexcept(std::is_nothrow_copy_constructible_v<T>)
86 : m_data{h, v} {}
87 QHVContainer() = default;
88
89 constexpr T &operator[](Qt::Orientation o) noexcept { return m_data[map(o)]; }
90 constexpr const T &operator[](Qt::Orientation o) const noexcept { return m_data[map(o)]; }
91
92 constexpr T &other(Qt::Orientation o) noexcept { return m_data[mapOther(o)]; }
93 constexpr const T &other(Qt::Orientation o) const noexcept { return m_data[mapOther(o)]; }
94
95 constexpr void transpose() noexcept { qSwap(m_data[0], m_data[1]); }
96 constexpr QHVContainer transposed() const
97 noexcept(std::is_nothrow_copy_constructible_v<T>)
98 { return {m_data[1], m_data[0]}; }
99};
100
101template <typename T>
102class QLayoutParameter
103{
104public:
105 enum State { Default, User, Cached };
106
107 inline QLayoutParameter() : q_value(T()), q_state(Default) {}
108 inline QLayoutParameter(T value, State state = Default) : q_value(value), q_state(state) {}
109
110 inline void setUserValue(T value) {
111 q_value = value;
112 q_state = User;
113 }
114 inline void setCachedValue(T value) const {
115 if (q_state != User) {
116 q_value = value;
117 q_state = Cached;
118 }
119 }
120 inline T value() const { return q_value; }
121 inline T value(T defaultValue) const { return isUser() ? q_value : defaultValue; }
122 inline bool isDefault() const { return q_state == Default; }
123 inline bool isUser() const { return q_state == User; }
124 inline bool isCached() const { return q_state == Cached; }
125
126private:
127 mutable T q_value;
128 mutable State q_state;
129};
130
131class QStretchParameter : public QLayoutParameter<int>
132{
133public:
134 QStretchParameter() : QLayoutParameter<int>(-1) {}
135
136};
137
138class Q_GUI_EXPORT QGridLayoutBox
139{
140public:
141 inline QGridLayoutBox()
142 : q_minimumSize(0), q_preferredSize(0), q_maximumSize(FLT_MAX),
143 q_minimumDescent(-1), q_minimumAscent(-1) {}
144
145 void add(const QGridLayoutBox &other, int stretch, qreal spacing);
146 void combine(const QGridLayoutBox &other);
147 void normalize();
148
149#ifdef QGRIDLAYOUTENGINE_DEBUG
150 void dump(int indent = 0) const;
151#endif
152 // This code could use the union-struct-array trick, but a compiler
153 // bug prevents this from working.
154 qreal q_minimumSize;
155 qreal q_preferredSize;
156 qreal q_maximumSize;
157 qreal q_minimumDescent;
158 qreal q_minimumAscent;
159 inline qreal &q_sizes(int which)
160 {
161 return const_cast<qreal&>(static_cast<const QGridLayoutBox*>(this)->q_sizes(which));
162 }
163 inline const qreal &q_sizes(int which) const
164 {
165 switch (which) {
166 case Qt::MinimumSize:
167 return q_minimumSize;
168 case Qt::PreferredSize:
169 return q_preferredSize;
170 case Qt::MaximumSize:
171 return q_maximumSize;
172 case Qt::MinimumDescent:
173 return q_minimumDescent;
174 case (Qt::MinimumDescent + 1):
175 return q_minimumAscent;
176 default:
177 Q_UNREACHABLE();
178 }
179 }
180};
181Q_DECLARE_TYPEINFO(QGridLayoutBox, Q_RELOCATABLE_TYPE); // cannot be Q_PRIMITIVE_TYPE, as q_maximumSize, say, is != 0
182
183bool operator==(const QGridLayoutBox &box1, const QGridLayoutBox &box2);
184inline bool operator!=(const QGridLayoutBox &box1, const QGridLayoutBox &box2)
185 { return !operator==(box1, box2); }
186
187class QGridLayoutMultiCellData
188{
189public:
190 inline QGridLayoutMultiCellData() : q_stretch(-1) {}
191
192 QGridLayoutBox q_box;
193 int q_stretch;
194};
195
196typedef QMap<QPair<int, int>, QGridLayoutMultiCellData> MultiCellMap;
197
198class QGridLayoutRowInfo;
199
200class QGridLayoutRowData
201{
202public:
203 void reset(int count);
204 void distributeMultiCells(const QGridLayoutRowInfo &rowInfo, bool snapToPixelGrid);
205 void calculateGeometries(int start, int end, qreal targetSize, qreal *positions, qreal *sizes,
206 qreal *descents, const QGridLayoutBox &totalBox,
207 const QGridLayoutRowInfo &rowInfo, bool snapToPixelGrid);
208 QGridLayoutBox totalBox(int start, int end) const;
209 void stealBox(int start, int end, int which, qreal *positions, qreal *sizes);
210
211#ifdef QGRIDLAYOUTENGINE_DEBUG
212 void dump(int indent = 0) const;
213#endif
214
215 QBitArray ignore; // ### rename q_
216 QList<QGridLayoutBox> boxes;
217 MultiCellMap multiCellMap;
218 QList<int> stretches;
219 QList<qreal> spacings;
220 bool hasIgnoreFlag;
221};
222
223class QGridLayoutRowInfo
224{
225public:
226 inline QGridLayoutRowInfo() : count(0) {}
227
228 void insertOrRemoveRows(int row, int delta);
229
230#ifdef QGRIDLAYOUTENGINE_DEBUG
231 void dump(int indent = 0) const;
232#endif
233
234 int count;
235 QList<QStretchParameter> stretches;
236 QList<QLayoutParameter<qreal>> spacings;
237 QList<Qt::Alignment> alignments;
238 QList<QGridLayoutBox> boxes;
239};
240
241
242class Q_GUI_EXPORT QGridLayoutItem
243{
244public:
245 QGridLayoutItem(int row, int column, int rowSpan = 1, int columnSpan = 1,
246 Qt::Alignment alignment = { });
247 virtual ~QGridLayoutItem() {}
248
249 inline int firstRow() const { return q_firstRows[Qt::Vertical]; }
250 inline int firstColumn() const { return q_firstRows[Qt::Horizontal]; }
251 inline int rowSpan() const { return q_rowSpans[Qt::Vertical]; }
252 inline int columnSpan() const { return q_rowSpans[Qt::Horizontal]; }
253 inline int lastRow() const { return firstRow() + rowSpan() - 1; }
254 inline int lastColumn() const { return firstColumn() + columnSpan() - 1; }
255
256 int firstRow(Qt::Orientation orientation) const;
257 int firstColumn(Qt::Orientation orientation) const;
258 int lastRow(Qt::Orientation orientation) const;
259 int lastColumn(Qt::Orientation orientation) const;
260 int rowSpan(Qt::Orientation orientation) const;
261 int columnSpan(Qt::Orientation orientation) const;
262 void setFirstRow(int row, Qt::Orientation orientation = Qt::Vertical);
263 void setRowSpan(int rowSpan, Qt::Orientation orientation = Qt::Vertical);
264
265 int stretchFactor(Qt::Orientation orientation) const;
266 void setStretchFactor(int stretch, Qt::Orientation orientation);
267
268 inline Qt::Alignment alignment() const { return q_alignment; }
269 inline void setAlignment(Qt::Alignment alignment) { q_alignment = alignment; }
270
271 virtual QLayoutPolicy::Policy sizePolicy(Qt::Orientation orientation) const = 0;
272 virtual QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const = 0;
273 virtual bool isEmpty() const { return false; }
274
275 virtual void setGeometry(const QRectF &rect) = 0;
276 /*
277 returns true if the size policy returns true for either hasHeightForWidth()
278 or hasWidthForHeight()
279 */
280 virtual bool hasDynamicConstraint() const { return false; }
281 virtual Qt::Orientation dynamicConstraintOrientation() const { return Qt::Horizontal; }
282
283
284 virtual QLayoutPolicy::ControlTypes controlTypes(LayoutSide side) const;
285
286 QRectF geometryWithin(qreal x, qreal y, qreal width, qreal height, qreal rowDescent, Qt::Alignment align, bool snapToPixelGrid) const;
287 QGridLayoutBox box(Qt::Orientation orientation, bool snapToPixelGrid, qreal constraint = -1.0) const;
288
289
290 void transpose();
291 void insertOrRemoveRows(int row, int delta, Qt::Orientation orientation = Qt::Vertical);
292 QSizeF effectiveMaxSize(const QSizeF &constraint) const;
293
294#ifdef QGRIDLAYOUTENGINE_DEBUG
295 void dump(int indent = 0) const;
296#endif
297
298private:
299 QHVContainer<int> q_firstRows;
300 QHVContainer<int> q_rowSpans;
301 QHVContainer<int> q_stretches;
302 Qt::Alignment q_alignment;
303
304};
305
306class Q_GUI_EXPORT QGridLayoutEngine
307{
308public:
309 QGridLayoutEngine(Qt::Alignment defaultAlignment = { }, bool snapToPixelGrid = false);
310 inline ~QGridLayoutEngine() { qDeleteAll(c: q_items); }
311
312 int rowCount(Qt::Orientation orientation) const;
313 int columnCount(Qt::Orientation orientation) const;
314 inline int rowCount() const { return q_infos[Qt::Vertical].count; }
315 inline int columnCount() const { return q_infos[Qt::Horizontal].count; }
316 // returns the number of items inserted, which may be less than (rowCount * columnCount)
317 int itemCount() const;
318 QGridLayoutItem *itemAt(int index) const;
319
320 int effectiveFirstRow(Qt::Orientation orientation = Qt::Vertical) const;
321 int effectiveLastRow(Qt::Orientation orientation = Qt::Vertical) const;
322
323 void setSpacing(qreal spacing, Qt::Orientations orientations);
324 qreal spacing(Qt::Orientation orientation, const QAbstractLayoutStyleInfo *styleInfo) const;
325 // ### setSpacingAfterRow(), spacingAfterRow()
326 void setRowSpacing(int row, qreal spacing, Qt::Orientation orientation = Qt::Vertical);
327 qreal rowSpacing(int row, Qt::Orientation orientation = Qt::Vertical) const;
328
329 void setRowStretchFactor(int row, int stretch, Qt::Orientation orientation = Qt::Vertical);
330 int rowStretchFactor(int row, Qt::Orientation orientation = Qt::Vertical) const;
331
332 void setRowSizeHint(Qt::SizeHint which, int row, qreal size,
333 Qt::Orientation orientation = Qt::Vertical);
334 qreal rowSizeHint(Qt::SizeHint which, int row,
335 Qt::Orientation orientation = Qt::Vertical) const;
336
337 bool uniformCellWidths() const;
338 void setUniformCellWidths(bool uniformCellWidths);
339
340 bool uniformCellHeights() const;
341 void setUniformCellHeights(bool uniformCellHeights);
342
343 void setRowAlignment(int row, Qt::Alignment alignment, Qt::Orientation orientation);
344 Qt::Alignment rowAlignment(int row, Qt::Orientation orientation) const;
345
346 Qt::Alignment effectiveAlignment(const QGridLayoutItem *layoutItem) const;
347
348
349 void insertItem(QGridLayoutItem *item, int index);
350 void addItem(QGridLayoutItem *item);
351 void removeItem(QGridLayoutItem *item);
352 void deleteItems()
353 {
354 const QList<QGridLayoutItem *> oldItems = q_items;
355 q_items.clear(); // q_items are used as input when the grid is regenerated in removeRows
356 // The following calls to removeRows are suboptimal
357 int rows = rowCount(orientation: Qt::Vertical);
358 removeRows(row: 0, count: rows, orientation: Qt::Vertical);
359 rows = rowCount(orientation: Qt::Horizontal);
360 removeRows(row: 0, count: rows, orientation: Qt::Horizontal);
361 qDeleteAll(c: oldItems);
362 }
363
364 QGridLayoutItem *itemAt(int row, int column, Qt::Orientation orientation = Qt::Vertical) const;
365 inline void insertRow(int row, Qt::Orientation orientation = Qt::Vertical)
366 { insertOrRemoveRows(row, delta: +1, orientation); }
367 inline void removeRows(int row, int count, Qt::Orientation orientation)
368 { insertOrRemoveRows(row, delta: -count, orientation); }
369
370 void invalidate();
371 void setGeometries(const QRectF &contentsGeometry, const QAbstractLayoutStyleInfo *styleInfo);
372 QRectF cellRect(const QRectF &contentsGeometry, int row, int column, int rowSpan, int columnSpan,
373 const QAbstractLayoutStyleInfo *styleInfo) const;
374 QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint,
375 const QAbstractLayoutStyleInfo *styleInfo) const;
376
377 // heightForWidth / widthForHeight support
378 QSizeF dynamicallyConstrainedSizeHint(Qt::SizeHint which, const QSizeF &constraint) const;
379 bool ensureDynamicConstraint() const;
380 bool hasDynamicConstraint() const;
381 Qt::Orientation constraintOrientation() const;
382
383
384 QLayoutPolicy::ControlTypes controlTypes(LayoutSide side) const;
385 void transpose();
386 void setVisualDirection(Qt::LayoutDirection direction);
387 Qt::LayoutDirection visualDirection() const;
388#ifdef QGRIDLAYOUTENGINE_DEBUG
389 void dump(int indent = 0) const;
390#endif
391
392private:
393 static int grossRoundUp(int n) { return ((n + 2) | 0x3) - 2; }
394
395 void maybeExpandGrid(int row, int column, Qt::Orientation orientation = Qt::Vertical);
396 void regenerateGrid();
397 inline int internalGridRowCount() const { return grossRoundUp(n: rowCount()); }
398 inline int internalGridColumnCount() const { return grossRoundUp(n: columnCount()); }
399 void setItemAt(int row, int column, QGridLayoutItem *item);
400 void insertOrRemoveRows(int row, int delta, Qt::Orientation orientation = Qt::Vertical);
401 void fillRowData(QGridLayoutRowData *rowData,
402 const qreal *colPositions, const qreal *colSizes,
403 Qt::Orientation orientation,
404 const QAbstractLayoutStyleInfo *styleInfo) const;
405 void ensureEffectiveFirstAndLastRows() const;
406 void ensureColumnAndRowData(QGridLayoutRowData *rowData, QGridLayoutBox *totalBox,
407 const qreal *colPositions, const qreal *colSizes,
408 Qt::Orientation orientation,
409 const QAbstractLayoutStyleInfo *styleInfo) const;
410
411 void ensureGeometries(const QSizeF &size, const QAbstractLayoutStyleInfo *styleInfo) const;
412protected:
413 QList<QGridLayoutItem *> q_items;
414private:
415 // User input
416 QList<QGridLayoutItem *> q_grid;
417 QHVContainer<QLayoutParameter<qreal>> q_defaultSpacings;
418 QHVContainer<QGridLayoutRowInfo> q_infos;
419 Qt::LayoutDirection m_visualDirection;
420
421 // Configuration
422 Qt::Alignment m_defaultAlignment;
423 unsigned m_snapToPixelGrid : 1;
424 unsigned m_uniformCellWidths : 1;
425 unsigned m_uniformCellHeights : 1;
426
427 // Lazily computed from the above user input
428 mutable QHVContainer<int> q_cachedEffectiveFirstRows;
429 mutable QHVContainer<int> q_cachedEffectiveLastRows;
430 mutable quint8 q_cachedConstraintOrientation : 3;
431
432 // this is useful to cache
433 mutable QHVContainer<QGridLayoutBox> q_totalBoxes;
434 enum {
435 NotCached = -2, // Cache is empty. Happens when the engine is invalidated.
436 CachedWithNoConstraint = -1 // cache has a totalBox without any HFW/WFH constraints.
437 // >= 0 // cache has a totalBox with this specific constraint.
438 };
439 mutable QHVContainer<qreal> q_totalBoxCachedConstraints; // holds the constraint used for the cached totalBox
440
441 // Layout item input
442 mutable QGridLayoutRowData q_columnData;
443 mutable QGridLayoutRowData q_rowData;
444
445 // Output
446 mutable QSizeF q_cachedSize;
447 mutable QList<qreal> q_xx;
448 mutable QList<qreal> q_yy;
449 mutable QList<qreal> q_widths;
450 mutable QList<qreal> q_heights;
451 mutable QList<qreal> q_descents;
452
453 friend class QGridLayoutItem;
454};
455
456QT_END_NAMESPACE
457
458#endif
459

source code of qtbase/src/gui/util/qgridlayoutengine_p.h