| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtWidgets module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qapplication.h" |
| |
| #include "qgraphicslayout.h" |
| #include "qgraphicslayout_p.h" |
| #include "qgraphicslayoutitem.h" |
| #include "qgraphicslayoutitem_p.h" |
| #include "qgraphicswidget.h" |
| #include "qgraphicswidget_p.h" |
| #include "qgraphicsscene.h" |
| |
| QT_BEGIN_NAMESPACE |
| |
| /*! |
| \class QGraphicsLayout |
| \brief The QGraphicsLayout class provides the base class for all layouts |
| in Graphics View. |
| \since 4.4 |
| \ingroup graphicsview-api |
| \inmodule QtWidgets |
| |
| QGraphicsLayout is an abstract class that defines a virtual API for |
| arranging QGraphicsWidget children and other QGraphicsLayoutItem objects |
| for a QGraphicsWidget. QGraphicsWidget assigns responsibility to a |
| QGraphicsLayout through QGraphicsWidget::setLayout(). As the widget |
| is resized, the layout will automatically arrange the widget's children. |
| QGraphicsLayout inherits QGraphicsLayoutItem, so, it can be managed by |
| any layout, including its own subclasses. |
| |
| \section1 Writing a Custom Layout |
| |
| You can use QGraphicsLayout as a base to write your own custom layout |
| (e.g., a flowlayout), but it is more common to use one of its subclasses |
| instead - QGraphicsLinearLayout or QGraphicsGridLayout. When creating |
| a custom layout, the following functions must be reimplemented as a bare |
| minimum: |
| |
| \table |
| \header \li Function \li Description |
| \row \li QGraphicsLayoutItem::setGeometry() |
| \li Notifies you when the geometry of the layout is set. You can |
| store the geometry in your own layout class in a reimplementation |
| of this function. |
| \row \li QGraphicsLayoutItem::sizeHint() |
| \li Returns the layout's size hints. |
| \row \li QGraphicsLayout::count() |
| \li Returns the number of items in your layout. |
| \row \li QGraphicsLayout::itemAt() |
| \li Returns a pointer to an item in your layout. |
| \row \li QGraphicsLayout::removeAt() |
| \li Removes an item from your layout without destroying it. |
| \endtable |
| |
| For more details on how to implement each function, refer to the individual |
| function documentation. |
| |
| Each layout defines its own API for arranging widgets and layout items. |
| For example, with a grid layout, you require a row and a |
| column index with optional row and column spans, alignment, spacing, and more. |
| A linear layout, however, requires a single row or column index to position its |
| items. For a grid layout, the order of insertion does not affect the layout in |
| any way, but for a linear layout, the order is essential. When writing your own |
| layout subclass, you are free to choose the API that best suits your layout. |
| |
| QGraphicsLayout provides the addChildLayoutItem() convenience function to add |
| layout items to a custom layout. The function will automatically reparent |
| graphics items, if required. |
| |
| \section1 Activating the Layout |
| |
| When the layout's geometry changes, QGraphicsLayout immediately rearranges |
| all of its managed items by calling setGeometry() on each item. This |
| rearrangement is called \e activating the layout. |
| |
| QGraphicsLayout updates its own geometry to match the contentsRect() of the |
| QGraphicsLayoutItem it is managing. Thus, it will automatically rearrange all |
| its items when the widget is resized. QGraphicsLayout caches the sizes of all |
| its managed items to avoid calling setGeometry() too often. |
| |
| \note A QGraphicsLayout will have the same geometry as the contentsRect() |
| of the widget (not the layout) it is assigned to. |
| |
| \section2 Activating the Layout Implicitly |
| |
| The layout can be activated implicitly using one of two ways: by calling |
| activate() or by calling invalidate(). Calling activate() activates the layout |
| immediately. In contrast, calling invalidate() is delayed, as it posts a |
| \l{QEvent::LayoutRequest}{LayoutRequest} event to the managed widget. Due |
| to event compression, the activate() will only be called once after control has |
| returned to the event loop. This is referred to as \e invalidating the layout. |
| Invalidating the layout also invalidates any cached information. Also, the |
| invalidate() function is a virtual function. So, you can invalidate your own |
| cache in a subclass of QGraphicsLayout by reimplementing this function. |
| |
| \section1 Event Handling |
| |
| QGraphicsLayout listens to events for the widget it manages through the |
| virtual widgetEvent() event handler. When the layout is assigned to a |
| widget, all events delivered to the widget are first processed by |
| widgetEvent(). This allows the layout to be aware of any relevant state |
| changes on the widget such as visibility changes or layout direction changes. |
| |
| \section1 Margin Handling |
| |
| The margins of a QGraphicsLayout can be modified by reimplementing |
| setContentsMargins() and getContentsMargins(). |
| |
| */ |
| |
| /*! |
| Contructs a QGraphicsLayout object. |
| |
| \a parent is passed to QGraphicsLayoutItem's constructor and the |
| QGraphicsLayoutItem's isLayout argument is set to \e true. |
| |
| If \a parent is a QGraphicsWidget the layout will be installed |
| on that widget. (Note that installing a layout will delete the old one |
| installed.) |
| */ |
| QGraphicsLayout::QGraphicsLayout(QGraphicsLayoutItem *parent) |
| : QGraphicsLayoutItem(*new QGraphicsLayoutPrivate) |
| { |
| setParentLayoutItem(parent); |
| if (parent && !parent->isLayout()) { |
| // If a layout has a parent that is not a layout it must be a QGraphicsWidget. |
| QGraphicsItem *itemParent = parent->graphicsItem(); |
| if (itemParent && itemParent->isWidget()) { |
| static_cast<QGraphicsWidget *>(itemParent)->d_func()->setLayout_helper(this); |
| } else { |
| qWarning("QGraphicsLayout::QGraphicsLayout: Attempt to create a layout with a parent that is" |
| " neither a QGraphicsWidget nor QGraphicsLayout"); |
| } |
| } |
| d_func()->sizePolicy = QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, QSizePolicy::DefaultType); |
| setOwnedByLayout(true); |
| } |
| |
| /*! |
| \internal |
| */ |
| QGraphicsLayout::QGraphicsLayout(QGraphicsLayoutPrivate &dd, QGraphicsLayoutItem *parent) |
| : QGraphicsLayoutItem(dd) |
| { |
| setParentLayoutItem(parent); |
| if (parent && !parent->isLayout()) { |
| // If a layout has a parent that is not a layout it must be a QGraphicsWidget. |
| QGraphicsItem *itemParent = parent->graphicsItem(); |
| if (itemParent && itemParent->isWidget()) { |
| static_cast<QGraphicsWidget *>(itemParent)->d_func()->setLayout_helper(this); |
| } else { |
| qWarning("QGraphicsLayout::QGraphicsLayout: Attempt to create a layout with a parent that is" |
| " neither a QGraphicsWidget nor QGraphicsLayout"); |
| } |
| } |
| d_func()->sizePolicy = QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, QSizePolicy::DefaultType); |
| setOwnedByLayout(true); |
| } |
| |
| /*! |
| Destroys the QGraphicsLayout object. |
| */ |
| QGraphicsLayout::~QGraphicsLayout() |
| { |
| } |
| |
| /*! |
| Sets the contents margins to \a left, \a top, \a right and \a bottom. The |
| default contents margins for toplevel layouts are style dependent |
| (by querying the pixelMetric for QStyle::PM_LayoutLeftMargin, |
| QStyle::PM_LayoutTopMargin, QStyle::PM_LayoutRightMargin and |
| QStyle::PM_LayoutBottomMargin). |
| |
| For sublayouts the default margins are 0. |
| |
| Changing the contents margins automatically invalidates the layout. |
| |
| \sa invalidate() |
| */ |
| void QGraphicsLayout::setContentsMargins(qreal left, qreal top, qreal right, qreal bottom) |
| { |
| Q_D(QGraphicsLayout); |
| if (d->left == left && d->top == top && d->right == right && d->bottom == bottom) |
| return; |
| d->left = left; |
| d->right = right; |
| d->top = top; |
| d->bottom = bottom; |
| invalidate(); |
| } |
| |
| /*! |
| \reimp |
| */ |
| void QGraphicsLayout::getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const |
| { |
| Q_D(const QGraphicsLayout); |
| d->getMargin(left, d->left, QStyle::PM_LayoutLeftMargin); |
| d->getMargin(top, d->top, QStyle::PM_LayoutTopMargin); |
| d->getMargin(right, d->right, QStyle::PM_LayoutRightMargin); |
| d->getMargin(bottom, d->bottom, QStyle::PM_LayoutBottomMargin); |
| } |
| |
| /*! |
| Activates the layout, causing all items in the layout to be immediately |
| rearranged. This function is based on calling count() and itemAt(), and |
| then calling setGeometry() on all items sequentially. When activated, |
| the layout will adjust its geometry to its parent's contentsRect(). |
| The parent will then invalidate any layout of its own. |
| |
| If called in sequence or recursively, e.g., by one of the arranged items |
| in response to being resized, this function will do nothing. |
| |
| Note that the layout is free to use geometry caching to optimize this |
| process. To forcefully invalidate any such cache, you can call |
| invalidate() before calling activate(). |
| |
| \sa invalidate() |
| */ |
| void QGraphicsLayout::activate() |
| { |
| Q_D(QGraphicsLayout); |
| if (d->activated) |
| return; |
| |
| d->activateRecursive(this); |
| |
| // we don't call activate on a sublayout, but somebody might. |
| // Therefore, we walk to the parentitem of the toplevel layout. |
| QGraphicsLayoutItem *parentItem = this; |
| while (parentItem && parentItem->isLayout()) |
| parentItem = parentItem->parentLayoutItem(); |
| if (!parentItem) |
| return; |
| Q_ASSERT(!parentItem->isLayout()); |
| |
| if (QGraphicsLayout::instantInvalidatePropagation()) { |
| QGraphicsWidget *parentWidget = static_cast<QGraphicsWidget*>(parentItem); |
| if (!parentWidget->parentLayoutItem()) { |
| // we've reached the topmost widget, resize it |
| bool wasResized = parentWidget->testAttribute(Qt::WA_Resized); |
| parentWidget->resize(parentWidget->size()); |
| parentWidget->setAttribute(Qt::WA_Resized, wasResized); |
| } |
| |
| setGeometry(parentItem->contentsRect()); // relayout children |
| } else { |
| setGeometry(parentItem->contentsRect()); // relayout children |
| parentLayoutItem()->updateGeometry(); |
| } |
| } |
| |
| /*! |
| Returns \c true if the layout is currently being activated; otherwise, |
| returns \c false. If the layout is being activated, this means that it is |
| currently in the process of rearranging its items (i.e., the activate() |
| function has been called, and has not yet returned). |
| |
| \sa activate(), invalidate() |
| */ |
| bool QGraphicsLayout::isActivated() const |
| { |
| Q_D(const QGraphicsLayout); |
| return d->activated; |
| } |
| |
| /*! |
| Clears any cached geometry and size hint information in the layout, and |
| posts a \l{QEvent::LayoutRequest}{LayoutRequest} event to the managed |
| parent QGraphicsLayoutItem. |
| |
| \sa activate(), setGeometry() |
| */ |
| void QGraphicsLayout::invalidate() |
| { |
| if (QGraphicsLayout::instantInvalidatePropagation()) { |
| updateGeometry(); |
| } else { |
| // only mark layouts as invalid (activated = false) if we can post a LayoutRequest event. |
| QGraphicsLayoutItem *layoutItem = this; |
| while (layoutItem && layoutItem->isLayout()) { |
| // we could call updateGeometry(), but what if that method |
| // does not call the base implementation? In addition, updateGeometry() |
| // does more than we need. |
| layoutItem->d_func()->sizeHintCacheDirty = true; |
| layoutItem->d_func()->sizeHintWithConstraintCacheDirty = true; |
| layoutItem = layoutItem->parentLayoutItem(); |
| } |
| if (layoutItem) { |
| layoutItem->d_func()->sizeHintCacheDirty = true; |
| layoutItem->d_func()->sizeHintWithConstraintCacheDirty = true; |
| } |
| |
| bool postIt = layoutItem ? !layoutItem->isLayout() : false; |
| if (postIt) { |
| layoutItem = this; |
| while (layoutItem && layoutItem->isLayout() |
| && static_cast<QGraphicsLayout*>(layoutItem)->d_func()->activated) { |
| static_cast<QGraphicsLayout*>(layoutItem)->d_func()->activated = false; |
| layoutItem = layoutItem->parentLayoutItem(); |
| } |
| if (layoutItem && !layoutItem->isLayout()) { |
| // If a layout has a parent that is not a layout it must be a QGraphicsWidget. |
| QCoreApplication::postEvent(static_cast<QGraphicsWidget *>(layoutItem), new QEvent(QEvent::LayoutRequest)); |
| } |
| } |
| } |
| } |
| |
| /*! |
| \reimp |
| */ |
| void QGraphicsLayout::updateGeometry() |
| { |
| Q_D(QGraphicsLayout); |
| if (QGraphicsLayout::instantInvalidatePropagation()) { |
| d->activated = false; |
| QGraphicsLayoutItem::updateGeometry(); |
| |
| QGraphicsLayoutItem *parentItem = parentLayoutItem(); |
| if (!parentItem) |
| return; |
| |
| if (parentItem->isLayout()) |
| static_cast<QGraphicsLayout *>(parentItem)->invalidate(); |
| else |
| parentItem->updateGeometry(); |
| } else { |
| QGraphicsLayoutItem::updateGeometry(); |
| if (QGraphicsLayoutItem *parentItem = parentLayoutItem()) { |
| if (parentItem->isLayout()) { |
| parentItem->updateGeometry(); |
| } else { |
| invalidate(); |
| } |
| } |
| } |
| } |
| |
| /*! |
| This virtual event handler receives all events for the managed |
| widget. QGraphicsLayout uses this event handler to listen for layout |
| related events such as geometry changes, layout changes or layout |
| direction changes. |
| |
| \a e is a pointer to the event. |
| |
| You can reimplement this event handler to track similar events for your |
| own custom layout. |
| |
| \sa QGraphicsWidget::event(), QGraphicsItem::sceneEvent() |
| */ |
| void QGraphicsLayout::widgetEvent(QEvent *e) |
| { |
| switch (e->type()) { |
| case QEvent::GraphicsSceneResize: |
| if (isActivated()) { |
| setGeometry(parentLayoutItem()->contentsRect()); |
| } else { |
| activate(); // relies on that activate() will call updateGeometry() |
| } |
| break; |
| case QEvent::LayoutRequest: |
| activate(); |
| break; |
| case QEvent::LayoutDirectionChange: |
| invalidate(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /*! |
| \fn virtual int QGraphicsLayout::count() const = 0 |
| |
| This pure virtual function must be reimplemented in a subclass of |
| QGraphicsLayout to return the number of items in the layout. |
| |
| The subclass is free to decide how to store the items. |
| |
| \sa itemAt(), removeAt() |
| */ |
| |
| /*! |
| \fn virtual QGraphicsLayoutItem *QGraphicsLayout::itemAt(int i) const = 0 |
| |
| This pure virtual function must be reimplemented in a subclass of |
| QGraphicsLayout to return a pointer to the item at index \a i. The |
| reimplementation can assume that \a i is valid (i.e., it respects the |
| value of count()). |
| Together with count(), it is provided as a means of iterating over all items in a layout. |
| |
| The subclass is free to decide how to store the items, and the visual arrangement |
| does not have to be reflected through this function. |
| |
| \sa count(), removeAt() |
| */ |
| |
| /*! |
| \fn virtual void QGraphicsLayout::removeAt(int index) = 0 |
| |
| This pure virtual function must be reimplemented in a subclass of |
| QGraphicsLayout to remove the item at \a index. The |
| reimplementation can assume that \a index is valid (i.e., it |
| respects the value of count()). |
| |
| The implementation must ensure that the parentLayoutItem() of |
| the removed item does not point to this layout, since the item is |
| considered to be removed from the layout hierarchy. |
| |
| If the layout is to be reused between applications, we recommend |
| that the layout deletes the item, but the graphics view framework |
| does not depend on this. |
| |
| The subclass is free to decide how to store the items. |
| |
| \sa itemAt(), count() |
| */ |
| |
| /*! |
| \since 4.6 |
| |
| This function is a convenience function provided for custom layouts, and will go through |
| all items in the layout and reparent their graphics items to the closest QGraphicsWidget |
| ancestor of the layout. |
| |
| If \a layoutItem is already in a different layout, it will be removed from that layout. |
| |
| If custom layouts want special behaviour they can ignore to use this function, and implement |
| their own behaviour. |
| |
| \sa graphicsItem() |
| */ |
| void QGraphicsLayout::addChildLayoutItem(QGraphicsLayoutItem *layoutItem) |
| { |
| Q_D(QGraphicsLayout); |
| d->addChildLayoutItem(layoutItem); |
| } |
| |
| static bool g_instantInvalidatePropagation = false; |
| |
| /*! |
| \internal |
| \since 4.8 |
| \sa instantInvalidatePropagation() |
| |
| Calling this function with \a enable set to true will enable a feature that |
| makes propagation of invalidation up to ancestor layout items to be done in |
| one go. It will propagate up the parentLayoutItem() hierarchy until it has |
| reached the root. If the root item is a QGraphicsWidget, it will *post* a |
| layout request to it. When the layout request is consumed it will traverse |
| down the hierarchy of layouts and widgets and activate all layouts that is |
| invalid (not activated). This is the recommended behaviour. |
| |
| If not set it will also propagate up the parentLayoutItem() hierarchy, but |
| it will stop at the \e{first widget} it encounters, and post a layout |
| request to the widget. When the layout request is consumed, this might |
| cause it to continue propagation up to the parentLayoutItem() of the |
| widget. It will continue in this fashion until it has reached a widget with |
| no parentLayoutItem(). This strategy might cause drawing artifacts, since |
| it is not done in one go, and the consumption of layout requests might be |
| interleaved by consumption of paint events, which might cause significant |
| flicker. |
| Note, this is not the recommended behavior, but for compatibility reasons |
| this is the default behaviour. |
| */ |
| void QGraphicsLayout::setInstantInvalidatePropagation(bool enable) |
| { |
| g_instantInvalidatePropagation = enable; |
| } |
| |
| /*! |
| \internal |
| \since 4.8 |
| \sa setInstantInvalidatePropagation() |
| |
| returns \c true if the complete widget/layout hierarchy is rearranged in one go. |
| */ |
| bool QGraphicsLayout::instantInvalidatePropagation() |
| { |
| return g_instantInvalidatePropagation; |
| } |
| |
| QT_END_NAMESPACE |