diff options
Diffstat (limited to 'src')
58 files changed, 1045 insertions, 273 deletions
diff --git a/src/imports/builtins/builtins.qmltypes b/src/imports/builtins/builtins.qmltypes index c2f8f5b521..5fb68d15d9 100644 --- a/src/imports/builtins/builtins.qmltypes +++ b/src/imports/builtins/builtins.qmltypes @@ -461,7 +461,10 @@ Module { "AA_CompressHighFrequencyEvents": 25, "AA_DontCheckOpenGLContextThreadAffinity": 26, "AA_DisableShaderDiskCache": 27, - "AA_AttributeCount": 28 + "AA_DontShowShortcutsInContextMenus": 28, + "AA_CompressTabletEvents": 29, + "AA_DisableWindowContextHelpButton": 30, + "AA_AttributeCount": 31 } } Enum { diff --git a/src/imports/qtquick2/plugins.qmltypes b/src/imports/qtquick2/plugins.qmltypes index d23d6cc311..0fce98a212 100644 --- a/src/imports/qtquick2/plugins.qmltypes +++ b/src/imports/qtquick2/plugins.qmltypes @@ -1808,6 +1808,7 @@ Module { Property { name: "wordSpacing"; type: "double" } Property { name: "hintingPreference"; type: "HintingPreference" } Property { name: "kerning"; type: "bool" } + Property { name: "preferShaping"; type: "bool" } Method { name: "toString"; type: "string" } } Component { @@ -3994,12 +3995,13 @@ Module { prototype: "QQuickImplicitSizeItem" exports: [ "QtQuick/Text 2.0", + "QtQuick/Text 2.10", "QtQuick/Text 2.2", "QtQuick/Text 2.3", "QtQuick/Text 2.6", "QtQuick/Text 2.9" ] - exportMetaObjectRevisions: [0, 2, 3, 6, 9] + exportMetaObjectRevisions: [0, 10, 2, 3, 6, 9] Enum { name: "HAlignment" values: { @@ -4185,12 +4187,13 @@ Module { exports: [ "QtQuick/TextEdit 2.0", "QtQuick/TextEdit 2.1", + "QtQuick/TextEdit 2.10", "QtQuick/TextEdit 2.2", "QtQuick/TextEdit 2.3", "QtQuick/TextEdit 2.6", "QtQuick/TextEdit 2.7" ] - exportMetaObjectRevisions: [0, 1, 2, 3, 6, 7] + exportMetaObjectRevisions: [0, 1, 10, 2, 3, 6, 7] Enum { name: "HAlignment" values: { @@ -4292,6 +4295,7 @@ Module { Property { name: "rightPadding"; revision: 6; type: "double" } Property { name: "bottomPadding"; revision: 6; type: "double" } Property { name: "preeditText"; revision: 7; type: "string"; isReadonly: true } + Property { name: "tabStopDistance"; revision: 10; type: "double" } Signal { name: "preeditTextChanged"; revision: 7 } Signal { name: "contentSizeChanged" } Signal { @@ -4374,6 +4378,11 @@ Module { Signal { name: "leftPaddingChanged"; revision: 6 } Signal { name: "rightPaddingChanged"; revision: 6 } Signal { name: "bottomPaddingChanged"; revision: 6 } + Signal { + name: "tabStopDistanceChanged" + revision: 10 + Parameter { name: "distance"; type: "double" } + } Method { name: "selectAll" } Method { name: "selectWord" } Method { diff --git a/src/imports/shapes/plugin.cpp b/src/imports/shapes/plugin.cpp index c509d28e33..e24826ee55 100644 --- a/src/imports/shapes/plugin.cpp +++ b/src/imports/shapes/plugin.cpp @@ -47,7 +47,7 @@ static void initResources() #ifdef QT_STATIC Q_INIT_RESOURCE(qmake_QtQuick_Shapes); #endif - Q_INIT_RESOURCE(shapes); + Q_INIT_RESOURCE(qtquickshapesplugin); } QT_BEGIN_NAMESPACE diff --git a/src/imports/shapes/plugins.qmltypes b/src/imports/shapes/plugins.qmltypes index b8a7c532e0..39aab42401 100644 --- a/src/imports/shapes/plugins.qmltypes +++ b/src/imports/shapes/plugins.qmltypes @@ -36,6 +36,17 @@ Module { Property { name: "vendorExtensionsEnabled"; type: "bool" } Property { name: "status"; type: "Status"; isReadonly: true } Property { name: "data"; type: "QObject"; isList: true; isReadonly: true } + Signal { name: "rendererChanged" } + } + Component { + name: "QQuickShapeConicalGradient" + defaultProperty: "stops" + prototype: "QQuickShapeGradient" + exports: ["QtQuick.Shapes/ConicalGradient 1.0"] + exportMetaObjectRevisions: [0] + Property { name: "centerX"; type: "double" } + Property { name: "centerY"; type: "double" } + Property { name: "angle"; type: "double" } } Component { name: "QQuickShapeGradient" @@ -114,4 +125,17 @@ Module { Property { name: "fillGradient"; type: "QQuickShapeGradient"; isPointer: true } Signal { name: "shapePathChanged" } } + Component { + name: "QQuickShapeRadialGradient" + defaultProperty: "stops" + prototype: "QQuickShapeGradient" + exports: ["QtQuick.Shapes/RadialGradient 1.0"] + exportMetaObjectRevisions: [0] + Property { name: "centerX"; type: "double" } + Property { name: "centerY"; type: "double" } + Property { name: "centerRadius"; type: "double" } + Property { name: "focalX"; type: "double" } + Property { name: "focalY"; type: "double" } + Property { name: "focalRadius"; type: "double" } + } } diff --git a/src/imports/shapes/qquickshape.cpp b/src/imports/shapes/qquickshape.cpp index 6a76743242..c749357cc5 100644 --- a/src/imports/shapes/qquickshape.cpp +++ b/src/imports/shapes/qquickshape.cpp @@ -46,9 +46,12 @@ #include <private/qquicksvgparser_p.h> #include <QtGui/private/qdrawhelper_p.h> #include <QOpenGLFunctions> +#include <QLoggingCategory> QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(QQSHAPE_LOG_TIME_DIRTY_SYNC, "qt.shape.time.sync") + /*! \qmlmodule QtQuick.Shapes 1.0 \title Qt Quick Shapes QML Types @@ -968,18 +971,26 @@ QSGNode *QQuickShapePrivate::createNode() return node; } -static void q_asyncShapeReady(void *data) +void QQuickShapePrivate::asyncShapeReady(void *data) { QQuickShapePrivate *self = static_cast<QQuickShapePrivate *>(data); self->setStatus(QQuickShape::Ready); + if (self->syncTimingActive) + qDebug("[Shape %p] [%d] [dirty=0x%x] async update took %lld ms", + self->q_func(), self->syncTimeCounter, self->syncTimingTotalDirty, self->syncTimer.elapsed()); } void QQuickShapePrivate::sync() { + syncTimingTotalDirty = 0; + syncTimingActive = QQSHAPE_LOG_TIME_DIRTY_SYNC().isDebugEnabled(); + if (syncTimingActive) + syncTimer.start(); + const bool useAsync = async && renderer->flags().testFlag(QQuickAbstractPathRenderer::SupportsAsync); if (useAsync) { setStatus(QQuickShape::Processing); - renderer->setAsyncCallback(q_asyncShapeReady, this); + renderer->setAsyncCallback(asyncShapeReady, this); } const int count = sp.count(); @@ -988,6 +999,7 @@ void QQuickShapePrivate::sync() for (int i = 0; i < count; ++i) { QQuickShapePath *p = sp[i]; int &dirty(QQuickShapePathPrivate::get(p)->dirty); + syncTimingTotalDirty |= dirty; if (dirty & QQuickShapePathPrivate::DirtyPath) renderer->setPath(i, p); @@ -1011,10 +1023,19 @@ void QQuickShapePrivate::sync() dirty = 0; } + if (syncTimingTotalDirty) + ++syncTimeCounter; + else + syncTimingActive = false; + renderer->endSync(useAsync); - if (!useAsync) + if (!useAsync) { setStatus(QQuickShape::Ready); + if (syncTimingActive) + qDebug("[Shape %p] [%d] [dirty=0x%x] update took %lld ms", + q_func(), syncTimeCounter, syncTimingTotalDirty, syncTimer.elapsed()); + } } // ***** gradient support ***** diff --git a/src/imports/shapes/qquickshape_p_p.h b/src/imports/shapes/qquickshape_p_p.h index bbe9a81d4a..f43831516a 100644 --- a/src/imports/shapes/qquickshape_p_p.h +++ b/src/imports/shapes/qquickshape_p_p.h @@ -56,6 +56,7 @@ #include <QPainterPath> #include <QColor> #include <QBrush> +#include <QElapsedTimer> #include <private/qopenglcontext_p.h> QT_BEGIN_NAMESPACE @@ -167,6 +168,8 @@ public: static QQuickShapePrivate *get(QQuickShape *item) { return item->d_func(); } + static void asyncShapeReady(void *data); + bool spChanged; QQuickShape::RendererType rendererType; bool async; @@ -174,6 +177,10 @@ public: QQuickAbstractPathRenderer *renderer; QVector<QQuickShapePath *> sp; bool enableVendorExts; + bool syncTimingActive = false; + int syncTimingTotalDirty; + int syncTimeCounter = 0; + QElapsedTimer syncTimer; }; #if QT_CONFIG(opengl) diff --git a/src/imports/shapes/shapes.qrc b/src/imports/shapes/qtquickshapesplugin.qrc index f139861693..f139861693 100644 --- a/src/imports/shapes/shapes.qrc +++ b/src/imports/shapes/qtquickshapesplugin.qrc diff --git a/src/imports/shapes/shapes.pro b/src/imports/shapes/shapes.pro index 60cc61e974..fee950a529 100644 --- a/src/imports/shapes/shapes.pro +++ b/src/imports/shapes/shapes.pro @@ -28,6 +28,6 @@ qtConfig(opengl) { qquickshapenvprrenderer.cpp } -RESOURCES += shapes.qrc +RESOURCES += qtquickshapesplugin.qrc load(qml_plugin) diff --git a/src/imports/testlib/TestCase.qml b/src/imports/testlib/TestCase.qml index 0e7e09c65c..4bcc95df89 100644 --- a/src/imports/testlib/TestCase.qml +++ b/src/imports/testlib/TestCase.qml @@ -39,7 +39,7 @@ import QtQuick 2.0 import QtQuick.Window 2.0 // used for qtest_verifyItem -import QtTest 1.1 +import QtTest 1.2 import "testlogger.js" as TestLogger import Qt.test.qtestroot 1.0 @@ -57,7 +57,7 @@ import Qt.test.qtestroot 1.0 \code import QtQuick 2.0 - import QtTest 1.0 + import QtTest 1.2 TestCase { name: "MathTests" @@ -108,7 +108,7 @@ import Qt.test.qtestroot 1.0 \code import QtQuick 2.0 - import QtTest 1.1 + import QtTest 1.2 TestCase { name: "DataTests" @@ -1212,6 +1212,26 @@ Item { } /*! + \since 5.10 + \qmlmethod TestCase::keySequence(keySequence) + + Simulates typing of \a keySequence. The key sequence can be set + to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts}, or + it can be described with a string containing a sequence of up to four key + presses. + + Each event shall be sent to the TestCase window or, in case of multiple windows, + to the current active window. See \l QGuiApplication::focusWindow() for more details. + + \sa keyPress(), keyRelease(), {GNU Emacs Style Key Sequences}, + {QtQuick::Shortcut::sequence}{Shortcut.sequence} + */ + function keySequence(keySequence) { + if (!qtest_events.keySequence(keySequence)) + qtest_fail("window not shown", 2) + } + + /*! \qmlmethod TestCase::mousePress(item, x = item.width / 2, y = item.height / 2, button = Qt.LeftButton, modifiers = Qt.NoModifier, delay = -1) Simulates pressing a mouse \a button with an optional \a modifier diff --git a/src/imports/testlib/main.cpp b/src/imports/testlib/main.cpp index 2dbe8e08dc..00e9592557 100644 --- a/src/imports/testlib/main.cpp +++ b/src/imports/testlib/main.cpp @@ -157,6 +157,7 @@ public: qmlRegisterType<QuickTestResult, 0>(uri,1,0,"TestResult"); qmlRegisterType<QuickTestResult, 1>(uri,1,1,"TestResult"); qmlRegisterType<QuickTestEvent>(uri,1,0,"TestEvent"); + qmlRegisterType<QuickTestEvent>(uri,1,2,"TestEvent"); qmlRegisterType<QuickTestUtil>(uri,1,0,"TestUtil"); qmlRegisterType<QQuickTouchEventSequence>(); } diff --git a/src/imports/testlib/plugins.qmltypes b/src/imports/testlib/plugins.qmltypes index 5d7ca51adc..7f3140d86b 100644 --- a/src/imports/testlib/plugins.qmltypes +++ b/src/imports/testlib/plugins.qmltypes @@ -7,7 +7,7 @@ import QtQuick.tooling 1.2 // 'qmlplugindump -nonrelocatable -noforceqtquick QtTest 1.2' Module { - dependencies: ["QtQuick 2.0"] + dependencies: ["QtQuick 2.0", "QtQuick.Window 2.0"] Component { name: "QQuickTouchEventSequence" prototype: "QObject" @@ -45,8 +45,8 @@ Module { Component { name: "QuickTestEvent" prototype: "QObject" - exports: ["QtTest/TestEvent 1.0"] - exportMetaObjectRevisions: [0] + exports: ["QtTest/TestEvent 1.0", "QtTest/TestEvent 1.2"] + exportMetaObjectRevisions: [0, 0] Property { name: "defaultMouseDelay"; type: "int"; isReadonly: true } Method { name: "keyPress" @@ -91,6 +91,12 @@ Module { Parameter { name: "delay"; type: "int" } } Method { + name: "keySequence" + revision: 2 + type: "bool" + Parameter { name: "keySequence"; type: "QVariant" } + } + Method { name: "mousePress" type: "bool" Parameter { name: "item"; type: "QObject"; isPointer: true } diff --git a/src/imports/window/plugins.qmltypes b/src/imports/window/plugins.qmltypes index cea2a910a7..4ae23a093e 100644 --- a/src/imports/window/plugins.qmltypes +++ b/src/imports/window/plugins.qmltypes @@ -4,7 +4,7 @@ import QtQuick.tooling 1.2 // It is used for QML tooling purposes only. // // This file was auto-generated by: -// 'qmlplugindump -nonrelocatable QtQuick.Window 2.3' +// 'qmlplugindump -nonrelocatable QtQuick.Window 2.10' Module { dependencies: ["QtQuick 2.8"] @@ -43,10 +43,16 @@ Module { Component { name: "QQuickScreenInfo" prototype: "QObject" - exports: ["QtQuick.Window/ScreenInfo 2.3"] + exports: [ + "QtQuick.Window/ScreenInfo 2.10", + "QtQuick.Window/ScreenInfo 2.3" + ] isCreatable: false - exportMetaObjectRevisions: [2] + exportMetaObjectRevisions: [10, 2] Property { name: "name"; type: "string"; isReadonly: true } + Property { name: "manufacturer"; revision: 10; type: "string"; isReadonly: true } + Property { name: "model"; revision: 10; type: "string"; isReadonly: true } + Property { name: "serialNumber"; revision: 10; type: "string"; isReadonly: true } Property { name: "width"; type: "int"; isReadonly: true } Property { name: "height"; type: "int"; isReadonly: true } Property { name: "desktopAvailableWidth"; type: "int"; isReadonly: true } @@ -58,6 +64,9 @@ Module { Property { name: "orientation"; type: "Qt::ScreenOrientation"; isReadonly: true } Property { name: "virtualX"; revision: 1; type: "int"; isReadonly: true } Property { name: "virtualY"; revision: 1; type: "int"; isReadonly: true } + Signal { name: "manufacturerChanged"; revision: 10 } + Signal { name: "modelChanged"; revision: 10 } + Signal { name: "serialNumberChanged"; revision: 10 } Signal { name: "desktopGeometryChanged" } Signal { name: "virtualXChanged"; revision: 1 } Signal { name: "virtualYChanged"; revision: 1 } @@ -74,6 +83,13 @@ Module { "ContextNotAvailable": 1 } } + Enum { + name: "TextRenderType" + values: { + "QtTextRendering": 0, + "NativeTextRendering": 1 + } + } Property { name: "data"; type: "QObject"; isList: true; isReadonly: true } Property { name: "color"; type: "QColor" } Property { name: "contentItem"; type: "QQuickItem"; isReadonly: true; isPointer: true } @@ -296,6 +312,17 @@ Module { Parameter { name: "arg"; type: "int" } } Method { + name: "setGeometry" + Parameter { name: "posx"; type: "int" } + Parameter { name: "posy"; type: "int" } + Parameter { name: "w"; type: "int" } + Parameter { name: "h"; type: "int" } + } + Method { + name: "setGeometry" + Parameter { name: "rect"; type: "QRect" } + } + Method { name: "setMinimumWidth" Parameter { name: "w"; type: "int" } } diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp index 182e4257f8..f191fe9679 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp @@ -337,6 +337,9 @@ void QQmlEngineDebugServiceImpl::buildObjectList(QDataStream &message, QQmlContext *ctxt, const QList<QPointer<QObject> > &instances) { + if (!ctxt->isValid()) + return; + QQmlContextData *p = QQmlContextData::get(ctxt); QString ctxtName = ctxt->objectName(); @@ -399,11 +402,8 @@ QQmlEngineDebugServiceImpl::objectData(QObject *object) } QQmlContext *context = qmlContext(object); - if (context) { - QQmlContextData *cdata = QQmlContextData::get(context); - if (cdata) - rv.idString = cdata->findObjectId(object); - } + if (context && context->isValid()) + rv.idString = QQmlContextData::get(context)->findObjectId(object); rv.objectName = object->objectName(); rv.objectId = QQmlDebugService::idForObject(object); @@ -564,14 +564,14 @@ void QQmlEngineDebugServiceImpl::processMessage(const QByteArray &message) QObject *object = QQmlDebugService::objectForId(objectId); QQmlContext *context = qmlContext(object); - if (!context) { + if (!context || !context->isValid()) { QQmlEngine *engine = qobject_cast<QQmlEngine *>( QQmlDebugService::objectForId(engineId)); if (engine && m_engines.contains(engine)) context = engine->rootContext(); } QVariant result; - if (context) { + if (context && context->isValid()) { QQmlExpression exprObj(context, object, expr); bool undefined = false; QVariant value = exprObj.evaluate(&undefined); @@ -632,7 +632,7 @@ bool QQmlEngineDebugServiceImpl::setBinding(int objectId, QObject *object = objectForId(objectId); QQmlContext *context = qmlContext(object); - if (object && context) { + if (object && context && context->isValid()) { QQmlProperty property(object, propertyName, context); if (property.isValid()) { @@ -677,7 +677,7 @@ bool QQmlEngineDebugServiceImpl::resetBinding(int objectId, const QString &prope QObject *object = objectForId(objectId); QQmlContext *context = qmlContext(object); - if (object && context) { + if (object && context && context->isValid()) { QStringRef parentPropertyRef(&propertyName); const int idx = parentPropertyRef.indexOf(QLatin1Char('.')); if (idx != -1) @@ -732,11 +732,9 @@ bool QQmlEngineDebugServiceImpl::setMethodBody(int objectId, const QString &meth { QObject *object = objectForId(objectId); QQmlContext *context = qmlContext(object); - if (!object || !context || !context->engine()) + if (!object || !context || !context->isValid()) return false; QQmlContextData *contextData = QQmlContextData::get(context); - if (!contextData) - return false; QQmlPropertyData dummy; QQmlPropertyData *prop = diff --git a/src/qml/debugger/qqmlprofiler_p.h b/src/qml/debugger/qqmlprofiler_p.h index f17b1a7528..ef38887303 100644 --- a/src/qml/debugger/qqmlprofiler_p.h +++ b/src/qml/debugger/qqmlprofiler_p.h @@ -290,14 +290,19 @@ public: // Add 1 to the ID, to make it different from the IDs the V4 profiler produces. The +1 makes // the pointer point into the middle of the QV4::Function. Thus it still points to valid // memory but we cannot accidentally create a duplicate key from another object. - quintptr locationId(id(function) + 1); + // If there is no function, use a static but valid address: The profiler itself. + quintptr locationId = function ? id(function) + 1 : id(this); m_data.append(QQmlProfilerData(m_timer.nsecsElapsed(), (1 << RangeStart | 1 << RangeLocation), Binding, locationId)); RefLocation &location = m_locations[locationId]; - if (!location.isValid()) - location = RefLocation(function); + if (!location.isValid()) { + if (function) + location = RefLocation(function); + else // Make it valid without actually providing a location + location.locationType = Binding; + } } // Have toByteArrays() construct another RangeData event from the same QString later. diff --git a/src/qml/doc/qtqml.qdocconf b/src/qml/doc/qtqml.qdocconf index 74b61fd6e1..6161760471 100644 --- a/src/qml/doc/qtqml.qdocconf +++ b/src/qml/doc/qtqml.qdocconf @@ -53,6 +53,8 @@ manifestmeta.thumbnail.names += "QtQml/Chapter 4*" \ "QtQml/Chapter 6*" \ "QtQml/C++ Extensions: *" +manifestmeta.highlighted.names = "QtQml/Writing QML Extensions with C++" + navigation.landingpage = "Qt QML" navigation.cppclassespage = "Qt QML C++ Classes" navigation.qmltypespage = "Qt QML QML Types" diff --git a/src/qml/doc/src/cppintegration/interactqmlfromcpp.qdoc b/src/qml/doc/src/cppintegration/interactqmlfromcpp.qdoc index 4dc32e3588..7c2ff703c6 100644 --- a/src/qml/doc/src/cppintegration/interactqmlfromcpp.qdoc +++ b/src/qml/doc/src/cppintegration/interactqmlfromcpp.qdoc @@ -114,17 +114,7 @@ multiple children with the same \c objectName. In this case, QObject::findChildren() can be used to find all children with a matching \c objectName. -\warning While it is possible to use C++ to access and manipulate QML objects -deep into the object tree, we recommend that you do not take this approach -outside of application testing and prototyping. One strength of QML and C++ -integration is the ability to implement the QML user interface separately -from the C++ logic and dataset backend, and this strategy breaks if the C++ -side reaches deep into the QML components to manipulate them directly. This -would make it difficult to, for example, swap a QML view component for -another view, if the new component was missing a required \c objectName. It -is better for the C++ implementation to know as little as possible about the -QML user interface implementation and the composition of the QML object tree. - +\include warning.qdocinc \section1 Accessing Members of a QML Object Type from C++ diff --git a/src/qml/doc/src/cppintegration/topic.qdoc b/src/qml/doc/src/cppintegration/topic.qdoc index da06c195dc..6b6e308edf 100644 --- a/src/qml/doc/src/cppintegration/topic.qdoc +++ b/src/qml/doc/src/cppintegration/topic.qdoc @@ -190,6 +190,8 @@ invoke their methods and receive their signal notifications. This is possible du all QML object types are implemented using QObject-derived classes, enabling the QML engine to dynamically load and introspect objects through the Qt meta object system. +\include warning.qdocinc + For more information on accessing QML objects from C++, see the documentation on \l{qtqml-cppintegration-interactqmlfromcpp.html}{Interacting with QML Objects from C++}. diff --git a/src/qml/doc/src/cppintegration/warning.qdocinc b/src/qml/doc/src/cppintegration/warning.qdocinc new file mode 100644 index 0000000000..a5da22b2a7 --- /dev/null +++ b/src/qml/doc/src/cppintegration/warning.qdocinc @@ -0,0 +1,6 @@ +\warning Although it is possible to access QML objects from C++ and manipulate +them, it is not the recommended approach, except for testing and prototyping +purposes. One of the strengths of QML and C++ integration is the ability to +implement UIs in QML separate from the C++ logic and dataset backend, and this +fails if the C++ side starts manipulating QML directly. Such an approach also +makes changing the QML UI difficult without affecting its C++ counterpart. diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 9ef47b2c2e..61345f05a8 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -897,6 +897,8 @@ void QQmlData::setQueuedForDeletion(QObject *object) if (ddata->ownContext) { Q_ASSERT(ddata->ownContext == ddata->context); ddata->context->emitDestruction(); + if (ddata->ownContext->contextObject == object) + ddata->ownContext->contextObject = nullptr; ddata->ownContext = 0; ddata->context = 0; } diff --git a/src/qml/qml/qqmlglobal_p.h b/src/qml/qml/qqmlglobal_p.h index a6c113f5a7..5c46da0ea4 100644 --- a/src/qml/qml/qqmlglobal_p.h +++ b/src/qml/qml/qqmlglobal_p.h @@ -193,16 +193,6 @@ do { \ return QObjectPrivate::get(sender)->isSignalConnected(signalIdx); \ } while (0) -struct QQmlGraphics_DerivedObject : public QObject -{ - void setParent_noEvent(QObject *parent) { - bool sce = d_ptr->sendChildEvents; - d_ptr->sendChildEvents = false; - setParent(parent); - d_ptr->sendChildEvents = sce; - } -}; - /*! Returns true if the case of \a fileName is equivalent to the file case of \a fileName on disk, and false otherwise. @@ -230,7 +220,11 @@ bool QQml_isFileCaseCorrect(const QString &fileName, int length = -1); */ inline void QQml_setParent_noEvent(QObject *object, QObject *parent) { - static_cast<QQmlGraphics_DerivedObject *>(object)->setParent_noEvent(parent); + QObjectPrivate *d_ptr = QObjectPrivate::get(object); + bool sce = d_ptr->sendChildEvents; + d_ptr->sendChildEvents = false; + object->setParent(parent); + d_ptr->sendChildEvents = sce; } class Q_QML_PRIVATE_EXPORT QQmlValueTypeProvider diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index a85166da65..a7cafa1a93 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -1231,10 +1231,12 @@ QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDataba stableRelativePath.replace(Backslash, Slash); // remove optional versioning in dot notation from uri - int lastSlash = stableRelativePath.lastIndexOf(Slash); - if (lastSlash >= 0) { - int versionDot = stableRelativePath.indexOf(Dot, lastSlash); - if (versionDot >= 0) + int versionDot = stableRelativePath.lastIndexOf(Dot); + if (versionDot >= 0) { + int nextSlash = stableRelativePath.indexOf(Slash, versionDot); + if (nextSlash >= 0) + stableRelativePath.remove(versionDot, nextSlash - versionDot); + else stableRelativePath = stableRelativePath.left(versionDot); } @@ -1539,7 +1541,7 @@ bool QQmlImportsPrivate::addFileImport(const QString& uri, const QString &prefix if (isImplicitImport) { for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin(); it != nameSpace->imports.constEnd(); ++it) { - if ((*it)->uri == importUri) { + if ((*it)->url == url) { (*it)->implicitlyImported = true; return true; } diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index 5836c85666..e696dc0362 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -272,8 +272,10 @@ void QQmlType::SingletonInstanceInfo::init(QQmlEngine *e) QQmlData::ensurePropertyCache(e, o); } else if (!url.isEmpty() && !qobjectApi(e)) { QQmlComponent component(e, url, QQmlComponent::PreferSynchronous); - QObject *o = component.create(); + QObject *o = component.beginCreate(e->rootContext()); setQObjectApi(e, o); + if (o) + component.completeCreate(); } } diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index 55e05afdaa..2b895994d2 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -317,7 +317,8 @@ Returns true if the status is WaitingForDependencies. */ bool QQmlDataBlob::isWaiting() const { - return status() == WaitingForDependencies; + return status() == WaitingForDependencies || + status() == ResolvingDependencies; } /*! @@ -609,6 +610,7 @@ The default implementation does nothing. */ void QQmlDataBlob::allDependenciesDone() { + m_data.setStatus(QQmlDataBlob::ResolvingDependencies); } /*! @@ -2408,7 +2410,15 @@ void QQmlTypeData::dataReceived(const SourceCodeData &data) void QQmlTypeData::initializeFromCachedUnit(const QQmlPrivate::CachedQmlUnit *unit) { m_document.reset(new QmlIR::Document(isDebugging())); - unit->loadIR(m_document.data(), unit); + if (unit->loadIR) { + // old code path for older generated code + unit->loadIR(m_document.data(), unit); + } else { + // new code path + QmlIR::IRLoader loader(unit->qmlData, m_document.data()); + loader.load(); + m_document->javaScriptCompilationUnit.adopt(unit->createCompilationUnit()); + } continueLoadFromIR(); } @@ -2500,6 +2510,8 @@ void QQmlTypeData::continueLoadFromIR() void QQmlTypeData::allDependenciesDone() { + QQmlTypeLoader::Blob::allDependenciesDone(); + if (!m_typesResolved) { // Check that all imports were resolved QList<QQmlError> errors; @@ -2619,6 +2631,10 @@ void QQmlTypeData::resolveTypes() if (ref.type.isCompositeSingleton()) { ref.typeData = typeLoader()->getType(ref.type.sourceUrl()); + if (ref.typeData->status() == QQmlDataBlob::ResolvingDependencies) { + // TODO: give an error message? If so, we should record and show the path of the cycle. + continue; + } addDependency(ref.typeData); ref.prefix = csRef.prefix; diff --git a/src/qml/qml/qqmltypeloader_p.h b/src/qml/qml/qqmltypeloader_p.h index 94552cd5a0..0ec9f9240e 100644 --- a/src/qml/qml/qqmltypeloader_p.h +++ b/src/qml/qml/qqmltypeloader_p.h @@ -98,6 +98,7 @@ public: Null, // Prior to QQmlTypeLoader::load() Loading, // Prior to data being received and dataReceived() being called WaitingForDependencies, // While there are outstanding addDependency()s + ResolvingDependencies, // While resolving outstanding dependencies, to detect cycles Complete, // Finished Error // Error }; diff --git a/src/qml/types/qqmldelegatemodel.cpp b/src/qml/types/qqmldelegatemodel.cpp index 967f89971d..2a1b11661e 100644 --- a/src/qml/types/qqmldelegatemodel.cpp +++ b/src/qml/types/qqmldelegatemodel.cpp @@ -1953,6 +1953,8 @@ void QQmlDelegateModelItem::destroyObject() Q_ASSERT(data); if (data->ownContext) { data->ownContext->clearContext(); + if (data->ownContext->contextObject == object) + data->ownContext->contextObject = nullptr; data->ownContext = 0; data->context = 0; } diff --git a/src/qmltest/quicktestevent.cpp b/src/qmltest/quicktestevent.cpp index 663d3c64e1..bf255e9cda 100644 --- a/src/qmltest/quicktestevent.cpp +++ b/src/qmltest/quicktestevent.cpp @@ -121,6 +121,23 @@ bool QuickTestEvent::keyClickChar(const QString &character, int modifiers, int d return true; } +// valueToKeySequence() is copied from qquickshortcut.cpp +static QKeySequence valueToKeySequence(const QVariant &value) +{ + if (value.type() == QVariant::Int) + return QKeySequence(static_cast<QKeySequence::StandardKey>(value.toInt())); + return QKeySequence::fromString(value.toString()); +} + +bool QuickTestEvent::keySequence(const QVariant &keySequence) +{ + QWindow *window = activeWindow(); + if (!window) + return false; + QTest::keySequence(window, valueToKeySequence(keySequence)); + return true; +} + namespace QtQuickTest { enum MouseAction { MousePress, MouseRelease, MouseClick, MouseDoubleClick, MouseMove, MouseDoubleClickSequence }; diff --git a/src/qmltest/quicktestevent_p.h b/src/qmltest/quicktestevent_p.h index 89065b8880..92477399bc 100644 --- a/src/qmltest/quicktestevent_p.h +++ b/src/qmltest/quicktestevent_p.h @@ -94,6 +94,8 @@ public Q_SLOTS: bool keyReleaseChar(const QString &character, int modifiers, int delay); bool keyClickChar(const QString &character, int modifiers, int delay); + Q_REVISION(2) bool keySequence(const QVariant &keySequence); + bool mousePress(QObject *item, qreal x, qreal y, int button, int modifiers, int delay); bool mouseRelease(QObject *item, qreal x, qreal y, int button, diff --git a/src/quick/doc/snippets/pointerHandlers/pointHandler.qml b/src/quick/doc/snippets/pointerHandlers/pointHandler.qml new file mode 100644 index 0000000000..262eb607b6 --- /dev/null +++ b/src/quick/doc/snippets/pointerHandlers/pointHandler.qml @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +//![0] +import QtQuick 2.10 +import QtQuick.Window 2.2 +import Qt.labs.handlers 1.0 + +Window { + width: 480 + height: 320 + visible: true + + Item { + id: glassPane + z: 10000 + anchors.fill: parent + + PointHandler { + id: handler + acceptedDevices: PointerDevice.TouchScreen | PointerDevice.TouchPad + target: Rectangle { + parent: glassPane + color: "red" + visible: handler.active + x: handler.point.position.x - width / 2 + y: handler.point.position.y - height / 2 + width: 20; height: width; radius: width / 2 + } + } + } +} +//![0] diff --git a/src/quick/doc/snippets/qml/layout-simple.qml b/src/quick/doc/snippets/qml/layout-simple.qml new file mode 100644 index 0000000000..6afdbe3ec9 --- /dev/null +++ b/src/quick/doc/snippets/qml/layout-simple.qml @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.9 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.2 + +//! [1] +Window { + RowLayout { + anchors.fill: parent + //! [spacing] + spacing: 6 + //! [spacing] + Rectangle { + color: 'azure' + Layout.preferredWidth: 100 + Layout.preferredHeight: 150 + } + Rectangle { + color: "plum" + Layout.fillWidth: true + Layout.fillHeight: true + } + } +} +//! [1] diff --git a/src/quick/doc/src/concepts/layouts/qtquicklayouts-overview.qdoc b/src/quick/doc/src/concepts/layouts/qtquicklayouts-overview.qdoc index 05e4465f2a..06ebe2d3d1 100644 --- a/src/quick/doc/src/concepts/layouts/qtquicklayouts-overview.qdoc +++ b/src/quick/doc/src/concepts/layouts/qtquicklayouts-overview.qdoc @@ -74,8 +74,31 @@ \endlist + \section1 A Simple Layout + + \snippet qml/layout-simple.qml 1 + + As the intention of using a layout is to rearrange its children whenever the layout changes + size, the application should make sure that the layout gets resized. In the above snippet the + RowLayout ensures that by specifying \c{anchors.fill: parent}. However, it can also be by other + means, such as directly specifying \l{Item::width}{width} and \l{Item::height}{height} + properties. In the same snippet, the \c azure Rectangle has a fixed size of \c{(100, 150)} + pixels, and the \c plum Rectangle will expand to occupy all the space it gets allocated. + + \note A layout is responsible for its children's geometry. You should + therefore not specify \l{Item::width}{width}, \l{Item::height}{height}, \l{Item::x}{x}, + \l{Item::y}{y} or any other properties that might influence those properties (such as + \l{Item::anchors}{anchors}) on those items. Otherwise there would be a conflict of interest, + and the result is undefined. This is also the case if the child item is a layout. Therefore, + only layouts with no parent layout can have \c{anchors.fill: parent}. + + All items in the layout will have 6 pixels of spacing between them: + + \snippet qml/layout-simple.qml spacing + + + \section2 Size Constraints - \section1 Size Constraints Since an item can be resized by its layout, the layout needs to know the \l{Layout::minimumWidth}{minimum}, \l{Layout::preferredWidth}{preferred}, and \l{Layout::maximumWidth}{maximum} sizes of all items where \l{Layout::fillWidth}{Layout.fillWidth} or diff --git a/src/quick/handlers/handlers.pri b/src/quick/handlers/handlers.pri index 9e32b9278c..8bd74d95da 100644 --- a/src/quick/handlers/handlers.pri +++ b/src/quick/handlers/handlers.pri @@ -5,6 +5,7 @@ HEADERS += \ $$PWD/qquickpinchhandler_p.h \ $$PWD/qquickpointerdevicehandler_p.h \ $$PWD/qquickpointerhandler_p.h \ + $$PWD/qquickpointhandler_p.h \ $$PWD/qquicksinglepointhandler_p.h \ $$PWD/qquicktaphandler_p.h \ @@ -15,6 +16,7 @@ SOURCES += \ $$PWD/qquickpinchhandler.cpp \ $$PWD/qquickpointerdevicehandler.cpp \ $$PWD/qquickpointerhandler.cpp \ + $$PWD/qquickpointhandler.cpp \ $$PWD/qquicksinglepointhandler.cpp \ $$PWD/qquicktaphandler.cpp \ diff --git a/src/quick/handlers/qquickdraghandler.cpp b/src/quick/handlers/qquickdraghandler.cpp index 12f1a29bca..d4a8f06db8 100644 --- a/src/quick/handlers/qquickdraghandler.cpp +++ b/src/quick/handlers/qquickdraghandler.cpp @@ -93,8 +93,9 @@ bool QQuickDragHandler::wantsEventPoint(QQuickEventPoint *point) void QQuickDragHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) { - if (grabber == this && stateChange == QQuickEventPoint::GrabExclusive) - // In case the grab got handled over from another grabber, we might not get the Press + if (grabber == this && stateChange == QQuickEventPoint::GrabExclusive && m_targetStartPos.isNull()) + // In case the grab got handled over from another grabber, we might not get the Press. + // Therefore, prefer the m_targetStartPos we got when it got Pressed. initializeTargetStartPos(point); enforceConstraints(); QQuickSinglePointHandler::onGrabChanged(grabber, stateChange, point); @@ -167,7 +168,7 @@ void QQuickDragHandler::enforceAxisConstraints(QPointF *localPos) void QQuickDragHandler::initializeTargetStartPos(QQuickEventPoint *point) { - if (target() && target()->parentItem() && m_targetStartPos.isNull()) { // prefer the m_targetStartPos we got when it got Pressed. + if (target() && target()->parentItem()) { m_targetStartPos = target()->parentItem()->mapToScene(target()->position()); if (!target()->contains(point->position())) { // If pressed outside of target item, move the target item so that the touchpoint is in its center, diff --git a/src/quick/handlers/qquickhandlersmodule.cpp b/src/quick/handlers/qquickhandlersmodule.cpp index 8472c6a062..c9e08fe4f2 100644 --- a/src/quick/handlers/qquickhandlersmodule.cpp +++ b/src/quick/handlers/qquickhandlersmodule.cpp @@ -41,6 +41,7 @@ #include "qquickpointerhandler_p.h" #include "qquickdraghandler_p.h" #include "qquickpinchhandler_p.h" +#include "qquickpointhandler_p.h" #include "qquicktaphandler_p.h" static void initResources() @@ -82,6 +83,7 @@ static void qt_quickhandlers_defineModule(const char *uri, int major, int minor) qmlRegisterUncreatableType<QQuickPointerHandler>(uri,major,minor,"PointerHandler", QQuickPointerHandler::tr("PointerHandler is an abstract base class")); + qmlRegisterType<QQuickPointHandler>(uri,major,minor,"PointHandler"); qmlRegisterType<QQuickDragHandler>(uri,major,minor,"DragHandler"); qmlRegisterUncreatableType<QQuickDragAxis>(uri, major, minor, "DragAxis", QQuickDragHandler::tr("DragAxis is only available as a grouped property of DragHandler")); diff --git a/src/quick/handlers/qquickmultipointhandler.cpp b/src/quick/handlers/qquickmultipointhandler.cpp index ff4914394c..b126b93211 100644 --- a/src/quick/handlers/qquickmultipointhandler.cpp +++ b/src/quick/handlers/qquickmultipointhandler.cpp @@ -298,17 +298,18 @@ void QQuickMultiPointHandler::acceptPoints(const QVector<QQuickEventPoint *> &po bool QQuickMultiPointHandler::grabPoints(QVector<QQuickEventPoint *> points) { - bool canGrab = true; + bool allowed = true; for (QQuickEventPoint* point : points) { - auto grabber = point->grabberItem(); - if (grabber && (grabber->keepMouseGrab() || grabber->keepTouchGrab())) - canGrab = false; + if (!canGrab(point)) { + allowed = false; + break; + } } - if (canGrab) { + if (allowed) { for (QQuickEventPoint* point : points) setExclusiveGrab(point); } - return canGrab; + return allowed; } QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp index 84c4e912d1..155822197f 100644 --- a/src/quick/handlers/qquickpinchhandler.cpp +++ b/src/quick/handlers/qquickpinchhandler.cpp @@ -248,8 +248,8 @@ bool QQuickPinchHandler::wantsPointerEvent(QQuickPointerEvent *event) if (!QQuickMultiPointHandler::wantsPointerEvent(event)) return false; - if (minimumPointCount() == 2) { - if (const auto gesture = event->asPointerNativeGestureEvent()) { + if (const auto gesture = event->asPointerNativeGestureEvent()) { + if (minimumPointCount() == 2) { switch (gesture->type()) { case Qt::BeginNativeGesture: case Qt::EndNativeGesture: @@ -259,6 +259,8 @@ bool QQuickPinchHandler::wantsPointerEvent(QQuickPointerEvent *event) default: return false; } + } else { + return false; } } @@ -349,13 +351,18 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) } } else { bool containsReleasedPoints = event->isReleaseEvent(); - if (!active() && !containsReleasedPoints) { + if (!active()) { // Verify that at least one of the points has moved beyond threshold needed to activate the handler for (QQuickEventPoint *point : qAsConst(m_currentPoints)) { - if (QQuickWindowPrivate::dragOverThreshold(point)) { - if (grabPoints(m_currentPoints)) - setActive(true); + if (!containsReleasedPoints && QQuickWindowPrivate::dragOverThreshold(point) && grabPoints(m_currentPoints)) { + setActive(true); break; + } else { + setPassiveGrab(point); + } + if (point->state() == QQuickEventPoint::Pressed) { + point->setAccepted(false); // don't stop propagation + setPassiveGrab(point); } } if (!active()) diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp index faebdf3621..64bf1a8a8b 100644 --- a/src/quick/handlers/qquickpointerhandler.cpp +++ b/src/quick/handlers/qquickpointerhandler.cpp @@ -42,6 +42,7 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcPointerHandlerDispatch, "qt.quick.handler.dispatch") +Q_LOGGING_CATEGORY(lcPointerHandlerGrab, "qt.quick.handler.grab") Q_LOGGING_CATEGORY(lcPointerHandlerActive, "qt.quick.handler.active") /*! @@ -67,6 +68,7 @@ QQuickPointerHandler::QQuickPointerHandler(QObject *parent) , m_targetExplicitlySet(false) , m_hadKeepMouseGrab(false) , m_hadKeepTouchGrab(false) + , m_grabPermissions(CanTakeOverFromItems | CanTakeOverFromHandlersOfDifferentType | ApprovesTakeOverByAnything) { } @@ -94,7 +96,7 @@ QQuickPointerHandler::~QQuickPointerHandler() */ void QQuickPointerHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) { - qCDebug(lcPointerHandlerDispatch) << point << stateChange << grabber; + qCDebug(lcPointerHandlerGrab) << point << stateChange << grabber; Q_ASSERT(point); if (grabber == this) { bool wasCanceled = false; @@ -145,7 +147,7 @@ void QQuickPointerHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEv */ void QQuickPointerHandler::setPassiveGrab(QQuickEventPoint *point, bool grab) { - qCDebug(lcPointerHandlerDispatch) << point << grab; + qCDebug(lcPointerHandlerGrab) << point << grab; if (grab) { point->setGrabberPointerHandler(this, false); } else { @@ -153,14 +155,124 @@ void QQuickPointerHandler::setPassiveGrab(QQuickEventPoint *point, bool grab) } } -void QQuickPointerHandler::setExclusiveGrab(QQuickEventPoint *point, bool grab) +/*! + Check whether it's OK to take an exclusive grab of the \a point. + + The default implementation will call approveGrabTransition() to check this + handler's \l grabPermissions. If grabbing can be done only by taking over + the exclusive grab from an Item, approveGrabTransition() checks the Item's + \l keepMouseGrab or \l keepTouchGrab flags appropriately. If grabbing can + be done only by taking over another handler's exclusive grab, canGrab() + also calls approveGrabTransition() on the handler which is about to lose + its grab. Either one can deny the takeover. +*/ +bool QQuickPointerHandler::canGrab(QQuickEventPoint *point) { - // TODO m_hadKeepMouseGrab m_hadKeepTouchGrab - qCDebug(lcPointerHandlerDispatch) << point << grab; - // Don't allow one handler to cancel another's grab, unless it is stealing it for itself - if (!grab && point->grabberPointerHandler() != this) + QQuickPointerHandler *existingPhGrabber = point->grabberPointerHandler(); + return approveGrabTransition(point, this) && + (existingPhGrabber ? existingPhGrabber->approveGrabTransition(point, this) : true); +} + +/*! + Check this handler's rules to see if \l proposedGrabber will be allowed to take + the exclusive grab. This function may be called twice: once on the instance which + will take the grab, and once on the instance which would thereby lose its grab, + in case of a takeover scenario. +*/ +bool QQuickPointerHandler::approveGrabTransition(QQuickEventPoint *point, QObject *proposedGrabber) +{ + bool allowed = false; + if (proposedGrabber == this) { + QObject* existingGrabber = point->exclusiveGrabber(); + allowed = (existingGrabber == nullptr) || ((m_grabPermissions & CanTakeOverFromAnything) == CanTakeOverFromAnything); + if (existingGrabber) { + if (QQuickPointerHandler *existingPhGrabber = point->grabberPointerHandler()) { + if (!allowed && (m_grabPermissions & CanTakeOverFromHandlersOfDifferentType) && + existingPhGrabber->metaObject()->className() != metaObject()->className()) + allowed = true; + if (!allowed && (m_grabPermissions & CanTakeOverFromHandlersOfSameType) && + existingPhGrabber->metaObject()->className() == metaObject()->className()) + allowed = true; + } else if ((m_grabPermissions & CanTakeOverFromItems)) { + QQuickItem * existingItemGrabber = point->grabberItem(); + if (existingItemGrabber && !((existingItemGrabber->keepMouseGrab() && point->pointerEvent()->asPointerMouseEvent()) || + (existingItemGrabber->keepTouchGrab() && point->pointerEvent()->asPointerTouchEvent()))) + allowed = true; + } + } + } else { + // proposedGrabber is different: that means this instance will lose its grab + if (proposedGrabber) { + if ((m_grabPermissions & ApprovesTakeOverByAnything) == ApprovesTakeOverByAnything) + allowed = true; + if (!allowed && (m_grabPermissions & ApprovesTakeOverByHandlersOfDifferentType) && + proposedGrabber->metaObject()->className() != metaObject()->className()) + allowed = true; + if (!allowed && (m_grabPermissions & ApprovesTakeOverByHandlersOfSameType) && + proposedGrabber->metaObject()->className() == metaObject()->className()) + allowed = true; + if (!allowed && (m_grabPermissions & ApprovesTakeOverByItems) && proposedGrabber->inherits("QQuickItem")) + allowed = true; + } else { + if (!allowed && (m_grabPermissions & ApprovesCancellation)) + allowed = true; + } + } + qCDebug(lcPointerHandlerGrab) << "point" << hex << point->pointId() << "permission" << + QMetaEnum::fromType<GrabPermissions>().valueToKeys(grabPermissions()) << + ':' << this << (allowed ? "approved to" : "denied to") << proposedGrabber; + return allowed; +} + +/*! + \qmlproperty bool QtQuick::PointerHandler::grabPermission + + This property specifies the permissions when this handler's logic decides + to take over the exclusive grab, or when it is asked to approve grab + takeover or cancellation by another handler. + + The default is + \c {CanTakeOverFromItems | CanTakeOverFromHandlersOfDifferentType | ApprovesTakeOverByAnything} + which allows most takeover scenarios but avoids e.g. two PinchHandlers fighting + over the same touchpoints. +*/ +void QQuickPointerHandler::setGrabPermissions(GrabPermissions grabPermission) +{ + if (m_grabPermissions == grabPermission) return; - point->setGrabberPointerHandler(grab ? this : nullptr, true); + + m_grabPermissions = grabPermission; + emit grabPermissionChanged(); +} + +/*! + \internal + Acquire or give up the exclusive grab of the given \a point, according to + the \a grab state, and subject to the rules: canGrab(), and the rule not to + relinquish another handler's grab. Returns true if permission is granted, + or if the exclusive grab has already been acquired or relinquished as + specified. Returns false if permission is denied either by this handler or + by the handler or item from which this handler would take over +*/ +bool QQuickPointerHandler::setExclusiveGrab(QQuickEventPoint *point, bool grab) +{ + if ((grab && point->exclusiveGrabber() == this) || (!grab && point->exclusiveGrabber() != this)) + return true; + // TODO m_hadKeepMouseGrab m_hadKeepTouchGrab + bool allowed = true; + if (grab) { + allowed = canGrab(point); + } else { + QQuickPointerHandler *existingPhGrabber = point->grabberPointerHandler(); + // Ask before allowing one handler to cancel another's grab + if (existingPhGrabber && existingPhGrabber != this && !existingPhGrabber->approveGrabTransition(point, nullptr)) + allowed = false; + } + qCDebug(lcPointerHandlerGrab) << point << (grab ? "grab" : "ungrab") << (allowed ? "allowed" : "forbidden") << + point->exclusiveGrabber() << "->" << (grab ? this : nullptr); + if (allowed) + point->setGrabberPointerHandler(grab ? this : nullptr, true); + return allowed; } /*! @@ -169,7 +281,7 @@ void QQuickPointerHandler::setExclusiveGrab(QQuickEventPoint *point, bool grab) */ void QQuickPointerHandler::cancelAllGrabs(QQuickEventPoint *point) { - qCDebug(lcPointerHandlerDispatch) << point; + qCDebug(lcPointerHandlerGrab) << point; point->cancelAllGrabs(this); } diff --git a/src/quick/handlers/qquickpointerhandler_p.h b/src/quick/handlers/qquickpointerhandler_p.h index 24a058275d..9a77dd714a 100644 --- a/src/quick/handlers/qquickpointerhandler_p.h +++ b/src/quick/handlers/qquickpointerhandler_p.h @@ -67,11 +67,27 @@ class Q_QUICK_PRIVATE_EXPORT QQuickPointerHandler : public QObject Q_PROPERTY(bool active READ active NOTIFY activeChanged) Q_PROPERTY(QQuickItem * target READ target WRITE setTarget NOTIFY targetChanged) Q_PROPERTY(QQuickItem * parent READ parentItem CONSTANT) + Q_PROPERTY(GrabPermissions grabPermissions READ grabPermissions WRITE setGrabPermissions NOTIFY grabPermissionChanged) public: explicit QQuickPointerHandler(QObject *parent = 0); virtual ~QQuickPointerHandler(); + enum GrabPermission { + TakeOverForbidden = 0x0, + CanTakeOverFromHandlersOfSameType = 0x01, + CanTakeOverFromHandlersOfDifferentType= 0x02, + CanTakeOverFromItems = 0x04, + CanTakeOverFromAnything = 0x0F, + ApprovesTakeOverByHandlersOfSameType = 0x10, + ApprovesTakeOverByHandlersOfDifferentType= 0x20, + ApprovesTakeOverByItems = 0x40, + ApprovesCancellation = 0x80, + ApprovesTakeOverByAnything = 0xF0 + }; + Q_DECLARE_FLAGS(GrabPermissions, GrabPermission) + Q_FLAG(GrabPermissions) + public: bool enabled() const { return m_enabled; } void setEnabled(bool enabled); @@ -85,11 +101,15 @@ public: void handlePointerEvent(QQuickPointerEvent *event); + GrabPermissions grabPermissions() const { return static_cast<GrabPermissions>(m_grabPermissions); } + void setGrabPermissions(GrabPermissions grabPermissions); + Q_SIGNALS: void enabledChanged(); void activeChanged(); void targetChanged(); void grabChanged(QQuickEventPoint *point); + void grabPermissionChanged(); void canceled(QQuickEventPoint *point); protected: @@ -99,8 +119,10 @@ protected: void setActive(bool active); virtual void onActiveChanged() { } virtual void onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point); + virtual bool canGrab(QQuickEventPoint *point); + virtual bool approveGrabTransition(QQuickEventPoint *point, QObject *proposedGrabber); void setPassiveGrab(QQuickEventPoint *point, bool grab = true); - void setExclusiveGrab(QQuickEventPoint *point, bool grab = true); + bool setExclusiveGrab(QQuickEventPoint *point, bool grab = true); void cancelAllGrabs(QQuickEventPoint *point); QPointF eventPos(const QQuickEventPoint *point) const; bool parentContains(const QQuickEventPoint *point) const; @@ -113,11 +135,15 @@ private: bool m_targetExplicitlySet : 1; bool m_hadKeepMouseGrab : 1; // some handlers override target()->setKeepMouseGrab(); this remembers previous state bool m_hadKeepTouchGrab : 1; // some handlers override target()->setKeepTouchGrab(); this remembers previous state + uint m_reserved : 19; + uint8_t m_grabPermissions : 8; friend class QQuickEventPoint; friend class QQuickWindowPrivate; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickPointerHandler::GrabPermissions) + QT_END_NAMESPACE QML_DECLARE_TYPE(QQuickPointerHandler) diff --git a/src/quick/handlers/qquickpointhandler.cpp b/src/quick/handlers/qquickpointhandler.cpp new file mode 100644 index 0000000000..ed03685252 --- /dev/null +++ b/src/quick/handlers/qquickpointhandler.cpp @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 "qquickpointhandler_p.h" +#include <private/qquickwindow_p.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype PointHandler + \instantiates QQuickPointHandler + \inherits SinglePointHandler + \inqmlmodule Qt.labs.handlers + \ingroup qtquick-handlers + \brief Handler for reacting to a single touchpoint. + + PointHandler can be used to show feedback about a touchpoint or the mouse + position, or to otherwise react to pointer events. + + When a press event occurs, each instance of PointHandler chooses a single + point which is not yet "taken" at that moment: if the press occurs within + the bounds of the \l parent, and no sibling PointHandler within the same + \l parent has yet acquired a passive grab on that point, and if the other + constraints such as \l acceptedMouseButtons, \l acceptedDevices etc. are + satisfied, it's eligible, and the PointHandler then acquires a passive + grab. In this way, the \l parent acts like an exclusive group: there can be + multiple instances of PointHandler, and the set of pressed touchpoints will + be distributed among them. Each PointHandler which has chosen a point to + track has its \l active property \c true. It then continues to track its + chosen point until release: the properties of the \l point will be kept + up-to-date. Any Item can bind to these properties, and thereby follow the + point's movements. + + By being only a passive grabber, it has the ability to keep independent + oversight of all movements. The passive grab cannot be stolen or overridden + even when other gestures are detected and exclusive grabs occur. + + If your goal is orthogonal surveillance of eventpoints, an older + alternative was QObject::installEventFilter(), but that has never been a + built-in QtQuick feature: it requires some C++ code, such as a QQuickItem + subclass. PointHandler is more efficient than that, because only pointer + events will be delivered to it, during the course of normal event delivery + in QQuickWindow; whereas an event filter needs to filter all QEvents of all + types, and thus sets itself up as a potential event delivery bottleneck. + + One possible use case is to add this handler to a transparent Item which is + on top of the rest of the scene (by having a high \l z value), so that when + a point is freshly pressed, it will be delivered to that Item and its + handlers first, providing the opportunity to take the passive grab as early + as possible. Such an item (like a pane of glass over the whole UI) can be a + convenient parent for other Items which visualize the kind of reactive + feedback which must always be on top; and likewise it can be the parent for + popups, popovers, dialogs and so on. If it will be used in that way, it can + be helpful for your main.cpp to use QQmlContext::setContextProperty() to + make the "glass pane" accessible by ID to the entire UI, so that other + Items and PointHandlers can be reparented to it. + + \snippet pointerHandlers/pointHandler.qml 0 + + Like all pointer handlers, a PointHandler has a \l target property, which + may be used as a convenient place to put a point-tracking Item; but + PointHandler will not automatically manipulate the \c target item in any way. + You need to use bindings to make it react to the \l point. + + \note On macOS, PointHandler does not react to the trackpad by default. + That is because macOS can provide either native gesture recognition, or raw + touchpoints, but not both. We prefer to use the native gesture event in + PinchHandler, so we do not want to disable it by enabling touch. However + MultiPointTouchArea does enable touch, thus disabling native gesture + recognition within the entire window; so it's an alternative if you only + want to react to all the touchpoints but do not require the smooth + native-gesture experience. + + \sa MultiPointTouchArea +*/ + +QQuickPointHandler::QQuickPointHandler(QObject *parent) + : QQuickSinglePointHandler(parent) +{ + setIgnoreAdditionalPoints(); +} + +QQuickPointHandler::~QQuickPointHandler() +{ +} + +bool QQuickPointHandler::wantsEventPoint(QQuickEventPoint *pt) +{ + // On press, we want it unless a sibling of the same type also does. + if (pt->state() == QQuickEventPoint::Pressed && QQuickSinglePointHandler::wantsEventPoint(pt)) { + for (const QQuickPointerHandler *grabber : pt->passiveGrabbers()) { + if (grabber && grabber->parent() == parent() && + grabber->metaObject()->className() == metaObject()->className()) + return false; + } + return true; + } + // If we've already been interested in a point, stay interested, even if it has strayed outside bounds. + return (pt->state() != QQuickEventPoint::Pressed && point().id() == pt->pointId()); +} + +void QQuickPointHandler::handleEventPoint(QQuickEventPoint *point) +{ + switch (point->state()) { + case QQuickEventPoint::Pressed: + setPassiveGrab(point); + setActive(true); + break; + case QQuickEventPoint::Released: + setActive(false); + break; + default: + break; + } + point->setAccepted(false); // Just lurking... don't interfere with propagation + emit translationChanged(); +} + +QVector2D QQuickPointHandler::translation() const +{ + return QVector2D(point().position() - point().pressPosition()); +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickpointhandler_p.h b/src/quick/handlers/qquickpointhandler_p.h new file mode 100644 index 0000000000..5babab0c4d --- /dev/null +++ b/src/quick/handlers/qquickpointhandler_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#ifndef QQUICKPONTHANDLER_H +#define QQUICKPONTHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquicksinglepointhandler_p.h" + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickPointHandler : public QQuickSinglePointHandler +{ + Q_OBJECT + Q_PROPERTY(QVector2D translation READ translation NOTIFY translationChanged) + +public: + explicit QQuickPointHandler(QObject *parent = 0); + ~QQuickPointHandler(); + + QVector2D translation() const; + +Q_SIGNALS: + void translationChanged(); + +protected: + bool wantsEventPoint(QQuickEventPoint *pt) override; + void handleEventPoint(QQuickEventPoint *point) override; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPointHandler) + +#endif // QQUICKPONTHANDLER_H diff --git a/src/quick/handlers/qquicktaphandler.cpp b/src/quick/handlers/qquicktaphandler.cpp index e5b728db0f..8313b415bf 100644 --- a/src/quick/handlers/qquicktaphandler.cpp +++ b/src/quick/handlers/qquicktaphandler.cpp @@ -301,6 +301,8 @@ void QQuickTapHandler::setPressed(bool press, bool cancel, QQuickEventPoint *poi // on release, ungrab after emitting changed signals setExclusiveGrab(point, press); } + if (cancel) + emit canceled(point); } } diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp index 8653d758de..4a786d5569 100644 --- a/src/quick/items/qquickevents.cpp +++ b/src/quick/items/qquickevents.cpp @@ -790,17 +790,19 @@ QQuickItem *QQuickEventPoint::grabberItem() const void QQuickEventPoint::setGrabberItem(QQuickItem *grabber) { if (grabber != m_exclusiveGrabber.data()) { + QQuickPointerHandler *oldGrabberHandler = grabberPointerHandler(); + if (oldGrabberHandler && !oldGrabberHandler->approveGrabTransition(this, grabber)) + return; if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) << ": grab" << m_exclusiveGrabber << "->" << grabber; } - QQuickPointerHandler *oldGrabberHandler = grabberPointerHandler(); - QQuickItem *oldGrabberItem = grabberItem(); m_exclusiveGrabber = QPointer<QObject>(grabber); m_grabberIsHandler = false; m_sceneGrabPos = m_scenePos; + QQuickItem *oldGrabberItem = grabberItem(); if (oldGrabberHandler) - oldGrabberHandler->onGrabChanged(oldGrabberHandler, CancelGrabExclusive, this); + oldGrabberHandler->onGrabChanged(oldGrabberHandler, (grabber ? CancelGrabExclusive : UngrabExclusive), this); else if (oldGrabberItem && oldGrabberItem != grabber && grabber && pointerEvent()->asPointerTouchEvent()) oldGrabberItem->touchUngrabEvent(); for (QPointer<QQuickPointerHandler> passiveGrabber : m_passiveGrabbers) @@ -837,26 +839,24 @@ void QQuickEventPoint::setGrabberPointerHandler(QQuickPointerHandler *grabber, b } if (exclusive) { if (grabber != m_exclusiveGrabber.data()) { + QQuickPointerHandler *oldGrabberHandler = grabberPointerHandler(); + QQuickItem *oldGrabberItem = grabberItem(); + m_exclusiveGrabber = QPointer<QObject>(grabber); + m_grabberIsHandler = true; + m_sceneGrabPos = m_scenePos; if (grabber) { - // set variables before notifying the new grabber - m_exclusiveGrabber = QPointer<QObject>(grabber); - m_grabberIsHandler = true; - m_sceneGrabPos = m_scenePos; grabber->onGrabChanged(grabber, GrabExclusive, this); for (QPointer<QQuickPointerHandler> passiveGrabber : m_passiveGrabbers) { if (passiveGrabber != grabber) passiveGrabber->onGrabChanged(grabber, OverrideGrabPassive, this); } - } else if (QQuickPointerHandler *oldGrabberPointerHandler = qmlobject_cast<QQuickPointerHandler *>(m_exclusiveGrabber.data())) { - oldGrabberPointerHandler->onGrabChanged(oldGrabberPointerHandler, UngrabExclusive, this); - } else if (!m_exclusiveGrabber.isNull()) { - // If there is a previous grabber and it's not a PointerHandler, it must be an Item. - QQuickItem *oldGrabberItem = static_cast<QQuickItem *>(m_exclusiveGrabber.data()); - // If this point came from a touchscreen, notify that previous grabber Item that it's losing its touch grab. - if (pointerEvent()->asPointerTouchEvent()) - oldGrabberItem->touchUngrabEvent(); } - // set variables after notifying the old grabber + if (oldGrabberHandler) + oldGrabberHandler->onGrabChanged(oldGrabberHandler, (grabber ? CancelGrabExclusive : UngrabExclusive), this); + else if (oldGrabberItem && pointerEvent()->asPointerTouchEvent()) + oldGrabberItem->touchUngrabEvent(); + // touchUngrabEvent() can result in the grabber being set to null (MPTA does that, for example). + // So set it again to ensure that final state is what we want. m_exclusiveGrabber = QPointer<QObject>(grabber); m_grabberIsHandler = true; m_sceneGrabPos = m_scenePos; diff --git a/src/quick/items/qquickevents_p_p.h b/src/quick/items/qquickevents_p_p.h index 09a63febdc..af5857cf63 100644 --- a/src/quick/items/qquickevents_p_p.h +++ b/src/quick/items/qquickevents_p_p.h @@ -306,7 +306,6 @@ public: void setExclusiveGrabber(QObject *exclusiveGrabber); QQuickItem *grabberItem() const; - Q_DECL_DEPRECATED QQuickItem *grabber() const { return grabberItem(); } void setGrabberItem(QQuickItem *exclusiveGrabber); QQuickPointerHandler *grabberPointerHandler() const; diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index cd918cef5f..bec5f06d72 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -725,7 +725,19 @@ QQuickFlickable::~QQuickFlickable() These properties hold the surface coordinate currently at the top-left corner of the Flickable. For example, if you flick an image up 100 pixels, - \c contentY will be 100. + \c contentY will increase by 100. + + \note If you flick back to the origin (the top-left corner), after the + rebound animation, \c contentX will settle to the same value as \c originX, + and \c contentY to \c originY. These are usually (0,0), however ListView + and GridView may have an arbitrary origin due to delegate size variation, + or item insertion/removal outside the visible region. So if you want to + implement something like a vertical scrollbar, one way is to use + \c {y: (contentY - originY) * (height / contentHeight)} + for the position; another way is to use the normalized values in + \l {QtQuick::Flickable::visibleArea}{visibleArea}. + + \sa originX, originY */ qreal QQuickFlickable::contentX() const { @@ -739,7 +751,8 @@ void QQuickFlickable::setContentX(qreal pos) d->hData.explicitValue = true; d->resetTimeline(d->hData); d->hData.vTime = d->timeline.time(); - movementEnding(true, false); + if (isMoving() || isFlicking()) + movementEnding(true, false); if (-pos != d->hData.move.value()) d->hData.move.setValue(-pos); } @@ -756,7 +769,8 @@ void QQuickFlickable::setContentY(qreal pos) d->vData.explicitValue = true; d->resetTimeline(d->vData); d->vData.vTime = d->timeline.time(); - movementEnding(false, true); + if (isMoving() || isFlicking()) + movementEnding(false, true); if (-pos != d->vData.move.value()) d->vData.move.setValue(-pos); } @@ -2150,6 +2164,8 @@ void QQuickFlickable::setRightMargin(qreal m) This is usually (0,0), however ListView and GridView may have an arbitrary origin due to delegate size variation, or item insertion/removal outside the visible region. + + \sa contentX, contentY */ qreal QQuickFlickable::originY() const @@ -2180,25 +2196,25 @@ qreal QQuickFlickable::originX() const void QQuickFlickable::resizeContent(qreal w, qreal h, QPointF center) { Q_D(QQuickFlickable); - if (w != d->hData.viewSize) { - qreal oldSize = d->hData.viewSize; - d->hData.viewSize = w; - d->contentItem->setWidth(w); + const qreal oldHSize = d->hData.viewSize; + const qreal oldVSize = d->vData.viewSize; + const bool needToUpdateWidth = w != oldHSize; + const bool needToUpdateHeight = h != oldVSize; + d->hData.viewSize = w; + d->vData.viewSize = h; + d->contentItem->setSize(QSizeF(w, h)); + if (needToUpdateWidth) emit contentWidthChanged(); - if (center.x() != 0) { - qreal pos = center.x() * w / oldSize; - setContentX(contentX() + pos - center.x()); - } - } - if (h != d->vData.viewSize) { - qreal oldSize = d->vData.viewSize; - d->vData.viewSize = h; - d->contentItem->setHeight(h); + if (needToUpdateHeight) emit contentHeightChanged(); - if (center.y() != 0) { - qreal pos = center.y() * h / oldSize; - setContentY(contentY() + pos - center.y()); - } + + if (center.x() != 0) { + qreal pos = center.x() * w / oldHSize; + setContentX(contentX() + pos - center.x()); + } + if (center.y() != 0) { + qreal pos = center.y() * h / oldVSize; + setContentY(contentY() + pos - center.y()); } d->updateBeginningEnd(); } diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index f8374bdbc2..2009d76677 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -3253,7 +3253,7 @@ void QQuickItemPrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o) // because there can be multiple handlers... that->setAcceptedMouseButtons(Qt::AllButtons); QQuickItemPrivate *p = QQuickItemPrivate::get(that); - p->extra.value().pointerHandlers.append(pointerHandler); + p->extra.value().pointerHandlers.prepend(pointerHandler); } else { QQuickWindow *thisWindow = qmlobject_cast<QQuickWindow *>(o); QQuickItem *item = that; diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp index 1d0d042839..c203f389ae 100644 --- a/src/quick/items/qquickitemview.cpp +++ b/src/quick/items/qquickitemview.cpp @@ -926,7 +926,6 @@ void QQuickItemView::setDisplacedTransition(QQuickTransition *transition) void QQuickItemViewPrivate::positionViewAtIndex(int index, int mode) { - Q_Q(QQuickItemView); if (!isValid()) return; if (mode < QQuickItemView::Beginning || mode > QQuickItemView::SnapPosition) @@ -953,11 +952,16 @@ void QQuickItemViewPrivate::positionViewAtIndex(int index, int mode) item = visibleItem(idx); } if (item) { + const bool stickyHeader = hasStickyHeader(); + const bool stickyFooter = hasStickyFooter(); + const qreal stickyHeaderSize = stickyHeader ? headerSize() : 0; + const qreal stickyFooterSize = stickyFooter ? footerSize() : 0; + const qreal itemPos = item->position(); switch (mode) { case QQuickItemView::Beginning: pos = itemPos; - if (header && (index < 0 || hasStickyHeader())) + if (header && (index < 0 || stickyHeader)) pos -= headerSize(); break; case QQuickItemView::Center: @@ -965,30 +969,29 @@ void QQuickItemViewPrivate::positionViewAtIndex(int index, int mode) break; case QQuickItemView::End: pos = itemPos - viewSize + item->size(); - if (footer && (index >= modelCount || hasStickyFooter())) + if (footer && (index >= modelCount || stickyFooter)) pos += footerSize(); break; case QQuickItemView::Visible: - if (itemPos > pos + viewSize) - pos = itemPos - viewSize + item->size(); - else if (item->endPosition() <= pos) - pos = itemPos; + if (itemPos > pos + viewSize - stickyFooterSize) + pos = item->endPosition() - viewSize + stickyFooterSize; + else if (item->endPosition() <= pos - stickyHeaderSize) + pos = itemPos - stickyHeaderSize; break; case QQuickItemView::Contain: - if (item->endPosition() >= pos + viewSize) - pos = itemPos - viewSize + item->size(); - if (itemPos < pos) - pos = itemPos; + if (item->endPosition() >= pos + viewSize + stickyFooterSize) + pos = itemPos - viewSize + item->size() + stickyFooterSize; + if (itemPos - stickyHeaderSize < pos) + pos = itemPos - stickyHeaderSize; break; case QQuickItemView::SnapPosition: - pos = itemPos - highlightRangeStart; + pos = itemPos - highlightRangeStart - stickyHeaderSize; break; } pos = qMin(pos, maxExtent); qreal minExtent = calculatedMinExtent(); pos = qMax(pos, minExtent); moveReason = QQuickItemViewPrivate::Other; - q->cancelFlick(); setPosition(pos); if (highlight) { @@ -1246,16 +1249,26 @@ void QQuickItemViewPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometry void QQuickItemView::destroyRemoved() { Q_D(QQuickItemView); + + bool hasRemoveTransition = false; + bool hasRemoveTransitionAsTarget = false; + if (d->transitioner) { + hasRemoveTransition = d->transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, false); + hasRemoveTransitionAsTarget = d->transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, true); + } + for (QList<FxViewItem*>::Iterator it = d->visibleItems.begin(); it != d->visibleItems.end();) { FxViewItem *item = *it; if (item->index == -1 && (!item->attached || item->attached->delayRemove() == false)) { - if (d->transitioner && d->transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, true)) { + if (hasRemoveTransitionAsTarget) { // don't remove from visibleItems until next layout() d->runDelayedRemoveTransition = true; QObject::disconnect(item->attached, SIGNAL(delayRemoveChanged()), this, SLOT(destroyRemoved())); ++it; } else { + if (hasRemoveTransition) + d->runDelayedRemoveTransition = true; d->releaseItem(item); it = d->visibleItems.erase(it); } @@ -1384,7 +1397,6 @@ void QQuickItemView::trackedPositionChanged() pos = qMax(trackedPos, toItemPos); } if (viewPos != pos) { - cancelFlick(); d->calcVelocity = true; d->setPosition(pos); d->calcVelocity = false; @@ -1934,8 +1946,9 @@ void QQuickItemViewPrivate::layout() if (transitioner) { // items added in the last refill() may need to be transitioned in - e.g. a remove // causes items to slide up into view - if (transitioner->canTransition(QQuickItemViewTransitioner::MoveTransition, false) - || transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, false)) { + if (lastIndexInView != -1 && + (transitioner->canTransition(QQuickItemViewTransitioner::MoveTransition, false) + || transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, false))) { translateAndTransitionItemsAfter(lastIndexInView, insertionPosChanges, removalPosChanges); } @@ -1978,7 +1991,6 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult } updateUnrequestedIndexes(); - moveReason = QQuickItemViewPrivate::Other; FxViewItem *prevVisibleItemsFirst = visibleItems.count() ? *visibleItems.constBegin() : 0; int prevItemCount = itemCount; diff --git a/src/quick/items/qquickopenglshadereffect.cpp b/src/quick/items/qquickopenglshadereffect.cpp index c3e0ba05bd..94a5b6a646 100644 --- a/src/quick/items/qquickopenglshadereffect.cpp +++ b/src/quick/items/qquickopenglshadereffect.cpp @@ -896,7 +896,7 @@ QSGNode *QQuickOpenGLShaderEffect::handleUpdatePaintNode(QSGNode *oldNode, QQuic bool geometryUsesTextureSubRect = false; if (m_supportsAtlasTextures && material->textureProviders.size() == 1) { QSGTextureProvider *provider = material->textureProviders.at(0); - if (provider->texture()) { + if (provider && provider->texture()) { srcRect = provider->texture()->normalizedTextureSubRect(); geometryUsesTextureSubRect = true; } diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 20a37e3dd9..436adf8c49 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -762,19 +762,18 @@ void QQuickWindowPrivate::setMouseGrabber(QQuickItem *grabber) auto point = pointerEventInstance(touchMouseDevice)->pointById(touchMouseId); if (point) { auto originalEvent = pointerEventInstance(point->pointerEvent()->device()); - for (int i = 0; i < originalEvent->pointCount(); ++i) - originalEvent->point(i)->cancelExclusiveGrab(); + for (int i = 0; i < originalEvent->pointCount(); ++i) { + QQuickEventPoint *pt = originalEvent->point(i); + if (pt->exclusiveGrabber()) + pt->cancelExclusiveGrab(); + } point->setGrabberItem(grabber); - for (auto handler : point->passiveGrabbers()) - point->cancelPassiveGrab(handler); } } else { QQuickPointerEvent *event = pointerEventInstance(QQuickPointerDevice::genericMouseDevice()); Q_ASSERT(event->pointCount() == 1); auto point = event->point(0); point->setGrabberItem(grabber); - for (auto handler : point->passiveGrabbers()) - point->cancelPassiveGrab(handler); } @@ -840,11 +839,12 @@ void QQuickWindowPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool to bool ungrab = false; const auto touchDevices = QQuickPointerDevice::touchDevices(); for (auto device : touchDevices) { - auto pointerEvent = pointerEventInstance(device); - for (int i = 0; i < pointerEvent->pointCount(); ++i) { - if (pointerEvent->point(i)->exclusiveGrabber() == grabber) { - pointerEvent->point(i)->setGrabberItem(nullptr); - ungrab = true; + if (auto pointerEvent = queryPointerEventInstance(device)) { + for (int i = 0; i < pointerEvent->pointCount(); ++i) { + if (pointerEvent->point(i)->exclusiveGrabber() == grabber) { + pointerEvent->point(i)->setGrabberItem(nullptr); + ungrab = true; + } } } } @@ -1317,6 +1317,8 @@ QQuickWindow::~QQuickWindow() delete d->dragGrabber; d->dragGrabber = 0; #endif delete d->contentItem; d->contentItem = 0; + qDeleteAll(d->pointerEventInstances); + d->pointerEventInstances.clear(); d->renderJobMutex.lock(); qDeleteAll(d->beforeSynchronizingJobs); @@ -1518,14 +1520,15 @@ QQuickItem *QQuickWindow::mouseGrabberItem() const Q_D(const QQuickWindow); if (d->touchMouseId != -1 && d->touchMouseDevice) { - QQuickPointerEvent *event = d->pointerEventInstance(d->touchMouseDevice); - auto point = event->pointById(d->touchMouseId); - return point ? point->grabberItem() : nullptr; + if (QQuickPointerEvent *event = d->queryPointerEventInstance(d->touchMouseDevice)) { + auto point = event->pointById(d->touchMouseId); + return point ? point->grabberItem() : nullptr; + } + } else if (QQuickPointerEvent *event = d->queryPointerEventInstance(QQuickPointerDevice::genericMouseDevice())) { + Q_ASSERT(event->pointCount()); + return event->point(0)->grabberItem(); } - - QQuickPointerEvent *event = d->pointerEventInstance(QQuickPointerDevice::genericMouseDevice()); - Q_ASSERT(event->pointCount()); - return event->point(0)->grabberItem(); + return nullptr; } @@ -1699,8 +1702,10 @@ void QQuickWindowPrivate::deliverToPassiveGrabbers(const QVector<QPointer <QQuic alreadyFiltered = sendFilteredPointerEvent(pointerEvent, par); sendFilteredPointerEventResult << qMakePair<QQuickItem*, bool>(par, alreadyFiltered); } - if (!alreadyFiltered) + if (!alreadyFiltered) { + pointerEvent->localize(handler->parentItem()); handler->handlePointerEvent(pointerEvent); + } } } } @@ -1715,24 +1720,27 @@ void QQuickWindowPrivate::deliverMouseEvent(QQuickPointerMouseEvent *pointerEven if (point->exclusiveGrabber()) { if (auto grabber = point->grabberItem()) { + bool handled = false; if (sendFilteredPointerEvent(pointerEvent, grabber)) - return; + handled = true; // if the grabber is an Item: // if the update consists of changing button state, don't accept it unless // the button is one in which the grabber is interested Qt::MouseButtons acceptedButtons = grabber->acceptedMouseButtons(); - if (pointerEvent->button() != Qt::NoButton && acceptedButtons + if (!handled && pointerEvent->button() != Qt::NoButton && acceptedButtons && !(acceptedButtons & pointerEvent->button())) { pointerEvent->setAccepted(false); - return; + handled = true; } // send update - QPointF localPos = grabber->mapFromScene(lastMousePosition); - auto me = pointerEvent->asMouseEvent(localPos); - me->accept(); - QCoreApplication::sendEvent(grabber, me); - point->setAccepted(me->isAccepted()); + if (!handled) { + QPointF localPos = grabber->mapFromScene(lastMousePosition); + auto me = pointerEvent->asMouseEvent(localPos); + me->accept(); + QCoreApplication::sendEvent(grabber, me); + point->setAccepted(me->isAccepted()); + } // release event: ungrab if no buttons are pressed anymore if (mouseIsReleased) @@ -1746,6 +1754,7 @@ void QQuickWindowPrivate::deliverMouseEvent(QQuickPointerMouseEvent *pointerEven if (mouseIsReleased) point->setGrabberPointerHandler(nullptr, true); } + deliverToPassiveGrabbers(point->passiveGrabbers(), pointerEvent); } else { bool delivered = false; if (pointerEvent->isPressEvent()) { @@ -1937,7 +1946,8 @@ bool QQuickWindowPrivate::deliverNativeGestureEvent(QQuickItem *item, QNativeGes { QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); - if ((itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) && !item->contains(event->localPos())) + QPointF p = item->mapFromScene(event->windowPos()); + if ((itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) && !item->contains(p)) return false; QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); @@ -1960,7 +1970,6 @@ bool QQuickWindowPrivate::deliverNativeGestureEvent(QQuickItem *item, QNativeGes } // If still not accepted, try direct delivery to the item - QPointF p = item->mapFromScene(event->localPos()); if (item->contains(p)) { QNativeGestureEvent copy(event->gestureType(), event->device(), p, event->windowPos(), event->screenPos(), event->value(), 0L, 0L); // TODO can't copy things I can't access @@ -1981,15 +1990,16 @@ bool QQuickWindowPrivate::deliverTouchCancelEvent(QTouchEvent *event) qCDebug(DBG_TOUCH) << event; Q_Q(QQuickWindow); + if (q->mouseGrabberItem()) + q->mouseGrabberItem()->ungrabMouse(); + touchMouseId = -1; + touchMouseDevice = nullptr; + // A TouchCancel event will typically not contain any points. // Deliver it to all items and handlers that have active touches. QQuickPointerEvent *pointerEvent = pointerEventInstance(QQuickPointerDevice::touchDevice(event->device())); for (int i = 0; i < pointerEvent->pointCount(); ++i) pointerEvent->point(i)->cancelExclusiveGrabImpl(event); - touchMouseId = -1; - touchMouseDevice = nullptr; - if (q->mouseGrabberItem()) - q->mouseGrabberItem()->ungrabMouse(); // The next touch event can only be a TouchBegin, so clean up. pointerEvent->clearGrabbers(); @@ -2208,7 +2218,7 @@ void QQuickWindowPrivate::flushFrameSynchronousEvents() } } -QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QQuickPointerDevice *device, QEvent::Type eventType) const +QQuickPointerEvent *QQuickWindowPrivate::queryPointerEventInstance(QQuickPointerDevice *device, QEvent::Type eventType) const { // Search for a matching reusable event object. for (QQuickPointerEvent *e : pointerEventInstances) { @@ -2221,9 +2231,14 @@ QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QQuickPointerDevic if (e->device() == device) return e; } + return nullptr; +} - // Not found: we have to create a suitable event instance. - QQuickPointerEvent *ev = nullptr; +QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QQuickPointerDevice *device, QEvent::Type eventType) const +{ + QQuickPointerEvent *ev = queryPointerEventInstance(device, eventType); + if (ev) + return ev; QQuickWindow *q = const_cast<QQuickWindow*>(q_func()); switch (device->type()) { case QQuickPointerDevice::Mouse: @@ -2405,6 +2420,7 @@ void QQuickWindowPrivate::deliverTouchEvent(QQuickPointerTouchEvent *event) // Deliver touch points to existing grabbers void QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *event) { + bool done = false; const auto grabbers = event->exclusiveGrabbers(); for (auto grabber : grabbers) { // The grabber is guaranteed to be either an item or a handler. @@ -2414,52 +2430,52 @@ void QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *eve QQuickPointerHandler *handler = static_cast<QQuickPointerHandler *>(grabber); receiver = static_cast<QQuickPointerHandler *>(grabber)->parentItem(); if (sendFilteredPointerEvent(event, receiver)) - return; + done = true; event->localize(receiver); handler->handlePointerEvent(event); if (event->allPointsAccepted()) - return; + done = true; } + if (done) + break; // If the grabber is an item or the grabbing handler didn't handle it, // then deliver the event to the item (which may have multiple handlers). deliverMatchingPointsToItem(receiver, event); } - // If some points weren't grabbed, deliver only to non-grabber PointerHandlers - if (!event->allPointsGrabbed()) { - int pointCount = event->pointCount(); + // Deliver to each eventpoint's passive grabbers (but don't visit any handler more than once) + int pointCount = event->pointCount(); + for (int i = 0; i < pointCount; ++i) { + QQuickEventPoint *point = event->point(i); + deliverToPassiveGrabbers(point->passiveGrabbers(), event); + } + + if (done) + return; - // Deliver to each eventpoint's passive grabbers (but don't visit any handler more than once) + // If some points weren't grabbed, deliver only to non-grabber PointerHandlers in reverse paint order + if (!event->allPointsGrabbed()) { + QVector<QQuickItem *> targetItems; for (int i = 0; i < pointCount; ++i) { QQuickEventPoint *point = event->point(i); - deliverToPassiveGrabbers(point->passiveGrabbers(), event); - } - - // If some points weren't grabbed, deliver to non-grabber PointerHandlers in reverse paint order - if (!event->allPointsGrabbed()) { - QVector<QQuickItem *> targetItems; - for (int i = 0; i < pointCount; ++i) { - QQuickEventPoint *point = event->point(i); - if (point->state() == QQuickEventPoint::Pressed) - continue; // presses were delivered earlier; not the responsibility of deliverUpdatedTouchPoints - QVector<QQuickItem *> targetItemsForPoint = pointerTargets(contentItem, point->scenePosition(), false, false); - if (targetItems.count()) { - targetItems = mergePointerTargets(targetItems, targetItemsForPoint); - } else { - targetItems = targetItemsForPoint; - } - } - - for (QQuickItem *item: targetItems) { - if (grabbers.contains(item)) - continue; - QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); - event->localize(item); - itemPrivate->handlePointerEvent(event, true); // avoid re-delivering to grabbers - if (event->allPointsGrabbed()) - break; + if (point->state() == QQuickEventPoint::Pressed) + continue; // presses were delivered earlier; not the responsibility of deliverUpdatedTouchPoints + QVector<QQuickItem *> targetItemsForPoint = pointerTargets(contentItem, point->scenePosition(), false, false); + if (targetItems.count()) { + targetItems = mergePointerTargets(targetItems, targetItemsForPoint); + } else { + targetItems = targetItemsForPoint; } } + for (QQuickItem *item : targetItems) { + if (grabbers.contains(item)) + continue; + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + event->localize(item); + itemPrivate->handlePointerEvent(event, true); // avoid re-delivering to grabbers + if (event->allPointsGrabbed()) + break; + } } } @@ -2498,7 +2514,7 @@ bool QQuickWindowPrivate::deliverPressOrReleaseEvent(QQuickPointerEvent *event, continue; deliverMatchingPointsToItem(item, event, handlersOnly); if (event->allPointsAccepted()) - break; + handlersOnly = true; } return event->allPointsAccepted(); @@ -2513,8 +2529,9 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo // Let the Item's handlers (if any) have the event first. // However, double click should never be delivered to handlers. if (!pointerEvent->isDoubleClickEvent()) { + bool wasAccepted = pointerEvent->allPointsAccepted(); itemPrivate->handlePointerEvent(pointerEvent); - allowDoubleClick = !(pointerEvent->asPointerMouseEvent() && pointerEvent->isPressEvent() && pointerEvent->allPointsAccepted()); + allowDoubleClick = wasAccepted || !(pointerEvent->asPointerMouseEvent() && pointerEvent->isPressEvent() && pointerEvent->allPointsAccepted()); } if (handlersOnly) return; @@ -2826,17 +2843,10 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QQuickPointerEvent *event // get a touch event customized for delivery to filteringParent QScopedPointer<QTouchEvent> filteringParentTouchEvent(pte->touchEventForItem(receiver, true)); if (filteringParentTouchEvent) { - QVarLengthArray<QPair<QQuickPointerHandler *, QQuickEventPoint *>, 32> passiveGrabsToCancel; if (filteringParent->childMouseEventFilter(receiver, filteringParentTouchEvent.data())) { qCDebug(DBG_TOUCH) << "touch event intercepted by childMouseEventFilter of " << filteringParent; skipDelivery.append(filteringParent); for (auto point: qAsConst(filteringParentTouchEvent->touchPoints())) { - auto pointerEventPoint = pte->pointById(point.id()); - for (auto handler : pointerEventPoint->passiveGrabbers()) { - QPair<QQuickPointerHandler *, QQuickEventPoint *> grab(handler, pointerEventPoint); - if (!passiveGrabsToCancel.contains(grab)) - passiveGrabsToCancel.append(grab); - } QQuickEventPoint *pt = event->pointById(point.id()); pt->setAccepted(); pt->setGrabberItem(filteringParent); @@ -2882,12 +2892,6 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QQuickPointerEvent *event touchMouseUnset = false; // We want to leave touchMouseId and touchMouseDevice set if (mouseEvent->isAccepted()) filteringParent->grabMouse(); - auto pointerEventPoint = pte->pointById(tp.id()); - for (auto handler : pointerEventPoint->passiveGrabbers()) { - QPair<QQuickPointerHandler *, QQuickEventPoint *> grab(handler, pointerEventPoint); - if (!passiveGrabsToCancel.contains(grab)) - passiveGrabsToCancel.append(grab); - } } filtered = true; } @@ -2903,8 +2907,6 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QQuickPointerEvent *event } } } - for (auto grab : passiveGrabsToCancel) - grab.second->cancelPassiveGrab(grab.first); } } } diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h index 06891406ab..87d7f63c8b 100644 --- a/src/quick/items/qquickwindow_p.h +++ b/src/quick/items/qquickwindow_p.h @@ -167,6 +167,7 @@ public: // the device-specific event instances which are reused during event delivery mutable QVector<QQuickPointerEvent *> pointerEventInstances; + QQuickPointerEvent *queryPointerEventInstance(QQuickPointerDevice *device, QEvent::Type eventType = QEvent::None) const; QQuickPointerEvent *pointerEventInstance(QQuickPointerDevice *device, QEvent::Type eventType = QEvent::None) const; // delivery of pointer events: diff --git a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp index 02cf8209d1..30088846a6 100644 --- a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp +++ b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp @@ -193,6 +193,12 @@ QRegion QSGAbstractSoftwareRenderer::optimizeRenderList() } } + if (m_obscuredRegion.contains(m_background->rect().toAlignedRect())) { + m_isOpaque = true; + } else { + m_isOpaque = false; + } + // Empty dirtyRegion (for second pass) m_dirtyRegion = QRegion(); m_obscuredRegion = QRegion(); diff --git a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h index 04a17ea377..f20c2cf977 100644 --- a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h +++ b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h @@ -86,6 +86,8 @@ protected: void setBackgroundSize(const QSize &size); QColor backgroundColor(); QSize backgroundSize(); + // only known after calling optimizeRenderList() + bool isOpaque() const { return m_isOpaque; } private: void nodeAdded(QSGNode *node); @@ -102,6 +104,7 @@ private: QRegion m_dirtyRegion; QRegion m_obscuredRegion; + bool m_isOpaque = false; QSGSoftwareRenderableNodeUpdater *m_nodeUpdater; }; diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp index 10291b9cb5..8843b6450a 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp @@ -490,12 +490,13 @@ QRectF QSGSoftwareInternalImageNode::rect() const const QPixmap &QSGSoftwareInternalImageNode::pixmap() const { - if (QSGSoftwarePixmapTexture *pt = qobject_cast<QSGSoftwarePixmapTexture*>(m_texture)) { + if (QSGSoftwarePixmapTexture *pt = qobject_cast<QSGSoftwarePixmapTexture*>(m_texture)) return pt->pixmap(); - } else { - QSGSoftwareLayer *layer = qobject_cast<QSGSoftwareLayer*>(m_texture); + if (QSGSoftwareLayer *layer = qobject_cast<QSGSoftwareLayer*>(m_texture)) return layer->pixmap(); - } + Q_ASSERT(m_texture == 0); + static const QPixmap nullPixmap; + return nullPixmap; } QT_END_NAMESPACE diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode_p.h index f21667fdf7..5c95eb064a 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode_p.h +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode_p.h @@ -124,8 +124,8 @@ public: QRectF rect() const; -private: const QPixmap &pixmap() const; +private: QRectF m_targetRect; QRectF m_innerTargetRect; diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarelayer.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarelayer.cpp index bd5d8f72c0..9d30c43f87 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwarelayer.cpp +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarelayer.cpp @@ -229,9 +229,6 @@ void QSGSoftwareLayer::grab() if (m_pixmap.size() != m_size) { m_pixmap = QPixmap(m_size); m_pixmap.setDevicePixelRatio(m_device_pixel_ratio); - // This fill here is wasteful, but necessary because it is the only way - // to force a QImage based pixmap to have an alpha channel. - m_pixmap.fill(Qt::transparent); } // Render texture. diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarepixmaprenderer.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarepixmaprenderer.cpp index ad6cf39425..186fd92fb7 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwarepixmaprenderer.cpp +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarepixmaprenderer.cpp @@ -82,13 +82,6 @@ void QSGSoftwarePixmapRenderer::render(QPaintDevice *target) setBackgroundSize(QSize(target->width(), target->height())); setBackgroundColor(clearColor()); - QPainter painter(target); - painter.setRenderHint(QPainter::Antialiasing); - painter.setWindow(m_projectionRect); - auto rc = static_cast<QSGSoftwareRenderContext *>(context()); - QPainter *prevPainter = rc->m_activePainter; - rc->m_activePainter = &painter; - renderTimer.start(); buildRenderList(); qint64 buildRenderListTime = renderTimer.restart(); @@ -101,6 +94,19 @@ void QSGSoftwarePixmapRenderer::render(QPaintDevice *target) optimizeRenderList(); qint64 optimizeRenderListTime = renderTimer.restart(); + if (!isOpaque() && target->devType() == QInternal::Pixmap) { + // This fill here is wasteful, but necessary because it is the only way + // to force a QImage based pixmap to have an alpha channel. + static_cast<QPixmap *>(target)->fill(Qt::transparent); + } + + QPainter painter(target); + painter.setRenderHint(QPainter::Antialiasing); + painter.setWindow(m_projectionRect); + auto rc = static_cast<QSGSoftwareRenderContext *>(context()); + QPainter *prevPainter = rc->m_activePainter; + rc->m_activePainter = &painter; + QRegion paintedRegion = renderNodes(&painter); qint64 renderTime = renderTimer.elapsed(); diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarepublicnodes_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwarepublicnodes_p.h index 9f1913205b..114137fb55 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwarepublicnodes_p.h +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarepublicnodes_p.h @@ -133,6 +133,8 @@ public: QRectF bounds() const; + bool isOpaque() const { return !m_pixmap.hasAlphaChannel(); } + private: QPixmap m_pixmap; QRectF m_bounds; diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp index 11d3153678..7fb531cca3 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp @@ -134,73 +134,58 @@ void QSGSoftwareRenderableNode::update() { // Update the Node properties m_isDirty = true; + m_isOpaque = false; QRectF boundingRect; switch (m_nodeType) { case QSGSoftwareRenderableNode::SimpleRect: - if (m_handle.simpleRectNode->color().alpha() == 255 && !m_transform.isRotating()) + if (m_handle.simpleRectNode->color().alpha() == 255) m_isOpaque = true; - else - m_isOpaque = false; boundingRect = m_handle.simpleRectNode->rect(); break; case QSGSoftwareRenderableNode::SimpleTexture: - if (!m_handle.simpleTextureNode->texture()->hasAlphaChannel() && !m_transform.isRotating()) + if (!m_handle.simpleTextureNode->texture()->hasAlphaChannel()) m_isOpaque = true; - else - m_isOpaque = false; boundingRect = m_handle.simpleTextureNode->rect(); break; case QSGSoftwareRenderableNode::Image: - // There isn't a way to tell, so assume it's not - m_isOpaque = false; + m_isOpaque = !m_handle.imageNode->pixmap().hasAlphaChannel(); boundingRect = m_handle.imageNode->rect().toRect(); break; case QSGSoftwareRenderableNode::Painter: - if (m_handle.painterNode->opaquePainting() && !m_transform.isRotating()) + if (m_handle.painterNode->opaquePainting()) m_isOpaque = true; - else - m_isOpaque = false; boundingRect = QRectF(0, 0, m_handle.painterNode->size().width(), m_handle.painterNode->size().height()); break; case QSGSoftwareRenderableNode::Rectangle: - if (m_handle.rectangleNode->isOpaque() && !m_transform.isRotating()) + if (m_handle.rectangleNode->isOpaque()) m_isOpaque = true; - else - m_isOpaque = false; boundingRect = m_handle.rectangleNode->rect(); break; case QSGSoftwareRenderableNode::Glyph: // Always has alpha - m_isOpaque = false; - boundingRect = m_handle.glpyhNode->boundingRect(); break; case QSGSoftwareRenderableNode::NinePatch: - // Difficult to tell, assume non-opaque - m_isOpaque = false; + m_isOpaque = m_handle.ninePatchNode->isOpaque(); boundingRect = m_handle.ninePatchNode->bounds(); break; case QSGSoftwareRenderableNode::SimpleRectangle: - if (m_handle.simpleRectangleNode->color().alpha() == 255 && !m_transform.isRotating()) + if (m_handle.simpleRectangleNode->color().alpha() == 255) m_isOpaque = true; - else - m_isOpaque = false; boundingRect = m_handle.simpleRectangleNode->rect(); break; case QSGSoftwareRenderableNode::SimpleImage: - if (!m_handle.simpleImageNode->texture()->hasAlphaChannel() && !m_transform.isRotating()) + if (!m_handle.simpleImageNode->texture()->hasAlphaChannel()) m_isOpaque = true; - else - m_isOpaque = false; boundingRect = m_handle.simpleImageNode->rect(); break; @@ -211,10 +196,8 @@ void QSGSoftwareRenderableNode::update() break; #endif case QSGSoftwareRenderableNode::RenderNode: - if (m_handle.renderNode->flags().testFlag(QSGRenderNode::OpaqueRendering) && !m_transform.isRotating()) + if (m_handle.renderNode->flags().testFlag(QSGRenderNode::OpaqueRendering)) m_isOpaque = true; - else - m_isOpaque = false; boundingRect = m_handle.renderNode->rect(); break; @@ -222,6 +205,9 @@ void QSGSoftwareRenderableNode::update() break; } + if (m_transform.isRotating()) + m_isOpaque = false; + const QRectF transformedRect = m_transform.mapRect(boundingRect); m_boundingRectMin = toRectMin(transformedRect); m_boundingRectMax = toRectMax(transformedRect); diff --git a/src/quick/util/qquickanimatorcontroller.cpp b/src/quick/util/qquickanimatorcontroller.cpp index 3f7347c01d..5cf8051922 100644 --- a/src/quick/util/qquickanimatorcontroller.cpp +++ b/src/quick/util/qquickanimatorcontroller.cpp @@ -123,8 +123,10 @@ static void qquickanimator_sync_before_start(QAbstractAnimationJob *job) void QQuickAnimatorController::beforeNodeSync() { - for (const QSharedPointer<QAbstractAnimationJob> &toStop : qAsConst(m_rootsPendingStop)) + for (const QSharedPointer<QAbstractAnimationJob> &toStop : qAsConst(m_rootsPendingStop)) { toStop->stop(); + m_animationRoots.remove(toStop.data()); + } m_rootsPendingStop.clear(); diff --git a/src/quickwidgets/qquickwidget.cpp b/src/quickwidgets/qquickwidget.cpp index f9c9e44fb0..63e1739688 100644 --- a/src/quickwidgets/qquickwidget.cpp +++ b/src/quickwidgets/qquickwidget.cpp @@ -1399,6 +1399,7 @@ bool QQuickWidget::event(QEvent *e) switch (e->type()) { + case QEvent::Leave: case QEvent::TouchBegin: case QEvent::TouchEnd: case QEvent::TouchUpdate: @@ -1452,6 +1453,14 @@ bool QQuickWidget::event(QEvent *e) 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; } |