diff options
Diffstat (limited to 'src/templates/qquickstackview.cpp')
-rw-r--r-- | src/templates/qquickstackview.cpp | 783 |
1 files changed, 783 insertions, 0 deletions
diff --git a/src/templates/qquickstackview.cpp b/src/templates/qquickstackview.cpp new file mode 100644 index 00000000..1c09b6e8 --- /dev/null +++ b/src/templates/qquickstackview.cpp @@ -0,0 +1,783 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 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 <QtQml/qjsvalue.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlinfo.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype StackView + \inherits Control + \instantiates QQuickStackView + \inqmlmodule QtQuick.Controls + \ingroup navigation + \brief Provides a stack-based navigation model. + + StackView can be used with a set of inter-linked information pages. For + example, an email application with separate views to list 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. + + 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. + + Given the stack [A, B, C]: + + \list + \li \l{push()}{push(D)} => [A, B, C, D] - "push" transition animation + between C and D + \li pop() => [A, B] - "pop" transition animation between C and B + \li \l{replace()}{replace(D)} => [A, B, D] - "replace" transition between + C and D + \li \l{pop()}{pop(A)} => [A] - "pop" transition between C and A + \endlist + + \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). A pop() operation on a stack with depth 1 or + 0 does nothing. In such cases, the stack can be emptied using the clear() + method. + + \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.Stack.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, and \l popExit properties of + StackView. + + \note The pop and push transition animations affect each others' + transitional behavior. So 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 + +*/ + +QQuickStackView::QQuickStackView(QQuickItem *parent) : + QQuickControl(*(new QQuickStackViewPrivate), parent) +{ + setFlag(ItemIsFocusScope); +} + +QQuickStackView::~QQuickStackView() +{ + Q_D(QQuickStackView); + if (d->transitioner) { + d->transitioner->setChangeListener(Q_NULLPTR); + delete d->transitioner; + } + qDeleteAll(d->removals); + qDeleteAll(d->elements); +} + +QQuickStackAttached *QQuickStackView::qmlAttachedProperties(QObject *object) +{ + QQuickItem *item = qobject_cast<QQuickItem *>(object); + if (!item) { + qmlInfo(object) << "StackView must be attached to an Item"; + return Q_NULLPTR; + } + return new QQuickStackAttached(item); +} + +/*! + \qmlproperty bool QtQuickControls2::StackView::busy + \readonly + This property holds whether a transition is running. +*/ +bool QQuickStackView::busy() const +{ + Q_D(const QQuickStackView); + return d->transitioner && !d->transitioner->runningJobs.isEmpty(); +} + +/*! + \qmlproperty int QtQuickControls2::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 QtQuickControls2::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 QtQuickControls2::StackView::get(index, behavior = DontLoad) + + Supported behavior values: + \list + \li StackView.DontLoad + \li StackView.ForceLoad + \endlist + + TODO +*/ +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 Q_NULLPTR; +} + +/*! + \qmlmethod Item QtQuickControls2::StackView::find(callback, behavior = DontLoad) + + Supported behavior values: + \list + \li StackView.DontLoad + \li StackView.ForceLoad + \endlist + + TODO +*/ +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 Q_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 Q_NULLPTR; +} + +/*! + \qmlmethod Item QtQuickControls2::StackView::push(item, properties, operation) + + TODO +*/ +void QQuickStackView::push(QQmlV4Function *args) +{ + Q_D(QQuickStackView); + if (args->length() <= 0) { + qmlInfo(this) << "push: missing arguments"; + args->setReturnValue(QV4::Encode::null()); + return; + } + + QV4::ExecutionEngine *v4 = args->v4engine(); + QV4::Scope scope(v4); + + Operation operation = d->elements.isEmpty() ? Immediate : Transition; + QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]); + if (lastArg->isInt32()) + operation = static_cast<Operation>(lastArg->toInt32()); + + QList<QQuickStackElement *> elements = d->parseElements(args); + if (elements.isEmpty()) { + qmlInfo(this) << "push: nothing to push"; + args->setReturnValue(QV4::Encode::null()); + return; + } + + QQuickStackElement *exit = Q_NULLPTR; + if (!d->elements.isEmpty()) + exit = d->elements.top(); + + if (d->pushElements(elements)) { + emit depthChanged(); + QQuickStackElement *enter = d->elements.top(); + d->pushTransition(enter, exit, boundingRect(), operation == Immediate); + d->setCurrentItem(enter->item); + } + + 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 QtQuickControls2::StackView::pop(item = null, operation = Transition) + + TODO +*/ +void QQuickStackView::pop(QQmlV4Function *args) +{ + Q_D(QQuickStackView); + int argc = args->length(); + if (d->elements.count() <= 1 || argc > 2) { + if (argc > 2) + qmlInfo(this) << "pop: too many arguments"; + args->setReturnValue(QV4::Encode::null()); + return; + } + + 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 (!value->isUndefined() && !value->isInt32()) { + enter = d->findElement(value); + if (!enter) { + qmlInfo(this) << "pop: unknown argument: " << value->toQString(); // TODO: safe? + args->setReturnValue(QV4::Encode::null()); + d->elements.push(exit); // restore + return; + } + } + } + + Operation operation = Transition; + if (argc > 0) { + QV4::ScopedValue lastArg(scope, (*args)[argc - 1]); + if (lastArg->isInt32()) + operation = static_cast<Operation>(lastArg->toInt32()); + } + + QQuickItem *previousItem = Q_NULLPTR; + + if (d->popElements(enter)) { + if (exit) + previousItem = exit->item; + emit depthChanged(); + d->popTransition(enter, exit, boundingRect(), operation == Immediate); + d->setCurrentItem(enter->item); + } + + if (previousItem) { + QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(v4, previousItem)); + args->setReturnValue(rv->asReturnedValue()); + } else { + args->setReturnValue(QV4::Encode::null()); + } +} + +/*! + \qmlmethod Item QtQuickControls2::StackView::push(item, properties, operation = Transition) + + TODO +*/ +void QQuickStackView::replace(QQmlV4Function *args) +{ + Q_D(QQuickStackView); + if (args->length() <= 0) { + qmlInfo(this) << "replace: missing arguments"; + args->setReturnValue(QV4::Encode::null()); + return; + } + + QV4::ExecutionEngine *v4 = args->v4engine(); + QV4::Scope scope(v4); + + Operation operation = d->elements.isEmpty() ? Immediate : Transition; + QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]); + if (lastArg->isInt32()) + operation = static_cast<Operation>(lastArg->toInt32()); + + QQuickStackElement *target = Q_NULLPTR; + QV4::ScopedValue firstArg(scope, (*args)[0]); + if (firstArg->isNull()) + target = d->elements.value(0); + else if (!firstArg->isInt32()) + target = d->findElement(firstArg); + + QList<QQuickStackElement *> elements = d->parseElements(args, target ? 1 : 0); + if (elements.isEmpty()) { + qmlInfo(this) << "replace: nothing to push"; + args->setReturnValue(QV4::Encode::null()); + return; + } + + int depth = d->elements.count(); + QQuickStackElement* exit = Q_NULLPTR; + if (!d->elements.isEmpty()) + exit = d->elements.pop(); + + if (d->replaceElements(target, elements)) { + if (depth != d->elements.count()) + emit depthChanged(); + QQuickStackElement *enter = d->elements.top(); + d->replaceTransition(enter, exit, boundingRect(), operation == Immediate); + d->setCurrentItem(enter->item); + } + + 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 QtQuickControls2::StackView::clear() + + TODO +*/ +void QQuickStackView::clear() +{ + Q_D(QQuickStackView); + d->setCurrentItem(Q_NULLPTR); + qDeleteAll(d->elements); + d->elements.clear(); + emit depthChanged(); +} + +/*! + \qmlproperty var QtQuickControls2::StackView::initialItem + + This property holds the initial item. + + \sa push() +*/ +QVariant QQuickStackView::initialItem() const +{ + Q_D(const QQuickStackView); + return d->initialItem; +} + +void QQuickStackView::setInitialItem(const QVariant &item) +{ + Q_D(QQuickStackView); + d->initialItem = item; +} + +/*! + \qmlproperty Transition QtQuickControls2::StackView::popEnter + + TODO +*/ +QQuickTransition *QQuickStackView::popEnter() const +{ + Q_D(const QQuickStackView); + if (d->transitioner) + return d->transitioner->removeDisplacedTransition; + return Q_NULLPTR; +} + +void QQuickStackView::setPopEnter(QQuickTransition *enter) +{ + Q_D(QQuickStackView); + d->ensureTransitioner(); + if (d->transitioner->removeDisplacedTransition != enter) { + d->transitioner->removeDisplacedTransition = enter; + emit popEnterChanged(); + } +} + +/*! + \qmlproperty Transition QtQuickControls2::StackView::popExit + + TODO +*/ +QQuickTransition *QQuickStackView::popExit() const +{ + Q_D(const QQuickStackView); + if (d->transitioner) + return d->transitioner->removeTransition; + return Q_NULLPTR; +} + +void QQuickStackView::setPopExit(QQuickTransition *exit) +{ + Q_D(QQuickStackView); + d->ensureTransitioner(); + if (d->transitioner->removeTransition != exit) { + d->transitioner->removeTransition = exit; + emit popExitChanged(); + } +} + +/*! + \qmlproperty Transition QtQuickControls2::StackView::pushEnter + + TODO +*/ +QQuickTransition *QQuickStackView::pushEnter() const +{ + Q_D(const QQuickStackView); + if (d->transitioner) + return d->transitioner->addTransition; + return Q_NULLPTR; +} + +void QQuickStackView::setPushEnter(QQuickTransition *enter) +{ + Q_D(QQuickStackView); + d->ensureTransitioner(); + if (d->transitioner->addTransition != enter) { + d->transitioner->addTransition = enter; + emit pushEnterChanged(); + } +} + +/*! + \qmlproperty Transition QtQuickControls2::StackView::pushExit + + TODO +*/ +QQuickTransition *QQuickStackView::pushExit() const +{ + Q_D(const QQuickStackView); + if (d->transitioner) + return d->transitioner->addDisplacedTransition; + return Q_NULLPTR; +} + +void QQuickStackView::setPushExit(QQuickTransition *exit) +{ + Q_D(QQuickStackView); + d->ensureTransitioner(); + if (d->transitioner->addDisplacedTransition != exit) { + d->transitioner->addDisplacedTransition = exit; + emit pushExitChanged(); + } +} + +void QQuickStackView::componentComplete() +{ + QQuickControl::componentComplete(); + + Q_D(QQuickStackView); + QQuickStackElement *element = Q_NULLPTR; + if (QObject *o = d->initialItem.value<QObject *>()) + element = QQuickStackElement::fromObject(o, this); + else if (d->initialItem.canConvert<QString>()) + element = QQuickStackElement::fromString(d->initialItem.toString(), this); + if (d->pushElement(element)) { + emit depthChanged(); + d->setCurrentItem(element->item); + } +} + +void QQuickStackView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + QQuickControl::geometryChanged(newGeometry, oldGeometry); + + Q_D(QQuickStackView); + foreach (QQuickStackElement *element, d->elements) { + if (element->item) { + QQuickItemPrivate *p = QQuickItemPrivate::get(element->item); + if (!p->widthValid) { + element->item->setWidth(newGeometry.width()); + p->widthValid = false; + } + if (!p->heightValid) { + element->item->setHeight(newGeometry.height()); + p->heightValid = false; + } + } + } +} + +bool QQuickStackView::childMouseEventFilter(QQuickItem *, QEvent *) +{ + // busy should be true if this function gets called + return true; +} + +void QQuickStackAttachedPrivate::init() +{ + QQuickItem *item = qobject_cast<QQuickItem *>(parent); + if (item) { + QQuickStackView *view = qobject_cast<QQuickStackView *>(item->parentItem()); + if (view) { + element = QQuickStackViewPrivate::get(view)->findElement(item); + if (element) + initialized = true; + } + } +} + +void QQuickStackAttachedPrivate::reset() +{ + Q_Q(QQuickStackAttached); + int oldIndex = element ? element->index : -1; + QQuickStackView *oldView = element ? element->view : Q_NULLPTR; + QQuickStackView::Status oldStatus = element ? element->status : QQuickStackView::Inactive; + + element = Q_NULLPTR; + + if (oldIndex != -1) + emit q->indexChanged(); + if (oldView) + emit q->viewChanged(); + if (oldStatus != QQuickStackView::Inactive) + emit q->statusChanged(); +} + +QQuickStackAttached::QQuickStackAttached(QQuickItem *parent) : + QObject(*(new QQuickStackAttachedPrivate), parent) +{ +} + +/*! + \qmlattachedproperty int QtQuickControls2::StackView::index + + TODO +*/ +int QQuickStackAttached::index() const +{ + Q_D(const QQuickStackAttached); + if (!d->initialized) + const_cast<QQuickStackAttachedPrivate *>(d)->init(); + return d->element ? d->element->index : -1; +} + +/*! + \qmlattachedproperty StackView QtQuickControls2::StackView::view + + TODO +*/ +QQuickStackView *QQuickStackAttached::view() const +{ + Q_D(const QQuickStackAttached); + if (!d->initialized) + const_cast<QQuickStackAttachedPrivate *>(d)->init(); + return d->element ? d->element->view : Q_NULLPTR; +} + +/*! + \qmlattachedproperty enumeration QtQuickControls2::StackView::status + + TODO +*/ +QQuickStackView::Status QQuickStackAttached::status() const +{ + Q_D(const QQuickStackAttached); + if (!d->initialized) + const_cast<QQuickStackAttachedPrivate *>(d)->init(); + return d->element ? d->element->status : QQuickStackView::Inactive; +} + +QT_END_NAMESPACE |