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 | |
14 | QT_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 | */ |
122 | QGraphicsLayout::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 | */ |
143 | QGraphicsLayout::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 | */ |
164 | QGraphicsLayout::~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 | */ |
181 | void 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 | */ |
196 | void 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 | */ |
221 | void 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 | */ |
262 | bool 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 | */ |
275 | void 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 | */ |
314 | void 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 | */ |
354 | void 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 | */ |
436 | void QGraphicsLayout::addChildLayoutItem(QGraphicsLayoutItem *layoutItem) |
437 | { |
438 | Q_D(QGraphicsLayout); |
439 | d->addChildLayoutItem(item: layoutItem); |
440 | } |
441 | |
442 | static 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 | */ |
469 | void 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 | */ |
481 | bool QGraphicsLayout::instantInvalidatePropagation() |
482 | { |
483 | return g_instantInvalidatePropagation; |
484 | } |
485 | |
486 | QT_END_NAMESPACE |
487 | |