| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQuick module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquickwidget.h" |
| #include "qquickwidget_p.h" |
| |
| #include "private/qquickwindow_p.h" |
| #include "private/qquickitem_p.h" |
| #include "private/qquickitemchangelistener_p.h" |
| #include "private/qquickrendercontrol_p.h" |
| |
| #include "private/qsgsoftwarerenderer_p.h" |
| |
| #include <private/qqmldebugconnector_p.h> |
| #include <private/qquickprofiler_p.h> |
| #include <private/qqmldebugserviceinterfaces_p.h> |
| |
| #include <QtQml/qqmlengine.h> |
| #include <private/qqmlengine_p.h> |
| #include <QtCore/qbasictimer.h> |
| #include <QtGui/QOffscreenSurface> |
| #include <QtGui/private/qguiapplication_p.h> |
| #include <QtGui/qpa/qplatformintegration.h> |
| |
| #if QT_CONFIG(opengl) |
| #include <QtGui/QOpenGLContext> |
| #include <QtGui/QOpenGLFunctions> |
| #include <QtGui/private/qopenglextensions_p.h> |
| #endif |
| #include <QtGui/QPainter> |
| |
| #include <QtQuick/QSGRendererInterface> |
| |
| #ifdef Q_OS_WIN |
| # include <QtWidgets/QMessageBox> |
| # include <QtCore/QLibraryInfo> |
| # include <QtCore/qt_windows.h> |
| #endif |
| |
| QT_BEGIN_NAMESPACE |
| |
| // override setVisble to prevent accidental offscreen window being created |
| // by base class. |
| class QQuickOffcreenWindowPrivate: public QQuickWindowPrivate { |
| public: |
| void setVisible(bool visible) override { |
| Q_Q(QWindow); |
| // this stays always invisible |
| visibility = visible ? QWindow::Windowed : QWindow::Hidden; |
| q->visibilityChanged(visibility); // workaround for QTBUG-49054 |
| } |
| }; |
| |
| class QQuickWidgetRenderControl : public QQuickRenderControl |
| { |
| public: |
| QQuickWidgetRenderControl(QQuickWidget *quickwidget) : m_quickWidget(quickwidget) {} |
| QWindow *renderWindow(QPoint *offset) override { |
| if (offset) |
| *offset = m_quickWidget->mapTo(m_quickWidget->window(), QPoint()); |
| return m_quickWidget->window()->windowHandle(); |
| } |
| private: |
| QQuickWidget *m_quickWidget; |
| }; |
| |
| void QQuickWidgetPrivate::init(QQmlEngine* e) |
| { |
| Q_Q(QQuickWidget); |
| |
| renderControl = new QQuickWidgetRenderControl(q); |
| offscreenWindow = new QQuickWindow(*new QQuickOffcreenWindowPrivate(),renderControl); |
| offscreenWindow->setTitle(QString::fromLatin1("Offscreen")); |
| offscreenWindow->setObjectName(QString::fromLatin1("QQuickOffScreenWindow")); |
| // Do not call create() on offscreenWindow. |
| |
| // Check if the Software Adaptation is being used |
| auto sgRendererInterface = offscreenWindow->rendererInterface(); |
| if (sgRendererInterface && sgRendererInterface->graphicsApi() == QSGRendererInterface::Software) |
| useSoftwareRenderer = true; |
| |
| if (!useSoftwareRenderer) { |
| #if QT_CONFIG(opengl) |
| if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RasterGLSurface)) |
| setRenderToTexture(); |
| else |
| #endif |
| qWarning("QQuickWidget is not supported on this platform."); |
| } |
| |
| engine = e; |
| |
| if (!engine.isNull() && !engine.data()->incubationController()) |
| engine.data()->setIncubationController(offscreenWindow->incubationController()); |
| |
| #if QT_CONFIG(quick_draganddrop) |
| q->setAcceptDrops(true); |
| #endif |
| |
| QWidget::connect(offscreenWindow, SIGNAL(sceneGraphInitialized()), q, SLOT(createFramebufferObject())); |
| QWidget::connect(offscreenWindow, SIGNAL(sceneGraphInvalidated()), q, SLOT(destroyFramebufferObject())); |
| QObject::connect(renderControl, SIGNAL(renderRequested()), q, SLOT(triggerUpdate())); |
| QObject::connect(renderControl, SIGNAL(sceneChanged()), q, SLOT(triggerUpdate())); |
| } |
| |
| void QQuickWidgetPrivate::ensureEngine() const |
| { |
| Q_Q(const QQuickWidget); |
| if (!engine.isNull()) |
| return; |
| |
| engine = new QQmlEngine(const_cast<QQuickWidget*>(q)); |
| engine.data()->setIncubationController(offscreenWindow->incubationController()); |
| } |
| |
| void QQuickWidgetPrivate::invalidateRenderControl() |
| { |
| #if QT_CONFIG(opengl) |
| if (!useSoftwareRenderer) { |
| if (!context) // this is not an error, could be called before creating the context, or multiple times |
| return; |
| |
| bool success = context->makeCurrent(offscreenSurface); |
| if (!success) { |
| qWarning("QQuickWidget::invalidateRenderControl could not make context current"); |
| return; |
| } |
| } |
| #endif |
| |
| renderControl->invalidate(); |
| |
| // Many things can happen inside the above invalidate() call, including a |
| // change of current context. Restore if needed since some code will rely |
| // on the fact that this function makes and leaves the context current. |
| #if QT_CONFIG(opengl) |
| if (!useSoftwareRenderer && context) { |
| if (QOpenGLContext::currentContext() != context) |
| context->makeCurrent(offscreenSurface); |
| } |
| #endif |
| } |
| |
| void QQuickWidgetPrivate::handleWindowChange() |
| { |
| if (offscreenWindow->isPersistentSceneGraph() && qGuiApp->testAttribute(Qt::AA_ShareOpenGLContexts)) |
| return; |
| |
| // In case of !isPersistentSceneGraph or when we need a new context due to |
| // the need to share resources with the new window's context, we must both |
| // invalidate the scenegraph and destroy the context. With |
| // QQuickRenderControl destroying the context must be preceded by an |
| // invalidate to prevent being left with dangling context references in the |
| // rendercontrol. |
| |
| invalidateRenderControl(); |
| |
| if (!useSoftwareRenderer) |
| destroyContext(); |
| } |
| |
| QQuickWidgetPrivate::QQuickWidgetPrivate() |
| : root(nullptr) |
| , component(nullptr) |
| , offscreenWindow(nullptr) |
| , offscreenSurface(nullptr) |
| , renderControl(nullptr) |
| #if QT_CONFIG(opengl) |
| , fbo(nullptr) |
| , resolvedFbo(nullptr) |
| , context(nullptr) |
| #endif |
| , resizeMode(QQuickWidget::SizeViewToRootObject) |
| , initialSize(0,0) |
| , eventPending(false) |
| , updatePending(false) |
| , fakeHidden(false) |
| , requestedSamples(0) |
| , useSoftwareRenderer(false) |
| , forceFullUpdate(false) |
| { |
| } |
| |
| QQuickWidgetPrivate::~QQuickWidgetPrivate() |
| { |
| invalidateRenderControl(); |
| |
| if (useSoftwareRenderer) { |
| delete renderControl; |
| delete offscreenWindow; |
| } else { |
| #if QT_CONFIG(opengl) |
| // context and offscreenSurface are current at this stage, if the context was created. |
| Q_ASSERT(!context || (QOpenGLContext::currentContext() == context && context->surface() == offscreenSurface)); |
| delete renderControl; // always delete the rendercontrol first |
| delete offscreenWindow; |
| delete resolvedFbo; |
| delete fbo; |
| |
| destroyContext(); |
| #endif |
| } |
| } |
| |
| void QQuickWidgetPrivate::execute() |
| { |
| Q_Q(QQuickWidget); |
| ensureEngine(); |
| |
| if (root) { |
| delete root; |
| root = nullptr; |
| } |
| if (component) { |
| delete component; |
| component = nullptr; |
| } |
| if (!source.isEmpty()) { |
| component = new QQmlComponent(engine.data(), source, q); |
| if (!component->isLoading()) { |
| q->continueExecute(); |
| } else { |
| QObject::connect(component, SIGNAL(statusChanged(QQmlComponent::Status)), |
| q, SLOT(continueExecute())); |
| } |
| } |
| } |
| |
| void QQuickWidgetPrivate::itemGeometryChanged(QQuickItem *resizeItem, QQuickGeometryChange change, |
| const QRectF &oldGeometry) |
| { |
| Q_Q(QQuickWidget); |
| if (resizeItem == root && resizeMode == QQuickWidget::SizeViewToRootObject) { |
| // wait for both width and height to be changed |
| resizetimer.start(0,q); |
| } |
| QQuickItemChangeListener::itemGeometryChanged(resizeItem, change, oldGeometry); |
| } |
| |
| void QQuickWidgetPrivate::render(bool needsSync) |
| { |
| if (!useSoftwareRenderer) { |
| #if QT_CONFIG(opengl) |
| // createFramebufferObject() bails out when the size is empty. In this case |
| // we cannot render either. |
| if (!fbo) |
| return; |
| |
| Q_ASSERT(context); |
| |
| bool current = context->makeCurrent(offscreenSurface); |
| |
| if (!current && !context->isValid()) { |
| renderControl->invalidate(); |
| current = context->create() && context->makeCurrent(offscreenSurface); |
| if (current) |
| renderControl->initialize(context); |
| } |
| |
| if (!current) { |
| qWarning("QQuickWidget: Cannot render due to failing makeCurrent()"); |
| return; |
| } |
| |
| QOpenGLContextPrivate::get(context)->defaultFboRedirect = fbo->handle(); |
| |
| if (needsSync) { |
| renderControl->polishItems(); |
| renderControl->sync(); |
| } |
| |
| renderControl->render(); |
| |
| if (resolvedFbo) { |
| QRect rect(QPoint(0, 0), fbo->size()); |
| QOpenGLFramebufferObject::blitFramebuffer(resolvedFbo, rect, fbo, rect); |
| } |
| |
| static_cast<QOpenGLExtensions *>(context->functions())->flushShared(); |
| |
| QOpenGLContextPrivate::get(context)->defaultFboRedirect = 0; |
| #endif |
| } else { |
| //Software Renderer |
| if (needsSync) { |
| renderControl->polishItems(); |
| renderControl->sync(); |
| } |
| |
| QQuickWindowPrivate *cd = QQuickWindowPrivate::get(offscreenWindow); |
| auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(cd->renderer); |
| if (softwareRenderer && !softwareImage.isNull()) { |
| softwareRenderer->setCurrentPaintDevice(&softwareImage); |
| if (forceFullUpdate) { |
| softwareRenderer->markDirty(); |
| forceFullUpdate = false; |
| } |
| renderControl->render(); |
| |
| updateRegion += softwareRenderer->flushRegion(); |
| } |
| } |
| } |
| |
| void QQuickWidgetPrivate::renderSceneGraph() |
| { |
| Q_Q(QQuickWidget); |
| updatePending = false; |
| |
| if (!q->isVisible() || fakeHidden) |
| return; |
| |
| if (!useSoftwareRenderer) { |
| QOpenGLContext *context = offscreenWindow->openglContext(); |
| if (!context) { |
| qWarning("QQuickWidget: Attempted to render scene with no context"); |
| return; |
| } |
| |
| Q_ASSERT(offscreenSurface); |
| } |
| |
| render(true); |
| |
| #if QT_CONFIG(graphicsview) |
| if (q->window()->graphicsProxyWidget()) |
| QWidgetPrivate::nearestGraphicsProxyWidget(q)->update(); |
| else |
| #endif |
| { |
| if (!useSoftwareRenderer) |
| q->update(); // schedule composition |
| else if (!updateRegion.isEmpty()) |
| q->update(updateRegion); |
| } |
| } |
| |
| QImage QQuickWidgetPrivate::grabFramebuffer() |
| { |
| if (!useSoftwareRenderer) { |
| #if QT_CONFIG(opengl) |
| if (!context) |
| return QImage(); |
| |
| context->makeCurrent(offscreenSurface); |
| #endif |
| } |
| return renderControl->grab(); |
| } |
| |
| // Intentionally not overriding the QQuickWindow's focusObject. |
| // Key events should go to our key event handlers, and then to the |
| // QQuickWindow, not any in-scene item. |
| |
| /*! |
| \module QtQuickWidgets |
| \title Qt Quick Widgets C++ Classes |
| \ingroup modules |
| \brief The C++ API provided by the Qt Quick Widgets module. |
| \qtvariable quickwidgets |
| |
| To link against the module, add this line to your \l qmake |
| \c .pro file: |
| |
| \code |
| QT += quickwidgets |
| \endcode |
| |
| For more information, see the QQuickWidget class documentation. |
| */ |
| |
| /*! |
| \class QQuickWidget |
| \since 5.3 |
| \brief The QQuickWidget class provides a widget for displaying a Qt Quick user interface. |
| |
| \inmodule QtQuickWidgets |
| |
| This is a convenience wrapper for QQuickWindow which will automatically load and display a QML |
| scene when given the URL of the main source file. Alternatively, you can instantiate your own |
| objects using QQmlComponent and place them in a manually set up QQuickWidget. |
| |
| Typical usage: |
| |
| \code |
| QQuickWidget *view = new QQuickWidget; |
| view->setSource(QUrl::fromLocalFile("myqmlfile.qml")); |
| view->show(); |
| \endcode |
| |
| To receive errors related to loading and executing QML with QQuickWidget, |
| you can connect to the statusChanged() signal and monitor for QQuickWidget::Error. |
| The errors are available via QQuickWidget::errors(). |
| |
| QQuickWidget also manages sizing of the view and root object. By default, the \l resizeMode |
| is SizeViewToRootObject, which will load the component and resize it to the |
| size of the view. Alternatively the resizeMode may be set to SizeRootObjectToView which |
| will resize the view to the size of the root object. |
| |
| \note QQuickWidget is an alternative to using QQuickView and QWidget::createWindowContainer(). |
| The restrictions on stacking order do not apply, making QQuickWidget the more flexible |
| alternative, behaving more like an ordinary widget. This comes at the expense of |
| performance. Unlike QQuickWindow and QQuickView, QQuickWidget involves rendering into OpenGL |
| framebuffer objects. This will naturally carry a minor performance hit. |
| |
| \note Using QQuickWidget disables the threaded render loop on all platforms. This means that |
| some of the benefits of threaded rendering, for example \l Animator classes and vsync driven |
| animations, will not be available. |
| |
| \note Avoid calling winId() on a QQuickWidget. This function triggers the creation of |
| a native window, resulting in reduced performance and possibly rendering glitches. The |
| entire purpose of QQuickWidget is to render Quick scenes without a separate native |
| window, hence making it a native widget should always be avoided. |
| |
| \section1 Scene Graph and Context Persistency |
| |
| QQuickWidget honors QQuickWindow::isPersistentSceneGraph(), meaning that |
| applications can decide - by calling |
| QQuickWindow::setPersistentSceneGraph() on the window returned from the |
| quickWindow() function - to let scenegraph nodes and other Qt Quick scene |
| related resources be released whenever the widget becomes hidden. By default |
| persistency is enabled, just like with QQuickWindow. |
| |
| When running with the OpenGL backend of the scene graph, QQuickWindow |
| offers the possibility to disable persistent OpenGL contexts as well. This |
| setting is currently ignored by QQuickWidget and the context is always |
| persistent. The OpenGL context is thus not destroyed when hiding the |
| widget. The context is destroyed only when the widget is destroyed or when |
| the widget gets reparented into another top-level widget's child hierarchy. |
| However, some applications, in particular those that have their own |
| graphics resources due to performing custom OpenGL rendering in the Qt |
| Quick scene, may wish to disable the latter since they may not be prepared |
| to handle the loss of the context when moving a QQuickWidget into another |
| window. Such applications can set the |
| QCoreApplication::AA_ShareOpenGLContexts attribute. For a discussion on the |
| details of resource initialization and cleanup, refer to the QOpenGLWidget |
| documentation. |
| |
| \note QQuickWidget offers less fine-grained control over its internal |
| OpenGL context than QOpenGLWidget, and there are subtle differences, most |
| notably that disabling the persistent scene graph will lead to destroying |
| the context on a window change regardless of the presence of |
| QCoreApplication::AA_ShareOpenGLContexts. |
| |
| \section1 Limitations |
| |
| Putting other widgets underneath and making the QQuickWidget transparent will not lead |
| to the expected results: the widgets underneath will not be visible. This is because |
| in practice the QQuickWidget is drawn before all other regular, non-OpenGL widgets, |
| and so see-through types of solutions are not feasible. Other type of layouts, like |
| having widgets on top of the QQuickWidget, will function as expected. |
| |
| When absolutely necessary, this limitation can be overcome by setting the |
| Qt::WA_AlwaysStackOnTop attribute on the QQuickWidget. Be aware, however that this |
| breaks stacking order. For example it will not be possible to have other widgets on |
| top of the QQuickWidget, so it should only be used in situations where a |
| semi-transparent QQuickWidget with other widgets visible underneath is required. |
| |
| This limitation only applies when there are other widgets underneath the QQuickWidget |
| inside the same window. Making the window semi-transparent, with other applications |
| and the desktop visible in the background, is done in the traditional way: Set |
| Qt::WA_TranslucentBackground on the top-level window, request an alpha channel, and |
| change the Qt Quick Scenegraph's clear color to Qt::transparent via setClearColor(). |
| |
| \section1 Support when not using OpenGL |
| |
| In addition to OpenGL, the \c software backend of Qt Quick also supports |
| QQuickWidget. Other backends, for example the Direct 3D 12 one, are not |
| compatible however and attempting to construct a QQuickWidget will lead to |
| problems. |
| |
| \section1 Tab Key Handling |
| |
| On press of the \c[TAB] key, the item inside the QQuickWidget gets focus. If |
| this item can handle \c[TAB] key press, focus will change accordingly within |
| the item, otherwise the next widget in the focus chain gets focus. |
| |
| \sa {Exposing Attributes of C++ Types to QML}, {Qt Quick Widgets Example}, QQuickView |
| */ |
| |
| |
| /*! |
| \fn void QQuickWidget::statusChanged(QQuickWidget::Status status) |
| This signal is emitted when the component's current \a status changes. |
| */ |
| |
| /*! |
| Constructs a QQuickWidget with the given \a parent. |
| The default value of \a parent is 0. |
| |
| */ |
| QQuickWidget::QQuickWidget(QWidget *parent) |
| : QWidget(*(new QQuickWidgetPrivate), parent, {}) |
| { |
| setMouseTracking(true); |
| setFocusPolicy(Qt::StrongFocus); |
| d_func()->init(); |
| } |
| |
| /*! |
| Constructs a QQuickWidget with the given QML \a source and \a parent. |
| The default value of \a parent is 0. |
| |
| */ |
| QQuickWidget::QQuickWidget(const QUrl &source, QWidget *parent) |
| : QQuickWidget(parent) |
| { |
| setSource(source); |
| } |
| |
| /*! |
| Constructs a QQuickWidget with the given QML \a engine and \a parent. |
| |
| Note: In this case, the QQuickWidget does not own the given \a engine object; |
| it is the caller's responsibility to destroy the engine. If the \a engine is deleted |
| before the view, status() will return QQuickWidget::Error. |
| |
| \sa Status, status(), errors() |
| */ |
| QQuickWidget::QQuickWidget(QQmlEngine* engine, QWidget *parent) |
| : QWidget(*(new QQuickWidgetPrivate), parent, {}) |
| { |
| setMouseTracking(true); |
| setFocusPolicy(Qt::StrongFocus); |
| d_func()->init(engine); |
| } |
| |
| /*! |
| Destroys the QQuickWidget. |
| */ |
| QQuickWidget::~QQuickWidget() |
| { |
| // Ensure that the component is destroyed before the engine; the engine may |
| // be a child of the QQuickWidgetPrivate, and will be destroyed by its dtor |
| Q_D(QQuickWidget); |
| delete d->root; |
| d->root = nullptr; |
| } |
| |
| /*! |
| \property QQuickWidget::source |
| \brief The URL of the source of the QML component. |
| |
| Ensure that the URL provided is full and correct, in particular, use |
| \l QUrl::fromLocalFile() when loading a file from the local filesystem. |
| |
| \note Setting a source URL will result in the QML component being |
| instantiated, even if the URL is unchanged from the current value. |
| */ |
| |
| /*! |
| Sets the source to the \a url, loads the QML component and instantiates it. |
| |
| Ensure that the URL provided is full and correct, in particular, use |
| \l QUrl::fromLocalFile() when loading a file from the local filesystem. |
| |
| Calling this method multiple times with the same URL will result |
| in the QML component being reinstantiated. |
| */ |
| void QQuickWidget::setSource(const QUrl& url) |
| { |
| Q_D(QQuickWidget); |
| d->source = url; |
| d->execute(); |
| } |
| |
| /*! |
| \internal |
| |
| Sets the source \a url, \a component and content \a item (root of the QML object hierarchy) directly. |
| */ |
| void QQuickWidget::setContent(const QUrl& url, QQmlComponent *component, QObject* item) |
| { |
| Q_D(QQuickWidget); |
| d->source = url; |
| d->component = component; |
| |
| if (d->component && d->component->isError()) { |
| const QList<QQmlError> errorList = d->component->errors(); |
| for (const QQmlError &error : errorList) { |
| QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning() |
| << error; |
| } |
| emit statusChanged(status()); |
| return; |
| } |
| |
| d->setRootObject(item); |
| emit statusChanged(status()); |
| } |
| |
| /*! |
| Returns the source URL, if set. |
| |
| \sa setSource() |
| */ |
| QUrl QQuickWidget::source() const |
| { |
| Q_D(const QQuickWidget); |
| return d->source; |
| } |
| |
| /*! |
| Returns a pointer to the QQmlEngine used for instantiating |
| QML Components. |
| */ |
| QQmlEngine* QQuickWidget::engine() const |
| { |
| Q_D(const QQuickWidget); |
| d->ensureEngine(); |
| return const_cast<QQmlEngine *>(d->engine.data()); |
| } |
| |
| /*! |
| This function returns the root of the context hierarchy. Each QML |
| component is instantiated in a QQmlContext. QQmlContext's are |
| essential for passing data to QML components. In QML, contexts are |
| arranged hierarchically and this hierarchy is managed by the |
| QQmlEngine. |
| */ |
| QQmlContext* QQuickWidget::rootContext() const |
| { |
| Q_D(const QQuickWidget); |
| d->ensureEngine(); |
| return d->engine.data()->rootContext(); |
| } |
| |
| /*! |
| \enum QQuickWidget::Status |
| Specifies the loading status of the QQuickWidget. |
| |
| \value Null This QQuickWidget has no source set. |
| \value Ready This QQuickWidget has loaded and created the QML component. |
| \value Loading This QQuickWidget is loading network data. |
| \value Error One or more errors occurred. Call errors() to retrieve a list |
| of errors. |
| */ |
| |
| /*! \enum QQuickWidget::ResizeMode |
| |
| This enum specifies how to resize the view. |
| |
| \value SizeViewToRootObject The view resizes with the root item in the QML. |
| \value SizeRootObjectToView The view will automatically resize the root item to the size of the view. |
| */ |
| |
| /*! |
| \fn void QQuickWidget::sceneGraphError(QQuickWindow::SceneGraphError error, const QString &message) |
| |
| This signal is emitted when an \a error occurred during scene graph initialization. |
| |
| Applications should connect to this signal if they wish to handle errors, |
| like OpenGL context creation failures, in a custom way. When no slot is |
| connected to the signal, the behavior will be different: Quick will print |
| the \a message, or show a message box, and terminate the application. |
| |
| This signal will be emitted from the gui thread. |
| |
| \sa QQuickWindow::sceneGraphError() |
| */ |
| |
| /*! |
| \property QQuickWidget::status |
| The component's current \l{QQuickWidget::Status} {status}. |
| */ |
| |
| QQuickWidget::Status QQuickWidget::status() const |
| { |
| Q_D(const QQuickWidget); |
| if (!d->engine && !d->source.isEmpty()) |
| return QQuickWidget::Error; |
| |
| if (!d->component) |
| return QQuickWidget::Null; |
| |
| if (d->component->status() == QQmlComponent::Ready && !d->root) |
| return QQuickWidget::Error; |
| |
| return QQuickWidget::Status(d->component->status()); |
| } |
| |
| /*! |
| Return the list of errors that occurred during the last compile or create |
| operation. When the status is not \l Error, an empty list is returned. |
| |
| \sa status |
| */ |
| QList<QQmlError> QQuickWidget::errors() const |
| { |
| Q_D(const QQuickWidget); |
| QList<QQmlError> errs; |
| |
| if (d->component) |
| errs = d->component->errors(); |
| |
| if (!d->engine && !d->source.isEmpty()) { |
| QQmlError error; |
| error.setDescription(QLatin1String("QQuickWidget: invalid qml engine.")); |
| errs << error; |
| } |
| if (d->component && d->component->status() == QQmlComponent::Ready && !d->root) { |
| QQmlError error; |
| error.setDescription(QLatin1String("QQuickWidget: invalid root object.")); |
| errs << error; |
| } |
| |
| return errs; |
| } |
| |
| /*! |
| \property QQuickWidget::resizeMode |
| \brief Determines whether the view should resize the window contents. |
| |
| If this property is set to SizeViewToRootObject (the default), the view |
| resizes to the size of the root item in the QML. |
| |
| If this property is set to SizeRootObjectToView, the view will |
| automatically resize the root item to the size of the view. |
| |
| Regardless of this property, the sizeHint of the view |
| is the initial size of the root item. Note though that |
| since QML may load dynamically, that size may change. |
| |
| \sa initialSize() |
| */ |
| |
| void QQuickWidget::setResizeMode(ResizeMode mode) |
| { |
| Q_D(QQuickWidget); |
| if (d->resizeMode == mode) |
| return; |
| |
| if (d->root) { |
| if (d->resizeMode == SizeViewToRootObject) { |
| QQuickItemPrivate *p = QQuickItemPrivate::get(d->root); |
| p->removeItemChangeListener(d, QQuickItemPrivate::Geometry); |
| } |
| } |
| |
| d->resizeMode = mode; |
| if (d->root) { |
| d->initResize(); |
| } |
| } |
| |
| void QQuickWidgetPrivate::initResize() |
| { |
| if (root) { |
| if (resizeMode == QQuickWidget::SizeViewToRootObject) { |
| QQuickItemPrivate *p = QQuickItemPrivate::get(root); |
| p->addItemChangeListener(this, QQuickItemPrivate::Geometry); |
| } |
| } |
| updateSize(); |
| } |
| |
| void QQuickWidgetPrivate::updateSize() |
| { |
| Q_Q(QQuickWidget); |
| if (!root) |
| return; |
| |
| if (resizeMode == QQuickWidget::SizeViewToRootObject) { |
| QSize newSize = QSize(root->width(), root->height()); |
| if (newSize.isValid() && newSize != q->size()) { |
| q->resize(newSize); |
| q->updateGeometry(); |
| } |
| } else if (resizeMode == QQuickWidget::SizeRootObjectToView) { |
| const bool needToUpdateWidth = !qFuzzyCompare(q->width(), root->width()); |
| const bool needToUpdateHeight = !qFuzzyCompare(q->height(), root->height()); |
| |
| if (needToUpdateWidth && needToUpdateHeight) { |
| // Make sure that we have realistic sizing behavior by following |
| // what on-screen windows would do and resize everything, not just |
| // the root item. We do this because other types may be relying on |
| // us to behave correctly. |
| const QSizeF newSize(q->width(), q->height()); |
| offscreenWindow->resize(newSize.toSize()); |
| offscreenWindow->contentItem()->setSize(newSize); |
| root->setSize(newSize); |
| } else if (needToUpdateWidth) { |
| const int newWidth = q->width(); |
| offscreenWindow->setWidth(newWidth); |
| offscreenWindow->contentItem()->setWidth(newWidth); |
| root->setWidth(newWidth); |
| } else if (needToUpdateHeight) { |
| const int newHeight = q->height(); |
| offscreenWindow->setHeight(newHeight); |
| offscreenWindow->contentItem()->setHeight(newHeight); |
| root->setHeight(newHeight); |
| } |
| } |
| } |
| |
| /*! |
| \internal |
| |
| Update the position of the offscreen window, so it matches the position of the QQuickWidget. |
| */ |
| void QQuickWidgetPrivate::updatePosition() |
| { |
| Q_Q(QQuickWidget); |
| if (offscreenWindow == nullptr) |
| return; |
| |
| const QPoint &pos = q->mapToGlobal(QPoint(0, 0)); |
| if (offscreenWindow->position() != pos) |
| offscreenWindow->setPosition(pos); |
| } |
| |
| QSize QQuickWidgetPrivate::rootObjectSize() const |
| { |
| QSize rootObjectSize(0,0); |
| int widthCandidate = -1; |
| int heightCandidate = -1; |
| if (root) { |
| widthCandidate = root->width(); |
| heightCandidate = root->height(); |
| } |
| if (widthCandidate > 0) { |
| rootObjectSize.setWidth(widthCandidate); |
| } |
| if (heightCandidate > 0) { |
| rootObjectSize.setHeight(heightCandidate); |
| } |
| return rootObjectSize; |
| } |
| |
| void QQuickWidgetPrivate::handleContextCreationFailure(const QSurfaceFormat &format) |
| { |
| Q_Q(QQuickWidget); |
| |
| QString translatedMessage; |
| QString untranslatedMessage; |
| QQuickWindowPrivate::contextCreationFailureMessage(format, &translatedMessage, &untranslatedMessage); |
| |
| static const QMetaMethod errorSignal = QMetaMethod::fromSignal(&QQuickWidget::sceneGraphError); |
| const bool signalConnected = q->isSignalConnected(errorSignal); |
| if (signalConnected) |
| emit q->sceneGraphError(QQuickWindow::ContextNotAvailable, translatedMessage); |
| |
| #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) |
| if (!signalConnected && !QLibraryInfo::isDebugBuild() && !GetConsoleWindow()) |
| QMessageBox::critical(q, QCoreApplication::applicationName(), translatedMessage); |
| #endif // Q_OS_WIN && !Q_OS_WINRT |
| if (!signalConnected) |
| qFatal("%s", qPrintable(untranslatedMessage)); |
| } |
| |
| // Never called by Software Rendering backend |
| void QQuickWidgetPrivate::createContext() |
| { |
| #if QT_CONFIG(opengl) |
| Q_Q(QQuickWidget); |
| |
| // On hide-show we may invalidate() (when !isPersistentSceneGraph) but our |
| // context is kept. We may need to initialize() again, though. |
| const bool reinit = context && !offscreenWindow->openglContext(); |
| |
| if (!reinit) { |
| if (context) |
| return; |
| |
| context = new QOpenGLContext; |
| context->setFormat(offscreenWindow->requestedFormat()); |
| const QWindow *win = q->window()->windowHandle(); |
| if (win && win->screen()) |
| context->setScreen(win->screen()); |
| QOpenGLContext *shareContext = qt_gl_global_share_context(); |
| if (!shareContext) |
| shareContext = QWidgetPrivate::get(q->window())->shareContext(); |
| if (shareContext) { |
| context->setShareContext(shareContext); |
| context->setScreen(shareContext->screen()); |
| } |
| if (!context->create()) { |
| delete context; |
| context = nullptr; |
| handleContextCreationFailure(offscreenWindow->requestedFormat()); |
| return; |
| } |
| |
| offscreenSurface = new QOffscreenSurface; |
| // Pass the context's format(), which, now that the underlying platform context is created, |
| // contains a QSurfaceFormat representing the _actual_ format of the underlying |
| // configuration. This is essential to get a surface that is compatible with the context. |
| offscreenSurface->setFormat(context->format()); |
| offscreenSurface->setScreen(context->screen()); |
| offscreenSurface->create(); |
| } |
| |
| if (context->makeCurrent(offscreenSurface)) { |
| if (!offscreenWindow->openglContext()) |
| renderControl->initialize(context); |
| } else |
| #endif |
| qWarning("QQuickWidget: Failed to make context current"); |
| } |
| |
| // Never called by Software Rendering backend |
| void QQuickWidgetPrivate::destroyContext() |
| { |
| delete offscreenSurface; |
| offscreenSurface = nullptr; |
| #if QT_CONFIG(opengl) |
| delete context; |
| context = nullptr; |
| #endif |
| } |
| |
| void QQuickWidget::createFramebufferObject() |
| { |
| Q_D(QQuickWidget); |
| |
| // Could come from Show -> createContext -> sceneGraphInitialized in which case the size may |
| // still be invalid on some platforms. Bail out. A resize will come later on. |
| if (size().isEmpty()) |
| return; |
| |
| // Even though this is just an offscreen window we should set the position on it, as it might be |
| // useful for an item to know the actual position of the scene. |
| // Note: The position will be update when we get a move event (see: updatePosition()). |
| const QPoint &globalPos = mapToGlobal(QPoint(0, 0)); |
| d->offscreenWindow->setGeometry(globalPos.x(), globalPos.y(), width(), height()); |
| |
| if (d->useSoftwareRenderer) { |
| const QSize imageSize = size() * devicePixelRatioF(); |
| d->softwareImage = QImage(imageSize, QImage::Format_ARGB32_Premultiplied); |
| d->softwareImage.setDevicePixelRatio(devicePixelRatioF()); |
| d->forceFullUpdate = true; |
| return; |
| } |
| |
| #if QT_CONFIG(opengl) |
| QOpenGLContext *context = d->offscreenWindow->openglContext(); |
| |
| if (!context) { |
| qWarning("QQuickWidget: Attempted to create FBO with no context"); |
| return; |
| } |
| |
| QOpenGLContext *shareWindowContext = QWidgetPrivate::get(window())->shareContext(); |
| if (shareWindowContext && context->shareContext() != shareWindowContext && !qGuiApp->testAttribute(Qt::AA_ShareOpenGLContexts)) { |
| context->setShareContext(shareWindowContext); |
| context->setScreen(shareWindowContext->screen()); |
| if (!context->create()) |
| qWarning("QQuickWidget: Failed to recreate context"); |
| // The screen may be different so we must recreate the offscreen surface too. |
| // Unlike QOpenGLContext, QOffscreenSurface's create() does not recreate so have to destroy() first. |
| d->offscreenSurface->destroy(); |
| d->offscreenSurface->setScreen(context->screen()); |
| d->offscreenSurface->create(); |
| } |
| |
| bool current = context->makeCurrent(d->offscreenSurface); |
| if (!current) { |
| qWarning("QQuickWidget: Failed to make context current when creating FBO"); |
| return; |
| } |
| |
| int samples = d->requestedSamples; |
| if (!QOpenGLExtensions(context).hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)) |
| samples = 0; |
| |
| QOpenGLFramebufferObjectFormat format; |
| format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); |
| format.setSamples(samples); |
| |
| // The default framebuffer for normal windows have sRGB support on OS X which leads to the Qt Quick text item |
| // utilizing sRGB blending. To get identical results with QQuickWidget we have to have our framebuffer backed |
| // by an sRGB texture. |
| #ifdef Q_OS_OSX |
| if (context->hasExtension("GL_ARB_framebuffer_sRGB") |
| && context->hasExtension("GL_EXT_texture_sRGB") |
| && context->hasExtension("GL_EXT_texture_sRGB_decode")) |
| format.setInternalTextureFormat(GL_SRGB8_ALPHA8_EXT); |
| #endif |
| |
| const QSize fboSize = size() * devicePixelRatioF(); |
| |
| // Could be a simple hide - show, in which case the previous fbo is just fine. |
| if (!d->fbo || d->fbo->size() != fboSize) { |
| delete d->fbo; |
| d->fbo = new QOpenGLFramebufferObject(fboSize, format); |
| } |
| |
| // When compositing in the backingstore, sampling the sRGB texture would perform an |
| // sRGB-linear conversion which is not what we want when the final framebuffer (the window's) |
| // is sRGB too. Disable the conversion. |
| #ifdef Q_OS_OSX |
| if (format.internalTextureFormat() == GL_SRGB8_ALPHA8_EXT) { |
| QOpenGLFunctions *funcs = context->functions(); |
| funcs->glBindTexture(GL_TEXTURE_2D, d->fbo->texture()); |
| funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SRGB_DECODE_EXT, GL_SKIP_DECODE_EXT); |
| } |
| #endif |
| |
| d->offscreenWindow->setRenderTarget(d->fbo); |
| |
| if (samples > 0) |
| d->resolvedFbo = new QOpenGLFramebufferObject(fboSize); |
| |
| // Sanity check: The window must not have an underlying platform window. |
| // Having one would mean create() was called and platforms that only support |
| // a single native window were in trouble. |
| Q_ASSERT(!d->offscreenWindow->handle()); |
| #endif |
| } |
| |
| void QQuickWidget::destroyFramebufferObject() |
| { |
| Q_D(QQuickWidget); |
| |
| if (d->useSoftwareRenderer) { |
| d->softwareImage = QImage(); |
| return; |
| } |
| |
| #if QT_CONFIG(opengl) |
| delete d->fbo; |
| d->fbo = nullptr; |
| delete d->resolvedFbo; |
| d->resolvedFbo = nullptr; |
| #endif |
| } |
| |
| QQuickWidget::ResizeMode QQuickWidget::resizeMode() const |
| { |
| Q_D(const QQuickWidget); |
| return d->resizeMode; |
| } |
| |
| /*! |
| \internal |
| */ |
| void QQuickWidget::continueExecute() |
| { |
| Q_D(QQuickWidget); |
| disconnect(d->component, SIGNAL(statusChanged(QQmlComponent::Status)), this, SLOT(continueExecute())); |
| |
| if (d->component->isError()) { |
| const QList<QQmlError> errorList = d->component->errors(); |
| for (const QQmlError &error : errorList) { |
| QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning() |
| << error; |
| } |
| emit statusChanged(status()); |
| return; |
| } |
| |
| QObject *obj = d->component->create(); |
| |
| if (d->component->isError()) { |
| const QList<QQmlError> errorList = d->component->errors(); |
| for (const QQmlError &error : errorList) { |
| QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning() |
| << error; |
| } |
| emit statusChanged(status()); |
| return; |
| } |
| |
| d->setRootObject(obj); |
| emit statusChanged(status()); |
| } |
| |
| |
| /*! |
| \internal |
| */ |
| void QQuickWidgetPrivate::setRootObject(QObject *obj) |
| { |
| Q_Q(QQuickWidget); |
| if (root == obj) |
| return; |
| if (QQuickItem *sgItem = qobject_cast<QQuickItem *>(obj)) { |
| root = sgItem; |
| sgItem->setParentItem(offscreenWindow->contentItem()); |
| } else if (qobject_cast<QWindow *>(obj)) { |
| qWarning() << "QQuickWidget does not support using windows as a root item." << Qt::endl |
| << Qt::endl |
| << "If you wish to create your root window from QML, consider using QQmlApplicationEngine instead." << Qt::endl; |
| } else { |
| qWarning() << "QQuickWidget only supports loading of root objects that derive from QQuickItem." << Qt::endl |
| << Qt::endl |
| << "Ensure your QML code is written for QtQuick 2, and uses a root that is or" << Qt::endl |
| << "inherits from QtQuick's Item (not a Timer, QtObject, etc)." << Qt::endl; |
| delete obj; |
| root = nullptr; |
| } |
| if (root) { |
| initialSize = rootObjectSize(); |
| bool resized = q->testAttribute(Qt::WA_Resized); |
| if ((resizeMode == QQuickWidget::SizeViewToRootObject || !resized) && |
| initialSize != q->size()) { |
| q->resize(initialSize); |
| } |
| initResize(); |
| } |
| } |
| |
| #if QT_CONFIG(opengl) |
| GLuint QQuickWidgetPrivate::textureId() const |
| { |
| Q_Q(const QQuickWidget); |
| if (!q->isWindow() && q->internalWinId()) { |
| qWarning() << "QQuickWidget cannot be used as a native child widget." |
| << "Consider setting Qt::AA_DontCreateNativeWidgetSiblings"; |
| return 0; |
| } |
| return resolvedFbo ? resolvedFbo->texture() |
| : (fbo ? fbo->texture() : 0); |
| } |
| |
| QPlatformTextureList::Flags QQuickWidgetPrivate::textureListFlags() |
| { |
| QPlatformTextureList::Flags flags = QWidgetPrivate::textureListFlags(); |
| flags |= QPlatformTextureList::NeedsPremultipliedAlphaBlending; |
| return flags; |
| } |
| #endif |
| |
| /*! |
| \internal |
| Handle item resize and scene updates. |
| */ |
| void QQuickWidget::timerEvent(QTimerEvent* e) |
| { |
| Q_D(QQuickWidget); |
| if (!e || e->timerId() == d->resizetimer.timerId()) { |
| d->updateSize(); |
| d->resizetimer.stop(); |
| } else if (e->timerId() == d->updateTimer.timerId()) { |
| d->eventPending = false; |
| d->updateTimer.stop(); |
| if (d->updatePending) |
| d->renderSceneGraph(); |
| } |
| } |
| |
| /*! |
| \internal |
| Preferred size follows the root object geometry. |
| */ |
| QSize QQuickWidget::sizeHint() const |
| { |
| Q_D(const QQuickWidget); |
| QSize rootObjectSize = d->rootObjectSize(); |
| if (rootObjectSize.isEmpty()) { |
| return size(); |
| } else { |
| return rootObjectSize; |
| } |
| } |
| |
| /*! |
| Returns the initial size of the root object. |
| |
| If \l resizeMode is SizeRootObjectToView, the root object will be |
| resized to the size of the view. This function returns the size of the |
| root object before it was resized. |
| */ |
| QSize QQuickWidget::initialSize() const |
| { |
| Q_D(const QQuickWidget); |
| return d->initialSize; |
| } |
| |
| /*! |
| Returns the view's root \l {QQuickItem} {item}. Can be null |
| when setSource() has not been called, if it was called with |
| broken QtQuick code or while the QtQuick contents are being created. |
| */ |
| QQuickItem *QQuickWidget::rootObject() const |
| { |
| Q_D(const QQuickWidget); |
| return d->root; |
| } |
| |
| /*! |
| \internal |
| This function handles the \l {QResizeEvent} {resize event} |
| \a e. |
| */ |
| void QQuickWidget::resizeEvent(QResizeEvent *e) |
| { |
| Q_D(QQuickWidget); |
| if (d->resizeMode == SizeRootObjectToView) |
| d->updateSize(); |
| |
| if (e->size().isEmpty()) { |
| //stop rendering |
| d->fakeHidden = true; |
| return; |
| } |
| |
| bool needsSync = false; |
| if (d->fakeHidden) { |
| //restart rendering |
| d->fakeHidden = false; |
| needsSync = true; |
| } |
| |
| // Software Renderer |
| if (d->useSoftwareRenderer) { |
| needsSync = true; |
| if (d->softwareImage.size() != size() * devicePixelRatioF()) { |
| createFramebufferObject(); |
| } |
| } else { |
| #if QT_CONFIG(opengl) |
| if (d->context) { |
| // Bail out when receiving a resize after scenegraph invalidation. This can happen |
| // during hide - resize - show sequences and also during application exit. |
| if (!d->fbo && !d->offscreenWindow->openglContext()) |
| return; |
| if (!d->fbo || d->fbo->size() != size() * devicePixelRatioF()) { |
| needsSync = true; |
| createFramebufferObject(); |
| } |
| } else { |
| // This will result in a scenegraphInitialized() signal which |
| // is connected to createFramebufferObject(). |
| needsSync = true; |
| d->createContext(); |
| } |
| |
| QOpenGLContext *context = d->offscreenWindow->openglContext(); |
| if (!context) { |
| qWarning("QQuickWidget::resizeEvent() no OpenGL context"); |
| return; |
| } |
| #endif |
| } |
| |
| d->render(needsSync); |
| } |
| |
| /*! \reimp */ |
| bool QQuickWidget::focusNextPrevChild(bool next) |
| { |
| Q_D(QQuickWidget); |
| QKeyEvent event(QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier); |
| Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyPress, event.key(), |
| Qt::NoModifier); |
| QCoreApplication::sendEvent(d->offscreenWindow, &event); |
| |
| QKeyEvent releaseEvent(QEvent::KeyRelease, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier); |
| Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyRelease, releaseEvent.key(), |
| Qt::NoModifier); |
| QCoreApplication::sendEvent(d->offscreenWindow, &releaseEvent); |
| return event.isAccepted(); |
| } |
| |
| /*! \reimp */ |
| void QQuickWidget::keyPressEvent(QKeyEvent *e) |
| { |
| Q_D(QQuickWidget); |
| Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyPress, e->key(), |
| e->modifiers()); |
| |
| QCoreApplication::sendEvent(d->offscreenWindow, e); |
| } |
| |
| /*! \reimp */ |
| void QQuickWidget::keyReleaseEvent(QKeyEvent *e) |
| { |
| Q_D(QQuickWidget); |
| Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyRelease, e->key(), |
| e->modifiers()); |
| |
| QCoreApplication::sendEvent(d->offscreenWindow, e); |
| } |
| |
| /*! \reimp */ |
| void QQuickWidget::mouseMoveEvent(QMouseEvent *e) |
| { |
| Q_D(QQuickWidget); |
| Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseMove, e->localPos().x(), |
| e->localPos().y()); |
| |
| // Put localPos into the event's localPos and windowPos, and screenPos into the |
| // event's screenPos. This way the windowPos in e is ignored and is replaced by |
| // localPos. This is necessary because QQuickWindow thinks of itself as a |
| // top-level window always. |
| QMouseEvent mappedEvent(e->type(), e->localPos(), e->localPos(), e->screenPos(), |
| e->button(), e->buttons(), e->modifiers(), e->source()); |
| QCoreApplication::sendEvent(d->offscreenWindow, &mappedEvent); |
| e->setAccepted(mappedEvent.isAccepted()); |
| } |
| |
| /*! \reimp */ |
| void QQuickWidget::mouseDoubleClickEvent(QMouseEvent *e) |
| { |
| Q_D(QQuickWidget); |
| Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseDoubleClick, |
| e->button(), e->buttons()); |
| |
| // As the second mouse press is suppressed in widget windows we emulate it here for QML. |
| // See QTBUG-25831 |
| QMouseEvent pressEvent(QEvent::MouseButtonPress, e->localPos(), e->localPos(), e->screenPos(), |
| e->button(), e->buttons(), e->modifiers(), e->source()); |
| QCoreApplication::sendEvent(d->offscreenWindow, &pressEvent); |
| e->setAccepted(pressEvent.isAccepted()); |
| QMouseEvent mappedEvent(e->type(), e->localPos(), e->localPos(), e->screenPos(), |
| e->button(), e->buttons(), e->modifiers(), e->source()); |
| QCoreApplication::sendEvent(d->offscreenWindow, &mappedEvent); |
| } |
| |
| /*! \reimp */ |
| void QQuickWidget::showEvent(QShowEvent *) |
| { |
| Q_D(QQuickWidget); |
| bool shouldTriggerUpdate = true; |
| |
| if (!d->useSoftwareRenderer) { |
| d->createContext(); |
| |
| if (d->offscreenWindow->openglContext()) { |
| shouldTriggerUpdate = false; |
| d->render(true); |
| // render() may have led to a QQuickWindow::update() call (for |
| // example, having a scene with a QQuickFramebufferObject::Renderer |
| // calling update() in its render()) which in turn results in |
| // renderRequested in the rendercontrol, ending up in |
| // triggerUpdate. In this case just calling update() is not |
| // acceptable, we need the full renderSceneGraph issued from |
| // timerEvent(). |
| if (!d->eventPending && d->updatePending) { |
| d->updatePending = false; |
| update(); |
| } |
| } |
| } |
| |
| if (shouldTriggerUpdate) |
| triggerUpdate(); |
| |
| // note offscreenWindow is "QQuickOffScreenWindow" instance |
| d->offscreenWindow->setVisible(true); |
| if (QQmlInspectorService *service = QQmlDebugConnector::service<QQmlInspectorService>()) |
| service->setParentWindow(d->offscreenWindow, window()->windowHandle()); |
| } |
| |
| /*! \reimp */ |
| void QQuickWidget::hideEvent(QHideEvent *) |
| { |
| Q_D(QQuickWidget); |
| if (!d->offscreenWindow->isPersistentSceneGraph()) |
| d->invalidateRenderControl(); |
| // note offscreenWindow is "QQuickOffScreenWindow" instance |
| d->offscreenWindow->setVisible(false); |
| if (QQmlInspectorService *service = QQmlDebugConnector::service<QQmlInspectorService>()) |
| service->setParentWindow(d->offscreenWindow, d->offscreenWindow); |
| } |
| |
| /*! \reimp */ |
| void QQuickWidget::mousePressEvent(QMouseEvent *e) |
| { |
| Q_D(QQuickWidget); |
| Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMousePress, e->button(), |
| e->buttons()); |
| |
| QMouseEvent mappedEvent(e->type(), e->localPos(), e->localPos(), e->screenPos(), |
| e->button(), e->buttons(), e->modifiers(), e->source()); |
| QCoreApplication::sendEvent(d->offscreenWindow, &mappedEvent); |
| e->setAccepted(mappedEvent.isAccepted()); |
| } |
| |
| /*! \reimp */ |
| void QQuickWidget::mouseReleaseEvent(QMouseEvent *e) |
| { |
| Q_D(QQuickWidget); |
| Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseRelease, e->button(), |
| e->buttons()); |
| |
| QMouseEvent mappedEvent(e->type(), e->localPos(), e->localPos(), e->screenPos(), |
| e->button(), e->buttons(), e->modifiers(), e->source()); |
| QCoreApplication::sendEvent(d->offscreenWindow, &mappedEvent); |
| e->setAccepted(mappedEvent.isAccepted()); |
| } |
| |
| #if QT_CONFIG(wheelevent) |
| /*! \reimp */ |
| void QQuickWidget::wheelEvent(QWheelEvent *e) |
| { |
| Q_D(QQuickWidget); |
| Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseWheel, |
| e->angleDelta().x(), e->angleDelta().y()); |
| |
| // Wheel events only have local and global positions, no need to map. |
| QCoreApplication::sendEvent(d->offscreenWindow, e); |
| } |
| #endif |
| |
| /*! |
| \reimp |
| */ |
| void QQuickWidget::focusInEvent(QFocusEvent * event) |
| { |
| Q_D(QQuickWidget); |
| d->offscreenWindow->focusInEvent(event); |
| } |
| |
| /*! |
| \reimp |
| */ |
| void QQuickWidget::focusOutEvent(QFocusEvent * event) |
| { |
| Q_D(QQuickWidget); |
| d->offscreenWindow->focusOutEvent(event); |
| } |
| |
| static Qt::WindowState resolveWindowState(Qt::WindowStates states) |
| { |
| // No more than one of these 3 can be set |
| if (states & Qt::WindowMinimized) |
| return Qt::WindowMinimized; |
| if (states & Qt::WindowMaximized) |
| return Qt::WindowMaximized; |
| if (states & Qt::WindowFullScreen) |
| return Qt::WindowFullScreen; |
| |
| // No state means "windowed" - we ignore Qt::WindowActive |
| return Qt::WindowNoState; |
| } |
| |
| static void remapInputMethodQueryEvent(QObject *object, QInputMethodQueryEvent *e) |
| { |
| auto item = qobject_cast<QQuickItem *>(object); |
| if (!item) |
| return; |
| |
| // Remap all QRectF values. |
| for (auto query : {Qt::ImCursorRectangle, Qt::ImAnchorRectangle, Qt::ImInputItemClipRectangle}) { |
| if (e->queries() & query) { |
| auto value = e->value(query); |
| if (value.canConvert<QRectF>()) |
| e->setValue(query, item->mapRectToScene(value.toRectF())); |
| } |
| } |
| // Remap all QPointF values. |
| if (e->queries() & Qt::ImCursorPosition) { |
| auto value = e->value(Qt::ImCursorPosition); |
| if (value.canConvert<QPointF>()) |
| e->setValue(Qt::ImCursorPosition, item->mapToScene(value.toPointF())); |
| } |
| } |
| |
| /*! \reimp */ |
| bool QQuickWidget::event(QEvent *e) |
| { |
| Q_D(QQuickWidget); |
| |
| switch (e->type()) { |
| |
| case QEvent::Leave: |
| case QEvent::TouchBegin: |
| case QEvent::TouchEnd: |
| case QEvent::TouchUpdate: |
| case QEvent::TouchCancel: |
| // Touch events only have local and global positions, no need to map. |
| return QCoreApplication::sendEvent(d->offscreenWindow, e); |
| |
| case QEvent::InputMethod: |
| return QCoreApplication::sendEvent(d->offscreenWindow->focusObject(), e); |
| case QEvent::InputMethodQuery: |
| { |
| bool eventResult = QCoreApplication::sendEvent(d->offscreenWindow->focusObject(), e); |
| // The result in focusObject are based on offscreenWindow. But |
| // the inputMethodTransform won't get updated because the focus |
| // is on QQuickWidget. We need to remap the value based on the |
| // widget. |
| remapInputMethodQueryEvent(d->offscreenWindow->focusObject(), static_cast<QInputMethodQueryEvent *>(e)); |
| return eventResult; |
| } |
| |
| case QEvent::WindowChangeInternal: |
| d->handleWindowChange(); |
| break; |
| |
| case QEvent::ScreenChangeInternal: |
| if (QWindow *window = this->window()->windowHandle()) { |
| QScreen *newScreen = window->screen(); |
| |
| if (d->offscreenWindow) |
| d->offscreenWindow->setScreen(newScreen); |
| if (d->offscreenSurface) |
| d->offscreenSurface->setScreen(newScreen); |
| #if QT_CONFIG(opengl) |
| if (d->context) |
| d->context->setScreen(newScreen); |
| #endif |
| } |
| |
| if (d->useSoftwareRenderer |
| #if QT_CONFIG(opengl) |
| || d->fbo |
| #endif |
| ) { |
| // This will check the size taking the devicePixelRatio into account |
| // and recreate if needed. |
| createFramebufferObject(); |
| d->render(true); |
| } |
| break; |
| |
| case QEvent::Show: |
| case QEvent::Move: |
| d->updatePosition(); |
| break; |
| |
| case QEvent::WindowStateChange: |
| d->offscreenWindow->setWindowState(resolveWindowState(windowState())); |
| break; |
| |
| case QEvent::ShortcutOverride: |
| return QCoreApplication::sendEvent(d->offscreenWindow, e); |
| |
| case QEvent::Enter: { |
| QEnterEvent *enterEvent = static_cast<QEnterEvent *>(e); |
| QEnterEvent mappedEvent(enterEvent->localPos(), enterEvent->windowPos(), |
| enterEvent->screenPos()); |
| const bool ret = QCoreApplication::sendEvent(d->offscreenWindow, &mappedEvent); |
| e->setAccepted(mappedEvent.isAccepted()); |
| return ret; |
| } |
| default: |
| break; |
| } |
| |
| return QWidget::event(e); |
| } |
| |
| #if QT_CONFIG(quick_draganddrop) |
| |
| /*! \reimp */ |
| void QQuickWidget::dragEnterEvent(QDragEnterEvent *e) |
| { |
| Q_D(QQuickWidget); |
| // Don't reject drag events for the entire widget when one |
| // item rejects the drag enter |
| d->offscreenWindow->event(e); |
| e->accept(); |
| } |
| |
| /*! \reimp */ |
| void QQuickWidget::dragMoveEvent(QDragMoveEvent *e) |
| { |
| Q_D(QQuickWidget); |
| // Drag/drop events only have local pos, so no need to map, |
| // but QQuickWindow::event() does not return true |
| d->offscreenWindow->event(e); |
| } |
| |
| /*! \reimp */ |
| void QQuickWidget::dragLeaveEvent(QDragLeaveEvent *e) |
| { |
| Q_D(QQuickWidget); |
| d->offscreenWindow->event(e); |
| } |
| |
| /*! \reimp */ |
| void QQuickWidget::dropEvent(QDropEvent *e) |
| { |
| Q_D(QQuickWidget); |
| d->offscreenWindow->event(e); |
| } |
| |
| #endif // quick_draganddrop |
| |
| // TODO: try to separate the two cases of |
| // 1. render() unconditionally without sync |
| // 2. sync() and then render if necessary |
| void QQuickWidget::triggerUpdate() |
| { |
| Q_D(QQuickWidget); |
| d->updatePending = true; |
| if (!d->eventPending) { |
| // There's no sense in immediately kicking a render off now, as |
| // there may be a number of triggerUpdate calls to come from a multitude |
| // of different sources (network, touch/mouse/keyboard, timers, |
| // animations, ...), and we want to batch them all into single frames as |
| // much as possible for the sake of interactivity and responsiveness. |
| // |
| // To achieve this, we set a timer and only perform the rendering when |
| // this is complete. |
| const int exhaustDelay = 5; |
| d->updateTimer.start(exhaustDelay, Qt::PreciseTimer, this); |
| d->eventPending = true; |
| } |
| } |
| |
| /*! |
| Sets the surface \a format for the context and offscreen surface used |
| by this widget. |
| |
| Call this function when there is a need to request a context for a |
| given OpenGL version or profile. The sizes for depth, stencil and |
| alpha buffers are taken care of automatically and there is no need |
| to request those explicitly. |
| |
| \sa QWindow::setFormat(), QWindow::format(), format() |
| */ |
| void QQuickWidget::setFormat(const QSurfaceFormat &format) |
| { |
| Q_D(QQuickWidget); |
| QSurfaceFormat currentFormat = d->offscreenWindow->format(); |
| QSurfaceFormat newFormat = format; |
| newFormat.setDepthBufferSize(qMax(newFormat.depthBufferSize(), currentFormat.depthBufferSize())); |
| newFormat.setStencilBufferSize(qMax(newFormat.stencilBufferSize(), currentFormat.stencilBufferSize())); |
| newFormat.setAlphaBufferSize(qMax(newFormat.alphaBufferSize(), currentFormat.alphaBufferSize())); |
| |
| // Do not include the sample count. Requesting a multisampled context is not necessary |
| // since we render into an FBO, never to an actual surface. What's more, attempting to |
| // create a pbuffer with a multisampled config crashes certain implementations. Just |
| // avoid the entire hassle, the result is the same. |
| d->requestedSamples = newFormat.samples(); |
| newFormat.setSamples(0); |
| |
| d->offscreenWindow->setFormat(newFormat); |
| } |
| |
| /*! |
| Returns the actual surface format. |
| |
| If the widget has not yet been shown, the requested format is returned. |
| |
| \sa setFormat() |
| */ |
| QSurfaceFormat QQuickWidget::format() const |
| { |
| Q_D(const QQuickWidget); |
| return d->offscreenWindow->format(); |
| } |
| |
| /*! |
| Renders a frame and reads it back into an image. |
| |
| \note This is a potentially expensive operation. |
| */ |
| QImage QQuickWidget::grabFramebuffer() const |
| { |
| return const_cast<QQuickWidgetPrivate *>(d_func())->grabFramebuffer(); |
| } |
| |
| /*! |
| Sets the clear \a color. By default this is an opaque color. |
| |
| To get a semi-transparent QQuickWidget, call this function with |
| \a color set to Qt::transparent, set the Qt::WA_TranslucentBackground |
| widget attribute on the top-level window, and request an alpha |
| channel via setFormat(). |
| |
| \sa QQuickWindow::setColor() |
| */ |
| void QQuickWidget::setClearColor(const QColor &color) |
| { |
| Q_D(QQuickWidget); |
| d->offscreenWindow->setColor(color); |
| } |
| |
| /*! |
| \since 5.5 |
| |
| Returns the offscreen QQuickWindow which is used by this widget to drive |
| the Qt Quick rendering. This is useful if you want to use QQuickWindow |
| APIs that are not currently exposed by QQuickWidget, for instance |
| connecting to the QQuickWindow::beforeRendering() signal in order |
| to draw native OpenGL content below Qt Quick's own rendering. |
| |
| \warning Use the return value of this function with caution. In |
| particular, do not ever attempt to show the QQuickWindow, and be |
| very careful when using other QWindow-only APIs. |
| */ |
| QQuickWindow *QQuickWidget::quickWindow() const |
| { |
| Q_D(const QQuickWidget); |
| return d->offscreenWindow; |
| } |
| |
| /*! |
| \reimp |
| */ |
| void QQuickWidget::paintEvent(QPaintEvent *event) |
| { |
| Q_D(QQuickWidget); |
| if (d->useSoftwareRenderer) { |
| QPainter painter(this); |
| d->updateRegion = d->updateRegion.united(event->region()); |
| if (d->updateRegion.isNull()) { |
| //Paint everything |
| painter.drawImage(rect(), d->softwareImage); |
| } else { |
| QTransform transform; |
| transform.scale(devicePixelRatioF(), devicePixelRatioF()); |
| //Paint only the updated areas |
| QRegion targetRegion; |
| d->updateRegion.swap(targetRegion); |
| for (auto targetRect : targetRegion) { |
| auto sourceRect = transform.mapRect(QRectF(targetRect)); |
| painter.drawImage(targetRect, d->softwareImage, sourceRect); |
| } |
| } |
| } |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qquickwidget.cpp" |