1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
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 https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qquickview.h"
41 #include "qquickview_p.h"
42 
43 #include "qquickwindow_p.h"
44 #include "qquickitem_p.h"
45 #include "qquickitemchangelistener_p.h"
46 
47 #include <QtQml/qqmlengine.h>
48 #include <private/qqmlengine_p.h>
49 #include <private/qv4qobjectwrapper_p.h>
50 #include <QtCore/qbasictimer.h>
51 
52 QT_BEGIN_NAMESPACE
53 
init(QQmlEngine * e)54 void QQuickViewPrivate::init(QQmlEngine* e)
55 {
56     Q_Q(QQuickView);
57 
58     engine = e;
59 
60     if (engine.isNull())
61         engine = new QQmlEngine(q);
62 
63     QQmlEngine::setContextForObject(contentItem, engine.data()->rootContext());
64 
65     if (!engine.data()->incubationController())
66         engine.data()->setIncubationController(q->incubationController());
67 
68     {
69         // The content item has CppOwnership policy (set in QQuickWindow). Ensure the presence of a JS
70         // wrapper so that the garbage collector can see the policy.
71         QV4::ExecutionEngine *v4 = engine.data()->handle();
72         QV4::QObjectWrapper::wrap(v4, contentItem);
73     }
74 }
75 
QQuickViewPrivate()76 QQuickViewPrivate::QQuickViewPrivate()
77     : component(nullptr), resizeMode(QQuickView::SizeViewToRootObject), initialSize(0,0)
78 {
79 }
80 
~QQuickViewPrivate()81 QQuickViewPrivate::~QQuickViewPrivate()
82 {
83 }
84 
execute()85 void QQuickViewPrivate::execute()
86 {
87     Q_Q(QQuickView);
88     if (!engine) {
89         qWarning() << "QQuickView: invalid qml engine.";
90         return;
91     }
92 
93     if (root)
94         delete root;
95     if (component) {
96         delete component;
97         component = nullptr;
98     }
99     if (!source.isEmpty()) {
100         component = new QQmlComponent(engine.data(), source, q);
101         if (!component->isLoading()) {
102             q->continueExecute();
103         } else {
104             QObject::connect(component, SIGNAL(statusChanged(QQmlComponent::Status)),
105                              q, SLOT(continueExecute()));
106         }
107     }
108 }
109 
itemGeometryChanged(QQuickItem * resizeItem,QQuickGeometryChange change,const QRectF & oldGeometry)110 void QQuickViewPrivate::itemGeometryChanged(QQuickItem *resizeItem, QQuickGeometryChange change,
111                                             const QRectF &oldGeometry)
112 {
113     Q_Q(QQuickView);
114     if (resizeItem == root && resizeMode == QQuickView::SizeViewToRootObject) {
115         // wait for both width and height to be changed
116         resizetimer.start(0,q);
117     }
118     QQuickItemChangeListener::itemGeometryChanged(resizeItem, change, oldGeometry);
119 }
120 
121 /*!
122     \class QQuickView
123     \since 5.0
124     \brief The QQuickView class provides a window for displaying a Qt Quick user interface.
125 
126     \inmodule QtQuick
127 
128     This is a convenience subclass of QQuickWindow which
129     will automatically load and display a QML scene when given the URL of the main source file. Alternatively,
130     you can instantiate your own objects using QQmlComponent and place them in a manually setup QQuickWindow.
131 
132     Typical usage:
133 
134     \snippet qquickview-ex.cpp 0
135 
136     To receive errors related to loading and executing QML with QQuickView,
137     you can connect to the statusChanged() signal and monitor for QQuickView::Error.
138     The errors are available via QQuickView::errors().
139 
140     QQuickView also manages sizing of the view and root object.  By default, the \l resizeMode
141     is SizeViewToRootObject, which will load the component and resize it to the
142     size of the view.  Alternatively the resizeMode may be set to SizeRootObjectToView which
143     will resize the view to the size of the root object.
144 
145     \sa {Exposing Attributes of C++ Types to QML}, QQuickWidget
146 */
147 
148 
149 /*! \fn void QQuickView::statusChanged(QQuickView::Status status)
150     This signal is emitted when the component's current \a status changes.
151 */
152 
153 /*!
154   Constructs a QQuickView with the given \a parent.
155   The default value of \a parent is 0.
156 
157 */
QQuickView(QWindow * parent)158 QQuickView::QQuickView(QWindow *parent)
159 : QQuickWindow(*(new QQuickViewPrivate), parent)
160 {
161     d_func()->init();
162 }
163 
164 /*!
165   Constructs a QQuickView with the given QML \a source and \a parent.
166   The default value of \a parent is 0.
167 
168 */
QQuickView(const QUrl & source,QWindow * parent)169 QQuickView::QQuickView(const QUrl &source, QWindow *parent)
170     : QQuickView(parent)
171 {
172     setSource(source);
173 }
174 
175 /*!
176   Constructs a QQuickView with the given QML \a engine and \a parent.
177 
178   Note: In this case, the QQuickView does not own the given \a engine object;
179   it is the caller's responsibility to destroy the engine. If the \a engine is deleted
180   before the view, status() will return QQuickView::Error.
181 
182   \sa Status, status(), errors()
183 */
QQuickView(QQmlEngine * engine,QWindow * parent)184 QQuickView::QQuickView(QQmlEngine* engine, QWindow *parent)
185     : QQuickWindow(*(new QQuickViewPrivate), parent)
186 {
187     Q_ASSERT(engine);
188     d_func()->init(engine);
189 }
190 
191 /*!
192     \internal
193 */
QQuickView(const QUrl & source,QQuickRenderControl * control)194 QQuickView::QQuickView(const QUrl &source, QQuickRenderControl *control)
195     : QQuickWindow(*(new QQuickViewPrivate), control)
196 {
197     d_func()->init();
198     setSource(source);
199 }
200 
201 /*!
202   Destroys the QQuickView.
203 */
~QQuickView()204 QQuickView::~QQuickView()
205 {
206     // Ensure that the component is destroyed before the engine; the engine may
207     // be a child of the QQuickViewPrivate, and will be destroyed by its dtor
208     Q_D(QQuickView);
209     delete d->root;
210 }
211 
212 /*!
213   \property QQuickView::source
214   \brief The URL of the source of the QML component.
215 
216   Ensure that the URL provided is full and correct, in particular, use
217   \l QUrl::fromLocalFile() when loading a file from the local filesystem.
218 
219   Note that setting a source URL will result in the QML component being
220   instantiated, even if the URL is unchanged from the current value.
221 */
222 
223 /*!
224     Sets the source to the \a url, loads the QML component and instantiates it.
225 
226     Ensure that the URL provided is full and correct, in particular, use
227     \l QUrl::fromLocalFile() when loading a file from the local filesystem.
228 
229     Calling this method multiple times with the same url will result
230     in the QML component being reinstantiated.
231  */
setSource(const QUrl & url)232 void QQuickView::setSource(const QUrl& url)
233 {
234     Q_D(QQuickView);
235     d->source = url;
236     d->execute();
237 }
238 
239 /*!
240    Sets the initial properties \a initialProperties with which the QML
241    component gets initialized after calling \l QQuickView::setSource().
242 
243    \snippet qquickview-ex.cpp 1
244 
245    \note You can only use this function to initialize top-level properties.
246    \note This function should always be called before setSource, as it has
247    no effect once the component has become \c Ready.
248 
249    \sa QQmlComponent::createWithInitialProperties()
250    \since 5.14
251 */
setInitialProperties(const QVariantMap & initialProperties)252 void QQuickView::setInitialProperties(const QVariantMap &initialProperties)
253 {
254     Q_D(QQuickView);
255     d->initialProperties = initialProperties;
256 }
257 
258 /*!
259     \internal
260 
261     Set the source \a url, \a component and content \a item (root of the QML object hierarchy) directly.
262  */
setContent(const QUrl & url,QQmlComponent * component,QObject * item)263 void QQuickView::setContent(const QUrl& url, QQmlComponent *component, QObject* item)
264 {
265     Q_D(QQuickView);
266     d->source = url;
267     d->component = component;
268 
269     if (d->component && d->component->isError()) {
270         const QList<QQmlError> errorList = d->component->errors();
271         for (const QQmlError &error : errorList) {
272             QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning()
273                     << error;
274         }
275         emit statusChanged(status());
276         return;
277     }
278 
279     if (!d->setRootObject(item))
280         delete item;
281     emit statusChanged(status());
282 }
283 
284 /*!
285   Returns the source URL, if set.
286 
287   \sa setSource()
288  */
source() const289 QUrl QQuickView::source() const
290 {
291     Q_D(const QQuickView);
292     return d->source;
293 }
294 
295 /*!
296   Returns a pointer to the QQmlEngine used for instantiating
297   QML Components.
298  */
engine() const299 QQmlEngine* QQuickView::engine() const
300 {
301     Q_D(const QQuickView);
302     return d->engine ? const_cast<QQmlEngine *>(d->engine.data()) : nullptr;
303 }
304 
305 /*!
306   This function returns the root of the context hierarchy.  Each QML
307   component is instantiated in a QQmlContext.  QQmlContext's are
308   essential for passing data to QML components.  In QML, contexts are
309   arranged hierarchically and this hierarchy is managed by the
310   QQmlEngine.
311  */
rootContext() const312 QQmlContext* QQuickView::rootContext() const
313 {
314     Q_D(const QQuickView);
315     return d->engine ? d->engine.data()->rootContext() : nullptr;
316 }
317 
318 /*!
319     \enum QQuickView::Status
320     Specifies the loading status of the QQuickView.
321 
322     \value Null This QQuickView has no source set.
323     \value Ready This QQuickView has loaded and created the QML component.
324     \value Loading This QQuickView is loading network data.
325     \value Error One or more errors has occurred. Call errors() to retrieve a list
326            of errors.
327 */
328 
329 /*! \enum QQuickView::ResizeMode
330 
331   This enum specifies how to resize the view.
332 
333   \value SizeViewToRootObject The view resizes with the root item in the QML.
334   \value SizeRootObjectToView The view will automatically resize the root item to the size of the view.
335 */
336 
337 /*!
338     \property QQuickView::status
339     The component's current \l{QQuickView::Status} {status}.
340 */
341 
status() const342 QQuickView::Status QQuickView::status() const
343 {
344     Q_D(const QQuickView);
345     if (!d->engine)
346         return QQuickView::Error;
347 
348     if (!d->component)
349         return QQuickView::Null;
350 
351     if (d->component->status() == QQmlComponent::Ready && !d->root)
352         return QQuickView::Error;
353 
354     return QQuickView::Status(d->component->status());
355 }
356 
357 /*!
358     Return the list of errors that occurred during the last compile or create
359     operation.  When the status is not Error, an empty list is returned.
360 */
errors() const361 QList<QQmlError> QQuickView::errors() const
362 {
363     Q_D(const QQuickView);
364     QList<QQmlError> errs;
365 
366     if (d->component)
367         errs = d->component->errors();
368 
369     if (!d->engine) {
370         QQmlError error;
371         error.setDescription(QLatin1String("QQuickView: invalid qml engine."));
372         errs << error;
373     } else if (d->component && d->component->status() == QQmlComponent::Ready && !d->root) {
374         QQmlError error;
375         error.setDescription(QLatin1String("QQuickView: invalid root object."));
376         errs << error;
377     }
378 
379     return errs;
380 }
381 
382 /*!
383     \property QQuickView::resizeMode
384     \brief whether the view should resize the window contents
385 
386     If this property is set to SizeViewToRootObject (the default), the view
387     resizes to the size of the root item in the QML.
388 
389     If this property is set to SizeRootObjectToView, the view will
390     automatically resize the root item to the size of the view.
391 
392     \sa initialSize()
393 */
394 
setResizeMode(ResizeMode mode)395 void QQuickView::setResizeMode(ResizeMode mode)
396 {
397     Q_D(QQuickView);
398     if (d->resizeMode == mode)
399         return;
400 
401     if (d->root) {
402         if (d->resizeMode == SizeViewToRootObject) {
403             QQuickItemPrivate *p = QQuickItemPrivate::get(d->root);
404             p->removeItemChangeListener(d, QQuickItemPrivate::Geometry);
405         }
406     }
407 
408     d->resizeMode = mode;
409     if (d->root) {
410         d->initResize();
411     }
412 }
413 
initResize()414 void QQuickViewPrivate::initResize()
415 {
416     if (root) {
417         if (resizeMode == QQuickView::SizeViewToRootObject) {
418             QQuickItemPrivate *p = QQuickItemPrivate::get(root);
419             p->addItemChangeListener(this, QQuickItemPrivate::Geometry);
420         }
421     }
422     updateSize();
423 }
424 
updateSize()425 void QQuickViewPrivate::updateSize()
426 {
427     Q_Q(QQuickView);
428     if (!root)
429         return;
430 
431     if (resizeMode == QQuickView::SizeViewToRootObject) {
432         QSize newSize = QSize(root->width(), root->height());
433         if (newSize.isValid() && newSize != q->size()) {
434             q->resize(newSize);
435         }
436     } else if (resizeMode == QQuickView::SizeRootObjectToView) {
437         bool needToUpdateWidth = !qFuzzyCompare(q->width(), root->width());
438         bool needToUpdateHeight = !qFuzzyCompare(q->height(), root->height());
439 
440         if (needToUpdateWidth && needToUpdateHeight)
441             root->setSize(QSizeF(q->width(), q->height()));
442         else if (needToUpdateWidth)
443             root->setWidth(q->width());
444         else if (needToUpdateHeight)
445             root->setHeight(q->height());
446     }
447 }
448 
rootObjectSize() const449 QSize QQuickViewPrivate::rootObjectSize() const
450 {
451     QSize rootObjectSize(0,0);
452     int widthCandidate = -1;
453     int heightCandidate = -1;
454     if (root) {
455         widthCandidate = root->width();
456         heightCandidate = root->height();
457     }
458     if (widthCandidate > 0) {
459         rootObjectSize.setWidth(widthCandidate);
460     }
461     if (heightCandidate > 0) {
462         rootObjectSize.setHeight(heightCandidate);
463     }
464     return rootObjectSize;
465 }
466 
resizeMode() const467 QQuickView::ResizeMode QQuickView::resizeMode() const
468 {
469     Q_D(const QQuickView);
470     return d->resizeMode;
471 }
472 
473 /*!
474   \internal
475  */
continueExecute()476 void QQuickView::continueExecute()
477 {
478     Q_D(QQuickView);
479     disconnect(d->component, SIGNAL(statusChanged(QQmlComponent::Status)), this, SLOT(continueExecute()));
480 
481     if (d->component->isError()) {
482         const QList<QQmlError> errorList = d->component->errors();
483         for (const QQmlError &error : errorList) {
484             QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning()
485                     << error;
486         }
487         emit statusChanged(status());
488         return;
489     }
490 
491     QScopedPointer<QObject> obj(d->initialProperties.empty()
492                                 ? d->component->create()
493                                 : d->component->createWithInitialProperties(d->initialProperties));
494 
495     if (d->component->isError()) {
496         const QList<QQmlError> errorList = d->component->errors();
497         for (const QQmlError &error : errorList) {
498             QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning()
499                     << error;
500         }
501         emit statusChanged(status());
502         return;
503     }
504 
505     if (d->setRootObject(obj.get()))
506         obj.take();
507     emit statusChanged(status());
508 }
509 
510 
511 /*!
512   \internal
513 
514   Sets \a obj as root object and returns true if that operation succeeds.
515   Otherwise returns \c false. If \c false is returned, the root object is
516   \c nullptr afterwards. You can explicitly set the root object to nullptr,
517   and the return value will be \c true.
518 */
setRootObject(QObject * obj)519 bool QQuickViewPrivate::setRootObject(QObject *obj)
520 {
521     Q_Q(QQuickView);
522     if (root == obj)
523         return true;
524 
525     delete root;
526     if (obj == nullptr)
527         return true;
528 
529     if (QQuickItem *sgItem = qobject_cast<QQuickItem *>(obj)) {
530         root = sgItem;
531         sgItem->setParentItem(q->QQuickWindow::contentItem());
532         QQml_setParent_noEvent(sgItem, q->QQuickWindow::contentItem());
533         initialSize = rootObjectSize();
534         if ((resizeMode == QQuickView::SizeViewToRootObject || q->width() <= 1 || q->height() <= 1) &&
535             initialSize != q->size()) {
536             q->resize(initialSize);
537         }
538         initResize();
539         return true;
540     }
541 
542     if (qobject_cast<QWindow *>(obj)) {
543         qWarning() << "QQuickView does not support using a window as a root item." << Qt::endl
544                    << Qt::endl
545                    << "If you wish to create your root window from QML, consider using QQmlApplicationEngine instead." << Qt::endl;
546         return false;
547     }
548 
549     qWarning() << "QQuickView only supports loading of root objects that derive from QQuickItem." << Qt::endl
550                << Qt::endl
551                << "Ensure your QML code is written for QtQuick 2, and uses a root that is or" << Qt::endl
552                << "inherits from QtQuick's Item (not a Timer, QtObject, etc)." << Qt::endl;
553     return false;
554 }
555 
556 /*!
557   \internal
558   If the \l {QTimerEvent} {timer event} \a e is this
559   view's resize timer, sceneResized() is emitted.
560  */
timerEvent(QTimerEvent * e)561 void QQuickView::timerEvent(QTimerEvent* e)
562 {
563     Q_D(QQuickView);
564     if (!e || e->timerId() == d->resizetimer.timerId()) {
565         d->updateSize();
566         d->resizetimer.stop();
567     }
568 }
569 
570 /*!
571     \internal
572     Preferred size follows the root object geometry.
573 */
sizeHint() const574 QSize QQuickView::sizeHint() const
575 {
576     Q_D(const QQuickView);
577     QSize rootObjectSize = d->rootObjectSize();
578     if (rootObjectSize.isEmpty()) {
579         return size();
580     } else {
581         return rootObjectSize;
582     }
583 }
584 
585 /*!
586   Returns the initial size of the root object.
587 
588   If \l resizeMode is QQuickItem::SizeRootObjectToView the root object will be
589   resized to the size of the view.  initialSize contains the size of the
590   root object before it was resized.
591 */
initialSize() const592 QSize QQuickView::initialSize() const
593 {
594     Q_D(const QQuickView);
595     return d->initialSize;
596 }
597 
598 /*!
599   Returns the view's root \l {QQuickItem} {item}.
600  */
rootObject() const601 QQuickItem *QQuickView::rootObject() const
602 {
603     Q_D(const QQuickView);
604     return d->root;
605 }
606 
607 /*!
608   \internal
609   This function handles the \l {QResizeEvent} {resize event}
610   \a e.
611  */
resizeEvent(QResizeEvent * e)612 void QQuickView::resizeEvent(QResizeEvent *e)
613 {
614     Q_D(QQuickView);
615     if (d->resizeMode == SizeRootObjectToView)
616         d->updateSize();
617 
618     QQuickWindow::resizeEvent(e);
619 }
620 
621 /*! \reimp */
keyPressEvent(QKeyEvent * e)622 void QQuickView::keyPressEvent(QKeyEvent *e)
623 {
624     QQuickWindow::keyPressEvent(e);
625 }
626 
627 /*! \reimp */
keyReleaseEvent(QKeyEvent * e)628 void QQuickView::keyReleaseEvent(QKeyEvent *e)
629 {
630     QQuickWindow::keyReleaseEvent(e);
631 }
632 
633 /*! \reimp */
mouseMoveEvent(QMouseEvent * e)634 void QQuickView::mouseMoveEvent(QMouseEvent *e)
635 {
636     QQuickWindow::mouseMoveEvent(e);
637 }
638 
639 /*! \reimp */
mousePressEvent(QMouseEvent * e)640 void QQuickView::mousePressEvent(QMouseEvent *e)
641 {
642     QQuickWindow::mousePressEvent(e);
643 }
644 
645 /*! \reimp */
mouseReleaseEvent(QMouseEvent * e)646 void QQuickView::mouseReleaseEvent(QMouseEvent *e)
647 {
648     QQuickWindow::mouseReleaseEvent(e);
649 }
650 
651 
652 QT_END_NAMESPACE
653 
654 #include "moc_qquickview.cpp"
655