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 #include "qapplication.h"
41 
42 #include "qgraphicslayout.h"
43 #include "qgraphicslayout_p.h"
44 #include "qgraphicslayoutitem.h"
45 #include "qgraphicslayoutitem_p.h"
46 #include "qgraphicswidget.h"
47 #include "qgraphicswidget_p.h"
48 #include "qgraphicsscene.h"
49 
50 QT_BEGIN_NAMESPACE
51 
52 /*!
53     \class QGraphicsLayout
54     \brief The QGraphicsLayout class provides the base class for all layouts
55     in Graphics View.
56     \since 4.4
57     \ingroup graphicsview-api
58     \inmodule QtWidgets
59 
60     QGraphicsLayout is an abstract class that defines a virtual API for
61     arranging QGraphicsWidget children and other QGraphicsLayoutItem objects
62     for a QGraphicsWidget. QGraphicsWidget assigns responsibility to a
63     QGraphicsLayout through QGraphicsWidget::setLayout(). As the widget
64     is resized, the layout will automatically arrange the widget's children.
65     QGraphicsLayout inherits QGraphicsLayoutItem, so, it can be managed by
66     any layout, including its own subclasses.
67 
68     \section1 Writing a Custom Layout
69 
70     You can use QGraphicsLayout as a base to write your own custom layout
71     (e.g., a flowlayout), but it is more common to use one of its subclasses
72     instead - QGraphicsLinearLayout or QGraphicsGridLayout. When creating
73     a custom layout, the following functions must be reimplemented as a bare
74     minimum:
75 
76     \table
77     \header \li Function                     \li Description
78     \row     \li QGraphicsLayoutItem::setGeometry()
79                \li Notifies you when the geometry of the layout is set. You can
80                    store the geometry in your own layout class in a reimplementation
81                    of this function.
82     \row    \li QGraphicsLayoutItem::sizeHint()
83                \li Returns the layout's size hints.
84     \row    \li QGraphicsLayout::count()
85               \li Returns the number of items in your layout.
86     \row    \li QGraphicsLayout::itemAt()
87               \li Returns a pointer to an item in your layout.
88     \row    \li QGraphicsLayout::removeAt()
89               \li Removes an item from your layout without destroying it.
90     \endtable
91 
92     For more details on how to implement each function, refer to the individual
93     function documentation.
94 
95     Each layout defines its own API for arranging widgets and layout items.
96     For example, with a grid layout, you require a row and a
97     column index with optional row and column spans, alignment, spacing, and more.
98     A linear layout, however, requires a single row or column index to position its
99     items. For a grid layout, the order of insertion does not affect the layout in
100     any way, but for a linear layout, the order is essential. When writing your own
101     layout subclass, you are free to choose the API that best suits your layout.
102 
103     QGraphicsLayout provides the addChildLayoutItem() convenience function to add
104     layout items to a custom layout. The function will automatically reparent
105     graphics items, if required.
106 
107     \section1 Activating the Layout
108 
109     When the layout's geometry changes, QGraphicsLayout immediately rearranges
110     all of its managed items by calling setGeometry() on each item. This
111     rearrangement is called \e activating the layout.
112 
113     QGraphicsLayout updates its own geometry to match the contentsRect() of the
114     QGraphicsLayoutItem it is managing. Thus, it will automatically rearrange all
115     its items when the widget is resized. QGraphicsLayout caches the sizes of all
116     its managed items to avoid calling setGeometry() too often.
117 
118     \note A QGraphicsLayout will have the same geometry as the contentsRect()
119     of the widget (not the layout) it is assigned to.
120 
121     \section2 Activating the Layout Implicitly
122 
123     The layout can be activated implicitly using one of two ways: by calling
124     activate() or by calling invalidate(). Calling activate() activates the layout
125     immediately. In contrast, calling invalidate() is delayed, as it posts a
126     \l{QEvent::LayoutRequest}{LayoutRequest} event to the managed widget. Due
127     to event compression, the activate() will only be called once after control has
128     returned to the event loop. This is referred to as \e invalidating the layout.
129     Invalidating the layout also invalidates any cached information. Also, the
130     invalidate() function is a virtual function. So, you can invalidate your own
131     cache in a subclass of QGraphicsLayout by reimplementing this function.
132 
133     \section1 Event Handling
134 
135     QGraphicsLayout listens to events for the widget it manages through the
136     virtual widgetEvent() event handler. When the layout is assigned to a
137     widget, all events delivered to the widget are first processed by
138     widgetEvent(). This allows the layout to be aware of any relevant state
139     changes on the widget such as visibility changes or layout direction changes.
140 
141     \section1 Margin Handling
142 
143     The margins of a QGraphicsLayout can be modified by reimplementing
144     setContentsMargins() and getContentsMargins().
145 
146 */
147 
148 /*!
149     Contructs a QGraphicsLayout object.
150 
151     \a parent is passed to QGraphicsLayoutItem's constructor and the
152     QGraphicsLayoutItem's isLayout argument is set to \e true.
153 
154     If \a parent is a QGraphicsWidget the layout will be installed
155     on that widget. (Note that installing a layout will delete the old one
156     installed.)
157 */
QGraphicsLayout(QGraphicsLayoutItem * parent)158 QGraphicsLayout::QGraphicsLayout(QGraphicsLayoutItem *parent)
159     : QGraphicsLayoutItem(*new QGraphicsLayoutPrivate)
160 {
161     setParentLayoutItem(parent);
162     if (parent && !parent->isLayout()) {
163         // If a layout has a parent that is not a layout it must be a QGraphicsWidget.
164         QGraphicsItem *itemParent = parent->graphicsItem();
165         if (itemParent && itemParent->isWidget()) {
166             static_cast<QGraphicsWidget *>(itemParent)->d_func()->setLayout_helper(this);
167         } else {
168             qWarning("QGraphicsLayout::QGraphicsLayout: Attempt to create a layout with a parent that is"
169                     " neither a QGraphicsWidget nor QGraphicsLayout");
170         }
171     }
172     d_func()->sizePolicy = QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, QSizePolicy::DefaultType);
173     setOwnedByLayout(true);
174 }
175 
176 /*!
177     \internal
178 */
QGraphicsLayout(QGraphicsLayoutPrivate & dd,QGraphicsLayoutItem * parent)179 QGraphicsLayout::QGraphicsLayout(QGraphicsLayoutPrivate &dd, QGraphicsLayoutItem *parent)
180     : QGraphicsLayoutItem(dd)
181 {
182     setParentLayoutItem(parent);
183     if (parent && !parent->isLayout()) {
184         // If a layout has a parent that is not a layout it must be a QGraphicsWidget.
185         QGraphicsItem *itemParent = parent->graphicsItem();
186         if (itemParent && itemParent->isWidget()) {
187             static_cast<QGraphicsWidget *>(itemParent)->d_func()->setLayout_helper(this);
188         } else {
189             qWarning("QGraphicsLayout::QGraphicsLayout: Attempt to create a layout with a parent that is"
190                     " neither a QGraphicsWidget nor QGraphicsLayout");
191         }
192     }
193     d_func()->sizePolicy = QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, QSizePolicy::DefaultType);
194     setOwnedByLayout(true);
195 }
196 
197 /*!
198     Destroys the QGraphicsLayout object.
199 */
~QGraphicsLayout()200 QGraphicsLayout::~QGraphicsLayout()
201 {
202 }
203 
204 /*!
205     Sets the contents margins to \a left, \a top, \a right and \a bottom. The
206     default contents margins for toplevel layouts are style dependent
207     (by querying the pixelMetric for QStyle::PM_LayoutLeftMargin,
208     QStyle::PM_LayoutTopMargin, QStyle::PM_LayoutRightMargin and
209     QStyle::PM_LayoutBottomMargin).
210 
211     For sublayouts the default margins are 0.
212 
213     Changing the contents margins automatically invalidates the layout.
214 
215     \sa invalidate()
216 */
setContentsMargins(qreal left,qreal top,qreal right,qreal bottom)217 void QGraphicsLayout::setContentsMargins(qreal left, qreal top, qreal right, qreal bottom)
218 {
219     Q_D(QGraphicsLayout);
220     if (d->left == left && d->top == top && d->right == right && d->bottom == bottom)
221         return;
222     d->left = left;
223     d->right = right;
224     d->top = top;
225     d->bottom = bottom;
226     invalidate();
227 }
228 
229 /*!
230     \reimp
231 */
getContentsMargins(qreal * left,qreal * top,qreal * right,qreal * bottom) const232 void QGraphicsLayout::getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const
233 {
234     Q_D(const QGraphicsLayout);
235     d->getMargin(left, d->left, QStyle::PM_LayoutLeftMargin);
236     d->getMargin(top, d->top, QStyle::PM_LayoutTopMargin);
237     d->getMargin(right, d->right, QStyle::PM_LayoutRightMargin);
238     d->getMargin(bottom, d->bottom, QStyle::PM_LayoutBottomMargin);
239 }
240 
241 /*!
242     Activates the layout, causing all items in the layout to be immediately
243     rearranged. This function is based on calling count() and itemAt(), and
244     then calling setGeometry() on all items sequentially. When activated,
245     the layout will adjust its geometry to its parent's contentsRect().
246     The parent will then invalidate any layout of its own.
247 
248     If called in sequence or recursively, e.g., by one of the arranged items
249     in response to being resized, this function will do nothing.
250 
251     Note that the layout is free to use geometry caching to optimize this
252     process.  To forcefully invalidate any such cache, you can call
253     invalidate() before calling activate().
254 
255     \sa invalidate()
256 */
activate()257 void QGraphicsLayout::activate()
258 {
259     Q_D(QGraphicsLayout);
260     if (d->activated)
261         return;
262 
263     d->activateRecursive(this);
264 
265     // we don't call activate on a sublayout, but somebody might.
266     // Therefore, we walk to the parentitem of the toplevel layout.
267     QGraphicsLayoutItem *parentItem = this;
268     while (parentItem && parentItem->isLayout())
269         parentItem = parentItem->parentLayoutItem();
270     if (!parentItem)
271         return;
272     Q_ASSERT(!parentItem->isLayout());
273 
274     if (QGraphicsLayout::instantInvalidatePropagation()) {
275         QGraphicsWidget *parentWidget = static_cast<QGraphicsWidget*>(parentItem);
276         if (!parentWidget->parentLayoutItem()) {
277             // we've reached the topmost widget, resize it
278             bool wasResized = parentWidget->testAttribute(Qt::WA_Resized);
279             parentWidget->resize(parentWidget->size());
280             parentWidget->setAttribute(Qt::WA_Resized, wasResized);
281         }
282 
283         setGeometry(parentItem->contentsRect());    // relayout children
284     } else {
285         setGeometry(parentItem->contentsRect());    // relayout children
286         parentLayoutItem()->updateGeometry();
287     }
288 }
289 
290 /*!
291     Returns \c true if the layout is currently being activated; otherwise,
292     returns \c false. If the layout is being activated, this means that it is
293     currently in the process of rearranging its items (i.e., the activate()
294     function has been called, and has not yet returned).
295 
296     \sa activate(), invalidate()
297 */
isActivated() const298 bool QGraphicsLayout::isActivated() const
299 {
300     Q_D(const QGraphicsLayout);
301     return d->activated;
302 }
303 
304 /*!
305     Clears any cached geometry and size hint information in the layout, and
306     posts a \l{QEvent::LayoutRequest}{LayoutRequest} event to the managed
307     parent QGraphicsLayoutItem.
308 
309     \sa activate(), setGeometry()
310 */
invalidate()311 void QGraphicsLayout::invalidate()
312 {
313     if (QGraphicsLayout::instantInvalidatePropagation()) {
314         updateGeometry();
315     } else {
316         // only mark layouts as invalid (activated = false) if we can post a LayoutRequest event.
317         QGraphicsLayoutItem *layoutItem = this;
318         while (layoutItem && layoutItem->isLayout()) {
319             // we could call updateGeometry(), but what if that method
320             // does not call the base implementation? In addition, updateGeometry()
321             // does more than we need.
322             layoutItem->d_func()->sizeHintCacheDirty = true;
323             layoutItem->d_func()->sizeHintWithConstraintCacheDirty = true;
324             layoutItem = layoutItem->parentLayoutItem();
325         }
326         if (layoutItem) {
327             layoutItem->d_func()->sizeHintCacheDirty = true;
328             layoutItem->d_func()->sizeHintWithConstraintCacheDirty = true;
329         }
330 
331         bool postIt = layoutItem ? !layoutItem->isLayout() : false;
332         if (postIt) {
333             layoutItem = this;
334             while (layoutItem && layoutItem->isLayout()
335                     && static_cast<QGraphicsLayout*>(layoutItem)->d_func()->activated) {
336                 static_cast<QGraphicsLayout*>(layoutItem)->d_func()->activated = false;
337                 layoutItem = layoutItem->parentLayoutItem();
338             }
339             if (layoutItem && !layoutItem->isLayout()) {
340                 // If a layout has a parent that is not a layout it must be a QGraphicsWidget.
341                 QCoreApplication::postEvent(static_cast<QGraphicsWidget *>(layoutItem), new QEvent(QEvent::LayoutRequest));
342             }
343         }
344     }
345 }
346 
347 /*!
348     \reimp
349 */
updateGeometry()350 void QGraphicsLayout::updateGeometry()
351 {
352     Q_D(QGraphicsLayout);
353     if (QGraphicsLayout::instantInvalidatePropagation()) {
354         d->activated = false;
355         QGraphicsLayoutItem::updateGeometry();
356 
357         QGraphicsLayoutItem *parentItem = parentLayoutItem();
358         if (!parentItem)
359             return;
360 
361         if (parentItem->isLayout())
362             static_cast<QGraphicsLayout *>(parentItem)->invalidate();
363         else
364             parentItem->updateGeometry();
365     } else {
366         QGraphicsLayoutItem::updateGeometry();
367         if (QGraphicsLayoutItem *parentItem = parentLayoutItem()) {
368             if (parentItem->isLayout()) {
369                 parentItem->updateGeometry();
370             } else {
371                 invalidate();
372             }
373         }
374     }
375 }
376 
377 /*!
378     This virtual event handler receives all events for the managed
379     widget. QGraphicsLayout uses this event handler to listen for layout
380     related events such as geometry changes, layout changes or layout
381     direction changes.
382 
383     \a e is a pointer to the event.
384 
385     You can reimplement this event handler to track similar events for your
386     own custom layout.
387 
388     \sa QGraphicsWidget::event(), QGraphicsItem::sceneEvent()
389 */
widgetEvent(QEvent * e)390 void QGraphicsLayout::widgetEvent(QEvent *e)
391 {
392     switch (e->type()) {
393     case QEvent::GraphicsSceneResize:
394         if (isActivated()) {
395             setGeometry(parentLayoutItem()->contentsRect());
396         } else {
397             activate(); // relies on that activate() will call updateGeometry()
398         }
399         break;
400     case QEvent::LayoutRequest:
401         activate();
402         break;
403     case QEvent::LayoutDirectionChange:
404         invalidate();
405         break;
406     default:
407         break;
408     }
409 }
410 
411 /*!
412     \fn virtual int QGraphicsLayout::count() const = 0
413 
414     This pure virtual function must be reimplemented in a subclass of
415     QGraphicsLayout to return the number of items in the layout.
416 
417     The subclass is free to decide how to store the items.
418 
419     \sa itemAt(), removeAt()
420 */
421 
422 /*!
423     \fn virtual QGraphicsLayoutItem *QGraphicsLayout::itemAt(int i) const = 0
424 
425     This pure virtual function must be reimplemented in a subclass of
426     QGraphicsLayout to return a pointer to the item at index \a i. The
427     reimplementation can assume that \a i is valid (i.e., it respects the
428     value of count()).
429     Together with count(), it is provided as a means of iterating over all items in a layout.
430 
431     The subclass is free to decide how to store the items, and the visual arrangement
432     does not have to be reflected through this function.
433 
434     \sa count(), removeAt()
435 */
436 
437 /*!
438     \fn virtual void QGraphicsLayout::removeAt(int index) = 0
439 
440     This pure virtual function must be reimplemented in a subclass of
441     QGraphicsLayout to remove the item at \a index. The
442     reimplementation can assume that \a index is valid (i.e., it
443     respects the value of count()).
444 
445     The implementation must ensure that the parentLayoutItem() of
446     the removed item does not point to this layout, since the item is
447     considered to be removed from the layout hierarchy.
448 
449     If the layout is to be reused between applications, we recommend
450     that the layout deletes the item, but the graphics view framework
451     does not depend on this.
452 
453     The subclass is free to decide how to store the items.
454 
455     \sa itemAt(), count()
456 */
457 
458 /*!
459     \since 4.6
460 
461     This function is a convenience function provided for custom layouts, and will go through
462     all items in the layout and reparent their graphics items to the closest QGraphicsWidget
463     ancestor of the layout.
464 
465     If \a layoutItem is already in a different layout, it will be removed  from that layout.
466 
467     If custom layouts want special behaviour they can ignore to use this function, and implement
468     their own behaviour.
469 
470     \sa graphicsItem()
471  */
addChildLayoutItem(QGraphicsLayoutItem * layoutItem)472 void QGraphicsLayout::addChildLayoutItem(QGraphicsLayoutItem *layoutItem)
473 {
474     Q_D(QGraphicsLayout);
475     d->addChildLayoutItem(layoutItem);
476 }
477 
478 static bool g_instantInvalidatePropagation = false;
479 
480 /*!
481     \internal
482     \since 4.8
483     \sa instantInvalidatePropagation()
484 
485     Calling this function with \a enable set to true will enable a feature that
486     makes propagation of invalidation up to ancestor layout items to be done in
487     one go. It will propagate up the parentLayoutItem() hierarchy until it has
488     reached the root. If the root item is a QGraphicsWidget, it will *post* a
489     layout request to it. When the layout request is consumed it will traverse
490     down the hierarchy of layouts and widgets and activate all layouts that is
491     invalid (not activated). This is the recommended behaviour.
492 
493     If not set it will also propagate up the parentLayoutItem() hierarchy, but
494     it will stop at the \e{first widget} it encounters, and post a layout
495     request to the widget. When the layout request is consumed, this might
496     cause it to continue propagation up to the parentLayoutItem() of the
497     widget. It will continue in this fashion until it has reached a widget with
498     no parentLayoutItem(). This strategy might cause drawing artifacts, since
499     it is not done in one go, and the consumption of layout requests might be
500     interleaved by consumption of paint events, which might cause significant
501     flicker.
502     Note, this is not the recommended behavior, but for compatibility reasons
503     this is the default behaviour.
504 */
setInstantInvalidatePropagation(bool enable)505 void QGraphicsLayout::setInstantInvalidatePropagation(bool enable)
506 {
507     g_instantInvalidatePropagation = enable;
508 }
509 
510 /*!
511     \internal
512     \since 4.8
513     \sa setInstantInvalidatePropagation()
514 
515     returns \c true if the complete widget/layout hierarchy is rearranged in one go.
516 */
instantInvalidatePropagation()517 bool QGraphicsLayout::instantInvalidatePropagation()
518 {
519     return g_instantInvalidatePropagation;
520 }
521 
522 QT_END_NAMESPACE
523