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