Merge tag 'v6.2.13-lts' into tqtc/lts-6.2-opensource

Qt 6.2.13-lts release

Conflicts solved:
	dependencies.yaml

Change-Id: I3cbe1ce4293179888e236dd1a3a299cd2c66c950
This commit is contained in:
Tarja Sundqvist 2025-08-22 09:33:17 +03:00
commit c6fdadd916
68 changed files with 1428 additions and 161 deletions

View File

@ -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 "")

View File

@ -5,4 +5,4 @@ DEFINES += QT_NO_JAVA_STYLE_ITERATORS
QQC2_SOURCE_TREE = $$PWD
MODULE_VERSION = 6.2.12
MODULE_VERSION = 6.2.13

View File

@ -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

View File

@ -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;
};

View File

@ -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;
}

View File

@ -42,5 +42,6 @@ import QML
Member {
property string alias
property bool isFlag: false
property bool isScoped: false
property var values: []
}

View File

@ -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);

View File

@ -117,6 +117,8 @@ public:
int top();
bool isEmpty() const { return m_end == 0; }
QSet<QQuickParticleData*> pop();
void clear();

View File

@ -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;
}
}
}

View File

@ -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 (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;
}
}

View File

@ -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;
}
}
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));
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;
}
return found;
}
return false;
}
// Returns the size of the table. Note that the size may not match the underlying allocation.

View File

@ -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

View File

@ -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,39 +323,47 @@ inline QList<int> SparseArray::keys() const
inline const SparseArrayNode *SparseArray::lowerBound(uint akey) const
{
const SparseArrayNode *lb = root()->lowerBound(akey);
if (!lb)
lb = end();
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();
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();
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();
if (SparseArrayNode *n = root()) {
if (SparseArrayNode *ub = n->upperBound(akey))
return ub;
}
return end();
}
}
QT_END_NAMESPACE

View File

@ -251,7 +251,7 @@ struct QV4QPointer {
private:
QtSharedPointer::ExternalRefCountData *d;
QObject *qObject;
T *qObject;
};
Q_STATIC_ASSERT(std::is_trivial< QV4QPointer<QObject> >::value);
#endif

View File

@ -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;

View File

@ -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, {});

View File

@ -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());
}

View File

@ -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

View File

@ -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));

View File

@ -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);
}
};

View File

@ -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."));
}
}

View File

@ -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 (QQmlListModel *model = that->d()->m_model) {
const int roleIndex
= model->listModel()->setExistingProperty(elementIndex, propName, value, eng);
if (roleIndex != -1)
that->d()->m_model->emitItemsChanged(elementIndex, 1, QVector<int>(1, roleIndex));
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)};

View File

@ -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();

View File

@ -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);

View File

@ -118,9 +118,51 @@
\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
@ -128,6 +170,16 @@
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
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,
then the current directory will be scanned when the harness is run.
@ -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

View File

@ -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);

View File

@ -55,6 +55,7 @@ struct QmlTypesClassDescription
bool isSingleton = false;
bool isRootClass = false;
bool hasCustomParser = false;
bool registerEnumClassesScoped = false;
QStringList implementsInterfaces;
enum CollectMode {

View File

@ -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));

View File

@ -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;

View File

@ -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]

View File

@ -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]

View File

@ -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"

View File

@ -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

View File

@ -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.
*/
/*!

View File

@ -3297,25 +3297,39 @@ 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.
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);
}
}
}
if (syncVertically) {
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.
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);
}
}
}
if (syncView && loadedItems.isEmpty() && !tableSize.isEmpty()) {
// When we have a syncView, we can sometimes temporarily end up with no loaded items.

View File

@ -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;
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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();
}

View File

@ -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)

View File

@ -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"

View File

@ -0,0 +1,5 @@
import QtQml 2.15
SimpleWidget {
width: 20
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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) }
}

View File

@ -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"

View File

@ -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]);
}
}

View File

@ -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"

View File

@ -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:
#####################################################################

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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"
}
}
}

View File

@ -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;

View File

@ -4,3 +4,5 @@ ubuntu-22.04
[nested]
ubuntu-20.04
ubuntu-22.04
[inFlickable]
* # QTBUG-127628

View File

@ -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");

View File

@ -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
}
}

View File

@ -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());

View File

@ -502,6 +502,36 @@ TestCase {
verify(control.background.height !== control.height)
}
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 {

View File

@ -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
}
}

View File

@ -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"

View File

@ -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"));