// Copyright (C) 2017 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 "qquickstackview_p.h"
#include "qquickstackview_p_p.h"
#include "qquickstackelement_p_p.h"
#if QT_CONFIG(quick_viewtransitions)
#include "qquickstacktransition_p_p.h"
#endif

#include <QtCore/qscopedvaluerollback.h>
#include <QtQml/qjsvalue.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlinfo.h>

#include <private/qv4qobjectwrapper_p.h>
#include <private/qqmlengine_p.h>

QT_BEGIN_NAMESPACE

/*!
    \qmltype StackView
    \inherits Control
//!     \instantiates QQuickStackView
    \inqmlmodule QtQuick.Controls
    \since 5.7
    \ingroup qtquickcontrols-navigation
    \ingroup qtquickcontrols-containers
    \ingroup qtquickcontrols-focusscopes
    \brief Provides a stack-based navigation model.

    \image qtquickcontrols-stackview-wireframe.png

    StackView can be used with a set of inter-linked information pages. For
    example, an email application with separate views to list the latest emails,
    view a specific email, and list/view the attachments. The email list view
    is pushed onto the stack as users open an email, and popped out as they
    choose to go back.

    The following snippet demonstrates a simple use case, where the \c mainView
    is pushed onto and popped out of the stack on relevant button click:

    \qml
    ApplicationWindow {
        title: qsTr("Hello World")
        width: 640
        height: 480
        visible: true

        StackView {
            id: stack
            initialItem: mainView
            anchors.fill: parent
        }

        Component {
            id: mainView

            Row {
                spacing: 10

                Button {
                    text: "Push"
                    onClicked: stack.push(mainView)
                }
                Button {
                    text: "Pop"
                    enabled: stack.depth > 1
                    onClicked: stack.pop()

                }
                Text {
                    text: stack.depth
                }
            }
        }
    }
    \endqml

    \section1 Using StackView in an Application

    Using StackView in an application is as simple as adding it as a child to
    a Window. The stack is usually anchored to the edges of the window, except
    at the top or bottom where it might be anchored to a status bar, or some
    other similar UI component. The stack can then be used by invoking its
    navigation methods. The first item to show in the StackView is the one
    that was assigned to \l initialItem, or the topmost item if \l initialItem
    is not set.

    \section1 Basic Navigation

    StackView supports three primary navigation operations: push(), pop(), and
    replace(). These correspond to classic stack operations where "push" adds
    an item to the top of a stack, "pop" removes the top item from the
    stack, and "replace" is like a pop followed by a push, which replaces the
    topmost item with the new item. The topmost item in the stack
    corresponds to the one that is \l{StackView::currentItem}{currently}
    visible on screen. Logically, "push" navigates forward or deeper into the
    application UI, "pop" navigates backward, and "replace" replaces the
    \l currentItem.

    \section2 Pushing Items

    In the following animation, three \l Label controls are pushed onto a
    stack view with the \l push() function:

    \image qtquickcontrols-stackview-push.gif

    The stack now contains the following items: \c [A, B, C].

    \note When the stack is empty, a push() operation will not have a
    transition animation because there is nothing to transition from (typically
    on application start-up).

    \section2 Popping Items

    Continuing on from the example above, the topmost item on the stack is
    removed with a call to \l pop():

    \image qtquickcontrols-stackview-pop.gif

    The stack now contains the following items: \c [A, B].

    \note A pop() operation on a stack with depth 1 or 0 does nothing. In such
    cases, the stack can be emptied using the \l clear() method.

    \section3 Unwinding Items via Pop

    Sometimes, it is necessary to go back more than a single step in the stack.
    For example, to return to a "main" item or some kind of section item in the
    application. In such cases, it is possible to specify an item as a
    parameter for pop(). This is called an "unwind" operation, where the stack
    unwinds till the specified item. If the item is not found, stack unwinds
    until it is left with one item, which becomes the \l currentItem. To
    explicitly unwind to the bottom of the stack, it is recommended to use
    \l{pop()}{pop(null)}, although any non-existent item will do.

    In the following animation, we unwind the stack to the first item by
    calling \c pop(null):

    \image qtquickcontrols-stackview-unwind.gif

    The stack now contains a single item: \c [A].

    \section2 Replacing Items

    In the following animation, we \l replace the topmost item with \c D:

    \image qtquickcontrols-stackview-replace.gif

    The stack now contains the following items: \c [A, B, D].

    \section1 Deep Linking

    \e{Deep linking} means launching an application into a particular state. For
    example, a newspaper application could be launched into showing a
    particular article, bypassing the topmost item. In terms of StackView, deep
    linking means the ability to modify the state of the stack, so much so that
    it is possible to push a set of items to the top of the stack, or to
    completely reset the stack to a given state.

    The API for deep linking in StackView is the same as for basic navigation.
    Pushing an array instead of a single item adds all the items in that array
    to the stack. The transition animation, however, is applied only for the
    last item in the array. The normal semantics of push() apply for deep
    linking, that is, it adds whatever is pushed onto the stack.

    \note Only the last item of the array is loaded. The rest of the items are
    loaded only when needed, either on subsequent calls to pop or on request to
    get an item using get().

    This gives us the following result, given the stack [A, B, C]:

    \list
    \li \l{push()}{push([D, E, F])} => [A, B, C, D, E, F] - "push" transition
        animation between C and F
    \li \l{replace()}{replace([D, E, F])} => [A, B, D, E, F] - "replace"
        transition animation between C and F
    \li \l{clear()} followed by \l{push()}{push([D, E, F])} => [D, E, F] - no
        transition animation for pushing items as the stack was empty.
    \endlist

    \section1 Finding Items

    An Item for which the application does not have a reference can be found
    by calling find(). The method needs a callback function, which is invoked
    for each item in the stack (starting at the top) until a match is found.
    If the callback returns \c true, find() stops and returns the matching
    item, otherwise \c null is returned.

    The code below searches the stack for an item named "order_id" and unwinds
    to that item.

    \badcode
    stackView.pop(stackView.find(function(item) {
        return item.name == "order_id";
    }));
    \endcode

    You can also get to an item in the stack using \l {get()}{get(index)}.

    \badcode
    previousItem = stackView.get(myItem.StackView.index - 1));
    \endcode

    \section1 Transitions

    For each push or pop operation, different transition animations are applied
    to entering and exiting items. These animations define how the entering item
    should animate in, and the exiting item should animate out. The animations
    can be customized by assigning different \l [QML] {Transition} {Transitions}
    for the \l pushEnter, \l pushExit, \l popEnter, \l popExit, replaceEnter,
    and \l replaceExit properties of StackView.

    \note The transition animations affect each others' transitional behavior.
    Customizing the animation for one and leaving the other may give unexpected
    results.

    The following snippet defines a simple fade transition for push and pop
    operations:

    \qml
    StackView {
        id: stackview
        anchors.fill: parent

        pushEnter: Transition {
            PropertyAnimation {
                property: "opacity"
                from: 0
                to:1
                duration: 200
            }
        }
        pushExit: Transition {
            PropertyAnimation {
                property: "opacity"
                from: 1
                to:0
                duration: 200
            }
        }
        popEnter: Transition {
            PropertyAnimation {
                property: "opacity"
                from: 0
                to:1
                duration: 200
            }
        }
        popExit: Transition {
            PropertyAnimation {
                property: "opacity"
                from: 1
                to:0
                duration: 200
            }
        }
    }
    \endqml

    \note Using anchors on the items added to a StackView is not supported.
          Typically push, pop, and replace transitions animate the position,
          which is not possible when anchors are applied. Notice that this
          only applies to the root of the item. Using anchors for its children
          works as expected.

    \section1 Item Ownership

    StackView only takes ownership of items that it creates itself. This means
    that any item pushed onto a StackView will never be destroyed by the
    StackView; only items that StackView creates from \l {Component}{Components}
    or \l [QML] {url}{URLs} are destroyed by the StackView. To illustrate this,
    the messages in the example below will only be printed when the StackView
    is destroyed, not when the items are popped off the stack:

    \qml
    Component {
        id: itemComponent

        Item {
            Component.onDestruction: print("Destroying second item")
        }
    }

    StackView {
        initialItem: Item {
            Component.onDestruction: print("Destroying initial item")
        }

        Component.onCompleted: push(itemComponent.createObject(window))
    }
    \endqml

    However, both of the items created from the URL and Component in the
    following example will be destroyed by the StackView when they are popped
    off of it:

    \qml
    Component {
        id: itemComponent

        Item {
            Component.onDestruction: print("Destroying second item")
        }
    }

    StackView {
        initialItem: "Item1.qml"

        Component.onCompleted: push(itemComponent)
    }
    \endqml

    \section1 Size

    StackView does not inherit an implicit size from items that are pushed onto
    it. This means that using it as the \l {Popup::}{contentItem} of a
    \l Dialog, for example, will not work as expected:

    \code
    Dialog {
        StackView {
            initialItem: Rectangle {
                width: 200
                height: 200
                color: "salmon"
            }
        }
    }
    \endcode

    There are several ways to ensure that StackView has a size in this
    situation:

    \list
        \li Set \l[QtQuick]{Item::}{implicitWidth} and
            \l[QtQuick]{Item::}{implicitHeight} on the StackView itself.
        \li Set \l[QtQuick]{Item::}{implicitWidth} and
            \l[QtQuick]{Item::}{implicitHeight} on the \l Rectangle.
        \li Set \l {Popup::}{contentWidth} and \l {Popup::}{contentHeight} on
            the Dialog.
        \li Give the Dialog a size.
    \endlist

    \sa {Customizing StackView}, {Navigating with StackView}, {Navigation Controls},
        {Container Controls}, {Focus Management in Qt Quick Controls}
*/

QQuickStackView::QQuickStackView(QQuickItem *parent)
    : QQuickControl(*(new QQuickStackViewPrivate), parent)
{
    setFlag(ItemIsFocusScope);
}

QQuickStackView::~QQuickStackView()
{
    Q_D(QQuickStackView);
#if QT_CONFIG(quick_viewtransitions)
    if (d->transitioner) {
        d->transitioner->setChangeListener(nullptr);
        delete d->transitioner;
    }
#endif
    qDeleteAll(d->removing);
    qDeleteAll(d->removed);
    qDeleteAll(d->elements);
}

QQuickStackViewAttached *QQuickStackView::qmlAttachedProperties(QObject *object)
{
    return new QQuickStackViewAttached(object);
}

/*!
    \qmlproperty bool QtQuick.Controls::StackView::busy
    \readonly
    This property holds whether a transition is running.
*/
bool QQuickStackView::isBusy() const
{
    Q_D(const QQuickStackView);
    return d->busy;
}

/*!
    \qmlproperty int QtQuick.Controls::StackView::depth
    \readonly
    This property holds the number of items currently pushed onto the stack.
*/
int QQuickStackView::depth() const
{
    Q_D(const QQuickStackView);
    return d->elements.size();
}

/*!
    \qmlproperty Item QtQuick.Controls::StackView::currentItem
    \readonly
    This property holds the current top-most item in the stack.
*/
QQuickItem *QQuickStackView::currentItem() const
{
    Q_D(const QQuickStackView);
    return d->currentItem;
}

/*!
    \qmlmethod Item QtQuick.Controls::StackView::get(index, behavior)

    Returns the item at position \a index in the stack, or \c null if the index
    is out of bounds.

    Supported \a behavior values:
    \value StackView.DontLoad The item is not forced to load (and \c null is returned if not yet loaded).
    \value StackView.ForceLoad The item is forced to load.
*/
QQuickItem *QQuickStackView::get(int index, LoadBehavior behavior)
{
    Q_D(QQuickStackView);
    QQuickStackElement *element = d->elements.value(index);
    if (element) {
        if (behavior == ForceLoad)
            element->load(this);
        return element->item;
    }
    return nullptr;
}

/*!
    \qmlmethod Item QtQuick.Controls::StackView::find(callback, behavior)

    Search for a specific item inside the stack. The \a callback function is called
    for each item in the stack (with the item and index as arguments) until the callback
    function returns \c true. The return value is the item found. For example:

    \code
    stackView.find(function(item, index) {
        return item.isTheOne
    })
    \endcode

    Supported \a behavior values:
    \value StackView.DontLoad Unloaded items are skipped (the callback function is not called for them).
    \value StackView.ForceLoad Unloaded items are forced to load.
*/
QQuickItem *QQuickStackView::find(const QJSValue &callback, LoadBehavior behavior)
{
    Q_D(QQuickStackView);
    QJSValue func(callback);
    QQmlEngine *engine = qmlEngine(this);
    if (!engine || !func.isCallable()) // TODO: warning?
        return nullptr;

    for (int i = d->elements.size() - 1; i >= 0; --i) {
        QQuickStackElement *element = d->elements.at(i);
        if (behavior == ForceLoad)
            element->load(this);
        if (element->item) {
            QJSValue rv = func.call(QJSValueList() << engine->newQObject(element->item) << i);
            if (rv.toBool())
                return element->item;
        }
    }

    return nullptr;
}

/*!
    \qmlmethod Item QtQuick.Controls::StackView::push(item, properties, operation)

    Pushes an \a item onto the stack using an optional \a operation, and
    optionally applies a set of \a properties on the item. The item can be
    an \l Item, \l Component, or a \l [QML] url. Returns the item that became
    current.

    StackView creates an instance automatically if the pushed item is a \l Component,
    or a \l [QML] url, and the instance will be destroyed when it is popped
    off the stack. See \l {Item Ownership} for more information.

    The optional \a properties argument specifies a map of initial
    property values for the pushed item. For dynamically created items, these values
    are applied before the creation is finalized. This is more efficient than setting
    property values after creation, particularly where large sets of property values
    are defined, and also allows property bindings to be set up (using \l{Qt::binding}
    {Qt.binding()}) before the item is created.

    Pushing a single item:
    \code
    stackView.push(rect)

    // or with properties:
    stackView.push(rect, {"color": "red"})
    \endcode

    Multiple items can be pushed at the same time either by passing them as
    additional arguments, or as an array. The last item becomes the current
    item. Each item can be followed by a set of properties to apply.

    Passing a variable amount of arguments:
    \code
    stackView.push(rect1, rect2, rect3)

    // or with properties:
    stackView.push(rect1, {"color": "red"}, rect2, {"color": "green"}, rect3, {"color": "blue"})
    \endcode

    Pushing an array of items:
    \code
    stackView.push([rect1, rect2, rect3])

    // or with properties:
    stackView.push([rect1, {"color": "red"}, rect2, {"color": "green"}, rect3, {"color": "blue"}])
    \endcode

    An \a operation can be optionally specified as the last argument. Supported
    operations:

    \value StackView.Immediate An immediate operation without transitions.
    \value StackView.PushTransition An operation with push transitions (since QtQuick.Controls 2.1).
    \value StackView.ReplaceTransition An operation with replace transitions (since QtQuick.Controls 2.1).
    \value StackView.PopTransition An operation with pop transitions (since QtQuick.Controls 2.1).

    If no operation is provided, \c Immediate will be used if the stack is
    empty, and \c PushTransition otherwise.

    \note Items that already exist in the stack are not pushed.

    \sa initialItem, {Pushing Items}
*/
void QQuickStackView::push(QQmlV4Function *args)
{
    Q_D(QQuickStackView);
    const QString operationName = QStringLiteral("push");
    if (d->modifyingElements) {
        d->warnOfInterruption(operationName);
        return;
    }

    QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true);
    QScopedValueRollback<QString> operationNameRollback(d->operation, operationName);
    if (args->length() <= 0) {
        d->warn(QStringLiteral("missing arguments"));
        args->setReturnValue(QV4::Encode::null());
        return;
    }

    QV4::ExecutionEngine *v4 = args->v4engine();
    QV4::Scope scope(v4);

#if QT_CONFIG(quick_viewtransitions)
    Operation operation = d->elements.isEmpty() ? Immediate : PushTransition;
    QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]);
    if (lastArg->isInt32())
        operation = static_cast<Operation>(lastArg->toInt32());
#endif

    QStringList errors;
    QList<QQuickStackElement *> elements = d->parseElements(0, args, &errors);
    // Remove any items that are already in the stack, as they can't be in two places at once.
    // not using erase_if, as we have to delete the elements first
    auto removeIt = std::remove_if(elements.begin(), elements.end(), [&](QQuickStackElement *element) {
        return element->item && d->findElement(element->item);
    });
    for (auto it = removeIt, end = elements.end(); it != end; ++it)
        delete *it;
    elements.erase(removeIt, elements.end());

    if (!errors.isEmpty() || elements.isEmpty()) {
        if (!errors.isEmpty()) {
            for (const QString &error : std::as_const(errors))
                d->warn(error);
        } else {
            d->warn(QStringLiteral("nothing to push"));
        }
        args->setReturnValue(QV4::Encode::null());
        return;
    }

#if QT_CONFIG(quick_viewtransitions)
    QQuickStackElement *exit = nullptr;
    if (!d->elements.isEmpty())
        exit = d->elements.top();
#endif

    int oldDepth = d->elements.size();
    if (d->pushElements(elements)) {
        d->depthChange(d->elements.size(), oldDepth);
        QQuickStackElement *enter = d->elements.top();
#if QT_CONFIG(quick_viewtransitions)
        d->startTransition(QQuickStackTransition::pushEnter(operation, enter, this),
                           QQuickStackTransition::pushExit(operation, exit, this),
                           operation == Immediate);
#endif
        d->setCurrentItem(enter);
    }

    if (d->currentItem) {
        QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(v4, d->currentItem));
        args->setReturnValue(rv->asReturnedValue());
    } else {
        args->setReturnValue(QV4::Encode::null());
    }
}

/*!
    \qmlmethod Item QtQuick.Controls::StackView::pop(item, operation)

    Pops one or more items off the stack. Returns the last item removed from the stack.

    If the \a item argument is specified, all items down to (but not
    including) \a item will be popped. If \a item is \c null, all
    items down to (but not including) the first item is popped.
    If not specified, only the current item is popped.

    \note A pop() operation on a stack with depth 1 or 0 does nothing. In such
    cases, the stack can be emptied using the \l clear() method.

    \include qquickstackview.qdocinc pop-ownership

    An \a operation can be optionally specified as the last argument. Supported
    operations:

    \value StackView.Immediate An immediate operation without transitions.
    \value StackView.PushTransition An operation with push transitions (since QtQuick.Controls 2.1).
    \value StackView.ReplaceTransition An operation with replace transitions (since QtQuick.Controls 2.1).
    \value StackView.PopTransition An operation with pop transitions (since QtQuick.Controls 2.1).

    If no operation is provided, \c PopTransition will be used.

    Examples:
    \code
    stackView.pop()
    stackView.pop(someItem, StackView.Immediate)
    stackView.pop(StackView.Immediate)
    stackView.pop(null)
    \endcode

    \sa clear(), {Popping Items}, {Unwinding Items via Pop}
*/
void QQuickStackView::pop(QQmlV4Function *args)
{
    Q_D(QQuickStackView);
    const QString operationName = QStringLiteral("pop");
    if (d->modifyingElements) {
        d->warnOfInterruption(operationName);
        args->setReturnValue(QV4::Encode::null());
        return;
    }

    QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true);
    QScopedValueRollback<QString> operationNameRollback(d->operation, operationName);
    int argc = args->length();
    if (d->elements.size() <= 1 || argc > 2) {
        if (argc > 2)
            d->warn(QStringLiteral("too many arguments"));
        args->setReturnValue(QV4::Encode::null());
        return;
    }

    int oldDepth = d->elements.size();
    QQuickStackElement *exit = d->elements.pop();
    QQuickStackElement *enter = d->elements.top();

    QV4::ExecutionEngine *v4 = args->v4engine();
    QV4::Scope scope(v4);

    if (argc > 0) {
        QV4::ScopedValue value(scope, (*args)[0]);
        if (value->isNull()) {
            enter = d->elements.value(0);
        } else if (const QV4::QObjectWrapper *o = value->as<QV4::QObjectWrapper>()) {
            QQuickItem *item = qobject_cast<QQuickItem *>(o->object());
            enter = d->findElement(item);
            if (!enter) {
                if (item != d->currentItem)
                    d->warn(QStringLiteral("unknown argument: ") + value->toQString()); // TODO: safe?
                args->setReturnValue(QV4::Encode::null());
                d->elements.push(exit); // restore
                return;
            }
        }
    }

#if QT_CONFIG(quick_viewtransitions)
    Operation operation = PopTransition;
    if (argc > 0) {
        QV4::ScopedValue lastArg(scope, (*args)[argc - 1]);
        if (lastArg->isInt32())
            operation = static_cast<Operation>(lastArg->toInt32());
    }
#endif

    QPointer<QQuickItem> previousItem;

    if (d->popElements(enter)) {
        if (exit) {
            exit->removal = true;
            d->removing.insert(exit);
            previousItem = exit->item;
        }
        d->depthChange(d->elements.size(), oldDepth);
#if QT_CONFIG(quick_viewtransitions)
        d->startTransition(QQuickStackTransition::popExit(operation, exit, this),
                           QQuickStackTransition::popEnter(operation, enter, this),
                           operation == Immediate);
#endif
        d->setCurrentItem(enter);
    }

    if (previousItem) {
        QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(v4, previousItem));
        args->setReturnValue(rv->asReturnedValue());
    } else {
        args->setReturnValue(QV4::Encode::null());
    }
}

/*!
    \qmlmethod Item QtQuick.Controls::StackView::replace(target, item, properties, operation)

    Replaces one or more items on the stack with the specified \a item and
    optional \a operation, and optionally applies a set of \a properties on the
    item. The item can be an \l Item, \l Component, or a \l [QML] url.
    Returns the item that became current.

    \include qquickstackview.qdocinc pop-ownership

    If the \a target argument is specified, all items down to the \a target
    item will be replaced. If \a target is \c null, all items in the stack
    will be replaced. If not specified, only the top item will be replaced.

    StackView creates an instance automatically if the replacing item is a \l Component,
    or a \l [QML] url. The optional \a properties argument specifies a map of initial
    property values for the replacing item. For dynamically created items, these values
    are applied before the creation is finalized. This is more efficient than setting
    property values after creation, particularly where large sets of property values
    are defined, and also allows property bindings to be set up (using \l{Qt::binding}
    {Qt.binding()}) before the item is created.

    Replace the top item:
    \code
    stackView.replace(rect)

    // or with properties:
    stackView.replace(rect, {"color": "red"})
    \endcode

    Multiple items can be replaced at the same time either by passing them as
    additional arguments, or as an array. Each item can be followed by a set
    of properties to apply.

    Passing a variable amount of arguments:
    \code
    stackView.replace(rect1, rect2, rect3)

    // or with properties:
    stackView.replace(rect1, {"color": "red"}, rect2, {"color": "green"}, rect3, {"color": "blue"})
    \endcode

    Replacing an array of items:
    \code
    stackView.replace([rect1, rect2, rect3])

    // or with properties:
    stackView.replace([rect1, {"color": "red"}, rect2, {"color": "green"}, rect3, {"color": "blue"}])
    \endcode

    An \a operation can be optionally specified as the last argument. Supported
    operations:

    \value StackView.Immediate An immediate operation without transitions.
    \value StackView.PushTransition An operation with push transitions (since QtQuick.Controls 2.1).
    \value StackView.ReplaceTransition An operation with replace transitions (since QtQuick.Controls 2.1).
    \value StackView.PopTransition An operation with pop transitions (since QtQuick.Controls 2.1).

    If no operation is provided, \c Immediate will be used if the stack is
    empty, and \c ReplaceTransition otherwise.

    The following example illustrates the use of push and pop transitions with replace().

    \code
    StackView {
        id: stackView

        initialItem: Component {
            id: page

            Page {
                Row {
                    spacing: 20
                    anchors.centerIn: parent

                    Button {
                        text: "<"
                        onClicked: stackView.replace(page, StackView.PopTransition)
                    }
                    Button {
                        text: ">"
                        onClicked: stackView.replace(page, StackView.PushTransition)
                    }
                }
            }
        }
    }
    \endcode

    \sa push(), {Replacing Items}
*/
void QQuickStackView::replace(QQmlV4Function *args)
{
    Q_D(QQuickStackView);
    const QString operationName = QStringLiteral("replace");
    if (d->modifyingElements) {
        d->warnOfInterruption(operationName);
        args->setReturnValue(QV4::Encode::null());
        return;
    }

    QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true);
    QScopedValueRollback<QString> operationNameRollback(d->operation, operationName);
    if (args->length() <= 0) {
        d->warn(QStringLiteral("missing arguments"));
        args->setReturnValue(QV4::Encode::null());
        return;
    }

    QV4::ExecutionEngine *v4 = args->v4engine();
    QV4::Scope scope(v4);

#if QT_CONFIG(quick_viewtransitions)
    Operation operation = d->elements.isEmpty() ? Immediate : ReplaceTransition;
    QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]);
    if (lastArg->isInt32())
        operation = static_cast<Operation>(lastArg->toInt32());
#endif

    QQuickStackElement *target = nullptr;
    QV4::ScopedValue firstArg(scope, (*args)[0]);
    if (firstArg->isNull())
        target = d->elements.value(0);
    else if (!firstArg->isInt32())
        target = d->findElement(firstArg);

    QStringList errors;
    QList<QQuickStackElement *> elements = d->parseElements(target ? 1 : 0, args, &errors);
    if (!errors.isEmpty() || elements.isEmpty()) {
        if (!errors.isEmpty()) {
            for (const QString &error : std::as_const(errors))
                d->warn(error);
        } else {
            d->warn(QStringLiteral("nothing to push"));
        }
        args->setReturnValue(QV4::Encode::null());
        return;
    }

    int oldDepth = d->elements.size();
    QQuickStackElement* exit = nullptr;
    if (!d->elements.isEmpty())
        exit = d->elements.pop();

    if (exit != target ? d->replaceElements(target, elements) : d->pushElements(elements)) {
        d->depthChange(d->elements.size(), oldDepth);
        if (exit) {
            exit->removal = true;
            d->removing.insert(exit);
        }
        QQuickStackElement *enter = d->elements.top();
#if QT_CONFIG(quick_viewtransitions)
        d->startTransition(QQuickStackTransition::replaceExit(operation, exit, this),
                           QQuickStackTransition::replaceEnter(operation, enter, this),
                           operation == Immediate);
#endif
        d->setCurrentItem(enter);
    }

    if (d->currentItem) {
        QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(v4, d->currentItem));
        args->setReturnValue(rv->asReturnedValue());
    } else {
        args->setReturnValue(QV4::Encode::null());
    }
}

/*!
    \since QtQuick.Controls 2.3 (Qt 5.10)
    \qmlproperty bool QtQuick.Controls::StackView::empty
    \readonly

    This property holds whether the stack is empty.

    \sa depth
*/
bool QQuickStackView::isEmpty() const
{
    Q_D(const QQuickStackView);
    return d->elements.isEmpty();
}

/*!
    \qmlmethod void QtQuick.Controls::StackView::clear(transition)

    Removes all items from the stack.

    \include qquickstackview.qdocinc pop-ownership

    Since QtQuick.Controls 2.3, a \a transition can be optionally specified. Supported transitions:

    \value StackView.Immediate Clear the stack immediately without any transition (default).
    \value StackView.PushTransition Clear the stack with a push transition.
    \value StackView.ReplaceTransition Clear the stack with a replace transition.
    \value StackView.PopTransition Clear the stack with a pop transition.
*/
void QQuickStackView::clear(Operation operation)
{
#if !QT_CONFIG(quick_viewtransitions)
    Q_UNUSED(operation)
#endif
    Q_D(QQuickStackView);
    if (d->elements.isEmpty())
        return;

    const QString operationName = QStringLiteral("clear");
    if (d->modifyingElements) {
        d->warnOfInterruption(operationName);
        return;
    }

    const int oldDepth = d->elements.size();

    QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true);
    QScopedValueRollback<QString> operationNameRollback(d->operation, operationName);
#if QT_CONFIG(quick_viewtransitions)
    if (operation != Immediate) {
        QQuickStackElement *exit = d->elements.pop();
        exit->removal = true;
        d->removing.insert(exit);
        d->startTransition(QQuickStackTransition::popExit(operation, exit, this),
                           QQuickStackTransition::popEnter(operation, nullptr, this), false);
    }
#endif

    d->setCurrentItem(nullptr);
    qDeleteAll(d->elements);
    d->elements.clear();
    d->depthChange(0, oldDepth);
}

/*!
    \qmlproperty var QtQuick.Controls::StackView::initialItem

    This property holds the initial item that should be shown when the StackView
    is created. The initial item can be an \l Item, \l Component, or a \l [QML] url.
    Specifying an initial item is equivalent to:
    \code
    Component.onCompleted: stackView.push(myInitialItem)
    \endcode

    \sa push()
*/
QJSValue QQuickStackView::initialItem() const
{
    Q_D(const QQuickStackView);
    return d->initialItem;
}

void QQuickStackView::setInitialItem(const QJSValue &item)
{
    Q_D(QQuickStackView);
    d->initialItem = item;
}

#if QT_CONFIG(quick_viewtransitions)
/*!
    \qmlproperty Transition QtQuick.Controls::StackView::popEnter

    This property holds the transition that is applied to the item that
    enters the stack when another item is popped off of it.

    \sa {Customizing StackView}
*/
QQuickTransition *QQuickStackView::popEnter() const
{
    Q_D(const QQuickStackView);
    if (d->transitioner)
        return d->transitioner->removeDisplacedTransition;
    return nullptr;
}

void QQuickStackView::setPopEnter(QQuickTransition *enter)
{
    Q_D(QQuickStackView);
    d->ensureTransitioner();
    if (d->transitioner->removeDisplacedTransition == enter)
        return;

    d->transitioner->removeDisplacedTransition = enter;
    emit popEnterChanged();
}

/*!
    \qmlproperty Transition QtQuick.Controls::StackView::popExit

    This property holds the transition that is applied to the item that
    exits the stack when the item is popped off of it.

    \sa {Customizing StackView}
*/
QQuickTransition *QQuickStackView::popExit() const
{
    Q_D(const QQuickStackView);
    if (d->transitioner)
        return d->transitioner->removeTransition;
    return nullptr;
}

void QQuickStackView::setPopExit(QQuickTransition *exit)
{
    Q_D(QQuickStackView);
    d->ensureTransitioner();
    if (d->transitioner->removeTransition == exit)
        return;

    d->transitioner->removeTransition = exit;
    emit popExitChanged();
}

/*!
    \qmlproperty Transition QtQuick.Controls::StackView::pushEnter

    This property holds the transition that is applied to the item that
    enters the stack when the item is pushed onto it.

    \sa {Customizing StackView}
*/
QQuickTransition *QQuickStackView::pushEnter() const
{
    Q_D(const QQuickStackView);
    if (d->transitioner)
        return d->transitioner->addTransition;
    return nullptr;
}

void QQuickStackView::setPushEnter(QQuickTransition *enter)
{
    Q_D(QQuickStackView);
    d->ensureTransitioner();
    if (d->transitioner->addTransition == enter)
        return;

    d->transitioner->addTransition = enter;
    emit pushEnterChanged();
}

/*!
    \qmlproperty Transition QtQuick.Controls::StackView::pushExit

    This property holds the transition that is applied to the item that
    exits the stack when another item is pushed onto it.

    \sa {Customizing StackView}
*/
QQuickTransition *QQuickStackView::pushExit() const
{
    Q_D(const QQuickStackView);
    if (d->transitioner)
        return d->transitioner->addDisplacedTransition;
    return nullptr;
}

void QQuickStackView::setPushExit(QQuickTransition *exit)
{
    Q_D(QQuickStackView);
    d->ensureTransitioner();
    if (d->transitioner->addDisplacedTransition == exit)
        return;

    d->transitioner->addDisplacedTransition = exit;
    emit pushExitChanged();
}

/*!
    \qmlproperty Transition QtQuick.Controls::StackView::replaceEnter

    This property holds the transition that is applied to the item that
    enters the stack when another item is replaced by it.

    \sa {Customizing StackView}
*/
QQuickTransition *QQuickStackView::replaceEnter() const
{
    Q_D(const QQuickStackView);
    if (d->transitioner)
        return d->transitioner->moveTransition;
    return nullptr;
}

void QQuickStackView::setReplaceEnter(QQuickTransition *enter)
{
    Q_D(QQuickStackView);
    d->ensureTransitioner();
    if (d->transitioner->moveTransition == enter)
        return;

    d->transitioner->moveTransition = enter;
    emit replaceEnterChanged();
}

/*!
    \qmlproperty Transition QtQuick.Controls::StackView::replaceExit

    This property holds the transition that is applied to the item that
    exits the stack when it is replaced by another item.

    \sa {Customizing StackView}
*/
QQuickTransition *QQuickStackView::replaceExit() const
{
    Q_D(const QQuickStackView);
    if (d->transitioner)
        return d->transitioner->moveDisplacedTransition;
    return nullptr;
}

void QQuickStackView::setReplaceExit(QQuickTransition *exit)
{
    Q_D(QQuickStackView);
    d->ensureTransitioner();
    if (d->transitioner->moveDisplacedTransition == exit)
        return;

    d->transitioner->moveDisplacedTransition = exit;
    emit replaceExitChanged();
}
#endif

void QQuickStackView::componentComplete()
{
    QQuickControl::componentComplete();

    Q_D(QQuickStackView);
    QScopedValueRollback<QString> operationNameRollback(d->operation, QStringLiteral("initialItem"));
    QQuickStackElement *element = nullptr;
    QString error;
    int oldDepth = d->elements.size();
    if (QObject *o = d->initialItem.toQObject())
        element = QQuickStackElement::fromObject(o, this, &error);
    else if (d->initialItem.isString())
        element = QQuickStackElement::fromString(d->initialItem.toString(), this, &error);
    if (!error.isEmpty()) {
        d->warn(error);
        delete element;
    } else if (d->pushElement(element)) {
        d->depthChange(d->elements.size(), oldDepth);
        d->setCurrentItem(element);
        element->setStatus(QQuickStackView::Active);
    }
}

void QQuickStackView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
{
    QQuickControl::geometryChange(newGeometry, oldGeometry);

    Q_D(QQuickStackView);
    for (QQuickStackElement *element : std::as_const(d->elements)) {
        if (element->item) {
            if (!element->widthValid)
                element->item->setWidth(newGeometry.width());
            if (!element->heightValid)
                element->item->setHeight(newGeometry.height());
        }
    }
}

bool QQuickStackView::childMouseEventFilter(QQuickItem *item, QEvent *event)
{
    // in order to block accidental user interaction while busy/transitioning,
    // StackView filters out childrens' mouse events. therefore we block all
    // press events. however, since push() may be called from signal handlers
    // such as onPressed or onDoubleClicked, we must let the current mouse
    // grabber item receive the respective mouse release event to avoid
    // breaking its state (QTBUG-50305).
    if (event->type() == QEvent::MouseButtonPress)
        return true;
    if (event->type() == QEvent::UngrabMouse)
        return false;
    QQuickWindow *window = item->window();
    return window && !window->mouseGrabberItem();
}

#if QT_CONFIG(quicktemplates2_multitouch)
void QQuickStackView::touchEvent(QTouchEvent *event)
{
    event->ignore(); // QTBUG-65084
}
#endif

#if QT_CONFIG(accessibility)
QAccessible::Role QQuickStackView::accessibleRole() const
{
    return QAccessible::LayeredPane;
}
#endif

void QQuickStackViewAttachedPrivate::itemParentChanged(QQuickItem *item, QQuickItem *parent)
{
    Q_Q(QQuickStackViewAttached);
    int oldIndex = element ? element->index : -1;
    QQuickStackView *oldView = element ? element->view : nullptr;
    QQuickStackView::Status oldStatus = element ? element->status : QQuickStackView::Inactive;

    QQuickStackView *newView = qobject_cast<QQuickStackView *>(parent);
    element = newView ? QQuickStackViewPrivate::get(newView)->findElement(item) : nullptr;

    int newIndex = element ? element->index : -1;
    QQuickStackView::Status newStatus = element ? element->status : QQuickStackView::Inactive;

    if (oldIndex != newIndex)
        emit q->indexChanged();
    if (oldView != newView)
        emit q->viewChanged();
    if (oldStatus != newStatus)
        emit q->statusChanged();
}

QQuickStackViewAttached::QQuickStackViewAttached(QObject *parent)
    : QObject(*(new QQuickStackViewAttachedPrivate), parent)
{
    Q_D(QQuickStackViewAttached);
    QQuickItem *item = qobject_cast<QQuickItem *>(parent);
    if (item) {
        connect(item, &QQuickItem::visibleChanged, this, &QQuickStackViewAttached::visibleChanged);
        QQuickItemPrivate::get(item)->addItemChangeListener(d, QQuickItemPrivate::Parent);
        d->itemParentChanged(item, item->parentItem());
    } else if (parent) {
        qmlWarning(parent) << "StackView must be attached to an Item";
    }
}

QQuickStackViewAttached::~QQuickStackViewAttached()
{
    Q_D(QQuickStackViewAttached);
    QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent());
    if (parentItem)
        QQuickItemPrivate::get(parentItem)->removeItemChangeListener(d, QQuickItemPrivate::Parent);
}

/*!
    \qmlattachedproperty int QtQuick.Controls::StackView::index
    \readonly

    This attached property holds the stack index of the item it's
    attached to, or \c -1 if the item is not in a stack.
*/
int QQuickStackViewAttached::index() const
{
    Q_D(const QQuickStackViewAttached);
    return d->element ? d->element->index : -1;
}

/*!
    \qmlattachedproperty StackView QtQuick.Controls::StackView::view
    \readonly

    This attached property holds the stack view of the item it's
    attached to, or \c null if the item is not in a stack.
*/
QQuickStackView *QQuickStackViewAttached::view() const
{
    Q_D(const QQuickStackViewAttached);
    return d->element ? d->element->view : nullptr;
}

/*!
    \qmlattachedproperty enumeration QtQuick.Controls::StackView::status
    \readonly

    This attached property holds the stack status of the item it's
    attached to, or \c StackView.Inactive if the item is not in a stack.

    Available values:
    \value StackView.Inactive The item is inactive (or not in a stack).
    \value StackView.Deactivating The item is being deactivated (popped off).
    \value StackView.Activating The item is being activated (becoming the current item).
    \value StackView.Active The item is active, that is, the current item.
*/
QQuickStackView::Status QQuickStackViewAttached::status() const
{
    Q_D(const QQuickStackViewAttached);
    return d->element ? d->element->status : QQuickStackView::Inactive;
}

/*!
    \since QtQuick.Controls 2.2 (Qt 5.9)
    \qmlattachedproperty bool QtQuick.Controls::StackView::visible

    This attached property holds the visibility of the item it's attached to.
    The value follows the value of \l Item::visible.

    By default, StackView shows incoming items when the enter transition begins,
    and hides outgoing items when the exit transition ends. Setting this property
    explicitly allows the default behavior to be overridden, making it possible
    to keep items that are below the top-most item visible.

    \note The default transitions of most styles slide outgoing items outside the
          view, and may also animate their opacity. In order to keep a full stack
          of items visible, consider customizing the \l transitions so that the
          items underneath can be seen.

    \image qtquickcontrols-stackview-visible.png

    \snippet qtquickcontrols-stackview-visible.qml 1
*/
bool QQuickStackViewAttached::isVisible() const
{
    const QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent());
    return parentItem && parentItem->isVisible();
}

void QQuickStackViewAttached::setVisible(bool visible)
{
    Q_D(QQuickStackViewAttached);
    d->explicitVisible = true;
    QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent());
    if (parentItem)
        parentItem->setVisible(visible);
}

void QQuickStackViewAttached::resetVisible()
{
    Q_D(QQuickStackViewAttached);
    d->explicitVisible = false;
    if (!d->element || !d->element->view)
        return;

    QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent());
    if (parentItem)
        parentItem->setVisible(parentItem == d->element->view->currentItem());
}

/*!
    \qmlattachedsignal QtQuick.Controls::StackView::activated()
    \since QtQuick.Controls 2.1 (Qt 5.8)

    This attached signal is emitted when the item it's attached to is activated in the stack.

    \sa status
*/

/*!
    \qmlattachedsignal QtQuick.Controls::StackView::deactivated()
    \since QtQuick.Controls 2.1 (Qt 5.8)

    This attached signal is emitted when the item it's attached to is deactivated in the stack.

    \sa status
*/

/*!
    \qmlattachedsignal QtQuick.Controls::StackView::activating()
    \since QtQuick.Controls 2.1 (Qt 5.8)

    This attached signal is emitted when the item it's attached to is in the process of being
    activated in the stack.

    \sa status
*/

/*!
    \qmlattachedsignal QtQuick.Controls::StackView::deactivating()
    \since QtQuick.Controls 2.1 (Qt 5.8)

    This attached signal is emitted when the item it's attached to is in the process of being
    dectivated in the stack.

    \sa status
*/

/*!
    \qmlattachedsignal QtQuick.Controls::StackView::removed()
    \since QtQuick.Controls 2.1 (Qt 5.8)

    This attached signal is emitted when the item it's attached to has been
    removed from the stack. It can be used to safely destroy an Item that was
    pushed onto the stack, for example:

    \code
    Item {
        StackView.onRemoved: destroy() // Will be destroyed sometime after this call.
    }
    \endcode

    \sa status
*/

QT_END_NAMESPACE

#include "moc_qquickstackview_p.cpp"
