| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: http://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL3$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see http://www.qt.io/terms-conditions. For further |
| ** information use the contact form at http://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or later as published by the Free |
| ** Software Foundation and appearing in the file LICENSE.GPL included in |
| ** the packaging of this file. Please review the following information to |
| ** ensure the GNU General Public License version 2.0 requirements will be |
| ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquickstackview_p.h" |
| #include "qquickstackview_p_p.h" |
| #include "qquickstackelement_p_p.h" |
| #include "qquickstacktransition_p_p.h" |
| |
| #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 qtquickcontrols2-navigation |
| \ingroup qtquickcontrols2-containers |
| \ingroup qtquickcontrols2-focusscopes |
| \brief Provides a stack-based navigation model. |
| |
| \image qtquickcontrols2-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 qtquickcontrols2-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 qtquickcontrols2-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 qtquickcontrols2-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 qtquickcontrols2-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{Transition}s for the |
| \l pushEnter, \l pushExit, \l popEnter, \l popExit, \l 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}, {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 (d->transitioner) { |
| d->transitioner->setChangeListener(nullptr); |
| delete d->transitioner; |
| } |
| 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.count(); |
| } |
| |
| /*! |
| \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.count() - 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 the specified \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.Transition An operation with default transitions (default). |
| \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). |
| |
| \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); |
| |
| Operation operation = d->elements.isEmpty() ? Immediate : PushTransition; |
| QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]); |
| if (lastArg->isInt32()) |
| operation = static_cast<Operation>(lastArg->toInt32()); |
| |
| 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. |
| for (int i = 0; i < elements.size(); ) { |
| QQuickStackElement *element = elements.at(i); |
| if (element->item && d->findElement(element->item)) |
| elements.removeAt(i); |
| else |
| ++i; |
| } |
| |
| if (!errors.isEmpty() || elements.isEmpty()) { |
| if (!errors.isEmpty()) { |
| for (const QString &error : qAsConst(errors)) |
| d->warn(error); |
| } else { |
| d->warn(QStringLiteral("nothing to push")); |
| } |
| args->setReturnValue(QV4::Encode::null()); |
| return; |
| } |
| |
| QQuickStackElement *exit = nullptr; |
| if (!d->elements.isEmpty()) |
| exit = d->elements.top(); |
| |
| int oldDepth = d->elements.count(); |
| if (d->pushElements(elements)) { |
| d->depthChange(d->elements.count(), oldDepth); |
| QQuickStackElement *enter = d->elements.top(); |
| d->startTransition(QQuickStackTransition::pushEnter(operation, enter, this), |
| QQuickStackTransition::pushExit(operation, exit, this), |
| operation == Immediate); |
| 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.Transition An operation with default transitions (default). |
| \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). |
| |
| 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.count() <= 1 || argc > 2) { |
| if (argc > 2) |
| d->warn(QStringLiteral("too many arguments")); |
| args->setReturnValue(QV4::Encode::null()); |
| return; |
| } |
| |
| int oldDepth = d->elements.count(); |
| 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; |
| } |
| } |
| } |
| |
| Operation operation = PopTransition; |
| if (argc > 0) { |
| QV4::ScopedValue lastArg(scope, (*args)[argc - 1]); |
| if (lastArg->isInt32()) |
| operation = static_cast<Operation>(lastArg->toInt32()); |
| } |
| |
| QQuickItem *previousItem = nullptr; |
| |
| if (d->popElements(enter)) { |
| if (exit) { |
| exit->removal = true; |
| d->removing.insert(exit); |
| previousItem = exit->item; |
| } |
| d->depthChange(d->elements.count(), oldDepth); |
| d->startTransition(QQuickStackTransition::popExit(operation, exit, this), |
| QQuickStackTransition::popEnter(operation, enter, this), |
| operation == Immediate); |
| 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 |
| \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 \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.Transition An operation with default transitions (default). |
| \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). |
| |
| 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); |
| |
| Operation operation = d->elements.isEmpty() ? Immediate : ReplaceTransition; |
| QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]); |
| if (lastArg->isInt32()) |
| operation = static_cast<Operation>(lastArg->toInt32()); |
| |
| 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 : qAsConst(errors)) |
| d->warn(error); |
| } else { |
| d->warn(QStringLiteral("nothing to push")); |
| } |
| args->setReturnValue(QV4::Encode::null()); |
| return; |
| } |
| |
| int oldDepth = d->elements.count(); |
| 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.count(), oldDepth); |
| if (exit) { |
| exit->removal = true; |
| d->removing.insert(exit); |
| } |
| QQuickStackElement *enter = d->elements.top(); |
| d->startTransition(QQuickStackTransition::replaceExit(operation, exit, this), |
| QQuickStackTransition::replaceEnter(operation, enter, this), |
| operation == Immediate); |
| 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) |
| { |
| Q_D(QQuickStackView); |
| if (d->elements.isEmpty()) |
| return; |
| |
| const QString operationName = QStringLiteral("clear"); |
| if (d->modifyingElements) { |
| d->warnOfInterruption(operationName); |
| return; |
| } |
| |
| QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true); |
| QScopedValueRollback<QString> operationNameRollback(d->operation, operationName); |
| 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); |
| } |
| |
| int oldDepth = d->elements.count(); |
| 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; |
| } |
| |
| /*! |
| \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(); |
| } |
| |
| void QQuickStackView::componentComplete() |
| { |
| QQuickControl::componentComplete(); |
| |
| Q_D(QQuickStackView); |
| QScopedValueRollback<QString> operationNameRollback(d->operation, QStringLiteral("initialItem")); |
| QQuickStackElement *element = nullptr; |
| QString error; |
| int oldDepth = d->elements.count(); |
| 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.count(), oldDepth); |
| d->setCurrentItem(element); |
| element->setStatus(QQuickStackView::Active); |
| } |
| } |
| |
| void QQuickStackView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) |
| { |
| QQuickControl::geometryChanged(newGeometry, oldGeometry); |
| |
| Q_D(QQuickStackView); |
| for (QQuickStackElement *element : qAsConst(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 qtquickcontrols2-stackview-visible.png |
| |
| \snippet qtquickcontrols2-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 |