| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Quick Layouts 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 "qquicklayout_p.h" |
| #include <QEvent> |
| #include <QtCore/qcoreapplication.h> |
| #include <QtCore/private/qnumeric_p.h> |
| #include <QtCore/qstack.h> |
| #include <QtCore/qmath.h> |
| #include <QtQml/qqmlinfo.h> |
| #include <limits> |
| |
| /*! |
| \qmltype Layout |
| \instantiates QQuickLayoutAttached |
| \inqmlmodule QtQuick.Layouts |
| \ingroup layouts |
| \brief Provides attached properties for items pushed onto a \l GridLayout, |
| \l RowLayout or \l ColumnLayout. |
| |
| An object of type Layout is attached to children of the layout to provide layout specific |
| information about the item. |
| The properties of the attached object influence how the layout will arrange the items. |
| |
| For instance, you can specify \l minimumWidth, \l preferredWidth, and |
| \l maximumWidth if the default values are not satisfactory. |
| |
| When a layout is resized, items may grow or shrink. Due to this, items have a |
| \l{Layout::minimumWidth}{minimum size}, \l{Layout::preferredWidth}{preferred size} and a |
| \l{Layout::maximumWidth}{maximum size}. |
| |
| If minimum size has not been explicitly specified on an item, the size is set to \c 0. |
| If maximum size has not been explicitly specified on an item, the size is set to |
| \c Number.POSITIVE_INFINITY. |
| |
| For layouts, the implicit minimum and maximum sizes depend on the content of the layouts. |
| |
| The \l fillWidth and \l fillHeight properties can either be \c true or \c false. If they are \c |
| false, the item's size will be fixed to its preferred size. Otherwise, it will grow or shrink |
| between its minimum and maximum size as the layout is resized. |
| |
| \note Do not bind to the x, y, width, or height properties of items in a layout, |
| as this would conflict with the goals of Layout, and can also cause binding loops. |
| The width and height properties are used by the layout engine to store the current |
| size of items as calculated from the minimum/preferred/maximum attached properties, |
| and can be ovewritten each time the items are laid out. Use |
| \l {Layout::preferredWidth}{Layout.preferredWidth} and |
| \l {Layout::preferredHeight}{Layout.preferredHeight}, or \l {Item::}{implicitWidth} |
| and \l {Item::}{implicitHeight} to specify the preferred size of items. |
| |
| \sa GridLayout |
| \sa RowLayout |
| \sa ColumnLayout |
| */ |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_LOGGING_CATEGORY(lcQuickLayouts, "qt.quick.layouts") |
| |
| QQuickLayoutAttached::QQuickLayoutAttached(QObject *parent) |
| : QObject(parent), |
| m_minimumWidth(0), |
| m_minimumHeight(0), |
| m_preferredWidth(-1), |
| m_preferredHeight(-1), |
| m_maximumWidth(std::numeric_limits<qreal>::infinity()), |
| m_maximumHeight(std::numeric_limits<qreal>::infinity()), |
| m_defaultMargins(0), |
| m_fallbackWidth(-1), |
| m_fallbackHeight(-1), |
| m_row(-1), |
| m_column(-1), |
| m_rowSpan(1), |
| m_columnSpan(1), |
| m_fillWidth(false), |
| m_fillHeight(false), |
| m_isFillWidthSet(false), |
| m_isFillHeightSet(false), |
| m_isMinimumWidthSet(false), |
| m_isMinimumHeightSet(false), |
| m_isMaximumWidthSet(false), |
| m_isMaximumHeightSet(false), |
| m_changesNotificationEnabled(true), |
| m_isLeftMarginSet(false), |
| m_isTopMarginSet(false), |
| m_isRightMarginSet(false), |
| m_isBottomMarginSet(false) |
| { |
| |
| } |
| |
| /*! |
| \qmlattachedproperty real Layout::minimumWidth |
| |
| This property holds the minimum width of an item in a layout. |
| The default value is the item's implicit minimum width. |
| |
| If the item is a layout, the implicit minimum width will be the minimum width the layout can |
| have without any of its items shrinking below their minimum width. |
| The implicit minimum width for any other item is \c 0. |
| |
| Setting this value to -1 will reset the width back to its implicit minimum width. |
| |
| |
| \sa preferredWidth |
| \sa maximumWidth |
| */ |
| void QQuickLayoutAttached::setMinimumWidth(qreal width) |
| { |
| if (qt_is_nan(width)) |
| return; |
| m_isMinimumWidthSet = width >= 0; |
| if (m_minimumWidth == width) |
| return; |
| |
| m_minimumWidth = width; |
| invalidateItem(); |
| emit minimumWidthChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty real Layout::minimumHeight |
| |
| This property holds the minimum height of an item in a layout. |
| The default value is the item's implicit minimum height. |
| |
| If the item is a layout, the implicit minimum height will be the minimum height the layout can |
| have without any of its items shrinking below their minimum height. |
| The implicit minimum height for any other item is \c 0. |
| |
| Setting this value to -1 will reset the height back to its implicit minimum height. |
| |
| \sa preferredHeight |
| \sa maximumHeight |
| */ |
| void QQuickLayoutAttached::setMinimumHeight(qreal height) |
| { |
| if (qt_is_nan(height)) |
| return; |
| m_isMinimumHeightSet = height >= 0; |
| if (m_minimumHeight == height) |
| return; |
| |
| m_minimumHeight = height; |
| invalidateItem(); |
| emit minimumHeightChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty real Layout::preferredWidth |
| |
| This property holds the preferred width of an item in a layout. |
| If the preferred width is \c -1 it will be ignored, and the layout |
| will use \l{Item::implicitWidth}{implicitWidth} instead. |
| The default is \c -1. |
| |
| \sa minimumWidth |
| \sa maximumWidth |
| */ |
| void QQuickLayoutAttached::setPreferredWidth(qreal width) |
| { |
| if (qt_is_nan(width) || m_preferredWidth == width) |
| return; |
| |
| m_preferredWidth = width; |
| invalidateItem(); |
| emit preferredWidthChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty real Layout::preferredHeight |
| |
| This property holds the preferred height of an item in a layout. |
| If the preferred height is \c -1 it will be ignored, and the layout |
| will use \l{Item::implicitHeight}{implicitHeight} instead. |
| The default is \c -1. |
| |
| \sa minimumHeight |
| \sa maximumHeight |
| */ |
| void QQuickLayoutAttached::setPreferredHeight(qreal height) |
| { |
| if (qt_is_nan(height) || m_preferredHeight == height) |
| return; |
| |
| m_preferredHeight = height; |
| invalidateItem(); |
| emit preferredHeightChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty real Layout::maximumWidth |
| |
| This property holds the maximum width of an item in a layout. |
| The default value is the item's implicit maximum width. |
| |
| If the item is a layout, the implicit maximum width will be the maximum width the layout can |
| have without any of its items growing beyond their maximum width. |
| The implicit maximum width for any other item is \c Number.POSITIVE_INFINITY. |
| |
| Setting this value to \c -1 will reset the width back to its implicit maximum width. |
| |
| \sa minimumWidth |
| \sa preferredWidth |
| */ |
| void QQuickLayoutAttached::setMaximumWidth(qreal width) |
| { |
| if (qt_is_nan(width)) |
| return; |
| m_isMaximumWidthSet = width >= 0; |
| if (m_maximumWidth == width) |
| return; |
| |
| m_maximumWidth = width; |
| invalidateItem(); |
| emit maximumWidthChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty real Layout::maximumHeight |
| |
| The default value is the item's implicit maximum height. |
| |
| If the item is a layout, the implicit maximum height will be the maximum height the layout can |
| have without any of its items growing beyond their maximum height. |
| The implicit maximum height for any other item is \c Number.POSITIVE_INFINITY. |
| |
| Setting this value to \c -1 will reset the height back to its implicit maximum height. |
| |
| \sa minimumHeight |
| \sa preferredHeight |
| */ |
| void QQuickLayoutAttached::setMaximumHeight(qreal height) |
| { |
| if (qt_is_nan(height)) |
| return; |
| m_isMaximumHeightSet = height >= 0; |
| if (m_maximumHeight == height) |
| return; |
| |
| m_maximumHeight = height; |
| invalidateItem(); |
| emit maximumHeightChanged(); |
| } |
| |
| void QQuickLayoutAttached::setMinimumImplicitSize(const QSizeF &sz) |
| { |
| bool emitWidthChanged = false; |
| bool emitHeightChanged = false; |
| if (!m_isMinimumWidthSet && m_minimumWidth != sz.width()) { |
| m_minimumWidth = sz.width(); |
| emitWidthChanged = true; |
| } |
| if (!m_isMinimumHeightSet && m_minimumHeight != sz.height()) { |
| m_minimumHeight = sz.height(); |
| emitHeightChanged = true; |
| } |
| // Only invalidate the item once, and make sure we emit signal changed after the call to |
| // invalidateItem() |
| if (emitWidthChanged || emitHeightChanged) { |
| invalidateItem(); |
| if (emitWidthChanged) |
| emit minimumWidthChanged(); |
| if (emitHeightChanged) |
| emit minimumHeightChanged(); |
| } |
| } |
| |
| void QQuickLayoutAttached::setMaximumImplicitSize(const QSizeF &sz) |
| { |
| bool emitWidthChanged = false; |
| bool emitHeightChanged = false; |
| if (!m_isMaximumWidthSet && m_maximumWidth != sz.width()) { |
| m_maximumWidth = sz.width(); |
| emitWidthChanged = true; |
| } |
| if (!m_isMaximumHeightSet && m_maximumHeight != sz.height()) { |
| m_maximumHeight = sz.height(); |
| emitHeightChanged = true; |
| } |
| // Only invalidate the item once, and make sure we emit changed signal after the call to |
| // invalidateItem() |
| if (emitWidthChanged || emitHeightChanged) { |
| invalidateItem(); |
| if (emitWidthChanged) |
| emit maximumWidthChanged(); |
| if (emitHeightChanged) |
| emit maximumHeightChanged(); |
| } |
| } |
| |
| /*! |
| \qmlattachedproperty bool Layout::fillWidth |
| |
| If this property is \c true, the item will be as wide as possible while respecting |
| the given constraints. If the property is \c false, the item will have a fixed width |
| set to the preferred width. |
| The default is \c false, except for layouts themselves, which default to \c true. |
| |
| \sa fillHeight |
| */ |
| void QQuickLayoutAttached::setFillWidth(bool fill) |
| { |
| m_isFillWidthSet = true; |
| if (m_fillWidth != fill) { |
| m_fillWidth = fill; |
| invalidateItem(); |
| emit fillWidthChanged(); |
| } |
| } |
| |
| /*! |
| \qmlattachedproperty bool Layout::fillHeight |
| |
| If this property is \c true, the item will be as tall as possible while respecting |
| the given constraints. If the property is \c false, the item will have a fixed height |
| set to the preferred height. |
| The default is \c false, except for layouts themselves, which default to \c true. |
| |
| \sa fillWidth |
| */ |
| void QQuickLayoutAttached::setFillHeight(bool fill) |
| { |
| m_isFillHeightSet = true; |
| if (m_fillHeight != fill) { |
| m_fillHeight = fill; |
| invalidateItem(); |
| emit fillHeightChanged(); |
| } |
| } |
| |
| /*! |
| \qmlattachedproperty int Layout::row |
| |
| This property allows you to specify the row position of an item in a \l GridLayout. |
| |
| If both \l column and this property are not set, it is up to the layout to assign a cell to the item. |
| |
| The default value is \c 0. |
| |
| \sa column |
| \sa rowSpan |
| */ |
| void QQuickLayoutAttached::setRow(int row) |
| { |
| if (row >= 0 && row != m_row) { |
| m_row = row; |
| invalidateItem(); |
| emit rowChanged(); |
| } |
| } |
| |
| /*! |
| \qmlattachedproperty int Layout::column |
| |
| This property allows you to specify the column position of an item in a \l GridLayout. |
| |
| If both \l row and this property are not set, it is up to the layout to assign a cell to the item. |
| |
| The default value is \c 0. |
| |
| \sa row |
| \sa columnSpan |
| */ |
| void QQuickLayoutAttached::setColumn(int column) |
| { |
| if (column >= 0 && column != m_column) { |
| m_column = column; |
| invalidateItem(); |
| emit columnChanged(); |
| } |
| } |
| |
| |
| /*! |
| \qmlattachedproperty Qt.Alignment Layout::alignment |
| |
| This property allows you to specify the alignment of an item within the cell(s) it occupies. |
| |
| The default value is \c 0, which means it will be \c{Qt.AlignVCenter | Qt.AlignLeft}. |
| These defaults also apply if only a horizontal or vertical flag is specified: |
| if only a horizontal flag is specified, the default vertical flag will be |
| \c Qt.AlignVCenter, and if only a vertical flag is specified, the default |
| horizontal flag will be \c Qt.AlignLeft. |
| |
| A valid alignment is a combination of the following flags: |
| \list |
| \li Qt::AlignLeft |
| \li Qt::AlignHCenter |
| \li Qt::AlignRight |
| \li Qt::AlignTop |
| \li Qt::AlignVCenter |
| \li Qt::AlignBottom |
| \li Qt::AlignBaseline |
| \endlist |
| |
| */ |
| void QQuickLayoutAttached::setAlignment(Qt::Alignment align) |
| { |
| if (align != m_alignment) { |
| m_alignment = align; |
| if (QQuickLayout *layout = parentLayout()) { |
| layout->setAlignment(item(), align); |
| invalidateItem(); |
| } |
| emit alignmentChanged(); |
| } |
| } |
| |
| /*! |
| \qmlattachedproperty real Layout::margins |
| |
| Sets the margins outside of an item to all have the same value. The item |
| itself does not evaluate its own margins. It is the parent's responsibility |
| to decide if it wants to evaluate the margins. |
| |
| Specifically, margins are only evaluated by ColumnLayout, RowLayout, |
| GridLayout, and other layout-like containers, such as SplitView, where the |
| effective cell size of an item will be increased as the margins are |
| increased. |
| |
| Therefore, if an item with margins is a child of another \c Item, its |
| position, size and implicit size will remain unchanged. |
| |
| Combining margins with alignment will align the item \e including its |
| margins. For instance, a vertically-centered Item with a top margin of \c 1 |
| and a bottom margin of \c 9 will cause the Items effective alignment within |
| the cell to be 4 pixels above the center. |
| |
| The default value is \c 0. |
| |
| \sa leftMargin |
| \sa topMargin |
| \sa rightMargin |
| \sa bottomMargin |
| |
| \since QtQuick.Layouts 1.2 |
| */ |
| void QQuickLayoutAttached::setMargins(qreal m) |
| { |
| if (m == m_defaultMargins) |
| return; |
| |
| m_defaultMargins = m; |
| invalidateItem(); |
| if (!m_isLeftMarginSet && m_margins.left() != m) |
| emit leftMarginChanged(); |
| if (!m_isTopMarginSet && m_margins.top() != m) |
| emit topMarginChanged(); |
| if (!m_isRightMarginSet && m_margins.right() != m) |
| emit rightMarginChanged(); |
| if (!m_isBottomMarginSet && m_margins.bottom() != m) |
| emit bottomMarginChanged(); |
| emit marginsChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty real Layout::leftMargin |
| |
| Specifies the left margin outside of an item. |
| If the value is not set, it will use the value from \l margins. |
| |
| \sa margins |
| |
| \since QtQuick.Layouts 1.2 |
| */ |
| void QQuickLayoutAttached::setLeftMargin(qreal m) |
| { |
| const bool changed = leftMargin() != m; |
| m_margins.setLeft(m); |
| m_isLeftMarginSet = true; |
| if (changed) { |
| invalidateItem(); |
| emit leftMarginChanged(); |
| } |
| } |
| |
| void QQuickLayoutAttached::resetLeftMargin() |
| { |
| const bool changed = m_isLeftMarginSet && (m_defaultMargins != m_margins.left()); |
| m_isLeftMarginSet = false; |
| if (changed) { |
| invalidateItem(); |
| emit leftMarginChanged(); |
| } |
| } |
| |
| /*! |
| \qmlattachedproperty real Layout::topMargin |
| |
| Specifies the top margin outside of an item. |
| If the value is not set, it will use the value from \l margins. |
| |
| \sa margins |
| |
| \since QtQuick.Layouts 1.2 |
| */ |
| void QQuickLayoutAttached::setTopMargin(qreal m) |
| { |
| const bool changed = topMargin() != m; |
| m_margins.setTop(m); |
| m_isTopMarginSet = true; |
| if (changed) { |
| invalidateItem(); |
| emit topMarginChanged(); |
| } |
| } |
| |
| void QQuickLayoutAttached::resetTopMargin() |
| { |
| const bool changed = m_isTopMarginSet && (m_defaultMargins != m_margins.top()); |
| m_isTopMarginSet = false; |
| if (changed) { |
| invalidateItem(); |
| emit topMarginChanged(); |
| } |
| } |
| |
| /*! |
| \qmlattachedproperty real Layout::rightMargin |
| |
| Specifies the right margin outside of an item. |
| If the value is not set, it will use the value from \l margins. |
| |
| \sa margins |
| |
| \since QtQuick.Layouts 1.2 |
| */ |
| void QQuickLayoutAttached::setRightMargin(qreal m) |
| { |
| const bool changed = rightMargin() != m; |
| m_margins.setRight(m); |
| m_isRightMarginSet = true; |
| if (changed) { |
| invalidateItem(); |
| emit rightMarginChanged(); |
| } |
| } |
| |
| void QQuickLayoutAttached::resetRightMargin() |
| { |
| const bool changed = m_isRightMarginSet && (m_defaultMargins != m_margins.right()); |
| m_isRightMarginSet = false; |
| if (changed) { |
| invalidateItem(); |
| emit rightMarginChanged(); |
| } |
| } |
| |
| /*! |
| \qmlattachedproperty real Layout::bottomMargin |
| |
| Specifies the bottom margin outside of an item. |
| If the value is not set, it will use the value from \l margins. |
| |
| \sa margins |
| |
| \since QtQuick.Layouts 1.2 |
| */ |
| void QQuickLayoutAttached::setBottomMargin(qreal m) |
| { |
| const bool changed = bottomMargin() != m; |
| m_margins.setBottom(m); |
| m_isBottomMarginSet = true; |
| if (changed) { |
| invalidateItem(); |
| emit bottomMarginChanged(); |
| } |
| } |
| |
| void QQuickLayoutAttached::resetBottomMargin() |
| { |
| const bool changed = m_isBottomMarginSet && (m_defaultMargins != m_margins.bottom()); |
| m_isBottomMarginSet = false; |
| if (changed) { |
| invalidateItem(); |
| emit bottomMarginChanged(); |
| } |
| } |
| |
| |
| /*! |
| \qmlattachedproperty int Layout::rowSpan |
| |
| This property allows you to specify the row span of an item in a \l GridLayout. |
| |
| The default value is \c 1. |
| |
| \sa columnSpan |
| \sa row |
| */ |
| void QQuickLayoutAttached::setRowSpan(int span) |
| { |
| if (span != m_rowSpan) { |
| m_rowSpan = span; |
| invalidateItem(); |
| emit rowSpanChanged(); |
| } |
| } |
| |
| |
| /*! |
| \qmlattachedproperty int Layout::columnSpan |
| |
| This property allows you to specify the column span of an item in a \l GridLayout. |
| |
| The default value is \c 1. |
| |
| \sa rowSpan |
| \sa column |
| */ |
| void QQuickLayoutAttached::setColumnSpan(int span) |
| { |
| if (span != m_columnSpan) { |
| m_columnSpan = span; |
| invalidateItem(); |
| emit columnSpanChanged(); |
| } |
| } |
| |
| |
| qreal QQuickLayoutAttached::sizeHint(Qt::SizeHint which, Qt::Orientation orientation) const |
| { |
| qreal result = 0; |
| if (QQuickLayout *layout = qobject_cast<QQuickLayout *>(item())) { |
| const QSizeF sz = layout->sizeHint(which); |
| result = (orientation == Qt::Horizontal ? sz.width() : sz.height()); |
| } else { |
| if (which == Qt::MaximumSize) |
| result = std::numeric_limits<qreal>::infinity(); |
| } |
| return result; |
| } |
| |
| void QQuickLayoutAttached::invalidateItem() |
| { |
| qCDebug(lcQuickLayouts) << "QQuickLayoutAttached::invalidateItem"; |
| if (QQuickLayout *layout = parentLayout()) { |
| layout->invalidate(item()); |
| } |
| } |
| |
| QQuickLayout *QQuickLayoutAttached::parentLayout() const |
| { |
| QQuickItem *parentItem = item(); |
| if (parentItem) { |
| parentItem = parentItem->parentItem(); |
| return qobject_cast<QQuickLayout *>(parentItem); |
| } else { |
| qmlWarning(parent()) << "Layout must be attached to Item elements"; |
| } |
| return nullptr; |
| } |
| |
| QQuickItem *QQuickLayoutAttached::item() const |
| { |
| return qobject_cast<QQuickItem *>(parent()); |
| } |
| |
| qreal QQuickLayoutPrivate::getImplicitWidth() const |
| { |
| Q_Q(const QQuickLayout); |
| if (q->invalidated()) { |
| QQuickLayoutPrivate *that = const_cast<QQuickLayoutPrivate*>(this); |
| that->implicitWidth = q->sizeHint(Qt::PreferredSize).width(); |
| } |
| return implicitWidth; |
| } |
| |
| qreal QQuickLayoutPrivate::getImplicitHeight() const |
| { |
| Q_Q(const QQuickLayout); |
| if (q->invalidated()) { |
| QQuickLayoutPrivate *that = const_cast<QQuickLayoutPrivate*>(this); |
| that->implicitHeight = q->sizeHint(Qt::PreferredSize).height(); |
| } |
| return implicitHeight; |
| } |
| |
| void QQuickLayoutPrivate::applySizeHints() const { |
| Q_Q(const QQuickLayout); |
| QQuickLayout *that = const_cast<QQuickLayout*>(q); |
| QQuickLayoutAttached *info = attachedLayoutObject(that, true); |
| |
| const QSizeF min = q->sizeHint(Qt::MinimumSize); |
| const QSizeF max = q->sizeHint(Qt::MaximumSize); |
| const QSizeF pref = q->sizeHint(Qt::PreferredSize); |
| info->setMinimumImplicitSize(min); |
| info->setMaximumImplicitSize(max); |
| that->setImplicitSize(pref.width(), pref.height()); |
| } |
| |
| QQuickLayout::QQuickLayout(QQuickLayoutPrivate &dd, QQuickItem *parent) |
| : QQuickItem(dd, parent) |
| , m_inUpdatePolish(false) |
| , m_polishInsideUpdatePolish(0) |
| { |
| } |
| |
| static QQuickItemPrivate::ChangeTypes changeTypes = |
| QQuickItemPrivate::SiblingOrder |
| | QQuickItemPrivate::ImplicitWidth |
| | QQuickItemPrivate::ImplicitHeight |
| | QQuickItemPrivate::Destroyed |
| | QQuickItemPrivate::Visibility; |
| |
| QQuickLayout::~QQuickLayout() |
| { |
| d_func()->m_isReady = false; |
| |
| const auto childItems = d_func()->childItems; |
| for (QQuickItem *child : childItems) |
| QQuickItemPrivate::get(child)->removeItemChangeListener(this, changeTypes); |
| } |
| |
| QQuickLayoutAttached *QQuickLayout::qmlAttachedProperties(QObject *object) |
| { |
| return new QQuickLayoutAttached(object); |
| } |
| |
| void QQuickLayout::updatePolish() |
| { |
| qCDebug(lcQuickLayouts) << "updatePolish() ENTERING" << this; |
| m_inUpdatePolish = true; |
| |
| // Might have become "undirty" before we reach this updatePolish() |
| // (e.g. if somebody queried for implicitWidth it will immediately |
| // calculate size hints) |
| if (invalidated()) { |
| // Ensure that all invalidated layouts are synced and valid again. Since |
| // ensureLayoutItemsUpdated() will also call applySizeHints(), and sizeHint() will call its |
| // childrens sizeHint(), and sizeHint() will call ensureLayoutItemsUpdated(), this will be done |
| // recursive as we want. |
| // Note that we need to call ensureLayoutItemsUpdated() *before* we query width() and height(), |
| // because width()/height() might return their implicitWidth/implicitHeight (e.g. for a layout |
| // with no explicitly specified size, (nor anchors.fill: parent)) |
| ensureLayoutItemsUpdated(); |
| } |
| rearrange(QSizeF(width(), height())); |
| m_inUpdatePolish = false; |
| qCDebug(lcQuickLayouts) << "updatePolish() LEAVING" << this; |
| } |
| |
| void QQuickLayout::componentComplete() |
| { |
| Q_D(QQuickLayout); |
| d->m_disableRearrange = true; |
| QQuickItem::componentComplete(); // will call our geometryChanged(), (where isComponentComplete() == true) |
| d->m_disableRearrange = false; |
| d->m_isReady = true; |
| } |
| |
| void QQuickLayout::invalidate(QQuickItem * /*childItem*/) |
| { |
| Q_D(QQuickLayout); |
| if (invalidated()) |
| return; |
| |
| qCDebug(lcQuickLayouts) << "QQuickLayout::invalidate()" << this; |
| d->m_dirty = true; |
| d->m_dirtyArrangement = true; |
| |
| if (!qobject_cast<QQuickLayout *>(parentItem())) { |
| |
| if (m_inUpdatePolish) |
| ++m_polishInsideUpdatePolish; |
| else |
| m_polishInsideUpdatePolish = 0; |
| |
| if (m_polishInsideUpdatePolish <= 2) { |
| // allow at most two consecutive loops in order to respond to height-for-width |
| // (e.g QQuickText changes implicitHeight when its width gets changed) |
| qCDebug(lcQuickLayouts) << "QQuickLayout::invalidate(), polish()"; |
| polish(); |
| } else { |
| qWarning() << "Qt Quick Layouts: Polish loop detected. Aborting after two iterations."; |
| } |
| } |
| } |
| |
| bool QQuickLayout::shouldIgnoreItem(QQuickItem *child, QQuickLayoutAttached *&info, QSizeF *sizeHints) const |
| { |
| bool ignoreItem = true; |
| QQuickItemPrivate *childPrivate = QQuickItemPrivate::get(child); |
| if (childPrivate->explicitVisible) { |
| effectiveSizeHints_helper(child, sizeHints, &info, true); |
| QSizeF effectiveMaxSize = sizeHints[Qt::MaximumSize]; |
| if (!effectiveMaxSize.isNull()) { |
| QSizeF &prefS = sizeHints[Qt::PreferredSize]; |
| if (effectiveSizePolicy_helper(child, Qt::Horizontal, info) == QLayoutPolicy::Fixed) |
| effectiveMaxSize.setWidth(prefS.width()); |
| if (effectiveSizePolicy_helper(child, Qt::Vertical, info) == QLayoutPolicy::Fixed) |
| effectiveMaxSize.setHeight(prefS.height()); |
| } |
| ignoreItem = effectiveMaxSize.isNull(); |
| } |
| |
| if (!ignoreItem && childPrivate->isTransparentForPositioner()) |
| ignoreItem = true; |
| |
| return ignoreItem; |
| } |
| |
| void QQuickLayout::checkAnchors(QQuickItem *item) const |
| { |
| QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors; |
| if (anchors && anchors->activeDirections()) |
| qmlWarning(item) << "Detected anchors on an item that is managed by a layout. This is undefined behavior; use Layout.alignment instead."; |
| } |
| |
| void QQuickLayout::ensureLayoutItemsUpdated() const |
| { |
| Q_D(const QQuickLayout); |
| if (!invalidated()) |
| return; |
| const_cast<QQuickLayout*>(this)->updateLayoutItems(); |
| d->m_dirty = false; |
| d->applySizeHints(); |
| } |
| |
| |
| void QQuickLayout::itemChange(ItemChange change, const ItemChangeData &value) |
| { |
| if (change == ItemChildAddedChange) { |
| Q_D(QQuickLayout); |
| QQuickItem *item = value.item; |
| qmlobject_connect(item, QQuickItem, SIGNAL(baselineOffsetChanged(qreal)), this, QQuickLayout, SLOT(invalidateSenderItem())); |
| QQuickItemPrivate::get(item)->addItemChangeListener(this, changeTypes); |
| d->m_hasItemChangeListeners = true; |
| qCDebug(lcQuickLayouts) << "ChildAdded" << item; |
| if (isReady()) |
| invalidate(); |
| } else if (change == ItemChildRemovedChange) { |
| QQuickItem *item = value.item; |
| qmlobject_disconnect(item, QQuickItem, SIGNAL(baselineOffsetChanged(qreal)), this, QQuickLayout, SLOT(invalidateSenderItem())); |
| QQuickItemPrivate::get(item)->removeItemChangeListener(this, changeTypes); |
| qCDebug(lcQuickLayouts) << "ChildRemoved" << item; |
| if (isReady()) |
| invalidate(); |
| } |
| QQuickItem::itemChange(change, value); |
| } |
| |
| void QQuickLayout::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) |
| { |
| Q_D(QQuickLayout); |
| QQuickItem::geometryChanged(newGeometry, oldGeometry); |
| if (d->m_disableRearrange || !isReady() || !newGeometry.isValid()) |
| return; |
| |
| qCDebug(lcQuickLayouts) << "QQuickLayout::geometryChanged" << newGeometry << oldGeometry; |
| rearrange(newGeometry.size()); |
| } |
| |
| void QQuickLayout::invalidateSenderItem() |
| { |
| if (!isReady()) |
| return; |
| QQuickItem *item = static_cast<QQuickItem *>(sender()); |
| Q_ASSERT(item); |
| invalidate(item); |
| } |
| |
| bool QQuickLayout::isReady() const |
| { |
| return d_func()->m_isReady; |
| } |
| |
| /*! |
| * \brief QQuickLayout::deactivateRecur |
| * \internal |
| * |
| * Call this from the dtor of the top-level layout. |
| * Otherwise, it will trigger lots of unneeded item change listeners (itemVisibleChanged()) for all its descendants |
| * that will have its impact thrown away. |
| */ |
| void QQuickLayout::deactivateRecur() |
| { |
| if (d_func()->m_hasItemChangeListeners) { |
| for (int i = 0; i < itemCount(); ++i) { |
| QQuickItem *item = itemAt(i); |
| // When deleting a layout with children, there is no reason for the children to inform the layout that their |
| // e.g. visibility got changed. The layout already knows that all its children will eventually become invisible, so |
| // we therefore remove its change listener. |
| QQuickItemPrivate::get(item)->removeItemChangeListener(this, changeTypes); |
| if (QQuickLayout *layout = qobject_cast<QQuickLayout*>(item)) |
| layout->deactivateRecur(); |
| } |
| d_func()->m_hasItemChangeListeners = false; |
| } |
| } |
| |
| bool QQuickLayout::invalidated() const |
| { |
| return d_func()->m_dirty; |
| } |
| |
| bool QQuickLayout::invalidatedArrangement() const |
| { |
| return d_func()->m_dirtyArrangement; |
| } |
| |
| void QQuickLayout::itemSiblingOrderChanged(QQuickItem *item) |
| { |
| Q_UNUSED(item); |
| invalidate(); |
| } |
| |
| void QQuickLayout::itemImplicitWidthChanged(QQuickItem *item) |
| { |
| if (!isReady() || item->signalsBlocked()) |
| return; |
| invalidate(item); |
| } |
| |
| void QQuickLayout::itemImplicitHeightChanged(QQuickItem *item) |
| { |
| if (!isReady() || item->signalsBlocked()) |
| return; |
| invalidate(item); |
| } |
| |
| void QQuickLayout::itemDestroyed(QQuickItem *item) |
| { |
| Q_UNUSED(item); |
| } |
| |
| void QQuickLayout::itemVisibilityChanged(QQuickItem *item) |
| { |
| Q_UNUSED(item); |
| } |
| |
| void QQuickLayout::rearrange(const QSizeF &/*size*/) |
| { |
| d_func()->m_dirtyArrangement = false; |
| } |
| |
| |
| /* |
| The layout engine assumes: |
| 1. minimum <= preferred <= maximum |
| 2. descent is within minimum and maximum bounds (### verify) |
| |
| This function helps to ensure that by the following rules (in the following order): |
| 1. If minimum > maximum, set minimum = maximum |
| 2. Clamp preferred to be between the [minimum,maximum] range. |
| 3. If descent > minimum, set descent = minimum (### verify if this is correct, it might |
| need some refinements to multiline texts) |
| |
| If any values are "not set" (i.e. negative), they will be left untouched, so that we |
| know which values needs to be fetched from the implicit hints (not user hints). |
| */ |
| static void normalizeHints(qreal &minimum, qreal &preferred, qreal &maximum, qreal &descent) |
| { |
| if (minimum >= 0 && maximum >= 0 && minimum > maximum) |
| minimum = maximum; |
| |
| if (preferred >= 0) { |
| if (minimum >= 0 && preferred < minimum) { |
| preferred = minimum; |
| } else if (maximum >= 0 && preferred > maximum) { |
| preferred = maximum; |
| } |
| } |
| |
| if (minimum >= 0 && descent > minimum) |
| descent = minimum; |
| } |
| |
| static void boundSize(QSizeF &result, const QSizeF &size) |
| { |
| if (size.width() >= 0 && size.width() < result.width()) |
| result.setWidth(size.width()); |
| if (size.height() >= 0 && size.height() < result.height()) |
| result.setHeight(size.height()); |
| } |
| |
| static void expandSize(QSizeF &result, const QSizeF &size) |
| { |
| if (size.width() >= 0 && size.width() > result.width()) |
| result.setWidth(size.width()); |
| if (size.height() >= 0 && size.height() > result.height()) |
| result.setHeight(size.height()); |
| } |
| |
| static inline void combineHints(qreal ¤t, qreal fallbackHint) |
| { |
| if (current < 0) |
| current = fallbackHint; |
| } |
| |
| static inline void combineSize(QSizeF &result, const QSizeF &fallbackSize) |
| { |
| combineHints(result.rwidth(), fallbackSize.width()); |
| combineHints(result.rheight(), fallbackSize.height()); |
| } |
| |
| static inline void combineImplicitHints(QQuickLayoutAttached *info, Qt::SizeHint which, QSizeF *size) |
| { |
| if (!info) return; |
| |
| Q_ASSERT(which == Qt::MinimumSize || which == Qt::MaximumSize); |
| |
| const QSizeF constraint(which == Qt::MinimumSize |
| ? QSizeF(info->minimumWidth(), info->minimumHeight()) |
| : QSizeF(info->maximumWidth(), info->maximumHeight())); |
| |
| if (!info->isExtentExplicitlySet(Qt::Horizontal, which)) |
| combineHints(size->rwidth(), constraint.width()); |
| if (!info->isExtentExplicitlySet(Qt::Vertical, which)) |
| combineHints(size->rheight(), constraint.height()); |
| } |
| |
| typedef qreal (QQuickLayoutAttached::*SizeGetter)() const; |
| |
| /*! |
| \internal |
| Note: Can potentially return the attached QQuickLayoutAttached object through \a attachedInfo. |
| |
| It is like this is because it enables it to be reused. |
| |
| The goal of this function is to return the effective minimum, preferred and maximum size hints |
| that the layout will use for this item. |
| This function takes care of gathering all explicitly set size hints, normalizes them so |
| that min < pref < max. |
| Further, the hints _not_explicitly_ set will then be initialized with the implicit size hints, |
| which is usually derived from the content of the layouts (or items). |
| |
| The following table illustrates the preference of the properties used for measuring layout |
| items. If present, the USER properties will be preferred. If USER properties are not present, |
| the HINT properties will be preferred. Finally, the FALLBACK properties will be used as an |
| ultimate fallback. |
| |
| Note that one can query if the value of Layout.minimumWidth or Layout.maximumWidth has been |
| explicitly or implicitly set with QQuickLayoutAttached::isExtentExplicitlySet(). This |
| determines if it should be used as a USER or as a HINT value. |
| |
| Fractional size hints will be ceiled to the closest integer. This is in order to give some |
| slack when the items are snapped to the pixel grid. |
| |
| | *Minimum* | *Preferred* | *Maximum* | |
| +----------------+----------------------+-----------------------+--------------------------+ |
| |USER (explicit) | Layout.minimumWidth | Layout.preferredWidth | Layout.maximumWidth | |
| |HINT (implicit) | Layout.minimumWidth | implicitWidth | Layout.maximumWidth | |
| |FALLBACK | 0 | width | Number.POSITIVE_INFINITY | |
| +----------------+----------------------+-----------------------+--------------------------+ |
| */ |
| void QQuickLayout::effectiveSizeHints_helper(QQuickItem *item, QSizeF *cachedSizeHints, QQuickLayoutAttached **attachedInfo, bool useFallbackToWidthOrHeight) |
| { |
| for (int i = 0; i < Qt::NSizeHints; ++i) |
| cachedSizeHints[i] = QSizeF(); |
| QQuickLayoutAttached *info = attachedLayoutObject(item, false); |
| // First, retrieve the user-specified hints from the attached "Layout." properties |
| if (info) { |
| struct Getters { |
| SizeGetter call[NSizes]; |
| }; |
| |
| static Getters horGetters = { |
| {&QQuickLayoutAttached::minimumWidth, &QQuickLayoutAttached::preferredWidth, &QQuickLayoutAttached::maximumWidth}, |
| }; |
| |
| static Getters verGetters = { |
| {&QQuickLayoutAttached::minimumHeight, &QQuickLayoutAttached::preferredHeight, &QQuickLayoutAttached::maximumHeight} |
| }; |
| for (int i = 0; i < NSizes; ++i) { |
| SizeGetter getter = horGetters.call[i]; |
| Q_ASSERT(getter); |
| |
| if (info->isExtentExplicitlySet(Qt::Horizontal, (Qt::SizeHint)i)) |
| cachedSizeHints[i].setWidth((info->*getter)()); |
| |
| getter = verGetters.call[i]; |
| Q_ASSERT(getter); |
| if (info->isExtentExplicitlySet(Qt::Vertical, (Qt::SizeHint)i)) |
| cachedSizeHints[i].setHeight((info->*getter)()); |
| } |
| } |
| |
| QSizeF &minS = cachedSizeHints[Qt::MinimumSize]; |
| QSizeF &prefS = cachedSizeHints[Qt::PreferredSize]; |
| QSizeF &maxS = cachedSizeHints[Qt::MaximumSize]; |
| QSizeF &descentS = cachedSizeHints[Qt::MinimumDescent]; |
| |
| // For instance, will normalize the following user-set hints |
| // from: [10, 5, 60] |
| // to: [10, 10, 60] |
| normalizeHints(minS.rwidth(), prefS.rwidth(), maxS.rwidth(), descentS.rwidth()); |
| normalizeHints(minS.rheight(), prefS.rheight(), maxS.rheight(), descentS.rheight()); |
| |
| // All explicit values gathered, now continue to gather the implicit sizes |
| |
| //--- GATHER MAXIMUM SIZE HINTS --- |
| combineImplicitHints(info, Qt::MaximumSize, &maxS); |
| combineSize(maxS, QSizeF(std::numeric_limits<qreal>::infinity(), std::numeric_limits<qreal>::infinity())); |
| // implicit max or min sizes should not limit an explicitly set preferred size |
| expandSize(maxS, prefS); |
| expandSize(maxS, minS); |
| |
| //--- GATHER MINIMUM SIZE HINTS --- |
| combineImplicitHints(info, Qt::MinimumSize, &minS); |
| expandSize(minS, QSizeF(0,0)); |
| boundSize(minS, prefS); |
| boundSize(minS, maxS); |
| |
| //--- GATHER PREFERRED SIZE HINTS --- |
| // First, from implicitWidth/Height |
| qreal &prefWidth = prefS.rwidth(); |
| qreal &prefHeight = prefS.rheight(); |
| if (prefWidth < 0 && item->implicitWidth() > 0) |
| prefWidth = qCeil(item->implicitWidth()); |
| if (prefHeight < 0 && item->implicitHeight() > 0) |
| prefHeight = qCeil(item->implicitHeight()); |
| |
| // If that fails, make an ultimate fallback to width/height |
| if (useFallbackToWidthOrHeight && !prefS.isValid()) { |
| /* If we want to support using width/height as preferred size hints in |
| layouts, (which we think most people expect), we only want to use the |
| initial width. |
| This is because the width will change due to layout rearrangement, |
| and the preferred width should return the same value, regardless of |
| the current width. |
| We therefore store this initial width in the attached layout object |
| and reuse it if needed rather than querying the width another time. |
| That means we need to ensure that an Layout attached object is available |
| by creating one if necessary. |
| */ |
| if (!info) |
| info = attachedLayoutObject(item); |
| |
| auto updatePreferredSizes = [](qreal &cachedSize, qreal &attachedSize, qreal size) { |
| if (cachedSize < 0) { |
| if (attachedSize < 0) |
| attachedSize = size; |
| |
| cachedSize = attachedSize; |
| } |
| }; |
| updatePreferredSizes(prefWidth, info->m_fallbackWidth, item->width()); |
| updatePreferredSizes(prefHeight, info->m_fallbackHeight, item->height()); |
| } |
| |
| // Normalize again after the implicit hints have been gathered |
| expandSize(prefS, minS); |
| boundSize(prefS, maxS); |
| |
| //--- GATHER DESCENT |
| // Minimum descent is only applicable for the effective minimum height, |
| // so we gather the descent last. |
| const qreal minimumDescent = minS.height() - item->baselineOffset(); |
| descentS.setHeight(minimumDescent); |
| |
| if (info) { |
| QMarginsF margins = info->qMargins(); |
| QSizeF extraMargins(margins.left() + margins.right(), margins.top() + margins.bottom()); |
| minS += extraMargins; |
| prefS += extraMargins; |
| maxS += extraMargins; |
| descentS += extraMargins; |
| } |
| if (attachedInfo) |
| *attachedInfo = info; |
| } |
| |
| /*! |
| \internal |
| |
| Assumes \a info is set (if the object has an attached property) |
| */ |
| QLayoutPolicy::Policy QQuickLayout::effectiveSizePolicy_helper(QQuickItem *item, Qt::Orientation orientation, QQuickLayoutAttached *info) |
| { |
| bool fillExtent = false; |
| bool isSet = false; |
| if (info) { |
| if (orientation == Qt::Horizontal) { |
| isSet = info->isFillWidthSet(); |
| if (isSet) fillExtent = info->fillWidth(); |
| } else { |
| isSet = info->isFillHeightSet(); |
| if (isSet) fillExtent = info->fillHeight(); |
| } |
| } |
| if (!isSet && qobject_cast<QQuickLayout*>(item)) |
| fillExtent = true; |
| return fillExtent ? QLayoutPolicy::Preferred : QLayoutPolicy::Fixed; |
| |
| } |
| |
| void QQuickLayout::_q_dumpLayoutTree() const |
| { |
| QString buf; |
| dumpLayoutTreeRecursive(0, buf); |
| qDebug("\n%s", qPrintable(buf)); |
| } |
| |
| void QQuickLayout::dumpLayoutTreeRecursive(int level, QString &buf) const |
| { |
| auto formatLine = [&level](const char *fmt) { |
| QString ss(level *4, QLatin1Char(' ')); |
| return QString::fromLatin1("%1%2\n").arg(ss).arg(fmt); |
| }; |
| |
| auto f2s = [](qreal f) { |
| return QString::number(f); |
| }; |
| auto b2s = [](bool b) { |
| static const char *strBool[] = {"false", "true"}; |
| return QLatin1String(strBool[int(b)]); |
| }; |
| |
| buf += formatLine("%1 {").arg(QQmlMetaType::prettyTypeName(this)); |
| ++level; |
| buf += formatLine("// Effective calculated values:"); |
| buf += formatLine("sizeHintDirty: %2").arg(invalidated()); |
| QSizeF min = sizeHint(Qt::MinimumSize); |
| buf += formatLine("sizeHint.min : [%1, %2]").arg(f2s(min.width()), 5).arg(min.height(), 5); |
| QSizeF pref = sizeHint(Qt::PreferredSize); |
| buf += formatLine("sizeHint.pref: [%1, %2]").arg(pref.width(), 5).arg(pref.height(), 5); |
| QSizeF max = sizeHint(Qt::MaximumSize); |
| buf += formatLine("sizeHint.max : [%1, %2]").arg(f2s(max.width()), 5).arg(f2s(max.height()), 5); |
| |
| for (QQuickItem *item : childItems()) { |
| buf += QLatin1Char('\n'); |
| if (QQuickLayout *childLayout = qobject_cast<QQuickLayout*>(item)) { |
| childLayout->dumpLayoutTreeRecursive(level, buf); |
| } else { |
| buf += formatLine("%1 {").arg(QQmlMetaType::prettyTypeName(item)); |
| ++level; |
| if (item->implicitWidth() > 0) |
| buf += formatLine("implicitWidth: %1").arg(f2s(item->implicitWidth())); |
| if (item->implicitHeight() > 0) |
| buf += formatLine("implicitHeight: %1").arg(f2s(item->implicitHeight())); |
| QSizeF min; |
| QSizeF pref; |
| QSizeF max; |
| QQuickLayoutAttached *info = attachedLayoutObject(item, false); |
| if (info) { |
| min = QSizeF(info->minimumWidth(), info->minimumHeight()); |
| pref = QSizeF(info->preferredWidth(), info->preferredHeight()); |
| max = QSizeF(info->maximumWidth(), info->maximumHeight()); |
| if (info->isExtentExplicitlySet(Qt::Horizontal, Qt::MinimumSize)) |
| buf += formatLine("Layout.minimumWidth: %1").arg(f2s(min.width())); |
| if (info->isExtentExplicitlySet(Qt::Vertical, Qt::MinimumSize)) |
| buf += formatLine("Layout.minimumHeight: %1").arg(f2s(min.height())); |
| if (pref.width() >= 0) |
| buf += formatLine("Layout.preferredWidth: %1").arg(f2s(pref.width())); |
| if (pref.height() >= 0) |
| buf += formatLine("Layout.preferredHeight: %1").arg(f2s(pref.height())); |
| if (info->isExtentExplicitlySet(Qt::Horizontal, Qt::MaximumSize)) |
| buf += formatLine("Layout.maximumWidth: %1").arg(f2s(max.width())); |
| if (info->isExtentExplicitlySet(Qt::Vertical, Qt::MaximumSize)) |
| buf += formatLine("Layout.maximumHeight: %1").arg(f2s(max.height())); |
| |
| if (info->isFillWidthSet()) |
| buf += formatLine("Layout.fillWidth: %1").arg(b2s(info->fillWidth())); |
| if (info->isFillHeightSet()) |
| buf += formatLine("Layout.fillHeight: %1").arg(b2s(info->fillHeight())); |
| } |
| --level; |
| buf += formatLine("}"); |
| } |
| } |
| --level; |
| buf += formatLine("}"); |
| } |
| |
| QT_END_NAMESPACE |