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

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