From 4c152da491d554a9203a3bcfc46e0ebff50de0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Wed, 12 May 2021 13:42:47 +0200 Subject: Set a11y role for QQuickTextInput to EditableText MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the current special case handling for text items to also handle QQuickTextInput. Task-number: QTBUG-93599 Change-Id: I5c0393e5d34a8bce2c6a2cbf491c6d3cad3ff294 Reviewed-by: Jan Arve Sæther (cherry picked from commit 94f34ef69bda1378471c0e39bd354b99179dbb0d) Reviewed-by: Qt Cherry-pick Bot --- src/quick/accessible/qaccessiblequickitem.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/quick/accessible/qaccessiblequickitem.cpp b/src/quick/accessible/qaccessiblequickitem.cpp index cd250321f9..ce0017363d 100644 --- a/src/quick/accessible/qaccessiblequickitem.cpp +++ b/src/quick/accessible/qaccessiblequickitem.cpp @@ -216,6 +216,8 @@ QAccessible::Role QAccessibleQuickItem::role() const if (role == QAccessible::NoRole) { if (qobject_cast(const_cast(item()))) role = QAccessible::StaticText; + else if (qobject_cast(const_cast(item()))) + role = QAccessible::EditableText; else role = QAccessible::Client; } -- cgit v1.2.3 From a86694cbfa54c2429cdaa3ad1496bf17946f4058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Thu, 27 May 2021 12:17:51 +0200 Subject: a11y: make links have pressAction first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VoiceOver on macOS maps control-option-space to the first action, so make sure that one is “press”. Fixes: QTBUG-93857 Change-Id: I9bb40c6e97772922bbcf349df0af3173c7f5912c Reviewed-by: Jan Arve Sæther (cherry picked from commit 2028b74fd24a9e7f822203372fd96af9356e7cf7) Reviewed-by: Qt Cherry-pick Bot --- src/quick/accessible/qaccessiblequickitem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/quick/accessible/qaccessiblequickitem.cpp b/src/quick/accessible/qaccessiblequickitem.cpp index ce0017363d..d37b276496 100644 --- a/src/quick/accessible/qaccessiblequickitem.cpp +++ b/src/quick/accessible/qaccessiblequickitem.cpp @@ -234,6 +234,7 @@ QStringList QAccessibleQuickItem::actionNames() const { QStringList actions; switch (role()) { + case QAccessible::Link: case QAccessible::PushButton: actions << QAccessibleActionInterface::pressAction(); break; -- cgit v1.2.3 From 21c4c5f4eb9590a7435e8cab98ae9236b7a9e8d8 Mon Sep 17 00:00:00 2001 From: Tarja Sundqvist Date: Mon, 7 Jun 2021 18:01:00 +0300 Subject: Bump version --- .qmake.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.qmake.conf b/.qmake.conf index a72382f252..e46219506c 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -4,4 +4,4 @@ CONFIG += warning_clean DEFINES += QT_NO_LINKED_LIST DEFINES += QT_NO_JAVA_STYLE_ITERATORS -MODULE_VERSION = 5.15.5 +MODULE_VERSION = 5.15.6 -- cgit v1.2.3 From 6c56f0eef6a6c54e437d6472eeda25ef4e7b9862 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Fri, 16 Apr 2021 14:16:37 +0200 Subject: QQuickItem::forceActiveFocus(): actually force active focus It was trying to get by with setFocus() but that doesn't always work, in cases when the item's d->focus flag is true (leftover state) but it doesn't actually have focus anymore after a reparenting scenario. Item.focus represents the intention to be focused when possible, and does not necessarily change due to environmental circumstances, such as having its parent reparented. QQuickItem::setFocus(true) returns early if the new requested focus state is the same as the stored d->focus; so it was not enough for foceActiveFocus() to call only setFocus(). In the bug, TextInput and Loader both get stuck in the state d->focus == true, so forceActiveFocus() did not do anything before. Fixes: QTBUG-89736 Change-Id: Ib7f4caccf81b60a02e2655332b64efba4d1fd7cf Reviewed-by: Richard Moe Gustavsen (cherry picked from commit a8be17a1472c6f504c0c40f68fdc13df035ac4b4) Reviewed-by: Qt CI Bot --- src/quick/items/qquickitem.cpp | 10 +++++ .../focusableItemReparentedToLoadedComponent.qml | 51 ++++++++++++++++++++++ tests/auto/quick/qquickitem2/tst_qquickitem.cpp | 31 +++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 tests/auto/quick/qquickitem2/data/focusableItemReparentedToLoadedComponent.qml diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index 0f03e776c9..f17b4110f9 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -4790,14 +4790,24 @@ void QQuickItem::forceActiveFocus() void QQuickItem::forceActiveFocus(Qt::FocusReason reason) { + Q_D(QQuickItem); setFocus(true, reason); QQuickItem *parent = parentItem(); + QQuickItem *scope = nullptr; while (parent) { if (parent->flags() & QQuickItem::ItemIsFocusScope) { parent->setFocus(true, reason); + if (!scope) + scope = parent; } parent = parent->parentItem(); } + // In certain reparenting scenarios, d->focus might be true and the scope + // might also have focus, so that setFocus() returns early without actually + // acquiring active focus, because it thinks it already has it. In that + // case, try to set the DeliveryAgent's active focus. (QTBUG-89736). + if (scope && !d->activeFocus && d->window) + QQuickWindowPrivate::get(d->window)->setFocusInScope(scope, this, Qt::OtherFocusReason); } /*! diff --git a/tests/auto/quick/qquickitem2/data/focusableItemReparentedToLoadedComponent.qml b/tests/auto/quick/qquickitem2/data/focusableItemReparentedToLoadedComponent.qml new file mode 100644 index 0000000000..a690c4243b --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/focusableItemReparentedToLoadedComponent.qml @@ -0,0 +1,51 @@ +import QtQuick 2.12 + +Item { + width: 240; height: 240 + Loader { + id: loader + sourceComponent: surfaceParent + anchors.fill: parent + + onStatusChanged: { + if (status === Loader.Ready) { + holder.create() + holder.item.parent = item + } else if (status === Loader.Null){ + holder.item.parent = null + } + } + } + + property var holder: QtObject { + property bool created: false + function create() + { + if (!created) + surfaceComponent.createObject(item) + created = true + } + + property Item item: Item { + anchors.fill: parent + Component { + id: surfaceComponent + Item { + anchors.fill: parent + TextInput { + width: parent.width + font.pixelSize: 40 + text: "focus me" + } + } + } + } + } + + Component { + id: surfaceParent + Rectangle { + anchors.fill: parent + } + } +} diff --git a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp index f65650cf9c..c8f251dbe1 100644 --- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +76,7 @@ private slots: void qtbug_50516(); void qtbug_50516_2_data(); void qtbug_50516_2(); + void focusableItemReparentedToLoadedComponent(); void keys(); #if QT_CONFIG(shortcut) @@ -1312,6 +1314,35 @@ void tst_QQuickItem::qtbug_50516_2() delete window; } +void tst_QQuickItem::focusableItemReparentedToLoadedComponent() // QTBUG-89736 +{ + QQuickView window; + window.setSource(testFileUrl("focusableItemReparentedToLoadedComponent.qml")); + window.show(); + QVERIFY(QTest::qWaitForWindowActive(&window)); + QCOMPARE(QGuiApplication::focusWindow(), &window); + QQuickLoader *loader = window.rootObject()->findChild(); + QVERIFY(loader); + QTRY_VERIFY(loader->status() == QQuickLoader::Ready); + QQuickTextInput *textInput = window.rootObject()->findChild(); + QVERIFY(textInput); + + // click to focus + QTest::mouseClick(&window, Qt::LeftButton, Qt::NoModifier, {10, 10}); + QTRY_VERIFY(textInput->hasActiveFocus()); + + // unload and reload + auto component = loader->sourceComponent(); + loader->resetSourceComponent(); + QTRY_VERIFY(loader->status() == QQuickLoader::Null); + loader->setSourceComponent(component); + QTRY_VERIFY(loader->status() == QQuickLoader::Ready); + + // click to focus again + QTest::mouseClick(&window, Qt::LeftButton, Qt::NoModifier, {10, 10}); + QTRY_VERIFY(textInput->hasActiveFocus()); +} + void tst_QQuickItem::keys() { QQuickView *window = new QQuickView(nullptr); -- cgit v1.2.3 From 835955cf8100c7c3381d11b275f62467844d8d7f Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Fri, 18 Jun 2021 09:24:04 +0200 Subject: Ensure model is in context if required properties are not used We need to set the set the contextObject already in QQmlDelegateModelPrivate::object, as the context property might be accessed in a child component before we are able to run setRequiredProperties (which would set-up the context in case there are no required properties). Instead of delaying setting the context object, we clear it now in setRequiredProperties. This has the drawback that one might be able to access context properties on initial component setup which should not exist (due to the presence of required properties). This could be avoided by inspecting the Compilation Unit and only setting the context if we do not find required properties, however that needs further work to expose the necessary information. Fixes: QTBUG-94223 Change-Id: I678de81408539417fc650f94da4c9f0d3167653d Reviewed-by: Ulf Hermann Reviewed-by: Qt CI Bot (cherry picked from commit 58cd06033cacadab541efaa16a3eecec37dab0fa) Reviewed-by: Maximilian Goldstein --- src/qmlmodels/qqmldelegatemodel.cpp | 13 +++++++++ .../qml/qqmldelegatemodel/data/ImageToggle.qml | 21 +++++++++++++++ .../data/contextAccessedByHandler.qml | 31 ++++++++++++++++++++++ .../qqmldelegatemodel/tst_qqmldelegatemodel.cpp | 22 +++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 tests/auto/qml/qqmldelegatemodel/data/ImageToggle.qml create mode 100644 tests/auto/qml/qqmldelegatemodel/data/contextAccessedByHandler.qml diff --git a/src/qmlmodels/qqmldelegatemodel.cpp b/src/qmlmodels/qqmldelegatemodel.cpp index dbd73ae667..e256964029 100644 --- a/src/qmlmodels/qqmldelegatemodel.cpp +++ b/src/qmlmodels/qqmldelegatemodel.cpp @@ -968,6 +968,17 @@ void QQDMIncubationTask::initializeRequiredProperties(QQmlDelegateModelItem *mod contextData->extraObject = modelItemToIncubate; } + // If we have required properties, we clear the context object + // so that the model role names are not polluting the context + if (incubating) { + Q_ASSERT(incubating->contextData); + incubating->contextData->contextObject = nullptr; + } + + if (proxyContext) { + proxyContext->contextObject = nullptr; + } + if (incubatorPriv->requiredProperties().empty()) return; RequiredProperties &requiredProperties = incubatorPriv->requiredProperties(); @@ -1279,6 +1290,7 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ QQmlContextData *ctxt = new QQmlContextData; ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_context.data())); + ctxt->contextObject = cacheItem; cacheItem->contextData = ctxt; if (m_adaptorModel.hasProxyObject()) { @@ -1289,6 +1301,7 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ QObject *proxied = proxy->proxiedObject(); cacheItem->incubationTask->proxiedObject = proxied; cacheItem->incubationTask->proxyContext = ctxt; + ctxt->contextObject = cacheItem; // We don't own the proxied object. We need to clear it if it goes away. QObject::connect(proxied, &QObject::destroyed, cacheItem, &QQmlDelegateModelItem::childContextObjectDestroyed); diff --git a/tests/auto/qml/qqmldelegatemodel/data/ImageToggle.qml b/tests/auto/qml/qqmldelegatemodel/data/ImageToggle.qml new file mode 100644 index 0000000000..fa154b25f3 --- /dev/null +++ b/tests/auto/qml/qqmldelegatemodel/data/ImageToggle.qml @@ -0,0 +1,21 @@ +import QtQuick 2.0 + +Item { + property var isSelected: null + property string source + implicitWidth: 16 + implicitHeight: 16 + + onSourceChanged: { + updateImageSource() + } + + onIsSelectedChanged: { + updateImageSource() + } + + function updateImageSource() { + let result = isSelected ? source + "_selected_dark.png" : source + "_active_dark.png" + } + +} diff --git a/tests/auto/qml/qqmldelegatemodel/data/contextAccessedByHandler.qml b/tests/auto/qml/qqmldelegatemodel/data/contextAccessedByHandler.qml new file mode 100644 index 0000000000..46d7524527 --- /dev/null +++ b/tests/auto/qml/qqmldelegatemodel/data/contextAccessedByHandler.qml @@ -0,0 +1,31 @@ +import QtQuick 2.0 + +Item { + id: root + width: 640 + height: 480 + property bool works: myView.currentItem.okay + + ListView { + id: myView + model: myModel + anchors.fill: parent + delegate: Row { + property alias okay: image.isSelected + ImageToggle { + id: image + source: "glyph_16_arrow_patch" + isSelected: model.age < 6 + } + Text { + text: "age:" + model.age + " selected:" + image.isSelected + } + } + } + + ListModel { + id: myModel + ListElement { type: "Cat"; age: 3; } + ListElement { type: "Dog"; age: 2; } + } +} diff --git a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp index 9bc359d243..35f1e2c94d 100644 --- a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp +++ b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp @@ -46,6 +46,7 @@ private slots: void valueWithoutCallingObjectFirst(); void filterOnGroup_removeWhenCompleted(); void qtbug_86017(); + void contextAccessedByHandler(); }; class AbstractItemModel : public QAbstractItemModel @@ -164,6 +165,27 @@ void tst_QQmlDelegateModel::qtbug_86017() QCOMPARE(model->filterGroup(), "selected"); } +void tst_QQmlDelegateModel::filterOnGroup_removeWhenCompleted() +{ + QQuickView view(testFileUrl("removeFromGroup.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QQuickItem *root = view.rootObject(); + QVERIFY(root); + QQmlDelegateModel *model = root->findChild(); + QVERIFY(model); + QVERIFY(QTest::qWaitFor([=]{ return model->count() == 2; })); +} + +void tst_QQmlDelegateModel::contextAccessedByHandler() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("contextAccessedByHandler.qml")); + QScopedPointer root(component.create()); + QVERIFY2(root, qPrintable(component.errorString())); + QVERIFY(root->property("works").toBool()); +} + QTEST_MAIN(tst_QQmlDelegateModel) #include "tst_qqmldelegatemodel.moc" -- cgit v1.2.3 From 3f70dcd3711224c4d9653c51883dc4eb16851814 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Thu, 13 Jun 2019 19:36:25 +0200 Subject: doc: Use DragHandler rather than MouseArea in the externaldrag snippet Task-number: QTBUG-68078 Task-number: QTBUG-76310 Change-Id: Id7b971f5147474274200ef2fdd52ce21f8d60bdd Reviewed-by: Fabian Kosmale (cherry picked from commit 5971a6faaa1124f5ef3f0b42d4ed0298cf8096a3) Reviewed-by: Qt Cherry-pick Bot --- src/quick/doc/snippets/qml/externaldrag.qml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/quick/doc/snippets/qml/externaldrag.qml b/src/quick/doc/snippets/qml/externaldrag.qml index 97a23293ca..5a88be2d4b 100644 --- a/src/quick/doc/snippets/qml/externaldrag.qml +++ b/src/quick/doc/snippets/qml/externaldrag.qml @@ -48,7 +48,7 @@ ** ****************************************************************************/ //![0] -import QtQuick 2.8 +import QtQuick 2.12 Item { width: 200; height: 200 @@ -59,7 +59,7 @@ Item { color: "green" radius: 5 - Drag.active: dragArea.drag.active + Drag.active: dragHandler.active Drag.dragType: Drag.Automatic Drag.supportedActions: Qt.CopyAction Drag.mimeData: { @@ -72,14 +72,14 @@ Item { text: "Drag me" } - MouseArea { - id: dragArea - anchors.fill: parent - - drag.target: parent - onPressed: parent.grabToImage(function(result) { - parent.Drag.imageSource = result.url - }) + DragHandler { + id: dragHandler + onActiveChanged: + if (active) { + parent.grabToImage(function(result) { + parent.Drag.imageSource = result.url; + }) + } } } } -- cgit v1.2.3 From 3df8c87754451a418e89c64c288e5fd5a8a2ec17 Mon Sep 17 00:00:00 2001 From: Andreas Buhr Date: Mon, 21 Jun 2021 09:29:28 +0200 Subject: Fix warnings Change-Id: I9dda78c04ce061767e719eb135ef8922c88704f8 Reviewed-by: Mitch Curtis (cherry picked from commit ac7b6617feca7fe5f63e71f8285090c74758d723) Reviewed-by: Qt Cherry-pick Bot --- tests/auto/quick/nodes/tst_nodestest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/auto/quick/nodes/tst_nodestest.cpp b/tests/auto/quick/nodes/tst_nodestest.cpp index 249ecd5aa5..a4f138c8df 100644 --- a/tests/auto/quick/nodes/tst_nodestest.cpp +++ b/tests/auto/quick/nodes/tst_nodestest.cpp @@ -125,12 +125,12 @@ public: setRootNode(root); } - void render() { + void render() override { ++renderCount; renderingOrder = ++globalRendereringOrder; } - void nodeChanged(QSGNode *node, QSGNode::DirtyState state) { + void nodeChanged(QSGNode *node, QSGNode::DirtyState state) override { changedNode = node; changedState = state; QSGBatchRenderer::Renderer::nodeChanged(node, state); -- cgit v1.2.3 From dd87ad1e7f677eeb83e9d5bdb6253f2c9a03679d Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Thu, 1 Jul 2021 17:44:23 +0200 Subject: crash fix - avoid wrong gcc optimization This fixes a crash that gcc optimized the code in the way that inspectedObjects->contains(object) executed at a nullptr. (only release only some gcc versions) This was already fixed in the past but reintroduced by another commit in the past. Task-number: QTBUG-94798 Change-Id: I9759f3347712f706dd99303ce9d804667c1525f3 Reviewed-by: Marco Bubke Reviewed-by: Qt CI Bot (cherry picked from commit e865d977f6b6b0e3f7e7e61e4a9200f047b0213e) Reviewed-by: Qt Cherry-pick Bot --- src/quick/designer/qquickdesignersupportproperties.cpp | 13 +++++++------ src/quick/designer/qquickdesignersupportproperties_p.h | 4 +--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/quick/designer/qquickdesignersupportproperties.cpp b/src/quick/designer/qquickdesignersupportproperties.cpp index 63e556fc66..06cb9a17cf 100644 --- a/src/quick/designer/qquickdesignersupportproperties.cpp +++ b/src/quick/designer/qquickdesignersupportproperties.cpp @@ -126,17 +126,12 @@ void QQuickDesignerSupportProperties::getPropertyCache(QObject *object, QQmlEngi QQmlEnginePrivate::get(engine)->cache(object->metaObject()); } -QQuickDesignerSupport::PropertyNameList QQuickDesignerSupportProperties::propertyNameListForWritableProperties(QObject *object, +static QQuickDesignerSupport::PropertyNameList propertyNameListForWritableProperties(QObject *object, const QQuickDesignerSupport::PropertyName &baseName, QObjectList *inspectedObjects) { QQuickDesignerSupport::PropertyNameList propertyNameList; - QObjectList localObjectList; - - if (inspectedObjects == nullptr) - inspectedObjects = &localObjectList; - if (!inspectedObjects->contains(object)) inspectedObjects->append(object); @@ -169,6 +164,12 @@ QQuickDesignerSupport::PropertyNameList QQuickDesignerSupportProperties::propert return propertyNameList; } +QQuickDesignerSupport::PropertyNameList QQuickDesignerSupportProperties::propertyNameListForWritableProperties(QObject *object) +{ + QObjectList localObjectList; + return ::propertyNameListForWritableProperties(object, {}, &localObjectList); +} + bool QQuickDesignerSupportProperties::isPropertyBlackListed(const QQuickDesignerSupport::PropertyName &propertyName) { if (propertyName.contains(".") && propertyName.contains("__")) diff --git a/src/quick/designer/qquickdesignersupportproperties_p.h b/src/quick/designer/qquickdesignersupportproperties_p.h index f469a87fdc..0b81533872 100644 --- a/src/quick/designer/qquickdesignersupportproperties_p.h +++ b/src/quick/designer/qquickdesignersupportproperties_p.h @@ -90,9 +90,7 @@ public: static void getPropertyCache(QObject *object, QQmlEngine *engine); static bool isPropertyBlackListed(const QQuickDesignerSupport::PropertyName &propertyName); - static QQuickDesignerSupport::PropertyNameList propertyNameListForWritableProperties(QObject *object, - const QQuickDesignerSupport::PropertyName &baseName = QQuickDesignerSupport::PropertyName(), - QObjectList *inspectedObjects = nullptr); + static QQuickDesignerSupport::PropertyNameList propertyNameListForWritableProperties(QObject *object); static QQuickDesignerSupport::PropertyNameList allPropertyNames(QObject *object, const QQuickDesignerSupport::PropertyName &baseName = QQuickDesignerSupport::PropertyName(), QObjectList *inspectedObjects = nullptr); -- cgit v1.2.3 From eeec277ba0e257e58609e96bef64b13d0272f3d5 Mon Sep 17 00:00:00 2001 From: Ivan Tkachenko Date: Fri, 21 May 2021 11:53:20 +0300 Subject: Fix QtQtuickImage docs: correct properties' signatures 1. Add missing "readonly" annotations. 2. Change sourceSize's type to QML basic type 'size'. Change-Id: I3636818571b1e923058363f62b595567a1ccdcf0 Reviewed-by: Paul Wicking (cherry picked from commit 9e0fdcf87fb0fd35a3313dff6243da6597289455) Reviewed-by: Qt Cherry-pick Bot --- src/quick/items/qquickimage.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/quick/items/qquickimage.cpp b/src/quick/items/qquickimage.cpp index bc7e734e37..ab00eccd5e 100644 --- a/src/quick/items/qquickimage.cpp +++ b/src/quick/items/qquickimage.cpp @@ -343,9 +343,9 @@ void QQuickImage::setFillMode(FillMode mode) } /*! - \qmlproperty real QtQuick::Image::paintedWidth \qmlproperty real QtQuick::Image::paintedHeight + \readonly These properties hold the size of the image that is actually painted. In most cases it is the same as \c width and \c height, but when using an @@ -367,6 +367,7 @@ qreal QQuickImage::paintedHeight() const /*! \qmlproperty enumeration QtQuick::Image::status + \readonly This property holds the status of image loading. It can be one of: \list @@ -404,6 +405,7 @@ qreal QQuickImage::paintedHeight() const /*! \qmlproperty real QtQuick::Image::progress + \readonly This property holds the progress of image loading, from 0.0 (nothing loaded) to 1.0 (finished). @@ -425,7 +427,7 @@ qreal QQuickImage::paintedHeight() const */ /*! - \qmlproperty QSize QtQuick::Image::sourceSize + \qmlproperty size QtQuick::Image::sourceSize This property holds the scaled width and height of the full-frame image. -- cgit v1.2.3 From 1ec362aa10149423aa1e71443d500dd87b39614f Mon Sep 17 00:00:00 2001 From: Andy Shaw Date: Tue, 13 Jul 2021 11:24:08 +0200 Subject: When setting the line dash to be an empty array reset the style to Solid An empty line dash array is indicating that it should be reset to be a Solid line, otherwise it ends up reusing the previous settings for the pen instead of drawing a solid line. Fixes: QTBUG-75553 Change-Id: I16466672de95da8ef0cf3fc261969e7cc6add227 Reviewed-by: Mitch Curtis (cherry picked from commit c31638f16b1fe709dd9df232afb9ab7fac3b231e) Reviewed-by: Qt Cherry-pick Bot --- .../context2d/qquickcontext2dcommandbuffer.cpp | 5 +++- .../auto/quick/qquickcanvasitem/data/tst_line.qml | 31 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/quick/items/context2d/qquickcontext2dcommandbuffer.cpp b/src/quick/items/context2d/qquickcontext2dcommandbuffer.cpp index bfa18acc0e..498f1c6d40 100644 --- a/src/quick/items/context2d/qquickcontext2dcommandbuffer.cpp +++ b/src/quick/items/context2d/qquickcontext2dcommandbuffer.cpp @@ -375,7 +375,10 @@ void QQuickContext2DCommandBuffer::replay(QPainter* p, QQuickContext2D::State& s } state.lineDash = pattern; QPen nPen = p->pen(); - nPen.setDashPattern(pattern); + if (count > 0) + nPen.setDashPattern(pattern); + else + nPen.setStyle(Qt::SolidLine); p->setPen(nPen); break; } diff --git a/tests/auto/quick/qquickcanvasitem/data/tst_line.qml b/tests/auto/quick/qquickcanvasitem/data/tst_line.qml index dc960a24d0..fb4db5ae54 100644 --- a/tests/auto/quick/qquickcanvasitem/data/tst_line.qml +++ b/tests/auto/quick/qquickcanvasitem/data/tst_line.qml @@ -894,6 +894,37 @@ CanvasTestCase { comparePixel(ctx, 39,0, 0,0,0,0); } + function test_lineDashReset(row) { + var canvas = createCanvasObject(row); + var ctx = canvas.getContext('2d'); + ctx.reset(); + ctx.strokeStyle = "#ff0000"; + ctx.lineWidth = 2; + var pattern = [2, 3, 5, 1, 6, 3] + ctx.setLineDash(pattern) + + compare(ctx.getLineDash(), pattern); + + pattern = [] + ctx.setLineDash(pattern) + compare(ctx.getLineDash(), pattern); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(40, 0); + ctx.stroke(); + + comparePixel(ctx, 0,0, 255,0,0,255); + comparePixel(ctx, 4,0, 255,0,0,255); + comparePixel(ctx, 5,0, 255,0,0,255); + comparePixel(ctx, 14,0, 255,0,0,255); + comparePixel(ctx, 20,0, 255,0,0,255); + comparePixel(ctx, 21,0, 255,0,0,255); + comparePixel(ctx, 22,0, 255,0,0,255); + comparePixel(ctx, 34,0, 255,0,0,255); + comparePixel(ctx, 35,0, 255,0,0,255); + } + function test_lineDashOffset(row) { var canvas = createCanvasObject(row); var ctx = canvas.getContext('2d'); -- cgit v1.2.3 From 8cfdf56deba3da6bbbb0561224730e0d45d4b311 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Tue, 13 Jul 2021 15:36:31 +0200 Subject: doc: Improve QQuickItem::containmentMask() docs The Qt Quick docs weren't as explicit as possible; and the \qmlproperty did not generate any C++ docs. Amends bf74a908cb0591c2adc024a6f93d566c7348c125 Fixes: QTBUG-89375 Change-Id: Ib81796c1d7a00a7a48940c65137e4ed6a38a21ab Reviewed-by: Volker Hilsheimer Reviewed-by: Mitch Curtis (cherry picked from commit bcf3f29c6d26777c8db814477b8f3c3e19826234) Reviewed-by: Qt Cherry-pick Bot --- src/quick/items/qquickitem.cpp | 44 +++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index f17b4110f9..dfc3ece60d 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -7868,22 +7868,48 @@ bool QQuickItem::contains(const QPointF &point) const \qmlproperty QObject* QtQuick::Item::containmentMask \since 5.11 This property holds an optional mask for the Item to be used in the - QtQuick::Item::contains method. - QtQuick::Item::contains main use is currently to determine whether - an input event has landed into the item or not. + QtQuick::Item::contains() method. Its main use is currently to determine + whether a \l {QPointerEvent}{pointer event} has landed into the item or not. By default the \l contains method will return true for any point - within the Item's bounding box. \c containmentMask allows for a - more fine-grained control. For example, the developer could - define and use an AnotherItem element as containmentMask, - which has a specialized contains method, like: + within the Item's bounding box. \c containmentMask allows for + more fine-grained control. For example, if a custom C++ + QQuickItem subclass with a specialized contains() method + is used as containmentMask: \code Item { id: item; containmentMask: AnotherItem { id: anotherItem } } \endcode - \e{item}'s contains method would then return true only if - \e{anotherItem}'s contains implementation returns true. + \e{item}'s contains method would then return \c true only if + \e{anotherItem}'s contains() implementation returns \c true. + + A \l Shape can be used in this way, to make an item react to + \l {QPointerEvent}{pointer events} only within a non-rectangular region, + as illustrated in the \l {Qt Quick Examples - Shapes}{Shapes example} + (see \c tapableTriangle.qml). +*/ +/*! + \property QQuickItem::containmentMask + \since 5.11 + This property holds an optional mask to be used in the contains() method, + which is mainly used for hit-testing each \l QPointerEvent. + + By default, \l contains() will return \c true for any point + within the Item's bounding box. But any QQuickItem, or any QObject + that implements a function of the form + \code + Q_INVOKABLE bool contains(const QPointF &point) const; + \endcode + can be used as a mask, to defer hit-testing to that object. + + \note contains() is called frequently during event delivery. + Deferring hit-testing to another object slows it down somewhat. + containmentMask() can cause performance problems if that object's + contains() method is not efficient. If you implement a custom + QQuickItem subclass, you can alternatively override contains(). + + \sa contains() */ QObject *QQuickItem::containmentMask() const { -- cgit v1.2.3 From d435fd9a26c2f5628de6c5ae75b80900c1f2c59f Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 14 Jul 2021 15:15:00 +0200 Subject: Register the QML builtin sequence metatypes only once Use the C++11 guarantee on function-local statics to be initialized exactly once for this. This does not apply to Qt6 because there the sequence types have been rewritten. Task-number: QTBUG-93973 Change-Id: Ia885f59da287036efa998cdf32211bb029090334 Reviewed-by: Fabian Kosmale --- src/qml/jsruntime/qv4sequenceobject.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/qml/jsruntime/qv4sequenceobject.cpp b/src/qml/jsruntime/qv4sequenceobject.cpp index 45c0aac926..5105660ff0 100644 --- a/src/qml/jsruntime/qv4sequenceobject.cpp +++ b/src/qml/jsruntime/qv4sequenceobject.cpp @@ -720,13 +720,20 @@ DEFINE_OBJECT_TEMPLATE_VTABLE(QQmlRealList); } #define REGISTER_QML_SEQUENCE_METATYPE(unused, unused2, SequenceType, unused3) qRegisterMetaType(#SequenceType); -void SequencePrototype::init() +static bool registerAllSequenceTypes() { FOREACH_QML_SEQUENCE_TYPE(REGISTER_QML_SEQUENCE_METATYPE) + return true; +} +#undef REGISTER_QML_SEQUENCE_METATYPE + +void SequencePrototype::init() +{ + static const bool registered = registerAllSequenceTypes(); + Q_UNUSED(registered); defineDefaultProperty(QStringLiteral("sort"), method_sort, 1); defineDefaultProperty(engine()->id_valueOf(), method_valueOf, 0); } -#undef REGISTER_QML_SEQUENCE_METATYPE ReturnedValue SequencePrototype::method_valueOf(const FunctionObject *f, const Value *thisObject, const Value *, int) { -- cgit v1.2.3 From 18ba7ed4933273ba463bd9b663fd83dee490bccd Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 14 Jul 2021 13:49:14 +0200 Subject: Resolve data race in QQmlPropertyData in a minimally invasive way We keep all the idiosyncrasies of when what flag or member is set. We only make sure not to call resolve() concurrently by moving the propType into an atomic int which also replaces the "fully resolved" flag. We need to move another 16bit member in there as only 32bit atomics are guaranteed to work everywhere and we want to keep the fixed size of the class. So we choose the relative property index and only set it when we are sure there isn't a data race. This is fine because for missing relative property indices there is a fallback. Task-number: QTBUG-93973 Change-Id: Ia357444cad85fe2df234e76226dd8adc10fe8549 Reviewed-by: Fabian Kosmale --- src/qml/qml/qqmlbinding.cpp | 9 +++- src/qml/qml/qqmlpropertycache.cpp | 108 +++++++++++++++++++++++--------------- src/qml/qml/qqmlpropertycache_p.h | 8 ++- src/qml/qml/qqmlpropertydata_p.h | 98 +++++++++++++++++++--------------- 4 files changed, 138 insertions(+), 85 deletions(-) diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index a4c0717dfc..3b5b0de91b 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -645,7 +645,10 @@ void QQmlBinding::getPropertyData(QQmlPropertyData **propertyData, QQmlPropertyD Q_ASSERT(valueTypeMetaObject); QMetaProperty vtProp = valueTypeMetaObject->property(m_targetIndex.valueTypeIndex()); valueTypeData->setFlags(QQmlPropertyData::flagsForProperty(vtProp)); + + // valueTypeData is expected to be local here. It must not be shared with other threads. valueTypeData->setPropType(vtProp.userType()); + valueTypeData->setCoreIndex(m_targetIndex.valueTypeIndex()); } } @@ -748,7 +751,11 @@ QQmlBinding *QQmlBinding::newBinding(QQmlEnginePrivate *engine, const QQmlProper if (property && property->isQObject()) return new QObjectPointerBinding(engine, property->propType()); - const int type = (property && property->isFullyResolved()) ? property->propType() : QMetaType::UnknownType; + // If the property is not resolved at this point, you get a binding of unknown type. + // This has been the case for a long time and we keep it like this in Qt5 to be bug-compatible. + const int type = (property && property->isResolved()) + ? property->propType() + : QMetaType::UnknownType; if (type == qMetaTypeId()) { return new QQmlBindingBinding; diff --git a/src/qml/qml/qqmlpropertycache.cpp b/src/qml/qml/qqmlpropertycache.cpp index 3cc2b1e391..c5e34e1d15 100644 --- a/src/qml/qml/qqmlpropertycache.cpp +++ b/src/qml/qml/qqmlpropertycache.cpp @@ -138,17 +138,17 @@ void QQmlPropertyData::lazyLoad(const QMetaProperty &p) { populate(this, p); int type = static_cast(p.userType()); - if (type == QMetaType::QObjectStar) { - setPropType(type); + + if (type >= QMetaType::User || type == 0) + return; // Resolve later + + if (type == QMetaType::QObjectStar) m_flags.type = Flags::QObjectDerivedType; - } else if (type == QMetaType::QVariant) { - setPropType(type); + else if (type == QMetaType::QVariant) m_flags.type = Flags::QVariantType; - } else if (type >= QMetaType::User || type == 0) { - m_flags.notFullyResolved = true; - } else { - setPropType(type); - } + + // This is OK because lazyLoad is done before exposing the property data. + setPropType(type); } void QQmlPropertyData::load(const QMetaProperty &p) @@ -158,45 +158,49 @@ void QQmlPropertyData::load(const QMetaProperty &p) flagsForPropertyType(propType(), m_flags); } -void QQmlPropertyData::load(const QMetaMethod &m) +static void populate(QQmlPropertyData *data, const QMetaMethod &m) { - setCoreIndex(m.methodIndex()); - setArguments(nullptr); + data->setCoreIndex(m.methodIndex()); + data->setArguments(nullptr); - setPropType(m.returnType()); - - m_flags.type = Flags::FunctionType; - if (m.methodType() == QMetaMethod::Signal) { - m_flags.setIsSignal(true); - } else if (m.methodType() == QMetaMethod::Constructor) { - m_flags.setIsConstructor(true); - setPropType(QMetaType::QObjectStar); - } + QQmlPropertyData::Flags flags = data->flags(); + flags.type = QQmlPropertyData::Flags::FunctionType; + if (m.methodType() == QMetaMethod::Signal) + flags.setIsSignal(true); + else if (m.methodType() == QMetaMethod::Constructor) + flags.setIsConstructor(true); const int paramCount = m.parameterCount(); if (paramCount) { - m_flags.setHasArguments(true); + flags.setHasArguments(true); if ((paramCount == 1) && (m.parameterTypes().constFirst() == "QQmlV4Function*")) - m_flags.setIsV4Function(true); + flags.setIsV4Function(true); } if (m.attributes() & QMetaMethod::Cloned) - m_flags.setIsCloned(true); + flags.setIsCloned(true); + + data->setFlags(flags); Q_ASSERT(m.revision() <= Q_INT16_MAX); - setRevision(m.revision()); + data->setRevision(m.revision()); } -void QQmlPropertyData::lazyLoad(const QMetaMethod &m) +void QQmlPropertyData::load(const QMetaMethod &m) { - load(m); + populate(this, m); + setPropType(m.methodType() == QMetaMethod::Constructor + ? QMetaType::QObjectStar + : m.returnType()); +} +void QQmlPropertyData::lazyLoad(const QMetaMethod &m) +{ const char *returnType = m.typeName(); - if (!returnType) - returnType = "\0"; - if ((*returnType != 'v') || (qstrcmp(returnType+1, "oid") != 0)) { - m_flags.notFullyResolved = true; - } + if (!returnType || *returnType != 'v' || qstrcmp(returnType + 1, "oid") != 0) + populate(this, m); + else + load(m); // If it's void, resolve it right away } /*! @@ -650,25 +654,23 @@ void QQmlPropertyCache::append(const QMetaObject *metaObject, } } -void QQmlPropertyCache::resolve(QQmlPropertyData *data) const +int QQmlPropertyCache::findPropType(const QQmlPropertyData *data) const { - Q_ASSERT(data->notFullyResolved()); - data->m_flags.notFullyResolved = false; - + int type = QMetaType::UnknownType; const QMetaObject *mo = firstCppMetaObject(); if (data->isFunction()) { auto metaMethod = mo->method(data->coreIndex()); const char *retTy = metaMethod.typeName(); if (!retTy) retTy = "\0"; - data->setPropType(QMetaType::type(retTy)); + type = QMetaType::type(retTy); } else { auto metaProperty = mo->property(data->coreIndex()); - data->setPropType(QMetaType::type(metaProperty.typeName())); + type = QMetaType::type(metaProperty.typeName()); } if (!data->isFunction()) { - if (data->propType() == QMetaType::UnknownType) { + if (type == QMetaType::UnknownType) { QQmlPropertyCache *p = _parent; while (p && (!mo || _ownMetaObject)) { mo = p->_metaObject; @@ -684,11 +686,33 @@ void QQmlPropertyCache::resolve(QQmlPropertyData *data) const int registerResult = -1; void *argv[] = { ®isterResult }; - mo->static_metacall(QMetaObject::RegisterPropertyMetaType, data->coreIndex() - propOffset, argv); - data->setPropType(registerResult == -1 ? QMetaType::UnknownType : registerResult); + mo->static_metacall(QMetaObject::RegisterPropertyMetaType, + data->coreIndex() - propOffset, argv); + type = registerResult == -1 ? QMetaType::UnknownType : registerResult; } } - flagsForPropertyType(data->propType(), data->m_flags); + } + + return type; +} + +void QQmlPropertyCache::resolve(QQmlPropertyData *data) const +{ + const int type = findPropType(data); + + // Setting the flags unsynchronized is somewhat dirty but unlikely to cause trouble + // in practice. We have to do this before setting the property type because otherwise + // a consumer of the flags might see outdated flags even after the property type has + // become valid. The flags should only depend on the property type and the property + // type should be the same across different invocations. So, setting this concurrently + // should be a noop. + if (!data->isFunction()) + flagsForPropertyType(type, data->m_flags); + + // This is the one place where we can update the property type after exposing the data. + if (!data->m_propTypeAndRelativePropIndex.testAndSetOrdered( + 0, type > 0 ? quint32(type) : quint32(QQmlPropertyData::PropTypeUnknown))) { + return; // Someone else is resolving it already } } diff --git a/src/qml/qml/qqmlpropertycache_p.h b/src/qml/qml/qqmlpropertycache_p.h index e6700e2e15..088c382d7d 100644 --- a/src/qml/qml/qqmlpropertycache_p.h +++ b/src/qml/qml/qqmlpropertycache_p.h @@ -220,6 +220,8 @@ private: _hasPropertyOverrides |= isOverride; } + int findPropType(const QQmlPropertyData *data) const; + private: QQmlPropertyCache *_parent; int propertyIndexCacheStart; @@ -246,7 +248,11 @@ private: inline QQmlPropertyData *QQmlPropertyCache::ensureResolved(QQmlPropertyData *p) const { - if (p && Q_UNLIKELY(p->notFullyResolved())) + // Avoid resolve() in the common case where it's already initialized and we don't + // run into a data race. resolve() checks again, with an atomic operation. + // If there is no coreIndex, there is no point in trying to resolve anything. In that + // case it's a default-constructed instance that never got load()'ed or lazyLoad()'ed. + if (p && p->coreIndex() != -1 && Q_UNLIKELY(p->m_propTypeAndRelativePropIndex == 0)) resolve(p); return p; diff --git a/src/qml/qml/qqmlpropertydata_p.h b/src/qml/qml/qqmlpropertydata_p.h index f4415aa6a9..345342af16 100644 --- a/src/qml/qml/qqmlpropertydata_p.h +++ b/src/qml/qml/qqmlpropertydata_p.h @@ -84,14 +84,6 @@ public: QVariantType = 9 // Property is a QVariant }; - // The _otherBits (which "pad" the Flags struct to align it nicely) are used - // to store the relative property index. It will only get used when said index fits. See - // trySetStaticMetaCallFunction for details. - // (Note: this padding is done here, because certain compilers have surprising behavior - // when an enum is declared in-between two bit fields.) - enum { BitsLeftInFlags = 15 }; - unsigned otherBits : BitsLeftInFlags; // align to 32 bits - // Members of the form aORb can only be a when type is not FunctionType, and only be // b when type equals FunctionType. For that reason, the semantic meaning of the bit is // overloaded, and the accessor functions are used to get the correct value @@ -102,25 +94,24 @@ public: // // Lastly, isDirect and isOverridden apply to both functions and non-functions private: - unsigned isConstantORisVMEFunction : 1; // Has CONST flag OR Function was added by QML - unsigned isWritableORhasArguments : 1; // Has WRITE function OR Function takes arguments - unsigned isResettableORisSignal : 1; // Has RESET function OR Function is a signal - unsigned isAliasORisVMESignal : 1; // Is a QML alias to another property OR Signal was added by QML - unsigned isFinalORisV4Function : 1; // Has FINAL flag OR Function takes QQmlV4Function* args - unsigned isSignalHandler : 1; // Function is a signal handler - unsigned isOverload : 1; // Function is an overload of another function - unsigned isRequiredORisCloned : 1; // Has REQUIRED flag OR The function was marked as cloned - unsigned isConstructor : 1; // The function was marked is a constructor - unsigned isDirect : 1; // Exists on a C++ QMetaObject - unsigned isOverridden : 1; // Is overridden by a extension property + quint16 isConstantORisVMEFunction : 1; // Has CONST flag OR Function was added by QML + quint16 isWritableORhasArguments : 1; // Has WRITE function OR Function takes arguments + quint16 isResettableORisSignal : 1; // Has RESET function OR Function is a signal + quint16 isAliasORisVMESignal : 1; // Is a QML alias to another property OR Signal was added by QML + quint16 isFinalORisV4Function : 1; // Has FINAL flag OR Function takes QQmlV4Function* args + quint16 isSignalHandler : 1; // Function is a signal handler + quint16 isOverload : 1; // Function is an overload of another function + quint16 isRequiredORisCloned : 1; // Has REQUIRED flag OR The function was marked as cloned + quint16 isConstructor : 1; // The function was marked is a constructor + quint16 isDirect : 1; // Exists on a C++ QMetaObject + quint16 isOverridden : 1; // Is overridden by a extension property public: - unsigned type : 4; // stores an entry of Types + quint16 type : 4; // stores an entry of Types // Apply only to IsFunctions // Internal QQmlPropertyCache flags - unsigned notFullyResolved : 1; // True if the type data is to be lazily resolved - unsigned overrideIndexIsProperty: 1; + quint16 overrideIndexIsProperty: 1; inline Flags(); inline bool operator==(const Flags &other) const; @@ -208,16 +199,12 @@ public: }; + Q_STATIC_ASSERT(sizeof(Flags) == sizeof(quint16)); inline bool operator==(const QQmlPropertyData &) const; Flags flags() const { return m_flags; } - void setFlags(Flags f) - { - unsigned otherBits = m_flags.otherBits; - m_flags = f; - m_flags.otherBits = otherBits; - } + void setFlags(Flags f) { m_flags = f; } bool isValid() const { return coreIndex() != -1; } @@ -253,14 +240,26 @@ public: bool hasOverride() const { return overrideIndex() >= 0; } bool hasRevision() const { return revision() != 0; } - bool isFullyResolved() const { return !m_flags.notFullyResolved; } + // This is unsafe in the general case. The property might be in the process of getting + // resolved. Only use it if this case has been taken into account. + bool isResolved() const { return m_propTypeAndRelativePropIndex != 0; } + + int propType() const + { + const quint32 type = m_propTypeAndRelativePropIndex & PropTypeMask; + Q_ASSERT(type > 0); // Property has to be fully resolved. + return type == PropTypeUnknown ? 0 : type; + } - int propType() const { Q_ASSERT(isFullyResolved()); return m_propType; } void setPropType(int pt) { + // You can only directly set the property type if you own the QQmlPropertyData. + // It must not be exposed to other threads before setting the type! Q_ASSERT(pt >= 0); - Q_ASSERT(pt <= std::numeric_limits::max()); - m_propType = quint16(pt); + Q_ASSERT(uint(pt) < PropTypeUnknown); + m_propTypeAndRelativePropIndex + = (m_propTypeAndRelativePropIndex & RelativePropIndexMask) + | (pt == 0 ? PropTypeUnknown : quint32(pt)); } int notifyIndex() const { return m_notifyIndex; } @@ -336,12 +335,26 @@ public: StaticMetaCallFunction staticMetaCallFunction() const { return m_staticMetaCallFunction; } void trySetStaticMetaCallFunction(StaticMetaCallFunction f, unsigned relativePropertyIndex) { - if (relativePropertyIndex < (1 << Flags::BitsLeftInFlags) - 1) { - m_flags.otherBits = relativePropertyIndex; + if (relativePropertyIndex > std::numeric_limits::max()) + return; + + const quint16 propType = m_propTypeAndRelativePropIndex & PropTypeMask; + if (propType > 0) { + // We can do this because we know that resolve() has run at this point + // and we don't need to synchronize anymore. If we get a 0, that means it hasn't + // run or is currently in progress. We don't want to interfer and just go through + // the meta object. + m_propTypeAndRelativePropIndex + = propType | (relativePropertyIndex << RelativePropIndexShift); m_staticMetaCallFunction = f; } } - quint16 relativePropertyIndex() const { Q_ASSERT(hasStaticMetaCallFunction()); return m_flags.otherBits; } + + quint16 relativePropertyIndex() const + { + Q_ASSERT(hasStaticMetaCallFunction()); + return m_propTypeAndRelativePropIndex >> 16; + } static Flags flagsForProperty(const QMetaProperty &); void load(const QMetaProperty &); @@ -401,11 +414,17 @@ private: friend class QQmlPropertyCache; void lazyLoad(const QMetaProperty &); void lazyLoad(const QMetaMethod &); - bool notFullyResolved() const { return m_flags.notFullyResolved; } + + enum { + PropTypeMask = 0x0000ffff, + RelativePropIndexMask = 0xffff0000, + RelativePropIndexShift = 16, + PropTypeUnknown = std::numeric_limits::max(), + }; + QAtomicInteger m_propTypeAndRelativePropIndex; Flags m_flags; qint16 m_coreIndex = -1; - quint16 m_propType = 0; // The notify index is in the range returned by QObjectPrivate::signalIndex(). // This is different from QMetaMethod::methodIndex(). @@ -436,8 +455,7 @@ bool QQmlPropertyData::operator==(const QQmlPropertyData &other) const } QQmlPropertyData::Flags::Flags() - : otherBits(0) - , isConstantORisVMEFunction(false) + : isConstantORisVMEFunction(false) , isWritableORhasArguments(false) , isResettableORisSignal(false) , isAliasORisVMESignal(false) @@ -449,7 +467,6 @@ QQmlPropertyData::Flags::Flags() , isDirect(false) , isOverridden(false) , type(OtherType) - , notFullyResolved(false) , overrideIndexIsProperty(false) {} @@ -465,7 +482,6 @@ bool QQmlPropertyData::Flags::operator==(const QQmlPropertyData::Flags &other) c isRequiredORisCloned == other.isRequiredORisCloned && type == other.type && isConstructor == other.isConstructor && - notFullyResolved == other.notFullyResolved && overrideIndexIsProperty == other.overrideIndexIsProperty; } -- cgit v1.2.3 From 8d326a649938e23ecc0756fe8c42bf6cccca0251 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 14 Jul 2021 14:44:57 +0200 Subject: Resolve data race on QQmlPropertyCache's arguments object We need make its assignment atomic. This does not apply to Qt6, because in Qt6 we don't need the property cache to resolve argument types. Task-number: QTBUG-93973 Change-Id: I9588c3582754a9aba3a6aa9e325de87d44d06aa2 Reviewed-by: Fabian Kosmale --- src/qml/qml/qqmlmetaobject.cpp | 23 ++++++++++++----------- src/qml/qml/qqmlpropertycache.cpp | 12 +++--------- src/qml/qml/qqmlpropertycache_p.h | 2 +- src/qml/qml/qqmlpropertycachemethodarguments_p.h | 2 -- src/qml/qml/qqmlpropertydata_p.h | 7 +++++-- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/qml/qml/qqmlmetaobject.cpp b/src/qml/qml/qqmlmetaobject.cpp index 988f4f2811..a6a1b57c17 100644 --- a/src/qml/qml/qqmlmetaobject.cpp +++ b/src/qml/qml/qqmlmetaobject.cpp @@ -232,8 +232,6 @@ int *QQmlMetaObject::methodParameterTypes(int index, ArgTypeStorage *argStorage, Q_ASSERT(!_m.isNull() && index >= 0); if (_m.isT1()) { - typedef QQmlPropertyCacheMethodArguments A; - QQmlPropertyCache *c = _m.asT1(); Q_ASSERT(index < c->methodIndexCacheStart + c->methodIndexCache.count()); @@ -242,19 +240,16 @@ int *QQmlMetaObject::methodParameterTypes(int index, ArgTypeStorage *argStorage, QQmlPropertyData *rv = const_cast(&c->methodIndexCache.at(index - c->methodIndexCacheStart)); - if (rv->arguments() && static_cast(rv->arguments())->argumentsValid) - return static_cast(rv->arguments())->arguments; + if (QQmlPropertyCacheMethodArguments *args = rv->arguments()) + return args->arguments; const QMetaObject *metaObject = c->createMetaObject(); Q_ASSERT(metaObject); QMetaMethod m = metaObject->method(index); int argc = m.parameterCount(); - if (!rv->arguments()) { - A *args = c->createArgumentsObject(argc, m.parameterNames()); - rv->setArguments(args); - } - A *args = static_cast(rv->arguments()); + + QQmlPropertyCacheMethodArguments *args = c->createArgumentsObject(argc, m.parameterNames()); QList argTypeNames; // Only loaded if needed @@ -280,8 +275,14 @@ int *QQmlMetaObject::methodParameterTypes(int index, ArgTypeStorage *argStorage, } args->arguments[ii + 1] = type; } - args->argumentsValid = true; - return static_cast(rv->arguments())->arguments; + + // If we cannot set it, then another thread has set it in the mean time. + // Just return that one, then. We don't have to delete the arguments object + // we've created as it's tracked in a linked list. + if (rv->setArguments(args)) + return args->arguments; + else + return rv->arguments()->arguments; } else { QMetaMethod m = _m.asT2()->method(index); diff --git a/src/qml/qml/qqmlpropertycache.cpp b/src/qml/qml/qqmlpropertycache.cpp index c5e34e1d15..11e967105c 100644 --- a/src/qml/qml/qqmlpropertycache.cpp +++ b/src/qml/qml/qqmlpropertycache.cpp @@ -161,7 +161,6 @@ void QQmlPropertyData::load(const QMetaProperty &p) static void populate(QQmlPropertyData *data, const QMetaMethod &m) { data->setCoreIndex(m.methodIndex()); - data->setArguments(nullptr); QQmlPropertyData::Flags flags = data->flags(); flags.type = QQmlPropertyData::Flags::FunctionType; @@ -322,7 +321,6 @@ void QQmlPropertyCache::appendSignal(const QString &name, QQmlPropertyData::Flag data.setPropType(QMetaType::UnknownType); data.setCoreIndex(coreIndex); data.setFlags(flags); - data.setArguments(nullptr); QQmlPropertyData handler = data; handler.m_flags.setIsSignalHandler(true); @@ -331,7 +329,6 @@ void QQmlPropertyCache::appendSignal(const QString &name, QQmlPropertyData::Flag int argumentCount = *types; QQmlPropertyCacheMethodArguments *args = createArgumentsObject(argumentCount, names); ::memcpy(args->arguments, types, (argumentCount + 1) * sizeof(int)); - args->argumentsValid = true; data.setArguments(args); } @@ -365,7 +362,6 @@ void QQmlPropertyCache::appendMethod(const QString &name, QQmlPropertyData::Flag QQmlPropertyCacheMethodArguments *args = createArgumentsObject(argumentCount, names); for (int ii = 0; ii < argumentCount; ++ii) args->arguments[ii + 1] = parameterTypes.at(ii); - args->argumentsValid = true; data.setArguments(args); data.setFlags(flags); @@ -897,12 +893,11 @@ QQmlPropertyCacheMethodArguments *QQmlPropertyCache::createArgumentsObject(int a typedef QQmlPropertyCacheMethodArguments A; A *args = static_cast(malloc(sizeof(A) + (argc) * sizeof(int))); args->arguments[0] = argc; - args->argumentsValid = false; args->signalParameterStringForJS = nullptr; - args->parameterError = false; args->names = argc ? new QList(names) : nullptr; - args->next = argumentsCache; - argumentsCache = args; + do { + args->next = argumentsCache; + } while (!argumentsCache.testAndSetRelease(args->next, args)); return args; } @@ -1196,7 +1191,6 @@ void QQmlPropertyCache::toMetaObjectBuilder(QMetaObjectBuilder &builder) QQmlPropertyCacheMethodArguments *arguments = nullptr; if (data->hasArguments()) { arguments = (QQmlPropertyCacheMethodArguments *)data->arguments(); - Q_ASSERT(arguments->argumentsValid); for (int ii = 0; ii < arguments->arguments[0]; ++ii) { if (ii != 0) signature.append(','); signature.append(QMetaType::typeName(arguments->arguments[1 + ii])); diff --git a/src/qml/qml/qqmlpropertycache_p.h b/src/qml/qml/qqmlpropertycache_p.h index 088c382d7d..46f924a938 100644 --- a/src/qml/qml/qqmlpropertycache_p.h +++ b/src/qml/qml/qqmlpropertycache_p.h @@ -241,7 +241,7 @@ private: QByteArray _dynamicClassName; QByteArray _dynamicStringData; QString _defaultPropertyName; - QQmlPropertyCacheMethodArguments *argumentsCache; + QAtomicPointer argumentsCache; int _jsFactoryMethodIndex; QByteArray _checksum; }; diff --git a/src/qml/qml/qqmlpropertycachemethodarguments_p.h b/src/qml/qml/qqmlpropertycachemethodarguments_p.h index 978f6ec1e4..096571040c 100644 --- a/src/qml/qml/qqmlpropertycachemethodarguments_p.h +++ b/src/qml/qml/qqmlpropertycachemethodarguments_p.h @@ -64,8 +64,6 @@ public: //for signal handler rewrites QString *signalParameterStringForJS; - int parameterError:1; - int argumentsValid:1; QList *names; diff --git a/src/qml/qml/qqmlpropertydata_p.h b/src/qml/qml/qqmlpropertydata_p.h index 345342af16..6dd58d6900 100644 --- a/src/qml/qml/qqmlpropertydata_p.h +++ b/src/qml/qml/qqmlpropertydata_p.h @@ -322,7 +322,10 @@ public: } QQmlPropertyCacheMethodArguments *arguments() const { return m_arguments; } - void setArguments(QQmlPropertyCacheMethodArguments *args) { m_arguments = args; } + bool setArguments(QQmlPropertyCacheMethodArguments *args) + { + return m_arguments.testAndSetRelease(nullptr, args); + } int metaObjectOffset() const { return m_metaObjectOffset; } void setMetaObjectOffset(int off) @@ -435,7 +438,7 @@ private: quint8 m_typeMinorVersion = 0; qint16 m_metaObjectOffset = -1; - QQmlPropertyCacheMethodArguments *m_arguments = nullptr; + QAtomicPointer m_arguments; StaticMetaCallFunction m_staticMetaCallFunction = nullptr; }; -- cgit v1.2.3 From 442c90356a7595b1e665a9475ea3a02c72a94c7e Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Thu, 20 May 2021 17:08:44 +0200 Subject: Emit grabChanged() from DragHandler and PinchHandler Followup to ca7cdd71ee33f0d77eb6bf1367d2532e26155cb2 : when overriding a virtual function, it's good practice to call the base class function, in the absence of any reason not to. Fixes: QTBUG-93880 Change-Id: Icbd04faec51d55d8fbf73319bd20f5846761d3d5 Reviewed-by: Fabian Kosmale (cherry picked from commit a10eeee97d42f05409074f69cc99d9a8da5db077) Reviewed-by: Shawn Rutledge --- src/quick/handlers/qquickmultipointhandler.cpp | 16 +++++++++++++++- .../qquickpinchhandler/tst_qquickpinchhandler.cpp | 4 ++++ tests/manual/pointer/pinchHandler.qml | 8 ++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/quick/handlers/qquickmultipointhandler.cpp b/src/quick/handlers/qquickmultipointhandler.cpp index 4a261f8b87..4f050b70b0 100644 --- a/src/quick/handlers/qquickmultipointhandler.cpp +++ b/src/quick/handlers/qquickmultipointhandler.cpp @@ -146,7 +146,7 @@ void QQuickMultiPointHandler::onActiveChanged() } } -void QQuickMultiPointHandler::onGrabChanged(QQuickPointerHandler *, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *) +void QQuickMultiPointHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point) { Q_D(QQuickMultiPointHandler); // If another handler or item takes over this set of points, assume it has @@ -155,6 +155,20 @@ void QQuickMultiPointHandler::onGrabChanged(QQuickPointerHandler *, QQuickEventP // (e.g. between DragHandler and PinchHandler). if (transition == QQuickEventPoint::UngrabExclusive || transition == QQuickEventPoint::CancelGrabExclusive) d->currentPoints.clear(); + if (grabber != this) + return; + switch (transition) { + case QQuickEventPoint::GrabExclusive: + case QQuickEventPoint::GrabPassive: + case QQuickEventPoint::UngrabPassive: + case QQuickEventPoint::UngrabExclusive: + case QQuickEventPoint::CancelGrabPassive: + case QQuickEventPoint::CancelGrabExclusive: + QQuickPointerHandler::onGrabChanged(grabber, transition, point); + break; + case QQuickEventPoint::OverrideGrabPassive: + return; // don't emit + } } QVector QQuickMultiPointHandler::eligiblePoints(QQuickPointerEvent *event) diff --git a/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp b/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp index 19fdae3b44..2b8ddd197d 100644 --- a/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp @@ -217,6 +217,7 @@ void tst_QQuickPinchHandler::scale() QQuickPinchHandler *pinchHandler = window->rootObject()->findChild("pinchHandler"); QVERIFY(pinchHandler != nullptr); + QSignalSpy grabChangedSpy(pinchHandler, SIGNAL(grabChanged(QQuickEventPoint::GrabTransition, QQuickEventPoint*))); QQuickItem *root = qobject_cast(window->rootObject()); QVERIFY(root != nullptr); @@ -238,6 +239,7 @@ void tst_QQuickPinchHandler::scale() // it is outside its bounds. pinchSequence.stationary(0).press(1, p1, window).commit(); QQuickTouchUtils::flush(window); + QTRY_COMPARE(grabChangedSpy.count(), 1); // passive grab QPoint pd(10, 10); // move one point until PinchHandler activates @@ -247,6 +249,8 @@ void tst_QQuickPinchHandler::scale() QQuickTouchUtils::flush(window); } QCOMPARE(pinchHandler->active(), true); + // first point got a passive grab; both points got exclusive grabs + QCOMPARE(grabChangedSpy.count(), 3); QLineF line(p0, p1); const qreal startLength = line.length(); diff --git a/tests/manual/pointer/pinchHandler.qml b/tests/manual/pointer/pinchHandler.qml index 46ab91c2ed..93169da60a 100644 --- a/tests/manual/pointer/pinchHandler.qml +++ b/tests/manual/pointer/pinchHandler.qml @@ -154,6 +154,14 @@ Rectangle { if (!active) anim.restart(centroid.velocity) } + onGrabChanged: function (transition, point) { + if (transition === 0x10) { // GrabExclusive + console.log(point.id, "grabbed @", point.position) + Qt.createQmlObject("import QtQuick 2.0; Rectangle { opacity: 0.5; border.color: 'red'; radius: 8; width: radius * 2; height: radius * 2; " + + "x: " + (point.position.x - 8) + "; y: " + (point.position.y - 8) + "}", + rect3, "touchpoint" + point.id); + } + } } TapHandler { gesturePolicy: TapHandler.DragThreshold; onTapped: rect3.z = rect2.z + 1 } MomentumAnimation { id: anim; target: rect3 } -- cgit v1.2.3 From 696d607d8596bd57e957a23778382d546196ef34 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Wed, 12 May 2021 23:22:11 +0200 Subject: Don't let PointerHandler steal mouse grab from keepMouseGrab layer As explained in the comment, the handler can override the keepMouseGrab "veto" if the item is a parent (like a Flickable) that filters events, but not in other cases. The logic was wrong though, apparently. Amends 090f404cf80da35734f712b02cc1543acecd5b62 Fixes: QTBUG-78258 Task-number: QTBUG-79163 Change-Id: I9a473ab3b23743f863cb0be13767fdbc29cd5e1c Reviewed-by: Fabian Kosmale Reviewed-by: Shawn Rutledge Reviewed-by: Qt CI Bot (cherry picked from commit b09ce7dcd8ecf24ef23da8197a64e3fced3fc894) --- src/quick/handlers/qquickpointerhandler.cpp | 27 +++++++---- .../data/dragHandlerUnderModalLayer.qml | 34 ++++++++++++++ .../qquickdraghandler/tst_qquickdraghandler.cpp | 54 ++++++++++++++++++++++ 3 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 tests/auto/quick/pointerhandlers/qquickdraghandler/data/dragHandlerUnderModalLayer.qml diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp index e46bd897a6..2e6bcec594 100644 --- a/src/quick/handlers/qquickpointerhandler.cpp +++ b/src/quick/handlers/qquickpointerhandler.cpp @@ -346,10 +346,16 @@ bool QQuickPointerHandler::approveGrabTransition(QQuickEventPoint *point, QObjec existingPhGrabber->metaObject()->className() == metaObject()->className()) allowed = true; } else if ((d->grabPermissions & CanTakeOverFromItems)) { + allowed = true; QQuickItem * existingItemGrabber = point->grabberItem(); - if (existingItemGrabber && !((existingItemGrabber->keepMouseGrab() && point->pointerEvent()->asPointerMouseEvent()) || - (existingItemGrabber->keepTouchGrab() && point->pointerEvent()->asPointerTouchEvent()))) { - allowed = true; + QQuickWindowPrivate *winPriv = QQuickWindowPrivate::get(parentItem()->window()); + const bool isMouse = point->pointerEvent()->asPointerMouseEvent(); + const bool isTouch = point->pointerEvent()->asPointerTouchEvent(); + if (existingItemGrabber && + ((existingItemGrabber->keepMouseGrab() && + (isMouse || winPriv->isDeliveringTouchAsMouse())) || + (existingItemGrabber->keepTouchGrab() && isTouch))) { + allowed = false; // If the handler wants to steal the exclusive grab from an Item, the Item can usually veto // by having its keepMouseGrab flag set. But an exception is if that Item is a parent that // normally filters events (such as a Flickable): it needs to be possible for e.g. a @@ -358,14 +364,19 @@ bool QQuickPointerHandler::approveGrabTransition(QQuickEventPoint *point, QObjec // at first and then expects to be able to steal the grab later on. It cannot respect // Flickable's wishes in that case, because then it would never have a chance. if (existingItemGrabber->keepMouseGrab() && - !(existingItemGrabber->filtersChildMouseEvents() && existingItemGrabber->isAncestorOf(parentItem()))) { - QQuickWindowPrivate *winPriv = QQuickWindowPrivate::get(parentItem()->window()); + existingItemGrabber->filtersChildMouseEvents() && existingItemGrabber->isAncestorOf(parentItem())) { if (winPriv->isDeliveringTouchAsMouse() && point->pointId() == winPriv->touchMouseId) { - qCDebug(lcPointerHandlerGrab) << this << "wants to grab touchpoint" << point->pointId() - << "but declines to steal grab from touch-mouse grabber with keepMouseGrab=true" << existingItemGrabber; - allowed = false; + qCDebug(lcPointerHandlerGrab) << this << "steals touchpoint" << point->pointId() + << "despite parent touch-mouse grabber with keepMouseGrab=true" << existingItemGrabber; + allowed = true; } } + if (!allowed) { + qCDebug(lcPointerHandlerGrab) << this << "wants to grab point" << point->pointId() + << "but declines to steal from grabber" << existingItemGrabber + << "with keepMouseGrab=" << existingItemGrabber->keepMouseGrab() + << "keepTouchGrab=" << existingItemGrabber->keepTouchGrab(); + } } } } diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/data/dragHandlerUnderModalLayer.qml b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/dragHandlerUnderModalLayer.qml new file mode 100644 index 0000000000..b24812c914 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/data/dragHandlerUnderModalLayer.qml @@ -0,0 +1,34 @@ +import QtQuick 2.15 + +import Test 1.0 + +Item { + width: 640 + height: 480 + + Rectangle { + anchors.fill: parent + color: "grey" + + Rectangle { + x: 200 + y: 200 + width: 100 + height: 100 + color: "orange" + DragHandler { + grabPermissions: DragHandler.CanTakeOverFromAnything // but not anything with keepMouseGrab! + } + } + } + + ModalLayer { + anchors.fill: parent + + Rectangle { + anchors.fill: parent + color: "red" + opacity: 0.4 + } + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp b/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp index 4d6866041e..f71febbaf9 100644 --- a/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp @@ -68,6 +68,7 @@ private slots: void touchPassiveGrabbers_data(); void touchPassiveGrabbers(); void touchPinchAndMouseMove(); + void underModalLayer(); private: void createView(QScopedPointer &window, const char *fileName); @@ -811,6 +812,59 @@ void tst_DragHandler::touchPinchAndMouseMove() } } +class ModalLayer : public QQuickItem { +public: + explicit ModalLayer(QQuickItem* parent = nullptr) : QQuickItem(parent) { + this->setAcceptedMouseButtons(Qt::AllButtons); + this->setAcceptTouchEvents(true); + this->setKeepMouseGrab(true); + this->setKeepTouchGrab(true); + } + + bool event(QEvent* event) override { + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::MouseMove: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseTrackingChange: + case QEvent::MouseButtonDblClick: + case QEvent::Wheel: + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchCancel: + case QEvent::TouchEnd: { + qCDebug(lcPointerTests) << "BLOCK!" << event->type(); + return true; + } + default: break; + } + return QQuickItem::event(event); + } +}; + +void tst_DragHandler::underModalLayer() // QTBUG-78258 +{ + qmlRegisterType("Test", 1, 0, "ModalLayer"); + + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer windowPtr; + createView(windowPtr, "dragHandlerUnderModalLayer.qml"); + QQuickView * window = windowPtr.data(); + QPointer dragHandler = window->rootObject()->findChild(); + QVERIFY(dragHandler); + + QPoint p1(250, 250); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + p1 += QPoint(dragThreshold, dragThreshold); + QTest::mouseMove(window, p1); + QVERIFY(!dragHandler->active()); + p1 += QPoint(dragThreshold, dragThreshold); + QTest::mouseMove(window, p1); + QVERIFY(!dragHandler->active()); + QTest::mouseRelease(window, Qt::LeftButton); +} + QTEST_MAIN(tst_DragHandler) #include "tst_qquickdraghandler.moc" -- cgit v1.2.3 From c50aefa2bc62f3c3f065c1aa640aaa5e0e58abcd Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Tue, 20 Jul 2021 16:39:08 +0200 Subject: Handle QScreen::physicalDotsPerInchChanged changes for the initial screen We connect to the signal when the screens change (in QQuickWindow::handleScreenChanged), but never for the initial screen. Not connecting means that changing the DPR of the initial screen doesn't re-render QQuickItems with the new DPR, which was in particular visible for QQuickImage when displaying SVGs. Task-number: QTBUG-94622 Change-Id: I44ff3f8f3713d5a7bba8b6b8b4d5ca14530fe373 Reviewed-by: Fabian Kosmale Reviewed-by: Shawn Rutledge (cherry picked from commit 7e39a2204382a78ac6ff1e32dfe29dcbce65004c) Reviewed-by: Qt Cherry-pick Bot --- src/quick/items/qquickwindow.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 344a767afb..3ab9313102 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -450,12 +450,13 @@ void QQuickWindow::physicalDpiChanged() void QQuickWindow::handleScreenChanged(QScreen *screen) { Q_D(QQuickWindow); + // we connected to the initial screen in QQuickWindowPrivate::init, but the screen changed disconnect(d->physicalDpiChangedConnection); if (screen) { physicalDpiChanged(); // When physical DPI changes on the same screen, either the resolution or the device pixel // ratio changed. We must check what it is. Device pixel ratio does not have its own - // ...Changed() signal. + // ...Changed() signal. Reconnect, same as in QQuickWindowPrivate::init. d->physicalDpiChangedConnection = connect(screen, &QScreen::physicalDotsPerInchChanged, this, &QQuickWindow::physicalDpiChanged); } @@ -707,8 +708,13 @@ void QQuickWindowPrivate::init(QQuickWindow *c, QQuickRenderControl *control) Q_ASSERT(windowManager || renderControl); - if (QScreen *screen = q->screen()) - devicePixelRatio = screen->devicePixelRatio(); + if (QScreen *screen = q->screen()) { + devicePixelRatio = screen->devicePixelRatio(); + // if the screen changes, then QQuickWindow::handleScreenChanged disconnects + // and connects to the new screen + physicalDpiChangedConnection = QObject::connect(screen, &QScreen::physicalDotsPerInchChanged, + q, &QQuickWindow::physicalDpiChanged); + } QSGContext *sg; if (renderControl) { -- cgit v1.2.3 From a7adcc6933a7a72bd74f41793874a87c5e33dbdd Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Fri, 16 Jul 2021 11:50:06 +0900 Subject: Add tests of matrix4x4 transformation functions These functions have been added at ded64d0368 "Make the qml/js matrix4x4 type more useful." Let's add some tests as I want to add another overload of rotate() function. Change-Id: I12e5970b8e3a6709e9f794c9713feb277cc8c449 Reviewed-by: Ulf Hermann Reviewed-by: Shawn Rutledge (cherry picked from commit b4866c499a6b2d1f42b77f08287988b01118c9f6) Reviewed-by: Qt Cherry-pick Bot --- .../qqmlvaluetypes/data/matrix4x4_invokables.qml | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml b/tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml index aa26956922..09f1d472b7 100644 --- a/tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml +++ b/tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml @@ -10,6 +10,59 @@ Item { property variant v2: Qt.vector3d(1,2,3) property real factor: 2.23 + function testTransformation() { + let m = Qt.matrix4x4(); + + m.scale(1, 2, 4); + if (m !== Qt.matrix4x4(1, 0, 0, 0, + 0, 2, 0, 0, + 0, 0, 4, 0, + 0, 0, 0, 1)) + return false; + m.scale(Qt.vector3d(-8, -4, -2)); + if (m !== Qt.matrix4x4(-8, 0, 0, 0, + 0,-8, 0, 0, + 0, 0, -8, 0, + 0, 0, 0, 1)) + return false; + m.scale(-1 / 8); + if (m !== Qt.matrix4x4()) + return false; + + m.rotate(180, Qt.vector3d(1, 0, 0)); + if (m !== Qt.matrix4x4(1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, -1, 0, + 0, 0, 0, 1)) + return false; + m.rotate(180, Qt.vector3d(0, 1, 0)); + if (m !== Qt.matrix4x4(-1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1)) + return false; + m.rotate(180, Qt.vector3d(0, 0, 1)); + if (m !== Qt.matrix4x4()) + return false; + + m.translate(Qt.vector3d(1, 2, 4)); + if (m !== Qt.matrix4x4(1, 0, 0, 1, + 0, 1, 0, 2, + 0, 0, 1, 4, + 0, 0, 0, 1)) + return false; + + m = Qt.matrix4x4(); + m.lookAt(Qt.vector3d(1, 2, 4), Qt.vector3d(1, 2, 0), Qt.vector3d(0, 1, 0)); + if (m !== Qt.matrix4x4(1, 0, 0, -1, + 0, 1, 0, -2, + 0, 0, 1, -4, + 0, 0, 0, 1)) + return false; + + return true; + } + Component.onCompleted: { success = true; if (m1.times(m2) != Qt.matrix4x4(26, 26, 26, 26, 52, 52, 52, 52, 78, 78, 78, 78, 104, 104, 104, 104)) success = false; @@ -27,5 +80,6 @@ Item { if (m1.transposed() != Qt.matrix4x4(1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4)) success = false; if (m1.fuzzyEquals(m2)) success = false; if (!m1.fuzzyEquals(m2, 10)) success = false; + if (!testTransformation()) success = false; } } -- cgit v1.2.3 From 556cd479823a0d0e88e5fa1a18368d23eb8f87f5 Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Fri, 16 Jul 2021 12:15:08 +0900 Subject: Doc: Document matrix4x4 transformation functions These functions have been added at ded64d0368 "Make the qml/js matrix4x4 type more useful." Change-Id: Ia61eb5c7c058e2b94bb1b8b0e7bd56371feadca1 Reviewed-by: Ulf Hermann Reviewed-by: Shawn Rutledge (cherry picked from commit d3b99595aee699dc4f01fb3658e0b33ba313f9f2) Reviewed-by: Qt Cherry-pick Bot --- src/quick/doc/src/qmltypereference.qdoc | 67 +++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/quick/doc/src/qmltypereference.qdoc b/src/quick/doc/src/qmltypereference.qdoc index 528444cad3..65de1284a4 100644 --- a/src/quick/doc/src/qmltypereference.qdoc +++ b/src/quick/doc/src/qmltypereference.qdoc @@ -739,6 +739,73 @@ console.log(c + " " + d); // false true \li Description \li Example + \row + \li translate(vector3d vector) + \li Multiplies \c this matrix4x4 by another that translates coordinates by the components + of \c vector + \li \code +var m = Qt.matrix4x4(); +m.translate(Qt.vector3d(1,2,3)); +console.log(m.toString()); +// QMatrix4x4(1, 0, 0, 1, 0, 1, 0, 2, 0, 0, 1, 3, 0, 0, 0, 1) + \endcode + + \row + \li rotate(real angle, vector3d axis) + \li Multiples \c this matrix4x4 by another that rotates coordinates through + \c angle degrees about \c axis + \li \code +var m = Qt.matrix4x4(); +m.rotate(180,vector3d(1,0,0)); +console.log(m.toString()); +// QMatrix4x4(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1) + \endcode + + \row + \li scale(real factor) + \li Multiplies \c this matrix4x4 by another that scales coordinates by the given \c factor + \li \code +var m = Qt.matrix4x4(); +m.scale(2); +console.log(m.toString()); +// QMatrix4x4(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1) + \endcode + + \row + \li scale(real x, real y, real z) + \li Multiplies \c this matrix4x4 by another that scales coordinates by the components + \c x, \c y, and \c z + \li \code +var m = Qt.matrix4x4(); +m.scale(1,2,3); +console.log(m.toString()); +// QMatrix4x4(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1) + \endcode + + \row + \li scale(vector3d vector) + \li Multiplies \c this matrix4x4 by another that scales coordinates by the components + of \c vector + \li \code +var m = Qt.matrix4x4(); +m.scale(Qt.vector3d(1,2,3)); +console.log(m.toString()); +// QMatrix4x4(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1) + \endcode + + \row + \li lookAt(vector3d eye, vector3d center, vector3d up) + \li Multiplies \c this matrix4x4 by a viewing matrix derived from an \c eye point. + The \c center vector3d indicates the center of the view that the \c eye is looking at. + The \c up vector3d indicates which direction should be considered up with respect to + the \c eye. + \li \code +var m = Qt.matrix4x4(); +m.lookAt(Qt.vector3d(1,2,3),Qt.vector3d(1,2,0),Qt.vector3d(0,1,0)); +console.log(m.toString()); +// QMatrix4x4(1, 0, 0, -1, 0, 1, 0, -2, 0, 0, 1, -3, 0, 0, 0, 1) + \endcode + \row \li matrix4x4 times(matrix4x4 other) \li Returns the matrix4x4 result of multiplying \c this matrix4x4 with -- cgit v1.2.3 From 040f7b93b44b5b7880a8e07f878158440fcb2f31 Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Tue, 20 Jul 2021 16:54:00 +0200 Subject: Update QQuickImage's DPR if re-loading the image didn't An SVG doesn't have a DPR, but we need to reload and repaint it anyway when the DPR of the screen changes. So store the DPR of the item explicitly if reloading it didn't change the value (for instance, because sourceSize was not set). Otherwise, DPR changes are only handled when moving from a 1.0 (the default) DPR display to a different display, but not when moving back. Fixes: QTBUG-94622 Change-Id: I9f0a847699ab027ef876e341b8c6a954a6167ab3 Reviewed-by: Shawn Rutledge (cherry picked from commit 08838f434b55024d9d5a9252fc2a3fb782b087da) Reviewed-by: Qt Cherry-pick Bot --- src/quick/items/qquickimagebase.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/quick/items/qquickimagebase.cpp b/src/quick/items/qquickimagebase.cpp index 1cf9d88795..2ad3683478 100644 --- a/src/quick/items/qquickimagebase.cpp +++ b/src/quick/items/qquickimagebase.cpp @@ -56,8 +56,8 @@ QT_BEGIN_NAMESPACE bool QQuickImageBasePrivate::updateDevicePixelRatio(qreal targetDevicePixelRatio) { // QQuickImageProvider and SVG and PDF can generate a high resolution image when - // sourceSize is set (this function is only called if it's set). - // If sourceSize is not set then the provider default size will be used, as usual. + // sourceSize is set. If sourceSize is not set then the provider default size will + // be used, as usual. bool setDevicePixelRatio = false; if (url.scheme() == QLatin1String("image")) { setDevicePixelRatio = true; @@ -418,10 +418,14 @@ void QQuickImageBase::itemChange(ItemChange change, const ItemChangeData &value) Q_D(QQuickImageBase); // If the screen DPI changed, reload image. if (change == ItemDevicePixelRatioHasChanged && value.realValue != d->devicePixelRatio) { + const auto oldDpr = d->devicePixelRatio; // ### how can we get here with !qmlEngine(this)? that implies // itemChange() on an item pending deletion, which seems strange. if (qmlEngine(this) && isComponentComplete() && d->url.isValid()) { load(); + // not changed when loading (sourceSize might not be set) + if (d->devicePixelRatio == oldDpr) + d->updateDevicePixelRatio(value.realValue); } } QQuickItem::itemChange(change, value); -- cgit v1.2.3 From 95e720471ad28cae66b24e7809897f148311599f Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Thu, 22 Jul 2021 09:56:04 +0200 Subject: Pass the focus reason through to the control on mouse press Otherwise, a TextInput item will always receive OtherFocusReason when clicking into it. Add a test that simulates focus changes with mouse and keys, records the incoming focus events and verifies the correct focus reason is set in the event. Qt Quick provides no mechanism to access the focus reason from within QML (onActiveFocusChange only has a bool parameter, the focusReason property exists only in QtQuickControl). Task-number: QTBUG-75862 Change-Id: Ifea95c7ef2ac88c6c8e8bbdc8d2dbe5205ff922e Reviewed-by: Qt CI Bot Reviewed-by: Fabian Kosmale (cherry picked from commit bbcbb7c78f2f07e6c5e380ec5c018ac2a7abf292) Reviewed-by: Qt Cherry-pick Bot --- src/quick/items/qquicktextinput.cpp | 8 +-- src/quick/items/qquicktextinput_p.h | 2 +- .../quick/qquicktextinput/data/focusReason.qml | 39 +++++++++++ .../quick/qquicktextinput/tst_qquicktextinput.cpp | 80 ++++++++++++++++++++++ 4 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 tests/auto/quick/qquicktextinput/data/focusReason.qml diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index c6944690f9..498fe8ab84 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -1585,7 +1585,7 @@ void QQuickTextInput::mousePressEvent(QMouseEvent *event) d->moveCursor(cursor, mark); if (d->focusOnPress && !qGuiApp->styleHints()->setFocusOnTouchRelease()) - ensureActiveFocus(); + ensureActiveFocus(Qt::MouseFocusReason); event->setAccepted(true); } @@ -1637,7 +1637,7 @@ void QQuickTextInput::mouseReleaseEvent(QMouseEvent *event) #endif if (d->focusOnPress && qGuiApp->styleHints()->setFocusOnTouchRelease()) - ensureActiveFocus(); + ensureActiveFocus(Qt::MouseFocusReason); if (!event->isAccepted()) QQuickImplicitSizeItem::mouseReleaseEvent(event); @@ -1872,10 +1872,10 @@ void QQuickTextInput::invalidateFontCaches() d->m_textLayout.engine()->resetFontEngineCache(); } -void QQuickTextInput::ensureActiveFocus() +void QQuickTextInput::ensureActiveFocus(Qt::FocusReason reason) { bool hadActiveFocus = hasActiveFocus(); - forceActiveFocus(); + forceActiveFocus(reason); #if QT_CONFIG(im) Q_D(QQuickTextInput); // re-open input panel on press if already focused diff --git a/src/quick/items/qquicktextinput_p.h b/src/quick/items/qquicktextinput_p.h index 0105a2be2c..5be71467e6 100644 --- a/src/quick/items/qquicktextinput_p.h +++ b/src/quick/items/qquicktextinput_p.h @@ -361,7 +361,7 @@ Q_SIGNALS: private: void invalidateFontCaches(); - void ensureActiveFocus(); + void ensureActiveFocus(Qt::FocusReason reason); protected: QQuickTextInput(QQuickTextInputPrivate &dd, QQuickItem *parent = nullptr); diff --git a/tests/auto/quick/qquicktextinput/data/focusReason.qml b/tests/auto/quick/qquicktextinput/data/focusReason.qml new file mode 100644 index 0000000000..7ac913d363 --- /dev/null +++ b/tests/auto/quick/qquicktextinput/data/focusReason.qml @@ -0,0 +1,39 @@ +import QtQuick 2.2 + +Rectangle { + width: 400 + height: 400 + + Column { + spacing: 5 + TextInput { + id: first + objectName: "first" + width: 100 + Rectangle { anchors.fill: parent; color: parent.activeFocus ? "red" : "blue"; opacity: 0.3 } + KeyNavigation.backtab: third + KeyNavigation.tab: second + KeyNavigation.down: second + } + TextInput { + id: second + objectName: "second" + width: 100 + Rectangle { anchors.fill: parent; color: parent.activeFocus ? "red" : "blue"; opacity: 0.3 } + KeyNavigation.up: first + KeyNavigation.backtab: first + KeyNavigation.tab: third + } + TextInput { + objectName: "third" + id: third + width: 100 + Rectangle { anchors.fill: parent; color: parent.activeFocus ? "red" : "blue"; opacity: 0.3 } + KeyNavigation.backtab: second + KeyNavigation.tab: first + } + Component.onCompleted: { + first.focus = true + } + } +} diff --git a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp index ac502bcb28..7c5c09055d 100644 --- a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp +++ b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp @@ -237,6 +237,8 @@ private slots: void QTBUG_77814_InsertRemoveNoSelection(); void checkCursorDelegateWhenPaddingChanged(); + + void focusReason(); private: void simulateKey(QWindow *, int key); @@ -7084,6 +7086,84 @@ void tst_qquicktextinput::checkCursorDelegateWhenPaddingChanged() QCOMPARE(cursorDelegate->y(), textInput->topPadding()); } +/*! + Verifies that TextInput items get focus in/out events with the + correct focus reason set. + + Up and Down keys translates to Backtab and Tab focus reasons. + + See QTBUG-75862. +*/ +void tst_qquicktextinput::focusReason() +{ + QQuickView view; + view.setSource(testFileUrl("focusReason.qml")); + + QQuickTextInput *first = view.rootObject()->findChild("first"); + QQuickTextInput *second = view.rootObject()->findChild("second"); + QQuickTextInput *third = view.rootObject()->findChild("third"); + QVERIFY(first && second && third); + + class FocusEventFilter : public QObject + { + public: + using QObject::QObject; + + QHash lastFocusReason; + protected: + bool eventFilter(QObject *o, QEvent *e) + { + if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) { + QFocusEvent *fe = static_cast(e); + lastFocusReason[o] = fe->reason(); + } + return QObject::eventFilter(o, e); + } + } eventFilter; + first->installEventFilter(&eventFilter); + second->installEventFilter(&eventFilter); + third->installEventFilter(&eventFilter); + + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QCOMPARE(qApp->focusObject(), first); + // on some platforms we don't get ActiveWindowFocusReason; tolerate this, + // it's not what we are testing in this test + if (eventFilter.lastFocusReason[first] != Qt::ActiveWindowFocusReason) { + QEXPECT_FAIL("", qPrintable(QString("No window activation event on the %1 platform") + .arg(QGuiApplication::platformName())), + Continue); + } + QCOMPARE(eventFilter.lastFocusReason[first], Qt::ActiveWindowFocusReason); + + QTest::mouseClick(&view, Qt::LeftButton, {}, + (second->boundingRect().center() + second->position()).toPoint()); + QTRY_COMPARE(qApp->focusObject(), second); + QCOMPARE(eventFilter.lastFocusReason[first], Qt::MouseFocusReason); + QCOMPARE(eventFilter.lastFocusReason[second], Qt::MouseFocusReason); + + QTest::keyClick(&view, Qt::Key_Tab); + QCOMPARE(qApp->focusObject(), third); + QCOMPARE(eventFilter.lastFocusReason[second], Qt::TabFocusReason); + QCOMPARE(eventFilter.lastFocusReason[third], Qt::TabFocusReason); + + QTest::keyClick(&view, Qt::Key_Backtab); + QCOMPARE(qApp->focusObject(), second); + QCOMPARE(eventFilter.lastFocusReason[third], Qt::BacktabFocusReason); + QCOMPARE(eventFilter.lastFocusReason[second], Qt::BacktabFocusReason); + + QTest::keyClick(&view, Qt::Key_Up); + QCOMPARE(qApp->focusObject(), first); + QCOMPARE(eventFilter.lastFocusReason[second], Qt::BacktabFocusReason); + QCOMPARE(eventFilter.lastFocusReason[first], Qt::BacktabFocusReason); + + QTest::keyClick(&view, Qt::Key_Down); + QCOMPARE(qApp->focusObject(), second); + QCOMPARE(eventFilter.lastFocusReason[second], Qt::TabFocusReason); + QCOMPARE(eventFilter.lastFocusReason[first], Qt::TabFocusReason); +} + QTEST_MAIN(tst_qquicktextinput) #include "tst_qquicktextinput.moc" -- cgit v1.2.3 From 7f5bd320dee4281cec70e4e4f6e3c03db4fa0450 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Tue, 20 Jul 2021 13:38:34 +0200 Subject: Avoid infinite loop in designer support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-number: QTBUG-94928 Change-Id: I1ee14600fb0fd9f0ee499546e3ffcd66114aaeff Reviewed-by: Thomas Hartmann (cherry picked from commit d3cae36550fe8b82c641cef6a207e991a9064d85) Reviewed-by: Henning Gründl --- .../designer/qquickdesignersupportproperties.cpp | 24 +++++++++--- .../designer/qquickdesignersupportproperties_p.h | 3 +- .../data/RecursiveProperty.qml | 8 ++++ .../data/propertyNameTest.qml | 13 +++++++ .../tst_qquickdesignersupport.cpp | 45 ++++++++++++++++++++++ 5 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 tests/auto/quick/qquickdesignersupport/data/RecursiveProperty.qml create mode 100644 tests/auto/quick/qquickdesignersupport/data/propertyNameTest.qml diff --git a/src/quick/designer/qquickdesignersupportproperties.cpp b/src/quick/designer/qquickdesignersupportproperties.cpp index 06cb9a17cf..b6e88dbfe9 100644 --- a/src/quick/designer/qquickdesignersupportproperties.cpp +++ b/src/quick/designer/qquickdesignersupportproperties.cpp @@ -128,10 +128,14 @@ void QQuickDesignerSupportProperties::getPropertyCache(QObject *object, QQmlEngi static QQuickDesignerSupport::PropertyNameList propertyNameListForWritableProperties(QObject *object, const QQuickDesignerSupport::PropertyName &baseName, - QObjectList *inspectedObjects) + QObjectList *inspectedObjects, + int depth = 0) { QQuickDesignerSupport::PropertyNameList propertyNameList; + if (depth > 2) + return propertyNameList; + if (!inspectedObjects->contains(object)) inspectedObjects->append(object); @@ -145,14 +149,16 @@ static QQuickDesignerSupport::PropertyNameList propertyNameListForWritableProper if (childObject) propertyNameList.append(propertyNameListForWritableProperties(childObject, baseName + QQuickDesignerSupport::PropertyName(metaProperty.name()) - + '.', inspectedObjects)); + + '.', inspectedObjects, + depth + 1)); } } else if (QQmlGadgetPtrWrapper *valueType = QQmlGadgetPtrWrapper::instance(qmlEngine(object), metaProperty.userType())) { valueType->setValue(metaProperty.read(object)); propertyNameList.append(propertyNameListForWritableProperties(valueType, baseName + QQuickDesignerSupport::PropertyName(metaProperty.name()) - + '.', inspectedObjects)); + + '.', inspectedObjects, + depth + 1)); } if (metaProperty.isReadable() && metaProperty.isWritable()) { @@ -183,7 +189,8 @@ bool QQuickDesignerSupportProperties::isPropertyBlackListed(const QQuickDesigner QQuickDesignerSupport::PropertyNameList QQuickDesignerSupportProperties::allPropertyNames(QObject *object, const QQuickDesignerSupport::PropertyName &baseName, - QObjectList *inspectedObjects) + QObjectList *inspectedObjects, + int depth) { QQuickDesignerSupport::PropertyNameList propertyNameList; @@ -192,6 +199,9 @@ QQuickDesignerSupport::PropertyNameList QQuickDesignerSupportProperties::allProp if (inspectedObjects == nullptr) inspectedObjects = &localObjectList; + if (depth > 2) + return propertyNameList; + if (!inspectedObjects->contains(object)) inspectedObjects->append(object); @@ -215,7 +225,8 @@ QQuickDesignerSupport::PropertyNameList QQuickDesignerSupportProperties::allProp propertyNameList.append(allPropertyNames(childObject, baseName + QQuickDesignerSupport::PropertyName(metaProperty.name()) - + '.', inspectedObjects)); + + '.', inspectedObjects, + depth + 1)); } } else if (QQmlGadgetPtrWrapper *valueType = QQmlGadgetPtrWrapper::instance(qmlEngine(object), metaProperty.userType())) { @@ -224,7 +235,8 @@ QQuickDesignerSupport::PropertyNameList QQuickDesignerSupportProperties::allProp propertyNameList.append(allPropertyNames(valueType, baseName + QQuickDesignerSupport::PropertyName(metaProperty.name()) - + '.', inspectedObjects)); + + '.', inspectedObjects, + depth + 1)); } else { addToPropertyNameListIfNotBlackListed(&propertyNameList, baseName + QQuickDesignerSupport::PropertyName(metaProperty.name())); diff --git a/src/quick/designer/qquickdesignersupportproperties_p.h b/src/quick/designer/qquickdesignersupportproperties_p.h index 0b81533872..c62d4913cb 100644 --- a/src/quick/designer/qquickdesignersupportproperties_p.h +++ b/src/quick/designer/qquickdesignersupportproperties_p.h @@ -93,7 +93,8 @@ public: static QQuickDesignerSupport::PropertyNameList propertyNameListForWritableProperties(QObject *object); static QQuickDesignerSupport::PropertyNameList allPropertyNames(QObject *object, const QQuickDesignerSupport::PropertyName &baseName = QQuickDesignerSupport::PropertyName(), - QObjectList *inspectedObjects = nullptr); + QObjectList *inspectedObjects = nullptr, + int depth = 0); static bool hasFullImplementedListInterface(const QQmlListReference &list); }; diff --git a/tests/auto/quick/qquickdesignersupport/data/RecursiveProperty.qml b/tests/auto/quick/qquickdesignersupport/data/RecursiveProperty.qml new file mode 100644 index 0000000000..ec419f8935 --- /dev/null +++ b/tests/auto/quick/qquickdesignersupport/data/RecursiveProperty.qml @@ -0,0 +1,8 @@ +import QtQuick 2.0 + +Item { + id: myObject + readonly property int testProperty: 0 + readonly property QtObject myproperty: myObject +} + diff --git a/tests/auto/quick/qquickdesignersupport/data/propertyNameTest.qml b/tests/auto/quick/qquickdesignersupport/data/propertyNameTest.qml new file mode 100644 index 0000000000..88fd8509cc --- /dev/null +++ b/tests/auto/quick/qquickdesignersupport/data/propertyNameTest.qml @@ -0,0 +1,13 @@ +import QtQuick 2.11 + +Rectangle { + objectName: "rootItem" + color: "white" + width: 800 + height: 600 + + RecursiveProperty { + objectName: "recursiveProperty" + + } +} diff --git a/tests/auto/quick/qquickdesignersupport/tst_qquickdesignersupport.cpp b/tests/auto/quick/qquickdesignersupport/tst_qquickdesignersupport.cpp index b44977bd5a..0471619049 100644 --- a/tests/auto/quick/qquickdesignersupport/tst_qquickdesignersupport.cpp +++ b/tests/auto/quick/qquickdesignersupport/tst_qquickdesignersupport.cpp @@ -62,6 +62,7 @@ private slots: void testNotifyPropertyChangeCallBack(); void testFixResourcePathsForObjectCallBack(); void testComponentOnCompleteSignal(); + void testPropertyNames(); }; void tst_qquickdesignersupport::customData() @@ -586,6 +587,50 @@ void tst_qquickdesignersupport::testComponentOnCompleteSignal() } } +void tst_qquickdesignersupport::testPropertyNames() +{ +#ifdef Q_CC_MINGW + QSKIP("QQuickDesignerSupportProperties::registerCustomData segfaults on mingw. QTBUG-90869"); +#endif + + QScopedPointer view(new QQuickView); + view->engine()->setOutputWarningsToStandardError(false); + view->setSource(testFileUrl("propertyNameTest.qml")); + + QVERIFY(view->errors().isEmpty()); + QQuickItem *rootItem = view->rootObject(); + QVERIFY(rootItem); + + QQuickDesignerSupport::PropertyNameList names = QQuickDesignerSupportProperties::allPropertyNames(rootItem); + QVERIFY(!names.isEmpty()); + QVERIFY(names.contains("width")); + QVERIFY(names.contains("height")); + QVERIFY(names.contains("clip")); + QVERIFY(names.contains("opacity")); + QVERIFY(names.contains("childrenRect")); + QVERIFY(names.contains("activeFocus")); + QVERIFY(names.contains("border.width")); + names = QQuickDesignerSupportProperties::propertyNameListForWritableProperties(rootItem); + QVERIFY(!names.isEmpty()); + QVERIFY(names.contains("width")); + QVERIFY(names.contains("height")); + QVERIFY(names.contains("opacity")); + QVERIFY(names.contains("clip")); + QVERIFY(!names.contains("childrenRect")); + QVERIFY(!names.contains("activeFocus")); + QVERIFY(names.contains("border.width")); + + QQuickItem *recursiveProperty = findItem(rootItem, QLatin1String("recursiveProperty")); + QVERIFY(recursiveProperty); + names = QQuickDesignerSupportProperties::allPropertyNames(recursiveProperty); + QVERIFY(!names.isEmpty()); + QVERIFY(names.contains("testProperty")); + QVERIFY(names.contains("myproperty.testProperty")); + + names = QQuickDesignerSupportProperties::propertyNameListForWritableProperties(recursiveProperty); + QVERIFY(!names.isEmpty()); + QVERIFY(!names.contains("testProperty")); +} QTEST_MAIN(tst_qquickdesignersupport) -- cgit v1.2.3 From 44f6a797563c084a1eaa763e8e6f3ceaeb936bd4 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Mon, 26 Jul 2021 13:47:56 +0200 Subject: Sync shadereffect materialType cleanup between renderloops The render loops behave differently: the threaded one cleans the "material cache" (ill-named for legacy GL, it is correctly called material _type_ cache on the newer RHI code path) every time the window is unexposed. The basic one keeps it and only cleans the cache (and so destroys the opaque QSGMaterialType objects) only when the scenegraph (i.e. QSGNodes and related geometry and materials) gets released. The former is incorrect, because if the QSGMaterial does not go away, the QSGMaterialType should stay valid too. Strictly speaking this is not a fatal error because the QSGMaterialType pointers are never dereferenced. However, potential reuse of the same address due to a subsequent 'new QSGMaterialType' is an issue. Just switch to the latter everywhere, and also, to be fully pedantic, make the order uniform across all 3 render loops: first destroy the scene tree (where each QSGGeometryNode destroys its QSGMaterial if owned), then the cache (which owns the QSGMaterialTypes for the now-gone shadereffect nodes). Fixes: QTBUG-94844 Change-Id: Iebbf34d36b9eccca85e2557b045e326eef347beb Reviewed-by: Andy Nichols --- src/quick/scenegraph/qsgrenderloop.cpp | 15 ++++++++------- src/quick/scenegraph/qsgthreadedrenderloop.cpp | 13 ++++++------- src/quick/scenegraph/qsgwindowsrenderloop.cpp | 3 ++- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/quick/scenegraph/qsgrenderloop.cpp b/src/quick/scenegraph/qsgrenderloop.cpp index 75bfd5bda4..58d24414e1 100644 --- a/src/quick/scenegraph/qsgrenderloop.cpp +++ b/src/quick/scenegraph/qsgrenderloop.cpp @@ -413,13 +413,6 @@ void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window) qCDebug(QSG_LOG_RENDERLOOP, "cleanup without an OpenGL context"); } -#if QT_CONFIG(quick_shadereffect) - QSGRhiShaderEffectNode::cleanupMaterialTypeCache(); -#if QT_CONFIG(opengl) - QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache(); -#endif -#endif - if (d->swapchain) { if (window->handle()) { // We get here when exiting via QCoreApplication::quit() instead of @@ -432,6 +425,14 @@ void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window) } d->cleanupNodesOnShutdown(); + +#if QT_CONFIG(quick_shadereffect) + QSGRhiShaderEffectNode::cleanupMaterialTypeCache(); +#if QT_CONFIG(opengl) + QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache(); +#endif +#endif + if (m_windows.size() == 0) { rc->invalidate(); d->rhi = nullptr; diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp index ea107d7ea1..da0e8bdc7c 100644 --- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp +++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp @@ -547,17 +547,16 @@ void QSGRenderThread::invalidateGraphics(QQuickWindow *window, bool inDestructor QQuickWindowPrivate *dd = QQuickWindowPrivate::get(window); + // The canvas nodes must be cleaned up regardless if we are in the destructor.. + if (wipeSG) { + dd->cleanupNodesOnShutdown(); #if QT_CONFIG(quick_shadereffect) - QSGRhiShaderEffectNode::cleanupMaterialTypeCache(); + QSGRhiShaderEffectNode::cleanupMaterialTypeCache(); #if QT_CONFIG(opengl) - if (current) - QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache(); + if (current) + QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache(); #endif #endif - - // The canvas nodes must be cleaned up regardless if we are in the destructor.. - if (wipeSG) { - dd->cleanupNodesOnShutdown(); } else { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent SG, avoiding cleanup"); if (current && gl) diff --git a/src/quick/scenegraph/qsgwindowsrenderloop.cpp b/src/quick/scenegraph/qsgwindowsrenderloop.cpp index 8ab7c37211..9d248c50a9 100644 --- a/src/quick/scenegraph/qsgwindowsrenderloop.cpp +++ b/src/quick/scenegraph/qsgwindowsrenderloop.cpp @@ -256,12 +256,13 @@ void QSGWindowsRenderLoop::windowDestroyed(QQuickWindow *window) if (Q_UNLIKELY(!current)) RLDEBUG("cleanup without an OpenGL context"); + d->cleanupNodesOnShutdown(); + #if QT_CONFIG(quick_shadereffect) && QT_CONFIG(opengl) if (current) QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache(); #endif - d->cleanupNodesOnShutdown(); if (m_windows.size() == 0) { d->context->invalidate(); delete m_gl; -- cgit v1.2.3 From cf48a9ef97b726cd458292f53b94dfa897934be5 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Thu, 29 Jul 2021 15:06:05 +0200 Subject: QV4::Heap::GeneratorObject: remove unused member The member was marked as a Pointer for the gc; however it was never used, and thus also left uninitialized. This could cause memory corruption or asserts during the gc's mark phase. Fixes: QTBUG-95417 Change-Id: Ide826c0284b6060de8689e6f0dc753011108dba9 Reviewed-by: Qt CI Bot Reviewed-by: Maximilian Goldstein Reviewed-by: Andrei Golubev (cherry picked from commit d36b480a956e2437888925aa8a1f5e3cb6c06ebd) Reviewed-by: Qt Cherry-pick Bot --- src/qml/jsruntime/qv4generatorobject_p.h | 1 - tests/auto/qml/qqmlecmascript/data/generatorCallsGC.qml | 13 +++++++++++++ tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 10 ++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/auto/qml/qqmlecmascript/data/generatorCallsGC.qml diff --git a/src/qml/jsruntime/qv4generatorobject_p.h b/src/qml/jsruntime/qv4generatorobject_p.h index 8e14bcfa84..21cec0b699 100644 --- a/src/qml/jsruntime/qv4generatorobject_p.h +++ b/src/qml/jsruntime/qv4generatorobject_p.h @@ -87,7 +87,6 @@ struct GeneratorPrototype : FunctionObject { #define GeneratorObjectMembers(class, Member) \ Member(class, Pointer, ExecutionContext *, context) \ - Member(class, Pointer, GeneratorFunction *, function) \ Member(class, NoMark, GeneratorState, state) \ Member(class, NoMark, CppStackFrame, cppFrame) \ Member(class, Pointer, ArrayObject *, values) \ diff --git a/tests/auto/qml/qqmlecmascript/data/generatorCallsGC.qml b/tests/auto/qml/qqmlecmascript/data/generatorCallsGC.qml new file mode 100644 index 0000000000..7fe366cac8 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/generatorCallsGC.qml @@ -0,0 +1,13 @@ +import QtQml 2.15 + +QtObject { + function test_generator_gc() { + ((function*() { gc() })()).next(); + ((function*() { gc() })()).next(); + ((function*() { gc() })()).next(); + ((function*() { gc() })()).next(); + } + + Component.onCompleted: () => test_generator_gc() + +} diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 3c3a2a7a99..7da1b2c500 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -240,6 +240,7 @@ private slots: void function(); void topLevelGeneratorFunction(); void generatorCrashNewProperty(); + void generatorCallsGC(); void qtbug_10696(); void qtbug_11606(); void qtbug_11600(); @@ -6505,6 +6506,15 @@ void tst_qqmlecmascript::generatorCrashNewProperty() QCOMPARE(o->property("c").toInt(), 42); } +void tst_qqmlecmascript::generatorCallsGC() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("generatorCallsGC.qml")); + + QScopedPointer o(component.create()); // should not crash + QVERIFY2(o != nullptr, qPrintable(component.errorString())); +} + // Test the "Qt.include" method void tst_qqmlecmascript::include() { -- cgit v1.2.3 From 7276e498d671ce72d17f5424c5ef5df931ce6a85 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Mon, 14 Jun 2021 12:17:27 +0200 Subject: DropArea: Tell qdoc that DropArea indeed inherits Item This ensures that we list all of Item's properties in the "List of all Members" page. Change-Id: Ide7e270fd187e6adc4a20b70b8ef84d2c25a836c Reviewed-by: Mitch Curtis Reviewed-by: Shawn Rutledge (cherry picked from commit 565290ef3b63fd6ac7b3eac06e64c6a09a2561ad) Reviewed-by: Qt Cherry-pick Bot --- src/quick/items/qquickdroparea.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/quick/items/qquickdroparea.cpp b/src/quick/items/qquickdroparea.cpp index 75cb54cc90..cda56836e6 100644 --- a/src/quick/items/qquickdroparea.cpp +++ b/src/quick/items/qquickdroparea.cpp @@ -91,6 +91,7 @@ QQuickDropAreaPrivate::~QQuickDropAreaPrivate() /*! \qmltype DropArea \instantiates QQuickDropArea + \inherits Item \inqmlmodule QtQuick \ingroup qtquick-input \brief For specifying drag and drop handling in an area. -- cgit v1.2.3 From d5364954e6b2717e21c0afa5db5d1a0bb3bff5a2 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Tue, 27 Feb 2018 16:12:24 +0100 Subject: doc: Improve the Input Handlers index page - Document the exclusive/passive grab concepts better - Mention gesturePolicy's impact on grab behavior in the TapHandler docs too - More links - Add a couple of snippets illustrating simple use cases with handlers - Don't bother mentioning Qt.labs.handlers anymore Task-number: QTBUG-68110 Change-Id: I5e6f538c2bc8cafef679f535a5248b218b4a8068 Reviewed-by: Paul Wicking (cherry picked from commit aade6857d12b2c9cd5552d25f4d084a5fcd25f7d) Reviewed-by: Qt Cherry-pick Bot --- .../snippets/pointerHandlers/hoverTapKeyButton.qml | 73 ++++++++++++++++++++++ .../inputhandlers/qtquickhandlers-index.qdoc | 64 +++++++++++++++---- src/quick/handlers/qquicktaphandler.cpp | 13 ++-- 3 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 src/quick/doc/snippets/pointerHandlers/hoverTapKeyButton.qml diff --git a/src/quick/doc/snippets/pointerHandlers/hoverTapKeyButton.qml b/src/quick/doc/snippets/pointerHandlers/hoverTapKeyButton.qml new file mode 100644 index 0000000000..1564aa16b5 --- /dev/null +++ b/src/quick/doc/snippets/pointerHandlers/hoverTapKeyButton.qml @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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.12 + +Rectangle { + id: button + signal clicked + + width: 150; height: 50; radius: 3 + color: tapHandler.pressed ? "goldenrod" : hoverHandler.hovered ? "wheat" : "beige" + border.color: activeFocus ? "brown" : "transparent" + focus: true + + HoverHandler { + id: hoverHandler + } + + TapHandler { + id: tapHandler + onTapped: button.clicked() + } + + Keys.onEnterPressed: button.clicked() +} +//![0] diff --git a/src/quick/doc/src/concepts/inputhandlers/qtquickhandlers-index.qdoc b/src/quick/doc/src/concepts/inputhandlers/qtquickhandlers-index.qdoc index 2ac9860e6f..bf889a9066 100644 --- a/src/quick/doc/src/concepts/inputhandlers/qtquickhandlers-index.qdoc +++ b/src/quick/doc/src/concepts/inputhandlers/qtquickhandlers-index.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the documentation of the Qt Toolkit. @@ -30,20 +30,21 @@ \title Qt Quick Input Handlers \brief A module with a set of QML elements that handle events from input devices in a user interface. - Qt Quick Input Handlers are a set of QML types used to handle events from - keyboard, touch, mouse, and stylus devices in a UI. In contrast to event-handling - items, such as \l MouseArea and \l Flickable, input handlers are explicitly non-visual, - require less memory and are intended to be used in greater numbers: one - handler instance per aspect of interaction. Each input handler instance - handles certain events on behalf of its \c parent Item. Thus the visual and + Qt Quick Input Handlers are a set of QML types used to handle + \l {QInputEvent}{events} from keyboard, touch, mouse, and stylus + \l {QInputDevice}{devices} in a UI. In contrast to event-handling + items, such as \l MouseArea and \l Flickable, input handlers are explicitly + non-visual, require less memory and are intended to be used in greater + numbers: one handler instance per aspect of interaction. Each input handler + instance handles certain events on behalf of its + \l {QQuickPointerHandler::parent()}{parent} Item. Thus the visual and behavioral concerns are better separated, and the behavior is built up by finer-grained composition. - In Qt 5.10, these handlers were introduced in a separate Qt.labs.handlers module. - Now they are included with Qt Quick since 5.12. The pre-existing - \l Keys attached property is similar in concept, so we refer to the - pointing-device-oriented handlers plus \c Keys together as the set of Input Handlers. - We expect to offer more attached-property use cases in future versions of Qt. + The pre-existing \l Keys attached property is similar in concept, so we + refer to the pointing-device-oriented handlers plus \c Keys together as the + set of Input Handlers. We expect to offer more attached-property use cases + in future versions of Qt. \section1 Input Handlers @@ -60,7 +61,44 @@ \li Each Item can have unlimited Handlers \endlist - \omit TODO actual overview with snippets and stuff \endomit + \section1 Handlers Manipulating Items + + Some Handlers add interactivity simply by being declared inside an Item: + + \snippet pointerHandlers/dragHandler.qml 0 + + \section1 Handler Properties and Signals + + All Handlers have properties that can be used in bindings, and signals that + can be handled to react to input: + + \snippet pointerHandlers/hoverTapKeyButton.qml 0 + + \section1 Pointer Grab + + An important concept with Pointer Handlers is the type of grabs that they + perform. The only kind of grab an Item can take is the exclusive grab: for + example if you call \l QPointerEvent::setExclusiveGrabber(), the following + mouse moves and mouse release event will be sent only to that object. (As a + workaround to this exclusivity, see \l QQuickItem::setFiltersChildMouseEvents() + and \l QQuickItem::childMouseEventFilter().) However Pointer Handlers have + an additional mechanism available: the + \l {QPointerEvent::addPassiveGrabber()} {passive grab}. Mouse and touch + \l {QEventPoint::state()}{press} events are delivered by visiting all the + Items in top-down Z order: first each Item's child Handlers, and then the + \l {QQuickItem::event()}{Item} itself. At the time a press event is + delivered, a Handler can take either a passive or an exclusive grab + depending on its needs. If it takes a passive grab, it is guaranteed to + receive the updates and the release, even if other Items or Handlers in the + scene take any kind of grab, passive or exclusve. Some Handlers (such as + PointHandler) can work only with passive grabs; others require exclusive + grabs; and others can "lurk" with passive grabs until they detect that a + gesture is being performed, and then make the transition from passive to + exclusive grab. + + When a grab transition is requested, \l PointerHandler::grabPermissions, + \l QQuickItem::keepMouseGrab() and \l QQuickItem::keepTouchGrab() control + whether the transition will be allowed. \section1 Related Information diff --git a/src/quick/handlers/qquicktaphandler.cpp b/src/quick/handlers/qquicktaphandler.cpp index 52e84aa649..04c9558e0a 100644 --- a/src/quick/handlers/qquicktaphandler.cpp +++ b/src/quick/handlers/qquicktaphandler.cpp @@ -209,6 +209,8 @@ void QQuickTapHandler::timerEvent(QTimerEvent *event) If the spatial constraint is violated, \l pressed transitions immediately from true to false, regardless of the time held. + The \c gesturePolicy also affects grab behavior as described below. + \value TapHandler.DragThreshold (the default value) The event point must not move significantly. If the mouse, finger or stylus moves past the system-wide drag @@ -217,11 +219,13 @@ void QQuickTapHandler::timerEvent(QTimerEvent *event) can be useful whenever TapHandler needs to cooperate with other input handlers (for example \l DragHandler) or event-handling Items (for example QtQuick Controls), because in this case TapHandler - will not take the exclusive grab, but merely a passive grab. + will not take the exclusive grab, but merely a + \l {QPointerEvent::addPassiveGrabber()}{passive grab}. \value TapHandler.WithinBounds If the event point leaves the bounds of the \c parent Item, the tap - gesture is canceled. The TapHandler will take the exclusive grab on + gesture is canceled. The TapHandler will take the + \l {QPointerEvent::setExclusiveGrabber}{exclusive grab} on press, but will release the grab as soon as the boundary constraint is no longer satisfied. @@ -232,8 +236,9 @@ void QQuickTapHandler::timerEvent(QTimerEvent *event) typical behavior for button widgets: you can cancel a click by dragging outside the button, and you can also change your mind by dragging back inside the button before release. Note that it's - necessary for TapHandler take the exclusive grab on press and retain - it until release in order to detect this gesture. + necessary for TapHandler to take the + \l {QPointerEvent::setExclusiveGrabber}{exclusive grab} on press + and retain it until release in order to detect this gesture. */ void QQuickTapHandler::setGesturePolicy(QQuickTapHandler::GesturePolicy gesturePolicy) { -- cgit v1.2.3 From 32072884bdba99205d7f681382b0fcf83d292c6d Mon Sep 17 00:00:00 2001 From: Maximilian Goldstein Date: Thu, 7 Jan 2021 13:59:43 +0100 Subject: Avoid crash for deep aliases Aliases are seriously broken and need a larger overhaul. This patch at least prevents the application from crashing. Task-number: QTBUG-89822 Fixes: QTBUG-94820 Change-Id: Ib6acc5b6f621a902f7f5ce370043986486f3c0d0 Reviewed-by: Ulf Hermann (cherry picked from commit 30136b1a82bfc54459741483e76f2f70c937dedd) Reviewed-by: Fabian Kosmale --- src/qml/qml/qqmlpropertycachecreator_p.h | 4 ++++ tests/auto/qml/qqmllanguage/data/qtbug_89822.errors.txt | 1 + tests/auto/qml/qqmllanguage/data/qtbug_89822.qml | 8 ++++++++ tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 7 +++++++ 4 files changed, 20 insertions(+) create mode 100644 tests/auto/qml/qqmllanguage/data/qtbug_89822.errors.txt create mode 100644 tests/auto/qml/qqmllanguage/data/qtbug_89822.qml diff --git a/src/qml/qml/qqmlpropertycachecreator_p.h b/src/qml/qml/qqmlpropertycachecreator_p.h index e18b12b13b..be01f23217 100644 --- a/src/qml/qml/qqmlpropertycachecreator_p.h +++ b/src/qml/qml/qqmlpropertycachecreator_p.h @@ -865,6 +865,10 @@ inline QQmlError QQmlPropertyCacheAliasCreator::propertyDataFor Q_ASSERT(targetCache); targetProperty = targetCache->property(valueTypeIndex); + if (targetProperty == nullptr) { + return qQmlCompileError(alias.referenceLocation, + QQmlPropertyCacheCreatorBase::tr("Invalid alias target")); + } *type = targetProperty->propType(); writable = targetProperty->isWritable(); diff --git a/tests/auto/qml/qqmllanguage/data/qtbug_89822.errors.txt b/tests/auto/qml/qqmllanguage/data/qtbug_89822.errors.txt new file mode 100644 index 0000000000..d69122ef8b --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/qtbug_89822.errors.txt @@ -0,0 +1 @@ +6:40:Invalid alias target diff --git a/tests/auto/qml/qqmllanguage/data/qtbug_89822.qml b/tests/auto/qml/qqmllanguage/data/qtbug_89822.qml new file mode 100644 index 0000000000..17602ca4b9 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/qtbug_89822.qml @@ -0,0 +1,8 @@ +import QtQml 2.0 + +QtObject { + id: root + readonly property QtObject test: QtObject { property int subproperty: 3} + readonly property alias testAlias: root.test.subproperty +} + diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 94ecd6862a..31bf30c57c 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -146,6 +146,7 @@ private slots: void aliasProperties(); void aliasPropertiesAndSignals(); void aliasPropertyChangeSignals(); + void qtbug_89822(); void componentCompositeType(); void i18n(); void i18n_data(); @@ -2232,6 +2233,12 @@ void tst_qqmllanguage::aliasPropertiesAndSignals() QCOMPARE(o->property("test").toBool(), true); } +void tst_qqmllanguage::qtbug_89822() +{ + QQmlComponent component(&engine, testFileUrl("qtbug_89822.qml")); + VERIFY_ERRORS("qtbug_89822.errors.txt"); +} + // Test that the root element in a composite type can be a Component void tst_qqmllanguage::componentCompositeType() { -- cgit v1.2.3 From 2c3ea30205efef22146f3c7218d60b39fee991c2 Mon Sep 17 00:00:00 2001 From: Jeremy Katz Date: Tue, 10 Aug 2021 22:46:56 -0700 Subject: Doc: fix AnchorChanges typo Change-Id: I6e5cc9807ddc2ae6a40a864b3154fe93fcc32d69 Reviewed-by: Shawn Rutledge (cherry picked from commit a4a80e9a5c0452936241657c19ce871094a4f335) Reviewed-by: Qt Cherry-pick Bot --- src/quick/items/qquickstateoperations.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quick/items/qquickstateoperations.cpp b/src/quick/items/qquickstateoperations.cpp index 7e4f533847..20e0f80729 100644 --- a/src/quick/items/qquickstateoperations.cpp +++ b/src/quick/items/qquickstateoperations.cpp @@ -573,7 +573,7 @@ void QQuickParentChange::rewind() The AnchorChanges type is used to modify the anchors of an item in a \l State. AnchorChanges cannot be used to modify the margins on an item. For this, use - PropertyChanges intead. + PropertyChanges instead. In the following example we change the top and bottom anchors of an item using AnchorChanges, and the top and bottom anchor margins using -- cgit v1.2.3 From a18926c736bf9de6bd652c8bb1799d4678001341 Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Wed, 28 Jul 2021 12:10:51 +0200 Subject: Use QDateTime to get offsets for emscripten (wasm) Since our wasm implementation doesn't have access to time-zone data by default, but its QDateTime implementation does manage to get offsets right for local time, use it as fall-back for V4's Date implementation's DaylightSavingTA() and getLocalTZA(). This implementation might also be viable for other cases without timezone support (and a way to reset it when the system zone has changed), but we'll need to experiment to find out. For now, since we have nothing better for wasm, use it there. In passing, update a comment about a bug report against the ECMA spec to say it's been fixed (and we're compatible with the result). Fixes: QTBUG-95314 Change-Id: I40c1537815ada950dc0b5cebd4d641f7bfc45bd9 Reviewed-by: Ulf Hermann Reviewed-by: Lorn Potter (cherry picked from commit 373897481fb930055e6e89035b1055f8dd80b83f) Reviewed-by: Qt Cherry-pick Bot --- src/qml/jsruntime/qv4dateobject.cpp | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/qml/jsruntime/qv4dateobject.cpp b/src/qml/jsruntime/qv4dateobject.cpp index 389e9351e7..9b751fbeaa 100644 --- a/src/qml/jsruntime/qv4dateobject.cpp +++ b/src/qml/jsruntime/qv4dateobject.cpp @@ -78,10 +78,21 @@ QTBUG-75585 for an explanation and possible workarounds. */ #define USE_QTZ_SYSTEM_ZONE +#elif defined(Q_OS_WASM) +/* + TODO: evaluate using this version of the code more generally, rather than + the #else branches of the various USE_QTZ_SYSTEM_ZONE choices. It might even + work better than the timezone variant; experiments needed. +*/ +// Kludge around the lack of time-zone info using QDateTime. +// It uses localtime() and friends to determine offsets from UTC. +#define USE_QDT_LOCAL_TIME #endif #ifdef USE_QTZ_SYSTEM_ZONE #include +#elif defined(USE_QDT_LOCAL_TIME) +// QDateTime already included above #else # ifdef Q_OS_WIN # include @@ -356,6 +367,7 @@ static inline double MakeDate(double day, double time) mean a whole day of DST offset for some zones, that have crossed the international date line. This shall confuse client code.) The bug report against the ECMAScript spec is https://github.com/tc39/ecma262/issues/725 + and they've now changed the spec so that the following conforms to it ;^> */ static inline double DaylightSavingTA(double t, double localTZA) // t is a UTC time @@ -363,6 +375,12 @@ static inline double DaylightSavingTA(double t, double localTZA) // t is a UTC t return QTimeZone::systemTimeZone().offsetFromUtc( QDateTime::fromMSecsSinceEpoch(qint64(t), Qt::UTC)) * 1e3 - localTZA; } +#elif defined(USE_QDT_LOCAL_TIME) +static inline double DaylightSavingTA(double t, double localTZA) // t is a UTC time +{ + return QDateTime::fromMSecsSinceEpoch(qint64(t), Qt::UTC + ).toLocalTime().offsetFromUtc() * 1e3 - localTZA; +} #else // This implementation fails to take account of past changes in standard offset. static inline double DaylightSavingTA(double t, double /*localTZA*/) @@ -721,6 +739,26 @@ static double getLocalTZA() // TODO: QTimeZone::resetSystemTimeZone(), see QTBUG-56899 and comment above. // Standard offset, with no daylight-savings adjustment, in ms: return QTimeZone::systemTimeZone().standardTimeOffset(QDateTime::currentDateTime()) * 1e3; +#elif defined(USE_QDT_LOCAL_TIME) + QDate today = QDate::currentDate(); + QDateTime near = today.startOfDay(Qt::LocalTime); + // Early out if we're in standard time anyway: + if (!near.isDaylightTime()) + return near.offsetFromUtc() * 1000; + int year, month; + today.getDate(&year, &month, nullptr); + // One of the solstices is probably in standard time: + QDate summer(year, 6, 21), winter(year - (month < 7 ? 1 : 0), 12, 21); + // But check the one closest to the present by preference, in case there's a + // standard time offset change between them: + QDateTime far = summer.startOfDay(Qt::LocalTime); + near = winter.startOfDay(Qt::LocalTime); + if (month > 3 && month < 10) + near.swap(far); + bool isDst = near.isDaylightTime(); + if (isDst && far.isDaylightTime()) // Permanent DST, probably an hour west: + return (qMin(near.offsetFromUtc(), far.offsetFromUtc()) - 3600) * 1000; + return (isDst ? far : near).offsetFromUtc() * 1000; #else # ifdef Q_OS_WIN TIME_ZONE_INFORMATION tzInfo; -- cgit v1.2.3 From eaf011c4ae440bbf7a02ceb7a33be5dfa5bcf907 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 16 Aug 2021 15:02:40 +0200 Subject: QmlPreview: Protect QQmlPreviewFileLoader::load with another mutex We can concurrently load multiple files from different threads, but the loader can only handle one such request at a time. Fixes: QTBUG-95825 Change-Id: I2b94d55f2cf0da6e84dabc47df5699cc57b903a6 Reviewed-by: Maximilian Goldstein Reviewed-by: Knud Dollereder (cherry picked from commit a068fdc0b2cf7d4223d9ad15345fe9d345875936) Reviewed-by: Ulf Hermann --- .../qmldbg_preview/qqmlpreviewfileengine.cpp | 8 ++++++++ .../qmldbg_preview/qqmlpreviewfileloader.cpp | 20 ++++++++++---------- .../qmldbg_preview/qqmlpreviewfileloader.h | 5 ++++- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp index 620c1cf1ba..7b53402df9 100644 --- a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp @@ -398,6 +398,14 @@ bool QQmlPreviewFileEngine::supportsExtension(Extension extension) const void QQmlPreviewFileEngine::load() const { + // We can get here from different threads on different instances of QQmlPreviewFileEngine. + // However, there is only one loader per QQmlPreviewFileEngineHandler and it is not thread-safe. + // Its content mutex doesn't help us here because we explicitly wait on it in load(), which + // causes it to be released. Therefore, lock the load mutex first. + // This doesn't cause any deadlocks because the only thread that wakes the loader on the content + // mutex never calls load(). It's the QML debug server thread that handles the debug protocol. + QMutexLocker loadLocker(m_loader->loadMutex()); + m_result = m_loader->load(m_absolute); switch (m_result) { case QQmlPreviewFileLoader::File: diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp index 72abba6154..f97dcfc767 100644 --- a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp @@ -101,7 +101,7 @@ QQmlPreviewFileLoader::~QQmlPreviewFileLoader() { QQmlPreviewFileLoader::Result QQmlPreviewFileLoader::load(const QString &path) { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_contentMutex); m_path = path; auto fileIterator = m_fileCache.constFind(path); @@ -124,19 +124,19 @@ QQmlPreviewFileLoader::Result QQmlPreviewFileLoader::load(const QString &path) m_entries.clear(); m_contents.clear(); emit request(path); - m_waitCondition.wait(&m_mutex); + m_waitCondition.wait(&m_contentMutex); return m_result; } QByteArray QQmlPreviewFileLoader::contents() { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_contentMutex); return m_contents; } QStringList QQmlPreviewFileLoader::entries() { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_contentMutex); return m_entries; } @@ -144,20 +144,20 @@ void QQmlPreviewFileLoader::whitelist(const QUrl &url) { const QString path = QQmlFile::urlToLocalFileOrQrc(url); if (!path.isEmpty()) { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_contentMutex); m_blacklist.whitelist(path); } } bool QQmlPreviewFileLoader::isBlacklisted(const QString &path) { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_contentMutex); return m_blacklist.isBlacklisted(path); } void QQmlPreviewFileLoader::file(const QString &path, const QByteArray &contents) { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_contentMutex); m_blacklist.whitelist(path); m_fileCache[path] = contents; if (path == m_path) { @@ -169,7 +169,7 @@ void QQmlPreviewFileLoader::file(const QString &path, const QByteArray &contents void QQmlPreviewFileLoader::directory(const QString &path, const QStringList &entries) { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_contentMutex); m_blacklist.whitelist(path); m_directoryCache[path] = entries; if (path == m_path) { @@ -181,7 +181,7 @@ void QQmlPreviewFileLoader::directory(const QString &path, const QStringList &en void QQmlPreviewFileLoader::error(const QString &path) { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_contentMutex); m_blacklist.blacklist(path); if (path == m_path) { m_result = Fallback; @@ -191,7 +191,7 @@ void QQmlPreviewFileLoader::error(const QString &path) void QQmlPreviewFileLoader::clearCache() { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_contentMutex); m_fileCache.clear(); m_directoryCache.clear(); } diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h index 9c337935e3..e66294cb06 100644 --- a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h @@ -79,7 +79,9 @@ public: QQmlPreviewFileLoader(QQmlPreviewServiceImpl *service); ~QQmlPreviewFileLoader(); + QMutex *loadMutex() { return &m_loadMutex; } Result load(const QString &file); + QByteArray contents(); QStringList entries(); @@ -90,7 +92,8 @@ signals: void request(const QString &file); private: - QMutex m_mutex; + QMutex m_loadMutex; + QMutex m_contentMutex; QWaitCondition m_waitCondition; QThread m_thread; -- cgit v1.2.3