diff options
Diffstat (limited to 'src/quicktemplates/qquicksplitview.cpp')
-rw-r--r-- | src/quicktemplates/qquicksplitview.cpp | 2215 |
1 files changed, 2215 insertions, 0 deletions
diff --git a/src/quicktemplates/qquicksplitview.cpp b/src/quicktemplates/qquicksplitview.cpp new file mode 100644 index 0000000000..1d41016f94 --- /dev/null +++ b/src/quicktemplates/qquicksplitview.cpp @@ -0,0 +1,2215 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#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 Container +//! \instantiates QQuickSplitView + \inqmlmodule QtQuick.Controls + \since 5.13 + \ingroup qtquickcontrols-containers + \ingroup qtquickcontrols-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 + import QtCore + import QtQuick.Controls + + 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 + import QtCore + import QtQuick.Controls + + 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(qlcQQuickSplitViewPointer, "qt.quick.controls.splitview.pointer") +Q_LOGGING_CATEGORY(qlcQQuickSplitViewState, "qt.quick.controls.splitview.state") + +/* + Updates m_fillIndex to be between 0 .. (item count - 1). +*/ +void QQuickSplitViewPrivate::updateFillIndex() +{ + const int count = contentModel->count(); + const bool horizontal = isHorizontal(); + + qCDebug(qlcQQuickSplitView) << "looking for fillWidth/Height item amongst" << count << "items"; + + int fillIndex = -1; + int lastVisibleIndex = -1; + for (int i = 0; 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())) { + fillIndex = i; + qCDebug(qlcQQuickSplitView) << "found fillWidth/Height item at index" << fillIndex; + break; + } + } + + if (fillIndex == -1) { + // If there was no item with fillWidth/fillHeight set, fillIndex will be -1, + // and we'll set m_fillIndex to the last visible item. + // If there was an item with fillWidth/fillHeight set, we were already done and this will be skipped. + fillIndex = lastVisibleIndex != -1 ? lastVisibleIndex : count - 1; + qCDebug(qlcQQuickSplitView) << "found no fillWidth/Height item; using last item at index" << fillIndex; + } + // Take new fillIndex into use. + m_fillIndex = 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_nextVisibleIndexAfterPressedHandle)); + 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(), qreal(width)) + : qBound(qreal(0.0), m_mousePos.y(), qreal(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_nextVisibleIndexAfterPressedHandle : 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; + for (int i = m_pressedHandleIndex - 1; i >= 0; --i) { + const QQuickItem *nextHandleToTheLeft = m_handleItems.at(i); + if (nextHandleToTheLeft->isVisible()) { + leftEdge = horizontal + ? nextHandleToTheLeft->x() + nextHandleToTheLeft->width() + : nextHandleToTheLeft->y() + nextHandleToTheLeft->height(); + break; + } + } + + // 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; + + // We still need to use requestedSize in the width/height 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=" << 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_nextVisibleIndexAfterPressedHandle < m_handleItems.size()) { + const QQuickItem *rightHandle = m_handleItems.at(m_nextVisibleIndexAfterPressedHandle); + 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); + + // We still need to use requestedSize in the width/height 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) { + LayoutData layoutData; + if (horizontal) { + layoutData.width = qBound( + sizeData.effectiveMinimumWidth, + requestedSize, + sizeData.effectiveMaximumWidth); + layoutData.height = height; + } else { + layoutData.width = width; + layoutData.height = qBound( + sizeData.effectiveMinimumHeight, + requestedSize, + sizeData.effectiveMaximumHeight); + } + + // Mark that this item has been manually resized. After this + // we can override the preferredWidth & preferredHeight + if (isBeingResized) + layoutData.wasResizedByHandle = true; + + m_layoutData.insert(item, layoutData); + + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": calculated the following size data for split item " << item + << ": eminW=" << sizeData.effectiveMinimumWidth + << ", eminH=" << sizeData.effectiveMinimumHeight + << ", eprfW=" << sizeData.effectivePreferredWidth + << ", eprfH=" << sizeData.effectivePreferredHeight + << ", emaxW=" << sizeData.effectiveMaximumWidth + << ", emaxH=" << sizeData.effectiveMaximumHeight + << ", w=" << layoutData.width + << ", h=" << layoutData.height << ""; + + // Keep track of how much space has been used so far. + if (horizontal) + usedWidth += layoutData.width; + else + usedHeight += layoutData.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); + + LayoutData layoutData; + if (isHorizontal()) { + layoutData.width = qBound( + fillSizeData.effectiveMinimumWidth, + width - usedWidth, + fillSizeData.effectiveMaximumWidth); + layoutData.height = height; + usedWidth += layoutData.width; + } else { + layoutData.width = width; + layoutData.height = qBound( + fillSizeData.effectiveMinimumHeight, + height - usedHeight, + fillSizeData.effectiveMaximumHeight); + usedHeight += layoutData.height; + } + + m_layoutData.insert(fillItem, layoutData); + + qCDebug(qlcQQuickSplitView).nospace() << " - " << m_fillIndex + << ": resized split fill item " << fillItem << " (effective" + << " minW=" << fillSizeData.effectiveMinimumWidth + << ", minH=" << fillSizeData.effectiveMinimumHeight + << ", maxW=" << fillSizeData.effectiveMaximumWidth + << ", maxH=" << fillSizeData.effectiveMaximumHeight << ")"; +} + +/* + Limit the sizes if needed and apply them into items. +*/ +void QQuickSplitViewPrivate::limitAndApplySizes(qreal usedWidth, qreal usedHeight) +{ + const int count = contentModel->count(); + const bool horizontal = isHorizontal(); + + const qreal maxSize = horizontal ? width : height; + const qreal usedSize = horizontal ? usedWidth : usedHeight; + if (usedSize > maxSize) { + qCDebug(qlcQQuickSplitView).nospace() << "usedSize " << usedSize << " is greater than maxSize " + << maxSize << "; reducing size of non-filled items from right to left / bottom to top"; + + // If items don't fit, reduce the size of non-filled items from + // right to left / bottom to top. At this point filled item is + // already at its minimum size or usedSize wouldn't be > maxSize. + qreal delta = usedSize - maxSize; + for (int index = count - 1; index >= 0; --index) { + if (index == m_fillIndex) + continue; + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(index)); + if (!item->isVisible()) + continue; + + const QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + const auto sizeData = effectiveSizeData(itemPrivate, attached); + const qreal maxReduce = horizontal ? + m_layoutData[item].width - sizeData.effectiveMinimumWidth : + m_layoutData[item].height - sizeData.effectiveMinimumHeight; + + const qreal reduce = std::min(maxReduce, delta); + if (horizontal) + m_layoutData[item].width -= reduce; + else + m_layoutData[item].height -= reduce; + + delta -= reduce; + if (delta <= 0) { + // Now all the items fit, so continue + break; + } + } + } + + qCDebug(qlcQQuickSplitView).nospace() << " applying new sizes to " << count << " items (excluding hidden items)"; + + // Apply the new sizes into items + for (int index = 0; index < count; ++index) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(index)); + if (!item->isVisible()) + continue; + + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + LayoutData layoutData = m_layoutData.value(item); + if (layoutData.wasResizedByHandle) { + // Modify the preferredWidth/Height, otherwise the original implicit/preferred size + // 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, otherwise the original implicit/preferred sizes + will be used on the next layout (when it's no longer being resized). + 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(layoutData.width); + else + attached->setPreferredHeight(layoutData.height); + } + + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized item " << item << " from " + << item->width() << "x" << item->height() << " to " + << layoutData.width << "x" << layoutData.height; + + item->setWidth(layoutData.width); + item->setHeight(layoutData.height); + } +} + +/* + 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(); +} + +/* + Layout steps are (horizontal SplitView as an example): + 1) layoutResizeSplitItems: Gives each non-filled item its preferredWidth + or if not set, implicitWidth. Sizes are kept between effectiveMinimumWidth + and effectiveMaximumWidth and stored into layoutData for now. + 2) layoutResizeFillItem: Gives filled item all the remaining space. Size is + kept between effectiveMinimumWidth and effectiveMaximumWidth and stored + into layoutData for now. + 3) limitAndApplySizes: If we have used more space than SplitView item has, + start reducing non-filled item sizes from right-to-left. Reduce them up + to minimumWidth or until SplitView item width is reached. Finally set the + new item sizes from layoutData. +*/ +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(); + + // Total sizes of items used during the layout operation. + qreal usedWidth = 0; + qreal usedHeight = 0; + int indexBeingResizedDueToDrag = -1; + m_layoutData.clear(); + + qCDebug(qlcQQuickSplitView) << " resizing:"; + + // First, resize the non-filled 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); + + // Reduce the sizes still if needed and apply them into items. + limitAndApplySizes(usedWidth, usedHeight); + + 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 *context = 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 (!context) + context = qmlContext(q); + QQuickItem *handleItem = qobject_cast<QQuickItem*>(m_handle->beginCreate(context)); + if (handleItem) { + handleItem->setParent(q); + qCDebug(qlcQQuickSplitView) << "- successfully created handle item" << handleItem << "for split item at index" << index; + + // 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, handleItem); + + handleItem->setParentItem(q); + // Handles must have priority for press events, so we need to set this. + handleItem->setAcceptedMouseButtons(Qt::LeftButton); + handleItem->setKeepMouseGrab(true); +#if QT_CONFIG(cursor) + updateCursorHandle(handleItem); +#endif + m_handle->completeCreate(); + resizeHandle(handleItem); + } +} + +void QQuickSplitViewPrivate::removeExcessHandles() +{ + int excess = m_handleItems.size() - qMax(0, contentModel->count() - 1); + qCDebug(qlcQQuickSplitView) << "removing" << excess << "excess handles from the end of our list"; + 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() +{ + qCDebug(qlcQQuickSplitView) << "destroying" << m_handleItems.size() << "handles"; + 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); +} + +#if QT_CONFIG(cursor) +void QQuickSplitViewPrivate::updateCursorHandle(QQuickItem *handleItem) +{ + handleItem->setCursor(isHorizontal() ? Qt::SplitHCursor : Qt::SplitVCursor); +} +#endif + +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" << handleItem << "at index" + << i << "to" << handleItem->isVisible(); + } +} + +void QQuickSplitViewPrivate::updateHoveredHandle(QQuickItem *hoveredItem) +{ + qCDebug(qlcQQuickSplitViewPointer) << "updating hovered handle after" << hoveredItem << "was hovered"; + + 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(qlcQQuickSplitViewPointer) << "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(qlcQQuickSplitViewPointer) << "handle item at index" << m_hoveredHandleIndex << "is now hovered"; + } else { + qCDebug(qlcQQuickSplitViewPointer) << "either there is no hovered item or" << hoveredItem << "is not a handle"; + } +} + +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); +} + +bool QQuickSplitViewPrivate::handlePress(const QPointF &point, ulong timestamp) +{ + Q_Q(QQuickSplitView); + QQuickContainerPrivate::handlePress(point, timestamp); + + 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)); + // Find the first item to the right/bottom of this one that is visible. + QQuickItem *rightOrBottomItem = nullptr; + m_nextVisibleIndexAfterPressedHandle = -1; + for (int i = m_pressedHandleIndex + 1; i < contentModel->count(); ++i) { + auto nextItem = qobject_cast<QQuickItem*>(contentModel->object(i)); + if (nextItem->isVisible()) { + rightOrBottomItem = nextItem; + m_nextVisibleIndexAfterPressedHandle = i; + break; + } + } + Q_ASSERT_X(rightOrBottomItem, Q_FUNC_INFO, qPrintable(QString::fromLatin1( + "Failed to find a visible item to the right/bottom of the one that was pressed at index %1; this shouldn't happen") + .arg(m_pressedHandleIndex))); + + 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(); + + + // 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(qlcQQuickSplitViewPointer).nospace() << "handled press -" + << " left/top index=" << m_pressedHandleIndex << "," + << " size before press=" << m_leftOrTopItemSizeBeforePress << "," + << " item=" << leftOrTopItem + << " right/bottom index=" << m_nextVisibleIndexAfterPressedHandle << "," + << " size before press=" << m_rightOrBottomItemSizeBeforePress + << " item=" << rightOrBottomItem; + } + return true; +} + +bool QQuickSplitViewPrivate::handleMove(const QPointF &point, ulong timestamp) +{ + QQuickContainerPrivate::handleMove(point, timestamp); + + 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(); + } + return true; +} + +bool QQuickSplitViewPrivate::handleRelease(const QPointF &point, ulong timestamp) +{ + QQuickContainerPrivate::handleRelease(point, timestamp); + + 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; + return true; +} + +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; + + setFiltersChildMouseEvents(true); +} + +QQuickSplitView::QQuickSplitView(QQuickSplitViewPrivate &dd, QQuickItem *parent) + : QQuickContainer(dd, parent) +{ + Q_D(QQuickSplitView); + d->changeTypes |= QQuickItemPrivate::Visibility; + + 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; + +#if QT_CONFIG(cursor) + for (QQuickItem *handleItem : d->m_handleItems) + d->updateCursorHandle(handleItem); +#endif + emit orientationChanged(); + + // Do this after emitting orientationChanged so that the bindings in QML + // update the implicit size in time. + d->resizeHandles(); + // This is queued (via polish) anyway, but to make our intentions clear, + // do it afterwards too. + d->requestLayout(); +} + +/*! + \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 + + To change the size of the handle for mouse and touch events without + changing its visual size, use a \l {Item::}{containmentMask}: + + \snippet qtquickcontrols-splitview-handle-containmentmask.qml 1 + + \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->updateHandleVisibilities(); + } + + 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() +{ +#if QT_CONFIG(cborstreamwriter) + 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); +#else + return QVariant(); +#endif +} + +/*! + \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->updateFillIndex(); + d->updatePolish(); +} + +void QQuickSplitView::hoverMoveEvent(QHoverEvent *event) +{ + Q_D(QQuickSplitView); + QQuickContainer::hoverMoveEvent(event); + + QQuickItem *hoveredItem = childAt(event->position().toPoint().x(), event->position().toPoint().y()); + d->updateHoveredHandle(hoveredItem); +} + +void QQuickSplitView::hoverLeaveEvent(QHoverEvent *event) +{ + Q_UNUSED(event); + Q_D(QQuickSplitView); + // If SplitView is no longer hovered (e.g. visible set to false), clear handle hovered value + d->updateHoveredHandle(nullptr); +} + +bool QQuickSplitView::childMouseEventFilter(QQuickItem *item, QEvent *event) +{ + Q_D(QQuickSplitView); + qCDebug(qlcQQuickSplitViewPointer) << "childMouseEventFilter called with" << item << event; + + if (Q_LIKELY(event->isPointerEvent())) { + auto *pointerEvent = static_cast<QPointerEvent *>(event); + const auto &eventPoint = pointerEvent->points().first(); + const QPointF point = mapFromItem(item, eventPoint.position()); + const auto timestamp = pointerEvent->timestamp(); + + switch (event->type()) { + case QEvent::MouseButtonPress: + d->handlePress(point, timestamp); + // Keep the mouse grab if this item belongs to the handle, + // otherwise this event can be stolen e.g. Flickable if we're inside it. + if (d->m_pressedHandleIndex != -1) + item->setKeepMouseGrab(true); + break; + case QEvent::MouseButtonRelease: + d->handleRelease(point, timestamp); + break; + case QEvent::MouseMove: + d->handleMove(point, timestamp); + break; + case QEvent::TouchBegin: + if (pointerEvent->pointCount() == 1) { + d->handlePress(point, timestamp); + // We filter the event on behalf of item, but we want the item + // to be the exclusive grabber so that we can continue to filter + // touch events for it. + if (d->m_pressedHandleIndex != -1) { + item->setKeepTouchGrab(true); + pointerEvent->setExclusiveGrabber(eventPoint, item); + } + } + break; + case QEvent::TouchEnd: + if (pointerEvent->pointCount() == 1) + d->handleRelease(point, timestamp); + break; + case QEvent::TouchUpdate: + if (pointerEvent->pointCount() == 1) + d->handleMove(point, timestamp); + break; + default: + break; + } + } + + // If this event belongs to the handle, filter it. (d->m_pressedHandleIndex != -1) means that + // we press or move the handle, so we don't need to propagate it further. + if (d->m_pressedHandleIndex != -1) + return true; + + return QQuickContainer::childMouseEventFilter(item, event); +} + +void QQuickSplitView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickSplitView); + QQuickControl::geometryChange(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); + } + + 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 fill the view, + but it can be changed by explicitly setting \c fillWidth to \c true on + another item. If multiple items have \c fillWidth set to \c true, the + left-most item will fill the view. + + 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 fill the view, + but it can be changed by explicitly setting \c fillHeight to \c true on + another item. If multiple items have \c fillHeight set to \c true, the + top-most item will fill the view. + + 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 QQuickSplitHandleAttachedPrivate), 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" |