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