diff options
author | Tarja Sundqvist <[email protected]> | 2025-08-22 09:33:17 +0300 |
---|---|---|
committer | Tarja Sundqvist <[email protected]> | 2025-08-22 09:33:17 +0300 |
commit | c6fdadd916a7568c1d71b750e054ca9aa2fd5dfc (patch) | |
tree | bbd5690b38a5e0df66dd070a9e9f42b0d1b2b673 | |
parent | a7c766a9863605eb81e8f0cdb4d2b93e087b5bde (diff) | |
parent | e436dad999060b92965291b45c0e95a3b93f5866 (diff) |
Merge tag 'v6.2.13-lts' into tqtc/lts-6.2-opensourcev6.2.13-lts-lgpl6.2
Qt 6.2.13-lts release
Conflicts solved:
dependencies.yaml
Change-Id: I3cbe1ce4293179888e236dd1a3a299cd2c66c950
68 files changed, 1428 insertions, 161 deletions
diff --git a/.cmake.conf b/.cmake.conf index 7733def214..0fcb9b1a08 100644 --- a/.cmake.conf +++ b/.cmake.conf @@ -1,2 +1,2 @@ -set(QT_REPO_MODULE_VERSION "6.2.12") +set(QT_REPO_MODULE_VERSION "6.2.13") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "") diff --git a/.qmake.conf b/.qmake.conf index ac797c6928..d10b666e6a 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -5,4 +5,4 @@ DEFINES += QT_NO_JAVA_STYLE_ITERATORS QQC2_SOURCE_TREE = $$PWD -MODULE_VERSION = 6.2.12 +MODULE_VERSION = 6.2.13 diff --git a/dependencies.yaml b/dependencies.yaml index c1eda40e5e..ec29efacb5 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -1,13 +1,13 @@ dependencies: ../tqtc-qtbase: - ref: 3a82051eade32b34c2f4f6f652a9d8ef0db96c71 + ref: a963a536e623499cc56b0231c35dc5790a4bbc29 required: true ../tqtc-qtimageformats: - ref: 8dd102eea4bf0e6d683f9f8bdfe74c5d63a626c8 + ref: 282852e3eff8909bfa157d80d1ff7c72fd1662d6 required: false ../tqtc-qtshadertools: - ref: ebe45c38bffcf8c3487949045f9ecf9c8a5f7e30 + ref: 4e8c28a3dc8d4dd6e237f7cc4235c078147385a2 required: false ../tqtc-qtsvg: - ref: 2563e58640a7a85144ed64641f00c7e5edbabb68 + ref: 0463e412eeadba6dcf1b84b9c655550f93656e7d required: false diff --git a/examples/quick/scenegraph/graph/shaders/noisy.frag b/examples/quick/scenegraph/graph/shaders/noisy.frag index 0b7cb1306b..0f13b83fd4 100644 --- a/examples/quick/scenegraph/graph/shaders/noisy.frag +++ b/examples/quick/scenegraph/graph/shaders/noisy.frag @@ -11,7 +11,7 @@ layout(location = 0) out vec4 fragColor; layout(std140, binding = 0) uniform buf { mat4 qt_Matrix; vec4 color; - vec2 textureSize; + vec2 texCoordScale; float qt_Opacity; }; diff --git a/examples/quick/scenegraph/graph/shaders/noisy.frag.qsb b/examples/quick/scenegraph/graph/shaders/noisy.frag.qsb Binary files differindex 1a49e93cf3..dd1739b1fe 100644 --- a/examples/quick/scenegraph/graph/shaders/noisy.frag.qsb +++ b/examples/quick/scenegraph/graph/shaders/noisy.frag.qsb diff --git a/examples/quick/scenegraph/graph/shaders/noisy.vert b/examples/quick/scenegraph/graph/shaders/noisy.vert index 5728f2a02f..057f1c8d16 100644 --- a/examples/quick/scenegraph/graph/shaders/noisy.vert +++ b/examples/quick/scenegraph/graph/shaders/noisy.vert @@ -9,7 +9,7 @@ layout(location = 1) out vec2 vShadeCoord; layout(std140, binding = 0) uniform buf { mat4 qt_Matrix; vec4 color; - vec2 textureSize; + vec2 texCoordScale; float qt_Opacity; }; @@ -17,6 +17,6 @@ out gl_PerVertex { vec4 gl_Position; }; void main() { gl_Position = qt_Matrix * aVertex; - vTexCoord = aVertex.xy * textureSize; + vTexCoord = aVertex.xy * texCoordScale; vShadeCoord = aTexCoord; } diff --git a/examples/quick/scenegraph/graph/shaders/noisy.vert.qsb b/examples/quick/scenegraph/graph/shaders/noisy.vert.qsb Binary files differindex ce2a828ead..9de384e3a6 100644 --- a/examples/quick/scenegraph/graph/shaders/noisy.vert.qsb +++ b/examples/quick/scenegraph/graph/shaders/noisy.vert.qsb diff --git a/src/imports/tooling/Enum.qml b/src/imports/tooling/Enum.qml index cd5904b6b6..0d0e90d621 100644 --- a/src/imports/tooling/Enum.qml +++ b/src/imports/tooling/Enum.qml @@ -42,5 +42,6 @@ import QML Member { property string alias property bool isFlag: false + property bool isScoped: false property var values: [] } diff --git a/src/particles/qquickparticlesystem.cpp b/src/particles/qquickparticlesystem.cpp index 5744c61849..0bb71d1d70 100644 --- a/src/particles/qquickparticlesystem.cpp +++ b/src/particles/qquickparticlesystem.cpp @@ -240,8 +240,7 @@ void QQuickParticleDataHeap::insertTimed(QQuickParticleData* data, int time) int QQuickParticleDataHeap::top() { - if (m_end == 0) - return 1 << 30; + Q_ASSERT(!isEmpty()); return m_data[0].time; } @@ -391,7 +390,7 @@ bool QQuickParticleGroupData::recycle() { m_latestAliveParticles.clear(); - while (dataHeap.top() <= m_system->timeInt) { + while (!dataHeap.isEmpty() && dataHeap.top() <= m_system->timeInt) { for (QQuickParticleData *datum : dataHeap.pop()) { if (!datum->stillAlive(m_system)) { freeList.free(datum->index); diff --git a/src/particles/qquickparticlesystem_p.h b/src/particles/qquickparticlesystem_p.h index 0ae2a5f345..ab5cc9cd7f 100644 --- a/src/particles/qquickparticlesystem_p.h +++ b/src/particles/qquickparticlesystem_p.h @@ -117,6 +117,8 @@ public: int top(); + bool isEmpty() const { return m_end == 0; } + QSet<QQuickParticleData*> pop(); void clear(); diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h index 1452027dcb..3d7fc92a30 100644 --- a/src/qml/common/qv4compileddata_p.h +++ b/src/qml/common/qv4compileddata_p.h @@ -1334,14 +1334,14 @@ struct TypeReferenceMap : QHash<int, TypeReference> if (!formal->type.indexIsBuiltinType()) { TypeReference &r = this->add(formal->type.typeNameIndexOrBuiltinType(), it->location); - r.errorWhenNotFound = true; + r.errorWhenNotFound = false; } } if (!it->returnType.indexIsBuiltinType()) { TypeReference &r = this->add(it->returnType.typeNameIndexOrBuiltinType(), it->location); - r.errorWhenNotFound = true; + r.errorWhenNotFound = false; } } } diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index b303dd497a..964bb78d3a 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -2497,16 +2497,15 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi return true; } - if (metaType == QMetaType::fromType<QQmlListReference>()) { - if (const QV4::QmlListWrapper *wrapper = value.as<QV4::QmlListWrapper>()) { + if (const QV4::QmlListWrapper *wrapper = value.as<QV4::QmlListWrapper>()) { + if (metaType == QMetaType::fromType<QQmlListReference>()) { *reinterpret_cast<QQmlListReference *>(data) = wrapper->toListReference(); return true; } - } - if (metaType == QMetaType::fromType<QQmlListProperty<QObject>>()) { - if (const QV4::QmlListWrapper *wrapper = value.as<QV4::QmlListWrapper>()) { - *reinterpret_cast<QQmlListProperty<QObject> *>(data) = wrapper->d()->property(); + const auto wrapperPrivate = wrapper->d(); + if (QMetaType(wrapperPrivate->propertyType) == metaType) { + *reinterpret_cast<QQmlListProperty<QObject> *>(data) = wrapperPrivate->property(); return true; } } diff --git a/src/qml/jsruntime/qv4estable.cpp b/src/qml/jsruntime/qv4estable.cpp index 99f6bf6aa0..2e504a70cb 100644 --- a/src/qml/jsruntime/qv4estable.cpp +++ b/src/qml/jsruntime/qv4estable.cpp @@ -147,21 +147,18 @@ ReturnedValue ESTable::get(const Value &key, bool *hasValue) const // Removes the given \a key from the table bool ESTable::remove(const Value &key) { - bool found = false; - uint idx = 0; - for (; idx < m_size; ++idx) { - if (m_keys[idx].sameValueZero(key)) { - found = true; - break; + for (uint index = 0; index < m_size; ++index) { + if (m_keys[index].sameValueZero(key)) { + // Remove the element at |index| by moving all elements to the right + // of |index| one place to the left. + size_t count = (m_size - (index + 1)) * sizeof(Value); + memmove(m_keys + index, m_keys + index + 1, count); + memmove(m_values + index, m_values + index + 1, count); + m_size--; + return true; } } - - if (found == true) { - memmove(m_keys + idx, m_keys + idx + 1, (m_size - idx)*sizeof(Value)); - memmove(m_values + idx, m_values + idx + 1, (m_size - idx)*sizeof(Value)); - m_size--; - } - return found; + return false; } // Returns the size of the table. Note that the size may not match the underlying allocation. diff --git a/src/qml/jsruntime/qv4estable_p.h b/src/qml/jsruntime/qv4estable_p.h index f54fc37a7b..e63dda7a4d 100644 --- a/src/qml/jsruntime/qv4estable_p.h +++ b/src/qml/jsruntime/qv4estable_p.h @@ -53,12 +53,13 @@ #include "qv4value_p.h" +class tst_qv4estable; + QT_BEGIN_NAMESPACE -namespace QV4 -{ +namespace QV4 { -class ESTable +class Q_AUTOTEST_EXPORT ESTable { public: ESTable(); @@ -76,13 +77,15 @@ public: void removeUnmarkedKeys(); private: + friend class ::tst_qv4estable; + Value *m_keys = nullptr; Value *m_values = nullptr; uint m_size = 0; uint m_capacity = 0; }; -} +} // namespace QV4 QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4sparsearray_p.h b/src/qml/jsruntime/qv4sparsearray_p.h index 248fcdb02f..daa21baf06 100644 --- a/src/qml/jsruntime/qv4sparsearray_p.h +++ b/src/qml/jsruntime/qv4sparsearray_p.h @@ -146,8 +146,8 @@ struct Q_QML_EXPORT SparseArray { SparseArray(); ~SparseArray() { - if (root()) - freeTree(header.left, alignof(SparseArrayNode)); + if (SparseArrayNode *n = root()) + freeTree(n, alignof(SparseArrayNode)); } SparseArray(const SparseArray &other); @@ -323,37 +323,45 @@ inline QList<int> SparseArray::keys() const inline const SparseArrayNode *SparseArray::lowerBound(uint akey) const { - const SparseArrayNode *lb = root()->lowerBound(akey); - if (!lb) - lb = end(); - return lb; + if (SparseArrayNode *n = root()) { + if (const SparseArrayNode *lb = n->lowerBound(akey)) + return lb; + } + + return end(); } inline SparseArrayNode *SparseArray::lowerBound(uint akey) { - SparseArrayNode *lb = root()->lowerBound(akey); - if (!lb) - lb = end(); - return lb; + if (SparseArrayNode *n = root()) { + if (SparseArrayNode *lb = n->lowerBound(akey)) + return lb; + } + + return end(); } inline const SparseArrayNode *SparseArray::upperBound(uint akey) const { - const SparseArrayNode *ub = root()->upperBound(akey); - if (!ub) - ub = end(); - return ub; + if (SparseArrayNode *n = root()) { + if (const SparseArrayNode *ub = n->upperBound(akey)) + return ub; + } + + return end(); } inline SparseArrayNode *SparseArray::upperBound(uint akey) { - SparseArrayNode *ub = root()->upperBound(akey); - if (!ub) - ub = end(); - return ub; + if (SparseArrayNode *n = root()) { + if (SparseArrayNode *ub = n->upperBound(akey)) + return ub; + } + + return end(); } } diff --git a/src/qml/memory/qv4heap_p.h b/src/qml/memory/qv4heap_p.h index 5fb14332ab..8989efe701 100644 --- a/src/qml/memory/qv4heap_p.h +++ b/src/qml/memory/qv4heap_p.h @@ -251,7 +251,7 @@ struct QV4QPointer { private: QtSharedPointer::ExternalRefCountData *d; - QObject *qObject; + T *qObject; }; Q_STATIC_ASSERT(std::is_trivial< QV4QPointer<QObject> >::value); #endif diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index 127fdf7ebb..ec596c0506 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -697,7 +697,13 @@ void qmlRegisterTypeAndRevisions<QQmlTypeNotAvailable, void>( QQmlEngine *AOTCompiledContext::qmlEngine() const { - return qmlContext ? qmlContext->engine() : nullptr; + return engine->handle()->qmlEngine(); +} + +static QQmlPropertyCapture *propertyCapture(const AOTCompiledContext *aotContext) +{ + QQmlEngine *engine = aotContext->qmlEngine(); + return engine ? QQmlEnginePrivate::get(aotContext->qmlEngine())->propertyCapture : nullptr; } QJSValue AOTCompiledContext::jsMetaType(int index) const @@ -722,31 +728,23 @@ void AOTCompiledContext::setReturnValueUndefined() const static void captureFallbackProperty( QObject *object, int coreIndex, int notifyIndex, bool isConstant, - QQmlContextData *qmlContext) + const AOTCompiledContext *aotContext) { - if (!qmlContext || isConstant) + if (isConstant) return; - QQmlEngine *engine = qmlContext->engine(); - Q_ASSERT(engine); - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); - Q_ASSERT(ep); - if (QQmlPropertyCapture *capture = ep->propertyCapture) + if (QQmlPropertyCapture *capture = propertyCapture(aotContext)) capture->captureProperty(object, coreIndex, notifyIndex); } static void captureObjectProperty( QObject *object, const QQmlPropertyCache *propertyCache, - const QQmlPropertyData *property, QQmlContextData *qmlContext) + const QQmlPropertyData *property, const AOTCompiledContext *aotContext) { - if (!qmlContext || property->isConstant()) + if (property->isConstant()) return; - QQmlEngine *engine = qmlContext->engine(); - Q_ASSERT(engine); - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); - Q_ASSERT(ep); - if (QQmlPropertyCapture *capture = ep->propertyCapture) + if (QQmlPropertyCapture *capture = propertyCapture(aotContext)) capture->captureProperty(object, propertyCache, property); } @@ -762,7 +760,7 @@ static bool inherits(const QQmlPropertyCache *descendent, const QQmlPropertyCach enum class ObjectPropertyResult { OK, NeedsInit, Deleted }; static ObjectPropertyResult loadObjectProperty( - QV4::Lookup *l, QObject *object, void *target, QQmlContextData *qmlContext) + QV4::Lookup *l, QObject *object, void *target, const AOTCompiledContext *aotContext) { QQmlData *qmlData = QQmlData::get(object); if (!qmlData) @@ -779,13 +777,13 @@ static ObjectPropertyResult loadObjectProperty( if (qmlData->hasPendingBindingBit(coreIndex)) qmlData->flushPendingBinding(coreIndex); - captureObjectProperty(object, propertyCache, property, qmlContext); + captureObjectProperty(object, propertyCache, property, aotContext); property->readProperty(object, target); return ObjectPropertyResult::OK; } static ObjectPropertyResult loadFallbackProperty( - QV4::Lookup *l, QObject *object, void *target, QQmlContextData *qmlContext) + QV4::Lookup *l, QObject *object, void *target, const AOTCompiledContext *aotContext) { QQmlData *qmlData = QQmlData::get(object); if (qmlData && qmlData->isQueuedForDeletion) @@ -803,7 +801,7 @@ static ObjectPropertyResult loadFallbackProperty( qmlData->flushPendingBinding(coreIndex); captureFallbackProperty(object, coreIndex, l->qobjectFallbackLookup.notifyIndex, - l->qobjectFallbackLookup.isConstant, qmlContext); + l->qobjectFallbackLookup.isConstant, aotContext); void *a[] = { target, nullptr }; metaObject->metacall(object, QMetaObject::ReadProperty, coreIndex, a); @@ -984,7 +982,7 @@ bool AOTCompiledContext::captureLookup(uint index, QObject *object) const || l->getter == QV4::Lookup::getterQObject) { const QQmlPropertyData *property = l->qobjectLookup.propertyData; QQmlData::flushPendingBinding(object, property->coreIndex()); - captureObjectProperty(object, l->qobjectLookup.propertyCache, property, qmlContext); + captureObjectProperty(object, l->qobjectLookup.propertyCache, property, this); return true; } @@ -993,7 +991,7 @@ bool AOTCompiledContext::captureLookup(uint index, QObject *object) const QQmlData::flushPendingBinding(object, coreIndex); captureFallbackProperty( object, coreIndex, l->qobjectFallbackLookup.notifyIndex, - l->qobjectFallbackLookup.isConstant, qmlContext); + l->qobjectFallbackLookup.isConstant, this); return true; } @@ -1007,7 +1005,7 @@ bool AOTCompiledContext::captureQmlContextPropertyLookup(uint index) const && l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupContextObjectProperty) { const QQmlPropertyData *property = l->qobjectLookup.propertyData; QQmlData::flushPendingBinding(qmlScopeObject, property->coreIndex()); - captureObjectProperty(qmlScopeObject, l->qobjectLookup.propertyCache, property, qmlContext); + captureObjectProperty(qmlScopeObject, l->qobjectLookup.propertyCache, property, this); return true; } @@ -1015,7 +1013,7 @@ bool AOTCompiledContext::captureQmlContextPropertyLookup(uint index) const const int coreIndex = l->qobjectFallbackLookup.coreIndex; QQmlData::flushPendingBinding(qmlScopeObject, coreIndex); captureFallbackProperty(qmlScopeObject, coreIndex, l->qobjectFallbackLookup.notifyIndex, - l->qobjectFallbackLookup.isConstant, qmlContext); + l->qobjectFallbackLookup.isConstant, this); return true; } @@ -1267,9 +1265,9 @@ bool AOTCompiledContext::loadScopeObjectPropertyLookup(uint index, void *target) ObjectPropertyResult result = ObjectPropertyResult::NeedsInit; if (l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupScopeObjectProperty) - result = loadObjectProperty(l, qmlScopeObject, target, qmlContext); + result = loadObjectProperty(l, qmlScopeObject, target, this); else if (l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupScopeFallbackProperty) - result = loadFallbackProperty(l, qmlScopeObject, target, qmlContext); + result = loadFallbackProperty(l, qmlScopeObject, target, this); else return false; @@ -1405,9 +1403,9 @@ bool AOTCompiledContext::getObjectLookup(uint index, QObject *object, void *targ ObjectPropertyResult result = ObjectPropertyResult::NeedsInit; if (l->getter == QV4::Lookup::getterQObject) - result = loadObjectProperty(l, object, target, qmlContext); + result = loadObjectProperty(l, object, target, this); else if (l->getter == QV4::Lookup::getterFallback) - result = loadFallbackProperty(l, object, target, qmlContext); + result = loadFallbackProperty(l, object, target, this); else return false; diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index 372103b94f..d4a6ba1dd3 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -812,17 +812,21 @@ static void removeOldBinding(QObject *object, QQmlPropertyIndex index, QQmlPrope oldBinding = data->bindings; while (oldBinding && (oldBinding->targetPropertyIndex().coreIndex() != coreIndex || - oldBinding->targetPropertyIndex().hasValueTypeIndex())) + oldBinding->targetPropertyIndex().hasValueTypeIndex())) { oldBinding = oldBinding->nextBinding(); + } - if (!oldBinding) - return; - - if (valueTypeIndex != -1 && oldBinding->isValueTypeProxy()) + if (valueTypeIndex != -1 + && oldBinding + && oldBinding->isValueTypeProxy()) { oldBinding = static_cast<QQmlValueTypeProxyBinding *>(oldBinding.data())->binding(index); + } - if (!oldBinding) + if (!oldBinding) { + // Clear the binding bit so that the binding doesn't appear later for any reason + data->clearBindingBit(coreIndex); return; + } if (!(flags & QQmlPropertyPrivate::DontEnable)) oldBinding->setEnabled(false, {}); diff --git a/src/qml/qml/qqmltypedata.cpp b/src/qml/qml/qqmltypedata.cpp index a244bdd0ba..2d2f37261c 100644 --- a/src/qml/qml/qqmltypedata.cpp +++ b/src/qml/qml/qqmltypedata.cpp @@ -345,7 +345,9 @@ void QQmlTypeData::done() ++it) { const TypeReference &type = *it; Q_ASSERT(!type.typeData || type.typeData->isCompleteOrError() || type.type.isInlineComponentType()); - if (type.type.isInlineComponentType() && !type.type.pendingResolutionName().isEmpty()) { + if (type.errorWhenNotFound + && type.type.isInlineComponentType() + && !type.type.pendingResolutionName().isEmpty()) { auto containingType = type.type.containingType(); auto objectId = containingType.lookupInlineComponentIdByName(type.type.pendingResolutionName()); if (objectId < 0) { // can be any negative number if we tentatively resolved it in QQmlImport but it actually was not an inline component @@ -365,7 +367,7 @@ void QQmlTypeData::done() type.type.setInlineComponentObjectId(objectId); } } - if (type.typeData && type.typeData->isError()) { + if (type.errorWhenNotFound && type.typeData && type.typeData->isError()) { const QString typeName = stringAt(it.key()); QList<QQmlError> errors = type.typeData->errors(); @@ -887,6 +889,7 @@ void QQmlTypeData::resolveTypes() ref.version = version; ref.location = unresolvedRef->location; ref.needsCreation = unresolvedRef->needsCreation; + ref.errorWhenNotFound = unresolvedRef->errorWhenNotFound; m_resolvedTypes.insert(unresolvedRef.key(), ref); } @@ -930,8 +933,12 @@ QQmlError QQmlTypeData::buildTypeResolutionCaches( } else { objectId = resolvedType->type.inlineComponentId(); } - Q_ASSERT(objectId != -1); - ref->setTypePropertyCache(resolvedType->typeData->compilationUnit()->propertyCaches.at(objectId)); + + if (objectId >= 0) { + ref->setTypePropertyCache( + resolvedType->typeData->compilationUnit()->propertyCaches.at(objectId)); + } + ref->setType(qmlType); Q_ASSERT(ref->type().isInlineComponentType()); } diff --git a/src/qml/qml/qqmltypedata_p.h b/src/qml/qml/qqmltypedata_p.h index 2c56ede9bd..e55e5ebae6 100644 --- a/src/qml/qml/qqmltypedata_p.h +++ b/src/qml/qml/qqmltypedata_p.h @@ -62,16 +62,16 @@ class Q_AUTOTEST_EXPORT QQmlTypeData : public QQmlTypeLoader::Blob public: struct TypeReference { - TypeReference() : version(QTypeRevision::zero()), needsCreation(true) {} - QV4::CompiledData::Location location; QQmlType type; - QTypeRevision version; + QTypeRevision version = QTypeRevision::zero(); QQmlRefPointer<QQmlTypeData> typeData; - bool selfReference = false; QString prefix; // used by CompositeSingleton types + bool selfReference = false; + bool needsCreation = true; + bool errorWhenNotFound = true; + QString qualifiedName() const; - bool needsCreation; }; struct ScriptReference diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 733cb3b2c0..827d2c2cf5 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -1340,6 +1340,7 @@ void QQmlJSImportVisitor::endVisit(UiArrayBinding *) bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied) { QQmlJSMetaEnum qmlEnum(uied->name.toString()); + qmlEnum.setIsQml(true); for (const auto *member = uied->members; member; member = member->next) { qmlEnum.addKey(member->member.toString()); qmlEnum.addValue(int(member->value)); diff --git a/src/qmlcompiler/qqmljsmetatypes_p.h b/src/qmlcompiler/qqmljsmetatypes_p.h index 513414d87f..c0b3144819 100644 --- a/src/qmlcompiler/qqmljsmetatypes_p.h +++ b/src/qmlcompiler/qqmljsmetatypes_p.h @@ -66,6 +66,8 @@ class QQmlJSMetaEnum QString m_alias; QSharedPointer<const QQmlJSScope> m_type; bool m_isFlag = false; + bool m_isScoped = false; + bool m_isQml = false; public: QQmlJSMetaEnum() = default; @@ -82,6 +84,12 @@ public: bool isFlag() const { return m_isFlag; } void setIsFlag(bool isFlag) { m_isFlag = isFlag; } + bool isScoped() const { return m_isScoped; } + void setIsScoped(bool v) { m_isScoped = v; } + + bool isQml() const { return m_isQml; } + void setIsQml(bool v) { m_isQml = v; } + void addKey(const QString &key) { m_keys.append(key); } QStringList keys() const { return m_keys; } @@ -102,7 +110,8 @@ public: && a.m_name == b.m_name && a.m_alias == b.m_alias && a.m_isFlag == b.m_isFlag - && a.m_type == b.m_type; + && a.m_type == b.m_type + && a.m_isScoped == b.m_isScoped; } friend bool operator!=(const QQmlJSMetaEnum &a, const QQmlJSMetaEnum &b) @@ -112,7 +121,8 @@ public: friend size_t qHash(const QQmlJSMetaEnum &e, size_t seed = 0) { - return qHashMulti(seed, e.m_keys, e.m_values, e.m_name, e.m_alias, e.m_isFlag, e.m_type); + return qHashMulti( + seed, e.m_keys, e.m_values, e.m_name, e.m_alias, e.m_isFlag, e.m_type, e.m_isScoped); } }; diff --git a/src/qmlcompiler/qqmljstypedescriptionreader.cpp b/src/qmlcompiler/qqmljstypedescriptionreader.cpp index 04293fd14c..779e040a2d 100644 --- a/src/qmlcompiler/qqmljstypedescriptionreader.cpp +++ b/src/qmlcompiler/qqmljstypedescriptionreader.cpp @@ -418,9 +418,11 @@ void QQmlJSTypeDescriptionReader::readEnum(UiObjectDefinition *ast, const QQmlJS metaEnum.setIsFlag(readBoolBinding(script)); } else if (name == QLatin1String("values")) { readEnumValues(script, &metaEnum); + } else if (name == QLatin1String("isScoped")) { + metaEnum.setIsScoped(readBoolBinding(script)); } else { addWarning(script->firstSourceLocation(), - tr("Expected only name and values script bindings.")); + tr("Expected only name, alias, isFlag, values, or isScoped.")); } } diff --git a/src/qmlmodels/qqmllistmodel.cpp b/src/qmlmodels/qqmllistmodel.cpp index c060aaec97..1663471ab5 100644 --- a/src/qmlmodels/qqmllistmodel.cpp +++ b/src/qmlmodels/qqmllistmodel.cpp @@ -1699,9 +1699,12 @@ bool ModelObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Val ExecutionEngine *eng = that->engine(); const int elementIndex = that->d()->elementIndex(); - int roleIndex = that->d()->m_model->m_listModel->setExistingProperty(elementIndex, propName, value, eng); - if (roleIndex != -1) - that->d()->m_model->emitItemsChanged(elementIndex, 1, QVector<int>(1, roleIndex)); + if (QQmlListModel *model = that->d()->m_model) { + const int roleIndex + = model->listModel()->setExistingProperty(elementIndex, propName, value, eng); + if (roleIndex != -1) + model->emitItemsChanged(elementIndex, 1, QVector<int>(1, roleIndex)); + } ModelNodeMetaObject *mo = ModelNodeMetaObject::get(that->object()); if (mo->initialized()) @@ -1717,7 +1720,11 @@ ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Va const ModelObject *that = static_cast<const ModelObject*>(m); Scope scope(that); ScopedString name(scope, id.asStringOrSymbol()); - const ListLayout::Role *role = that->d()->m_model->m_listModel->getExistingRole(name); + QQmlListModel *model = that->d()->m_model; + if (!model) + return QObjectWrapper::virtualGet(m, id, receiver, hasProperty); + + const ListLayout::Role *role = model->listModel()->getExistingRole(name); if (!role) return QObjectWrapper::virtualGet(m, id, receiver, hasProperty); if (hasProperty) @@ -1730,7 +1737,7 @@ ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Va } const int elementIndex = that->d()->elementIndex(); - QVariant value = that->d()->m_model->data(elementIndex, role->index); + QVariant value = model->data(elementIndex, role->index); return that->engine()->fromVariant(value); } @@ -1753,16 +1760,19 @@ PropertyKey ModelObjectOwnPropertyKeyIterator::next(const Object *o, Property *p const ModelObject *that = static_cast<const ModelObject *>(o); ExecutionEngine *v4 = that->engine(); - if (roleNameIndex < that->listModel()->roleCount()) { + + QQmlListModel *model = that->d()->m_model; + ListModel *listModel = model ? model->listModel() : nullptr; + if (listModel && roleNameIndex < listModel->roleCount()) { Scope scope(that->engine()); - const ListLayout::Role &role = that->listModel()->getExistingRole(roleNameIndex); + const ListLayout::Role &role = listModel->getExistingRole(roleNameIndex); ++roleNameIndex; ScopedString roleName(scope, v4->newString(role.name)); if (attrs) *attrs = QV4::Attr_Data; if (pd) { - QVariant value = that->d()->m_model->data(that->d()->elementIndex(), role.index); + QVariant value = model->data(that->d()->elementIndex(), role.index); if (auto recursiveListModel = qvariant_cast<QQmlListModel*>(value)) { auto size = recursiveListModel->count(); auto array = ScopedArrayObject{scope, v4->newArrayObject(size)}; diff --git a/src/qmlmodels/qqmllistmodel_p.h b/src/qmlmodels/qqmllistmodel_p.h index 7cd2b4662b..3ebdf209a4 100644 --- a/src/qmlmodels/qqmllistmodel_p.h +++ b/src/qmlmodels/qqmllistmodel_p.h @@ -115,6 +115,8 @@ public: bool dynamicRoles() const { return m_dynamicRoles; } void setDynamicRoles(bool enableDynamicRoles); + ListModel *listModel() const { return m_listModel; } + Q_SIGNALS: void countChanged(); diff --git a/src/qmlmodels/qqmllistmodel_p_p.h b/src/qmlmodels/qqmllistmodel_p_p.h index 19cba980f4..da04b0a5f5 100644 --- a/src/qmlmodels/qqmllistmodel_p_p.h +++ b/src/qmlmodels/qqmllistmodel_p_p.h @@ -161,13 +161,23 @@ struct ModelObject : public QObjectWrapper { { QObjectWrapper::init(object); m_model = model; - QObjectPrivate *op = QObjectPrivate::get(object); - m_nodeModelMetaObject = static_cast<ModelNodeMetaObject *>(op->metaObject); } - void destroy() { QObjectWrapper::destroy(); } - int elementIndex() const { return m_nodeModelMetaObject->m_elementIndex; } - QQmlListModel *m_model; - ModelNodeMetaObject *m_nodeModelMetaObject; + + void destroy() + { + m_model.destroy(); + QObjectWrapper::destroy(); + } + + int elementIndex() const { + if (const QObject *o = object()) { + const QObjectPrivate *op = QObjectPrivate::get(o); + return static_cast<ModelNodeMetaObject *>(op->metaObject)->m_elementIndex; + } + return -1; + } + + QV4QPointer<QQmlListModel> m_model; }; } @@ -177,8 +187,6 @@ struct ModelObject : public QObjectWrapper V4_OBJECT2(ModelObject, QObjectWrapper) V4_NEEDS_DESTROY - ListModel *listModel() const { return d()->m_model->m_listModel; } - protected: static bool virtualPut(Managed *m, PropertyKey id, const Value& value, Value *receiver); static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); diff --git a/src/qmltest/doc/src/qtquicktest-index.qdoc b/src/qmltest/doc/src/qtquicktest-index.qdoc index 5a3fdfeef4..48cfdf205f 100644 --- a/src/qmltest/doc/src/qtquicktest-index.qdoc +++ b/src/qmltest/doc/src/qtquicktest-index.qdoc @@ -118,15 +118,67 @@ \snippet src_qmltest_qquicktest_snippet.cpp 1 Where "example" is the identifier to use to uniquely identify - this set of tests. Finally, add \c{CONFIG += qmltestcase} to the project - file: + this set of tests. + + \if defined(onlinedocs) + \tab {run-qtquicktest}{tab-cmake}{CMake}{checked} + \tab {run-qtquicktest}{tab-qmake}{qmake}{} + \tabcontent {tab-cmake} + \else + \section1 Using CMake + \endif + Configure your CMakeLists.txt file and build your project using your + favorite generator. + \badcode + cmake_minimum_required(VERSION 3.2) + + project(tst_example LANGUAGES CXX) + + enable_testing() + + find_package(Qt6 REQUIRED COMPONENTS QuickTest Qml) + + #[[The test harness scans the specified source directory recursively + for "tst_*.qml" files. By default, it looks in the current directory, + which is usually where the executable is. This command makes it look + in the project's source directory instead.]] + add_definitions(-DQUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") + + qt_standard_project_setup(REQUIRES 6.6) + + add_executable(tst_example tst_example.cpp) + + add_test(NAME tst_example COMMAND tst_example) + + target_link_libraries(tst_example + PRIVATE + Qt6::QuickTest + Qt6::Qml + ) + \endcode + \if defined(onlinedocs) + \endtabcontent + \tabcontent {tab-qmake} + \else + \section1 Using qmake + \endif + Add \c{CONFIG += qmltestcase} to your project file: + \badcode + TEMPLATE = app + TARGET = tst_example + CONFIG += warn_on qmltestcase + SOURCES += tst_example.cpp + \endcode + + If \c IMPORTPATH is specified in your .pro file, each import path added to \c IMPORTPATH + will be passed as a command-line argument when the test is run using "make check": \badcode - TEMPLATE = app - TARGET = tst_example - CONFIG += warn_on qmltestcase - SOURCES += tst_example.cpp + IMPORTPATH += $$PWD/../imports/my_module1 $$PWD/../imports/my_module2 \endcode + \if defined(onlinedocs) + \endtabcontent + \endif The test harness scans the specified source directory recursively for "tst_*.qml" files. If \c{QUICK_TEST_SOURCE_DIR} is not defined, @@ -160,12 +212,6 @@ If your test case needs QML imports, then you can add them as \c{-import} options to the test program command-line. - If \c IMPORTPATH is specified in your .pro file, each import path added to \c IMPORTPATH - will be passed as a command-line argument when the test is run using "make check": - - \badcode - IMPORTPATH += $$PWD/../imports/my_module1 $$PWD/../imports/my_module2 - \endcode The \c{-functions} command-line option will return a list of the current tests functions. It is possible to run a single test function using the name diff --git a/src/qmltyperegistrar/qmltypesclassdescription.cpp b/src/qmltyperegistrar/qmltypesclassdescription.cpp index b58d4ac00a..ec58998ee7 100644 --- a/src/qmltyperegistrar/qmltypesclassdescription.cpp +++ b/src/qmltyperegistrar/qmltypesclassdescription.cpp @@ -112,10 +112,16 @@ void QmlTypesClassDescription::collectLocalAnonymous( const auto classInfos = classDef->value(QLatin1String("classInfos")).toArray(); for (const QJsonValue classInfo : classInfos) { const QJsonObject obj = classInfo.toObject(); - if (obj[QStringLiteral("name")].toString() == QStringLiteral("DefaultProperty")) - defaultProp = obj[QStringLiteral("value")].toString(); - if (obj[QStringLiteral("name")].toString() == QStringLiteral("ParentProperty")) - parentProp = obj[QStringLiteral("value")].toString(); + const QString name = obj[QStringLiteral("name")].toString(); + const auto value = [&]() { return obj[QStringLiteral("value")].toString(); }; + if (name == QStringLiteral("DefaultProperty")) { + defaultProp = value(); + } else if (name == QStringLiteral("ParentProperty")) { + parentProp = value(); + } else if (name == QStringLiteral("RegisterEnumClassesUnscoped") + && value() == QStringLiteral("false")) { + registerEnumClassesScoped = true; + } } collectInterfaces(classDef); @@ -143,6 +149,9 @@ void QmlTypesClassDescription::collect( } else if (name == QLatin1String("ParentProperty")) { if (mode != RelatedType && parentProp.isEmpty()) parentProp = value; + } else if (name == QLatin1String("RegisterEnumClassesUnscoped")) { + if (mode != RelatedType && value == QLatin1String("false")) + registerEnumClassesScoped = true; } else if (name == QLatin1String("QML.AddedInVersion")) { const QTypeRevision revision = QTypeRevision::fromEncodedVersion(value.toInt()); if (mode == TopLevel) { @@ -192,10 +201,12 @@ void QmlTypesClassDescription::collect( if (const QJsonObject *other = findType(foreign, foreignTypeName)) { classDef = other; - // Default properties are always local. + // Default properties and enum classes are always local. defaultProp.clear(); + registerEnumClassesScoped = false; // Foreign type can have a default property or an attached types + // or RegisterEnumClassesUnscoped classinfo. const auto classInfos = classDef->value(QLatin1String("classInfos")).toArray(); for (const QJsonValue classInfo : classInfos) { const QJsonObject obj = classInfo.toObject(); @@ -205,6 +216,9 @@ void QmlTypesClassDescription::collect( defaultProp = foreignValue; } else if (parentProp.isEmpty() && foreignName == QLatin1String("ParentProperty")) { parentProp = foreignValue; + } else if (foreignName == QLatin1String("RegisterEnumClassesUnscoped")) { + if (foreignValue == QLatin1String("false")) + registerEnumClassesScoped = true; } else if (foreignName == QLatin1String("QML.Attached")) { attachedType = foreignValue; collectRelated(foreignValue, types, foreign, defaultRevision); diff --git a/src/qmltyperegistrar/qmltypesclassdescription.h b/src/qmltyperegistrar/qmltypesclassdescription.h index 1e043fb925..ecc5eb1ebf 100644 --- a/src/qmltyperegistrar/qmltypesclassdescription.h +++ b/src/qmltyperegistrar/qmltypesclassdescription.h @@ -55,6 +55,7 @@ struct QmlTypesClassDescription bool isSingleton = false; bool isRootClass = false; bool hasCustomParser = false; + bool registerEnumClassesScoped = false; QStringList implementsInterfaces; enum CollectMode { diff --git a/src/qmltyperegistrar/qmltypescreator.cpp b/src/qmltyperegistrar/qmltypescreator.cpp index 15740579c0..dbc9721a37 100644 --- a/src/qmltyperegistrar/qmltypescreator.cpp +++ b/src/qmltyperegistrar/qmltypescreator.cpp @@ -244,7 +244,8 @@ void QmlTypesCreator::writeMethods(const QJsonArray &methods, const QString &typ } } -void QmlTypesCreator::writeEnums(const QJsonArray &enums) +void QmlTypesCreator::writeEnums( + const QJsonArray &enums, QmlTypesCreator::EnumClassesMode enumClassesMode) { for (const QJsonValue item : enums) { const QJsonObject obj = item.toObject(); @@ -263,6 +264,13 @@ void QmlTypesCreator::writeEnums(const QJsonArray &enums) auto isFlag = obj.find(QLatin1String("isFlag")); if (isFlag != obj.end() && isFlag->toBool()) m_qml.writeBooleanBinding(isFlag.key(), true); + + if (enumClassesMode == EnumClassesMode::Scoped) { + const auto isClass = obj.find(QLatin1String("isClass")); + if (isClass != obj.end() && isClass->toBool()) + m_qml.writeBooleanBinding(QLatin1String("isScoped"), true); + } + m_qml.writeArrayBinding(QLatin1String("values"), valueList); m_qml.writeEndObject(); } @@ -382,7 +390,11 @@ void QmlTypesCreator::writeComponents() writeClassProperties(collector); if (const QJsonObject *classDef = collector.resolvedClass) { - writeEnums(members(classDef, enumsKey, m_version)); + writeEnums( + members(classDef, enumsKey, m_version), + collector.registerEnumClassesScoped + ? EnumClassesMode::Scoped + : EnumClassesMode::Unscoped); writeProperties(members(classDef, propertiesKey, m_version)); @@ -411,7 +423,11 @@ void QmlTypesCreator::writeComponents() collector.collectLocalAnonymous(&component, m_ownTypes, m_foreignTypes, m_version); writeClassProperties(collector); - writeEnums(members(&component, enumsKey, m_version)); + writeEnums( + members(&component, enumsKey, m_version), + collector.registerEnumClassesScoped + ? EnumClassesMode::Scoped + : EnumClassesMode::Unscoped); writeProperties(members(&component, propertiesKey, m_version)); diff --git a/src/qmltyperegistrar/qmltypescreator.h b/src/qmltyperegistrar/qmltypescreator.h index 3e8fa480a2..aff41485cc 100644 --- a/src/qmltyperegistrar/qmltypescreator.h +++ b/src/qmltyperegistrar/qmltypescreator.h @@ -53,7 +53,10 @@ private: void writeType(const QJsonObject &property, const QString &key); void writeProperties(const QJsonArray &properties); void writeMethods(const QJsonArray &methods, const QString &type); - void writeEnums(const QJsonArray &enums); + + enum class EnumClassesMode { Scoped, Unscoped }; + void writeEnums(const QJsonArray &enums, EnumClassesMode enumClassesMode); + void writeComponents(); QByteArray m_output; diff --git a/src/quick/doc/snippets/pointerHandlers/dragHandlerMargin.qml b/src/quick/doc/snippets/pointerHandlers/dragHandlerMargin.qml new file mode 100644 index 0000000000..20a3a802cf --- /dev/null +++ b/src/quick/doc/snippets/pointerHandlers/dragHandlerMargin.qml @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2024 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick + +//! [entire] +Item { + width: 320 + height: 240 + //![draggable] + Rectangle { + width: 24 + height: 24 + border.color: "steelblue" + Text { + text: "it's\ntiny" + font.pixelSize: 7 + rotation: -45 + anchors.centerIn: parent + } + + DragHandler { + margin: 12 + } + } + //![draggable] +} +//! [entire] diff --git a/src/quick/doc/snippets/pointerHandlers/draggableGridView.qml b/src/quick/doc/snippets/pointerHandlers/draggableGridView.qml new file mode 100644 index 0000000000..0b19274e0d --- /dev/null +++ b/src/quick/doc/snippets/pointerHandlers/draggableGridView.qml @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2024 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$ +** +****************************************************************************/ + +pragma ComponentBehavior: Bound +import QtQml +import QtQuick +import QtQml.Models + +//! [entire] +GridView { + id: root + width: 320 + height: 480 + cellWidth: 80 + cellHeight: 80 + interactive: false + + displaced: Transition { + NumberAnimation { + properties: "x,y" + easing.type: Easing.OutQuad + } + } + + model: DelegateModel { + id: visualModel + model: 24 + property var dropTarget: undefined + property bool copy: false + delegate: DropArea { + id: delegateRoot + + width: 80 + height: 80 + + onEntered: drag => { + if (visualModel.copy) { + if (drag.source !== icon) + visualModel.dropTarget = icon + } else { + visualModel.items.move(drag.source.DelegateModel.itemsIndex, icon.DelegateModel.itemsIndex) + } + } + + Rectangle { + id: icon + objectName: DelegateModel.itemsIndex + + property string text + Component.onCompleted: { + color = Qt.rgba(0.2 + (48 - DelegateModel.itemsIndex) * Math.random() / 48, + 0.3 + DelegateModel.itemsIndex * Math.random() / 48, + 0.4 * Math.random(), + 1.0) + text = DelegateModel.itemsIndex + } + border.color: visualModel.dropTarget === this ? "black" : "transparent" + border.width: 2 + radius: 3 + width: 72 + height: 72 + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + + states: [ + State { + when: dragHandler.active || controlDragHandler.active + ParentChange { + target: icon + parent: root + } + + AnchorChanges { + target: icon + anchors { + horizontalCenter: undefined + verticalCenter: undefined + } + } + } + ] + + Text { + anchors.centerIn: parent + color: "white" + font.pointSize: 14 + text: controlDragHandler.active ? "+" : icon.text + } + + //! [draghandlers] + DragHandler { + id: dragHandler + acceptedModifiers: Qt.NoModifier + onActiveChanged: if (!active) visualModel.dropTarget = undefined + } + + DragHandler { + id: controlDragHandler + acceptedModifiers: Qt.ControlModifier + onActiveChanged: { + visualModel.copy = active + if (!active) { + visualModel.dropTarget.text = icon.text + visualModel.dropTarget.color = icon.color + visualModel.dropTarget = undefined + } + } + } + //! [draghandlers] + + Drag.active: dragHandler.active || controlDragHandler.active + Drag.source: icon + Drag.hotSpot.x: 36 + Drag.hotSpot.y: 36 + } + } + } +} +//! [entire] diff --git a/src/quick/handlers/qquickdraghandler.cpp b/src/quick/handlers/qquickdraghandler.cpp index 8c9464ee55..c3f57f9b02 100644 --- a/src/quick/handlers/qquickdraghandler.cpp +++ b/src/quick/handlers/qquickdraghandler.cpp @@ -86,7 +86,8 @@ Q_LOGGING_CATEGORY(lcDragHandler, "qt.quick.handler.drag") \c target is an Item, \c centroid is the point at which the drag begins and to which the \c target will be moved (subject to constraints). - At this time, drag-and-drop is not yet supported. + DragHandler can be used together with the \l Drag attached property to + implement drag-and-drop. \sa Drag, MouseArea, {Pointer Handlers Example} */ @@ -134,7 +135,7 @@ void QQuickDragHandler::onGrabChanged(QQuickPointerHandler *grabber, QPointingDe The snap mode configures snapping of the \l target item's center to the \l eventPoint. Possible values: - \value DragHandler.SnapNever Never snap + \value DragHandler.NoSnap Never snap \value DragHandler.SnapAuto The \l target snaps if the \l eventPoint was pressed outside of the \l target item \e and the \l target is a descendant of \l {PointerHandler::}{parent} item (default) \value DragHandler.SnapWhenPressedOutsideTarget The \l target snaps if the \l eventPoint was pressed outside of the \l target @@ -394,6 +395,93 @@ void QQuickDragHandler::setActiveTranslation(const QVector2D &trans) \c {0, 0} again. */ +/*! + \qmlproperty flags QtQuick::DragHandler::acceptedButtons + + The mouse buttons that can activate this DragHandler. + + By default, this property is set to + \l {QtQuick::MouseEvent::button} {Qt.LeftButton}. + It can be set to an OR combination of mouse buttons, and will ignore events + from other buttons. + + For example, if a component (such as TextEdit) already handles + left-button drags in its own way, it can be augmented with a + DragHandler that does something different when dragged via the + right button: + + \snippet pointerHandlers/dragHandlerAcceptedButtons.qml 0 +*/ + +/*! + \qmlproperty flags DragHandler::acceptedDevices + + The types of pointing devices that can activate this DragHandler. + + By default, this property is set to + \l{QInputDevice::DeviceType}{PointerDevice.AllDevices}. + If you set it to an OR combination of device types, it will ignore events + from non-matching devices. + + \note Not all platforms are yet able to distinguish mouse and touchpad; and + on those that do, you often want to make mouse and touchpad behavior the same. +*/ + +/*! + \qmlproperty flags DragHandler::acceptedModifiers + + If this property is set, it will require the given keyboard modifiers to + be pressed in order to react to pointer events, and otherwise ignore them. + + For example, two DragHandlers can perform two different drag-and-drop + operations, depending on whether the \c Control modifier is pressed: + + \snippet pointerHandlers/draggableGridView.qml entire + + If this property is set to \c Qt.KeyboardModifierMask (the default value), + then the DragHandler ignores the modifier keys. + + If you set \c acceptedModifiers to an OR combination of modifier keys, + it means \e all of those modifiers must be pressed to activate the handler. + + The available modifiers are as follows: + + \value NoModifier No modifier key is allowed. + \value ShiftModifier A Shift key on the keyboard must be pressed. + \value ControlModifier A Ctrl key on the keyboard must be pressed. + \value AltModifier An Alt key on the keyboard must be pressed. + \value MetaModifier A Meta key on the keyboard must be pressed. + \value KeypadModifier A keypad button must be pressed. + \value GroupSwitchModifier X11 only (unless activated on Windows by a command line argument). + A Mode_switch key on the keyboard must be pressed. + \value KeyboardModifierMask The handler does not care which modifiers are pressed. + + \sa Qt::KeyboardModifier +*/ + +/*! + \qmlproperty flags DragHandler::acceptedPointerTypes + + The types of pointing instruments (finger, stylus, eraser, etc.) + that can activate this DragHandler. + + By default, this property is set to + \l {QPointingDevice::PointerType} {PointerDevice.AllPointerTypes}. + If you set it to an OR combination of device types, it will ignore events + from non-matching \l {PointerDevice}{devices}. +*/ + +/*! + \qmlproperty real DragHandler::margin + + The margin beyond the bounds of the \l {PointerHandler::parent}{parent} + item within which an \l eventPoint can activate this handler. For example, + you can make it easier to drag small items by allowing the user to drag + from a position nearby: + + \snippet pointerHandlers/dragHandlerMargin.qml draggable +*/ + QT_END_NAMESPACE #include "moc_qquickdraghandler_p.cpp" diff --git a/src/quick/items/qquickgridview.cpp b/src/quick/items/qquickgridview.cpp index 916ae61cf5..27438bda14 100644 --- a/src/quick/items/qquickgridview.cpp +++ b/src/quick/items/qquickgridview.cpp @@ -1404,9 +1404,53 @@ void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight) /*! \qmlproperty int QtQuick::GridView::count - This property holds the number of items in the view. + This property holds the number of items in the model. */ +/*! + \qmlproperty bool QtQuick::GridView::reuseItems + + This property enables you to reuse items that are instantiated + from the \l delegate. If set to \c false, any currently + pooled items are destroyed. + + This property is \c false by default. + + \since 5.15 + + \sa {Reusing items}, pooled(), reused() +*/ + +/*! + \qmlattachedsignal QtQuick::GridView::pooled() + + This signal is emitted after an item has been added to the reuse + pool. You can use it to pause ongoing timers or animations inside + the item, or free up resources that cannot be reused. + + This signal is emitted only if the \l reuseItems property is \c true. + + \sa {Reusing items}, reuseItems, reused() +*/ + +/*! + \qmlattachedsignal QtQuick::GridView::reused() + + This signal is emitted after an item has been reused. At this point, the + item has been taken out of the pool and placed inside the content view, + and the model properties such as \c index and \c row have been updated. + + Other properties that are not provided by the model does not change when an + item is reused. You should avoid storing any state inside a delegate, but if + you do, manually reset that state on receiving this signal. + + This signal is emitted when the item is reused, and not the first time the + item is created. + + This signal is emitted only if the \l reuseItems property is \c true. + + \sa {Reusing items}, reuseItems, pooled() +*/ /*! \qmlproperty Component QtQuick::GridView::highlight diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp index 9e7a8f4803..6c822e62c1 100644 --- a/src/quick/items/qquicklistview.cpp +++ b/src/quick/items/qquicklistview.cpp @@ -1721,7 +1721,7 @@ void QQuickListViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExte updateHighlight(); bottomItem = currentItem; } - qreal pos; + qreal pos = 0; bool isInBounds = -position() > maxExtent && -position() <= minExtent; if (header && !topItem && isInBounds) { @@ -1802,6 +1802,19 @@ void QQuickListViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExte QQuickItemViewPrivate::fixup(data, minExtent, maxExtent); return; } + // If we have the CurrentLabelAtStart flag set, then we need to consider + // the section size while calculating the position + if (sectionCriteria + && (sectionCriteria->labelPositioning() & QQuickViewSection::CurrentLabelAtStart) + && currentSectionItem) { + auto sectionSize = (orient == QQuickListView::Vertical) ? currentSectionItem->height() + : currentSectionItem->width(); + if (isContentFlowReversed()) + pos += sectionSize; + else + pos -= sectionSize; + } + pos = qBound(-minExtent, pos, -maxExtent); qreal dist = qAbs(data.move + pos); @@ -2380,7 +2393,7 @@ QQuickListView::~QQuickListView() /*! \qmlproperty int QtQuick::ListView::count - This property holds the number of items in the view. + This property holds the number of items in the model. */ /*! diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 9b0334b851..490e89acad 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -3297,11 +3297,18 @@ void QQuickTableViewPrivate::syncSyncView() q->setColumnSpacing(syncView->columnSpacing()); updateContentWidth(); - if (syncView->leftColumn() != q->leftColumn()) { - // The left column is no longer the same as the left - // column in syncView. This requires a rebuild. - scheduledRebuildOptions |= QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftColumn; - scheduledRebuildOptions.setFlag(RebuildOption::ViewportOnly); + if (scheduledRebuildOptions & RebuildOption::LayoutOnly) { + if (syncView->leftColumn() != q->leftColumn() + || syncView->d_func()->loadedTableOuterRect.left() != loadedTableOuterRect.left()) { + // The left column is no longer the same, or at the same pos, as the left column in + // syncView. This can happen if syncView did a relayout that caused its left column + // to be resized so small that it ended up outside the viewport. It can also happen + // if the syncView loaded and unloaded columns after the relayout. We therefore need + // to sync our own left column and pos to be the same, which we do by rebuilding the + // whole viewport instead of just doing a plain LayoutOnly. + scheduledRebuildOptions |= QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftColumn; + scheduledRebuildOptions.setFlag(RebuildOption::ViewportOnly); + } } } @@ -3309,11 +3316,18 @@ void QQuickTableViewPrivate::syncSyncView() q->setRowSpacing(syncView->rowSpacing()); updateContentHeight(); - if (syncView->topRow() != q->topRow()) { - // The top row is no longer the same as the top - // row in syncView. This requires a rebuild. - scheduledRebuildOptions |= QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftRow; - scheduledRebuildOptions.setFlag(RebuildOption::ViewportOnly); + if (scheduledRebuildOptions & RebuildOption::LayoutOnly) { + if (syncView->topRow() != q->topRow() + || syncView->d_func()->loadedTableOuterRect.top() != loadedTableOuterRect.top()) { + // The top row is no longer the same, or at the same pos, as the top row in + // syncView. This can happen if syncView did a relayout that caused its top row + // to be resized so small that it ended up outside the viewport. It can also happen + // if the syncView loaded and unloaded rows after the relayout. We therefore need + // to sync our own top row and pos to be the same, which we do by rebuilding the + // whole viewport instead of just doing a plain LayoutOnly. + scheduledRebuildOptions |= QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftRow; + scheduledRebuildOptions.setFlag(RebuildOption::ViewportOnly); + } } } diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index cef0dd2900..cdbcf46380 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -2462,8 +2462,10 @@ void QQuickText::geometryChange(const QRectF &newGeometry, const QRectF &oldGeom } } } else if (!heightChanged && widthMaximum) { - if (!qFuzzyIsNull(oldGeometry.width())) { + if (oldGeometry.width() > 0) { // no change to height, width is adequate and wasn't 0 before + // (old width could also be negative if it was 0 and the margins + // were set) goto geomChangeDone; } } diff --git a/src/quick/scenegraph/qsgadaptationlayer.cpp b/src/quick/scenegraph/qsgadaptationlayer.cpp index 9d18868c58..d3aa5ef7a8 100644 --- a/src/quick/scenegraph/qsgadaptationlayer.cpp +++ b/src/quick/scenegraph/qsgadaptationlayer.cpp @@ -190,7 +190,12 @@ void QSGDistanceFieldGlyphCache::update() distanceFields.reserve(pendingGlyphsSize); for (int i = 0; i < pendingGlyphsSize; ++i) { GlyphData &gd = glyphData(m_pendingGlyphs.at(i)); - distanceFields.append(QDistanceField(gd.path, + + QSize size = QSize(qCeil(gd.texCoord.width + gd.texCoord.xMargin * 2), + qCeil(gd.texCoord.height + gd.texCoord.yMargin * 2)); + + distanceFields.append(QDistanceField(size, + gd.path, m_pendingGlyphs.at(i), m_doubleGlyphResolution)); gd.path = QPainterPath(); // no longer needed, so release memory used by the painter path diff --git a/src/quick/scenegraph/qsgdefaultglyphnode.cpp b/src/quick/scenegraph/qsgdefaultglyphnode.cpp index d3e0d5a7c5..fbc2227bae 100644 --- a/src/quick/scenegraph/qsgdefaultglyphnode.cpp +++ b/src/quick/scenegraph/qsgdefaultglyphnode.cpp @@ -83,11 +83,14 @@ void QSGDefaultGlyphNode::update() QRawFont font = m_glyphs.rawFont(); QMargins margins(0, 0, 0, 0); - if (m_style == QQuickText::Normal) { + const auto *fontEngine = QRawFontPrivate::get(font)->fontEngine; + const bool isColorFont = fontEngine->glyphFormat == QFontEngine::Format_ARGB; + + if (m_style == QQuickText::Normal || isColorFont) { QFontEngine::GlyphFormat glyphFormat; // Don't try to override glyph format of color fonts - if (QRawFontPrivate::get(font)->fontEngine->glyphFormat == QFontEngine::Format_ARGB) { + if (isColorFont) { glyphFormat = QFontEngine::Format_None; } else { switch (m_preferredAntialiasingMode) { diff --git a/src/quick/util/qquickanimation.cpp b/src/quick/util/qquickanimation.cpp index af60f5b02b..a2003fdc37 100644 --- a/src/quick/util/qquickanimation.cpp +++ b/src/quick/util/qquickanimation.cpp @@ -310,6 +310,11 @@ void QQuickAbstractAnimation::setRunning(bool r) // Therefore, the state of d->running will in that case be different than r if we are back in // the root stack frame of the recursive calls to setRunning() emit runningChanged(d->running); + } else if (d->animationInstance) { + // If there was a recursive call, make sure the d->running is set correctly + d->running = d->animationInstance->isRunning(); + } else { + d->running = r; } } diff --git a/src/quicklayouts/qquicklinearlayout.cpp b/src/quicklayouts/qquicklinearlayout.cpp index dc55196c11..f7f7523c21 100644 --- a/src/quicklayouts/qquicklinearlayout.cpp +++ b/src/quicklayouts/qquicklinearlayout.cpp @@ -482,7 +482,7 @@ void QQuickGridLayoutBase::itemVisibilityChanged(QQuickItem *item) void QQuickGridLayoutBase::rearrange(const QSizeF &size) { Q_D(QQuickGridLayoutBase); - if (!isReady()) + if (!isReady() || !size.isValid()) return; qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::rearrange" << d->m_recurRearrangeCounter << this; diff --git a/src/quicktemplates2/qquickcontrol.cpp b/src/quicktemplates2/qquickcontrol.cpp index 8a46d040f2..f999aec9b7 100644 --- a/src/quicktemplates2/qquickcontrol.cpp +++ b/src/quicktemplates2/qquickcontrol.cpp @@ -385,12 +385,22 @@ void QQuickControlPrivate::resizeBackground() bool changeHeight = false; if (((!p->widthValid() || !extra.isAllocated() || !extra->hasBackgroundWidth) && qFuzzyIsNull(background->x())) || (extra.isAllocated() && (extra->hasLeftInset || extra->hasRightInset))) { - background->setX(getLeftInset()); + const auto leftInset = getLeftInset(); + if (!qt_is_nan(leftInset) && p->x.valueBypassingBindings() != leftInset) { + // We bypass the binding here to prevent it from being removed + p->x.setValueBypassingBindings(leftInset); + p->dirty(DirtyType::Position); + } changeWidth = !p->width.hasBinding(); } if (((!p->heightValid() || !extra.isAllocated() || !extra->hasBackgroundHeight) && qFuzzyIsNull(background->y())) || (extra.isAllocated() && (extra->hasTopInset || extra->hasBottomInset))) { - background->setY(getTopInset()); + const auto topInset = getTopInset(); + if (!qt_is_nan(topInset) && p->y.valueBypassingBindings() != topInset) { + // We bypass the binding here to prevent it from being removed + p->y.setValueBypassingBindings(topInset); + p->dirty(DirtyType::Position); + } changeHeight = !p->height.hasBinding(); } if (changeHeight || changeWidth) { diff --git a/src/quicktemplates2/qquickoverlay.cpp b/src/quicktemplates2/qquickoverlay.cpp index 1829f9a30e..62abaae005 100644 --- a/src/quicktemplates2/qquickoverlay.cpp +++ b/src/quicktemplates2/qquickoverlay.cpp @@ -498,6 +498,13 @@ bool QQuickOverlay::childMouseEventFilter(QQuickItem *item, QEvent *event) case QEvent::HoverEnter: case QEvent::HoverMove: case QEvent::HoverLeave: + // If the control item has already been hovered, allow the hover leave event + // to be processed by the same item for resetting its internal hovered state + // instead of filtering it here. + if (auto *control = qobject_cast<QQuickControl *>(item)) { + if (control->isHovered() && event->type() == QEvent::HoverLeave) + return false; + } handled = d->handleHoverEvent(item, static_cast<QHoverEvent *>(event), popup); break; diff --git a/src/quickwidgets/qquickwidget.cpp b/src/quickwidgets/qquickwidget.cpp index c593ca2a81..058b959258 100644 --- a/src/quickwidgets/qquickwidget.cpp +++ b/src/quickwidgets/qquickwidget.cpp @@ -664,7 +664,24 @@ QQuickWidget::QQuickWidget(QWidget *parent) { setMouseTracking(true); setFocusPolicy(Qt::StrongFocus); +#ifndef Q_OS_MACOS + /* + Usually, a QTouchEvent comes from a touchscreen, and we want those + touch events in Qt Quick. But on macOS, there are no touchscreens, and + WA_AcceptTouchEvents has a different meaning: QApplication::notify() + calls the native-integration function registertouchwindow() to change + NSView::allowedTouchTypes to include NSTouchTypeMaskIndirect when the + trackpad cursor enters the window, and removes that mask when the + cursor exits. In other words, WA_AcceptTouchEvents enables getting + discrete touchpoints from the trackpad. We rather prefer to get mouse, + wheel and native gesture events from the trackpad (because those + provide more of a "native feel"). The only exception is for + MultiPointTouchArea, and it takes care of that for itself. So don't + automatically set WA_AcceptTouchEvents on macOS. The user can still do + it, but we don't recommend it. + */ setAttribute(Qt::WA_AcceptTouchEvents); +#endif d_func()->init(); } diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt index 5fe0b0950f..4b2fb9c1e7 100644 --- a/tests/auto/qml/CMakeLists.txt +++ b/tests/auto/qml/CMakeLists.txt @@ -120,6 +120,7 @@ if(QT_FEATURE_private_tests) add_subdirectory(qqmltablemodel) add_subdirectory(qv4assembler) add_subdirectory(qv4mm) + add_subdirectory(qv4estable) add_subdirectory(qv4identifiertable) add_subdirectory(qv4regexp) if(QT_FEATURE_process AND NOT QNX) diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index c2c6f3f43f..8d2b5179f8 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -293,6 +293,7 @@ private slots: void spreadNoOverflow(); void deleteDefineCycle(); + void deleteFromSparseArray(); public: Q_INVOKABLE QJSValue throwingCppMethod1(); @@ -5837,6 +5838,26 @@ void tst_QJSEngine::deleteDefineCycle() QVERIFY(stackTrace.isEmpty()); } +void tst_QJSEngine::deleteFromSparseArray() +{ + QJSEngine engine; + + // Should not crash + const QJSValue result = engine.evaluate(QLatin1String(R"((function() { + let o = []; + o[10000] = 10; + o[20000] = 20; + for (let k in o) + delete o[k]; + return o; + })())")); + + QVERIFY(result.isArray()); + QCOMPARE(result.property("length").toNumber(), 20001); + QVERIFY(result.property(10000).isUndefined()); + QVERIFY(result.property(20000).isUndefined()); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" diff --git a/tests/auto/qml/qqmllanguage/data/BindingOverrider.qml b/tests/auto/qml/qqmllanguage/data/BindingOverrider.qml new file mode 100644 index 0000000000..de7e1e96a3 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/BindingOverrider.qml @@ -0,0 +1,5 @@ +import QtQml 2.15 + +SimpleWidget { + width: 20 +} diff --git a/tests/auto/qml/qqmllanguage/data/SimpleWidget.qml b/tests/auto/qml/qqmllanguage/data/SimpleWidget.qml new file mode 100644 index 0000000000..9150ebaa4e --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/SimpleWidget.qml @@ -0,0 +1,25 @@ +import QtQuick 2.15 + +Item { + id: outer + + property real innerWidth: 0 + + Item { + id: inner + width: style.width + onWidthChanged: outer.innerWidth = width + } + + width: inner.width + + onWidthChanged: { + if (width !== inner.width) + inner.width = width // overwrite binding + } + + QtObject { + id: style + property int width: 50 + } +} diff --git a/tests/auto/qml/qqmllanguage/data/badICAnnotation.qml b/tests/auto/qml/qqmllanguage/data/badICAnnotation.qml new file mode 100644 index 0000000000..78555ac7dc --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/badICAnnotation.qml @@ -0,0 +1,26 @@ + +import QtQml + +QtObject { + id: self + + function doStuff(status: Binding.NotAnInlineComponent) : int { + return status + } + + function doStuff2(status: InlineComponentBase.IC) : QtObject { + return status + } + + function doStuff3(status: InlineComponentBase.NotIC) : QtObject { + return status + } + + property InlineComponentBase.IC ic: InlineComponentBase.IC {} + + property int a: doStuff(5) + property QtObject b: doStuff2(ic) + property QtObject c: doStuff3(ic) + property QtObject d: doStuff2(self) +} + diff --git a/tests/auto/qml/qqmllanguage/data/typedObjectList.qml b/tests/auto/qml/qqmllanguage/data/typedObjectList.qml new file mode 100644 index 0000000000..89c66249cf --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/typedObjectList.qml @@ -0,0 +1,13 @@ +import QtQml + +QtObject { + property var b; + property Component c: QtObject {} + + // In 6.5 and earlier we don't have heap-managed QQmlListProperty, yet. + property list<Component> ll; + + function returnList(a: Component) : list<Component> { ll.push(a); return ll; } + + Component.onCompleted: b = { b: returnList(c) } +} diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 3d1f023425..95e838476f 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -394,6 +394,11 @@ private slots: void deepAliasOnICOrReadonly(); void writeNumberToEnumAlias(); + void badInlineComponentAnnotation(); + + void typedObjectList(); + + void overrideInnerBinding(); private: QQmlEngine engine; @@ -6747,6 +6752,52 @@ void tst_qqmllanguage::writeNumberToEnumAlias() QCOMPARE(o->property("strokeStyle").toInt(), 1); } +void tst_qqmllanguage::badInlineComponentAnnotation() +{ + QQmlEngine engine; + const QUrl url = testFileUrl("badICAnnotation.qml"); + QQmlComponent c(&engine, testFileUrl("badICAnnotation.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("a").toInt(), 5); + + QObject *ic = o->property("ic").value<QObject *>(); + QVERIFY(ic); + + QCOMPARE(o->property("b").value<QObject *>(), ic); + QCOMPARE(o->property("c").value<QObject *>(), ic); +} + +void tst_qqmllanguage::typedObjectList() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("typedObjectList.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QJSValue b = o->property("b").value<QJSValue>(); + auto list = qjsvalue_cast<QQmlListProperty<QQmlComponent>>(b.property(QStringLiteral("b"))); + + QCOMPARE(list.count(&list), 1); + QVERIFY(list.at(&list, 0) != nullptr); +} + +void tst_qqmllanguage::overrideInnerBinding() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("BindingOverrider.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("width").toReal(), 20.0); + QCOMPARE(o->property("innerWidth").toReal(), 20.0); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" diff --git a/tests/auto/qml/qqmllistmodel/data/deadModelData.qml b/tests/auto/qml/qqmllistmodel/data/deadModelData.qml new file mode 100644 index 0000000000..fc3dd6fa11 --- /dev/null +++ b/tests/auto/qml/qqmllistmodel/data/deadModelData.qml @@ -0,0 +1,45 @@ +import QtQml + +QtObject { + function swapCorpses() { + const lhsData = getModelData(lhsButtonListModel); + const rhsData = getModelData(rhsButtonListModel); + + lhsButtonListModel.clear(); + rhsButtonListModel.clear(); + + addToModel(lhsButtonListModel, rhsData); + addToModel(rhsButtonListModel, lhsData); + } + + property ListModel l1: ListModel { + id: lhsButtonListModel + } + + property ListModel l2: ListModel { + id: rhsButtonListModel + } + + Component.onCompleted: { + lhsButtonListModel.append({ "ident": 1, "buttonText": "B 1"}); + lhsButtonListModel.append({ "ident": 2, "buttonText": "B 2"}); + lhsButtonListModel.append({ "ident": 3, "buttonText": "B 3"}); + + rhsButtonListModel.append({ "ident": 4, "buttonText": "B 4"}); + rhsButtonListModel.append({ "ident": 5, "buttonText": "B 5"}); + rhsButtonListModel.append({ "ident": 6, "buttonText": "B 6"}); + } + + function getModelData(model) { + var dataList = [] + for (var i = 0; i < model.count; ++i) + dataList.push(model.get(i)); + + return dataList; + } + + function addToModel(model, buttonData) { + for (var i = 0; i < buttonData.length; ++i) + model.append(buttonData[i]); + } +} diff --git a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp index fbdf6d90f3..f440eab6b7 100644 --- a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp +++ b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp @@ -140,6 +140,7 @@ private slots: void destroyComponentObject(); void objectOwnershipFlip(); void protectQObjectFromGC(); + void deadModelData(); }; bool tst_qqmllistmodel::compareVariantList(const QVariantList &testList, QVariant object) @@ -1940,6 +1941,67 @@ void tst_qqmllistmodel::protectQObjectFromGC() } } +void tst_qqmllistmodel::deadModelData() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("deadModelData.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> o(component.create()); + QVERIFY(!o.isNull()); + + QQmlListModel *l1 = o->property("l1").value<QQmlListModel *>(); + QVERIFY(l1); + QQmlListModel *l2 = o->property("l2").value<QQmlListModel *>(); + QVERIFY(l2); + + QCOMPARE(l1->count(), 3); + QCOMPARE(l2->count(), 3); + + for (int i = 0; i < 3; ++i) { + QObject *i1 = qjsvalue_cast<QObject *>(l1->get(i)); + QVERIFY(i1); + QCOMPARE(i1->property("ident").value<double>(), i + 1); + QCOMPARE(i1->property("buttonText").value<QString>(), + QLatin1String("B %1").arg(QLatin1Char('0' + i + 1))); + + QObject *i2 = qjsvalue_cast<QObject *>(l2->get(i)); + QVERIFY(i2); + QCOMPARE(i2->property("ident").value<double>(), i + 4); + QCOMPARE(i2->property("buttonText").value<QString>(), + QLatin1String("B %1").arg(QLatin1Char('0' + i + 4))); + } + + for (int i = 0; i < 6; ++i) { + QTest::ignoreMessage( + QtWarningMsg, + QRegularExpression(".*: ident is undefined. Adding an object with a undefined " + "member does not create a role for it.")); + QTest::ignoreMessage( + QtWarningMsg, + QRegularExpression(".*: buttonText is undefined. Adding an object with a undefined " + "member does not create a role for it.")); + } + + QMetaObject::invokeMethod(o.data(), "swapCorpses"); + + // We get default-created values for all the roles now. + + QCOMPARE(l1->count(), 3); + QCOMPARE(l2->count(), 3); + + for (int i = 0; i < 3; ++i) { + QObject *i1 = qjsvalue_cast<QObject *>(l1->get(i)); + QVERIFY(i1); + QCOMPARE(i1->property("ident").value<double>(), double()); + QCOMPARE(i1->property("buttonText").value<QString>(), QString()); + + QObject *i2 = qjsvalue_cast<QObject *>(l2->get(i)); + QVERIFY(i2); + QCOMPARE(i2->property("ident").value<double>(), double()); + QCOMPARE(i2->property("buttonText").value<QString>(), QString()); + } +} + QTEST_MAIN(tst_qqmllistmodel) #include "tst_qqmllistmodel.moc" diff --git a/tests/auto/qml/qv4estable/CMakeLists.txt b/tests/auto/qml/qv4estable/CMakeLists.txt new file mode 100644 index 0000000000..01d2663a04 --- /dev/null +++ b/tests/auto/qml/qv4estable/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qv4estable Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qv4estable LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qv4estable + SOURCES + tst_qv4estable.cpp + LIBRARIES + Qt::Gui + Qt::Qml + Qt::QmlPrivate +) + +## Scopes: +##################################################################### diff --git a/tests/auto/qml/qv4estable/tst_qv4estable.cpp b/tests/auto/qml/qv4estable/tst_qv4estable.cpp new file mode 100644 index 0000000000..eecb672bc6 --- /dev/null +++ b/tests/auto/qml/qv4estable/tst_qv4estable.cpp @@ -0,0 +1,40 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <qtest.h> +#include <private/qv4estable_p.h> + +class tst_qv4estable : public QObject +{ + Q_OBJECT + +private slots: + void checkRemoveAvoidsHeapBufferOverflow(); +}; + +// QTBUG-123999 +void tst_qv4estable::checkRemoveAvoidsHeapBufferOverflow() +{ + QV4::ESTable estable; + + // Fill the ESTable with values so it is at max capacity. + QCOMPARE(estable.m_capacity, 8u); + for (uint i = 0; i < estable.m_capacity; ++i) { + estable.set(QV4::Value::fromUInt32(i), QV4::Value::fromUInt32(i)); + } + // Our |m_keys| array should now contain eight values. + // > [v0, v1, v2, v3, v4, v5, v6, v7] + for (uint i = 0; i < estable.m_capacity; ++i) { + QVERIFY(estable.m_keys[i].sameValueZero(QV4::Value::fromUInt32(i))); + } + QCOMPARE(estable.m_capacity, 8u); + QCOMPARE(estable.m_size, 8u); + + // Remove the first item from the set to verify that asan does not trip. + // Relies on the CI platform propagating asan flag to all tests. + estable.remove(QV4::Value::fromUInt32(0)); +} + +QTEST_MAIN(tst_qv4estable) + +#include "tst_qv4estable.moc" diff --git a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp index 30b934eeec..579eeb7c0a 100644 --- a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp +++ b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp @@ -119,6 +119,7 @@ private slots: void cleanupWhenRenderThreadStops(); void infiniteLoopsWithoutFrom(); void targetsDeletedNotRemoved(); + void alwaysRunToEndSetFalseRestartBug(); }; #define QTIMED_COMPARE(lhs, rhs) do { \ for (int ii = 0; ii < 5; ++ii) { \ @@ -2081,6 +2082,41 @@ void tst_qquickanimations::targetsDeletedNotRemoved() } } +//QTBUG-125224 +void tst_qquickanimations::alwaysRunToEndSetFalseRestartBug() +{ + QQuickRectangle rect; + QQuickSequentialAnimation sequential; + QQuickPropertyAnimation beginAnim; + QQuickPropertyAnimation endAnim; + + beginAnim.setTargetObject(&rect); + beginAnim.setProperty("x"); + beginAnim.setTo(200); + beginAnim.setDuration(1000); + + endAnim.setTargetObject(&rect); + endAnim.setProperty("x"); + endAnim.setFrom(200); + endAnim.setDuration(1000); + + beginAnim.setGroup(&sequential); + endAnim.setGroup(&sequential); + + sequential.setLoops(-1); + sequential.setAlwaysRunToEnd(true); + + QCOMPARE(sequential.loops(), -1); + QVERIFY(sequential.alwaysRunToEnd()); + sequential.start(); + sequential.stop(); + sequential.setAlwaysRunToEnd(false); + sequential.start(); + QCOMPARE(sequential.isRunning(), true); + sequential.stop(); + QCOMPARE(sequential.isRunning(), false); +} + QTEST_MAIN(tst_qquickanimations) #include "tst_qquickanimations.moc" diff --git a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml index 5e8f6f9bcf..37166af119 100644 --- a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml +++ b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml @@ -1308,6 +1308,53 @@ Item { compare(rootRect.item1.width, 100) } + //--------------------------- + // Layout with negative size + Component { + id: negativeSize_Component + Item { + id: rootItem + width: 0 + height: 0 + // default width x height: (0 x 0) + RowLayout { + spacing: 0 + anchors.fill: parent + anchors.leftMargin: 1 // since parent size == (0 x 0), it causes layout size + anchors.bottomMargin: 1 // to become (-1, -1) + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + } + + function test_negativeSize() { + let rootItem = createTemporaryObject(negativeSize_Component, container) + let rowLayout = rootItem.children[0] + let item = rowLayout.children[0] + + // 1 doesn't work in 6.2 because that will make the layout of size 0x0 + // Setting the geometry of a layout to 0x0 doesn't work because + // QQuickLayout::geometryChange() early returns if !newGeometry.isValid() + // This check has been later removed. + const arr = [7, /*1,*/ 7, 0] + arr.forEach((n) => { + rootItem.width = n + rootItem.height = n + + // n === 0 is special: It will cause the layout to have a + // negative size. In this case it will simply not rearrange its + // child (and leave it at its previous size, 6) + const expectedItemExtent = n === 0 ? 6 : n - 1 + + compare(item.width, expectedItemExtent) + compare(item.height, expectedItemExtent) + }); + } + + //--------------------------- Component { id: rowlayoutWithTextItems_Component diff --git a/tests/auto/quick/qquicklistview/data/snapToItemWithSectionAtStart.qml b/tests/auto/quick/qquicklistview/data/snapToItemWithSectionAtStart.qml new file mode 100644 index 0000000000..0d4c233345 --- /dev/null +++ b/tests/auto/quick/qquicklistview/data/snapToItemWithSectionAtStart.qml @@ -0,0 +1,52 @@ +import QtQuick + +ListView { + id: listView + width: 240 + height: 300 + + model: ListModel { + ListElement { section: "section 1" } + ListElement { section: "section 1" } + ListElement { section: "section 1" } + ListElement { section: "section 2" } + ListElement { section: "section 2" } + ListElement { section: "section 2" } + ListElement { section: "section 3" } + ListElement { section: "section 3" } + ListElement { section: "section 3" } + ListElement { section: "section 4" } + ListElement { section: "section 4" } + ListElement { section: "section 4" } + ListElement { section: "section 5" } + ListElement { section: "section 5" } + ListElement { section: "section 5" } + } + + section.property: "section" + section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart + section.delegate: Rectangle { + width: listView.width + height: 30 + Text { + anchors.fill: parent + text: section + } + color: "lightblue" + } + + snapMode: ListView.SnapToItem + + delegate: Rectangle { + width: listView.width + height: 30 + Text { + anchors.fill: parent + text: index + } + border { + width: 1 + color: "black" + } + } +} diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index 24196e2ba1..8f3aa551e1 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -193,6 +193,7 @@ private slots: void headerSnapToItem_data(); void headerSnapToItem(); void snapToItemWithSpacing_QTBUG_59852(); + void snapToItemWithSectionAtStart(); void snapOneItemResize_QTBUG_43555(); void snapOneItem_data(); void snapOneItem(); @@ -5407,6 +5408,27 @@ void tst_QQuickListView::snapToItemWithSpacing_QTBUG_59852() releaseView(window); } +void tst_QQuickListView::snapToItemWithSectionAtStart() // QTBUG-30768 +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("snapToItemWithSectionAtStart.qml"))); + QQuickListView *listView = qobject_cast<QQuickListView *>(window.rootObject()); + QTRY_VERIFY(listView); + + // Both sections and elements are 30px high. The list height is 300px, so + // it fits exactly 10 elements. We can do some random flicks, but the + // content position always MUST be divisible by 30. + for (int i = 0; i < 10; ++i) { + const bool even = (i % 2 == 0); + const QPoint start = even ? QPoint(20, 100 + i * 5) : QPoint(20, 20 + i * 3); + const QPoint end = even ? start - QPoint(0, 50 + i * 10) : start + QPoint(0, 50 + i * 5); + + flick(&window, start, end, 180); + QTRY_COMPARE(listView->isMoving(), false); // wait until it stops + QCOMPARE(int(listView->contentY()) % 30, 0); + } +} + static void drag_helper(QWindow *window, QPoint *startPos, const QPoint &delta) { QPoint pos = *startPos; diff --git a/tests/auto/quick/qquickmultipointtoucharea/BLACKLIST b/tests/auto/quick/qquickmultipointtoucharea/BLACKLIST index 2488eff270..46db6477f5 100644 --- a/tests/auto/quick/qquickmultipointtoucharea/BLACKLIST +++ b/tests/auto/quick/qquickmultipointtoucharea/BLACKLIST @@ -4,3 +4,5 @@ ubuntu-22.04 [nested] ubuntu-20.04 ubuntu-22.04 +[inFlickable] +* # QTBUG-127628 diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index 5991621376..5330d8d01f 100644 --- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -185,6 +185,8 @@ private slots: void checkSyncView_pageFlicking(); void checkSyncView_emptyModel(); void checkSyncView_topLeftChanged(); + void checkSyncView_dontRelayoutWhileFlicking(); + void checkSyncView_detectTopLeftPositionChanged(); void delegateWithRequiredProperties(); void checkThatFetchMoreIsCalledWhenScrolledToTheEndOfTable(); void replaceModel(); @@ -3108,6 +3110,85 @@ void tst_QQuickTableView::checkSyncView_topLeftChanged() QCOMPARE(tableViewV->topRow(), tableView->topRow()); } +void tst_QQuickTableView::checkSyncView_dontRelayoutWhileFlicking() +{ + // Check that we don't do a full relayout in a sync child when + // a new row or column is flicked into the view. Normal load + // and unload of edges should suffice, equal to how the main + // TableView (syncView) does it. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewHV); + + auto model = TestModelAsVariant(100, 100); + tableView->setModel(model); + tableViewHV->setModel(model); + + tableView->setColumnWidthProvider(QJSValue()); + tableView->setRowHeightProvider(QJSValue()); + view->rootObject()->setProperty("delegateWidth", 50); + view->rootObject()->setProperty("delegateHeight", 50); + + WAIT_UNTIL_POLISHED; + + // To check that we don't do a relayout when flicking horizontally, we use a "trick" + // where we check the rebuildOptions when we receive the rightColumnChanged + // signal. If this signal is emitted as a part of a relayout, rebuildOptions + // would still be different from RebuildOption::None at that point. + bool columnFlickedIn = false; + connect(tableViewHV, &QQuickTableView::rightColumnChanged, [&] { + columnFlickedIn = true; + QCOMPARE(tableViewHVPrivate->rebuildOptions, QQuickTableViewPrivate::RebuildOption::None); + }); + + // We do the same for vertical flicking + bool rowFlickedIn = false; + connect(tableViewHV, &QQuickTableView::bottomRowChanged, [&] { + rowFlickedIn = true; + QCOMPARE(tableViewHVPrivate->rebuildOptions, QQuickTableViewPrivate::RebuildOption::None); + }); + + // Move the main tableview so that a new column is flicked in + tableView->setContentX(60); + QTRY_VERIFY(columnFlickedIn); + + // Move the main tableview so that a new row is flicked in + tableView->setContentY(60); + QTRY_VERIFY(rowFlickedIn); +} + +void tst_QQuickTableView::checkSyncView_detectTopLeftPositionChanged() +{ + // It can happen that, during a resize of columns or rows from using a float-based + // slider, that the position of the top-left delegate item is shifted a bit left or + // right because of rounding issues. And this again can over time, as you flick, make + // the loadedTableOuterRect get slightly out of sync in the sync child compared to the + // sync view. TableView will detect if this happens (in syncSyncView), and correct for + // it. And this test will test that it works. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewHV); + + auto model = TestModelAsVariant(100, 100); + tableView->setModel(model); + tableViewHV->setModel(model); + + WAIT_UNTIL_POLISHED; + + // Writing an auto test to trigger this rounding issue is very hard. So to keep it + // simple, we cheat by just moving the loadedTableOuterRect directly, and + // check that the syncView child detects it, and corrects it, upon doing a + // forceLayout() + tableViewPrivate->loadedTableOuterRect.moveLeft(20); + tableViewPrivate->loadedTableOuterRect.moveTop(30); + tableViewPrivate->relayoutTableItems(); + tableViewHV->forceLayout(); + + QCOMPARE(tableViewPrivate->loadedTableOuterRect.left(), 20); + QCOMPARE(tableViewHVPrivate->loadedTableOuterRect.left(), 20); + + QCOMPARE(tableViewPrivate->loadedTableOuterRect.top(), 30); + QCOMPARE(tableViewHVPrivate->loadedTableOuterRect.top(), 30); +} + void tst_QQuickTableView::checkThatFetchMoreIsCalledWhenScrolledToTheEndOfTable() { LOAD_TABLEVIEW("plaintableview.qml"); diff --git a/tests/auto/quick/qquicktext/data/elideZeroWidthWithMargins.qml b/tests/auto/quick/qquicktext/data/elideZeroWidthWithMargins.qml new file mode 100644 index 0000000000..bb269e6ad5 --- /dev/null +++ b/tests/auto/quick/qquicktext/data/elideZeroWidthWithMargins.qml @@ -0,0 +1,27 @@ +import QtQuick + +Item { + id: root + property bool ok: false + width: 640 + height: 480 + + Text { + id: text + text: "This is a quite long text. Click me and i should remain visible!!! Sadly this doesn't happen" + elide: Text.ElideRight + anchors { + fill: parent + margins: 1 + } + } + + Component.onCompleted: { + text.width = 300; + text.height = 0; + text.width = 0; + text.height = 30; + text.width = 300; + root.ok = text.paintedWidth > 0 && text.paintedHeight > 0 + } +} diff --git a/tests/auto/quick/qquicktext/tst_qquicktext.cpp b/tests/auto/quick/qquicktext/tst_qquicktext.cpp index 476297918d..ea90fc74d6 100644 --- a/tests/auto/quick/qquicktext/tst_qquicktext.cpp +++ b/tests/auto/quick/qquicktext/tst_qquicktext.cpp @@ -69,6 +69,7 @@ private slots: void wrap(); void elide(); void elideParentChanged(); + void elideRelayoutAfterZeroWidth_data(); void elideRelayoutAfterZeroWidth(); void multilineElide_data(); void multilineElide(); @@ -611,10 +612,19 @@ void tst_qquicktext::elideParentChanged() QCOMPARE(actualItemImageGrab, expectedItemImageGrab); } +void tst_qquicktext::elideRelayoutAfterZeroWidth_data() +{ + QTest::addColumn<QByteArray>("fileName"); + + QTest::newRow("no_margins") << QByteArray("elideZeroWidth.qml"); + QTest::newRow("with_margins") << QByteArray("elideZeroWidthWithMargins.qml"); +} + void tst_qquicktext::elideRelayoutAfterZeroWidth() { + QFETCH(const QByteArray, fileName); QQmlEngine engine; - QQmlComponent component(&engine, testFileUrl("elideZeroWidth.qml")); + QQmlComponent component(&engine, testFileUrl(fileName.constData())); QScopedPointer<QObject> root(component.create()); QVERIFY2(root, qPrintable(component.errorString())); QVERIFY(root->property("ok").toBool()); diff --git a/tests/auto/quickcontrols2/controls/data/tst_control.qml b/tests/auto/quickcontrols2/controls/data/tst_control.qml index e12cbbf4ef..5103d9afd4 100644 --- a/tests/auto/quickcontrols2/controls/data/tst_control.qml +++ b/tests/auto/quickcontrols2/controls/data/tst_control.qml @@ -503,6 +503,36 @@ TestCase { } Component { + id: backgroundTest2 + Button { + id: btn + width: 100 + height: 100 + topInset: 0 + objectName: "" + + background: Rectangle { + id: bg + implicitHeight: 80 + border.color: "red" + y: btn.objectName === "aaa" ? 20 : 0 + } + } + } + + // QTBUG-120033: Make sure that the binding for y on the tab button's background doesn't get removed + function test_background2() { + let button = createTemporaryObject(backgroundTest2, testCase) + verify(button) + + verify(button.background.y === 0) + button.objectName = "aaa" + verify(button.background.y === 20) + button.objectName = "" + verify(button.background.y === 0) + } + + Component { id: component2 T.Control { id: item2 diff --git a/tests/auto/quickcontrols2/qquickpopup/data/resetHoveredForItemsWithinOverlay.qml b/tests/auto/quickcontrols2/qquickpopup/data/resetHoveredForItemsWithinOverlay.qml new file mode 100644 index 0000000000..a7e66718ee --- /dev/null +++ b/tests/auto/quickcontrols2/qquickpopup/data/resetHoveredForItemsWithinOverlay.qml @@ -0,0 +1,28 @@ +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: root + width: 100 + height: 100 + property alias controlsPopup: _controlsPopup + property alias blockInputPopup: _blockInputPopup + Popup { + id: _controlsPopup + width: parent.width + height: parent.height + modal: true + Control { + id: controls + anchors.fill: parent + hoverEnabled: true + contentItem: Text { text: "Test Control" } + } + } + Popup { + id: _blockInputPopup + width: parent.width + height: parent.height + modal: true + } +} diff --git a/tests/auto/quickcontrols2/qquickpopup/tst_qquickpopup.cpp b/tests/auto/quickcontrols2/qquickpopup/tst_qquickpopup.cpp index a587c085a4..2895ac4a83 100644 --- a/tests/auto/quickcontrols2/qquickpopup/tst_qquickpopup.cpp +++ b/tests/auto/quickcontrols2/qquickpopup/tst_qquickpopup.cpp @@ -121,6 +121,7 @@ private slots: void shrinkPopupThatWasLargerThanWindow(); void mirroredCombobox(); void rotatedCombobox(); + void resetHoveredStateForItemsWithinPopup(); private: static bool hasWindowActivation(); @@ -2038,6 +2039,40 @@ void tst_QQuickPopup::rotatedCombobox() } } +void tst_QQuickPopup::resetHoveredStateForItemsWithinPopup() +{ + QQuickControlsApplicationHelper helper(this, "resetHoveredForItemsWithinOverlay.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickPopup *controlsPopup = window->property("controlsPopup").value<QQuickPopup*>(); + QVERIFY(controlsPopup); + + QQuickPopup *blockInputPopup = window->property("blockInputPopup").value<QQuickPopup*>(); + QVERIFY(controlsPopup); + + controlsPopup->open(); + QTRY_VERIFY(controlsPopup->isOpened()); + + QTest::mouseMove(window, QPoint(window->width() / 2 + 2, window->height() / 2 + 2)); + QTest::mouseMove(window, QPoint(window->width() / 2, window->height() / 2)); + + auto *controlItem = qobject_cast<QQuickControl *>(controlsPopup->contentItem()->childItems().at(0)); + QVERIFY(controlItem); + // Check hover enabled for the control item within the popup + QTRY_VERIFY(controlItem->isHovered()); + + // Open the modal popup window over the existing control item + blockInputPopup->open(); + QTRY_VERIFY(blockInputPopup->isOpened()); + + // Control item hovered shall be disabled once we open the modal popup + QTRY_VERIFY(!controlItem->isHovered()); +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickPopup) #include "tst_qquickpopup.moc" diff --git a/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp b/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp index 54673b70ce..f0bfd3c79c 100644 --- a/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp +++ b/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp @@ -711,6 +711,9 @@ void tst_qquickwidget::touchMultipleWidgets() QWidget window; QQuickWidget *leftQuick = new QQuickWidget; leftQuick->setSource(testFileUrl("button.qml")); + if (!leftQuick->testAttribute(Qt::WA_AcceptTouchEvents)) + QSKIP("irrelevant on non-touch platforms"); + QQuickWidget *rightQuick = new QQuickWidget; rightQuick->setSource(testFileUrl("button.qml")); |