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

source code of qtdeclarative/src/quick/items/qquickview.cpp