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/*!
5 \class QGraphicsLinearLayout
6 \brief The QGraphicsLinearLayout class provides a horizontal or vertical
7 layout for managing widgets in Graphics View.
8 \since 4.4
9 \ingroup graphicsview-api
10 \inmodule QtWidgets
11
12 The default orientation for a linear layout is Qt::Horizontal. You can
13 choose a vertical orientation either by calling setOrientation(), or by
14 passing Qt::Vertical to QGraphicsLinearLayout's constructor.
15
16 The most common way to use QGraphicsLinearLayout is to construct an object
17 on the heap, passing a parent widget to the constructor, then add widgets
18 and layouts by calling addItem().
19
20 \snippet code/src_gui_graphicsview_qgraphicslinearlayout.cpp 0
21
22 Alternatively, if you do not pass a parent widget to the layout's constructor,
23 you will need to call QGraphicsWidget::setLayout() to set this layout as the
24 top-level layout for that widget, the widget will take ownership of
25 the layout.
26
27 You can add widgets, layouts, stretches (addStretch(), insertStretch() or
28 setStretchFactor()), and spacings (setItemSpacing()) to a linear
29 layout. The layout takes ownership of the items. In some cases when the layout
30 item also inherits from QGraphicsItem (such as QGraphicsWidget) there will be a
31 ambiguity in ownership because the layout item belongs to two ownership hierarchies.
32 See the documentation of QGraphicsLayoutItem::setOwnedByLayout() how to handle
33 this.
34 You can access each item in the layout by calling count() and itemAt(). Calling
35 removeAt() or removeItem() will remove an item from the layout, without
36 destroying it.
37
38 \section1 Size Hints and Size Policies in QGraphicsLinearLayout
39
40 QGraphicsLinearLayout respects each item's size hints and size policies,
41 and when the layout contains more space than the items can fill, each item
42 is arranged according to the layout's alignment for that item. You can set
43 an alignment for each item by calling setAlignment(), and check the
44 alignment for any item by calling alignment(). By default, items are
45 aligned to the top left.
46
47 \section1 Spacing within QGraphicsLinearLayout
48
49 Between the items, the layout distributes some space. The actual amount of
50 space depends on the managed widget's current style, but the common
51 spacing is 4. You can also set your own spacing by calling setSpacing(),
52 and get the current spacing value by calling spacing(). If you want to
53 configure individual spacing for your items, you can call setItemSpacing().
54
55 \section1 Stretch Factor in QGraphicsLinearLayout
56
57 You can assign a stretch factor to each item to control how much space it
58 will get compared to the other items. By default, two identical widgets
59 arranged in a linear layout will have the same size, but if the first
60 widget has a stretch factor of 1 and the second widget has a stretch
61 factor of 2, the first widget will get 1/3 of the available space, and the
62 second will get 2/3.
63
64 QGraphicsLinearLayout calculates the distribution of sizes by adding up
65 the stretch factors of all items, and then dividing the available space
66 accordingly. The default stretch factor is 0 for all items; a factor of 0
67 means the item does not have any defined stretch factor; effectively this
68 is the same as setting the stretch factor to 1. The stretch factor only
69 applies to the available space in the lengthwise direction of the layout
70 (following its orientation). If you want to control both the item's
71 horizontal and vertical stretch, you can use QGraphicsGridLayout instead.
72
73 \section1 QGraphicsLinearLayout Compared to Other Layouts
74
75 QGraphicsLinearLayout is very similar to QVBoxLayout and QHBoxLayout, but
76 in contrast to these classes, it is used to manage QGraphicsWidget and
77 QGraphicsLayout instead of QWidget and QLayout.
78
79 \sa QGraphicsGridLayout, QGraphicsWidget
80*/
81
82#include "qapplication.h"
83
84#include "qwidget.h"
85#include "qgraphicslayout_p.h"
86#include "qgraphicslayoutitem.h"
87#include "qgraphicslinearlayout.h"
88#include "qgraphicswidget.h"
89#include "qgraphicsgridlayoutengine_p.h"
90#include "qgraphicslayoutstyleinfo_p.h"
91#include "qscopedpointer.h"
92#ifdef QT_DEBUG
93#include <QtCore/qdebug.h>
94#endif
95
96QT_BEGIN_NAMESPACE
97
98class QGraphicsLinearLayoutPrivate : public QGraphicsLayoutPrivate
99{
100public:
101 QGraphicsLinearLayoutPrivate(Qt::Orientation orientation)
102 : orientation(orientation)
103 { }
104
105 void removeGridItem(QGridLayoutItem *gridItem);
106 QGraphicsLayoutStyleInfo *styleInfo() const;
107 void fixIndex(int *index) const;
108 int gridRow(int index) const;
109 int gridColumn(int index) const;
110
111 Qt::Orientation orientation;
112 mutable QScopedPointer<QGraphicsLayoutStyleInfo> m_styleInfo;
113 QGraphicsGridLayoutEngine engine;
114};
115
116void QGraphicsLinearLayoutPrivate::removeGridItem(QGridLayoutItem *gridItem)
117{
118 int index = gridItem->firstRow(orientation);
119 engine.removeItem(item: gridItem);
120 engine.removeRows(row: index, count: 1, orientation);
121}
122
123void QGraphicsLinearLayoutPrivate::fixIndex(int *index) const
124{
125 int count = engine.rowCount(orientation);
126 if (uint(*index) > uint(count))
127 *index = count;
128}
129
130int QGraphicsLinearLayoutPrivate::gridRow(int index) const
131{
132 if (orientation == Qt::Horizontal)
133 return 0;
134 return int(qMin(a: uint(index), b: uint(engine.rowCount())));
135}
136
137int QGraphicsLinearLayoutPrivate::gridColumn(int index) const
138{
139 if (orientation == Qt::Vertical)
140 return 0;
141 return int(qMin(a: uint(index), b: uint(engine.columnCount())));
142}
143
144QGraphicsLayoutStyleInfo *QGraphicsLinearLayoutPrivate::styleInfo() const
145{
146 if (!m_styleInfo)
147 m_styleInfo.reset(other: new QGraphicsLayoutStyleInfo(this));
148 return m_styleInfo.data();
149}
150
151/*!
152 Constructs a QGraphicsLinearLayout instance. You can pass the
153 \a orientation for the layout, either horizontal or vertical, and
154 \a parent is passed to QGraphicsLayout's constructor.
155*/
156QGraphicsLinearLayout::QGraphicsLinearLayout(Qt::Orientation orientation, QGraphicsLayoutItem *parent)
157 : QGraphicsLayout(*new QGraphicsLinearLayoutPrivate(orientation), parent)
158{
159}
160
161/*!
162 Constructs a QGraphicsLinearLayout instance using Qt::Horizontal
163 orientation. \a parent is passed to QGraphicsLayout's constructor.
164*/
165QGraphicsLinearLayout::QGraphicsLinearLayout(QGraphicsLayoutItem *parent)
166 : QGraphicsLayout(*new QGraphicsLinearLayoutPrivate(Qt::Horizontal), parent)
167{
168}
169
170/*!
171 Destroys the QGraphicsLinearLayout object.
172*/
173QGraphicsLinearLayout::~QGraphicsLinearLayout()
174{
175 for (int i = count() - 1; i >= 0; --i) {
176 QGraphicsLayoutItem *item = itemAt(index: i);
177 // The following lines can be removed, but this removes the item
178 // from the layout more efficiently than the implementation of
179 // ~QGraphicsLayoutItem.
180 removeAt(index: i);
181 if (item) {
182 item->setParentLayoutItem(nullptr);
183 if (item->ownedByLayout())
184 delete item;
185 }
186 }
187}
188
189/*!
190 Change the layout orientation to \a orientation. Changing the layout
191 orientation will automatically invalidate the layout.
192
193 \sa orientation()
194*/
195void QGraphicsLinearLayout::setOrientation(Qt::Orientation orientation)
196{
197 Q_D(QGraphicsLinearLayout);
198 if (orientation != d->orientation) {
199 d->engine.transpose();
200 d->orientation = orientation;
201 invalidate();
202 }
203}
204
205/*!
206 Returns the layout orientation.
207 \sa setOrientation()
208 */
209Qt::Orientation QGraphicsLinearLayout::orientation() const
210{
211 Q_D(const QGraphicsLinearLayout);
212 return d->orientation;
213}
214
215/*!
216 \fn void QGraphicsLinearLayout::addItem(QGraphicsLayoutItem *item)
217
218 This convenience function is equivalent to calling
219 insertItem(-1, \a item).
220*/
221
222/*!
223 \fn void QGraphicsLinearLayout::addStretch(int stretch)
224
225 This convenience function is equivalent to calling
226 insertStretch(-1, \a stretch).
227*/
228
229/*!
230 Inserts \a item into the layout at \a index, or before any item that is
231 currently at \a index.
232
233 \sa addItem(), itemAt(), insertStretch(), setItemSpacing()
234*/
235void QGraphicsLinearLayout::insertItem(int index, QGraphicsLayoutItem *item)
236{
237 Q_D(QGraphicsLinearLayout);
238 if (!item) {
239 qWarning(msg: "QGraphicsLinearLayout::insertItem: cannot insert null item");
240 return;
241 }
242 if (item == this) {
243 qWarning(msg: "QGraphicsLinearLayout::insertItem: cannot insert itself");
244 return;
245 }
246 d->addChildLayoutItem(item);
247
248 Q_ASSERT(item);
249 d->fixIndex(index: &index);
250 d->engine.insertRow(row: index, orientation: d->orientation);
251 QGraphicsGridLayoutEngineItem *gridEngineItem = new QGraphicsGridLayoutEngineItem(item, d->gridRow(index), d->gridColumn(index), 1, 1, { });
252 d->engine.insertItem(item: gridEngineItem, index);
253 invalidate();
254}
255
256/*!
257 Inserts a stretch of \a stretch at \a index, or before any item that is
258 currently at \a index.
259
260 \sa addStretch(), setStretchFactor(), setItemSpacing(), insertItem()
261*/
262void QGraphicsLinearLayout::insertStretch(int index, int stretch)
263{
264 Q_D(QGraphicsLinearLayout);
265 d->fixIndex(index: &index);
266 d->engine.insertRow(row: index, orientation: d->orientation);
267 d->engine.setRowStretchFactor(row: index, stretch, orientation: d->orientation);
268 invalidate();
269}
270
271/*!
272 Removes \a item from the layout without destroying it. Ownership of
273 \a item is transferred to the caller.
274
275 \sa removeAt(), insertItem()
276*/
277void QGraphicsLinearLayout::removeItem(QGraphicsLayoutItem *item)
278{
279 Q_D(QGraphicsLinearLayout);
280 if (QGraphicsGridLayoutEngineItem *gridItem = d->engine.findLayoutItem(layoutItem: item)) {
281 item->setParentLayoutItem(nullptr);
282 d->removeGridItem(gridItem);
283 delete gridItem;
284 invalidate();
285 }
286}
287
288/*!
289 Removes the item at \a index without destroying it. Ownership of the item
290 is transferred to the caller.
291
292 \sa removeItem(), insertItem()
293*/
294void QGraphicsLinearLayout::removeAt(int index)
295{
296 Q_D(QGraphicsLinearLayout);
297 if (index < 0 || index >= d->engine.itemCount()) {
298 qWarning(msg: "QGraphicsLinearLayout::removeAt: invalid index %d", index);
299 return;
300 }
301
302 if (QGraphicsGridLayoutEngineItem *gridItem = static_cast<QGraphicsGridLayoutEngineItem*>(d->engine.itemAt(index))) {
303 if (QGraphicsLayoutItem *layoutItem = gridItem->layoutItem())
304 layoutItem->setParentLayoutItem(nullptr);
305 d->removeGridItem(gridItem);
306 delete gridItem;
307 invalidate();
308 }
309}
310
311/*!
312 Sets the layout's spacing to \a spacing. Spacing refers to the
313 vertical and horizontal distances between items.
314
315 \sa setItemSpacing(), setStretchFactor(), QGraphicsGridLayout::setSpacing()
316*/
317void QGraphicsLinearLayout::setSpacing(qreal spacing)
318{
319 Q_D(QGraphicsLinearLayout);
320 if (spacing < 0) {
321 qWarning(msg: "QGraphicsLinearLayout::setSpacing: invalid spacing %g", spacing);
322 return;
323 }
324 d->engine.setSpacing(spacing, orientations: Qt::Horizontal | Qt::Vertical);
325 invalidate();
326}
327
328/*!
329 Returns the layout's spacing. Spacing refers to the
330 vertical and horizontal distances between items.
331
332 \sa setSpacing()
333 */
334qreal QGraphicsLinearLayout::spacing() const
335{
336 Q_D(const QGraphicsLinearLayout);
337 return d->engine.spacing(orientation: d->orientation, styleInfo: d->styleInfo());
338}
339
340/*!
341 Sets the spacing after item at \a index to \a spacing.
342*/
343void QGraphicsLinearLayout::setItemSpacing(int index, qreal spacing)
344{
345 Q_D(QGraphicsLinearLayout);
346 d->engine.setRowSpacing(row: index, spacing, orientation: d->orientation);
347 invalidate();
348}
349/*!
350 Returns the spacing after item at \a index.
351*/
352qreal QGraphicsLinearLayout::itemSpacing(int index) const
353{
354 Q_D(const QGraphicsLinearLayout);
355 return d->engine.rowSpacing(row: index, orientation: d->orientation);
356}
357
358/*!
359 Sets the stretch factor for \a item to \a stretch. If an item's stretch
360 factor changes, this function will invalidate the layout.
361
362 Setting \a stretch to 0 removes the stretch factor from the item, and is
363 effectively equivalent to setting \a stretch to 1.
364
365 \sa stretchFactor()
366*/
367void QGraphicsLinearLayout::setStretchFactor(QGraphicsLayoutItem *item, int stretch)
368{
369 Q_D(QGraphicsLinearLayout);
370 if (!item) {
371 qWarning(msg: "QGraphicsLinearLayout::setStretchFactor: cannot assign"
372 " a stretch factor to a null item");
373 return;
374 }
375 if (stretchFactor(item) == stretch)
376 return;
377 d->engine.setStretchFactor(layoutItem: item, stretch, orientation: d->orientation);
378 invalidate();
379}
380
381/*!
382 Returns the stretch factor for \a item. The default stretch factor is 0,
383 meaning that the item has no assigned stretch factor.
384
385 \sa setStretchFactor()
386*/
387int QGraphicsLinearLayout::stretchFactor(QGraphicsLayoutItem *item) const
388{
389 Q_D(const QGraphicsLinearLayout);
390 if (!item) {
391 qWarning(msg: "QGraphicsLinearLayout::setStretchFactor: cannot return"
392 " a stretch factor for a null item");
393 return 0;
394 }
395 return d->engine.stretchFactor(layoutItem: item, orientation: d->orientation);
396}
397
398/*!
399 Sets the alignment of \a item to \a alignment. If \a item's alignment
400 changes, the layout is automatically invalidated.
401
402 \sa alignment(), invalidate()
403*/
404void QGraphicsLinearLayout::setAlignment(QGraphicsLayoutItem *item, Qt::Alignment alignment)
405{
406 Q_D(QGraphicsLinearLayout);
407 if (this->alignment(item) == alignment)
408 return;
409 d->engine.setAlignment(graphicsLayoutItem: item, alignment);
410 invalidate();
411}
412
413/*!
414 Returns the alignment for \a item. The default alignment is
415 Qt::AlignTop | Qt::AlignLeft.
416
417 The alignment decides how the item is positioned within its assigned space
418 in the case where there's more space available in the layout than the
419 widgets can occupy.
420
421 \sa setAlignment()
422*/
423Qt::Alignment QGraphicsLinearLayout::alignment(QGraphicsLayoutItem *item) const
424{
425 Q_D(const QGraphicsLinearLayout);
426 return d->engine.alignment(graphicsLayoutItem: item);
427}
428
429#if 0 // ###
430QSizePolicy::ControlTypes QGraphicsLinearLayout::controlTypes(LayoutSide side) const
431{
432 return d->engine.controlTypes(side);
433}
434#endif
435
436/*!
437 \reimp
438*/
439int QGraphicsLinearLayout::count() const
440{
441 Q_D(const QGraphicsLinearLayout);
442 return d->engine.itemCount();
443}
444
445/*!
446 \reimp
447 When iterating from 0 and up, it will return the items in the visual arranged order.
448*/
449QGraphicsLayoutItem *QGraphicsLinearLayout::itemAt(int index) const
450{
451 Q_D(const QGraphicsLinearLayout);
452 if (index < 0 || index >= d->engine.itemCount()) {
453 qWarning(msg: "QGraphicsLinearLayout::itemAt: invalid index %d", index);
454 return nullptr;
455 }
456 QGraphicsLayoutItem *item = nullptr;
457 if (QGraphicsGridLayoutEngineItem *gridItem = static_cast<QGraphicsGridLayoutEngineItem *>(d->engine.itemAt(index)))
458 item = gridItem->layoutItem();
459 return item;
460}
461
462/*!
463 \reimp
464*/
465void QGraphicsLinearLayout::setGeometry(const QRectF &rect)
466{
467 Q_D(QGraphicsLinearLayout);
468 QGraphicsLayout::setGeometry(rect);
469 QRectF effectiveRect = geometry();
470 qreal left, top, right, bottom;
471 getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
472 Qt::LayoutDirection visualDir = d->visualDirection();
473 d->engine.setVisualDirection(visualDir);
474 if (visualDir == Qt::RightToLeft)
475 qSwap(value1&: left, value2&: right);
476 effectiveRect.adjust(xp1: +left, yp1: +top, xp2: -right, yp2: -bottom);
477#ifdef QGRIDLAYOUTENGINE_DEBUG
478 if (qt_graphicsLayoutDebug()) {
479 static int counter = 0;
480 qDebug() << counter++ << "QGraphicsLinearLayout::setGeometry - " << rect;
481 dump(1);
482 }
483#endif
484 d->engine.setGeometries(contentsGeometry: effectiveRect, styleInfo: d->styleInfo());
485#ifdef QGRIDLAYOUTENGINE_DEBUG
486 if (qt_graphicsLayoutDebug()) {
487 qDebug("post dump");
488 dump(1);
489 }
490#endif
491}
492
493/*!
494 \reimp
495*/
496QSizeF QGraphicsLinearLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
497{
498 Q_D(const QGraphicsLinearLayout);
499 qreal left, top, right, bottom;
500 getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
501 const QSizeF extraMargins(left + right, top + bottom);
502 return d->engine.sizeHint(which , constraint: constraint - extraMargins, styleInfo: d->styleInfo()) + extraMargins;
503}
504
505/*!
506 \reimp
507*/
508void QGraphicsLinearLayout::invalidate()
509{
510 Q_D(QGraphicsLinearLayout);
511 d->engine.invalidate();
512 if (d->m_styleInfo)
513 d->m_styleInfo->invalidate();
514 QGraphicsLayout::invalidate();
515}
516
517/*!
518 \internal
519*/
520void QGraphicsLinearLayout::dump(int indent) const
521{
522#ifdef QGRIDLAYOUTENGINE_DEBUG
523 if (qt_graphicsLayoutDebug()) {
524 Q_D(const QGraphicsLinearLayout);
525 qDebug("%*s%s layout", indent, "",
526 d->orientation == Qt::Horizontal ? "Horizontal" : "Vertical");
527 d->engine.dump(indent + 1);
528 }
529#else
530 Q_UNUSED(indent);
531#endif
532}
533
534QT_END_NAMESPACE
535

source code of qtbase/src/widgets/graphicsview/qgraphicslinearlayout.cpp