diff options
Diffstat (limited to 'src/quick/items/qquickcanvas.cpp')
-rw-r--r-- | src/quick/items/qquickcanvas.cpp | 2646 |
1 files changed, 2646 insertions, 0 deletions
diff --git a/src/quick/items/qquickcanvas.cpp b/src/quick/items/qquickcanvas.cpp new file mode 100644 index 0000000000..9c6ab08fc7 --- /dev/null +++ b/src/quick/items/qquickcanvas.cpp @@ -0,0 +1,2646 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation ([email protected]) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickcanvas.h" +#include "qquickcanvas_p.h" + +#include "qquickitem.h" +#include "qquickitem_p.h" + +#include <QtQuick/private/qsgrenderer_p.h> +#include <QtQuick/private/qsgtexture_p.h> +#include <private/qsgflashnode_p.h> +#include <QtQuick/qsgengine.h> + +#include <private/qguiapplication_p.h> +#include <QtGui/QInputPanel> + +#include <private/qabstractanimation_p.h> + +#include <QtGui/qpainter.h> +#include <QtGui/qevent.h> +#include <QtGui/qmatrix4x4.h> +#include <QtCore/qvarlengtharray.h> +#include <QtCore/qabstractanimation.h> +#include <QtDeclarative/qdeclarativeincubator.h> + +#include <private/qdeclarativedebugtrace_p.h> + +QT_BEGIN_NAMESPACE + +#define QQUICK_CANVAS_TIMING +#ifdef QQUICK_CANVAS_TIMING +static bool qquick_canvas_timing = !qgetenv("QML_CANVAS_TIMING").isEmpty(); +static QTime threadTimer; +static int syncTime; +static int renderTime; +static int swapTime; +#endif + +DEFINE_BOOL_CONFIG_OPTION(qmlFixedAnimationStep, QML_FIXED_ANIMATION_STEP) +DEFINE_BOOL_CONFIG_OPTION(qmlNoThreadedRenderer, QML_BAD_GUI_RENDER_LOOP) + +extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); + +void QQuickCanvasPrivate::updateFocusItemTransform() +{ + Q_Q(QQuickCanvas); + QQuickItem *focus = q->activeFocusItem(); + if (focus && qApp->inputPanel()->inputItem() == focus) + qApp->inputPanel()->setInputItemTransform(QQuickItemPrivate::get(focus)->itemToCanvasTransform()); +} + +class QQuickCanvasIncubationController : public QObject, public QDeclarativeIncubationController +{ +public: + QQuickCanvasIncubationController(QQuickCanvasPrivate *canvas) + : m_canvas(canvas), m_eventSent(false) {} + +protected: + virtual bool event(QEvent *e) + { + if (e->type() == QEvent::User) { + Q_ASSERT(m_eventSent); + + bool *amtp = m_canvas->thread->allowMainThreadProcessing(); + while (incubatingObjectCount()) { + if (amtp) + incubateWhile(amtp); + else + incubateFor(5); + QCoreApplication::processEvents(); + } + + m_eventSent = false; + } + return QObject::event(e); + } + + virtual void incubatingObjectCountChanged(int count) + { + if (count && !m_eventSent) { + m_eventSent = true; + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + } + } + +private: + QQuickCanvasPrivate *m_canvas; + bool m_eventSent; +}; + +class QQuickCanvasPlainRenderLoop : public QObject, public QQuickCanvasRenderLoop +{ +public: + QQuickCanvasPlainRenderLoop() + : updatePending(false) + , animationRunning(false) + { + } + + virtual void paint() { + updatePending = false; + if (animationRunning && animationDriver()) + animationDriver()->advance(); + polishItems(); + syncSceneGraph(); + makeCurrent(); + glViewport(0, 0, size.width(), size.height()); + renderSceneGraph(size); + swapBuffers(); + + if (animationRunning) + maybeUpdate(); + } + + virtual QImage grab() { + return qt_gl_read_framebuffer(size, false, false); + } + + virtual void startRendering() { + if (!glContext()) { + createGLContext(); + makeCurrent(); + initializeSceneGraph(); + } else { + makeCurrent(); + } + maybeUpdate(); + } + + virtual void stopRendering() { + cleanupNodesOnShutdown(); + } + + virtual void maybeUpdate() { + if (!updatePending) { + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + updatePending = true; + } + } + + virtual void animationStarted() { + animationRunning = true; + maybeUpdate(); + } + + virtual void animationStopped() { + animationRunning = false; + } + + virtual bool isRunning() const { return glContext(); } // Event loop is always running... + virtual void resize(const QSize &s) { size = s; } + virtual void setWindowSize(const QSize &s) { size = s; } + + bool event(QEvent *e) { + if (e->type() == QEvent::User) { + paint(); + return true; + } + return QObject::event(e); + } + + QSize size; + + uint updatePending : 1; + uint animationRunning : 1; +}; + + + +/* +Focus behavior +============== + +Prior to being added to a valid canvas items can set and clear focus with no +effect. Only once items are added to a canvas (by way of having a parent set that +already belongs to a canvas) do the focus rules apply. Focus goes back to +having no effect if an item is removed from a canvas. + +When an item is moved into a new focus scope (either being added to a canvas +for the first time, or having its parent changed), if the focus scope already has +a scope focused item that takes precedence over the item being added. Otherwise, +the focus of the added tree is used. In the case of of a tree of items being +added to a canvas for the first time, which may have a conflicted focus state (two +or more items in one scope having focus set), the same rule is applied item by item - +thus the first item that has focus will get it (assuming the scope doesn't already +have a scope focused item), and the other items will have their focus cleared. +*/ + +/* + Threaded Rendering + ================== + + The threaded rendering uses a number of different variables to track potential + states used to handle resizing, initial paint, grabbing and driving animations + while ALWAYS keeping the GL context in the rendering thread and keeping the + overhead of normal one-shot paints and vblank driven animations at a minimum. + + Resize, initial show and grab suffer slightly in this model as they are locked + to the rendering in the rendering thread, but this is a necessary evil for + the system to work. + + Variables that are used: + + Private::animationRunning: This is true while the animations are running, and only + written to inside locks. + + RenderThread::isGuiBlocked: This is used to indicate that the GUI thread owns the + lock. This variable is an integer to allow for recursive calls to lockInGui() + without using a recursive mutex. See isGuiBlockPending. + + RenderThread::isPaintComplete: This variable is cleared when rendering starts and + set once rendering is complete. It is monitored in the paintEvent(), + resizeEvent() and grab() functions to force them to wait for rendering to + complete. + + RenderThread::isGuiBlockPending: This variable is set in the render thread just + before the sync event is sent to the GUI thread. It is used to avoid deadlocks + in the case where render thread waits while waiting for GUI to pick up the sync + event and GUI thread gets a resizeEvent, the initial paintEvent or a grab. + When this happens, we use the + exhaustSyncEvent() function to do the sync right there and mark the coming + sync event to be discarded. There can only ever be one sync incoming. + + RenderThread::isRenderBlock: This variable is true when animations are not + running and the render thread has gone to sleep, waiting for more to do. + + RenderThread::isExternalUpdatePending: This variable is set to false when + a new render pass is started and to true in maybeUpdate(). It is an + indication to the render thread that another render pass needs to take + place, rather than the render thread going to sleep after completing its swap. + + RenderThread::doGrab: This variable is set by the grab() function and + tells the renderer to do a grab after rendering is complete and before + swapping happens. + + RenderThread::shouldExit: This variable is used to determine if the render + thread should do a nother pass. It is typically set as a result of show() + and unset as a result of hide() or during shutdown() + + RenderThread::hasExited: Used by the GUI thread to synchronize the shutdown + after shouldExit has been set to true. + */ + +// #define FOCUS_DEBUG +// #define MOUSE_DEBUG +// #define TOUCH_DEBUG +// #define DIRTY_DEBUG +// #define THREAD_DEBUG + +// #define FRAME_TIMING + +#ifdef FRAME_TIMING +static QTime frameTimer; +int sceneGraphRenderTime; +int readbackTime; +#endif + +QQuickItem::UpdatePaintNodeData::UpdatePaintNodeData() +: transformNode(0) +{ +} + +QQuickRootItem::QQuickRootItem() +{ +} + +void QQuickCanvas::exposeEvent(QExposeEvent *) +{ + Q_D(QQuickCanvas); + d->thread->paint(); +} + +void QQuickCanvas::resizeEvent(QResizeEvent *) +{ + Q_D(QQuickCanvas); + d->thread->resize(size()); +} + +void QQuickCanvas::animationStarted() +{ + d_func()->thread->animationStarted(); +} + +void QQuickCanvas::animationStopped() +{ + d_func()->thread->animationStopped(); +} + +void QQuickCanvas::showEvent(QShowEvent *) +{ + Q_D(QQuickCanvas); + if (d->vsyncAnimations) { + if (!d->animationDriver) { + d->animationDriver = d->context->createAnimationDriver(this); + connect(d->animationDriver, SIGNAL(started()), this, SLOT(animationStarted()), Qt::DirectConnection); + connect(d->animationDriver, SIGNAL(stopped()), this, SLOT(animationStopped()), Qt::DirectConnection); + } + d->animationDriver->install(); + } + + if (!d->thread->isRunning()) { + d->thread->setWindowSize(size()); + d->thread->startRendering(); + } +} + +void QQuickCanvas::hideEvent(QHideEvent *) +{ + Q_D(QQuickCanvas); + d->thread->stopRendering(); +} + +void QQuickCanvas::focusOutEvent(QFocusEvent *) +{ + Q_D(QQuickCanvas); + d->rootItem->setFocus(false); +} + +void QQuickCanvas::focusInEvent(QFocusEvent *) +{ + Q_D(QQuickCanvas); + d->rootItem->setFocus(true); +} + + +/*! + Sets weither this canvas should use vsync driven animations. + + This option can only be set on one single QQuickCanvas, and that it's + vsync signal will then be used to drive all animations in the + process. + + This feature is primarily useful for single QQuickCanvas, QML-only + applications. + + \warning Enabling vsync on multiple QQuickCanvas instances has + undefined behavior. + */ +void QQuickCanvas::setVSyncAnimations(bool enabled) +{ + Q_D(QQuickCanvas); + if (visible()) { + qWarning("QQuickCanvas::setVSyncAnimations: Cannot be changed when widget is shown"); + return; + } + d->vsyncAnimations = enabled; +} + + + +/*! + Returns true if this canvas should use vsync driven animations; + otherwise returns false. + */ +bool QQuickCanvas::vsyncAnimations() const +{ + Q_D(const QQuickCanvas); + return d->vsyncAnimations; +} + +void QQuickCanvasPrivate::initializeSceneGraph() +{ + if (!context) + context = QSGContext::createDefaultContext(); + + if (context->isReady()) + return; + + QOpenGLContext *glctx = const_cast<QOpenGLContext *>(QOpenGLContext::currentContext()); + context->initialize(glctx); + + Q_Q(QQuickCanvas); + + if (!QQuickItemPrivate::get(rootItem)->itemNode()->parent()) { + context->rootNode()->appendChildNode(QQuickItemPrivate::get(rootItem)->itemNode()); + } + + engine = new QSGEngine(); + engine->setCanvas(q); + + emit q_func()->sceneGraphInitialized(); +} + +void QQuickCanvasPrivate::polishItems() +{ + while (!itemsToPolish.isEmpty()) { + QSet<QQuickItem *>::Iterator iter = itemsToPolish.begin(); + QQuickItem *item = *iter; + itemsToPolish.erase(iter); + QQuickItemPrivate::get(item)->polishScheduled = false; + item->updatePolish(); + } + updateFocusItemTransform(); +} + + +void QQuickCanvasPrivate::syncSceneGraph() +{ + updateDirtyNodes(); + + // Copy the current state of clearing from canvas into renderer. + context->renderer()->setClearColor(clearColor); + QSGRenderer::ClearMode mode = QSGRenderer::ClearStencilBuffer | QSGRenderer::ClearDepthBuffer; + if (clearBeforeRendering) + mode |= QSGRenderer::ClearColorBuffer; + context->renderer()->setClearMode(mode); +} + + +void QQuickCanvasPrivate::renderSceneGraph(const QSize &size) +{ + Q_Q(QQuickCanvas); + context->renderer()->setDeviceRect(QRect(QPoint(0, 0), size)); + context->renderer()->setViewportRect(QRect(QPoint(0, 0), renderTarget ? renderTarget->size() : size)); + context->renderer()->setProjectionMatrixToDeviceRect(); + + emit q->beforeRendering(); + context->renderNextFrame(renderTarget); + emit q->afterRendering(); + +#ifdef FRAME_TIMING + sceneGraphRenderTime = frameTimer.elapsed(); +#endif + +#ifdef FRAME_TIMING +// int pixel; +// glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel); + readbackTime = frameTimer.elapsed(); +#endif +} + + +QQuickCanvasPrivate::QQuickCanvasPrivate() + : rootItem(0) + , activeFocusItem(0) + , mouseGrabberItem(0) + , dirtyItemList(0) + , context(0) + , clearColor(Qt::white) + , vsyncAnimations(false) + , clearBeforeRendering(true) + , thread(0) + , animationDriver(0) + , renderTarget(0) + , incubationController(0) +{ +} + +QQuickCanvasPrivate::~QQuickCanvasPrivate() +{ +} + +void QQuickCanvasPrivate::init(QQuickCanvas *c) +{ + QUnifiedTimer* ut = QUnifiedTimer::instance(true); + if (qmlFixedAnimationStep()) + ut->setConsistentTiming(true); + + q_ptr = c; + + Q_Q(QQuickCanvas); + + rootItem = new QQuickRootItem; + QQuickItemPrivate *rootItemPrivate = QQuickItemPrivate::get(rootItem); + rootItemPrivate->canvas = q; + rootItemPrivate->flags |= QQuickItem::ItemIsFocusScope; + + // In the absence of a focus in event on some platforms assume the window will + // be activated immediately and set focus on the rootItem + // ### Remove when QTBUG-22415 is resolved. + //It is important that this call happens after the rootItem has a canvas.. + rootItem->setFocus(true); + + bool threaded = !qmlNoThreadedRenderer(); + + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)) { + qWarning("QQuickCanvas: platform does not support threaded rendering!"); + threaded = false; + } + + if (threaded) + thread = new QQuickCanvasRenderThread(); + else + thread = new QQuickCanvasPlainRenderLoop(); + + thread->renderer = q; + thread->d = this; + + context = QSGContext::createDefaultContext(); + thread->moveContextToThread(context); + + q->setSurfaceType(QWindow::OpenGLSurface); + q->setFormat(context->defaultSurfaceFormat()); +} + +QDeclarativeListProperty<QObject> QQuickCanvasPrivate::data() +{ + initRootItem(); + return QQuickItemPrivate::get(rootItem)->data(); +} + +void QQuickCanvasPrivate::initRootItem() +{ + Q_Q(QQuickCanvas); + q->connect(q, SIGNAL(widthChanged(int)), + rootItem, SLOT(setWidth(int))); + q->connect(q, SIGNAL(heightChanged(int)), + rootItem, SLOT(setHeight(int))); + rootItem->setWidth(q->width()); + rootItem->setHeight(q->height()); +} + +void QQuickCanvasPrivate::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform) +{ + for (int i=0; i<touchPoints.count(); i++) { + QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; + touchPoint.setRect(transform.mapRect(touchPoint.sceneRect())); + touchPoint.setStartPos(transform.map(touchPoint.startScenePos())); + touchPoint.setLastPos(transform.map(touchPoint.lastScenePos())); + } +} + + +/*! +Translates the data in \a touchEvent to this canvas. This method leaves the item local positions in +\a touchEvent untouched (these are filled in later). +*/ +void QQuickCanvasPrivate::translateTouchEvent(QTouchEvent *touchEvent) +{ +// Q_Q(QQuickCanvas); + +// touchEvent->setWidget(q); // ### refactor... + + QList<QTouchEvent::TouchPoint> touchPoints = touchEvent->touchPoints(); + for (int i = 0; i < touchPoints.count(); ++i) { + QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; + + touchPoint.setScreenRect(touchPoint.sceneRect()); + touchPoint.setStartScreenPos(touchPoint.startScenePos()); + touchPoint.setLastScreenPos(touchPoint.lastScenePos()); + + touchPoint.setSceneRect(touchPoint.rect()); + touchPoint.setStartScenePos(touchPoint.startPos()); + touchPoint.setLastScenePos(touchPoint.lastPos()); + + if (touchPoint.isPrimary()) + lastMousePosition = touchPoint.pos().toPoint(); + } + touchEvent->setTouchPoints(touchPoints); +} + +void QQuickCanvasPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *item, FocusOptions options) +{ + Q_Q(QQuickCanvas); + + Q_ASSERT(item); + Q_ASSERT(scope || item == rootItem); + +#ifdef FOCUS_DEBUG + qWarning() << "QQuickCanvasPrivate::setFocusInScope():"; + qWarning() << " scope:" << (QObject *)scope; + if (scope) + qWarning() << " scopeSubFocusItem:" << (QObject *)QQuickItemPrivate::get(scope)->subFocusItem; + qWarning() << " item:" << (QObject *)item; + qWarning() << " activeFocusItem:" << (QObject *)activeFocusItem; +#endif + + QQuickItemPrivate *scopePrivate = scope ? QQuickItemPrivate::get(scope) : 0; + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + + QQuickItem *oldActiveFocusItem = 0; + QQuickItem *newActiveFocusItem = 0; + + QVarLengthArray<QQuickItem *, 20> changed; + + // Does this change the active focus? + if (item == rootItem || scopePrivate->activeFocus) { + oldActiveFocusItem = activeFocusItem; + newActiveFocusItem = item; + while (newActiveFocusItem->isFocusScope() && newActiveFocusItem->scopedFocusItem()) + newActiveFocusItem = newActiveFocusItem->scopedFocusItem(); + + if (oldActiveFocusItem) { +#ifndef QT_NO_IM + qApp->inputPanel()->commit(); +#endif + + activeFocusItem = 0; + QFocusEvent event(QEvent::FocusOut, Qt::OtherFocusReason); + q->sendEvent(oldActiveFocusItem, &event); + + QQuickItem *afi = oldActiveFocusItem; + while (afi != scope) { + if (QQuickItemPrivate::get(afi)->activeFocus) { + QQuickItemPrivate::get(afi)->activeFocus = false; + changed << afi; + } + afi = afi->parentItem(); + } + } + } + + if (item != rootItem) { + QQuickItem *oldSubFocusItem = scopePrivate->subFocusItem; + // Correct focus chain in scope + if (oldSubFocusItem) { + QQuickItem *sfi = scopePrivate->subFocusItem->parentItem(); + while (sfi != scope) { + QQuickItemPrivate::get(sfi)->subFocusItem = 0; + sfi = sfi->parentItem(); + } + } + { + scopePrivate->subFocusItem = item; + QQuickItem *sfi = scopePrivate->subFocusItem->parentItem(); + while (sfi != scope) { + QQuickItemPrivate::get(sfi)->subFocusItem = item; + sfi = sfi->parentItem(); + } + } + + if (oldSubFocusItem) { + QQuickItemPrivate::get(oldSubFocusItem)->focus = false; + changed << oldSubFocusItem; + } + } + + if (!(options & DontChangeFocusProperty)) { +// if (item != rootItem || QGuiApplication::focusWindow() == q) { // QTBUG-22415 + itemPrivate->focus = true; + changed << item; +// } + } + + if (newActiveFocusItem && rootItem->hasFocus()) { + activeFocusItem = newActiveFocusItem; + + QQuickItemPrivate::get(newActiveFocusItem)->activeFocus = true; + changed << newActiveFocusItem; + + QQuickItem *afi = newActiveFocusItem->parentItem(); + while (afi && afi != scope) { + if (afi->isFocusScope()) { + QQuickItemPrivate::get(afi)->activeFocus = true; + changed << afi; + } + afi = afi->parentItem(); + } + + updateInputMethodData(); + + QFocusEvent event(QEvent::FocusIn, Qt::OtherFocusReason); + q->sendEvent(newActiveFocusItem, &event); + } else { + updateInputMethodData(); + } + + if (!changed.isEmpty()) + notifyFocusChangesRecur(changed.data(), changed.count() - 1); +} + +void QQuickCanvasPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem *item, FocusOptions options) +{ + Q_Q(QQuickCanvas); + + Q_UNUSED(item); + Q_ASSERT(item); + Q_ASSERT(scope || item == rootItem); + +#ifdef FOCUS_DEBUG + qWarning() << "QQuickCanvasPrivate::clearFocusInScope():"; + qWarning() << " scope:" << (QObject *)scope; + qWarning() << " item:" << (QObject *)item; + qWarning() << " activeFocusItem:" << (QObject *)activeFocusItem; +#endif + + QQuickItemPrivate *scopePrivate = scope ? QQuickItemPrivate::get(scope) : 0; + + QQuickItem *oldActiveFocusItem = 0; + QQuickItem *newActiveFocusItem = 0; + + QVarLengthArray<QQuickItem *, 20> changed; + + Q_ASSERT(item == rootItem || item == scopePrivate->subFocusItem); + + // Does this change the active focus? + if (item == rootItem || scopePrivate->activeFocus) { + oldActiveFocusItem = activeFocusItem; + newActiveFocusItem = scope; + + Q_ASSERT(oldActiveFocusItem); + +#ifndef QT_NO_IM + qApp->inputPanel()->commit(); +#endif + + activeFocusItem = 0; + QFocusEvent event(QEvent::FocusOut, Qt::OtherFocusReason); + q->sendEvent(oldActiveFocusItem, &event); + + QQuickItem *afi = oldActiveFocusItem; + while (afi != scope) { + if (QQuickItemPrivate::get(afi)->activeFocus) { + QQuickItemPrivate::get(afi)->activeFocus = false; + changed << afi; + } + afi = afi->parentItem(); + } + } + + if (item != rootItem) { + QQuickItem *oldSubFocusItem = scopePrivate->subFocusItem; + // Correct focus chain in scope + if (oldSubFocusItem) { + QQuickItem *sfi = scopePrivate->subFocusItem->parentItem(); + while (sfi != scope) { + QQuickItemPrivate::get(sfi)->subFocusItem = 0; + sfi = sfi->parentItem(); + } + } + scopePrivate->subFocusItem = 0; + + if (oldSubFocusItem && !(options & DontChangeFocusProperty)) { + QQuickItemPrivate::get(oldSubFocusItem)->focus = false; + changed << oldSubFocusItem; + } + } else if (!(options & DontChangeFocusProperty)) { + QQuickItemPrivate::get(item)->focus = false; + changed << item; + } + + if (newActiveFocusItem) { + Q_ASSERT(newActiveFocusItem == scope); + activeFocusItem = scope; + + updateInputMethodData(); + + QFocusEvent event(QEvent::FocusIn, Qt::OtherFocusReason); + q->sendEvent(newActiveFocusItem, &event); + } else { + updateInputMethodData(); + } + + if (!changed.isEmpty()) + notifyFocusChangesRecur(changed.data(), changed.count() - 1); +} + +void QQuickCanvasPrivate::notifyFocusChangesRecur(QQuickItem **items, int remaining) +{ + QDeclarativeGuard<QQuickItem> item(*items); + + if (remaining) + notifyFocusChangesRecur(items + 1, remaining - 1); + + if (item) { + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + + if (itemPrivate->notifiedFocus != itemPrivate->focus) { + itemPrivate->notifiedFocus = itemPrivate->focus; + emit item->focusChanged(itemPrivate->focus); + } + + if (item && itemPrivate->notifiedActiveFocus != itemPrivate->activeFocus) { + itemPrivate->notifiedActiveFocus = itemPrivate->activeFocus; + itemPrivate->itemChange(QQuickItem::ItemActiveFocusHasChanged, itemPrivate->activeFocus); + emit item->activeFocusChanged(itemPrivate->activeFocus); + } + } +} + +void QQuickCanvasPrivate::updateInputMethodData() +{ + QQuickItem *inputItem = 0; + if (activeFocusItem && activeFocusItem->flags() & QQuickItem::ItemAcceptsInputMethod) + inputItem = activeFocusItem; + qApp->inputPanel()->setInputItem(inputItem); +} + +void QQuickCanvasPrivate::dirtyItem(QQuickItem *) +{ + Q_Q(QQuickCanvas); + q->maybeUpdate(); +} + +void QQuickCanvasPrivate::cleanup(QSGNode *n) +{ + Q_Q(QQuickCanvas); + + Q_ASSERT(!cleanupNodeList.contains(n)); + cleanupNodeList.append(n); + q->maybeUpdate(); +} + + +/*! + \qmlclass Window QQuickCanvas + \inqmlmodule QtQuick.Window 2 + \brief The Window object creates a new top-level window. + + The Window object creates a new top-level window for a QtQuick scene. It automatically sets up the + window for use with QtQuick 2.0 graphical elements. +*/ +/*! + \class QQuickCanvas + \since QtQuick 2.0 + \brief The QQuickCanvas class provides the canvas for displaying a graphical QML scene + + \inmodule QtQuick + + QQuickCanvas provides the graphical scene management needed to interact with and display + a scene of QQuickItems. + + A QQuickCanvas always has a single invisible root item. To add items to this canvas, + reparent the items to the root item or to an existing item in the scene. + + For easily displaying a scene from a QML file, see \l{QQuickView}. +*/ +QQuickCanvas::QQuickCanvas(QWindow *parent) + : QWindow(*(new QQuickCanvasPrivate), parent) +{ + Q_D(QQuickCanvas); + d->init(this); +} + +QQuickCanvas::QQuickCanvas(QQuickCanvasPrivate &dd, QWindow *parent) + : QWindow(dd, parent) +{ + Q_D(QQuickCanvas); + d->init(this); +} + +QQuickCanvas::~QQuickCanvas() +{ + Q_D(QQuickCanvas); + + if (d->thread->isRunning()) + d->thread->stopRendering(); + + // ### should we change ~QQuickItem to handle this better? + // manually cleanup for the root item (item destructor only handles these when an item is parented) + QQuickItemPrivate *rootItemPrivate = QQuickItemPrivate::get(d->rootItem); + rootItemPrivate->removeFromDirtyList(); + + delete d->incubationController; d->incubationController = 0; + + delete d->rootItem; d->rootItem = 0; + + delete d->thread; d->thread = 0; +} + +/*! + Returns the invisible root item of the scene. + + A QQuickCanvas always has a single invisible root item. To add items to this canvas, + reparent the items to the root item or to an existing item in the scene. +*/ +QQuickItem *QQuickCanvas::rootItem() const +{ + Q_D(const QQuickCanvas); + + return d->rootItem; +} + +/*! + Returns the item which currently has active focus. +*/ +QQuickItem *QQuickCanvas::activeFocusItem() const +{ + Q_D(const QQuickCanvas); + + return d->activeFocusItem; +} + +/*! + Returns the item which currently has the mouse grab. +*/ +QQuickItem *QQuickCanvas::mouseGrabberItem() const +{ + Q_D(const QQuickCanvas); + + return d->mouseGrabberItem; +} + + +/*! + \qmlproperty color QtQuick2.Window::Window::color + + The background color for the window. + + Setting this property is more efficient than using a separate Rectangle. +*/ + +bool QQuickCanvasPrivate::clearHover() +{ + if (hoverItems.isEmpty()) + return false; + + QPointF pos = QCursor::pos(); // ### refactor: q->mapFromGlobal(QCursor::pos()); + + bool accepted = false; + foreach (QQuickItem* item, hoverItems) + accepted = sendHoverEvent(QEvent::HoverLeave, item, pos, pos, QGuiApplication::keyboardModifiers(), true) || accepted; + hoverItems.clear(); + return accepted; +} + + +bool QQuickCanvas::event(QEvent *e) +{ + Q_D(QQuickCanvas); + + switch (e->type()) { + + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + { + QTouchEvent *touch = static_cast<QTouchEvent *>(e); + d->translateTouchEvent(touch); + d->deliverTouchEvent(touch); + if (!touch->isAccepted()) + return false; + break; + } + case QEvent::Leave: + d->clearHover(); + d->lastMousePosition = QPoint(); + break; + case QEvent::DragEnter: + case QEvent::DragLeave: + case QEvent::DragMove: + case QEvent::Drop: + d->deliverDragEvent(&d->dragGrabber, e); + break; + case QEvent::WindowDeactivate: + rootItem()->windowDeactivateEvent(); + break; + default: + break; + } + + return QWindow::event(e); +} + +void QQuickCanvas::keyPressEvent(QKeyEvent *e) +{ + Q_D(QQuickCanvas); + + if (d->activeFocusItem) + sendEvent(d->activeFocusItem, e); +} + +void QQuickCanvas::keyReleaseEvent(QKeyEvent *e) +{ + Q_D(QQuickCanvas); + + if (d->activeFocusItem) + sendEvent(d->activeFocusItem, e); +} + +bool QQuickCanvasPrivate::deliverInitialMousePressEvent(QQuickItem *item, QMouseEvent *event) +{ + Q_Q(QQuickCanvas); + + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + if (itemPrivate->opacity == 0.0) + return false; + + if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { + QPointF p = item->mapFromScene(event->windowPos()); + if (!QRectF(0, 0, item->width(), item->height()).contains(p)) + return false; + } + + QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); + for (int ii = children.count() - 1; ii >= 0; --ii) { + QQuickItem *child = children.at(ii); + if (!child->isVisible() || !child->isEnabled()) + continue; + if (deliverInitialMousePressEvent(child, event)) + return true; + } + + if (itemPrivate->acceptedMouseButtons & event->button()) { + QPointF p = item->mapFromScene(event->windowPos()); + if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + QMouseEvent me(event->type(), p, event->windowPos(), event->screenPos(), + event->button(), event->buttons(), event->modifiers()); + me.accept(); + mouseGrabberItem = item; + q->sendEvent(item, &me); + event->setAccepted(me.isAccepted()); + if (me.isAccepted()) + return true; + mouseGrabberItem->ungrabMouse(); + mouseGrabberItem = 0; + } + } + + return false; +} + +bool QQuickCanvasPrivate::deliverMouseEvent(QMouseEvent *event) +{ + Q_Q(QQuickCanvas); + + lastMousePosition = event->windowPos(); + + if (!mouseGrabberItem && + event->type() == QEvent::MouseButtonPress && + (event->button() & event->buttons()) == event->buttons()) { + return deliverInitialMousePressEvent(rootItem, event); + } + + if (mouseGrabberItem) { + QQuickItemPrivate *mgPrivate = QQuickItemPrivate::get(mouseGrabberItem); + const QTransform &transform = mgPrivate->canvasToItemTransform(); + QMouseEvent me(event->type(), transform.map(event->windowPos()), event->windowPos(), event->screenPos(), + event->button(), event->buttons(), event->modifiers()); + me.accept(); + q->sendEvent(mouseGrabberItem, &me); + event->setAccepted(me.isAccepted()); + if (me.isAccepted()) + return true; + } + + return false; +} + +void QQuickCanvas::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickCanvas); + +#ifdef MOUSE_DEBUG + qWarning() << "QQuickCanvas::mousePressEvent()" << event->pos() << event->button() << event->buttons(); +#endif + + d->deliverMouseEvent(event); +} + +void QQuickCanvas::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QQuickCanvas); + +#ifdef MOUSE_DEBUG + qWarning() << "QQuickCanvas::mouseReleaseEvent()" << event->pos() << event->button() << event->buttons(); +#endif + + if (!d->mouseGrabberItem) { + QWindow::mouseReleaseEvent(event); + return; + } + + d->deliverMouseEvent(event); + d->mouseGrabberItem = 0; +} + +void QQuickCanvas::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QQuickCanvas); + +#ifdef MOUSE_DEBUG + qWarning() << "QQuickCanvas::mouseDoubleClickEvent()" << event->pos() << event->button() << event->buttons(); +#endif + + if (!d->mouseGrabberItem && (event->button() & event->buttons()) == event->buttons()) { + if (d->deliverInitialMousePressEvent(d->rootItem, event)) + event->accept(); + else + event->ignore(); + return; + } + + d->deliverMouseEvent(event); +} + +bool QQuickCanvasPrivate::sendHoverEvent(QEvent::Type type, QQuickItem *item, + const QPointF &scenePos, const QPointF &lastScenePos, + Qt::KeyboardModifiers modifiers, bool accepted) +{ + Q_Q(QQuickCanvas); + const QTransform transform = QQuickItemPrivate::get(item)->canvasToItemTransform(); + + //create copy of event + QHoverEvent hoverEvent(type, transform.map(scenePos), transform.map(lastScenePos), modifiers); + hoverEvent.setAccepted(accepted); + + q->sendEvent(item, &hoverEvent); + + return hoverEvent.isAccepted(); +} + +void QQuickCanvas::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickCanvas); + +#ifdef MOUSE_DEBUG + qWarning() << "QQuickCanvas::mouseMoveEvent()" << event->pos() << event->button() << event->buttons(); +#endif + + if (!d->mouseGrabberItem) { + if (d->lastMousePosition.isNull()) + d->lastMousePosition = event->windowPos(); + QPointF last = d->lastMousePosition; + d->lastMousePosition = event->windowPos(); + + bool accepted = event->isAccepted(); + bool delivered = d->deliverHoverEvent(d->rootItem, event->windowPos(), last, event->modifiers(), accepted); + if (!delivered) { + //take care of any exits + accepted = d->clearHover(); + } + event->setAccepted(accepted); + return; + } + + d->deliverMouseEvent(event); +} + +bool QQuickCanvasPrivate::deliverHoverEvent(QQuickItem *item, const QPointF &scenePos, const QPointF &lastScenePos, + Qt::KeyboardModifiers modifiers, bool &accepted) +{ + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + if (itemPrivate->opacity == 0.0) + return false; + + if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { + QPointF p = item->mapFromScene(scenePos); + if (!QRectF(0, 0, item->width(), item->height()).contains(p)) + return false; + } + + QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); + for (int ii = children.count() - 1; ii >= 0; --ii) { + QQuickItem *child = children.at(ii); + if (!child->isVisible() || !child->isEnabled()) + continue; + if (deliverHoverEvent(child, scenePos, lastScenePos, modifiers, accepted)) + return true; + } + + if (itemPrivate->hoverEnabled) { + QPointF p = item->mapFromScene(scenePos); + if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + if (!hoverItems.isEmpty() && hoverItems[0] == item) { + //move + accepted = sendHoverEvent(QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, accepted); + } else { + QList<QQuickItem *> itemsToHover; + QQuickItem* parent = item; + itemsToHover << item; + while ((parent = parent->parentItem())) + itemsToHover << parent; + + // Leaving from previous hovered items until we reach the item or one of its ancestors. + while (!hoverItems.isEmpty() && !itemsToHover.contains(hoverItems[0])) { + sendHoverEvent(QEvent::HoverLeave, hoverItems[0], scenePos, lastScenePos, modifiers, accepted); + hoverItems.removeFirst(); + } + + if (!hoverItems.isEmpty() && hoverItems[0] == item){//Not entering a new Item + // ### Shouldn't we send moves for the parent items as well? + accepted = sendHoverEvent(QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, accepted); + } else { + // Enter items that are not entered yet. + int startIdx = -1; + if (!hoverItems.isEmpty()) + startIdx = itemsToHover.indexOf(hoverItems[0]) - 1; + if (startIdx == -1) + startIdx = itemsToHover.count() - 1; + + for (int i = startIdx; i >= 0; i--) { + QQuickItem *itemToHover = itemsToHover[i]; + if (QQuickItemPrivate::get(itemToHover)->hoverEnabled) { + hoverItems.prepend(itemToHover); + sendHoverEvent(QEvent::HoverEnter, itemToHover, scenePos, lastScenePos, modifiers, accepted); + } + } + } + } + return true; + } + } + + return false; +} + +bool QQuickCanvasPrivate::deliverWheelEvent(QQuickItem *item, QWheelEvent *event) +{ + Q_Q(QQuickCanvas); + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + if (itemPrivate->opacity == 0.0) + return false; + + if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { + QPointF p = item->mapFromScene(event->posF()); + if (!QRectF(0, 0, item->width(), item->height()).contains(p)) + return false; + } + + QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); + for (int ii = children.count() - 1; ii >= 0; --ii) { + QQuickItem *child = children.at(ii); + if (!child->isVisible() || !child->isEnabled()) + continue; + if (deliverWheelEvent(child, event)) + return true; + } + + QPointF p = item->mapFromScene(event->posF()); + if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + QWheelEvent wheel(p, event->delta(), event->buttons(), event->modifiers(), event->orientation()); + wheel.accept(); + q->sendEvent(item, &wheel); + if (wheel.isAccepted()) { + event->accept(); + return true; + } + } + + return false; +} + +#ifndef QT_NO_WHEELEVENT +void QQuickCanvas::wheelEvent(QWheelEvent *event) +{ + Q_D(QQuickCanvas); +#ifdef MOUSE_DEBUG + qWarning() << "QQuickCanvas::wheelEvent()" << event->pos() << event->delta() << event->orientation(); +#endif + event->ignore(); + d->deliverWheelEvent(d->rootItem, event); +} +#endif // QT_NO_WHEELEVENT + +bool QQuickCanvasPrivate::deliverTouchEvent(QTouchEvent *event) +{ +#ifdef TOUCH_DEBUG + if (event->type() == QEvent::TouchBegin) + qWarning("touchBeginEvent"); + else if (event->type() == QEvent::TouchUpdate) + qWarning("touchUpdateEvent"); + else if (event->type() == QEvent::TouchEnd) + qWarning("touchEndEvent"); +#endif + + QHash<QQuickItem *, QList<QTouchEvent::TouchPoint> > updatedPoints; + + if (event->type() == QTouchEvent::TouchBegin) { // all points are new touch points + QSet<int> acceptedNewPoints; + deliverTouchPoints(rootItem, event, event->touchPoints(), &acceptedNewPoints, &updatedPoints); + if (acceptedNewPoints.count() > 0) + event->accept(); + return event->isAccepted(); + } + + const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints(); + QList<QTouchEvent::TouchPoint> newPoints; + QQuickItem *item = 0; + for (int i=0; i<touchPoints.count(); i++) { + const QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; + switch (touchPoint.state()) { + case Qt::TouchPointPressed: + newPoints << touchPoint; + break; + case Qt::TouchPointMoved: + case Qt::TouchPointStationary: + case Qt::TouchPointReleased: + if (itemForTouchPointId.contains(touchPoint.id())) { + item = itemForTouchPointId[touchPoint.id()]; + if (item) + updatedPoints[item].append(touchPoint); + } + break; + default: + break; + } + } + + if (newPoints.count() > 0 || updatedPoints.count() > 0) { + QSet<int> acceptedNewPoints; + int prevCount = updatedPoints.count(); + deliverTouchPoints(rootItem, event, newPoints, &acceptedNewPoints, &updatedPoints); + if (acceptedNewPoints.count() > 0 || updatedPoints.count() != prevCount) + event->accept(); + } + + if (event->touchPointStates() & Qt::TouchPointReleased) { + for (int i=0; i<touchPoints.count(); i++) { + if (touchPoints[i].state() == Qt::TouchPointReleased) + itemForTouchPointId.remove(touchPoints[i].id()); + } + } + + return event->isAccepted(); +} + +bool QQuickCanvasPrivate::deliverTouchPoints(QQuickItem *item, QTouchEvent *event, const QList<QTouchEvent::TouchPoint> &newPoints, QSet<int> *acceptedNewPoints, QHash<QQuickItem *, QList<QTouchEvent::TouchPoint> > *updatedPoints) +{ + Q_Q(QQuickCanvas); + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + + if (itemPrivate->opacity == 0.0) + return false; + + if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { + QRectF bounds(0, 0, item->width(), item->height()); + for (int i=0; i<newPoints.count(); i++) { + QPointF p = item->mapFromScene(newPoints[i].scenePos()); + if (!bounds.contains(p)) + return false; + } + } + + QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); + for (int ii = children.count() - 1; ii >= 0; --ii) { + QQuickItem *child = children.at(ii); + if (!child->isEnabled()) + continue; + if (deliverTouchPoints(child, event, newPoints, acceptedNewPoints, updatedPoints)) + return true; + } + + QList<QTouchEvent::TouchPoint> matchingPoints; + if (newPoints.count() > 0 && acceptedNewPoints->count() < newPoints.count()) { + QRectF bounds(0, 0, item->width(), item->height()); + for (int i=0; i<newPoints.count(); i++) { + if (acceptedNewPoints->contains(newPoints[i].id())) + continue; + QPointF p = item->mapFromScene(newPoints[i].scenePos()); + if (bounds.contains(p)) + matchingPoints << newPoints[i]; + } + } + + if (matchingPoints.count() > 0 || (*updatedPoints)[item].count() > 0) { + QList<QTouchEvent::TouchPoint> &eventPoints = (*updatedPoints)[item]; + eventPoints.append(matchingPoints); + transformTouchPoints(eventPoints, itemPrivate->canvasToItemTransform()); + + Qt::TouchPointStates eventStates; + for (int i=0; i<eventPoints.count(); i++) + eventStates |= eventPoints[i].state(); + // if all points have the same state, set the event type accordingly + QEvent::Type eventType; + switch (eventStates) { + case Qt::TouchPointPressed: + eventType = QEvent::TouchBegin; + break; + case Qt::TouchPointReleased: + eventType = QEvent::TouchEnd; + break; + default: + eventType = QEvent::TouchUpdate; + break; + } + + if (eventStates != Qt::TouchPointStationary) { + QTouchEvent touchEvent(eventType); + // touchEvent.setWidget(q); // ### refactor: what is the consequence of not setting the widget? + touchEvent.setDeviceType(event->deviceType()); + touchEvent.setModifiers(event->modifiers()); + touchEvent.setTouchPointStates(eventStates); + touchEvent.setTouchPoints(eventPoints); + touchEvent.setTimestamp(event->timestamp()); + + touchEvent.accept(); + q->sendEvent(item, &touchEvent); + + if (touchEvent.isAccepted()) { + for (int i=0; i<matchingPoints.count(); i++) { + itemForTouchPointId[matchingPoints[i].id()] = item; + acceptedNewPoints->insert(matchingPoints[i].id()); + } + } + } + } + + updatedPoints->remove(item); + if (acceptedNewPoints->count() == newPoints.count() && updatedPoints->isEmpty()) + return true; + + return false; +} + +void QQuickCanvasPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QEvent *event) +{ + Q_Q(QQuickCanvas); + grabber->resetTarget(); + QQuickDragGrabber::iterator grabItem = grabber->begin(); + if (grabItem != grabber->end()) { + Q_ASSERT(event->type() != QEvent::DragEnter); + if (event->type() == QEvent::Drop) { + QDropEvent *e = static_cast<QDropEvent *>(event); + for (e->setAccepted(false); !e->isAccepted() && grabItem != grabber->end(); grabItem = grabber->release(grabItem)) { + QPointF p = (**grabItem)->mapFromScene(e->pos()); + QDropEvent translatedEvent( + p.toPoint(), + e->possibleActions(), + e->mimeData(), + e->mouseButtons(), + e->keyboardModifiers()); + QQuickDropEventEx::copyActions(&translatedEvent, *e); + q->sendEvent(**grabItem, &translatedEvent); + e->setAccepted(translatedEvent.isAccepted()); + e->setDropAction(translatedEvent.dropAction()); + grabber->setTarget(**grabItem); + } + } + if (event->type() != QEvent::DragMove) { // Either an accepted drop or a leave. + QDragLeaveEvent leaveEvent; + for (; grabItem != grabber->end(); grabItem = grabber->release(grabItem)) + q->sendEvent(**grabItem, &leaveEvent); + return; + } else for (; grabItem != grabber->end(); grabItem = grabber->release(grabItem)) { + QDragMoveEvent *moveEvent = static_cast<QDragMoveEvent *>(event); + if (deliverDragEvent(grabber, **grabItem, moveEvent)) { + moveEvent->setAccepted(true); + for (++grabItem; grabItem != grabber->end();) { + QPointF p = (**grabItem)->mapFromScene(moveEvent->pos()); + if (QRectF(0, 0, (**grabItem)->width(), (**grabItem)->height()).contains(p)) { + QDragMoveEvent translatedEvent( + p.toPoint(), + moveEvent->possibleActions(), + moveEvent->mimeData(), + moveEvent->mouseButtons(), + moveEvent->keyboardModifiers()); + QQuickDropEventEx::copyActions(&translatedEvent, *moveEvent); + q->sendEvent(**grabItem, &translatedEvent); + ++grabItem; + } else { + QDragLeaveEvent leaveEvent; + q->sendEvent(**grabItem, &leaveEvent); + grabItem = grabber->release(grabItem); + } + } + return; + } else { + QDragLeaveEvent leaveEvent; + q->sendEvent(**grabItem, &leaveEvent); + } + } + } + if (event->type() == QEvent::DragEnter || event->type() == QEvent::DragMove) { + QDragMoveEvent *e = static_cast<QDragMoveEvent *>(event); + QDragEnterEvent enterEvent( + e->pos(), + e->possibleActions(), + e->mimeData(), + e->mouseButtons(), + e->keyboardModifiers()); + QQuickDropEventEx::copyActions(&enterEvent, *e); + event->setAccepted(deliverDragEvent(grabber, rootItem, &enterEvent)); + } +} + +bool QQuickCanvasPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QQuickItem *item, QDragMoveEvent *event) +{ + Q_Q(QQuickCanvas); + bool accepted = false; + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + if (itemPrivate->opacity == 0.0 || !item->isVisible() || !item->isEnabled()) + return false; + + QPointF p = item->mapFromScene(event->pos()); + if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + if (event->type() == QEvent::DragMove || itemPrivate->flags & QQuickItem::ItemAcceptsDrops) { + QDragMoveEvent translatedEvent( + p.toPoint(), + event->possibleActions(), + event->mimeData(), + event->mouseButtons(), + event->keyboardModifiers(), + event->type()); + QQuickDropEventEx::copyActions(&translatedEvent, *event); + q->sendEvent(item, &translatedEvent); + if (event->type() == QEvent::DragEnter) { + if (translatedEvent.isAccepted()) { + grabber->grab(item); + accepted = true; + } + } else { + accepted = true; + } + } + } else if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { + return false; + } + + QDragEnterEvent enterEvent( + event->pos(), + event->possibleActions(), + event->mimeData(), + event->mouseButtons(), + event->keyboardModifiers()); + QQuickDropEventEx::copyActions(&enterEvent, *event); + QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); + for (int ii = children.count() - 1; ii >= 0; --ii) { + if (deliverDragEvent(grabber, children.at(ii), &enterEvent)) + return true; + } + + return accepted; +} + +bool QQuickCanvasPrivate::sendFilteredMouseEvent(QQuickItem *target, QQuickItem *item, QEvent *event) +{ + if (!target) + return false; + + QQuickItemPrivate *targetPrivate = QQuickItemPrivate::get(target); + if (targetPrivate->filtersChildMouseEvents) + if (target->childMouseEventFilter(item, event)) + return true; + + if (sendFilteredMouseEvent(target->parentItem(), item, event)) + return true; + + return false; +} + +/*! + Propagates an event to a QQuickItem on the canvas +*/ +bool QQuickCanvas::sendEvent(QQuickItem *item, QEvent *e) +{ + Q_D(QQuickCanvas); + + if (!item) { + qWarning("QQuickCanvas::sendEvent: Cannot send event to a null item"); + return false; + } + + Q_ASSERT(e); + + switch (e->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: + e->accept(); + QQuickItemPrivate::get(item)->deliverKeyEvent(static_cast<QKeyEvent *>(e)); + while (!e->isAccepted() && (item = item->parentItem())) { + e->accept(); + QQuickItemPrivate::get(item)->deliverKeyEvent(static_cast<QKeyEvent *>(e)); + } + break; + case QEvent::FocusIn: + case QEvent::FocusOut: + QQuickItemPrivate::get(item)->deliverFocusEvent(static_cast<QFocusEvent *>(e)); + break; + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + // XXX todo - should sendEvent be doing this? how does it relate to forwarded events? + if (!d->sendFilteredMouseEvent(item->parentItem(), item, e)) { + e->accept(); + QQuickItemPrivate::get(item)->deliverMouseEvent(static_cast<QMouseEvent *>(e)); + } + break; + case QEvent::Wheel: + QQuickItemPrivate::get(item)->deliverWheelEvent(static_cast<QWheelEvent *>(e)); + break; + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: + QQuickItemPrivate::get(item)->deliverHoverEvent(static_cast<QHoverEvent *>(e)); + break; + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + // XXX todo - should sendEvent be doing this? how does it relate to forwarded events? + if (!d->sendFilteredMouseEvent(item->parentItem(), item, e)) { + e->accept(); + QQuickItemPrivate::get(item)->deliverTouchEvent(static_cast<QTouchEvent *>(e)); + } + break; + case QEvent::DragEnter: + case QEvent::DragMove: + case QEvent::DragLeave: + case QEvent::Drop: + QQuickItemPrivate::get(item)->deliverDragEvent(e); + break; + default: + break; + } + + return false; +} + +void QQuickCanvasPrivate::cleanupNodes() +{ + for (int ii = 0; ii < cleanupNodeList.count(); ++ii) + delete cleanupNodeList.at(ii); + cleanupNodeList.clear(); +} + +void QQuickCanvasPrivate::cleanupNodesOnShutdown(QQuickItem *item) +{ + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + if (p->itemNodeInstance) { + delete p->itemNodeInstance; + p->itemNodeInstance = 0; + p->opacityNode = 0; + p->clipNode = 0; + p->groupNode = 0; + p->paintNode = 0; + } + + for (int ii = 0; ii < p->childItems.count(); ++ii) + cleanupNodesOnShutdown(p->childItems.at(ii)); +} + +// This must be called from the render thread, with the main thread frozen +void QQuickCanvasPrivate::cleanupNodesOnShutdown() +{ + cleanupNodes(); + + cleanupNodesOnShutdown(rootItem); +} + +void QQuickCanvasPrivate::updateDirtyNodes() +{ +#ifdef DIRTY_DEBUG + qWarning() << "QQuickCanvasPrivate::updateDirtyNodes():"; +#endif + + cleanupNodes(); + + QQuickItem *updateList = dirtyItemList; + dirtyItemList = 0; + if (updateList) QQuickItemPrivate::get(updateList)->prevDirtyItem = &updateList; + + while (updateList) { + QQuickItem *item = updateList; + QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(item); + itemPriv->removeFromDirtyList(); + +#ifdef DIRTY_DEBUG + qWarning() << " QSGNode:" << item << qPrintable(itemPriv->dirtyToString()); +#endif + updateDirtyNode(item); + } +} + +void QQuickCanvasPrivate::updateDirtyNode(QQuickItem *item) +{ +#ifdef QML_RUNTIME_TESTING + bool didFlash = false; +#endif + + QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(item); + quint32 dirty = itemPriv->dirtyAttributes; + itemPriv->dirtyAttributes = 0; + + if ((dirty & QQuickItemPrivate::TransformUpdateMask) || + (dirty & QQuickItemPrivate::Size && itemPriv->origin != QQuickItem::TopLeft && + (itemPriv->scale != 1. || itemPriv->rotation != 0.))) { + + QMatrix4x4 matrix; + + if (itemPriv->x != 0. || itemPriv->y != 0.) + matrix.translate(itemPriv->x, itemPriv->y); + + for (int ii = itemPriv->transforms.count() - 1; ii >= 0; --ii) + itemPriv->transforms.at(ii)->applyTo(&matrix); + + if (itemPriv->scale != 1. || itemPriv->rotation != 0.) { + QPointF origin = item->transformOriginPoint(); + matrix.translate(origin.x(), origin.y()); + if (itemPriv->scale != 1.) + matrix.scale(itemPriv->scale, itemPriv->scale); + if (itemPriv->rotation != 0.) + matrix.rotate(itemPriv->rotation, 0, 0, 1); + matrix.translate(-origin.x(), -origin.y()); + } + + itemPriv->itemNode()->setMatrix(matrix); + } + + bool clipEffectivelyChanged = dirty & QQuickItemPrivate::Clip && + ((item->clip() == false) != (itemPriv->clipNode == 0)); + bool effectRefEffectivelyChanged = dirty & QQuickItemPrivate::EffectReference && + ((itemPriv->effectRefCount == 0) != (itemPriv->rootNode == 0)); + + if (clipEffectivelyChanged) { + QSGNode *parent = itemPriv->opacityNode ? (QSGNode *) itemPriv->opacityNode : (QSGNode *)itemPriv->itemNode(); + QSGNode *child = itemPriv->rootNode ? (QSGNode *)itemPriv->rootNode : (QSGNode *)itemPriv->groupNode; + + if (item->clip()) { + Q_ASSERT(itemPriv->clipNode == 0); + itemPriv->clipNode = new QQuickDefaultClipNode(item->boundingRect()); + itemPriv->clipNode->update(); + + if (child) + parent->removeChildNode(child); + parent->appendChildNode(itemPriv->clipNode); + if (child) + itemPriv->clipNode->appendChildNode(child); + + } else { + Q_ASSERT(itemPriv->clipNode != 0); + parent->removeChildNode(itemPriv->clipNode); + if (child) + itemPriv->clipNode->removeChildNode(child); + delete itemPriv->clipNode; + itemPriv->clipNode = 0; + if (child) + parent->appendChildNode(child); + } + } + + if (dirty & QQuickItemPrivate::ChildrenUpdateMask) + itemPriv->childContainerNode()->removeAllChildNodes(); + + if (effectRefEffectivelyChanged) { + QSGNode *parent = itemPriv->clipNode; + if (!parent) + parent = itemPriv->opacityNode; + if (!parent) + parent = itemPriv->itemNode(); + QSGNode *child = itemPriv->groupNode; + + if (itemPriv->effectRefCount) { + Q_ASSERT(itemPriv->rootNode == 0); + itemPriv->rootNode = new QSGRootNode; + + if (child) + parent->removeChildNode(child); + parent->appendChildNode(itemPriv->rootNode); + if (child) + itemPriv->rootNode->appendChildNode(child); + } else { + Q_ASSERT(itemPriv->rootNode != 0); + parent->removeChildNode(itemPriv->rootNode); + if (child) + itemPriv->rootNode->removeChildNode(child); + delete itemPriv->rootNode; + itemPriv->rootNode = 0; + if (child) + parent->appendChildNode(child); + } + } + + if (dirty & QQuickItemPrivate::ChildrenUpdateMask) { + QSGNode *groupNode = itemPriv->groupNode; + if (groupNode) + groupNode->removeAllChildNodes(); + + QList<QQuickItem *> orderedChildren = itemPriv->paintOrderChildItems(); + int ii = 0; + + for (; ii < orderedChildren.count() && orderedChildren.at(ii)->z() < 0; ++ii) { + QQuickItemPrivate *childPrivate = QQuickItemPrivate::get(orderedChildren.at(ii)); + if (!childPrivate->explicitVisible && !childPrivate->effectRefCount) + continue; + if (childPrivate->itemNode()->parent()) + childPrivate->itemNode()->parent()->removeChildNode(childPrivate->itemNode()); + + itemPriv->childContainerNode()->appendChildNode(childPrivate->itemNode()); + } + itemPriv->beforePaintNode = itemPriv->groupNode ? itemPriv->groupNode->lastChild() : 0; + + if (itemPriv->paintNode) + itemPriv->childContainerNode()->appendChildNode(itemPriv->paintNode); + + for (; ii < orderedChildren.count(); ++ii) { + QQuickItemPrivate *childPrivate = QQuickItemPrivate::get(orderedChildren.at(ii)); + if (!childPrivate->explicitVisible && !childPrivate->effectRefCount) + continue; + if (childPrivate->itemNode()->parent()) + childPrivate->itemNode()->parent()->removeChildNode(childPrivate->itemNode()); + + itemPriv->childContainerNode()->appendChildNode(childPrivate->itemNode()); + } + } + + if ((dirty & QQuickItemPrivate::Size) && itemPriv->clipNode) { + itemPriv->clipNode->setRect(item->boundingRect()); + itemPriv->clipNode->update(); + } + + if (dirty & (QQuickItemPrivate::OpacityValue | QQuickItemPrivate::Visible | QQuickItemPrivate::HideReference)) { + qreal opacity = itemPriv->explicitVisible && itemPriv->hideRefCount == 0 + ? itemPriv->opacity : qreal(0); + + if (opacity != 1 && !itemPriv->opacityNode) { + itemPriv->opacityNode = new QSGOpacityNode; + + QSGNode *parent = itemPriv->itemNode(); + QSGNode *child = itemPriv->clipNode; + if (!child) + child = itemPriv->rootNode; + if (!child) + child = itemPriv->groupNode; + + if (child) + parent->removeChildNode(child); + parent->appendChildNode(itemPriv->opacityNode); + if (child) + itemPriv->opacityNode->appendChildNode(child); + } + if (itemPriv->opacityNode) + itemPriv->opacityNode->setOpacity(opacity); + } + + if (dirty & QQuickItemPrivate::ContentUpdateMask) { + + if (itemPriv->flags & QQuickItem::ItemHasContents) { + updatePaintNodeData.transformNode = itemPriv->itemNode(); + itemPriv->paintNode = item->updatePaintNode(itemPriv->paintNode, &updatePaintNodeData); + + Q_ASSERT(itemPriv->paintNode == 0 || + itemPriv->paintNode->parent() == 0 || + itemPriv->paintNode->parent() == itemPriv->childContainerNode()); + + if (itemPriv->paintNode && itemPriv->paintNode->parent() == 0) { + if (itemPriv->beforePaintNode) + itemPriv->childContainerNode()->insertChildNodeAfter(itemPriv->paintNode, itemPriv->beforePaintNode); + else + itemPriv->childContainerNode()->prependChildNode(itemPriv->paintNode); + } + } else if (itemPriv->paintNode) { + delete itemPriv->paintNode; + itemPriv->paintNode = 0; + } + } + +#ifndef QT_NO_DEBUG + // Check consistency. + const QSGNode *nodeChain[] = { + itemPriv->itemNodeInstance, + itemPriv->opacityNode, + itemPriv->clipNode, + itemPriv->rootNode, + itemPriv->groupNode, + itemPriv->paintNode, + }; + + int ip = 0; + for (;;) { + while (ip < 5 && nodeChain[ip] == 0) + ++ip; + if (ip == 5) + break; + int ic = ip + 1; + while (ic < 5 && nodeChain[ic] == 0) + ++ic; + const QSGNode *parent = nodeChain[ip]; + const QSGNode *child = nodeChain[ic]; + if (child == 0) { + Q_ASSERT(parent == itemPriv->groupNode || parent->childCount() == 0); + } else { + Q_ASSERT(parent == itemPriv->groupNode || parent->childCount() == 1); + Q_ASSERT(child->parent() == parent); + bool containsChild = false; + for (QSGNode *n = parent->firstChild(); n; n = n->nextSibling()) + containsChild |= (n == child); + Q_ASSERT(containsChild); + } + ip = ic; + } +#endif + +#ifdef QML_RUNTIME_TESTING + if (itemPriv->sceneGraphContext()->isFlashModeEnabled()) { + QSGFlashNode *flash = new QSGFlashNode(); + flash->setRect(item->boundingRect()); + itemPriv->childContainerNode()->appendChildNode(flash); + didFlash = true; + } + Q_Q(QQuickCanvas); + if (didFlash) { + q->maybeUpdate(); + } +#endif + +} + +void QQuickCanvas::maybeUpdate() +{ + Q_D(QQuickCanvas); + + if (d->thread && d->thread->isRunning()) + d->thread->maybeUpdate(); +} + +/*! + \fn void QSGEngine::sceneGraphInitialized(); + + This signal is emitted when the scene graph has been initialized. + + This signal will be emitted from the scene graph rendering thread. + */ + +/*! + Returns the QSGEngine used for this scene. + + The engine will only be available once the scene graph has been + initialized. Register for the sceneGraphEngine() signal to get + notification about this. + + \deprecated + */ + +QSGEngine *QQuickCanvas::sceneGraphEngine() const +{ + Q_D(const QQuickCanvas); + qWarning("QQuickCanvas::sceneGraphEngine() is deprecated, use members of QQuickCanvas instead"); + if (d->context && d->context->isReady()) + return d->engine; + return 0; +} + + + +/*! + Sets the render target for this canvas to be \a fbo. + + The specified fbo must be created in the context of the canvas + or one that shares with it. + + \warning + This function can only be called from the thread doing + the rendering. + */ + +void QQuickCanvas::setRenderTarget(QOpenGLFramebufferObject *fbo) +{ + Q_D(QQuickCanvas); + if (d->context && d->context && QThread::currentThread() != d->context->thread()) { + qWarning("QQuickCanvas::setRenderThread: Cannot set render target from outside the rendering thread"); + return; + } + + d->renderTarget = fbo; +} + + + +/*! + Returns the render target for this canvas. + + The default is to render to the surface of the canvas, in which + case the render target is 0. + */ +QOpenGLFramebufferObject *QQuickCanvas::renderTarget() const +{ + Q_D(const QQuickCanvas); + return d->renderTarget; +} + + +/*! + Grabs the contents of the framebuffer and returns it as an image. + + This function might not work if the view is not visible. + + \warning Calling this function will cause performance problems. + + \warning This function can only be called from the GUI thread. + */ +QImage QQuickCanvas::grabFrameBuffer() +{ + Q_D(QQuickCanvas); + return d->thread ? d->thread->grab() : QImage(); +} + +/*! + Returns an incubation controller that splices incubation between frames + for this canvas. QQuickView automatically installs this controller for you, + otherwise you will need to install it yourself using \l{QDeclarativeEngine::setIncubationController} + + The controller is owned by the canvas and will be destroyed when the canvas + is deleted. +*/ +QDeclarativeIncubationController *QQuickCanvas::incubationController() const +{ + Q_D(const QQuickCanvas); + + if (!d->incubationController) + d->incubationController = new QQuickCanvasIncubationController(const_cast<QQuickCanvasPrivate *>(d)); + return d->incubationController; +} + + + +/*! + \enum QQuickCanvas::CreateTextureOption + + The CreateTextureOption enums are used to customize a texture is wrapped. + + \value TextureHasAlphaChannel The texture has an alpha channel and should + be drawn using blending. + + \value TextureHasMipmaps The texture has mipmaps and can be drawn with + mipmapping enabled. + + \value TextureOwnsGLTexture The texture object owns the texture id and + will delete the GL texture when the texture object is deleted. + */ + +/*! + \fn void QQuickCanvas::beforeRendering() + + This signal is emitted before the scene starts rendering. + + Combined with the modes for clearing the background, this option + can be used to paint using raw GL under QML content. + + The GL context used for rendering the scene graph will be bound + at this point. + + Since this signal is emitted from the scene graph rendering thread, the receiver should + be on the scene graph thread or the connection should be Qt::DirectConnection. + +*/ + +/*! + \fn void QQuickCanvas::afterRendering() + + This signal is emitted after the scene has completed rendering, before swapbuffers is called. + + This signal can be used to paint using raw GL on top of QML content, + or to do screen scraping of the current frame buffer. + + The GL context used for rendering the scene graph will be bound at this point. + + Since this signal is emitted from the scene graph rendering thread, the receiver should + be on the scene graph thread or the connection should be Qt::DirectConnection. + */ + + + +/*! + Sets weither the scene graph rendering of QML should clear the color buffer + before it starts rendering to \a enbled. + + By disabling clearing of the color buffer, it is possible to do GL painting + under the scene graph. + + The color buffer is cleared by default. + + \sa beforeRendering() + */ + +void QQuickCanvas::setClearBeforeRendering(bool enabled) +{ + Q_D(QQuickCanvas); + d->clearBeforeRendering = enabled; +} + + + +/*! + Returns weither clearing of the color buffer is done before rendering or not. + */ + +bool QQuickCanvas::clearBeforeRendering() const +{ + Q_D(const QQuickCanvas); + return d->clearBeforeRendering; +} + + + +/*! + Creates a new QSGTexture from the supplied \a image. If the image has an + alpha channel, the corresponding texture will have an alpha channel. + + The caller of the function is responsible for deleting the returned texture. + The actual GL texture will be deleted when the texture object is deleted. + + \warning This function will return 0 if the scene graph has not yet been + initialized. + + This function can be called both from the GUI thread and the rendering thread. + + \sa sceneGraphInitialized() + */ + +QSGTexture *QQuickCanvas::createTextureFromImage(const QImage &image) const +{ + Q_D(const QQuickCanvas); + if (d->context) + return d->context->createTexture(image); + else + return 0; +} + + + +/*! + Creates a new QSGTexture object from an existing GL texture \a id. + + The caller of the function is responsible for deleting the returned texture. + + Use \a options to customize the texture attributes. + + \warning This function will return 0 if the scenegraph has not yet been + initialized. + + \sa sceneGraphInitialized() + */ +QSGTexture *QQuickCanvas::createTextureFromId(uint id, const QSize &size, CreateTextureOptions options) const +{ + Q_D(const QQuickCanvas); + if (d->context) { + QSGPlainTexture *texture = new QSGPlainTexture(); + texture->setTextureId(id); + texture->setHasAlphaChannel(options & TextureHasAlphaChannel); + texture->setHasMipmaps(options & TextureHasMipmaps); + texture->setOwnsTexture(options & TextureOwnsGLTexture); + texture->setTextureSize(size); + return texture; + } + return 0; +} + + +/*! + Sets the color used to clear the opengl context to \a color. + + Setting the clear color has no effect when clearing is disabled. + + \sa setClearBeforeRendering() + */ + +void QQuickCanvas::setClearColor(const QColor &color) +{ + if (color == d_func()->clearColor) + return; + d_func()->clearColor = color; + emit clearColorChanged(color); +} + + + +/*! + Returns the color used to clear the opengl context. + */ + +QColor QQuickCanvas::clearColor() const +{ + return d_func()->clearColor; +} + + + +void QQuickCanvasRenderLoop::createGLContext() +{ + gl = new QOpenGLContext(); + gl->setFormat(renderer->requestedFormat()); + gl->create(); +} + +void QQuickCanvasRenderThread::run() +{ +#ifdef THREAD_DEBUG + qDebug("QML Rendering Thread Started"); +#endif + + if (!glContext()) { + createGLContext(); + makeCurrent(); + initializeSceneGraph(); + } else { + makeCurrent(); + } + + while (!shouldExit) { + lock(); + + bool sizeChanged = false; + isExternalUpdatePending = false; + + if (renderedSize != windowSize) { +#ifdef THREAD_DEBUG + printf(" RenderThread: window has changed size...\n"); +#endif + glViewport(0, 0, windowSize.width(), windowSize.height()); + sizeChanged = true; + } + +#ifdef THREAD_DEBUG + printf(" RenderThread: preparing to sync...\n"); +#endif + + if (!isGuiBlocked) { + isGuiBlockPending = true; + +#ifdef THREAD_DEBUG + printf(" RenderThread: aquired sync lock...\n"); +#endif + allowMainThreadProcessingFlag = false; + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); +#ifdef THREAD_DEBUG + printf(" RenderThread: going to sleep...\n"); +#endif + wait(); + + isGuiBlockPending = false; + } + +#ifdef THREAD_DEBUG + printf(" RenderThread: Doing locked sync\n"); +#endif +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) + threadTimer.start(); +#endif + inSync = true; + syncSceneGraph(); + inSync = false; + + // Wake GUI after sync to let it continue animating and event processing. + allowMainThreadProcessingFlag = true; + wake(); + unlock(); +#ifdef THREAD_DEBUG + printf(" RenderThread: sync done\n"); +#endif +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) + syncTime = threadTimer.elapsed(); +#endif + +#ifdef THREAD_DEBUG + printf(" RenderThread: rendering... %d x %d\n", windowSize.width(), windowSize.height()); +#endif + + renderSceneGraph(windowSize); +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) + renderTime = threadTimer.elapsed() - syncTime; +#endif + + // The content of the target buffer is undefined after swap() so grab needs + // to happen before swap(); + if (doGrab) { +#ifdef THREAD_DEBUG + printf(" RenderThread: doing a grab...\n"); +#endif + grabContent = qt_gl_read_framebuffer(windowSize, false, false); + doGrab = false; + } + +#ifdef THREAD_DEBUG + printf(" RenderThread: wait for swap...\n"); +#endif + + swapBuffers(); +#ifdef THREAD_DEBUG + printf(" RenderThread: swap complete...\n"); +#endif +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) { + swapTime = threadTimer.elapsed() - renderTime; + qDebug() << "- Breakdown of frame time: sync:" << syncTime + << "ms render:" << renderTime << "ms swap:" << swapTime + << "ms total:" << swapTime + renderTime << "ms"; + } +#endif + + lock(); + isPaintCompleted = true; + if (sizeChanged) + renderedSize = windowSize; + + // Wake the GUI thread now that rendering is complete, to signal that painting + // is done, resizing is done or grabbing is completed. For grabbing, we're + // signalling this much later than needed (we could have done it before swap) + // but we don't want to lock an extra time. + wake(); + + if (!animationRunning && !isExternalUpdatePending && !shouldExit && !doGrab) { +#ifdef THREAD_DEBUG + printf(" RenderThread: nothing to do, going to sleep...\n"); +#endif + isRenderBlocked = true; + wait(); + isRenderBlocked = false; + } + + unlock(); + + QCoreApplication::processEvents(); + + // Process any "deleteLater" objects... + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + } + +#ifdef THREAD_DEBUG + printf(" RenderThread: deleting all outstanding nodes\n"); +#endif + cleanupNodesOnShutdown(); + +#ifdef THREAD_DEBUG + printf(" RenderThread: render loop exited... Good Night!\n"); +#endif + + doneCurrent(); + + lock(); + hasExited = true; +#ifdef THREAD_DEBUG + printf(" RenderThread: waking GUI for final sleep..\n"); +#endif + wake(); + unlock(); + +#ifdef THREAD_DEBUG + printf(" RenderThread: All done...\n"); +#endif +} + + + +bool QQuickCanvasRenderThread::event(QEvent *e) +{ + Q_ASSERT(QThread::currentThread() == qApp->thread()); + + if (e->type() == QEvent::User) { + if (!syncAlreadyHappened) + sync(false); + + syncAlreadyHappened = false; + + if (animationRunning && animationDriver()) { +#ifdef THREAD_DEBUG + qDebug("GUI: Advancing animations...\n"); +#endif + + animationDriver()->advance(); + +#ifdef THREAD_DEBUG + qDebug("GUI: Animations advanced...\n"); +#endif + } + + return true; + } + + return QThread::event(e); +} + + + +void QQuickCanvasRenderThread::exhaustSyncEvent() +{ + if (isGuiBlockPending) { + sync(true); + syncAlreadyHappened = true; + } +} + + + +void QQuickCanvasRenderThread::sync(bool guiAlreadyLocked) +{ +#ifdef THREAD_DEBUG + printf("GUI: sync - %s\n", guiAlreadyLocked ? "outside event" : "inside event"); +#endif + if (!guiAlreadyLocked) + lockInGui(); + + renderThreadAwakened = false; + + polishItems(); + + wake(); + wait(); + + if (!guiAlreadyLocked) + unlockInGui(); +} + + + + +/*! + Acquires the mutex for the GUI thread. The function uses the isGuiBlocked + variable to keep track of how many recursion levels the gui is locked with. + We only actually acquire the mutex for the first level to avoid deadlocking + ourselves. + */ + +void QQuickCanvasRenderThread::lockInGui() +{ + // We must avoid recursive locking in the GUI thread, hence we + // only lock when we are the first one to try to block. + if (!isGuiBlocked) + lock(); + + isGuiBlocked++; + +#ifdef THREAD_DEBUG + printf("GUI: aquired lock... %d\n", isGuiBlocked); +#endif +} + + + +void QQuickCanvasRenderThread::unlockInGui() +{ +#ifdef THREAD_DEBUG + printf("GUI: releasing lock... %d\n", isGuiBlocked); +#endif + --isGuiBlocked; + if (!isGuiBlocked) + unlock(); +} + + + + +void QQuickCanvasRenderThread::animationStarted() +{ +#ifdef THREAD_DEBUG + printf("GUI: animationStarted()\n"); +#endif + + lockInGui(); + + animationRunning = true; + + if (isRenderBlocked) + wake(); + + unlockInGui(); +} + + + +void QQuickCanvasRenderThread::animationStopped() +{ +#ifdef THREAD_DEBUG + printf("GUI: animationStopped()...\n"); +#endif + + lockInGui(); + animationRunning = false; + unlockInGui(); +} + + +void QQuickCanvasRenderThread::paint() +{ +#ifdef THREAD_DEBUG + printf("GUI: paint called..\n"); +#endif + + lockInGui(); + exhaustSyncEvent(); + + isPaintCompleted = false; + while (isRunning() && !isPaintCompleted) { + if (isRenderBlocked) + wake(); + wait(); + } + unlockInGui(); +} + + + +void QQuickCanvasRenderThread::resize(const QSize &size) +{ +#ifdef THREAD_DEBUG + printf("GUI: Resize Event: %dx%d\n", size.width(), size.height()); +#endif + + if (!isRunning()) { + windowSize = size; + return; + } + + lockInGui(); + exhaustSyncEvent(); + + windowSize = size; + + while (isRunning() && renderedSize != windowSize) { + if (isRenderBlocked) + wake(); + wait(); + } + unlockInGui(); +} + + + +void QQuickCanvasRenderThread::startRendering() +{ +#ifdef THREAD_DEBUG + printf("GUI: Starting Render Thread\n"); +#endif + hasExited = false; + shouldExit = false; + isGuiBlocked = 0; + isGuiBlockPending = false; + start(); +} + + + +void QQuickCanvasRenderThread::stopRendering() +{ +#ifdef THREAD_DEBUG + printf("GUI: stopping render thread\n"); +#endif + + lockInGui(); + exhaustSyncEvent(); + shouldExit = true; + + if (isRenderBlocked) { +#ifdef THREAD_DEBUG + printf("GUI: waking up render thread\n"); +#endif + wake(); + } + + while (!hasExited) { +#ifdef THREAD_DEBUG + printf("GUI: waiting for render thread to have exited..\n"); +#endif + wait(); + } + + unlockInGui(); + +#ifdef THREAD_DEBUG + printf("GUI: waiting for render thread to terminate..\n"); +#endif + // Actually wait for the thread to terminate. Otherwise we can delete it + // too early and crash. + QThread::wait(); + +#ifdef THREAD_DEBUG + printf("GUI: thread has terminated and we're all good..\n"); +#endif + +} + + + +QImage QQuickCanvasRenderThread::grab() +{ + if (!isRunning()) + return QImage(); + + if (QThread::currentThread() != qApp->thread()) { + qWarning("QQuickCanvas::grabFrameBuffer: can only be called from the GUI thread"); + return QImage(); + } + +#ifdef THREAD_DEBUG + printf("GUI: doing a pixelwise grab..\n"); +#endif + + lockInGui(); + exhaustSyncEvent(); + + doGrab = true; + isPaintCompleted = false; + while (isRunning() && !isPaintCompleted) { + if (isRenderBlocked) + wake(); + wait(); + } + + QImage grabbed = grabContent; + grabContent = QImage(); + + unlockInGui(); + + return grabbed; +} + + + +void QQuickCanvasRenderThread::maybeUpdate() +{ + Q_ASSERT_X(QThread::currentThread() == QCoreApplication::instance()->thread() || inSync, + "QQuickCanvas::update", + "Function can only be called from GUI thread or during QQuickItem::updatePaintNode()"); + + if (inSync) { + isExternalUpdatePending = true; + + } else if (!renderThreadAwakened) { +#ifdef THREAD_DEBUG + printf("GUI: doing update...\n"); +#endif + renderThreadAwakened = true; + lockInGui(); + isExternalUpdatePending = true; + if (isRenderBlocked) + wake(); + unlockInGui(); + } +} + + +#include "moc_qquickcanvas.cpp" + +QT_END_NAMESPACE |