1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "qquickstackview_p.h"
38#include "qquickstackview_p_p.h"
39#include "qquickstackelement_p_p.h"
40#include "qquickstacktransition_p_p.h"
41
42#include <QtCore/qscopedvaluerollback.h>
43#include <QtQml/qjsvalue.h>
44#include <QtQml/qqmlengine.h>
45#include <QtQml/qqmlinfo.h>
46
47#include <private/qv4qobjectwrapper_p.h>
48#include <private/qqmlengine_p.h>
49
50QT_BEGIN_NAMESPACE
51
52/*!
53 \qmltype StackView
54 \inherits Control
55//! \instantiates QQuickStackView
56 \inqmlmodule QtQuick.Controls
57 \since 5.7
58 \ingroup qtquickcontrols2-navigation
59 \ingroup qtquickcontrols2-containers
60 \ingroup qtquickcontrols2-focusscopes
61 \brief Provides a stack-based navigation model.
62
63 \image qtquickcontrols2-stackview-wireframe.png
64
65 StackView can be used with a set of inter-linked information pages. For
66 example, an email application with separate views to list the latest emails,
67 view a specific email, and list/view the attachments. The email list view
68 is pushed onto the stack as users open an email, and popped out as they
69 choose to go back.
70
71 The following snippet demonstrates a simple use case, where the \c mainView
72 is pushed onto and popped out of the stack on relevant button click:
73
74 \qml
75 ApplicationWindow {
76 title: qsTr("Hello World")
77 width: 640
78 height: 480
79 visible: true
80
81 StackView {
82 id: stack
83 initialItem: mainView
84 anchors.fill: parent
85 }
86
87 Component {
88 id: mainView
89
90 Row {
91 spacing: 10
92
93 Button {
94 text: "Push"
95 onClicked: stack.push(mainView)
96 }
97 Button {
98 text: "Pop"
99 enabled: stack.depth > 1
100 onClicked: stack.pop()
101
102 }
103 Text {
104 text: stack.depth
105 }
106 }
107 }
108 }
109 \endqml
110
111 \section1 Using StackView in an Application
112
113 Using StackView in an application is as simple as adding it as a child to
114 a Window. The stack is usually anchored to the edges of the window, except
115 at the top or bottom where it might be anchored to a status bar, or some
116 other similar UI component. The stack can then be used by invoking its
117 navigation methods. The first item to show in the StackView is the one
118 that was assigned to \l initialItem, or the topmost item if \l initialItem
119 is not set.
120
121 \section1 Basic Navigation
122
123 StackView supports three primary navigation operations: push(), pop(), and
124 replace(). These correspond to classic stack operations where "push" adds
125 an item to the top of a stack, "pop" removes the top item from the
126 stack, and "replace" is like a pop followed by a push, which replaces the
127 topmost item with the new item. The topmost item in the stack
128 corresponds to the one that is \l{StackView::currentItem}{currently}
129 visible on screen. Logically, "push" navigates forward or deeper into the
130 application UI, "pop" navigates backward, and "replace" replaces the
131 \l currentItem.
132
133 \section2 Pushing Items
134
135 In the following animation, three \l Label controls are pushed onto a
136 stack view with the \l push() function:
137
138 \image qtquickcontrols2-stackview-push.gif
139
140 The stack now contains the following items: \c [A, B, C].
141
142 \note When the stack is empty, a push() operation will not have a
143 transition animation because there is nothing to transition from (typically
144 on application start-up).
145
146 \section2 Popping Items
147
148 Continuing on from the example above, the topmost item on the stack is
149 removed with a call to \l pop():
150
151 \image qtquickcontrols2-stackview-pop.gif
152
153 The stack now contains the following items: \c [A, B].
154
155 \note A pop() operation on a stack with depth 1 or 0 does nothing. In such
156 cases, the stack can be emptied using the \l clear() method.
157
158 \section3 Unwinding Items via Pop
159
160 Sometimes, it is necessary to go back more than a single step in the stack.
161 For example, to return to a "main" item or some kind of section item in the
162 application. In such cases, it is possible to specify an item as a
163 parameter for pop(). This is called an "unwind" operation, where the stack
164 unwinds till the specified item. If the item is not found, stack unwinds
165 until it is left with one item, which becomes the \l currentItem. To
166 explicitly unwind to the bottom of the stack, it is recommended to use
167 \l{pop()}{pop(null)}, although any non-existent item will do.
168
169 In the following animation, we unwind the stack to the first item by
170 calling \c pop(null):
171
172 \image qtquickcontrols2-stackview-unwind.gif
173
174 The stack now contains a single item: \c [A].
175
176 \section2 Replacing Items
177
178 In the following animation, we \l replace the topmost item with \c D:
179
180 \image qtquickcontrols2-stackview-replace.gif
181
182 The stack now contains the following items: \c [A, B, D].
183
184 \section1 Deep Linking
185
186 \e{Deep linking} means launching an application into a particular state. For
187 example, a newspaper application could be launched into showing a
188 particular article, bypassing the topmost item. In terms of StackView, deep linking means the ability to modify
189 the state of the stack, so much so that it is possible to push a set of
190 items to the top of the stack, or to completely reset the stack to a given
191 state.
192
193 The API for deep linking in StackView is the same as for basic navigation.
194 Pushing an array instead of a single item adds all the items in that array
195 to the stack. The transition animation, however, is applied only for the
196 last item in the array. The normal semantics of push() apply for deep
197 linking, that is, it adds whatever is pushed onto the stack.
198
199 \note Only the last item of the array is loaded. The rest of the items are
200 loaded only when needed, either on subsequent calls to pop or on request to
201 get an item using get().
202
203 This gives us the following result, given the stack [A, B, C]:
204
205 \list
206 \li \l{push()}{push([D, E, F])} => [A, B, C, D, E, F] - "push" transition
207 animation between C and F
208 \li \l{replace()}{replace([D, E, F])} => [A, B, D, E, F] - "replace"
209 transition animation between C and F
210 \li \l{clear()} followed by \l{push()}{push([D, E, F])} => [D, E, F] - no
211 transition animation for pushing items as the stack was empty.
212 \endlist
213
214 \section1 Finding Items
215
216 An Item for which the application does not have a reference can be found
217 by calling find(). The method needs a callback function, which is invoked
218 for each item in the stack (starting at the top) until a match is found.
219 If the callback returns \c true, find() stops and returns the matching
220 item, otherwise \c null is returned.
221
222 The code below searches the stack for an item named "order_id" and unwinds
223 to that item.
224
225 \badcode
226 stackView.pop(stackView.find(function(item) {
227 return item.name == "order_id";
228 }));
229 \endcode
230
231 You can also get to an item in the stack using \l {get()}{get(index)}.
232
233 \badcode
234 previousItem = stackView.get(myItem.StackView.index - 1));
235 \endcode
236
237 \section1 Transitions
238
239 For each push or pop operation, different transition animations are applied
240 to entering and exiting items. These animations define how the entering item
241 should animate in, and the exiting item should animate out. The animations
242 can be customized by assigning different \l{Transition}s for the
243 \l pushEnter, \l pushExit, \l popEnter, \l popExit, \l replaceEnter, and
244 \l replaceExit properties of StackView.
245
246 \note The transition animations affect each others' transitional behavior.
247 Customizing the animation for one and leaving the other may give unexpected
248 results.
249
250 The following snippet defines a simple fade transition for push and pop
251 operations:
252
253 \qml
254 StackView {
255 id: stackview
256 anchors.fill: parent
257
258 pushEnter: Transition {
259 PropertyAnimation {
260 property: "opacity"
261 from: 0
262 to:1
263 duration: 200
264 }
265 }
266 pushExit: Transition {
267 PropertyAnimation {
268 property: "opacity"
269 from: 1
270 to:0
271 duration: 200
272 }
273 }
274 popEnter: Transition {
275 PropertyAnimation {
276 property: "opacity"
277 from: 0
278 to:1
279 duration: 200
280 }
281 }
282 popExit: Transition {
283 PropertyAnimation {
284 property: "opacity"
285 from: 1
286 to:0
287 duration: 200
288 }
289 }
290 }
291 \endqml
292
293 \note Using anchors on the items added to a StackView is not supported.
294 Typically push, pop, and replace transitions animate the position,
295 which is not possible when anchors are applied. Notice that this
296 only applies to the root of the item. Using anchors for its children
297 works as expected.
298
299 \section1 Item Ownership
300
301 StackView only takes ownership of items that it creates itself. This means
302 that any item pushed onto a StackView will never be destroyed by the
303 StackView; only items that StackView creates from \l {Component}{Components}
304 or \l [QML] {url}{URLs} are destroyed by the StackView. To illustrate this,
305 the messages in the example below will only be printed when the StackView
306 is destroyed, not when the items are popped off the stack:
307
308 \qml
309 Component {
310 id: itemComponent
311
312 Item {
313 Component.onDestruction: print("Destroying second item")
314 }
315 }
316
317 StackView {
318 initialItem: Item {
319 Component.onDestruction: print("Destroying initial item")
320 }
321
322 Component.onCompleted: push(itemComponent.createObject(window))
323 }
324 \endqml
325
326 However, both of the items created from the URL and Component in the
327 following example will be destroyed by the StackView when they are popped
328 off of it:
329
330 \qml
331 Component {
332 id: itemComponent
333
334 Item {
335 Component.onDestruction: print("Destroying second item")
336 }
337 }
338
339 StackView {
340 initialItem: "Item1.qml"
341
342 Component.onCompleted: push(itemComponent)
343 }
344 \endqml
345
346 \section1 Size
347
348 StackView does not inherit an implicit size from items that are pushed onto
349 it. This means that using it as the \l {Popup::}{contentItem} of a
350 \l Dialog, for example, will not work as expected:
351
352 \code
353 Dialog {
354 StackView {
355 initialItem: Rectangle {
356 width: 200
357 height: 200
358 color: "salmon"
359 }
360 }
361 }
362 \endcode
363
364 There are several ways to ensure that StackView has a size in this
365 situation:
366
367 \list
368 \li Set \l[QtQuick]{Item::}{implicitWidth} and
369 \l[QtQuick]{Item::}{implicitHeight} on the StackView itself.
370 \li Set \l[QtQuick]{Item::}{implicitWidth} and
371 \l[QtQuick]{Item::}{implicitHeight} on the \l Rectangle.
372 \li Set \l {Popup::}{contentWidth} and \l {Popup::}{contentHeight} on
373 the Dialog.
374 \li Give the Dialog a size.
375 \endlist
376
377 \sa {Customizing StackView}, {Navigation Controls}, {Container Controls},
378 {Focus Management in Qt Quick Controls}
379*/
380
381QQuickStackView::QQuickStackView(QQuickItem *parent)
382 : QQuickControl(*(new QQuickStackViewPrivate), parent)
383{
384 setFlag(flag: ItemIsFocusScope);
385}
386
387QQuickStackView::~QQuickStackView()
388{
389 Q_D(QQuickStackView);
390 if (d->transitioner) {
391 d->transitioner->setChangeListener(nullptr);
392 delete d->transitioner;
393 }
394 qDeleteAll(c: d->removing);
395 qDeleteAll(c: d->removed);
396 qDeleteAll(c: d->elements);
397}
398
399QQuickStackViewAttached *QQuickStackView::qmlAttachedProperties(QObject *object)
400{
401 return new QQuickStackViewAttached(object);
402}
403
404/*!
405 \qmlproperty bool QtQuick.Controls::StackView::busy
406 \readonly
407 This property holds whether a transition is running.
408*/
409bool QQuickStackView::isBusy() const
410{
411 Q_D(const QQuickStackView);
412 return d->busy;
413}
414
415/*!
416 \qmlproperty int QtQuick.Controls::StackView::depth
417 \readonly
418 This property holds the number of items currently pushed onto the stack.
419*/
420int QQuickStackView::depth() const
421{
422 Q_D(const QQuickStackView);
423 return d->elements.count();
424}
425
426/*!
427 \qmlproperty Item QtQuick.Controls::StackView::currentItem
428 \readonly
429 This property holds the current top-most item in the stack.
430*/
431QQuickItem *QQuickStackView::currentItem() const
432{
433 Q_D(const QQuickStackView);
434 return d->currentItem;
435}
436
437/*!
438 \qmlmethod Item QtQuick.Controls::StackView::get(index, behavior)
439
440 Returns the item at position \a index in the stack, or \c null if the index
441 is out of bounds.
442
443 Supported \a behavior values:
444 \value StackView.DontLoad The item is not forced to load (and \c null is returned if not yet loaded).
445 \value StackView.ForceLoad The item is forced to load.
446*/
447QQuickItem *QQuickStackView::get(int index, LoadBehavior behavior)
448{
449 Q_D(QQuickStackView);
450 QQuickStackElement *element = d->elements.value(i: index);
451 if (element) {
452 if (behavior == ForceLoad)
453 element->load(parent: this);
454 return element->item;
455 }
456 return nullptr;
457}
458
459/*!
460 \qmlmethod Item QtQuick.Controls::StackView::find(callback, behavior)
461
462 Search for a specific item inside the stack. The \a callback function is called
463 for each item in the stack (with the item and index as arguments) until the callback
464 function returns \c true. The return value is the item found. For example:
465
466 \code
467 stackView.find(function(item, index) {
468 return item.isTheOne
469 })
470 \endcode
471
472 Supported \a behavior values:
473 \value StackView.DontLoad Unloaded items are skipped (the callback function is not called for them).
474 \value StackView.ForceLoad Unloaded items are forced to load.
475*/
476QQuickItem *QQuickStackView::find(const QJSValue &callback, LoadBehavior behavior)
477{
478 Q_D(QQuickStackView);
479 QJSValue func(callback);
480 QQmlEngine *engine = qmlEngine(this);
481 if (!engine || !func.isCallable()) // TODO: warning?
482 return nullptr;
483
484 for (int i = d->elements.count() - 1; i >= 0; --i) {
485 QQuickStackElement *element = d->elements.at(i);
486 if (behavior == ForceLoad)
487 element->load(parent: this);
488 if (element->item) {
489 QJSValue rv = func.call(args: QJSValueList() << engine->newQObject(object: element->item) << i);
490 if (rv.toBool())
491 return element->item;
492 }
493 }
494
495 return nullptr;
496}
497
498/*!
499 \qmlmethod Item QtQuick.Controls::StackView::push(item, properties, operation)
500
501 Pushes an \a item onto the stack using the specified \a operation, and
502 optionally applies a set of \a properties on the item. The item can be
503 an \l Item, \l Component, or a \l [QML] url. Returns the item that became
504 current.
505
506 StackView creates an instance automatically if the pushed item is a \l Component,
507 or a \l [QML] url, and the instance will be destroyed when it is popped
508 off the stack. See \l {Item Ownership} for more information.
509
510 The optional \a properties argument specifies a map of initial
511 property values for the pushed item. For dynamically created items, these values
512 are applied before the creation is finalized. This is more efficient than setting
513 property values after creation, particularly where large sets of property values
514 are defined, and also allows property bindings to be set up (using \l{Qt::binding}
515 {Qt.binding()}) before the item is created.
516
517 Pushing a single item:
518 \code
519 stackView.push(rect)
520
521 // or with properties:
522 stackView.push(rect, {"color": "red"})
523 \endcode
524
525 Multiple items can be pushed at the same time either by passing them as
526 additional arguments, or as an array. The last item becomes the current
527 item. Each item can be followed by a set of properties to apply.
528
529 Passing a variable amount of arguments:
530 \code
531 stackView.push(rect1, rect2, rect3)
532
533 // or with properties:
534 stackView.push(rect1, {"color": "red"}, rect2, {"color": "green"}, rect3, {"color": "blue"})
535 \endcode
536
537 Pushing an array of items:
538 \code
539 stackView.push([rect1, rect2, rect3])
540
541 // or with properties:
542 stackView.push([rect1, {"color": "red"}, rect2, {"color": "green"}, rect3, {"color": "blue"}])
543 \endcode
544
545 An \a operation can be optionally specified as the last argument. Supported
546 operations:
547
548 \value StackView.Transition An operation with default transitions (default).
549 \value StackView.Immediate An immediate operation without transitions.
550 \value StackView.PushTransition An operation with push transitions (since QtQuick.Controls 2.1).
551 \value StackView.ReplaceTransition An operation with replace transitions (since QtQuick.Controls 2.1).
552 \value StackView.PopTransition An operation with pop transitions (since QtQuick.Controls 2.1).
553
554 \note Items that already exist in the stack are not pushed.
555
556 \sa initialItem, {Pushing Items}
557*/
558void QQuickStackView::push(QQmlV4Function *args)
559{
560 Q_D(QQuickStackView);
561 const QString operationName = QStringLiteral("push");
562 if (d->modifyingElements) {
563 d->warnOfInterruption(attemptedOperation: operationName);
564 return;
565 }
566
567 QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true);
568 QScopedValueRollback<QString> operationNameRollback(d->operation, operationName);
569 if (args->length() <= 0) {
570 d->warn(QStringLiteral("missing arguments"));
571 args->setReturnValue(QV4::Encode::null());
572 return;
573 }
574
575 QV4::ExecutionEngine *v4 = args->v4engine();
576 QV4::Scope scope(v4);
577
578 Operation operation = d->elements.isEmpty() ? Immediate : PushTransition;
579 QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]);
580 if (lastArg->isInt32())
581 operation = static_cast<Operation>(lastArg->toInt32());
582
583 QStringList errors;
584 QList<QQuickStackElement *> elements = d->parseElements(from: 0, args, errors: &errors);
585 // Remove any items that are already in the stack, as they can't be in two places at once.
586 for (int i = 0; i < elements.size(); ) {
587 QQuickStackElement *element = elements.at(i);
588 if (element->item && d->findElement(item: element->item))
589 elements.removeAt(i);
590 else
591 ++i;
592 }
593
594 if (!errors.isEmpty() || elements.isEmpty()) {
595 if (!errors.isEmpty()) {
596 for (const QString &error : qAsConst(t&: errors))
597 d->warn(error);
598 } else {
599 d->warn(QStringLiteral("nothing to push"));
600 }
601 args->setReturnValue(QV4::Encode::null());
602 return;
603 }
604
605 QQuickStackElement *exit = nullptr;
606 if (!d->elements.isEmpty())
607 exit = d->elements.top();
608
609 int oldDepth = d->elements.count();
610 if (d->pushElements(elements)) {
611 d->depthChange(newDepth: d->elements.count(), oldDepth);
612 QQuickStackElement *enter = d->elements.top();
613 d->startTransition(first: QQuickStackTransition::pushEnter(operation, element: enter, view: this),
614 second: QQuickStackTransition::pushExit(operation, element: exit, view: this),
615 immediate: operation == Immediate);
616 d->setCurrentItem(enter);
617 }
618
619 if (d->currentItem) {
620 QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(engine: v4, object: d->currentItem));
621 args->setReturnValue(rv->asReturnedValue());
622 } else {
623 args->setReturnValue(QV4::Encode::null());
624 }
625}
626
627/*!
628 \qmlmethod Item QtQuick.Controls::StackView::pop(item, operation)
629
630 Pops one or more items off the stack. Returns the last item removed from the stack.
631
632 If the \a item argument is specified, all items down to (but not
633 including) \a item will be popped. If \a item is \c null, all
634 items down to (but not including) the first item is popped.
635 If not specified, only the current item is popped.
636
637 \note A pop() operation on a stack with depth 1 or 0 does nothing. In such
638 cases, the stack can be emptied using the \l clear() method.
639
640 \include qquickstackview.qdocinc pop-ownership
641
642 An \a operation can be optionally specified as the last argument. Supported
643 operations:
644
645 \value StackView.Transition An operation with default transitions (default).
646 \value StackView.Immediate An immediate operation without transitions.
647 \value StackView.PushTransition An operation with push transitions (since QtQuick.Controls 2.1).
648 \value StackView.ReplaceTransition An operation with replace transitions (since QtQuick.Controls 2.1).
649 \value StackView.PopTransition An operation with pop transitions (since QtQuick.Controls 2.1).
650
651 Examples:
652 \code
653 stackView.pop()
654 stackView.pop(someItem, StackView.Immediate)
655 stackView.pop(StackView.Immediate)
656 stackView.pop(null)
657 \endcode
658
659 \sa clear(), {Popping Items}, {Unwinding Items via Pop}
660*/
661void QQuickStackView::pop(QQmlV4Function *args)
662{
663 Q_D(QQuickStackView);
664 const QString operationName = QStringLiteral("pop");
665 if (d->modifyingElements) {
666 d->warnOfInterruption(attemptedOperation: operationName);
667 args->setReturnValue(QV4::Encode::null());
668 return;
669 }
670
671 QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true);
672 QScopedValueRollback<QString> operationNameRollback(d->operation, operationName);
673 int argc = args->length();
674 if (d->elements.count() <= 1 || argc > 2) {
675 if (argc > 2)
676 d->warn(QStringLiteral("too many arguments"));
677 args->setReturnValue(QV4::Encode::null());
678 return;
679 }
680
681 int oldDepth = d->elements.count();
682 QQuickStackElement *exit = d->elements.pop();
683 QQuickStackElement *enter = d->elements.top();
684
685 QV4::ExecutionEngine *v4 = args->v4engine();
686 QV4::Scope scope(v4);
687
688 if (argc > 0) {
689 QV4::ScopedValue value(scope, (*args)[0]);
690 if (value->isNull()) {
691 enter = d->elements.value(i: 0);
692 } else if (const QV4::QObjectWrapper *o = value->as<QV4::QObjectWrapper>()) {
693 QQuickItem *item = qobject_cast<QQuickItem *>(object: o->object());
694 enter = d->findElement(item);
695 if (!enter) {
696 if (item != d->currentItem)
697 d->warn(QStringLiteral("unknown argument: ") + value->toQString()); // TODO: safe?
698 args->setReturnValue(QV4::Encode::null());
699 d->elements.push(t: exit); // restore
700 return;
701 }
702 }
703 }
704
705 Operation operation = PopTransition;
706 if (argc > 0) {
707 QV4::ScopedValue lastArg(scope, (*args)[argc - 1]);
708 if (lastArg->isInt32())
709 operation = static_cast<Operation>(lastArg->toInt32());
710 }
711
712 QQuickItem *previousItem = nullptr;
713
714 if (d->popElements(element: enter)) {
715 if (exit) {
716 exit->removal = true;
717 d->removing.insert(value: exit);
718 previousItem = exit->item;
719 }
720 d->depthChange(newDepth: d->elements.count(), oldDepth);
721 d->startTransition(first: QQuickStackTransition::popExit(operation, element: exit, view: this),
722 second: QQuickStackTransition::popEnter(operation, element: enter, view: this),
723 immediate: operation == Immediate);
724 d->setCurrentItem(enter);
725 }
726
727 if (previousItem) {
728 QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(engine: v4, object: previousItem));
729 args->setReturnValue(rv->asReturnedValue());
730 } else {
731 args->setReturnValue(QV4::Encode::null());
732 }
733}
734
735/*!
736 \qmlmethod Item QtQuick.Controls::StackView::replace(target, item, properties, operation)
737
738 Replaces one or more items on the stack with the specified \a item and
739 \a operation, and optionally applies a set of \a properties on the
740 item. The item can be an \l Item, \l Component, or a \l [QML] url.
741 Returns the item that became current.
742
743 \include qquickstackview.qdocinc pop-ownership
744
745 If the \a target argument is specified, all items down to the \target
746 item will be replaced. If \a target is \c null, all items in the stack
747 will be replaced. If not specified, only the top item will be replaced.
748
749 StackView creates an instance automatically if the replacing item is a \l Component,
750 or a \l [QML] url. The optional \a properties argument specifies a map of initial
751 property values for the replacing item. For dynamically created items, these values
752 are applied before the creation is finalized. This is more efficient than setting
753 property values after creation, particularly where large sets of property values
754 are defined, and also allows property bindings to be set up (using \l{Qt::binding}
755 {Qt.binding()}) before the item is created.
756
757 Replace the top item:
758 \code
759 stackView.replace(rect)
760
761 // or with properties:
762 stackView.replace(rect, {"color": "red"})
763 \endcode
764
765 Multiple items can be replaced at the same time either by passing them as
766 additional arguments, or as an array. Each item can be followed by a set
767 of properties to apply.
768
769 Passing a variable amount of arguments:
770 \code
771 stackView.replace(rect1, rect2, rect3)
772
773 // or with properties:
774 stackView.replace(rect1, {"color": "red"}, rect2, {"color": "green"}, rect3, {"color": "blue"})
775 \endcode
776
777 Replacing an array of items:
778 \code
779 stackView.replace([rect1, rect2, rect3])
780
781 // or with properties:
782 stackView.replace([rect1, {"color": "red"}, rect2, {"color": "green"}, rect3, {"color": "blue"}])
783 \endcode
784
785 An \a operation can be optionally specified as the last argument. Supported
786 operations:
787
788 \value StackView.Transition An operation with default transitions (default).
789 \value StackView.Immediate An immediate operation without transitions.
790 \value StackView.PushTransition An operation with push transitions (since QtQuick.Controls 2.1).
791 \value StackView.ReplaceTransition An operation with replace transitions (since QtQuick.Controls 2.1).
792 \value StackView.PopTransition An operation with pop transitions (since QtQuick.Controls 2.1).
793
794 The following example illustrates the use of push and pop transitions with replace().
795
796 \code
797 StackView {
798 id: stackView
799
800 initialItem: Component {
801 id: page
802
803 Page {
804 Row {
805 spacing: 20
806 anchors.centerIn: parent
807
808 Button {
809 text: "<"
810 onClicked: stackView.replace(page, StackView.PopTransition)
811 }
812 Button {
813 text: ">"
814 onClicked: stackView.replace(page, StackView.PushTransition)
815 }
816 }
817 }
818 }
819 }
820 \endcode
821
822 \sa push(), {Replacing Items}
823*/
824void QQuickStackView::replace(QQmlV4Function *args)
825{
826 Q_D(QQuickStackView);
827 const QString operationName = QStringLiteral("replace");
828 if (d->modifyingElements) {
829 d->warnOfInterruption(attemptedOperation: operationName);
830 args->setReturnValue(QV4::Encode::null());
831 return;
832 }
833
834 QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true);
835 QScopedValueRollback<QString> operationNameRollback(d->operation, operationName);
836 if (args->length() <= 0) {
837 d->warn(QStringLiteral("missing arguments"));
838 args->setReturnValue(QV4::Encode::null());
839 return;
840 }
841
842 QV4::ExecutionEngine *v4 = args->v4engine();
843 QV4::Scope scope(v4);
844
845 Operation operation = d->elements.isEmpty() ? Immediate : ReplaceTransition;
846 QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]);
847 if (lastArg->isInt32())
848 operation = static_cast<Operation>(lastArg->toInt32());
849
850 QQuickStackElement *target = nullptr;
851 QV4::ScopedValue firstArg(scope, (*args)[0]);
852 if (firstArg->isNull())
853 target = d->elements.value(i: 0);
854 else if (!firstArg->isInt32())
855 target = d->findElement(value: firstArg);
856
857 QStringList errors;
858 QList<QQuickStackElement *> elements = d->parseElements(from: target ? 1 : 0, args, errors: &errors);
859 if (!errors.isEmpty() || elements.isEmpty()) {
860 if (!errors.isEmpty()) {
861 for (const QString &error : qAsConst(t&: errors))
862 d->warn(error);
863 } else {
864 d->warn(QStringLiteral("nothing to push"));
865 }
866 args->setReturnValue(QV4::Encode::null());
867 return;
868 }
869
870 int oldDepth = d->elements.count();
871 QQuickStackElement* exit = nullptr;
872 if (!d->elements.isEmpty())
873 exit = d->elements.pop();
874
875 if (exit != target ? d->replaceElements(element: target, elements) : d->pushElements(elements)) {
876 d->depthChange(newDepth: d->elements.count(), oldDepth);
877 if (exit) {
878 exit->removal = true;
879 d->removing.insert(value: exit);
880 }
881 QQuickStackElement *enter = d->elements.top();
882 d->startTransition(first: QQuickStackTransition::replaceExit(operation, element: exit, view: this),
883 second: QQuickStackTransition::replaceEnter(operation, element: enter, view: this),
884 immediate: operation == Immediate);
885 d->setCurrentItem(enter);
886 }
887
888 if (d->currentItem) {
889 QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(engine: v4, object: d->currentItem));
890 args->setReturnValue(rv->asReturnedValue());
891 } else {
892 args->setReturnValue(QV4::Encode::null());
893 }
894}
895
896/*!
897 \since QtQuick.Controls 2.3 (Qt 5.10)
898 \qmlproperty bool QtQuick.Controls::StackView::empty
899 \readonly
900
901 This property holds whether the stack is empty.
902
903 \sa depth
904*/
905bool QQuickStackView::isEmpty() const
906{
907 Q_D(const QQuickStackView);
908 return d->elements.isEmpty();
909}
910
911/*!
912 \qmlmethod void QtQuick.Controls::StackView::clear(transition)
913
914 Removes all items from the stack.
915
916 \include qquickstackview.qdocinc pop-ownership
917
918 Since QtQuick.Controls 2.3, a \a transition can be optionally specified. Supported transitions:
919
920 \value StackView.Immediate Clear the stack immediately without any transition (default).
921 \value StackView.PushTransition Clear the stack with a push transition.
922 \value StackView.ReplaceTransition Clear the stack with a replace transition.
923 \value StackView.PopTransition Clear the stack with a pop transition.
924*/
925void QQuickStackView::clear(Operation operation)
926{
927 Q_D(QQuickStackView);
928 if (d->elements.isEmpty())
929 return;
930
931 const QString operationName = QStringLiteral("clear");
932 if (d->modifyingElements) {
933 d->warnOfInterruption(attemptedOperation: operationName);
934 return;
935 }
936
937 QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true);
938 QScopedValueRollback<QString> operationNameRollback(d->operation, operationName);
939 if (operation != Immediate) {
940 QQuickStackElement *exit = d->elements.pop();
941 exit->removal = true;
942 d->removing.insert(value: exit);
943 d->startTransition(first: QQuickStackTransition::popExit(operation, element: exit, view: this),
944 second: QQuickStackTransition::popEnter(operation, element: nullptr, view: this), immediate: false);
945 }
946
947 int oldDepth = d->elements.count();
948 d->setCurrentItem(nullptr);
949 qDeleteAll(c: d->elements);
950 d->elements.clear();
951 d->depthChange(newDepth: 0, oldDepth);
952}
953
954/*!
955 \qmlproperty var QtQuick.Controls::StackView::initialItem
956
957 This property holds the initial item that should be shown when the StackView
958 is created. The initial item can be an \l Item, \l Component, or a \l [QML] url.
959 Specifying an initial item is equivalent to:
960 \code
961 Component.onCompleted: stackView.push(myInitialItem)
962 \endcode
963
964 \sa push()
965*/
966QJSValue QQuickStackView::initialItem() const
967{
968 Q_D(const QQuickStackView);
969 return d->initialItem;
970}
971
972void QQuickStackView::setInitialItem(const QJSValue &item)
973{
974 Q_D(QQuickStackView);
975 d->initialItem = item;
976}
977
978/*!
979 \qmlproperty Transition QtQuick.Controls::StackView::popEnter
980
981 This property holds the transition that is applied to the item that
982 enters the stack when another item is popped off of it.
983
984 \sa {Customizing StackView}
985*/
986QQuickTransition *QQuickStackView::popEnter() const
987{
988 Q_D(const QQuickStackView);
989 if (d->transitioner)
990 return d->transitioner->removeDisplacedTransition;
991 return nullptr;
992}
993
994void QQuickStackView::setPopEnter(QQuickTransition *enter)
995{
996 Q_D(QQuickStackView);
997 d->ensureTransitioner();
998 if (d->transitioner->removeDisplacedTransition == enter)
999 return;
1000
1001 d->transitioner->removeDisplacedTransition = enter;
1002 emit popEnterChanged();
1003}
1004
1005/*!
1006 \qmlproperty Transition QtQuick.Controls::StackView::popExit
1007
1008 This property holds the transition that is applied to the item that
1009 exits the stack when the item is popped off of it.
1010
1011 \sa {Customizing StackView}
1012*/
1013QQuickTransition *QQuickStackView::popExit() const
1014{
1015 Q_D(const QQuickStackView);
1016 if (d->transitioner)
1017 return d->transitioner->removeTransition;
1018 return nullptr;
1019}
1020
1021void QQuickStackView::setPopExit(QQuickTransition *exit)
1022{
1023 Q_D(QQuickStackView);
1024 d->ensureTransitioner();
1025 if (d->transitioner->removeTransition == exit)
1026 return;
1027
1028 d->transitioner->removeTransition = exit;
1029 emit popExitChanged();
1030}
1031
1032/*!
1033 \qmlproperty Transition QtQuick.Controls::StackView::pushEnter
1034
1035 This property holds the transition that is applied to the item that
1036 enters the stack when the item is pushed onto it.
1037
1038 \sa {Customizing StackView}
1039*/
1040QQuickTransition *QQuickStackView::pushEnter() const
1041{
1042 Q_D(const QQuickStackView);
1043 if (d->transitioner)
1044 return d->transitioner->addTransition;
1045 return nullptr;
1046}
1047
1048void QQuickStackView::setPushEnter(QQuickTransition *enter)
1049{
1050 Q_D(QQuickStackView);
1051 d->ensureTransitioner();
1052 if (d->transitioner->addTransition == enter)
1053 return;
1054
1055 d->transitioner->addTransition = enter;
1056 emit pushEnterChanged();
1057}
1058
1059/*!
1060 \qmlproperty Transition QtQuick.Controls::StackView::pushExit
1061
1062 This property holds the transition that is applied to the item that
1063 exits the stack when another item is pushed onto it.
1064
1065 \sa {Customizing StackView}
1066*/
1067QQuickTransition *QQuickStackView::pushExit() const
1068{
1069 Q_D(const QQuickStackView);
1070 if (d->transitioner)
1071 return d->transitioner->addDisplacedTransition;
1072 return nullptr;
1073}
1074
1075void QQuickStackView::setPushExit(QQuickTransition *exit)
1076{
1077 Q_D(QQuickStackView);
1078 d->ensureTransitioner();
1079 if (d->transitioner->addDisplacedTransition == exit)
1080 return;
1081
1082 d->transitioner->addDisplacedTransition = exit;
1083 emit pushExitChanged();
1084}
1085
1086/*!
1087 \qmlproperty Transition QtQuick.Controls::StackView::replaceEnter
1088
1089 This property holds the transition that is applied to the item that
1090 enters the stack when another item is replaced by it.
1091
1092 \sa {Customizing StackView}
1093*/
1094QQuickTransition *QQuickStackView::replaceEnter() const
1095{
1096 Q_D(const QQuickStackView);
1097 if (d->transitioner)
1098 return d->transitioner->moveTransition;
1099 return nullptr;
1100}
1101
1102void QQuickStackView::setReplaceEnter(QQuickTransition *enter)
1103{
1104 Q_D(QQuickStackView);
1105 d->ensureTransitioner();
1106 if (d->transitioner->moveTransition == enter)
1107 return;
1108
1109 d->transitioner->moveTransition = enter;
1110 emit replaceEnterChanged();
1111}
1112
1113/*!
1114 \qmlproperty Transition QtQuick.Controls::StackView::replaceExit
1115
1116 This property holds the transition that is applied to the item that
1117 exits the stack when it is replaced by another item.
1118
1119 \sa {Customizing StackView}
1120*/
1121QQuickTransition *QQuickStackView::replaceExit() const
1122{
1123 Q_D(const QQuickStackView);
1124 if (d->transitioner)
1125 return d->transitioner->moveDisplacedTransition;
1126 return nullptr;
1127}
1128
1129void QQuickStackView::setReplaceExit(QQuickTransition *exit)
1130{
1131 Q_D(QQuickStackView);
1132 d->ensureTransitioner();
1133 if (d->transitioner->moveDisplacedTransition == exit)
1134 return;
1135
1136 d->transitioner->moveDisplacedTransition = exit;
1137 emit replaceExitChanged();
1138}
1139
1140void QQuickStackView::componentComplete()
1141{
1142 QQuickControl::componentComplete();
1143
1144 Q_D(QQuickStackView);
1145 QScopedValueRollback<QString> operationNameRollback(d->operation, QStringLiteral("initialItem"));
1146 QQuickStackElement *element = nullptr;
1147 QString error;
1148 int oldDepth = d->elements.count();
1149 if (QObject *o = d->initialItem.toQObject())
1150 element = QQuickStackElement::fromObject(object: o, view: this, error: &error);
1151 else if (d->initialItem.isString())
1152 element = QQuickStackElement::fromString(str: d->initialItem.toString(), view: this, error: &error);
1153 if (!error.isEmpty()) {
1154 d->warn(error);
1155 delete element;
1156 } else if (d->pushElement(element)) {
1157 d->depthChange(newDepth: d->elements.count(), oldDepth);
1158 d->setCurrentItem(element);
1159 element->setStatus(QQuickStackView::Active);
1160 }
1161}
1162
1163void QQuickStackView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
1164{
1165 QQuickControl::geometryChanged(newGeometry, oldGeometry);
1166
1167 Q_D(QQuickStackView);
1168 for (QQuickStackElement *element : qAsConst(t&: d->elements)) {
1169 if (element->item) {
1170 if (!element->widthValid)
1171 element->item->setWidth(newGeometry.width());
1172 if (!element->heightValid)
1173 element->item->setHeight(newGeometry.height());
1174 }
1175 }
1176}
1177
1178bool QQuickStackView::childMouseEventFilter(QQuickItem *item, QEvent *event)
1179{
1180 // in order to block accidental user interaction while busy/transitioning,
1181 // StackView filters out childrens' mouse events. therefore we block all
1182 // press events. however, since push() may be called from signal handlers
1183 // such as onPressed or onDoubleClicked, we must let the current mouse
1184 // grabber item receive the respective mouse release event to avoid
1185 // breaking its state (QTBUG-50305).
1186 if (event->type() == QEvent::MouseButtonPress)
1187 return true;
1188 if (event->type() == QEvent::UngrabMouse)
1189 return false;
1190 QQuickWindow *window = item->window();
1191 return window && !window->mouseGrabberItem();
1192}
1193
1194#if QT_CONFIG(quicktemplates2_multitouch)
1195void QQuickStackView::touchEvent(QTouchEvent *event)
1196{
1197 event->ignore(); // QTBUG-65084
1198}
1199#endif
1200
1201#if QT_CONFIG(accessibility)
1202QAccessible::Role QQuickStackView::accessibleRole() const
1203{
1204 return QAccessible::LayeredPane;
1205}
1206#endif
1207
1208void QQuickStackViewAttachedPrivate::itemParentChanged(QQuickItem *item, QQuickItem *parent)
1209{
1210 Q_Q(QQuickStackViewAttached);
1211 int oldIndex = element ? element->index : -1;
1212 QQuickStackView *oldView = element ? element->view : nullptr;
1213 QQuickStackView::Status oldStatus = element ? element->status : QQuickStackView::Inactive;
1214
1215 QQuickStackView *newView = qobject_cast<QQuickStackView *>(object: parent);
1216 element = newView ? QQuickStackViewPrivate::get(view: newView)->findElement(item) : nullptr;
1217
1218 int newIndex = element ? element->index : -1;
1219 QQuickStackView::Status newStatus = element ? element->status : QQuickStackView::Inactive;
1220
1221 if (oldIndex != newIndex)
1222 emit q->indexChanged();
1223 if (oldView != newView)
1224 emit q->viewChanged();
1225 if (oldStatus != newStatus)
1226 emit q->statusChanged();
1227}
1228
1229QQuickStackViewAttached::QQuickStackViewAttached(QObject *parent)
1230 : QObject(*(new QQuickStackViewAttachedPrivate), parent)
1231{
1232 Q_D(QQuickStackViewAttached);
1233 QQuickItem *item = qobject_cast<QQuickItem *>(object: parent);
1234 if (item) {
1235 connect(sender: item, signal: &QQuickItem::visibleChanged, receiver: this, slot: &QQuickStackViewAttached::visibleChanged);
1236 QQuickItemPrivate::get(item)->addItemChangeListener(listener: d, types: QQuickItemPrivate::Parent);
1237 d->itemParentChanged(item, parent: item->parentItem());
1238 } else if (parent) {
1239 qmlWarning(me: parent) << "StackView must be attached to an Item";
1240 }
1241}
1242
1243QQuickStackViewAttached::~QQuickStackViewAttached()
1244{
1245 Q_D(QQuickStackViewAttached);
1246 QQuickItem *parentItem = qobject_cast<QQuickItem *>(object: parent());
1247 if (parentItem)
1248 QQuickItemPrivate::get(item: parentItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Parent);
1249}
1250
1251/*!
1252 \qmlattachedproperty int QtQuick.Controls::StackView::index
1253 \readonly
1254
1255 This attached property holds the stack index of the item it's
1256 attached to, or \c -1 if the item is not in a stack.
1257*/
1258int QQuickStackViewAttached::index() const
1259{
1260 Q_D(const QQuickStackViewAttached);
1261 return d->element ? d->element->index : -1;
1262}
1263
1264/*!
1265 \qmlattachedproperty StackView QtQuick.Controls::StackView::view
1266 \readonly
1267
1268 This attached property holds the stack view of the item it's
1269 attached to, or \c null if the item is not in a stack.
1270*/
1271QQuickStackView *QQuickStackViewAttached::view() const
1272{
1273 Q_D(const QQuickStackViewAttached);
1274 return d->element ? d->element->view : nullptr;
1275}
1276
1277/*!
1278 \qmlattachedproperty enumeration QtQuick.Controls::StackView::status
1279 \readonly
1280
1281 This attached property holds the stack status of the item it's
1282 attached to, or \c StackView.Inactive if the item is not in a stack.
1283
1284 Available values:
1285 \value StackView.Inactive The item is inactive (or not in a stack).
1286 \value StackView.Deactivating The item is being deactivated (popped off).
1287 \value StackView.Activating The item is being activated (becoming the current item).
1288 \value StackView.Active The item is active, that is, the current item.
1289*/
1290QQuickStackView::Status QQuickStackViewAttached::status() const
1291{
1292 Q_D(const QQuickStackViewAttached);
1293 return d->element ? d->element->status : QQuickStackView::Inactive;
1294}
1295
1296/*!
1297 \since QtQuick.Controls 2.2 (Qt 5.9)
1298 \qmlattachedproperty bool QtQuick.Controls::StackView::visible
1299
1300 This attached property holds the visibility of the item it's attached to.
1301 The value follows the value of \l Item::visible.
1302
1303 By default, StackView shows incoming items when the enter transition begins,
1304 and hides outgoing items when the exit transition ends. Setting this property
1305 explicitly allows the default behavior to be overridden, making it possible
1306 to keep items that are below the top-most item visible.
1307
1308 \note The default transitions of most styles slide outgoing items outside the
1309 view, and may also animate their opacity. In order to keep a full stack
1310 of items visible, consider customizing the \l transitions so that the
1311 items underneath can be seen.
1312
1313 \image qtquickcontrols2-stackview-visible.png
1314
1315 \snippet qtquickcontrols2-stackview-visible.qml 1
1316*/
1317bool QQuickStackViewAttached::isVisible() const
1318{
1319 const QQuickItem *parentItem = qobject_cast<QQuickItem *>(object: parent());
1320 return parentItem && parentItem->isVisible();
1321}
1322
1323void QQuickStackViewAttached::setVisible(bool visible)
1324{
1325 Q_D(QQuickStackViewAttached);
1326 d->explicitVisible = true;
1327 QQuickItem *parentItem = qobject_cast<QQuickItem *>(object: parent());
1328 if (parentItem)
1329 parentItem->setVisible(visible);
1330}
1331
1332void QQuickStackViewAttached::resetVisible()
1333{
1334 Q_D(QQuickStackViewAttached);
1335 d->explicitVisible = false;
1336 if (!d->element || !d->element->view)
1337 return;
1338
1339 QQuickItem *parentItem = qobject_cast<QQuickItem *>(object: parent());
1340 if (parentItem)
1341 parentItem->setVisible(parentItem == d->element->view->currentItem());
1342}
1343
1344/*!
1345 \qmlattachedsignal QtQuick.Controls::StackView::activated()
1346 \since QtQuick.Controls 2.1 (Qt 5.8)
1347
1348 This attached signal is emitted when the item it's attached to is activated in the stack.
1349
1350 \sa status
1351*/
1352
1353/*!
1354 \qmlattachedsignal QtQuick.Controls::StackView::deactivated()
1355 \since QtQuick.Controls 2.1 (Qt 5.8)
1356
1357 This attached signal is emitted when the item it's attached to is deactivated in the stack.
1358
1359 \sa status
1360*/
1361
1362/*!
1363 \qmlattachedsignal QtQuick.Controls::StackView::activating()
1364 \since QtQuick.Controls 2.1 (Qt 5.8)
1365
1366 This attached signal is emitted when the item it's attached to is in the process of being
1367 activated in the stack.
1368
1369 \sa status
1370*/
1371
1372/*!
1373 \qmlattachedsignal QtQuick.Controls::StackView::deactivating()
1374 \since QtQuick.Controls 2.1 (Qt 5.8)
1375
1376 This attached signal is emitted when the item it's attached to is in the process of being
1377 dectivated in the stack.
1378
1379 \sa status
1380*/
1381
1382/*!
1383 \qmlattachedsignal QtQuick.Controls::StackView::removed()
1384 \since QtQuick.Controls 2.1 (Qt 5.8)
1385
1386 This attached signal is emitted when the item it's attached to has been
1387 removed from the stack. It can be used to safely destroy an Item that was
1388 pushed onto the stack, for example:
1389
1390 \code
1391 Item {
1392 StackView.onRemoved: destroy() // Will be destroyed sometime after this call.
1393 }
1394 \endcode
1395
1396 \sa status
1397*/
1398
1399QT_END_NAMESPACE
1400

source code of qtquickcontrols2/src/quicktemplates2/qquickstackview.cpp