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

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