// 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"
