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 "qquickwidget.h"
41#include "qquickwidget_p.h"
42
43#include "private/qquickwindow_p.h"
44#include "private/qquickitem_p.h"
45#include "private/qquickitemchangelistener_p.h"
46#include "private/qquickrendercontrol_p.h"
47
48#include "private/qsgsoftwarerenderer_p.h"
49
50#include <private/qqmldebugconnector_p.h>
51#include <private/qquickprofiler_p.h>
52#include <private/qqmldebugserviceinterfaces_p.h>
53
54#include <QtQml/qqmlengine.h>
55#include <private/qqmlengine_p.h>
56#include <QtCore/qbasictimer.h>
57#include <QtGui/QOffscreenSurface>
58#include <QtGui/private/qguiapplication_p.h>
59#include <QtGui/qpa/qplatformintegration.h>
60
61#if QT_CONFIG(opengl)
62#include <QtGui/QOpenGLContext>
63#include <QtGui/QOpenGLFunctions>
64#include <QtGui/private/qopenglextensions_p.h>
65#endif
66#include <QtGui/QPainter>
67
68#include <QtQuick/QSGRendererInterface>
69
70#ifdef Q_OS_WIN
71# include <QtWidgets/QMessageBox>
72# include <QtCore/QLibraryInfo>
73# include <QtCore/qt_windows.h>
74#endif
75
76QT_BEGIN_NAMESPACE
77
78// override setVisble to prevent accidental offscreen window being created
79// by base class.
80class QQuickOffcreenWindowPrivate: public QQuickWindowPrivate {
81public:
82 void setVisible(bool visible) override {
83 Q_Q(QWindow);
84 // this stays always invisible
85 visibility = visible ? QWindow::Windowed : QWindow::Hidden;
86 q->visibilityChanged(visibility); // workaround for QTBUG-49054
87 }
88};
89
90class QQuickWidgetRenderControl : public QQuickRenderControl
91{
92public:
93 QQuickWidgetRenderControl(QQuickWidget *quickwidget) : m_quickWidget(quickwidget) {}
94 QWindow *renderWindow(QPoint *offset) override {
95 if (offset)
96 *offset = m_quickWidget->mapTo(m_quickWidget->window(), QPoint());
97 return m_quickWidget->window()->windowHandle();
98 }
99private:
100 QQuickWidget *m_quickWidget;
101};
102
103void QQuickWidgetPrivate::init(QQmlEngine* e)
104{
105 Q_Q(QQuickWidget);
106
107 renderControl = new QQuickWidgetRenderControl(q);
108 offscreenWindow = new QQuickWindow(*new QQuickOffcreenWindowPrivate(),renderControl);
109 offscreenWindow->setTitle(QString::fromLatin1(str: "Offscreen"));
110 offscreenWindow->setObjectName(QString::fromLatin1(str: "QQuickOffScreenWindow"));
111 // Do not call create() on offscreenWindow.
112
113 // Check if the Software Adaptation is being used
114 auto sgRendererInterface = offscreenWindow->rendererInterface();
115 if (sgRendererInterface && sgRendererInterface->graphicsApi() == QSGRendererInterface::Software)
116 useSoftwareRenderer = true;
117
118 if (!useSoftwareRenderer) {
119#if QT_CONFIG(opengl)
120 if (QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::RasterGLSurface))
121 setRenderToTexture();
122 else
123#endif
124 qWarning(msg: "QQuickWidget is not supported on this platform.");
125 }
126
127 engine = e;
128
129 if (!engine.isNull() && !engine.data()->incubationController())
130 engine.data()->setIncubationController(offscreenWindow->incubationController());
131
132#if QT_CONFIG(quick_draganddrop)
133 q->setAcceptDrops(true);
134#endif
135
136 QWidget::connect(sender: offscreenWindow, SIGNAL(sceneGraphInitialized()), receiver: q, SLOT(createFramebufferObject()));
137 QWidget::connect(sender: offscreenWindow, SIGNAL(sceneGraphInvalidated()), receiver: q, SLOT(destroyFramebufferObject()));
138 QWidget::connect(sender: offscreenWindow, signal: &QQuickWindow::focusObjectChanged, receiver: q, slot: &QQuickWidget::propagateFocusObjectChanged);
139 QObject::connect(sender: renderControl, SIGNAL(renderRequested()), receiver: q, SLOT(triggerUpdate()));
140 QObject::connect(sender: renderControl, SIGNAL(sceneChanged()), receiver: q, SLOT(triggerUpdate()));
141}
142
143void QQuickWidgetPrivate::ensureEngine() const
144{
145 Q_Q(const QQuickWidget);
146 if (!engine.isNull())
147 return;
148
149 engine = new QQmlEngine(const_cast<QQuickWidget*>(q));
150 engine.data()->setIncubationController(offscreenWindow->incubationController());
151}
152
153void QQuickWidgetPrivate::invalidateRenderControl()
154{
155#if QT_CONFIG(opengl)
156 if (!useSoftwareRenderer) {
157 if (!context) // this is not an error, could be called before creating the context, or multiple times
158 return;
159
160 bool success = context->makeCurrent(surface: offscreenSurface);
161 if (!success) {
162 qWarning(msg: "QQuickWidget::invalidateRenderControl could not make context current");
163 return;
164 }
165 }
166#endif
167
168 renderControl->invalidate();
169
170 // Many things can happen inside the above invalidate() call, including a
171 // change of current context. Restore if needed since some code will rely
172 // on the fact that this function makes and leaves the context current.
173#if QT_CONFIG(opengl)
174 if (!useSoftwareRenderer && context) {
175 if (QOpenGLContext::currentContext() != context)
176 context->makeCurrent(surface: offscreenSurface);
177 }
178#endif
179}
180
181void QQuickWidgetPrivate::handleWindowChange()
182{
183 if (offscreenWindow->isPersistentSceneGraph() && qGuiApp->testAttribute(attribute: Qt::AA_ShareOpenGLContexts))
184 return;
185
186 // In case of !isPersistentSceneGraph or when we need a new context due to
187 // the need to share resources with the new window's context, we must both
188 // invalidate the scenegraph and destroy the context. With
189 // QQuickRenderControl destroying the context must be preceded by an
190 // invalidate to prevent being left with dangling context references in the
191 // rendercontrol.
192
193 invalidateRenderControl();
194
195 if (!useSoftwareRenderer)
196 destroyContext();
197}
198
199QQuickWidgetPrivate::QQuickWidgetPrivate()
200 : root(nullptr)
201 , component(nullptr)
202 , offscreenWindow(nullptr)
203 , offscreenSurface(nullptr)
204 , renderControl(nullptr)
205#if QT_CONFIG(opengl)
206 , fbo(nullptr)
207 , resolvedFbo(nullptr)
208 , context(nullptr)
209#endif
210 , resizeMode(QQuickWidget::SizeViewToRootObject)
211 , initialSize(0,0)
212 , eventPending(false)
213 , updatePending(false)
214 , fakeHidden(false)
215 , requestedSamples(0)
216 , useSoftwareRenderer(false)
217 , forceFullUpdate(false)
218{
219}
220
221QQuickWidgetPrivate::~QQuickWidgetPrivate()
222{
223 invalidateRenderControl();
224
225 if (useSoftwareRenderer) {
226 delete renderControl;
227 delete offscreenWindow;
228 } else {
229#if QT_CONFIG(opengl)
230 // context and offscreenSurface are current at this stage, if the context was created.
231 Q_ASSERT(!context || (QOpenGLContext::currentContext() == context && context->surface() == offscreenSurface));
232 delete renderControl; // always delete the rendercontrol first
233 delete offscreenWindow;
234 delete resolvedFbo;
235 delete fbo;
236
237 destroyContext();
238#endif
239 }
240}
241
242void QQuickWidgetPrivate::execute()
243{
244 Q_Q(QQuickWidget);
245 ensureEngine();
246
247 if (root) {
248 delete root;
249 root = nullptr;
250 }
251 if (component) {
252 delete component;
253 component = nullptr;
254 }
255 if (!source.isEmpty()) {
256 component = new QQmlComponent(engine.data(), source, q);
257 if (!component->isLoading()) {
258 q->continueExecute();
259 } else {
260 QObject::connect(sender: component, SIGNAL(statusChanged(QQmlComponent::Status)),
261 receiver: q, SLOT(continueExecute()));
262 }
263 }
264}
265
266void QQuickWidgetPrivate::itemGeometryChanged(QQuickItem *resizeItem, QQuickGeometryChange change,
267 const QRectF &oldGeometry)
268{
269 Q_Q(QQuickWidget);
270 if (resizeItem == root && resizeMode == QQuickWidget::SizeViewToRootObject) {
271 // wait for both width and height to be changed
272 resizetimer.start(msec: 0,obj: q);
273 }
274 QQuickItemChangeListener::itemGeometryChanged(resizeItem, change, oldGeometry);
275}
276
277void QQuickWidgetPrivate::render(bool needsSync)
278{
279 if (!useSoftwareRenderer) {
280#if QT_CONFIG(opengl)
281 // createFramebufferObject() bails out when the size is empty. In this case
282 // we cannot render either.
283 if (!fbo)
284 return;
285
286 Q_ASSERT(context);
287
288 bool current = context->makeCurrent(surface: offscreenSurface);
289
290 if (!current && !context->isValid()) {
291 renderControl->invalidate();
292 current = context->create() && context->makeCurrent(surface: offscreenSurface);
293 if (current)
294 renderControl->initialize(gl: context);
295 }
296
297 if (!current) {
298 qWarning(msg: "QQuickWidget: Cannot render due to failing makeCurrent()");
299 return;
300 }
301
302 QOpenGLContextPrivate::get(context)->defaultFboRedirect = fbo->handle();
303
304 if (needsSync) {
305 renderControl->polishItems();
306 renderControl->sync();
307 }
308
309 renderControl->render();
310
311 if (resolvedFbo) {
312 QRect rect(QPoint(0, 0), fbo->size());
313 QOpenGLFramebufferObject::blitFramebuffer(target: resolvedFbo, targetRect: rect, source: fbo, sourceRect: rect);
314 }
315
316 static_cast<QOpenGLExtensions *>(context->functions())->flushShared();
317
318 QOpenGLContextPrivate::get(context)->defaultFboRedirect = 0;
319#endif
320 } else {
321 //Software Renderer
322 if (needsSync) {
323 renderControl->polishItems();
324 renderControl->sync();
325 }
326
327 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: offscreenWindow);
328 auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(cd->renderer);
329 if (softwareRenderer && !softwareImage.isNull()) {
330 softwareRenderer->setCurrentPaintDevice(&softwareImage);
331 if (forceFullUpdate) {
332 softwareRenderer->markDirty();
333 forceFullUpdate = false;
334 }
335 renderControl->render();
336
337 updateRegion += softwareRenderer->flushRegion();
338 }
339 }
340}
341
342void QQuickWidgetPrivate::renderSceneGraph()
343{
344 Q_Q(QQuickWidget);
345 updatePending = false;
346
347 if (!q->isVisible() || fakeHidden)
348 return;
349
350 if (!useSoftwareRenderer) {
351 QOpenGLContext *context = offscreenWindow->openglContext();
352 if (!context) {
353 qWarning(msg: "QQuickWidget: Attempted to render scene with no context");
354 return;
355 }
356
357 Q_ASSERT(offscreenSurface);
358 }
359
360 render(needsSync: true);
361
362#if QT_CONFIG(graphicsview)
363 if (q->window()->graphicsProxyWidget())
364 QWidgetPrivate::nearestGraphicsProxyWidget(origin: q)->update();
365 else
366#endif
367 {
368 if (!useSoftwareRenderer)
369 q->update(); // schedule composition
370 else if (!updateRegion.isEmpty())
371 q->update(updateRegion);
372 }
373}
374
375QImage QQuickWidgetPrivate::grabFramebuffer()
376{
377 if (!useSoftwareRenderer) {
378#if QT_CONFIG(opengl)
379 if (!context)
380 return QImage();
381
382 context->makeCurrent(surface: offscreenSurface);
383#endif
384 }
385 return renderControl->grab();
386}
387
388// Intentionally not overriding the QQuickWindow's focusObject.
389// Key events should go to our key event handlers, and then to the
390// QQuickWindow, not any in-scene item.
391
392/*!
393 \module QtQuickWidgets
394 \title Qt Quick Widgets C++ Classes
395 \ingroup modules
396 \brief The C++ API provided by the Qt Quick Widgets module.
397 \qtvariable quickwidgets
398
399 To link against the module, add this line to your \l qmake
400 \c .pro file:
401
402 \code
403 QT += quickwidgets
404 \endcode
405
406 For more information, see the QQuickWidget class documentation.
407*/
408
409/*!
410 \class QQuickWidget
411 \since 5.3
412 \brief The QQuickWidget class provides a widget for displaying a Qt Quick user interface.
413
414 \inmodule QtQuickWidgets
415
416 This is a convenience wrapper for QQuickWindow which will automatically load and display a QML
417 scene when given the URL of the main source file. Alternatively, you can instantiate your own
418 objects using QQmlComponent and place them in a manually set up QQuickWidget.
419
420 Typical usage:
421
422 \code
423 QQuickWidget *view = new QQuickWidget;
424 view->setSource(QUrl::fromLocalFile("myqmlfile.qml"));
425 view->show();
426 \endcode
427
428 To receive errors related to loading and executing QML with QQuickWidget,
429 you can connect to the statusChanged() signal and monitor for QQuickWidget::Error.
430 The errors are available via QQuickWidget::errors().
431
432 QQuickWidget also manages sizing of the view and root object. By default, the \l resizeMode
433 is SizeViewToRootObject, which will load the component and resize it to the
434 size of the view. Alternatively the resizeMode may be set to SizeRootObjectToView which
435 will resize the view to the size of the root object.
436
437 \note QQuickWidget is an alternative to using QQuickView and QWidget::createWindowContainer().
438 The restrictions on stacking order do not apply, making QQuickWidget the more flexible
439 alternative, behaving more like an ordinary widget. This comes at the expense of
440 performance. Unlike QQuickWindow and QQuickView, QQuickWidget involves rendering into OpenGL
441 framebuffer objects. This will naturally carry a minor performance hit.
442
443 \note Using QQuickWidget disables the threaded render loop on all platforms. This means that
444 some of the benefits of threaded rendering, for example \l Animator classes and vsync driven
445 animations, will not be available.
446
447 \note Avoid calling winId() on a QQuickWidget. This function triggers the creation of
448 a native window, resulting in reduced performance and possibly rendering glitches. The
449 entire purpose of QQuickWidget is to render Quick scenes without a separate native
450 window, hence making it a native widget should always be avoided.
451
452 \section1 Scene Graph and Context Persistency
453
454 QQuickWidget honors QQuickWindow::isPersistentSceneGraph(), meaning that
455 applications can decide - by calling
456 QQuickWindow::setPersistentSceneGraph() on the window returned from the
457 quickWindow() function - to let scenegraph nodes and other Qt Quick scene
458 related resources be released whenever the widget becomes hidden. By default
459 persistency is enabled, just like with QQuickWindow.
460
461 When running with the OpenGL backend of the scene graph, QQuickWindow
462 offers the possibility to disable persistent OpenGL contexts as well. This
463 setting is currently ignored by QQuickWidget and the context is always
464 persistent. The OpenGL context is thus not destroyed when hiding the
465 widget. The context is destroyed only when the widget is destroyed or when
466 the widget gets reparented into another top-level widget's child hierarchy.
467 However, some applications, in particular those that have their own
468 graphics resources due to performing custom OpenGL rendering in the Qt
469 Quick scene, may wish to disable the latter since they may not be prepared
470 to handle the loss of the context when moving a QQuickWidget into another
471 window. Such applications can set the
472 QCoreApplication::AA_ShareOpenGLContexts attribute. For a discussion on the
473 details of resource initialization and cleanup, refer to the QOpenGLWidget
474 documentation.
475
476 \note QQuickWidget offers less fine-grained control over its internal
477 OpenGL context than QOpenGLWidget, and there are subtle differences, most
478 notably that disabling the persistent scene graph will lead to destroying
479 the context on a window change regardless of the presence of
480 QCoreApplication::AA_ShareOpenGLContexts.
481
482 \section1 Limitations
483
484 Putting other widgets underneath and making the QQuickWidget transparent will not lead
485 to the expected results: the widgets underneath will not be visible. This is because
486 in practice the QQuickWidget is drawn before all other regular, non-OpenGL widgets,
487 and so see-through types of solutions are not feasible. Other type of layouts, like
488 having widgets on top of the QQuickWidget, will function as expected.
489
490 When absolutely necessary, this limitation can be overcome by setting the
491 Qt::WA_AlwaysStackOnTop attribute on the QQuickWidget. Be aware, however that this
492 breaks stacking order. For example it will not be possible to have other widgets on
493 top of the QQuickWidget, so it should only be used in situations where a
494 semi-transparent QQuickWidget with other widgets visible underneath is required.
495
496 This limitation only applies when there are other widgets underneath the QQuickWidget
497 inside the same window. Making the window semi-transparent, with other applications
498 and the desktop visible in the background, is done in the traditional way: Set
499 Qt::WA_TranslucentBackground on the top-level window, request an alpha channel, and
500 change the Qt Quick Scenegraph's clear color to Qt::transparent via setClearColor().
501
502 \section1 Support when not using OpenGL
503
504 In addition to OpenGL, the \c software backend of Qt Quick also supports
505 QQuickWidget. Other backends, for example the Direct 3D 12 one, are not
506 compatible however and attempting to construct a QQuickWidget will lead to
507 problems.
508
509 \section1 Tab Key Handling
510
511 On press of the \c[TAB] key, the item inside the QQuickWidget gets focus. If
512 this item can handle \c[TAB] key press, focus will change accordingly within
513 the item, otherwise the next widget in the focus chain gets focus.
514
515 \sa {Exposing Attributes of C++ Types to QML}, {Qt Quick Widgets Example}, QQuickView
516*/
517
518
519/*!
520 \fn void QQuickWidget::statusChanged(QQuickWidget::Status status)
521 This signal is emitted when the component's current \a status changes.
522*/
523
524/*!
525 Constructs a QQuickWidget with the given \a parent.
526 The default value of \a parent is 0.
527
528*/
529QQuickWidget::QQuickWidget(QWidget *parent)
530 : QWidget(*(new QQuickWidgetPrivate), parent, {})
531{
532 setMouseTracking(true);
533 setFocusPolicy(Qt::StrongFocus);
534 d_func()->init();
535}
536
537/*!
538 Constructs a QQuickWidget with the given QML \a source and \a parent.
539 The default value of \a parent is 0.
540
541*/
542QQuickWidget::QQuickWidget(const QUrl &source, QWidget *parent)
543 : QQuickWidget(parent)
544{
545 setSource(source);
546}
547
548/*!
549 Constructs a QQuickWidget with the given QML \a engine and \a parent.
550
551 Note: In this case, the QQuickWidget does not own the given \a engine object;
552 it is the caller's responsibility to destroy the engine. If the \a engine is deleted
553 before the view, status() will return QQuickWidget::Error.
554
555 \sa Status, status(), errors()
556*/
557QQuickWidget::QQuickWidget(QQmlEngine* engine, QWidget *parent)
558 : QWidget(*(new QQuickWidgetPrivate), parent, {})
559{
560 setMouseTracking(true);
561 setFocusPolicy(Qt::StrongFocus);
562 d_func()->init(e: engine);
563}
564
565/*!
566 Destroys the QQuickWidget.
567*/
568QQuickWidget::~QQuickWidget()
569{
570 // Ensure that the component is destroyed before the engine; the engine may
571 // be a child of the QQuickWidgetPrivate, and will be destroyed by its dtor
572 Q_D(QQuickWidget);
573 delete d->root;
574 d->root = nullptr;
575}
576
577/*!
578 \property QQuickWidget::source
579 \brief The URL of the source of the QML component.
580
581 Ensure that the URL provided is full and correct, in particular, use
582 \l QUrl::fromLocalFile() when loading a file from the local filesystem.
583
584 \note Setting a source URL will result in the QML component being
585 instantiated, even if the URL is unchanged from the current value.
586*/
587
588/*!
589 Sets the source to the \a url, loads the QML component and instantiates it.
590
591 Ensure that the URL provided is full and correct, in particular, use
592 \l QUrl::fromLocalFile() when loading a file from the local filesystem.
593
594 Calling this method multiple times with the same URL will result
595 in the QML component being reinstantiated.
596 */
597void QQuickWidget::setSource(const QUrl& url)
598{
599 Q_D(QQuickWidget);
600 d->source = url;
601 d->execute();
602}
603
604/*!
605 \internal
606
607 Sets the source \a url, \a component and content \a item (root of the QML object hierarchy) directly.
608 */
609void QQuickWidget::setContent(const QUrl& url, QQmlComponent *component, QObject* item)
610{
611 Q_D(QQuickWidget);
612 d->source = url;
613 d->component = component;
614
615 if (d->component && d->component->isError()) {
616 const QList<QQmlError> errorList = d->component->errors();
617 for (const QQmlError &error : errorList) {
618 QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning()
619 << error;
620 }
621 emit statusChanged(status());
622 return;
623 }
624
625 d->setRootObject(item);
626 emit statusChanged(status());
627}
628
629/*!
630 Returns the source URL, if set.
631
632 \sa setSource()
633 */
634QUrl QQuickWidget::source() const
635{
636 Q_D(const QQuickWidget);
637 return d->source;
638}
639
640/*!
641 Returns a pointer to the QQmlEngine used for instantiating
642 QML Components.
643 */
644QQmlEngine* QQuickWidget::engine() const
645{
646 Q_D(const QQuickWidget);
647 d->ensureEngine();
648 return const_cast<QQmlEngine *>(d->engine.data());
649}
650
651/*!
652 This function returns the root of the context hierarchy. Each QML
653 component is instantiated in a QQmlContext. QQmlContext's are
654 essential for passing data to QML components. In QML, contexts are
655 arranged hierarchically and this hierarchy is managed by the
656 QQmlEngine.
657 */
658QQmlContext* QQuickWidget::rootContext() const
659{
660 Q_D(const QQuickWidget);
661 d->ensureEngine();
662 return d->engine.data()->rootContext();
663}
664
665/*!
666 \enum QQuickWidget::Status
667 Specifies the loading status of the QQuickWidget.
668
669 \value Null This QQuickWidget has no source set.
670 \value Ready This QQuickWidget has loaded and created the QML component.
671 \value Loading This QQuickWidget is loading network data.
672 \value Error One or more errors occurred. Call errors() to retrieve a list
673 of errors.
674*/
675
676/*! \enum QQuickWidget::ResizeMode
677
678 This enum specifies how to resize the view.
679
680 \value SizeViewToRootObject The view resizes with the root item in the QML.
681 \value SizeRootObjectToView The view will automatically resize the root item to the size of the view.
682*/
683
684/*!
685 \fn void QQuickWidget::sceneGraphError(QQuickWindow::SceneGraphError error, const QString &message)
686
687 This signal is emitted when an \a error occurred during scene graph initialization.
688
689 Applications should connect to this signal if they wish to handle errors,
690 like OpenGL context creation failures, in a custom way. When no slot is
691 connected to the signal, the behavior will be different: Quick will print
692 the \a message, or show a message box, and terminate the application.
693
694 This signal will be emitted from the GUI thread.
695
696 \sa QQuickWindow::sceneGraphError()
697 */
698
699/*!
700 \property QQuickWidget::status
701 The component's current \l{QQuickWidget::Status} {status}.
702*/
703
704QQuickWidget::Status QQuickWidget::status() const
705{
706 Q_D(const QQuickWidget);
707 if (!d->engine && !d->source.isEmpty())
708 return QQuickWidget::Error;
709
710 if (!d->component)
711 return QQuickWidget::Null;
712
713 if (d->component->status() == QQmlComponent::Ready && !d->root)
714 return QQuickWidget::Error;
715
716 return QQuickWidget::Status(d->component->status());
717}
718
719/*!
720 Return the list of errors that occurred during the last compile or create
721 operation. When the status is not \l Error, an empty list is returned.
722
723 \sa status
724*/
725QList<QQmlError> QQuickWidget::errors() const
726{
727 Q_D(const QQuickWidget);
728 QList<QQmlError> errs;
729
730 if (d->component)
731 errs = d->component->errors();
732
733 if (!d->engine && !d->source.isEmpty()) {
734 QQmlError error;
735 error.setDescription(QLatin1String("QQuickWidget: invalid qml engine."));
736 errs << error;
737 }
738 if (d->component && d->component->status() == QQmlComponent::Ready && !d->root) {
739 QQmlError error;
740 error.setDescription(QLatin1String("QQuickWidget: invalid root object."));
741 errs << error;
742 }
743
744 return errs;
745}
746
747/*!
748 \property QQuickWidget::resizeMode
749 \brief Determines whether the view should resize the window contents.
750
751 If this property is set to SizeViewToRootObject (the default), the view
752 resizes to the size of the root item in the QML.
753
754 If this property is set to SizeRootObjectToView, the view will
755 automatically resize the root item to the size of the view.
756
757 Regardless of this property, the sizeHint of the view
758 is the initial size of the root item. Note though that
759 since QML may load dynamically, that size may change.
760
761 \sa initialSize()
762*/
763
764void QQuickWidget::setResizeMode(ResizeMode mode)
765{
766 Q_D(QQuickWidget);
767 if (d->resizeMode == mode)
768 return;
769
770 if (d->root) {
771 if (d->resizeMode == SizeViewToRootObject) {
772 QQuickItemPrivate *p = QQuickItemPrivate::get(item: d->root);
773 p->removeItemChangeListener(d, types: QQuickItemPrivate::Geometry);
774 }
775 }
776
777 d->resizeMode = mode;
778 if (d->root) {
779 d->initResize();
780 }
781}
782
783void QQuickWidgetPrivate::initResize()
784{
785 if (root) {
786 if (resizeMode == QQuickWidget::SizeViewToRootObject) {
787 QQuickItemPrivate *p = QQuickItemPrivate::get(item: root);
788 p->addItemChangeListener(listener: this, types: QQuickItemPrivate::Geometry);
789 }
790 }
791 updateSize();
792}
793
794void QQuickWidgetPrivate::updateSize()
795{
796 Q_Q(QQuickWidget);
797 if (!root)
798 return;
799
800 if (resizeMode == QQuickWidget::SizeViewToRootObject) {
801 QSize newSize = QSize(root->width(), root->height());
802 if (newSize.isValid() && newSize != q->size()) {
803 q->resize(newSize);
804 q->updateGeometry();
805 }
806 } else if (resizeMode == QQuickWidget::SizeRootObjectToView) {
807 const bool needToUpdateWidth = !qFuzzyCompare(p1: q->width(), p2: root->width());
808 const bool needToUpdateHeight = !qFuzzyCompare(p1: q->height(), p2: root->height());
809
810 if (needToUpdateWidth && needToUpdateHeight) {
811 // Make sure that we have realistic sizing behavior by following
812 // what on-screen windows would do and resize everything, not just
813 // the root item. We do this because other types may be relying on
814 // us to behave correctly.
815 const QSizeF newSize(q->width(), q->height());
816 offscreenWindow->resize(newSize: newSize.toSize());
817 offscreenWindow->contentItem()->setSize(newSize);
818 root->setSize(newSize);
819 } else if (needToUpdateWidth) {
820 const int newWidth = q->width();
821 offscreenWindow->setWidth(newWidth);
822 offscreenWindow->contentItem()->setWidth(newWidth);
823 root->setWidth(newWidth);
824 } else if (needToUpdateHeight) {
825 const int newHeight = q->height();
826 offscreenWindow->setHeight(newHeight);
827 offscreenWindow->contentItem()->setHeight(newHeight);
828 root->setHeight(newHeight);
829 }
830 }
831}
832
833/*!
834 \internal
835
836 Update the position of the offscreen window, so it matches the position of the QQuickWidget.
837 */
838void QQuickWidgetPrivate::updatePosition()
839{
840 Q_Q(QQuickWidget);
841 if (offscreenWindow == nullptr)
842 return;
843
844 const QPoint &pos = q->mapToGlobal(QPoint(0, 0));
845 if (offscreenWindow->position() != pos)
846 offscreenWindow->setPosition(pos);
847}
848
849QSize QQuickWidgetPrivate::rootObjectSize() const
850{
851 QSize rootObjectSize(0,0);
852 int widthCandidate = -1;
853 int heightCandidate = -1;
854 if (root) {
855 widthCandidate = root->width();
856 heightCandidate = root->height();
857 }
858 if (widthCandidate > 0) {
859 rootObjectSize.setWidth(widthCandidate);
860 }
861 if (heightCandidate > 0) {
862 rootObjectSize.setHeight(heightCandidate);
863 }
864 return rootObjectSize;
865}
866
867void QQuickWidgetPrivate::handleContextCreationFailure(const QSurfaceFormat &format)
868{
869 Q_Q(QQuickWidget);
870
871 QString translatedMessage;
872 QString untranslatedMessage;
873 QQuickWindowPrivate::contextCreationFailureMessage(format, translatedMessage: &translatedMessage, untranslatedMessage: &untranslatedMessage);
874
875 static const QMetaMethod errorSignal = QMetaMethod::fromSignal(signal: &QQuickWidget::sceneGraphError);
876 const bool signalConnected = q->isSignalConnected(signal: errorSignal);
877 if (signalConnected)
878 emit q->sceneGraphError(error: QQuickWindow::ContextNotAvailable, message: translatedMessage);
879
880#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
881 if (!signalConnected && !QLibraryInfo::isDebugBuild() && !GetConsoleWindow())
882 QMessageBox::critical(q, QCoreApplication::applicationName(), translatedMessage);
883#endif // Q_OS_WIN && !Q_OS_WINRT
884 if (!signalConnected)
885 qFatal(msg: "%s", qPrintable(untranslatedMessage));
886}
887
888// Never called by Software Rendering backend
889void QQuickWidgetPrivate::createContext()
890{
891#if QT_CONFIG(opengl)
892 Q_Q(QQuickWidget);
893
894 // On hide-show we may invalidate() (when !isPersistentSceneGraph) but our
895 // context is kept. We may need to initialize() again, though.
896 const bool reinit = context && !offscreenWindow->openglContext();
897
898 if (!reinit) {
899 if (context)
900 return;
901
902 context = new QOpenGLContext;
903 context->setFormat(offscreenWindow->requestedFormat());
904 const QWindow *win = q->window()->windowHandle();
905 if (win && win->screen())
906 context->setScreen(win->screen());
907 QOpenGLContext *shareContext = qt_gl_global_share_context();
908 if (!shareContext)
909 shareContext = QWidgetPrivate::get(w: q->window())->shareContext();
910 if (shareContext) {
911 context->setShareContext(shareContext);
912 context->setScreen(shareContext->screen());
913 }
914 if (!context->create()) {
915 delete context;
916 context = nullptr;
917 handleContextCreationFailure(format: offscreenWindow->requestedFormat());
918 return;
919 }
920
921 offscreenSurface = new QOffscreenSurface;
922 // Pass the context's format(), which, now that the underlying platform context is created,
923 // contains a QSurfaceFormat representing the _actual_ format of the underlying
924 // configuration. This is essential to get a surface that is compatible with the context.
925 offscreenSurface->setFormat(context->format());
926 offscreenSurface->setScreen(context->screen());
927 offscreenSurface->create();
928 }
929
930 if (context->makeCurrent(surface: offscreenSurface)) {
931 if (!offscreenWindow->openglContext())
932 renderControl->initialize(gl: context);
933 } else
934#endif
935 qWarning(msg: "QQuickWidget: Failed to make context current");
936}
937
938// Never called by Software Rendering backend
939void QQuickWidgetPrivate::destroyContext()
940{
941 delete offscreenSurface;
942 offscreenSurface = nullptr;
943#if QT_CONFIG(opengl)
944 delete context;
945 context = nullptr;
946#endif
947}
948
949void QQuickWidget::createFramebufferObject()
950{
951 Q_D(QQuickWidget);
952
953 // Could come from Show -> createContext -> sceneGraphInitialized in which case the size may
954 // still be invalid on some platforms. Bail out. A resize will come later on.
955 if (size().isEmpty())
956 return;
957
958 // Even though this is just an offscreen window we should set the position on it, as it might be
959 // useful for an item to know the actual position of the scene.
960 // Note: The position will be update when we get a move event (see: updatePosition()).
961 const QPoint &globalPos = mapToGlobal(QPoint(0, 0));
962 d->offscreenWindow->setGeometry(posx: globalPos.x(), posy: globalPos.y(), w: width(), h: height());
963
964 if (d->useSoftwareRenderer) {
965 const QSize imageSize = size() * devicePixelRatioF();
966 d->softwareImage = QImage(imageSize, QImage::Format_ARGB32_Premultiplied);
967 d->softwareImage.setDevicePixelRatio(devicePixelRatioF());
968 d->forceFullUpdate = true;
969 return;
970 }
971
972#if QT_CONFIG(opengl)
973 QOpenGLContext *context = d->offscreenWindow->openglContext();
974
975 if (!context) {
976 qWarning(msg: "QQuickWidget: Attempted to create FBO with no context");
977 return;
978 }
979
980 QOpenGLContext *shareWindowContext = QWidgetPrivate::get(w: window())->shareContext();
981 if (shareWindowContext && context->shareContext() != shareWindowContext && !qGuiApp->testAttribute(attribute: Qt::AA_ShareOpenGLContexts)) {
982 context->setShareContext(shareWindowContext);
983 context->setScreen(shareWindowContext->screen());
984 if (!context->create())
985 qWarning(msg: "QQuickWidget: Failed to recreate context");
986 // The screen may be different so we must recreate the offscreen surface too.
987 // Unlike QOpenGLContext, QOffscreenSurface's create() does not recreate so have to destroy() first.
988 d->offscreenSurface->destroy();
989 d->offscreenSurface->setScreen(context->screen());
990 d->offscreenSurface->create();
991 }
992
993 bool current = context->makeCurrent(surface: d->offscreenSurface);
994 if (!current) {
995 qWarning(msg: "QQuickWidget: Failed to make context current when creating FBO");
996 return;
997 }
998
999 int samples = d->requestedSamples;
1000 if (!QOpenGLExtensions(context).hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferMultisample))
1001 samples = 0;
1002
1003 QOpenGLFramebufferObjectFormat format;
1004 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
1005 format.setSamples(samples);
1006
1007 // The default framebuffer for normal windows have sRGB support on OS X which leads to the Qt Quick text item
1008 // utilizing sRGB blending. To get identical results with QQuickWidget we have to have our framebuffer backed
1009 // by an sRGB texture.
1010#ifdef Q_OS_OSX
1011 if (context->hasExtension("GL_ARB_framebuffer_sRGB")
1012 && context->hasExtension("GL_EXT_texture_sRGB")
1013 && context->hasExtension("GL_EXT_texture_sRGB_decode"))
1014 format.setInternalTextureFormat(GL_SRGB8_ALPHA8_EXT);
1015#endif
1016
1017 const QSize fboSize = size() * devicePixelRatioF();
1018
1019 // Could be a simple hide - show, in which case the previous fbo is just fine.
1020 if (!d->fbo || d->fbo->size() != fboSize) {
1021 delete d->fbo;
1022 d->fbo = new QOpenGLFramebufferObject(fboSize, format);
1023 }
1024
1025 // When compositing in the backingstore, sampling the sRGB texture would perform an
1026 // sRGB-linear conversion which is not what we want when the final framebuffer (the window's)
1027 // is sRGB too. Disable the conversion.
1028#ifdef Q_OS_OSX
1029 if (format.internalTextureFormat() == GL_SRGB8_ALPHA8_EXT) {
1030 QOpenGLFunctions *funcs = context->functions();
1031 funcs->glBindTexture(GL_TEXTURE_2D, d->fbo->texture());
1032 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SRGB_DECODE_EXT, GL_SKIP_DECODE_EXT);
1033 }
1034#endif
1035
1036 d->offscreenWindow->setRenderTarget(d->fbo);
1037
1038 if (samples > 0)
1039 d->resolvedFbo = new QOpenGLFramebufferObject(fboSize);
1040
1041 // Sanity check: The window must not have an underlying platform window.
1042 // Having one would mean create() was called and platforms that only support
1043 // a single native window were in trouble.
1044 Q_ASSERT(!d->offscreenWindow->handle());
1045#endif
1046}
1047
1048void QQuickWidget::destroyFramebufferObject()
1049{
1050 Q_D(QQuickWidget);
1051
1052 if (d->useSoftwareRenderer) {
1053 d->softwareImage = QImage();
1054 return;
1055 }
1056
1057#if QT_CONFIG(opengl)
1058 delete d->fbo;
1059 d->fbo = nullptr;
1060 delete d->resolvedFbo;
1061 d->resolvedFbo = nullptr;
1062#endif
1063}
1064
1065QQuickWidget::ResizeMode QQuickWidget::resizeMode() const
1066{
1067 Q_D(const QQuickWidget);
1068 return d->resizeMode;
1069}
1070
1071/*!
1072 \internal
1073 */
1074void QQuickWidget::continueExecute()
1075{
1076 Q_D(QQuickWidget);
1077 disconnect(sender: d->component, SIGNAL(statusChanged(QQmlComponent::Status)), receiver: this, SLOT(continueExecute()));
1078
1079 if (d->component->isError()) {
1080 const QList<QQmlError> errorList = d->component->errors();
1081 for (const QQmlError &error : errorList) {
1082 QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning()
1083 << error;
1084 }
1085 emit statusChanged(status());
1086 return;
1087 }
1088
1089 QObject *obj = d->component->create();
1090
1091 if (d->component->isError()) {
1092 const QList<QQmlError> errorList = d->component->errors();
1093 for (const QQmlError &error : errorList) {
1094 QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning()
1095 << error;
1096 }
1097 emit statusChanged(status());
1098 return;
1099 }
1100
1101 d->setRootObject(obj);
1102 emit statusChanged(status());
1103}
1104
1105
1106/*!
1107 \internal
1108*/
1109void QQuickWidgetPrivate::setRootObject(QObject *obj)
1110{
1111 Q_Q(QQuickWidget);
1112 if (root == obj)
1113 return;
1114 if (QQuickItem *sgItem = qobject_cast<QQuickItem *>(object: obj)) {
1115 root = sgItem;
1116 sgItem->setParentItem(offscreenWindow->contentItem());
1117 } else if (qobject_cast<QWindow *>(o: obj)) {
1118 qWarning() << "QQuickWidget does not support using windows as a root item." << Qt::endl
1119 << Qt::endl
1120 << "If you wish to create your root window from QML, consider using QQmlApplicationEngine instead." << Qt::endl;
1121 } else {
1122 qWarning() << "QQuickWidget only supports loading of root objects that derive from QQuickItem." << Qt::endl
1123 << Qt::endl
1124 << "Ensure your QML code is written for QtQuick 2, and uses a root that is or" << Qt::endl
1125 << "inherits from QtQuick's Item (not a Timer, QtObject, etc)." << Qt::endl;
1126 delete obj;
1127 root = nullptr;
1128 }
1129 if (root) {
1130 initialSize = rootObjectSize();
1131 bool resized = q->testAttribute(attribute: Qt::WA_Resized);
1132 if ((resizeMode == QQuickWidget::SizeViewToRootObject || !resized) &&
1133 initialSize != q->size()) {
1134 q->resize(initialSize);
1135 }
1136 initResize();
1137 }
1138}
1139
1140#if QT_CONFIG(opengl)
1141GLuint QQuickWidgetPrivate::textureId() const
1142{
1143 Q_Q(const QQuickWidget);
1144 if (!q->isWindow() && q->internalWinId()) {
1145 qWarning() << "QQuickWidget cannot be used as a native child widget."
1146 << "Consider setting Qt::AA_DontCreateNativeWidgetSiblings";
1147 return 0;
1148 }
1149 return resolvedFbo ? resolvedFbo->texture()
1150 : (fbo ? fbo->texture() : 0);
1151}
1152
1153QPlatformTextureList::Flags QQuickWidgetPrivate::textureListFlags()
1154{
1155 QPlatformTextureList::Flags flags = QWidgetPrivate::textureListFlags();
1156 flags |= QPlatformTextureList::NeedsPremultipliedAlphaBlending;
1157 return flags;
1158}
1159#endif
1160
1161/*!
1162 \internal
1163 Handle item resize and scene updates.
1164 */
1165void QQuickWidget::timerEvent(QTimerEvent* e)
1166{
1167 Q_D(QQuickWidget);
1168 if (!e || e->timerId() == d->resizetimer.timerId()) {
1169 d->updateSize();
1170 d->resizetimer.stop();
1171 } else if (e->timerId() == d->updateTimer.timerId()) {
1172 d->eventPending = false;
1173 d->updateTimer.stop();
1174 if (d->updatePending)
1175 d->renderSceneGraph();
1176 }
1177}
1178
1179/*!
1180 \internal
1181 Preferred size follows the root object geometry.
1182*/
1183QSize QQuickWidget::sizeHint() const
1184{
1185 Q_D(const QQuickWidget);
1186 QSize rootObjectSize = d->rootObjectSize();
1187 if (rootObjectSize.isEmpty()) {
1188 return size();
1189 } else {
1190 return rootObjectSize;
1191 }
1192}
1193
1194/*!
1195 Returns the initial size of the root object.
1196
1197 If \l resizeMode is SizeRootObjectToView, the root object will be
1198 resized to the size of the view. This function returns the size of the
1199 root object before it was resized.
1200*/
1201QSize QQuickWidget::initialSize() const
1202{
1203 Q_D(const QQuickWidget);
1204 return d->initialSize;
1205}
1206
1207/*!
1208 Returns the view's root \l {QQuickItem} {item}. Can be null
1209 when setSource() has not been called, if it was called with
1210 broken QtQuick code or while the QtQuick contents are being created.
1211 */
1212QQuickItem *QQuickWidget::rootObject() const
1213{
1214 Q_D(const QQuickWidget);
1215 return d->root;
1216}
1217
1218/*!
1219 \internal
1220 This function handles the \l {QResizeEvent} {resize event}
1221 \a e.
1222 */
1223void QQuickWidget::resizeEvent(QResizeEvent *e)
1224{
1225 Q_D(QQuickWidget);
1226 if (d->resizeMode == SizeRootObjectToView)
1227 d->updateSize();
1228
1229 if (e->size().isEmpty()) {
1230 //stop rendering
1231 d->fakeHidden = true;
1232 return;
1233 }
1234
1235 bool needsSync = false;
1236 if (d->fakeHidden) {
1237 //restart rendering
1238 d->fakeHidden = false;
1239 needsSync = true;
1240 }
1241
1242 // Software Renderer
1243 if (d->useSoftwareRenderer) {
1244 needsSync = true;
1245 if (d->softwareImage.size() != size() * devicePixelRatioF()) {
1246 createFramebufferObject();
1247 }
1248 } else {
1249#if QT_CONFIG(opengl)
1250 if (d->context) {
1251 // Bail out when receiving a resize after scenegraph invalidation. This can happen
1252 // during hide - resize - show sequences and also during application exit.
1253 if (!d->fbo && !d->offscreenWindow->openglContext())
1254 return;
1255 if (!d->fbo || d->fbo->size() != size() * devicePixelRatioF()) {
1256 needsSync = true;
1257 createFramebufferObject();
1258 }
1259 } else {
1260 // This will result in a scenegraphInitialized() signal which
1261 // is connected to createFramebufferObject().
1262 needsSync = true;
1263 d->createContext();
1264 }
1265
1266 QOpenGLContext *context = d->offscreenWindow->openglContext();
1267 if (!context) {
1268 qWarning(msg: "QQuickWidget::resizeEvent() no OpenGL context");
1269 return;
1270 }
1271#endif
1272 }
1273
1274 d->render(needsSync);
1275}
1276
1277/*! \reimp */
1278bool QQuickWidget::focusNextPrevChild(bool next)
1279{
1280 Q_D(QQuickWidget);
1281 QKeyEvent event(QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier);
1282 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyPress, event.key(),
1283 Qt::NoModifier);
1284 QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &event);
1285
1286 QKeyEvent releaseEvent(QEvent::KeyRelease, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier);
1287 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyRelease, releaseEvent.key(),
1288 Qt::NoModifier);
1289 QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &releaseEvent);
1290 return event.isAccepted();
1291}
1292
1293/*! \reimp */
1294void QQuickWidget::keyPressEvent(QKeyEvent *e)
1295{
1296 Q_D(QQuickWidget);
1297 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyPress, e->key(),
1298 e->modifiers());
1299
1300 QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: e);
1301}
1302
1303/*! \reimp */
1304void QQuickWidget::keyReleaseEvent(QKeyEvent *e)
1305{
1306 Q_D(QQuickWidget);
1307 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyRelease, e->key(),
1308 e->modifiers());
1309
1310 QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: e);
1311}
1312
1313/*! \reimp */
1314void QQuickWidget::mouseMoveEvent(QMouseEvent *e)
1315{
1316 Q_D(QQuickWidget);
1317 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseMove, e->localPos().x(),
1318 e->localPos().y());
1319
1320 // Put localPos into the event's localPos and windowPos, and screenPos into the
1321 // event's screenPos. This way the windowPos in e is ignored and is replaced by
1322 // localPos. This is necessary because QQuickWindow thinks of itself as a
1323 // top-level window always.
1324 QMouseEvent mappedEvent(e->type(), e->localPos(), e->localPos(), e->screenPos(),
1325 e->button(), e->buttons(), e->modifiers(), e->source());
1326 QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &mappedEvent);
1327 e->setAccepted(mappedEvent.isAccepted());
1328}
1329
1330/*! \reimp */
1331void QQuickWidget::mouseDoubleClickEvent(QMouseEvent *e)
1332{
1333 Q_D(QQuickWidget);
1334 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseDoubleClick,
1335 e->button(), e->buttons());
1336
1337 // As the second mouse press is suppressed in widget windows we emulate it here for QML.
1338 // See QTBUG-25831
1339 QMouseEvent pressEvent(QEvent::MouseButtonPress, e->localPos(), e->localPos(), e->screenPos(),
1340 e->button(), e->buttons(), e->modifiers(), e->source());
1341 QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &pressEvent);
1342 e->setAccepted(pressEvent.isAccepted());
1343 QMouseEvent mappedEvent(e->type(), e->localPos(), e->localPos(), e->screenPos(),
1344 e->button(), e->buttons(), e->modifiers(), e->source());
1345 QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &mappedEvent);
1346}
1347
1348/*! \reimp */
1349void QQuickWidget::showEvent(QShowEvent *)
1350{
1351 Q_D(QQuickWidget);
1352 bool shouldTriggerUpdate = true;
1353
1354 if (!d->useSoftwareRenderer) {
1355 d->createContext();
1356
1357 if (d->offscreenWindow->openglContext()) {
1358 shouldTriggerUpdate = false;
1359 d->render(needsSync: true);
1360 // render() may have led to a QQuickWindow::update() call (for
1361 // example, having a scene with a QQuickFramebufferObject::Renderer
1362 // calling update() in its render()) which in turn results in
1363 // renderRequested in the rendercontrol, ending up in
1364 // triggerUpdate. In this case just calling update() is not
1365 // acceptable, we need the full renderSceneGraph issued from
1366 // timerEvent().
1367 if (!d->eventPending && d->updatePending) {
1368 d->updatePending = false;
1369 update();
1370 }
1371 }
1372 }
1373
1374 if (shouldTriggerUpdate)
1375 triggerUpdate();
1376
1377 // note offscreenWindow is "QQuickOffScreenWindow" instance
1378 d->offscreenWindow->setVisible(true);
1379 if (QQmlInspectorService *service = QQmlDebugConnector::service<QQmlInspectorService>())
1380 service->setParentWindow(d->offscreenWindow, window()->windowHandle());
1381}
1382
1383/*! \reimp */
1384void QQuickWidget::hideEvent(QHideEvent *)
1385{
1386 Q_D(QQuickWidget);
1387 if (!d->offscreenWindow->isPersistentSceneGraph())
1388 d->invalidateRenderControl();
1389 // note offscreenWindow is "QQuickOffScreenWindow" instance
1390 d->offscreenWindow->setVisible(false);
1391 if (QQmlInspectorService *service = QQmlDebugConnector::service<QQmlInspectorService>())
1392 service->setParentWindow(d->offscreenWindow, d->offscreenWindow);
1393}
1394
1395/*! \reimp */
1396void QQuickWidget::mousePressEvent(QMouseEvent *e)
1397{
1398 Q_D(QQuickWidget);
1399 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMousePress, e->button(),
1400 e->buttons());
1401
1402 QMouseEvent mappedEvent(e->type(), e->localPos(), e->localPos(), e->screenPos(),
1403 e->button(), e->buttons(), e->modifiers(), e->source());
1404 QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &mappedEvent);
1405 e->setAccepted(mappedEvent.isAccepted());
1406}
1407
1408/*! \reimp */
1409void QQuickWidget::mouseReleaseEvent(QMouseEvent *e)
1410{
1411 Q_D(QQuickWidget);
1412 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseRelease, e->button(),
1413 e->buttons());
1414
1415 QMouseEvent mappedEvent(e->type(), e->localPos(), e->localPos(), e->screenPos(),
1416 e->button(), e->buttons(), e->modifiers(), e->source());
1417 QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &mappedEvent);
1418 e->setAccepted(mappedEvent.isAccepted());
1419}
1420
1421#if QT_CONFIG(wheelevent)
1422/*! \reimp */
1423void QQuickWidget::wheelEvent(QWheelEvent *e)
1424{
1425 Q_D(QQuickWidget);
1426 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseWheel,
1427 e->angleDelta().x(), e->angleDelta().y());
1428
1429 // Wheel events only have local and global positions, no need to map.
1430 QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: e);
1431}
1432#endif
1433
1434/*!
1435 \reimp
1436*/
1437void QQuickWidget::focusInEvent(QFocusEvent * event)
1438{
1439 Q_D(QQuickWidget);
1440 d->offscreenWindow->focusInEvent(event);
1441}
1442
1443/*!
1444 \reimp
1445*/
1446void QQuickWidget::focusOutEvent(QFocusEvent * event)
1447{
1448 Q_D(QQuickWidget);
1449 d->offscreenWindow->focusOutEvent(event);
1450}
1451
1452static Qt::WindowState resolveWindowState(Qt::WindowStates states)
1453{
1454 // No more than one of these 3 can be set
1455 if (states & Qt::WindowMinimized)
1456 return Qt::WindowMinimized;
1457 if (states & Qt::WindowMaximized)
1458 return Qt::WindowMaximized;
1459 if (states & Qt::WindowFullScreen)
1460 return Qt::WindowFullScreen;
1461
1462 // No state means "windowed" - we ignore Qt::WindowActive
1463 return Qt::WindowNoState;
1464}
1465
1466static void remapInputMethodQueryEvent(QObject *object, QInputMethodQueryEvent *e)
1467{
1468 auto item = qobject_cast<QQuickItem *>(object);
1469 if (!item)
1470 return;
1471
1472 // Remap all QRectF values.
1473 for (auto query : {Qt::ImCursorRectangle, Qt::ImAnchorRectangle, Qt::ImInputItemClipRectangle}) {
1474 if (e->queries() & query) {
1475 auto value = e->value(query);
1476 if (value.canConvert<QRectF>())
1477 e->setValue(query, value: item->mapRectToScene(rect: value.toRectF()));
1478 }
1479 }
1480 // Remap all QPointF values.
1481 if (e->queries() & Qt::ImCursorPosition) {
1482 auto value = e->value(query: Qt::ImCursorPosition);
1483 if (value.canConvert<QPointF>())
1484 e->setValue(query: Qt::ImCursorPosition, value: item->mapToScene(point: value.toPointF()));
1485 }
1486}
1487
1488/*! \reimp */
1489bool QQuickWidget::event(QEvent *e)
1490{
1491 Q_D(QQuickWidget);
1492
1493 switch (e->type()) {
1494
1495 case QEvent::Leave:
1496 case QEvent::TouchBegin:
1497 case QEvent::TouchEnd:
1498 case QEvent::TouchUpdate:
1499 case QEvent::TouchCancel:
1500 // Touch events only have local and global positions, no need to map.
1501 return QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: e);
1502
1503 case QEvent::FocusAboutToChange:
1504 return QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: e);
1505
1506 case QEvent::InputMethod:
1507 return QCoreApplication::sendEvent(receiver: d->offscreenWindow->focusObject(), event: e);
1508 case QEvent::InputMethodQuery:
1509 {
1510 bool eventResult = QCoreApplication::sendEvent(receiver: d->offscreenWindow->focusObject(), event: e);
1511 // The result in focusObject are based on offscreenWindow. But
1512 // the inputMethodTransform won't get updated because the focus
1513 // is on QQuickWidget. We need to remap the value based on the
1514 // widget.
1515 remapInputMethodQueryEvent(object: d->offscreenWindow->focusObject(), e: static_cast<QInputMethodQueryEvent *>(e));
1516 return eventResult;
1517 }
1518
1519 case QEvent::WindowChangeInternal:
1520 d->handleWindowChange();
1521 break;
1522
1523 case QEvent::ScreenChangeInternal:
1524 if (QWindow *window = this->window()->windowHandle()) {
1525 QScreen *newScreen = window->screen();
1526
1527 if (d->offscreenWindow)
1528 d->offscreenWindow->setScreen(newScreen);
1529 if (d->offscreenSurface)
1530 d->offscreenSurface->setScreen(newScreen);
1531#if QT_CONFIG(opengl)
1532 if (d->context)
1533 d->context->setScreen(newScreen);
1534#endif
1535 }
1536
1537 if (d->useSoftwareRenderer
1538#if QT_CONFIG(opengl)
1539 || d->fbo
1540#endif
1541 ) {
1542 // This will check the size taking the devicePixelRatio into account
1543 // and recreate if needed.
1544 createFramebufferObject();
1545 d->render(needsSync: true);
1546 }
1547 break;
1548
1549 case QEvent::Show:
1550 case QEvent::Move:
1551 d->updatePosition();
1552 break;
1553
1554 case QEvent::WindowStateChange:
1555 d->offscreenWindow->setWindowState(resolveWindowState(states: windowState()));
1556 break;
1557
1558 case QEvent::ShortcutOverride:
1559 return QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: e);
1560
1561 case QEvent::Enter: {
1562 QEnterEvent *enterEvent = static_cast<QEnterEvent *>(e);
1563 QEnterEvent mappedEvent(enterEvent->localPos(), enterEvent->windowPos(),
1564 enterEvent->screenPos());
1565 const bool ret = QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &mappedEvent);
1566 e->setAccepted(mappedEvent.isAccepted());
1567 return ret;
1568 }
1569 default:
1570 break;
1571 }
1572
1573 return QWidget::event(event: e);
1574}
1575
1576#if QT_CONFIG(quick_draganddrop)
1577
1578/*! \reimp */
1579void QQuickWidget::dragEnterEvent(QDragEnterEvent *e)
1580{
1581 Q_D(QQuickWidget);
1582 // Don't reject drag events for the entire widget when one
1583 // item rejects the drag enter
1584 d->offscreenWindow->event(e);
1585 e->accept();
1586}
1587
1588/*! \reimp */
1589void QQuickWidget::dragMoveEvent(QDragMoveEvent *e)
1590{
1591 Q_D(QQuickWidget);
1592 // Drag/drop events only have local pos, so no need to map,
1593 // but QQuickWindow::event() does not return true
1594 d->offscreenWindow->event(e);
1595}
1596
1597/*! \reimp */
1598void QQuickWidget::dragLeaveEvent(QDragLeaveEvent *e)
1599{
1600 Q_D(QQuickWidget);
1601 d->offscreenWindow->event(e);
1602}
1603
1604/*! \reimp */
1605void QQuickWidget::dropEvent(QDropEvent *e)
1606{
1607 Q_D(QQuickWidget);
1608 d->offscreenWindow->event(e);
1609}
1610
1611#endif // quick_draganddrop
1612
1613// TODO: try to separate the two cases of
1614// 1. render() unconditionally without sync
1615// 2. sync() and then render if necessary
1616void QQuickWidget::triggerUpdate()
1617{
1618 Q_D(QQuickWidget);
1619 d->updatePending = true;
1620 if (!d->eventPending) {
1621 // There's no sense in immediately kicking a render off now, as
1622 // there may be a number of triggerUpdate calls to come from a multitude
1623 // of different sources (network, touch/mouse/keyboard, timers,
1624 // animations, ...), and we want to batch them all into single frames as
1625 // much as possible for the sake of interactivity and responsiveness.
1626 //
1627 // To achieve this, we set a timer and only perform the rendering when
1628 // this is complete.
1629 const int exhaustDelay = 5;
1630 d->updateTimer.start(msec: exhaustDelay, timerType: Qt::PreciseTimer, obj: this);
1631 d->eventPending = true;
1632 }
1633}
1634
1635/*!
1636 Sets the surface \a format for the context and offscreen surface used
1637 by this widget.
1638
1639 Call this function when there is a need to request a context for a
1640 given OpenGL version or profile. The sizes for depth, stencil and
1641 alpha buffers are taken care of automatically and there is no need
1642 to request those explicitly.
1643
1644 \sa QWindow::setFormat(), QWindow::format(), format()
1645*/
1646void QQuickWidget::setFormat(const QSurfaceFormat &format)
1647{
1648 Q_D(QQuickWidget);
1649 QSurfaceFormat currentFormat = d->offscreenWindow->format();
1650 QSurfaceFormat newFormat = format;
1651 newFormat.setDepthBufferSize(qMax(a: newFormat.depthBufferSize(), b: currentFormat.depthBufferSize()));
1652 newFormat.setStencilBufferSize(qMax(a: newFormat.stencilBufferSize(), b: currentFormat.stencilBufferSize()));
1653 newFormat.setAlphaBufferSize(qMax(a: newFormat.alphaBufferSize(), b: currentFormat.alphaBufferSize()));
1654
1655 // Do not include the sample count. Requesting a multisampled context is not necessary
1656 // since we render into an FBO, never to an actual surface. What's more, attempting to
1657 // create a pbuffer with a multisampled config crashes certain implementations. Just
1658 // avoid the entire hassle, the result is the same.
1659 d->requestedSamples = newFormat.samples();
1660 newFormat.setSamples(0);
1661
1662 d->offscreenWindow->setFormat(newFormat);
1663}
1664
1665/*!
1666 Returns the actual surface format.
1667
1668 If the widget has not yet been shown, the requested format is returned.
1669
1670 \sa setFormat()
1671*/
1672QSurfaceFormat QQuickWidget::format() const
1673{
1674 Q_D(const QQuickWidget);
1675 return d->offscreenWindow->format();
1676}
1677
1678/*!
1679 Renders a frame and reads it back into an image.
1680
1681 \note This is a potentially expensive operation.
1682 */
1683QImage QQuickWidget::grabFramebuffer() const
1684{
1685 return const_cast<QQuickWidgetPrivate *>(d_func())->grabFramebuffer();
1686}
1687
1688/*!
1689 Sets the clear \a color. By default this is an opaque color.
1690
1691 To get a semi-transparent QQuickWidget, call this function with
1692 \a color set to Qt::transparent, set the Qt::WA_TranslucentBackground
1693 widget attribute on the top-level window, and request an alpha
1694 channel via setFormat().
1695
1696 \sa QQuickWindow::setColor()
1697 */
1698void QQuickWidget::setClearColor(const QColor &color)
1699{
1700 Q_D(QQuickWidget);
1701 d->offscreenWindow->setColor(color);
1702}
1703
1704/*!
1705 \since 5.5
1706
1707 Returns the offscreen QQuickWindow which is used by this widget to drive
1708 the Qt Quick rendering. This is useful if you want to use QQuickWindow
1709 APIs that are not currently exposed by QQuickWidget, for instance
1710 connecting to the QQuickWindow::beforeRendering() signal in order
1711 to draw native OpenGL content below Qt Quick's own rendering.
1712
1713 \warning Use the return value of this function with caution. In
1714 particular, do not ever attempt to show the QQuickWindow, and be
1715 very careful when using other QWindow-only APIs.
1716*/
1717QQuickWindow *QQuickWidget::quickWindow() const
1718{
1719 Q_D(const QQuickWidget);
1720 return d->offscreenWindow;
1721}
1722
1723/*!
1724 \reimp
1725 */
1726void QQuickWidget::paintEvent(QPaintEvent *event)
1727{
1728 Q_D(QQuickWidget);
1729 if (d->useSoftwareRenderer) {
1730 QPainter painter(this);
1731 d->updateRegion = d->updateRegion.united(r: event->region());
1732 if (d->updateRegion.isNull()) {
1733 //Paint everything
1734 painter.drawImage(r: rect(), image: d->softwareImage);
1735 } else {
1736 QTransform transform;
1737 transform.scale(sx: devicePixelRatioF(), sy: devicePixelRatioF());
1738 //Paint only the updated areas
1739 QRegion targetRegion;
1740 d->updateRegion.swap(other&: targetRegion);
1741 for (auto targetRect : targetRegion) {
1742 auto sourceRect = transform.mapRect(QRectF(targetRect));
1743 painter.drawImage(targetRect, image: d->softwareImage, sourceRect);
1744 }
1745 }
1746 }
1747}
1748
1749void QQuickWidget::propagateFocusObjectChanged(QObject *focusObject)
1750{
1751 Q_D(QQuickWidget);
1752 if (QApplication::focusObject() != this)
1753 return;
1754 if (QWindow *window = d->windowHandle(mode: QWidgetPrivate::WindowHandleMode::TopLevel))
1755 emit window->focusObjectChanged(object: focusObject);
1756}
1757
1758QT_END_NAMESPACE
1759
1760#include "moc_qquickwidget.cpp"
1761

source code of qtdeclarative/src/quickwidgets/qquickwidget.cpp