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
50QT_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*/
158QGraphicsLayout::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*/
179QGraphicsLayout::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*/
200QGraphicsLayout::~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*/
217void 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*/
232void 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*/
257void 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*/
298bool 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*/
311void 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*/
350void 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*/
390void 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 */
472void QGraphicsLayout::addChildLayoutItem(QGraphicsLayoutItem *layoutItem)
473{
474 Q_D(QGraphicsLayout);
475 d->addChildLayoutItem(layoutItem);
476}
477
478static 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*/
505void 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*/
517bool QGraphicsLayout::instantInvalidatePropagation()
518{
519 return g_instantInvalidatePropagation;
520}
521
522QT_END_NAMESPACE
523