| /**************************************************************************** |
| ** |
| ** Copyright (C) 2018 The Qt Company Ltd. |
| ** Contact: http://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL3$ |
| ** 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 http://www.qt.io/terms-conditions. For further |
| ** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free |
| ** Software Foundation and appearing in the file LICENSE.GPL included in |
| ** the packaging of this file. Please review the following information to |
| ** ensure the GNU General Public License version 2.0 requirements will be |
| ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquicksplitview_p.h" |
| #include "qquicksplitview_p_p.h" |
| #include "qquickcontentitem_p.h" |
| |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qloggingcategory.h> |
| #include <QtCore/qcborarray.h> |
| #include <QtCore/qcbormap.h> |
| #include <QtCore/qcborvalue.h> |
| #include <QtQml/QQmlInfo> |
| |
| QT_BEGIN_NAMESPACE |
| |
| /*! |
| \qmltype SplitView |
| \inherits Control |
| //! \instantiates QQuickSplitView |
| \inqmlmodule QtQuick.Controls |
| \since 5.13 |
| \ingroup qtquickcontrols2-containers |
| \ingroup qtquickcontrols2-focusscopes |
| \brief Lays out items with a draggable splitter between each item. |
| |
| SplitView is a control that lays out items horizontally or vertically with |
| a draggable splitter between each item. |
| |
| SplitView supports the following attached properties on items it manages: |
| |
| \list |
| \li \l{minimumWidth}{SplitView.minimumWidth} |
| \li \l{minimumHeight}{SplitView.minimumHeight} |
| \li \l{preferredWidth}{SplitView.preferredWidth} |
| \li \l{preferredHeight}{SplitView.preferredHeight} |
| \li \l{maximumWidth}{SplitView.maximumWidth} |
| \li \l{maximumHeight}{SplitView.maximumHeight} |
| \li \l{fillWidth}{SplitView.fillWidth} (true for only one child) |
| \li \l{fillHeight}{SplitView.fillHeight} (true for only one child) |
| \endlist |
| |
| In addition, each handle has the following read-only attached properties: |
| |
| \list |
| \li \l{SplitHandle::hovered}{SplitHandle.hovered} |
| \li \l{SplitHandle::pressed}{SplitHandle.pressed} |
| \endlist |
| |
| \note Handles should be purely visual and not handle events, as it can |
| interfere with their hovered and pressed states. |
| |
| The preferred size of items in a SplitView can be specified via |
| \l{Item::}{implicitWidth} and \l{Item::}{implicitHeight} or |
| \c SplitView.preferredWidth and \c SplitView.preferredHeight: |
| |
| \code |
| SplitView { |
| anchors.fill: parent |
| |
| Item { |
| SplitView.preferredWidth: 50 |
| } |
| |
| // ... |
| } |
| \endcode |
| |
| For a horizontal SplitView, it's not necessary to specify the preferred |
| height of each item, as they will be resized to the height of the view. |
| This applies in reverse for vertical views. |
| |
| When a split handle is dragged, the \c SplitView.preferredWidth or |
| \c SplitView.preferredHeight property is overwritten, depending on the |
| \l orientation of the view. |
| |
| To limit the size of items in a horizontal view, use the following |
| properties: |
| |
| \code |
| SplitView { |
| anchors.fill: parent |
| |
| Item { |
| SplitView.minimumWidth: 25 |
| SplitView.preferredWidth: 50 |
| SplitView.maximumWidth: 100 |
| } |
| |
| // ... |
| } |
| \endcode |
| |
| To limit the size of items in a vertical view, use the following |
| properties: |
| |
| \code |
| SplitView { |
| anchors.fill: parent |
| orientation: Qt.Vertical |
| |
| Item { |
| SplitView.minimumHeight: 25 |
| SplitView.preferredHeight: 50 |
| SplitView.maximumHeight: 100 |
| } |
| |
| // ... |
| } |
| \endcode |
| |
| There will always be one item (the fill item) in the SplitView that has |
| \c SplitView.fillWidth set to \c true (or \c SplitView.fillHeight, if |
| \l orientation is \c Qt.Vertical). This means that the item will get all |
| leftover space when other items have been laid out. By default, the last |
| visible child of the SplitView will have this set, but it can be changed by |
| explicitly setting \c fillWidth to \c true on another item. |
| |
| A handle can belong to the item either on the left or top side, or on the |
| right or bottom side: |
| |
| \list |
| \li If the fill item is to the right: the handle belongs to the left |
| item. |
| \li If the fill item is on the left: the handle belongs to the right |
| item. |
| \endlist |
| |
| To create a SplitView with three items, and let the center item get |
| superfluous space, one could do the following: |
| |
| \code |
| SplitView { |
| anchors.fill: parent |
| orientation: Qt.Horizontal |
| |
| Rectangle { |
| implicitWidth: 200 |
| SplitView.maximumWidth: 400 |
| color: "lightblue" |
| Label { |
| text: "View 1" |
| anchors.centerIn: parent |
| } |
| } |
| Rectangle { |
| id: centerItem |
| SplitView.minimumWidth: 50 |
| SplitView.fillWidth: true |
| color: "lightgray" |
| Label { |
| text: "View 2" |
| anchors.centerIn: parent |
| } |
| } |
| Rectangle { |
| implicitWidth: 200 |
| color: "lightgreen" |
| Label { |
| text: "View 3" |
| anchors.centerIn: parent |
| } |
| } |
| } |
| \endcode |
| |
| \section1 Serializing SplitView's State |
| |
| The main purpose of SplitView is to allow users to easily configure the |
| size of various UI elements. In addition, the user's preferred sizes should |
| be remembered across sessions. To achieve this, the values of the \c |
| SplitView.preferredWidth and \c SplitView.preferredHeight properties can be |
| serialized using the \l saveState() and \l restoreState() functions: |
| |
| \qml \QtMinorVersion |
| import QtQuick.Controls 2.\1 |
| import Qt.labs.settings 1.0 |
| |
| ApplicationWindow { |
| // ... |
| |
| Component.onCompleted: splitView.restoreState(settings.splitView) |
| Component.onDestruction: settings.splitView = splitView.saveState() |
| |
| Settings { |
| id: settings |
| property var splitView |
| } |
| |
| SplitView { |
| id: splitView |
| // ... |
| } |
| } |
| \endqml |
| |
| Alternatively, the \l {Settings::}{value()} and \l {Settings::}{setValue()} |
| functions of \l Settings can be used: |
| |
| \qml \QtMinorVersion |
| import QtQuick.Controls 2.\1 |
| import Qt.labs.settings 1.0 |
| |
| ApplicationWindow { |
| // ... |
| |
| Component.onCompleted: splitView.restoreState(settings.value("ui/splitview")) |
| Component.onDestruction: settings.setValue("ui/splitview", splitView.saveState()) |
| |
| Settings { |
| id: settings |
| } |
| |
| SplitView { |
| id: splitView |
| // ... |
| } |
| } |
| \endqml |
| |
| \sa SplitHandle, {Customizing SplitView}, {Container Controls} |
| */ |
| |
| Q_LOGGING_CATEGORY(qlcQQuickSplitView, "qt.quick.controls.splitview") |
| Q_LOGGING_CATEGORY(qlcQQuickSplitViewMouse, "qt.quick.controls.splitview.mouse") |
| Q_LOGGING_CATEGORY(qlcQQuickSplitViewState, "qt.quick.controls.splitview.state") |
| |
| void QQuickSplitViewPrivate::updateFillIndex() |
| { |
| const int count = contentModel->count(); |
| const bool horizontal = isHorizontal(); |
| |
| qCDebug(qlcQQuickSplitView) << "looking for fillWidth/Height item amongst" << count << "items"; |
| |
| m_fillIndex = -1; |
| int i = 0; |
| int lastVisibleIndex = -1; |
| for (; i < count; ++i) { |
| QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); |
| if (!item->isVisible()) |
| continue; |
| |
| lastVisibleIndex = i; |
| |
| const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); |
| if (!attached) |
| continue; |
| |
| if ((horizontal && attached->fillWidth()) || (!horizontal && attached->fillHeight())) { |
| m_fillIndex = i; |
| qCDebug(qlcQQuickSplitView) << "found fillWidth/Height item at index" << m_fillIndex; |
| break; |
| } |
| } |
| |
| if (m_fillIndex == -1) { |
| // If there was no item with fillWidth/fillHeight set, m_fillIndex will be -1, |
| // and we'll set it to the last visible item. |
| // If there was an item with fillWidth/fillHeight set, we were already done and this will be skipped. |
| m_fillIndex = lastVisibleIndex != -1 ? lastVisibleIndex : count - 1; |
| qCDebug(qlcQQuickSplitView) << "found no fillWidth/Height item; using last item at index" << m_fillIndex; |
| } |
| } |
| |
| /* |
| Resizes split items according to their preferred size and any constraints. |
| |
| If a split item is being resized due to a split handle being dragged, |
| it will be resized accordingly. |
| |
| Items that aren't visible are skipped. |
| */ |
| void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &usedHeight, int &indexBeingResizedDueToDrag) |
| { |
| const int count = contentModel->count(); |
| const bool horizontal = isHorizontal(); |
| for (int index = 0; index < count; ++index) { |
| QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(index)); |
| if (!item->isVisible()) { |
| // The item is not visible, so skip it. |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": split item " << item |
| << " at index " << index << " is not visible; skipping it and its handles (if any)"; |
| continue; |
| } |
| |
| const QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
| QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); |
| const auto sizeData = effectiveSizeData(itemPrivate, attached); |
| |
| const bool resizeLeftItem = m_fillIndex > m_pressedHandleIndex; |
| // True if any handle is pressed. |
| const bool isAHandlePressed = m_pressedHandleIndex != -1; |
| // True if this particular item is being resized as a result of a handle being dragged. |
| const bool isBeingResized = isAHandlePressed && ((resizeLeftItem && index == m_pressedHandleIndex) |
| || (!resizeLeftItem && index == m_pressedHandleIndex + 1)); |
| if (isBeingResized) { |
| indexBeingResizedDueToDrag = index; |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": dragging handle for item"; |
| } |
| |
| const qreal size = horizontal ? width : height; |
| qreal requestedSize = 0; |
| if (isBeingResized) { |
| // Don't let the mouse go past either edge of the SplitView. |
| const qreal clampedMousePos = horizontal |
| ? qBound(qreal(0.0), m_mousePos.x(), width) |
| : qBound(qreal(0.0), m_mousePos.y(), height); |
| |
| // We also need to ensure that the item's edge doesn't go too far |
| // out and hence give the item more space than is available. |
| const int firstIndex = resizeLeftItem ? m_pressedHandleIndex + 1 : 0; |
| const int lastIndex = resizeLeftItem ? contentModel->count() - 1 : m_pressedHandleIndex; |
| const qreal accumulated = accumulatedSize(firstIndex, lastIndex); |
| |
| const qreal mousePosRelativeToLeftHandleEdge = horizontal |
| ? m_pressPos.x() - m_handlePosBeforePress.x() |
| : m_pressPos.y() - m_handlePosBeforePress.y(); |
| |
| const QQuickItem *pressedHandleItem = m_handleItems.at(m_pressedHandleIndex); |
| const qreal pressedHandleSize = horizontal ? pressedHandleItem->width() : pressedHandleItem->height(); |
| |
| if (resizeLeftItem) { |
| // The handle shouldn't cross other handles, so use the right edge of |
| // the first handle to the left as the left edge. |
| qreal leftEdge = 0; |
| if (m_pressedHandleIndex - 1 >= 0) { |
| const QQuickItem *leftHandle = m_handleItems.at(m_pressedHandleIndex - 1); |
| leftEdge = horizontal |
| ? leftHandle->x() + leftHandle->width() |
| : leftHandle->y() + leftHandle->height(); |
| } |
| |
| // The mouse can be clicked anywhere in the handle, and if we don't account for |
| // its position within the handle, the handle will jump when dragged. |
| const qreal pressedHandlePos = clampedMousePos - mousePosRelativeToLeftHandleEdge; |
| |
| const qreal rightStop = size - accumulated - pressedHandleSize; |
| qreal leftStop = qMax(leftEdge, pressedHandlePos); |
| // qBound() doesn't care if min is greater than max, but we do. |
| if (leftStop > rightStop) |
| leftStop = rightStop; |
| const qreal newHandlePos = qBound(leftStop, pressedHandlePos, rightStop); |
| const qreal newItemSize = newHandlePos - leftEdge; |
| |
| // Modify the preferredWidth, otherwise the original implicitWidth/preferredWidth |
| // will be used on the next layout (when it's no longer being resized). |
| if (!attached) { |
| // Force the attached object to be created since we rely on it. |
| attached = qobject_cast<QQuickSplitViewAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); |
| } |
| |
| /* |
| Users could conceivably respond to size changes in items by setting attached |
| SplitView properties: |
| |
| onWidthChanged: if (width < 10) secondItem.SplitView.preferredWidth = 100 |
| |
| We handle this by doing another layout after the current layout if the |
| attached/implicit size properties are set during this layout. However, we also |
| need to set preferredWidth/Height here (for reasons mentioned in the comment above), |
| but we don't want this to count as a request for a delayed layout, so we guard against it. |
| */ |
| m_ignoreNextLayoutRequest = true; |
| |
| if (horizontal) |
| attached->setPreferredWidth(newItemSize); |
| else |
| attached->setPreferredHeight(newItemSize); |
| |
| // We still need to use requestedWidth in the setWidth() call below, |
| // because sizeData has already been calculated and now contains an old |
| // effectivePreferredWidth value. |
| requestedSize = newItemSize; |
| |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized (dragged) " << item |
| << " (clampedMousePos=" << clampedMousePos |
| << " pressedHandlePos=" << pressedHandlePos |
| << " accumulated=" << accumulated |
| << " leftEdge=" << leftEdge |
| << " leftStop=" << leftStop |
| << " rightStop=" << rightStop |
| << " newHandlePos=" << newHandlePos |
| << " newItemSize=" << newItemSize << ")"; |
| } else { // Resizing the item on the right. |
| // The handle shouldn't cross other handles, so use the left edge of |
| // the first handle to the right as the right edge. |
| qreal rightEdge = size; |
| if (m_pressedHandleIndex + 1 < m_handleItems.size()) { |
| const QQuickItem *rightHandle = m_handleItems.at(m_pressedHandleIndex + 1); |
| rightEdge = horizontal ? rightHandle->x() : rightHandle->y(); |
| } |
| |
| // The mouse can be clicked anywhere in the handle, and if we don't account for |
| // its position within the handle, the handle will jump when dragged. |
| const qreal pressedHandlePos = clampedMousePos - mousePosRelativeToLeftHandleEdge; |
| |
| const qreal leftStop = accumulated - pressedHandleSize; |
| qreal rightStop = qMin(rightEdge - pressedHandleSize, pressedHandlePos); |
| // qBound() doesn't care if min is greater than max, but we do. |
| if (rightStop < leftStop) |
| rightStop = leftStop; |
| const qreal newHandlePos = qBound(leftStop, pressedHandlePos, rightStop); |
| const qreal newItemSize = rightEdge - (newHandlePos + pressedHandleSize); |
| |
| // Modify the preferredWidth, otherwise the original implicitWidth/preferredWidth |
| // will be used on the next layout (when it's no longer being resized). |
| if (!attached) { |
| // Force the attached object to be created since we rely on it. |
| attached = qobject_cast<QQuickSplitViewAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); |
| } |
| |
| m_ignoreNextLayoutRequest = true; |
| |
| if (horizontal) |
| attached->setPreferredWidth(newItemSize); |
| else |
| attached->setPreferredHeight(newItemSize); |
| |
| // We still need to use requestedSize in the setWidth()/setHeight() call below, |
| // because sizeData has already been calculated and now contains an old |
| // effectivePreferredWidth/Height value. |
| requestedSize = newItemSize; |
| |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized (dragged) " << item |
| << " (clampedMousePos=" << clampedMousePos |
| << " pressedHandlePos=" << pressedHandlePos |
| << " accumulated=" << accumulated |
| << " leftEdge=" << rightEdge |
| << " leftStop=" << leftStop |
| << " rightStop=" << rightStop |
| << " newHandlePos=" << newHandlePos |
| << " newItemSize=" << newItemSize << ")"; |
| } |
| } else if (index != m_fillIndex) { |
| // No handle is being dragged and we're not the fill item, |
| // so set our preferred size as we normally would. |
| requestedSize = horizontal |
| ? sizeData.effectivePreferredWidth : sizeData.effectivePreferredHeight; |
| } |
| |
| if (index != m_fillIndex) { |
| if (horizontal) { |
| item->setWidth(qBound( |
| sizeData.effectiveMinimumWidth, |
| requestedSize, |
| sizeData.effectiveMaximumWidth)); |
| item->setHeight(height); |
| } else { |
| item->setWidth(width); |
| item->setHeight(qBound( |
| sizeData.effectiveMinimumHeight, |
| requestedSize, |
| sizeData.effectiveMaximumHeight)); |
| } |
| |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized split item " << item |
| << " (effective" |
| << " minW=" << sizeData.effectiveMinimumWidth |
| << ", minH=" << sizeData.effectiveMinimumHeight |
| << ", prfW=" << sizeData.effectivePreferredWidth |
| << ", prfH=" << sizeData.effectivePreferredHeight |
| << ", maxW=" << sizeData.effectiveMaximumWidth |
| << ", maxH=" << sizeData.effectiveMaximumHeight << ")"; |
| |
| // Keep track of how much space has been used so far. |
| if (horizontal) |
| usedWidth += item->width(); |
| else |
| usedHeight += item->height(); |
| } else if (indexBeingResizedDueToDrag != m_fillIndex) { |
| // The fill item is resized afterwards, outside of the loop. |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": skipping fill item as we resize it last"; |
| } |
| |
| // Also account for the size of the handle for this item (if any). |
| // We do this for the fill item too, which is why it's outside of the check above. |
| if (index < count - 1 && m_handle) { |
| QQuickItem *handleItem = m_handleItems.at(index); |
| // The handle for an item that's not visible will usually already be skipped |
| // with the item visibility check higher up, but if the view looks like this |
| // [ visible ] | [ visible (fill) ] | [ hidden ] |
| // ^ |
| // hidden |
| // and we're iterating over the second item (which is visible but has no handle), |
| // we need to add an extra check for it to avoid it still taking up space. |
| if (handleItem->isVisible()) { |
| if (horizontal) { |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << index |
| << ": handle takes up " << handleItem->width() << " width"; |
| usedWidth += handleItem->width(); |
| } else { |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << index |
| << ": handle takes up " << handleItem->height() << " height"; |
| usedHeight += handleItem->height(); |
| } |
| } else { |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": handle is not visible; skipping it"; |
| } |
| } |
| } |
| } |
| |
| /* |
| Resizes the fill item by giving it the remaining space |
| after all other items have been resized. |
| |
| Items that aren't visible are skipped. |
| */ |
| void QQuickSplitViewPrivate::layoutResizeFillItem(QQuickItem *fillItem, |
| qreal &usedWidth, qreal &usedHeight, int indexBeingResizedDueToDrag) |
| { |
| // Only bother resizing if it it's visible. Also, if it's being resized due to a drag, |
| // then we've already set its size in layoutResizeSplitItems(), so no need to do it here. |
| if (!fillItem->isVisible() || indexBeingResizedDueToDrag == m_fillIndex) { |
| qCDebug(qlcQQuickSplitView).nospace() << m_fillIndex << ": - fill item " << fillItem |
| << " is not visible or was already resized due to a drag;" |
| << " skipping it and its handles (if any)"; |
| return; |
| } |
| |
| const QQuickItemPrivate *fillItemPrivate = QQuickItemPrivate::get(fillItem); |
| const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitView>(fillItem, false)); |
| const auto fillSizeData = effectiveSizeData(fillItemPrivate, attached); |
| if (isHorizontal()) { |
| fillItem->setWidth(qBound( |
| fillSizeData.effectiveMinimumWidth, |
| width - usedWidth, |
| fillSizeData.effectiveMaximumWidth)); |
| fillItem->setHeight(height); |
| } else { |
| fillItem->setWidth(width); |
| fillItem->setHeight(qBound( |
| fillSizeData.effectiveMinimumHeight, |
| height - usedHeight, |
| fillSizeData.effectiveMaximumHeight)); |
| } |
| |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << m_fillIndex |
| << ": resized split fill item " << fillItem << " (effective" |
| << " minW=" << fillSizeData.effectiveMinimumWidth |
| << ", minH=" << fillSizeData.effectiveMinimumHeight |
| << ", maxW=" << fillSizeData.effectiveMaximumWidth |
| << ", maxH=" << fillSizeData.effectiveMaximumHeight << ")"; |
| } |
| |
| /* |
| Positions items by laying them out in a row or column. |
| |
| Items that aren't visible are skipped. |
| */ |
| void QQuickSplitViewPrivate::layoutPositionItems(const QQuickItem *fillItem) |
| { |
| const bool horizontal = isHorizontal(); |
| const int count = contentModel->count(); |
| qreal usedWidth = 0; |
| qreal usedHeight = 0; |
| |
| for (int i = 0; i < count; ++i) { |
| QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); |
| if (!item->isVisible()) { |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << i << ": split item " << item |
| << " is not visible; skipping it and its handles (if any)"; |
| continue; |
| } |
| |
| // Position the item. |
| if (horizontal) { |
| item->setX(usedWidth); |
| item->setY(0); |
| } else { |
| item->setX(0); |
| item->setY(usedHeight); |
| } |
| |
| // Keep track of how much space has been used so far. |
| if (horizontal) |
| usedWidth += item->width(); |
| else |
| usedHeight += item->height(); |
| |
| if (Q_UNLIKELY(qlcQQuickSplitView().isDebugEnabled())) { |
| const QQuickItemPrivate *fillItemPrivate = QQuickItemPrivate::get(fillItem); |
| const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitView>(fillItem, false)); |
| const auto sizeData = effectiveSizeData(fillItemPrivate, attached); |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << i << ": positioned " |
| << (i == m_fillIndex ? "fill item " : "item ") << item << " (effective" |
| << " minW=" << sizeData.effectiveMinimumWidth |
| << ", minH=" << sizeData.effectiveMinimumHeight |
| << ", prfW=" << sizeData.effectivePreferredWidth |
| << ", prfH=" << sizeData.effectivePreferredHeight |
| << ", maxW=" << sizeData.effectiveMaximumWidth |
| << ", maxH=" << sizeData.effectiveMaximumHeight << ")"; |
| } |
| |
| // Position the handle for this item (if any). |
| if (i < count - 1 && m_handle) { |
| // Position the handle. |
| QQuickItem *handleItem = m_handleItems.at(i); |
| handleItem->setX(horizontal ? usedWidth : 0); |
| handleItem->setY(horizontal ? 0 : usedHeight); |
| |
| if (horizontal) |
| usedWidth += handleItem->width(); |
| else |
| usedHeight += handleItem->height(); |
| |
| qCDebug(qlcQQuickSplitView).nospace() << " - " << i << ": positioned handle " << handleItem; |
| } |
| } |
| } |
| |
| void QQuickSplitViewPrivate::requestLayout() |
| { |
| Q_Q(QQuickSplitView); |
| q->polish(); |
| } |
| |
| void QQuickSplitViewPrivate::layout() |
| { |
| if (!componentComplete) |
| return; |
| |
| if (m_layingOut) |
| return; |
| |
| const int count = contentModel->count(); |
| if (count <= 0) |
| return; |
| |
| Q_ASSERT_X(m_fillIndex < count, Q_FUNC_INFO, qPrintable( |
| QString::fromLatin1("m_fillIndex is %1 but our count is %2").arg(m_fillIndex).arg(count))); |
| |
| Q_ASSERT_X(!m_handle || m_handleItems.size() == count - 1, Q_FUNC_INFO, qPrintable(QString::fromLatin1( |
| "Expected %1 handle items, but there are %2").arg(count - 1).arg(m_handleItems.size()))); |
| |
| // We allow mouse events to instantly trigger layouts, whereas with e.g. |
| // attached properties being set, we require a delayed layout. |
| // To prevent recursive calls during mouse events, we need this guard. |
| QBoolBlocker guard(m_layingOut, true); |
| |
| const bool horizontal = isHorizontal(); |
| qCDebug(qlcQQuickSplitView) << "laying out" << count << "split items" |
| << (horizontal ? "horizontally" : "vertically") << "in SplitView" << q_func(); |
| |
| qreal usedWidth = 0; |
| qreal usedHeight = 0; |
| int indexBeingResizedDueToDrag = -1; |
| |
| qCDebug(qlcQQuickSplitView) << " resizing:"; |
| |
| // First, resize the items. We need to do this first because otherwise fill |
| // items would take up all of the remaining space as soon as they are encountered. |
| layoutResizeSplitItems(usedWidth, usedHeight, indexBeingResizedDueToDrag); |
| |
| qCDebug(qlcQQuickSplitView).nospace() |
| << " - (remaining width=" << width - usedWidth |
| << " remaining height=" << height - usedHeight << ")"; |
| |
| // Give the fill item the remaining space. |
| QQuickItem *fillItem = qobject_cast<QQuickItem*>(contentModel->object(m_fillIndex)); |
| layoutResizeFillItem(fillItem, usedWidth, usedHeight, indexBeingResizedDueToDrag); |
| |
| qCDebug(qlcQQuickSplitView) << " positioning:"; |
| |
| // Position the items. |
| layoutPositionItems(fillItem); |
| |
| qCDebug(qlcQQuickSplitView).nospace() << "finished layouting"; |
| } |
| |
| void QQuickSplitViewPrivate::createHandles() |
| { |
| Q_ASSERT(m_handle); |
| // A handle only makes sense if there are two items on either side. |
| if (contentModel->count() <= 1) |
| return; |
| |
| // Create new handle items if there aren't enough. |
| const int count = contentModel->count() - 1; |
| qCDebug(qlcQQuickSplitView) << "creating" << count << "handles"; |
| m_handleItems.reserve(count); |
| for (int i = 0; i < count; ++i) |
| createHandleItem(i); |
| } |
| |
| void QQuickSplitViewPrivate::createHandleItem(int index) |
| { |
| Q_Q(QQuickSplitView); |
| if (contentModel->count() <= 1) |
| return; |
| |
| qCDebug(qlcQQuickSplitView) << "- creating handle for split item at index" << index |
| << "from handle component" << m_handle; |
| |
| // If we don't use the correct context, it won't be possible to refer to |
| // the control's id from within the delegate. |
| QQmlContext *creationContext = m_handle->creationContext(); |
| // The component might not have been created in QML, in which case |
| // the creation context will be null and we have to create it ourselves. |
| if (!creationContext) |
| creationContext = qmlContext(q); |
| QQmlContext *context = new QQmlContext(creationContext, q); |
| context->setContextObject(q); |
| QQuickItem *item = qobject_cast<QQuickItem*>(m_handle->beginCreate(context)); |
| if (item) { |
| // Insert the item to our list of items *before* its parent is set to us, |
| // so that we can avoid it being added as a content item by checking |
| // if it is in the list in isContent(). |
| m_handleItems.insert(index, item); |
| |
| item->setParentItem(q); |
| |
| m_handle->completeCreate(); |
| resizeHandle(item); |
| } |
| } |
| |
| void QQuickSplitViewPrivate::removeExcessHandles() |
| { |
| int excess = m_handleItems.size() - qMax(0, contentModel->count() - 1); |
| for (; excess > 0; --excess) { |
| QQuickItem *handleItem = m_handleItems.takeLast(); |
| delete handleItem; |
| } |
| } |
| |
| qreal QQuickSplitViewPrivate::accumulatedSize(int firstIndex, int lastIndex) const |
| { |
| qreal size = 0.0; |
| const bool horizontal = isHorizontal(); |
| for (int i = firstIndex; i <= lastIndex; ++i) { |
| QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); |
| if (item->isVisible()) { |
| if (i != m_fillIndex) { |
| size += horizontal ? item->width() : item->height(); |
| } else { |
| // If the fill item has a minimum size specified, we must respect it. |
| const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); |
| if (attached) { |
| const QQuickSplitViewAttachedPrivate *attachedPrivate |
| = QQuickSplitViewAttachedPrivate::get(attached); |
| if (horizontal && attachedPrivate->m_isMinimumWidthSet) |
| size += attachedPrivate->m_minimumWidth; |
| else if (!horizontal && attachedPrivate->m_isMinimumHeightSet) |
| size += attachedPrivate->m_minimumHeight; |
| } |
| } |
| } |
| |
| // Only add the handle's width if there's actually a handle for this split item index. |
| if (i < lastIndex || lastIndex < contentModel->count() - 1) { |
| const QQuickItem *handleItem = m_handleItems.at(i); |
| if (handleItem->isVisible()) |
| size += horizontal ? handleItem->width() : handleItem->height(); |
| } |
| } |
| return size; |
| } |
| |
| qreal effectiveMinimumWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate) |
| { |
| return attachedPrivate && attachedPrivate->m_isMinimumWidthSet ? attachedPrivate->m_minimumWidth : 0; |
| } |
| |
| qreal effectiveMinimumHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate) |
| { |
| return attachedPrivate && attachedPrivate->m_isMinimumHeightSet ? attachedPrivate->m_minimumHeight : 0; |
| } |
| |
| qreal effectivePreferredWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate, |
| const QQuickItemPrivate *itemPrivate) |
| { |
| return attachedPrivate && attachedPrivate->m_isPreferredWidthSet |
| ? attachedPrivate->m_preferredWidth : itemPrivate->implicitWidth; |
| } |
| |
| qreal effectivePreferredHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate, |
| const QQuickItemPrivate *itemPrivate) |
| { |
| return attachedPrivate && attachedPrivate->m_isPreferredHeightSet |
| ? attachedPrivate->m_preferredHeight : itemPrivate->implicitHeight; |
| } |
| |
| qreal effectiveMaximumWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate) |
| { |
| return attachedPrivate && attachedPrivate->m_isMaximumWidthSet |
| ? attachedPrivate->m_maximumWidth : std::numeric_limits<qreal>::infinity(); |
| } |
| |
| qreal effectiveMaximumHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate) |
| { |
| return attachedPrivate && attachedPrivate->m_isMaximumHeightSet |
| ? attachedPrivate->m_maximumHeight : std::numeric_limits<qreal>::infinity(); |
| } |
| |
| // We don't just take an index, because the item and attached properties object |
| // will both be used outside of this function by calling code, so save some |
| // time by not accessing them twice. |
| QQuickSplitViewPrivate::EffectiveSizeData QQuickSplitViewPrivate::effectiveSizeData( |
| const QQuickItemPrivate *itemPrivate, const QQuickSplitViewAttached *attached) const |
| { |
| EffectiveSizeData data; |
| const QQuickSplitViewAttachedPrivate *attachedPrivate = attached ? QQuickSplitViewAttachedPrivate::get(attached) : nullptr; |
| data.effectiveMinimumWidth = effectiveMinimumWidth(attachedPrivate); |
| data.effectiveMinimumHeight = effectiveMinimumHeight(attachedPrivate); |
| data.effectivePreferredWidth = effectivePreferredWidth(attachedPrivate, itemPrivate); |
| data.effectivePreferredHeight = effectivePreferredHeight(attachedPrivate, itemPrivate); |
| data.effectiveMaximumWidth = effectiveMaximumWidth(attachedPrivate); |
| data.effectiveMaximumHeight = effectiveMaximumHeight(attachedPrivate); |
| return data; |
| } |
| |
| int QQuickSplitViewPrivate::handleIndexForSplitIndex(int splitIndex) const |
| { |
| // If it's the first and only item in the view, it doesn't have a handle, |
| // so return -1: splitIndex (0) - 1. |
| // If it's the last item in the view, it doesn't have a handle, so use |
| // the handle for the previous item. |
| return splitIndex == contentModel->count() - 1 ? splitIndex - 1 : splitIndex; |
| } |
| |
| void QQuickSplitViewPrivate::destroyHandles() |
| { |
| qDeleteAll(m_handleItems); |
| m_handleItems.clear(); |
| } |
| |
| void QQuickSplitViewPrivate::resizeHandle(QQuickItem *handleItem) |
| { |
| const bool horizontal = isHorizontal(); |
| handleItem->setWidth(horizontal ? handleItem->implicitWidth() : width); |
| handleItem->setHeight(horizontal ? height : handleItem->implicitHeight()); |
| } |
| |
| void QQuickSplitViewPrivate::resizeHandles() |
| { |
| for (QQuickItem *handleItem : m_handleItems) |
| resizeHandle(handleItem); |
| } |
| |
| void QQuickSplitViewPrivate::updateHandleVisibilities() |
| { |
| // If this is the first item that is visible, we won't have any |
| // handles yet, because we don't create a handle if we only have one item. |
| if (m_handleItems.isEmpty()) |
| return; |
| |
| // If the visibility/children change makes any item the last (right/bottom-most) |
| // visible item, we don't want to display a handle for it either: |
| // [ visible (fill) ] | [ hidden ] | [ hidden ] |
| // ^ ^ |
| // hidden hidden |
| const int count = contentModel->count(); |
| int lastVisibleItemIndex = -1; |
| for (int i = count - 1; i >= 0; --i) { |
| const QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); |
| if (item->isVisible()) { |
| lastVisibleItemIndex = i; |
| break; |
| } |
| } |
| |
| for (int i = 0; i < count - 1; ++i) { |
| const QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); |
| QQuickItem *handleItem = m_handleItems.at(i); |
| if (i != lastVisibleItemIndex) |
| handleItem->setVisible(item->isVisible()); |
| else |
| handleItem->setVisible(false); |
| qCDebug(qlcQQuickSplitView) << "set visible property of handle at index" |
| << i << "to" << handleItem->isVisible(); |
| } |
| } |
| |
| void QQuickSplitViewPrivate::updateHoveredHandle(QQuickItem *hoveredItem) |
| { |
| Q_Q(QQuickSplitView); |
| const int oldHoveredHandleIndex = m_hoveredHandleIndex; |
| m_hoveredHandleIndex = m_handleItems.indexOf(hoveredItem); |
| if (m_hoveredHandleIndex == oldHoveredHandleIndex) |
| return; |
| |
| // First, clear the hovered flag of any previously-hovered handle. |
| if (oldHoveredHandleIndex != -1) { |
| QQuickItem *oldHoveredHandle = m_handleItems.at(oldHoveredHandleIndex); |
| QQuickSplitHandleAttached *oldHoveredHandleAttached = qobject_cast<QQuickSplitHandleAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(oldHoveredHandle, true)); |
| QQuickSplitHandleAttachedPrivate::get(oldHoveredHandleAttached)->setHovered(false); |
| qCDebug(qlcQQuickSplitViewMouse) << "handle item at index" << oldHoveredHandleIndex << "is no longer hovered"; |
| } |
| |
| if (m_hoveredHandleIndex != -1) { |
| QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(hoveredItem, true)); |
| QQuickSplitHandleAttachedPrivate::get(handleAttached)->setHovered(true); |
| qCDebug(qlcQQuickSplitViewMouse) << "handle item at index" << m_hoveredHandleIndex << "is now hovered"; |
| } else { |
| qCDebug(qlcQQuickSplitViewMouse) << "either there is no hovered item or" << hoveredItem << "is not a handle"; |
| } |
| |
| #if QT_CONFIG(cursor) |
| if (m_hoveredHandleIndex != -1) |
| q->setCursor(m_orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor); |
| else |
| q->setCursor(Qt::ArrowCursor); |
| #endif |
| } |
| |
| void QQuickSplitViewPrivate::setResizing(bool resizing) |
| { |
| Q_Q(QQuickSplitView); |
| if (resizing == m_resizing) |
| return; |
| |
| m_resizing = resizing; |
| emit q->resizingChanged(); |
| } |
| |
| bool QQuickSplitViewPrivate::isHorizontal() const |
| { |
| return m_orientation == Qt::Horizontal; |
| } |
| |
| QQuickItem *QQuickSplitViewPrivate::getContentItem() |
| { |
| Q_Q(QQuickSplitView); |
| if (QQuickItem *item = QQuickContainerPrivate::getContentItem()) |
| return item; |
| |
| return new QQuickContentItem(q); |
| } |
| |
| void QQuickSplitViewPrivate::handlePress(const QPointF &point) |
| { |
| Q_Q(QQuickSplitView); |
| QQuickContainerPrivate::handlePress(point); |
| |
| QQuickItem *pressedItem = q->childAt(point.x(), point.y()); |
| const int pressedHandleIndex = m_handleItems.indexOf(pressedItem); |
| if (pressedHandleIndex != -1) { |
| m_pressedHandleIndex = pressedHandleIndex; |
| m_pressPos = point; |
| m_mousePos = point; |
| |
| const QQuickItem *leftOrTopItem = qobject_cast<QQuickItem*>(contentModel->object(m_pressedHandleIndex)); |
| const QQuickItem *rightOrBottomItem = qobject_cast<QQuickItem*>(contentModel->object(m_pressedHandleIndex + 1)); |
| const bool isHorizontal = m_orientation == Qt::Horizontal; |
| m_leftOrTopItemSizeBeforePress = isHorizontal ? leftOrTopItem->width() : leftOrTopItem->height(); |
| m_rightOrBottomItemSizeBeforePress = isHorizontal ? rightOrBottomItem->width() : rightOrBottomItem->height(); |
| m_handlePosBeforePress = pressedItem->position(); |
| |
| // Avoid e.g. Flickable stealing our drag if we're inside it. |
| q->setKeepMouseGrab(true); |
| |
| // Force the attached object to be created since we rely on it. |
| QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(pressedItem, true)); |
| QQuickSplitHandleAttachedPrivate::get(handleAttached)->setPressed(true); |
| |
| setResizing(true); |
| |
| qCDebug(qlcQQuickSplitViewMouse).nospace() << "handled press -" |
| << " left/top index=" << m_pressedHandleIndex << "," |
| << " size before press=" << m_leftOrTopItemSizeBeforePress << "," |
| << " right/bottom index=" << m_pressedHandleIndex + 1 << "," |
| << " size before press=" << m_rightOrBottomItemSizeBeforePress; |
| } |
| } |
| |
| void QQuickSplitViewPrivate::handleMove(const QPointF &point) |
| { |
| QQuickContainerPrivate::handleMove(point); |
| |
| if (m_pressedHandleIndex != -1) { |
| m_mousePos = point; |
| // Don't request layouts for input events because we want |
| // resizing to be as responsive and smooth as possible. |
| updatePolish(); |
| } |
| } |
| |
| void QQuickSplitViewPrivate::handleRelease(const QPointF &point) |
| { |
| Q_Q(QQuickSplitView); |
| QQuickContainerPrivate::handleRelease(point); |
| |
| if (m_pressedHandleIndex != -1) { |
| QQuickItem *pressedHandle = m_handleItems.at(m_pressedHandleIndex); |
| QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(pressedHandle, true)); |
| QQuickSplitHandleAttachedPrivate::get(handleAttached)->setPressed(false); |
| } |
| |
| setResizing(false); |
| |
| m_pressedHandleIndex = -1; |
| m_pressPos = QPointF(); |
| m_mousePos = QPointF(); |
| m_handlePosBeforePress = QPointF(); |
| m_leftOrTopItemSizeBeforePress = 0.0; |
| m_rightOrBottomItemSizeBeforePress = 0.0; |
| q->setKeepMouseGrab(false); |
| } |
| |
| void QQuickSplitViewPrivate::itemVisibilityChanged(QQuickItem *item) |
| { |
| const int itemIndex = contentModel->indexOf(item, nullptr); |
| Q_ASSERT(itemIndex != -1); |
| |
| qCDebug(qlcQQuickSplitView) << "visible property of split item" |
| << item << "at index" << itemIndex << "changed to" << item->isVisible(); |
| |
| // The visibility of an item just changed, so we need to update the visibility |
| // of the corresponding handle (if one exists). |
| |
| const int handleIndex = handleIndexForSplitIndex(itemIndex); |
| if (handleIndex != -1) { |
| QQuickItem *handleItem = m_handleItems.at(handleIndex); |
| handleItem->setVisible(item->isVisible()); |
| |
| qCDebug(qlcQQuickSplitView) << "set visible property of handle item" |
| << handleItem << "at index" << handleIndex << "to" << item->isVisible(); |
| } |
| |
| updateHandleVisibilities(); |
| updateFillIndex(); |
| requestLayout(); |
| } |
| |
| void QQuickSplitViewPrivate::itemImplicitWidthChanged(QQuickItem *) |
| { |
| requestLayout(); |
| } |
| |
| void QQuickSplitViewPrivate::itemImplicitHeightChanged(QQuickItem *) |
| { |
| requestLayout(); |
| } |
| |
| void QQuickSplitViewPrivate::updatePolish() |
| { |
| layout(); |
| } |
| |
| QQuickSplitViewPrivate *QQuickSplitViewPrivate::get(QQuickSplitView *splitView) |
| { |
| return splitView->d_func(); |
| } |
| |
| QQuickSplitView::QQuickSplitView(QQuickItem *parent) |
| : QQuickContainer(*(new QQuickSplitViewPrivate), parent) |
| { |
| Q_D(QQuickSplitView); |
| d->changeTypes |= QQuickItemPrivate::Visibility; |
| |
| setAcceptedMouseButtons(Qt::LeftButton); |
| setFiltersChildMouseEvents(true); |
| } |
| |
| QQuickSplitView::QQuickSplitView(QQuickSplitViewPrivate &dd, QQuickItem *parent) |
| : QQuickContainer(dd, parent) |
| { |
| Q_D(QQuickSplitView); |
| d->changeTypes |= QQuickItemPrivate::Visibility; |
| |
| setAcceptedMouseButtons(Qt::LeftButton); |
| setFiltersChildMouseEvents(true); |
| } |
| |
| QQuickSplitView::~QQuickSplitView() |
| { |
| Q_D(QQuickSplitView); |
| for (int i = 0; i < d->contentModel->count(); ++i) { |
| QQuickItem *item = qobject_cast<QQuickItem*>(d->contentModel->object(i)); |
| d->removeImplicitSizeListener(item); |
| } |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick.Controls::SplitView::orientation |
| |
| This property holds the orientation of the SplitView. |
| |
| The orientation determines how the split items are laid out: |
| |
| Possible values: |
| \value Qt.Horizontal The items are laid out horizontally (default). |
| \value Qt.Vertical The items are laid out vertically. |
| */ |
| Qt::Orientation QQuickSplitView::orientation() const |
| { |
| Q_D(const QQuickSplitView); |
| return d->m_orientation; |
| } |
| |
| void QQuickSplitView::setOrientation(Qt::Orientation orientation) |
| { |
| Q_D(QQuickSplitView); |
| if (orientation == d->m_orientation) |
| return; |
| |
| d->m_orientation = orientation; |
| d->resizeHandles(); |
| d->requestLayout(); |
| emit orientationChanged(); |
| } |
| |
| /*! |
| \qmlproperty bool QtQuick.Controls::SplitView::resizing |
| \readonly |
| |
| This property is \c true when the user is resizing |
| split items by dragging on the splitter handles. |
| */ |
| bool QQuickSplitView::isResizing() const |
| { |
| Q_D(const QQuickSplitView); |
| return d->m_resizing; |
| } |
| |
| /*! |
| \qmlproperty Component QtQuick.Controls::SplitView::handle |
| |
| This property holds the handle component. |
| |
| An instance of this component will be instantiated \c {count - 1} |
| times, as long as \c count is greater than than \c {1}. |
| |
| The following table explains how each handle will be resized |
| depending on the orientation of the split view: |
| |
| \table |
| \header |
| \li Orientation |
| \li Handle Width |
| \li Handle Height |
| \row |
| \li \c Qt.Horizontal |
| \li \c implicitWidth |
| \li The \c height of the SplitView. |
| \row |
| \li \c Qt.Vertical |
| \li The \c width of the SplitView. |
| \li \c implicitHeight |
| \endtable |
| |
| \sa {Customizing SplitView} |
| */ |
| QQmlComponent *QQuickSplitView::handle() |
| { |
| Q_D(const QQuickSplitView); |
| return d->m_handle; |
| } |
| |
| void QQuickSplitView::setHandle(QQmlComponent *handle) |
| { |
| Q_D(QQuickSplitView); |
| if (handle == d->m_handle) |
| return; |
| |
| qCDebug(qlcQQuickSplitView) << "setting handle" << handle; |
| |
| if (d->m_handle) |
| d->destroyHandles(); |
| |
| d->m_handle = handle; |
| |
| if (d->m_handle) |
| d->createHandles(); |
| |
| d->requestLayout(); |
| |
| emit handleChanged(); |
| } |
| |
| bool QQuickSplitView::isContent(QQuickItem *item) const |
| { |
| Q_D(const QQuickSplitView); |
| if (!qmlContext(item)) |
| return false; |
| |
| if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) |
| return false; |
| |
| return !d->m_handleItems.contains(item); |
| } |
| |
| QQuickSplitViewAttached *QQuickSplitView::qmlAttachedProperties(QObject *object) |
| { |
| return new QQuickSplitViewAttached(object); |
| } |
| |
| /*! |
| \qmlmethod var QtQuick.Controls::SplitView::saveState() |
| |
| Saves the preferred sizes of split items into a byte array and returns it. |
| |
| \sa {Serializing SplitView's State}, restoreState() |
| */ |
| QVariant QQuickSplitView::saveState() |
| { |
| Q_D(QQuickSplitView); |
| qCDebug(qlcQQuickSplitViewState) << "saving state for split items in" << this; |
| |
| // Save the preferred sizes of each split item. |
| QCborArray cborArray; |
| for (int i = 0; i < d->contentModel->count(); ++i) { |
| const QQuickItem *item = qobject_cast<QQuickItem*>(d->contentModel->object(i)); |
| const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); |
| // Don't serialise stuff if we don't need to. If a split item was given a preferred |
| // size in QML or it was dragged, it will have an attached object and either |
| // m_isPreferredWidthSet or m_isPreferredHeightSet (or both) will be true, |
| // so items without these can be skipped. We write the index of each item |
| // that has data so that we know which item to set it on when restoring. |
| if (!attached) |
| continue; |
| |
| const QQuickSplitViewAttachedPrivate *attachedPrivate = QQuickSplitViewAttachedPrivate::get(attached); |
| if (!attachedPrivate->m_isPreferredWidthSet && !attachedPrivate->m_isPreferredHeightSet) |
| continue; |
| |
| QCborMap cborMap; |
| cborMap[QLatin1String("index")] = i; |
| if (attachedPrivate->m_isPreferredWidthSet) { |
| cborMap[QLatin1String("preferredWidth")] = static_cast<double>(attachedPrivate->m_preferredWidth); |
| |
| qCDebug(qlcQQuickSplitViewState).nospace() << "- wrote preferredWidth of " |
| << attachedPrivate->m_preferredWidth << " for split item " << item << " at index " << i; |
| } |
| if (attachedPrivate->m_isPreferredHeightSet) { |
| cborMap[QLatin1String("preferredHeight")] = static_cast<double>(attachedPrivate->m_preferredHeight); |
| |
| qCDebug(qlcQQuickSplitViewState).nospace() << "- wrote preferredHeight of " |
| << attachedPrivate->m_preferredHeight << " for split item " << item << " at index " << i; |
| } |
| |
| cborArray.append(cborMap); |
| } |
| |
| const QByteArray byteArray = cborArray.toCborValue().toCbor(); |
| qCDebug(qlcQQuickSplitViewState) << "the resulting byte array is:" << byteArray; |
| return QVariant(byteArray); |
| } |
| |
| /*! |
| \qmlmethod bool QtQuick.Controls::SplitView::restoreState(state) |
| |
| Reads the preferred sizes from \a state and applies them to the split items. |
| |
| Returns \c true if the state was successfully restored, otherwise \c false. |
| |
| \sa {Serializing SplitView's State}, saveState() |
| */ |
| bool QQuickSplitView::restoreState(const QVariant &state) |
| { |
| const QByteArray cborByteArray = state.toByteArray(); |
| Q_D(QQuickSplitView); |
| if (cborByteArray.isEmpty()) |
| return false; |
| |
| QCborParserError parserError; |
| const QCborValue cborValue(QCborValue::fromCbor(cborByteArray, &parserError)); |
| if (parserError.error != QCborError::NoError) { |
| qmlWarning(this) << "Error reading SplitView state:" << parserError.errorString(); |
| return false; |
| } |
| |
| qCDebug(qlcQQuickSplitViewState) << "restoring state for split items of" << this |
| << "from the following string:" << state; |
| |
| const QCborArray cborArray(cborValue.toArray()); |
| const int ourCount = d->contentModel->count(); |
| // This could conceivably happen if items were removed from the SplitView since the state was last saved. |
| if (cborArray.size() > ourCount) { |
| qmlWarning(this) << "Error reading SplitView state: expected " |
| << ourCount << " or less split items but got " << cborArray.size(); |
| return false; |
| } |
| |
| for (auto it = cborArray.constBegin(); it != cborArray.constEnd(); ++it) { |
| QCborMap cborMap(it->toMap()); |
| const int splitItemIndex = cborMap.value(QLatin1String("index")).toInteger(); |
| const bool isPreferredWidthSet = cborMap.contains(QLatin1String("preferredWidth")); |
| const bool isPreferredHeightSet = cborMap.contains(QLatin1String("preferredHeight")); |
| |
| QQuickItem *item = qobject_cast<QQuickItem*>(d->contentModel->object(splitItemIndex)); |
| // If the split item does not have a preferred size specified in QML, it could still have |
| // been resized via dragging before it was saved. In this case, it won't have an |
| // attached object upon application startup, so we create it. |
| QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); |
| if (isPreferredWidthSet) { |
| const qreal preferredWidth = cborMap.value(QLatin1String("preferredWidth")).toDouble(); |
| attached->setPreferredWidth(preferredWidth); |
| } |
| if (isPreferredHeightSet) { |
| const qreal preferredHeight = cborMap.value(QLatin1String("preferredHeight")).toDouble(); |
| attached->setPreferredHeight(preferredHeight); |
| } |
| |
| const QQuickSplitViewAttachedPrivate *attachedPrivate = QQuickSplitViewAttachedPrivate::get(attached); |
| qCDebug(qlcQQuickSplitViewState).nospace() |
| << "- restored the following state for split item " << item << " at index " << splitItemIndex |
| << ": preferredWidthSet=" << attachedPrivate->m_isPreferredWidthSet |
| << " preferredWidth=" << attachedPrivate->m_preferredWidth |
| << " preferredHeightSet=" << attachedPrivate->m_isPreferredHeightSet |
| << " preferredHeight=" << attachedPrivate->m_preferredHeight; |
| } |
| |
| return true; |
| } |
| |
| void QQuickSplitView::componentComplete() |
| { |
| Q_D(QQuickSplitView); |
| QQuickControl::componentComplete(); |
| d->resizeHandles(); |
| d->updateFillIndex(); |
| d->updatePolish(); |
| } |
| |
| void QQuickSplitView::hoverMoveEvent(QHoverEvent *event) |
| { |
| Q_D(QQuickSplitView); |
| QQuickContainer::hoverMoveEvent(event); |
| |
| QQuickItem *hoveredItem = childAt(event->pos().x(), event->pos().y()); |
| d->updateHoveredHandle(hoveredItem); |
| } |
| |
| bool QQuickSplitView::childMouseEventFilter(QQuickItem *item, QEvent *event) |
| { |
| Q_D(QQuickSplitView); |
| qCDebug(qlcQQuickSplitViewMouse) << "childMouseEventFilter called with" << item << event; |
| if (event->type() != QEvent::HoverEnter) |
| return false; |
| |
| // If a child item received a hover enter event, then it means our handle is no longer hovered. |
| // Handles should be purely visual and not accept hover events, |
| // so we should never get hover events for them here. |
| d->updateHoveredHandle(nullptr); |
| return false; |
| } |
| |
| void QQuickSplitView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) |
| { |
| Q_D(QQuickSplitView); |
| QQuickControl::geometryChanged(newGeometry, oldGeometry); |
| d->resizeHandles(); |
| d->requestLayout(); |
| } |
| |
| void QQuickSplitView::itemAdded(int index, QQuickItem *item) |
| { |
| Q_D(QQuickSplitView); |
| if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) |
| return; |
| |
| const int count = d->contentModel->count(); |
| qCDebug(qlcQQuickSplitView).nospace() << "split item " << item << " added at index " << index |
| << "; there are now " << count << " items"; |
| |
| QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); |
| if (attached) |
| QQuickSplitViewAttachedPrivate::get(attached)->setView(this); |
| |
| // Only need to add handles if we have more than one split item. |
| if (count > 1) { |
| // If the item was added at the end, it shouldn't get a handle; |
| // the handle always goes to the split item on the left. |
| d->createHandleItem(index < count - 1 ? index : index - 1); |
| } |
| |
| d->addImplicitSizeListener(item); |
| |
| d->updateHandleVisibilities(); |
| d->updateFillIndex(); |
| d->requestLayout(); |
| } |
| |
| void QQuickSplitView::itemMoved(int index, QQuickItem *item) |
| { |
| Q_D(QQuickSplitView); |
| if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) |
| return; |
| |
| qCDebug(qlcQQuickSplitView) << "split item" << item << "moved to index" << index; |
| |
| d->updateHandleVisibilities(); |
| d->updateFillIndex(); |
| d->requestLayout(); |
| } |
| |
| void QQuickSplitView::itemRemoved(int index, QQuickItem *item) |
| { |
| Q_D(QQuickSplitView); |
| if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) |
| return; |
| |
| qCDebug(qlcQQuickSplitView).nospace() << "split item " << item << " removed from index " << index |
| << "; there are now " << d->contentModel->count() << " items"; |
| |
| // Clear hovered/pressed handle if there are any. |
| if (d->m_hoveredHandleIndex != -1 || d->m_pressedHandleIndex != -1) { |
| const int handleIndex = d->m_hoveredHandleIndex != -1 ? d->m_hoveredHandleIndex : d->m_pressedHandleIndex; |
| QQuickItem *itemHandle = d->m_handleItems.at(handleIndex); |
| QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(itemHandle, false)); |
| if (handleAttached) { |
| auto handleAttachedPrivate = QQuickSplitHandleAttachedPrivate::get(handleAttached); |
| handleAttachedPrivate->setHovered(false); |
| handleAttachedPrivate->setPressed(false); |
| } |
| |
| setKeepMouseGrab(false); |
| d->m_hoveredHandleIndex = -1; |
| d->m_pressedHandleIndex = -1; |
| } |
| |
| // Unset any attached properties since the item is no longer owned by us. |
| QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( |
| qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); |
| if (attached) |
| QQuickSplitViewAttachedPrivate::get(attached)->setView(this); |
| |
| d->removeImplicitSizeListener(item); |
| |
| d->removeExcessHandles(); |
| d->updateHandleVisibilities(); |
| d->updateFillIndex(); |
| d->requestLayout(); |
| } |
| |
| #if QT_CONFIG(accessibility) |
| QAccessible::Role QQuickSplitView::accessibleRole() const |
| { |
| return QAccessible::Pane; |
| } |
| #endif |
| |
| QQuickSplitViewAttached::QQuickSplitViewAttached(QObject *parent) |
| : QObject(*(new QQuickSplitViewAttachedPrivate), parent) |
| { |
| Q_D(QQuickSplitViewAttached); |
| QQuickItem *item = qobject_cast<QQuickItem *>(parent); |
| if (!item) { |
| qmlWarning(parent) << "SplitView: attached properties can only be used on Items"; |
| return; |
| } |
| |
| if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) |
| return; |
| |
| d->m_splitItem = item; |
| |
| // Child items get added to SplitView's contentItem, so we have to ensure |
| // that exists first before trying to set m_splitView. |
| // Apparently, in some cases it's normal for the parent item |
| // to not exist until shortly after this constructor has run. |
| if (!item->parentItem()) |
| return; |
| |
| // This will get hit when attached SplitView properties are imperatively set |
| // on an item that previously had none set, for example. |
| QQuickSplitView *splitView = qobject_cast<QQuickSplitView*>(item->parentItem()->parentItem()); |
| if (!splitView) { |
| qmlWarning(parent) << "SplitView: attached properties must be accessed through a direct child of SplitView"; |
| return; |
| } |
| |
| d->setView(splitView); |
| } |
| |
| /*! |
| \qmlattachedproperty SplitView QtQuick.Controls::SplitView::view |
| |
| This attached property holds the split view of the item it is |
| attached to, or \c null if the item is not in a split view. |
| */ |
| QQuickSplitView *QQuickSplitViewAttached::view() const |
| { |
| Q_D(const QQuickSplitViewAttached); |
| return d->m_splitView; |
| } |
| |
| /*! |
| \qmlattachedproperty real QtQuick.Controls::SplitView::minimumWidth |
| |
| This attached property controls the minimum width of the split item. |
| The \l preferredWidth is bound within the \l minimumWidth and |
| \l maximumWidth. A split item cannot be dragged to be smaller than |
| its \c minimumWidth. |
| |
| The default value is \c 0. To reset this property to its default value, |
| set it to \c undefined. |
| |
| \sa maximumWidth, preferredWidth, fillWidth, minimumHeight |
| */ |
| qreal QQuickSplitViewAttached::minimumWidth() const |
| { |
| Q_D(const QQuickSplitViewAttached); |
| return d->m_minimumWidth; |
| } |
| |
| void QQuickSplitViewAttached::setMinimumWidth(qreal width) |
| { |
| Q_D(QQuickSplitViewAttached); |
| d->m_isMinimumWidthSet = true; |
| if (qFuzzyCompare(width, d->m_minimumWidth)) |
| return; |
| |
| d->m_minimumWidth = width; |
| d->requestLayoutView(); |
| emit minimumWidthChanged(); |
| } |
| |
| void QQuickSplitViewAttached::resetMinimumWidth() |
| { |
| Q_D(QQuickSplitViewAttached); |
| const qreal oldEffectiveMinimumWidth = effectiveMinimumWidth(d); |
| |
| d->m_isMinimumWidthSet = false; |
| d->m_minimumWidth = -1; |
| |
| const qreal newEffectiveMinimumWidth = effectiveMinimumWidth(d); |
| if (qFuzzyCompare(newEffectiveMinimumWidth, oldEffectiveMinimumWidth)) |
| return; |
| |
| d->requestLayoutView(); |
| emit minimumWidthChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty real QtQuick.Controls::SplitView::minimumHeight |
| |
| This attached property controls the minimum height of the split item. |
| The \l preferredHeight is bound within the \l minimumHeight and |
| \l maximumHeight. A split item cannot be dragged to be smaller than |
| its \c minimumHeight. |
| |
| The default value is \c 0. To reset this property to its default value, |
| set it to \c undefined. |
| |
| \sa maximumHeight, preferredHeight, fillHeight, minimumWidth |
| */ |
| qreal QQuickSplitViewAttached::minimumHeight() const |
| { |
| Q_D(const QQuickSplitViewAttached); |
| return d->m_minimumHeight; |
| } |
| |
| void QQuickSplitViewAttached::setMinimumHeight(qreal height) |
| { |
| Q_D(QQuickSplitViewAttached); |
| d->m_isMinimumHeightSet = true; |
| if (qFuzzyCompare(height, d->m_minimumHeight)) |
| return; |
| |
| d->m_minimumHeight = height; |
| d->requestLayoutView(); |
| emit minimumHeightChanged(); |
| } |
| |
| void QQuickSplitViewAttached::resetMinimumHeight() |
| { |
| Q_D(QQuickSplitViewAttached); |
| const qreal oldEffectiveMinimumHeight = effectiveMinimumHeight(d); |
| |
| d->m_isMinimumHeightSet = false; |
| d->m_minimumHeight = -1; |
| |
| const qreal newEffectiveMinimumHeight = effectiveMinimumHeight(d); |
| if (qFuzzyCompare(newEffectiveMinimumHeight, oldEffectiveMinimumHeight)) |
| return; |
| |
| d->requestLayoutView(); |
| emit minimumHeightChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty real QtQuick.Controls::SplitView::preferredWidth |
| |
| This attached property controls the preferred width of the split item. The |
| preferred width will be used as the size of the item, and will be bound |
| within the \l minimumWidth and \l maximumWidth. If the preferred width |
| is not set, the item's \l {Item::}{implicitWidth} will be used. |
| |
| When a split item is resized, the preferredWidth will be set in order |
| to keep track of the new size. |
| |
| By default, this property is not set, and therefore |
| \l {Item::}{implicitWidth} will be used instead. To reset this property to |
| its default value, set it to \c undefined. |
| |
| \note Do not set the \l{Item::}{width} property of a split item, as it will be |
| overwritten upon each layout of the SplitView. |
| |
| \sa minimumWidth, maximumWidth, fillWidth, preferredHeight |
| */ |
| qreal QQuickSplitViewAttached::preferredWidth() const |
| { |
| Q_D(const QQuickSplitViewAttached); |
| return d->m_preferredWidth; |
| } |
| |
| void QQuickSplitViewAttached::setPreferredWidth(qreal width) |
| { |
| Q_D(QQuickSplitViewAttached); |
| d->m_isPreferredWidthSet = true; |
| // Make sure that we clear this flag now, before we emit the change signals |
| // which could cause another setter to be called. |
| auto splitViewPrivate = d->m_splitView ? QQuickSplitViewPrivate::get(d->m_splitView) : nullptr; |
| const bool ignoreNextLayoutRequest = splitViewPrivate && splitViewPrivate->m_ignoreNextLayoutRequest; |
| if (splitViewPrivate) |
| splitViewPrivate->m_ignoreNextLayoutRequest = false; |
| |
| if (qFuzzyCompare(width, d->m_preferredWidth)) |
| return; |
| |
| d->m_preferredWidth = width; |
| |
| if (!ignoreNextLayoutRequest) { |
| // We are currently in the middle of performing a layout, and the user (not our internal code) |
| // changed the preferred width of one of the split items, so request another layout. |
| d->requestLayoutView(); |
| } |
| |
| emit preferredWidthChanged(); |
| } |
| |
| void QQuickSplitViewAttached::resetPreferredWidth() |
| { |
| Q_D(QQuickSplitViewAttached); |
| const qreal oldEffectivePreferredWidth = effectivePreferredWidth( |
| d, QQuickItemPrivate::get(d->m_splitItem)); |
| |
| d->m_isPreferredWidthSet = false; |
| d->m_preferredWidth = -1; |
| |
| const qreal newEffectivePreferredWidth = effectivePreferredWidth( |
| d, QQuickItemPrivate::get(d->m_splitItem)); |
| if (qFuzzyCompare(newEffectivePreferredWidth, oldEffectivePreferredWidth)) |
| return; |
| |
| d->requestLayoutView(); |
| emit preferredWidthChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty real QtQuick.Controls::SplitView::preferredHeight |
| |
| This attached property controls the preferred height of the split item. The |
| preferred height will be used as the size of the item, and will be bound |
| within the \l minimumHeight and \l maximumHeight. If the preferred height |
| is not set, the item's \l{Item::}{implicitHeight} will be used. |
| |
| When a split item is resized, the preferredHeight will be set in order |
| to keep track of the new size. |
| |
| By default, this property is not set, and therefore |
| \l{Item::}{implicitHeight} will be used instead. To reset this property to |
| its default value, set it to \c undefined. |
| |
| \note Do not set the \l{Item:}{height} property of a split item, as it will be |
| overwritten upon each layout of the SplitView. |
| |
| \sa minimumHeight, maximumHeight, fillHeight, preferredWidth |
| */ |
| qreal QQuickSplitViewAttached::preferredHeight() const |
| { |
| Q_D(const QQuickSplitViewAttached); |
| return d->m_preferredHeight; |
| } |
| |
| void QQuickSplitViewAttached::setPreferredHeight(qreal height) |
| { |
| Q_D(QQuickSplitViewAttached); |
| d->m_isPreferredHeightSet = true; |
| // Make sure that we clear this flag now, before we emit the change signals |
| // which could cause another setter to be called. |
| auto splitViewPrivate = d->m_splitView ? QQuickSplitViewPrivate::get(d->m_splitView) : nullptr; |
| const bool ignoreNextLayoutRequest = splitViewPrivate && splitViewPrivate->m_ignoreNextLayoutRequest; |
| if (splitViewPrivate) |
| splitViewPrivate->m_ignoreNextLayoutRequest = false; |
| |
| if (qFuzzyCompare(height, d->m_preferredHeight)) |
| return; |
| |
| d->m_preferredHeight = height; |
| |
| if (!ignoreNextLayoutRequest) { |
| // We are currently in the middle of performing a layout, and the user (not our internal code) |
| // changed the preferred height of one of the split items, so request another layout. |
| d->requestLayoutView(); |
| } |
| |
| emit preferredHeightChanged(); |
| } |
| |
| void QQuickSplitViewAttached::resetPreferredHeight() |
| { |
| Q_D(QQuickSplitViewAttached); |
| const qreal oldEffectivePreferredHeight = effectivePreferredHeight( |
| d, QQuickItemPrivate::get(d->m_splitItem)); |
| |
| d->m_isPreferredHeightSet = false; |
| d->m_preferredHeight = -1; |
| |
| const qreal newEffectivePreferredHeight = effectivePreferredHeight( |
| d, QQuickItemPrivate::get(d->m_splitItem)); |
| if (qFuzzyCompare(newEffectivePreferredHeight, oldEffectivePreferredHeight)) |
| return; |
| |
| d->requestLayoutView(); |
| emit preferredHeightChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty real QtQuick.Controls::SplitView::maximumWidth |
| |
| This attached property controls the maximum width of the split item. |
| The \l preferredWidth is bound within the \l minimumWidth and |
| \l maximumWidth. A split item cannot be dragged to be larger than |
| its \c maximumWidth. |
| |
| The default value is \c Infinity. To reset this property to its default |
| value, set it to \c undefined. |
| |
| \sa minimumWidth, preferredWidth, fillWidth, maximumHeight |
| */ |
| qreal QQuickSplitViewAttached::maximumWidth() const |
| { |
| Q_D(const QQuickSplitViewAttached); |
| return d->m_maximumWidth; |
| } |
| |
| void QQuickSplitViewAttached::setMaximumWidth(qreal width) |
| { |
| Q_D(QQuickSplitViewAttached); |
| d->m_isMaximumWidthSet = true; |
| if (qFuzzyCompare(width, d->m_maximumWidth)) |
| return; |
| |
| d->m_maximumWidth = width; |
| d->requestLayoutView(); |
| emit maximumWidthChanged(); |
| } |
| |
| void QQuickSplitViewAttached::resetMaximumWidth() |
| { |
| Q_D(QQuickSplitViewAttached); |
| const qreal oldEffectiveMaximumWidth = effectiveMaximumWidth(d); |
| |
| d->m_isMaximumWidthSet = false; |
| d->m_maximumWidth = -1; |
| |
| const qreal newEffectiveMaximumWidth = effectiveMaximumWidth(d); |
| if (qFuzzyCompare(newEffectiveMaximumWidth, oldEffectiveMaximumWidth)) |
| return; |
| |
| d->requestLayoutView(); |
| emit maximumWidthChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty real QtQuick.Controls::SplitView::maximumHeight |
| |
| This attached property controls the maximum height of the split item. |
| The \l preferredHeight is bound within the \l minimumHeight and |
| \l maximumHeight. A split item cannot be dragged to be larger than |
| its \c maximumHeight. |
| |
| The default value is \c Infinity. To reset this property to its default |
| value, set it to \c undefined. |
| |
| \sa minimumHeight, preferredHeight, fillHeight, maximumWidth |
| */ |
| qreal QQuickSplitViewAttached::maximumHeight() const |
| { |
| Q_D(const QQuickSplitViewAttached); |
| return d->m_maximumHeight; |
| } |
| |
| void QQuickSplitViewAttached::setMaximumHeight(qreal height) |
| { |
| Q_D(QQuickSplitViewAttached); |
| d->m_isMaximumHeightSet = true; |
| if (qFuzzyCompare(height, d->m_maximumHeight)) |
| return; |
| |
| d->m_maximumHeight = height; |
| d->requestLayoutView(); |
| emit maximumHeightChanged(); |
| } |
| |
| void QQuickSplitViewAttached::resetMaximumHeight() |
| { |
| Q_D(QQuickSplitViewAttached); |
| const qreal oldEffectiveMaximumHeight = effectiveMaximumHeight(d); |
| |
| d->m_isMaximumHeightSet = false; |
| d->m_maximumHeight = -1; |
| |
| const qreal newEffectiveMaximumHeight = effectiveMaximumHeight(d); |
| if (qFuzzyCompare(newEffectiveMaximumHeight, oldEffectiveMaximumHeight)) |
| return; |
| |
| d->requestLayoutView(); |
| emit maximumHeightChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty bool QtQuick.Controls::SplitView::fillWidth |
| |
| This attached property controls whether the item takes the remaining space |
| in the split view after all other items have been laid out. |
| |
| By default, the last visible child of the split view will have this set, |
| but it can be changed by explicitly setting \c fillWidth to \c true on |
| another item. |
| |
| The width of a split item with \c fillWidth set to \c true is still |
| restricted within its \l minimumWidth and \l maximumWidth. |
| |
| \sa minimumWidth, preferredWidth, maximumWidth, fillHeight |
| */ |
| bool QQuickSplitViewAttached::fillWidth() const |
| { |
| Q_D(const QQuickSplitViewAttached); |
| return d->m_fillWidth; |
| } |
| |
| void QQuickSplitViewAttached::setFillWidth(bool fill) |
| { |
| Q_D(QQuickSplitViewAttached); |
| d->m_isFillWidthSet = true; |
| if (fill == d->m_fillWidth) |
| return; |
| |
| d->m_fillWidth = fill; |
| if (d->m_splitView && d->m_splitView->orientation() == Qt::Horizontal) |
| QQuickSplitViewPrivate::get(d->m_splitView)->updateFillIndex(); |
| d->requestLayoutView(); |
| emit fillWidthChanged(); |
| } |
| |
| /*! |
| \qmlattachedproperty bool QtQuick.Controls::SplitView::fillHeight |
| |
| This attached property controls whether the item takes the remaining space |
| in the split view after all other items have been laid out. |
| |
| By default, the last visible child of the split view will have this set, |
| but it can be changed by explicitly setting \c fillHeight to \c true on |
| another item. |
| |
| The height of a split item with \c fillHeight set to \c true is still |
| restricted within its \l minimumHeight and \l maximumHeight. |
| |
| \sa minimumHeight, preferredHeight, maximumHeight, fillWidth |
| */ |
| bool QQuickSplitViewAttached::fillHeight() const |
| { |
| Q_D(const QQuickSplitViewAttached); |
| return d->m_fillHeight; |
| } |
| |
| void QQuickSplitViewAttached::setFillHeight(bool fill) |
| { |
| Q_D(QQuickSplitViewAttached); |
| d->m_isFillHeightSet = true; |
| if (fill == d->m_fillHeight) |
| return; |
| |
| d->m_fillHeight = fill; |
| if (d->m_splitView && d->m_splitView->orientation() == Qt::Vertical) |
| QQuickSplitViewPrivate::get(d->m_splitView)->updateFillIndex(); |
| d->requestLayoutView(); |
| emit fillHeightChanged(); |
| } |
| |
| QQuickSplitViewAttachedPrivate::QQuickSplitViewAttachedPrivate() |
| : m_fillWidth(false) |
| , m_fillHeight(false) |
| , m_isFillWidthSet(false) |
| , m_isFillHeightSet(false) |
| , m_isMinimumWidthSet(false) |
| , m_isMinimumHeightSet(false) |
| , m_isPreferredWidthSet(false) |
| , m_isPreferredHeightSet(false) |
| , m_isMaximumWidthSet(false) |
| , m_isMaximumHeightSet(false) |
| , 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()) |
| { |
| } |
| |
| void QQuickSplitViewAttachedPrivate::setView(QQuickSplitView *newView) |
| { |
| Q_Q(QQuickSplitViewAttached); |
| if (newView == m_splitView) |
| return; |
| |
| m_splitView = newView; |
| qCDebug(qlcQQuickSplitView) << "set SplitView" << newView << "on attached object" << this; |
| emit q->viewChanged(); |
| } |
| |
| void QQuickSplitViewAttachedPrivate::requestLayoutView() |
| { |
| if (m_splitView) |
| QQuickSplitViewPrivate::get(m_splitView)->requestLayout(); |
| } |
| |
| QQuickSplitViewAttachedPrivate *QQuickSplitViewAttachedPrivate::get(QQuickSplitViewAttached *attached) |
| { |
| return attached->d_func(); |
| } |
| |
| const QQuickSplitViewAttachedPrivate *QQuickSplitViewAttachedPrivate::get(const QQuickSplitViewAttached *attached) |
| { |
| return attached->d_func(); |
| } |
| |
| QQuickSplitHandleAttachedPrivate::QQuickSplitHandleAttachedPrivate() |
| : m_hovered(false) |
| , m_pressed(false) |
| { |
| } |
| |
| void QQuickSplitHandleAttachedPrivate::setHovered(bool hovered) |
| { |
| Q_Q(QQuickSplitHandleAttached); |
| if (hovered == m_hovered) |
| return; |
| |
| m_hovered = hovered; |
| emit q->hoveredChanged(); |
| } |
| |
| void QQuickSplitHandleAttachedPrivate::setPressed(bool pressed) |
| { |
| Q_Q(QQuickSplitHandleAttached); |
| if (pressed == m_pressed) |
| return; |
| |
| m_pressed = pressed; |
| emit q->pressedChanged(); |
| } |
| |
| QQuickSplitHandleAttachedPrivate *QQuickSplitHandleAttachedPrivate::get(QQuickSplitHandleAttached *attached) |
| { |
| return attached->d_func(); |
| } |
| |
| const QQuickSplitHandleAttachedPrivate *QQuickSplitHandleAttachedPrivate::get(const QQuickSplitHandleAttached *attached) |
| { |
| return attached->d_func(); |
| } |
| |
| QQuickSplitHandleAttached::QQuickSplitHandleAttached(QObject *parent) |
| : QObject(*(new QQuickSplitViewAttachedPrivate), parent) |
| { |
| } |
| |
| /*! |
| \qmltype SplitHandle |
| \inherits QtObject |
| //! \instantiates QQuickSplitHandleAttached |
| \inqmlmodule QtQuick.Controls |
| \since 5.13 |
| \brief Provides attached properties for SplitView handles. |
| |
| SplitHandle provides attached properties for \l SplitView handles. |
| |
| For split items themselves, use the attached \l SplitView properties. |
| |
| \sa SplitView |
| */ |
| |
| /*! |
| \qmlattachedproperty bool QtQuick.Controls::SplitHandle::hovered |
| |
| This attached property holds whether the split handle is hovered. |
| |
| \sa pressed |
| */ |
| bool QQuickSplitHandleAttached::isHovered() const |
| { |
| Q_D(const QQuickSplitHandleAttached); |
| return d->m_hovered; |
| } |
| |
| /*! |
| \qmlattachedproperty bool QtQuick.Controls::SplitHandle::pressed |
| |
| This attached property holds whether the split handle is pressed. |
| |
| \sa hovered |
| */ |
| bool QQuickSplitHandleAttached::isPressed() const |
| { |
| Q_D(const QQuickSplitHandleAttached); |
| return d->m_pressed; |
| } |
| |
| QQuickSplitHandleAttached *QQuickSplitHandleAttached::qmlAttachedProperties(QObject *object) |
| { |
| return new QQuickSplitHandleAttached(object); |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qquicksplitview_p.cpp" |