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_p.h"
5#include "qquickstackelement_p_p.h"
6#if QT_CONFIG(quick_viewtransitions)
7#include "qquickstacktransition_p_p.h"
8#endif
9
10#include <QtQml/qqmlinfo.h>
11#include <QtQml/qqmllist.h>
12#include <QtQml/private/qv4qmlcontext_p.h>
13#include <QtQml/private/qv4qobjectwrapper_p.h>
14#include <QtQml/private/qv4variantobject_p.h>
15#include <QtQml/private/qv4urlobject_p.h>
16#include <QtQuick/private/qquickanimation_p.h>
17#include <QtQuick/private/qquicktransition_p.h>
18
19QT_BEGIN_NAMESPACE
20
21void QQuickStackViewPrivate::warn(const QString &error)
22{
23 Q_Q(QQuickStackView);
24 if (operation.isEmpty())
25 qmlWarning(me: q) << error;
26 else
27 qmlWarning(me: q) << operation << ": " << error;
28}
29
30void QQuickStackViewPrivate::warnOfInterruption(const QString &attemptedOperation)
31{
32 Q_Q(QQuickStackView);
33 qmlWarning(me: q) << "cannot " << attemptedOperation << " while already in the process of completing a " << operation;
34}
35
36void QQuickStackViewPrivate::setCurrentItem(QQuickStackElement *element)
37{
38 Q_Q(QQuickStackView);
39 QQuickItem *item = element ? element->item : nullptr;
40 if (currentItem == item)
41 return;
42
43 currentItem = item;
44 if (element)
45 element->setVisible(true);
46 if (item)
47 item->setFocus(true);
48 emit q->currentItemChanged();
49}
50
51static bool initProperties(QQuickStackElement *element, const QV4::Value &props, QQmlV4Function *args)
52{
53 if (props.isObject()) {
54 const QV4::QObjectWrapper *wrapper = props.as<QV4::QObjectWrapper>();
55 if (!wrapper) {
56 QV4::ExecutionEngine *v4 = args->v4engine();
57 element->properties.set(engine: v4, value: props);
58 element->qmlCallingContext.set(engine: v4, obj: v4->qmlContext());
59 return true;
60 }
61 }
62 return false;
63}
64
65QList<QQuickStackElement *> QQuickStackViewPrivate::parseElements(int from, QQmlV4Function *args, QStringList *errors)
66{
67 QV4::ExecutionEngine *v4 = args->v4engine();
68 auto context = v4->callingQmlContext();
69 QV4::Scope scope(v4);
70
71 QList<QQuickStackElement *> elements;
72
73 int argc = args->length();
74 for (int i = from; i < argc; ++i) {
75 QV4::ScopedValue arg(scope, (*args)[i]);
76 if (QV4::ArrayObject *array = arg->as<QV4::ArrayObject>()) {
77 const uint len = uint(array->getLength());
78 for (uint j = 0; j < len; ++j) {
79 QString error;
80 QV4::ScopedValue value(scope, array->get(idx: j));
81 QQuickStackElement *element = createElement(value, context, error: &error);
82 if (element) {
83 if (j < len - 1) {
84 QV4::ScopedValue props(scope, array->get(idx: j + 1));
85 if (initProperties(element, props, args))
86 ++j;
87 }
88 elements += element;
89 } else if (!error.isEmpty()) {
90 *errors += error;
91 }
92 }
93 } else {
94 QString error;
95 QQuickStackElement *element = createElement(value: arg, context, error: &error);
96 if (element) {
97 if (i < argc - 1) {
98 QV4::ScopedValue props(scope, (*args)[i + 1]);
99 if (initProperties(element, props, args))
100 ++i;
101 }
102 elements += element;
103 } else if (!error.isEmpty()) {
104 *errors += error;
105 }
106 }
107 }
108 return elements;
109}
110
111QQuickStackElement *QQuickStackViewPrivate::findElement(QQuickItem *item) const
112{
113 if (item) {
114 for (QQuickStackElement *e : std::as_const(t: elements)) {
115 if (e->item == item)
116 return e;
117 }
118 }
119 return nullptr;
120}
121
122QQuickStackElement *QQuickStackViewPrivate::findElement(const QV4::Value &value) const
123{
124 if (const QV4::QObjectWrapper *o = value.as<QV4::QObjectWrapper>())
125 return findElement(item: qobject_cast<QQuickItem *>(o: o->object()));
126 return nullptr;
127}
128
129static QUrl resolvedUrl(const QUrl &url, const QQmlRefPointer<QQmlContextData> &context)
130{
131 if (url.isRelative())
132 return context->resolvedUrl(url).toString();
133 return url;
134}
135
136static QString resolvedUrl(const QString &str, const QQmlRefPointer<QQmlContextData> &context)
137{
138 QUrl url(str);
139 if (url.isRelative())
140 return context->resolvedUrl(url).toString();
141 return str;
142}
143
144QQuickStackElement *QQuickStackViewPrivate::createElement(const QV4::Value &value, const QQmlRefPointer<QQmlContextData> &context, QString *error)
145{
146 Q_Q(QQuickStackView);
147 if (const QV4::String *s = value.as<QV4::String>())
148 return QQuickStackElement::fromString(str: resolvedUrl(str: s->toQString(), context), view: q, error);
149 if (const QV4::QObjectWrapper *o = value.as<QV4::QObjectWrapper>())
150 return QQuickStackElement::fromObject(object: o->object(), view: q, error);
151 if (const QV4::UrlObject *u = value.as<QV4::UrlObject>())
152 return QQuickStackElement::fromString(str: resolvedUrl(str: u->href(), context), view: q, error);
153
154 if (value.as<QV4::Object>()) {
155 const QVariant data = QV4::ExecutionEngine::toVariant(value, typeHint: QMetaType::fromType<QUrl>());
156 if (data.typeId() == QMetaType::QUrl) {
157 return QQuickStackElement::fromString(str: resolvedUrl(url: data.toUrl(), context).toString(), view: q,
158 error);
159 }
160 }
161
162 return nullptr;
163}
164
165bool QQuickStackViewPrivate::pushElements(const QList<QQuickStackElement *> &elems)
166{
167 Q_Q(QQuickStackView);
168 if (!elems.isEmpty()) {
169 for (QQuickStackElement *e : elems) {
170 e->setIndex(elements.size());
171 elements += e;
172 }
173 return elements.top()->load(parent: q);
174 }
175 return false;
176}
177
178bool QQuickStackViewPrivate::pushElement(QQuickStackElement *element)
179{
180 if (element)
181 return pushElements(elems: QList<QQuickStackElement *>() << element);
182 return false;
183}
184
185bool QQuickStackViewPrivate::popElements(QQuickStackElement *element)
186{
187 Q_Q(QQuickStackView);
188 while (elements.size() > 1 && elements.top() != element) {
189 delete elements.pop();
190 if (!element)
191 break;
192 }
193 return elements.top()->load(parent: q);
194}
195
196bool QQuickStackViewPrivate::replaceElements(QQuickStackElement *target, const QList<QQuickStackElement *> &elems)
197{
198 if (target) {
199 while (!elements.isEmpty()) {
200 QQuickStackElement* top = elements.pop();
201 delete top;
202 if (top == target)
203 break;
204 }
205 }
206 return pushElements(elems);
207}
208
209#if QT_CONFIG(quick_viewtransitions)
210void QQuickStackViewPrivate::ensureTransitioner()
211{
212 if (!transitioner) {
213 transitioner = new QQuickItemViewTransitioner;
214 transitioner->setChangeListener(this);
215 }
216}
217
218void QQuickStackViewPrivate::startTransition(const QQuickStackTransition &first, const QQuickStackTransition &second, bool immediate)
219{
220 if (first.element)
221 first.element->transitionNextReposition(transitioner, type: first.type, asTarget: first.target);
222 if (second.element)
223 second.element->transitionNextReposition(transitioner, type: second.type, asTarget: second.target);
224
225 if (first.element) {
226 // Let the check for immediate happen after prepareTransition() is
227 // called, because we need the prepared transition in both branches.
228 // Same for the second element.
229 if (!first.element->item || !first.element->prepareTransition(transitioner, viewBounds: first.viewBounds) || immediate)
230 completeTransition(element: first.element, transition: first.transition, status: first.status);
231 else
232 first.element->startTransition(transitioner, status: first.status);
233 }
234 if (second.element) {
235 if (!second.element->item || !second.element->prepareTransition(transitioner, viewBounds: second.viewBounds) || immediate)
236 completeTransition(element: second.element, transition: second.transition, status: second.status);
237 else
238 second.element->startTransition(transitioner, status: second.status);
239 }
240
241 if (transitioner) {
242 setBusy(!transitioner->runningJobs.isEmpty());
243 transitioner->resetTargetLists();
244 }
245}
246
247void QQuickStackViewPrivate::completeTransition(QQuickStackElement *element, QQuickTransition *transition, QQuickStackView::Status status)
248{
249 element->setStatus(status);
250 if (transition) {
251 if (element->prepared) {
252 // Here we force reading all the animations, even if the desired
253 // transition type is StackView.Immediate. After that we force
254 // all the animations to complete immediately, without waiting for
255 // the animation timer.
256 // This allows us to correctly restore all the properties affected
257 // by the push/pop animations.
258 ACTION_IF_DELETED(element, element->completeTransition(transition), return);
259 } else if (element->item) {
260 // At least try to move the item to its desired place. This,
261 // however, is only a partly correct solution, because a lot more
262 // properties can be affected by the transition
263 element->item->setPosition(element->nextTransitionTo);
264 }
265 }
266 viewItemTransitionFinished(item: element);
267}
268
269void QQuickStackViewPrivate::viewItemTransitionFinished(QQuickItemViewTransitionableItem *transitionable)
270{
271 QQuickStackElement *element = static_cast<QQuickStackElement *>(transitionable);
272 if (element->status == QQuickStackView::Activating) {
273 element->setStatus(QQuickStackView::Active);
274 } else if (element->status == QQuickStackView::Deactivating) {
275 element->setStatus(QQuickStackView::Inactive);
276 QQuickStackElement *existingElement = element->item ? findElement(item: element->item) : nullptr;
277 // If a different element with the same item is found,
278 // do not call setVisible(false) since it needs to be visible.
279 if (!existingElement || element == existingElement)
280 element->setVisible(false);
281 if (element->removal || element->isPendingRemoval())
282 removed += element;
283 }
284
285 if (transitioner && transitioner->runningJobs.isEmpty()) {
286 // ~QQuickStackElement() emits QQuickStackViewAttached::removed(), which may be used
287 // to modify the stack. Set the status first and make a copy of the destroyable stack
288 // elements to exclude any modifications that may happen during qDeleteAll(). (QTBUG-62153)
289 setBusy(false);
290 QList<QQuickStackElement*> removedElements = removed;
291 removed.clear();
292
293 for (QQuickStackElement *removedElement : std::as_const(t&: removedElements)) {
294 // If an element with the same item is found in the active stack list,
295 // forget about the item so that we don't hide it.
296 if (removedElement->item && findElement(item: removedElement->item)) {
297 QQuickItemPrivate::get(item: removedElement->item)->removeItemChangeListener(removedElement, types: QQuickItemPrivate::Destroyed);
298 removedElement->item = nullptr;
299 }
300 }
301
302 qDeleteAll(c: removedElements);
303 }
304
305 removing.remove(value: element);
306}
307#endif
308
309void QQuickStackViewPrivate::setBusy(bool b)
310{
311 Q_Q(QQuickStackView);
312 if (busy == b)
313 return;
314
315 busy = b;
316 q->setFiltersChildMouseEvents(busy);
317 emit q->busyChanged();
318}
319
320void QQuickStackViewPrivate::depthChange(int newDepth, int oldDepth)
321{
322 Q_Q(QQuickStackView);
323 if (newDepth == oldDepth)
324 return;
325
326 emit q->depthChanged();
327 if (newDepth == 0 || oldDepth == 0)
328 emit q->emptyChanged();
329}
330
331QT_END_NAMESPACE
332

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