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 "qquickstackelement_p_p.h" |
5 | #include "qquickstackview_p_p.h" |
6 | |
7 | #include <QtQml/qqmlinfo.h> |
8 | #include <QtQml/qqmlengine.h> |
9 | #include <QtQml/qqmlcomponent.h> |
10 | #include <QtQml/qqmlincubator.h> |
11 | #include <QtQml/private/qv4qobjectwrapper_p.h> |
12 | #include <QtQml/private/qqmlcomponent_p.h> |
13 | #include <QtQml/private/qqmlengine_p.h> |
14 | #include <QtQml/private/qqmlincubator_p.h> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | #if QT_CONFIG(quick_viewtransitions) |
19 | static QQuickStackViewAttached *attachedStackObject(QQuickStackElement *element) |
20 | { |
21 | QQuickStackViewAttached *attached = qobject_cast<QQuickStackViewAttached *>(object: qmlAttachedPropertiesObject<QQuickStackView>(obj: element->item, create: false)); |
22 | if (attached) |
23 | QQuickStackViewAttachedPrivate::get(attached)->element = element; |
24 | return attached; |
25 | } |
26 | #endif |
27 | |
28 | class QQuickStackIncubator : public QQmlIncubator |
29 | { |
30 | public: |
31 | QQuickStackIncubator(QQuickStackElement *element) |
32 | : QQmlIncubator(Synchronous), |
33 | element(element) |
34 | { |
35 | } |
36 | |
37 | protected: |
38 | void setInitialState(QObject *object) override |
39 | { |
40 | auto privIncubator = QQmlIncubatorPrivate::get(incubator: this); |
41 | element->incubate(object, requiredProperties: privIncubator->requiredProperties()); |
42 | } |
43 | |
44 | private: |
45 | QQuickStackElement *element; |
46 | }; |
47 | |
48 | QQuickStackElement::QQuickStackElement() |
49 | #if QT_CONFIG(quick_viewtransitions) |
50 | : QQuickItemViewTransitionableItem(nullptr) |
51 | #endif |
52 | { |
53 | } |
54 | |
55 | QQuickStackElement::~QQuickStackElement() |
56 | { |
57 | #if QT_CONFIG(quick_viewtransitions) |
58 | if (item) |
59 | QQuickItemPrivate::get(item)->removeItemChangeListener(this, types: QQuickItemPrivate::Destroyed); |
60 | #endif |
61 | |
62 | if (ownComponent) |
63 | delete component; |
64 | |
65 | #if QT_CONFIG(quick_viewtransitions) |
66 | QQuickStackViewAttached *attached = attachedStackObject(element: this); |
67 | if (item) { |
68 | if (ownItem) { |
69 | item->setParentItem(nullptr); |
70 | item->deleteLater(); |
71 | item = nullptr; |
72 | } else { |
73 | setVisible(false); |
74 | if (!widthValid) |
75 | item->resetWidth(); |
76 | if (!heightValid) |
77 | item->resetHeight(); |
78 | if (item->parentItem() != originalParent) { |
79 | item->setParentItem(originalParent); |
80 | } else { |
81 | if (attached) |
82 | QQuickStackViewAttachedPrivate::get(attached)->itemParentChanged(item, parent: nullptr); |
83 | } |
84 | } |
85 | } |
86 | |
87 | if (attached) |
88 | emit attached->removed(); |
89 | #endif |
90 | } |
91 | |
92 | QQuickStackElement *QQuickStackElement::fromString(const QString &str, QQuickStackView *view, QString *error) |
93 | { |
94 | QUrl url(str); |
95 | if (!url.isValid()) { |
96 | *error = QStringLiteral("invalid url: " ) + str; |
97 | return nullptr; |
98 | } |
99 | |
100 | if (url.isRelative()) |
101 | url = qmlContext(view)->resolvedUrl(url); |
102 | |
103 | QQuickStackElement *element = new QQuickStackElement; |
104 | element->component = new QQmlComponent(qmlEngine(view), url, view); |
105 | element->ownComponent = true; |
106 | return element; |
107 | } |
108 | |
109 | QQuickStackElement *QQuickStackElement::fromObject(QObject *object, QQuickStackView *view, QString *error) |
110 | { |
111 | Q_UNUSED(view); |
112 | QQmlComponent *component = qobject_cast<QQmlComponent *>(object); |
113 | QQuickItem *item = qobject_cast<QQuickItem *>(o: object); |
114 | if (!component && !item) { |
115 | *error = QQmlMetaType::prettyTypeName(object) + QStringLiteral(" is not supported. Must be Item or Component." ); |
116 | return nullptr; |
117 | } |
118 | |
119 | QQuickStackElement *element = new QQuickStackElement; |
120 | element->component = qobject_cast<QQmlComponent *>(object); |
121 | #if QT_CONFIG(quick_viewtransitions) |
122 | element->item = qobject_cast<QQuickItem *>(o: object); |
123 | if (element->item) |
124 | element->originalParent = element->item->parentItem(); |
125 | #endif |
126 | return element; |
127 | } |
128 | |
129 | bool QQuickStackElement::load(QQuickStackView *parent) |
130 | { |
131 | setView(parent); |
132 | if (!item) { |
133 | ownItem = true; |
134 | |
135 | if (component->isLoading()) { |
136 | QObject::connect(sender: component, signal: &QQmlComponent::statusChanged, slot: [this](QQmlComponent::Status status) { |
137 | if (status == QQmlComponent::Ready) |
138 | load(parent: view); |
139 | else if (status == QQmlComponent::Error) |
140 | QQuickStackViewPrivate::get(view)->warn(error: component->errorString().trimmed()); |
141 | }); |
142 | return true; |
143 | } |
144 | |
145 | QQmlContext *context = component->creationContext(); |
146 | if (!context) |
147 | context = qmlContext(parent); |
148 | |
149 | QQuickStackIncubator incubator(this); |
150 | component->create(incubator, context); |
151 | if (component->isError()) |
152 | QQuickStackViewPrivate::get(view: parent)->warn(error: component->errorString().trimmed()); |
153 | } else { |
154 | initialize(/*required properties=*/requiredProperties: nullptr); |
155 | } |
156 | return item; |
157 | } |
158 | |
159 | void QQuickStackElement::incubate(QObject *object, RequiredProperties *requiredProperties) |
160 | { |
161 | item = qmlobject_cast<QQuickItem *>(object); |
162 | if (item) { |
163 | QQmlEngine::setObjectOwnership(item, QQmlEngine::CppOwnership); |
164 | item->setParent(view); |
165 | initialize(requiredProperties); |
166 | } |
167 | } |
168 | |
169 | void QQuickStackElement::initialize(RequiredProperties *requiredProperties) |
170 | { |
171 | if (!item || init) |
172 | return; |
173 | |
174 | QQuickItemPrivate *p = QQuickItemPrivate::get(item); |
175 | if (!(widthValid = p->widthValid())) |
176 | item->setWidth(view->width()); |
177 | if (!(heightValid = p->heightValid())) |
178 | item->setHeight(view->height()); |
179 | item->setParentItem(view); |
180 | |
181 | if (!properties.isUndefined()) { |
182 | QQmlEngine *engine = qmlEngine(view); |
183 | Q_ASSERT(engine); |
184 | QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(e: engine); |
185 | Q_ASSERT(v4); |
186 | QV4::Scope scope(v4); |
187 | QV4::ScopedValue ipv(scope, properties.value()); |
188 | QV4::Scoped<QV4::QmlContext> qmlContext(scope, qmlCallingContext.value()); |
189 | QV4::ScopedValue qmlObject(scope, QV4::QObjectWrapper::wrap(engine: v4, object: item)); |
190 | QQmlComponentPrivate::setInitialProperties( |
191 | engine: v4, qmlContext, o: qmlObject, v: ipv, requiredProperties, createdComponent: item, |
192 | creator: component ? QQmlComponentPrivate::get(c: component)->state.creator() : nullptr); |
193 | properties.clear(); |
194 | } |
195 | |
196 | if (requiredProperties && !requiredProperties->empty()) { |
197 | QString error; |
198 | for (const auto &property: *requiredProperties) { |
199 | error += QLatin1String("Property %1 was marked as required but not set.\n" ) |
200 | .arg(args: property.propertyName); |
201 | } |
202 | QQuickStackViewPrivate::get(view)->warn(error); |
203 | item = nullptr; |
204 | } else { |
205 | p->addItemChangeListener(listener: this, types: QQuickItemPrivate::Destroyed); |
206 | } |
207 | |
208 | init = true; |
209 | } |
210 | |
211 | void QQuickStackElement::setIndex(int value) |
212 | { |
213 | if (index == value) |
214 | return; |
215 | |
216 | index = value; |
217 | #if QT_CONFIG(quick_viewtransitions) |
218 | QQuickStackViewAttached *attached = attachedStackObject(element: this); |
219 | if (attached) |
220 | emit attached->indexChanged(); |
221 | #endif |
222 | } |
223 | |
224 | void QQuickStackElement::setView(QQuickStackView *value) |
225 | { |
226 | if (view == value) |
227 | return; |
228 | |
229 | view = value; |
230 | #if QT_CONFIG(quick_viewtransitions) |
231 | QQuickStackViewAttached *attached = attachedStackObject(element: this); |
232 | if (attached) |
233 | emit attached->viewChanged(); |
234 | #endif |
235 | } |
236 | |
237 | void QQuickStackElement::setStatus(QQuickStackView::Status value) |
238 | { |
239 | if (status == value) |
240 | return; |
241 | |
242 | status = value; |
243 | #if QT_CONFIG(quick_viewtransitions) |
244 | QQuickStackViewAttached *attached = attachedStackObject(element: this); |
245 | if (!attached) |
246 | return; |
247 | |
248 | switch (value) { |
249 | case QQuickStackView::Inactive: |
250 | emit attached->deactivated(); |
251 | break; |
252 | case QQuickStackView::Deactivating: |
253 | emit attached->deactivating(); |
254 | break; |
255 | case QQuickStackView::Activating: |
256 | emit attached->activating(); |
257 | break; |
258 | case QQuickStackView::Active: |
259 | emit attached->activated(); |
260 | break; |
261 | default: |
262 | Q_UNREACHABLE(); |
263 | break; |
264 | } |
265 | |
266 | emit attached->statusChanged(); |
267 | #endif |
268 | } |
269 | |
270 | void QQuickStackElement::setVisible(bool visible) |
271 | { |
272 | #if QT_CONFIG(quick_viewtransitions) |
273 | QQuickStackViewAttached *attached = attachedStackObject(element: this); |
274 | #endif |
275 | if (!item |
276 | #if QT_CONFIG(quick_viewtransitions) |
277 | || (attached && QQuickStackViewAttachedPrivate::get(attached)->explicitVisible) |
278 | #endif |
279 | ) |
280 | return; |
281 | |
282 | item->setVisible(visible); |
283 | } |
284 | |
285 | #if QT_CONFIG(quick_viewtransitions) |
286 | void QQuickStackElement::transitionNextReposition(QQuickItemViewTransitioner *transitioner, QQuickItemViewTransitioner::TransitionType type, bool asTarget) |
287 | { |
288 | if (transitioner) |
289 | transitioner->transitionNextReposition(item: this, type, isTarget: asTarget); |
290 | } |
291 | |
292 | bool QQuickStackElement::prepareTransition(QQuickItemViewTransitioner *transitioner, const QRectF &viewBounds) |
293 | { |
294 | if (transitioner) { |
295 | if (item) { |
296 | QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors; |
297 | // TODO: expose QQuickAnchorLine so we can test for other conflicting anchors |
298 | if (anchors && (anchors->fill() || anchors->centerIn())) |
299 | qmlWarning(me: item) << "StackView has detected conflicting anchors. Transitions may not execute properly." ; |
300 | } |
301 | |
302 | // TODO: add force argument to QQuickItemViewTransitionableItem::prepareTransition()? |
303 | nextTransitionToSet = true; |
304 | nextTransitionFromSet = true; |
305 | nextTransitionFrom += QPointF(1, 1); |
306 | return QQuickItemViewTransitionableItem::prepareTransition(transitioner, index, viewBounds); |
307 | } |
308 | return false; |
309 | } |
310 | |
311 | void QQuickStackElement::startTransition(QQuickItemViewTransitioner *transitioner, QQuickStackView::Status status) |
312 | { |
313 | setStatus(status); |
314 | if (transitioner) |
315 | QQuickItemViewTransitionableItem::startTransition(transitioner, index); |
316 | } |
317 | |
318 | void QQuickStackElement::completeTransition(QQuickTransition *quickTransition) |
319 | { |
320 | QQuickItemViewTransitionableItem::completeTransition(quickTransition); |
321 | } |
322 | #endif |
323 | |
324 | void QQuickStackElement::itemDestroyed(QQuickItem *) |
325 | { |
326 | #if QT_CONFIG(quick_viewtransitions) |
327 | item = nullptr; |
328 | #endif |
329 | } |
330 | |
331 | QT_END_NAMESPACE |
332 | |