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