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

source code of qtdeclarative/src/quicktemplates/qquickstackview.cpp