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