diff options
author | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2025-10-09 14:56:44 +0300 |
---|---|---|
committer | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2025-10-09 14:56:44 +0300 |
commit | 844f9b9b376838bcb44324984876f8bf99d85d38 (patch) | |
tree | 4c25561714bbef67edc31b50812bfa5bd5ea6a89 | |
parent | ff0a47c8f267e905113b82c53af2742027f0eca6 (diff) | |
parent | 4332989bef24c1039c9097db52196f05979f2b72 (diff) |
Merge tag 'v6.5.7-lts' into tqtc/lts-6.5-opensourcev6.5.7-lts-lgpl
Qt 6.5.7-lts release
Conflicts solved:
dependencies.yaml
Change-Id: I8fbe9c1606825b726adf95e69a4fa86e2196f4e8
359 files changed, 5232 insertions, 1011 deletions
diff --git a/.cmake.conf b/.cmake.conf index 1a96398215..4880c96a5e 100644 --- a/.cmake.conf +++ b/.cmake.conf @@ -1,4 +1,4 @@ -set(QT_REPO_MODULE_VERSION "6.5.6") +set(QT_REPO_MODULE_VERSION "6.5.7") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_LEAN_HEADERS=1") diff --git a/dependencies.yaml b/dependencies.yaml index 0aa43094bd..0db0fca1c2 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -1,16 +1,16 @@ dependencies: ../tqtc-qtbase: - ref: 5d8e9a8415562ba004b38508d91e1fa0254c17d3 + ref: fc0e66eefe3a08428ca4a6e92c66f37ac126d3c4 required: true ../tqtc-qtimageformats: - ref: 4e5091185c5197cab8d259e06e85ac5c94ff40f0 + ref: e068a1dbc18721591d44e7ee5bd2c52dd84a747f required: false ../tqtc-qtlanguageserver: - ref: 6f2192cd0bc5bb4dd79ebfcafe3d3d9543dd966f + ref: 6f8f643a77b247777e0b914637a036303b55a0fa required: false ../tqtc-qtshadertools: - ref: ac330781f44d174045e7a6770ed81c1dd29691f8 + ref: 4c6749e750764297ee4237d9a1f2657b8313c4f7 required: false ../tqtc-qtsvg: - ref: db9aa1da68ad4468e0b42dd913691da2a8070765 + ref: 759343eb902a391aa42049f52691f230c0ed1b79 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/3rdparty/masm/disassembler/mips32/Mips32Opcode.cpp b/src/3rdparty/masm/disassembler/mips32/Mips32Opcode.cpp index 164217eb55..bf6a448863 100644 --- a/src/3rdparty/masm/disassembler/mips32/Mips32Opcode.cpp +++ b/src/3rdparty/masm/disassembler/mips32/Mips32Opcode.cpp @@ -228,7 +228,7 @@ void Mips32Opcode::formatJumpEncodingOpcode(uint32_t iOp, uint32_t index, uint32 void Mips32Opcode::formatREGIMMEncodingOpcode(uint8_t rs, uint8_t rt, int16_t imm, uint32_t* opcodePtr) { const char *opcodes[] = { "bltz", "bgez", "bltzl", "bgezl" }; - if (rt < sizeof(opcodes)) + if (rt < sizeof(opcodes) /sizeof(decltype(opcodes[0]))) FORMAT_INSTR(OPCODE_FMT "%s, 0x%x", opcodes[rt], registerName(rs), reinterpret_cast<unsigned>(opcodePtr+1) + (imm << 2)); else FORMAT_INSTR("unknown REGIMM encoding opcode 0x%x", rt); diff --git a/src/effects/qquickmultieffect.cpp b/src/effects/qquickmultieffect.cpp index e01bac8a99..7d0df83f9d 100644 --- a/src/effects/qquickmultieffect.cpp +++ b/src/effects/qquickmultieffect.cpp @@ -1696,11 +1696,12 @@ void QQuickMultiEffectPrivate::updateBlurItemsAmount(int blurLevel) } // Set the blur items source components - static const auto dummyShaderSource = new QQuickShaderEffectSource(q); + if (!m_dummyShaderSource) + m_dummyShaderSource = new QQuickShaderEffectSource(q); for (int i = 0; i < m_blurEffects.size(); i++) { auto *blurEffect = m_blurEffects[i]; auto sourceItem = (i >= itemsAmount) ? - static_cast<QQuickItem *>(dummyShaderSource) : (i == 0) ? + static_cast<QQuickItem *>(m_dummyShaderSource) : (i == 0) ? static_cast<QQuickItem *>(m_shaderSource->output()) : static_cast<QQuickItem *>(m_blurEffects[i - 1]); auto sourceVariant = QVariant::fromValue<QQuickItem*>(sourceItem); diff --git a/src/effects/qquickmultieffect_p_p.h b/src/effects/qquickmultieffect_p_p.h index 2df33db45b..7d635e2e25 100644 --- a/src/effects/qquickmultieffect_p_p.h +++ b/src/effects/qquickmultieffect_p_p.h @@ -27,6 +27,7 @@ QT_REQUIRE_CONFIG(quick_shadereffect); QT_BEGIN_NAMESPACE class QQuickShaderEffect; +class QQuickShaderEffectSource; class QQuickMultiEffectPrivate : public QQuickItemPrivate { @@ -145,6 +146,7 @@ private: QQuickItem *m_sourceItem = nullptr; QGfxSourceProxy *m_shaderSource = nullptr; QQuickShaderEffect *m_shaderEffect = nullptr; + QQuickShaderEffectSource *m_dummyShaderSource = nullptr; QVector<QQuickShaderEffect *> m_blurEffects; bool m_autoPaddingEnabled = true; QRectF m_paddingRect; diff --git a/src/labs/folderlistmodel/qquickfolderlistmodel.cpp b/src/labs/folderlistmodel/qquickfolderlistmodel.cpp index d7e872f9fd..04d4054cf5 100644 --- a/src/labs/folderlistmodel/qquickfolderlistmodel.cpp +++ b/src/labs/folderlistmodel/qquickfolderlistmodel.cpp @@ -38,11 +38,14 @@ public: bool showHidden = false; bool caseSensitive = true; bool sortCaseSensitive = true; + bool resettingModel = false; ~QQuickFolderListModelPrivate() {} void init(); void updateSorting(); + void finishModelReset(); + // private slots void _q_directoryChanged(const QString &directory, const QList<FileProperty> &list); void _q_directoryUpdated(const QString &directory, const QList<FileProperty> &list, int fromIndex, int toIndex); @@ -104,6 +107,22 @@ void QQuickFolderListModelPrivate::updateSorting() fileInfoThread.setSortFlags(flags); } +void QQuickFolderListModelPrivate::finishModelReset() +{ + Q_Q(QQuickFolderListModel); + const bool wasDataEmpty = data.isEmpty(); + data.clear(); + qCDebug(lcFolderListModel) << "about to emit endResetModel"; + q->endResetModel(); + if (!wasDataEmpty) + emit q->rowCountChanged(); + if (status != QQuickFolderListModel::Null) { + status = QQuickFolderListModel::Null; + emit q->statusChanged(); + } + resettingModel = false; +} + void QQuickFolderListModelPrivate::_q_directoryChanged(const QString &directory, const QList<FileProperty> &list) { qCDebug(lcFolderListModel) << "_q_directoryChanged called with directory" << directory; @@ -115,6 +134,7 @@ void QQuickFolderListModelPrivate::_q_directoryChanged(const QString &directory, qCDebug(lcFolderListModel) << "- endResetModel called"; emit q->rowCountChanged(); emit q->folderChanged(); + resettingModel = false; } @@ -411,6 +431,16 @@ void QQuickFolderListModel::setFolder(const QUrl &folder) if (folder == d->currentDir) return; + // It's possible for the folder to be set twice in quick succession, + // in which case we could still be waiting for FileInfoThread to finish + // getting the list of files from the previously set folder. We should + // at least ensure that the begin/end model reset routine is followed + // in the correct order, and not e.g. call beginResetModel twice. + if (d->resettingModel) + d->finishModelReset(); + + d->resettingModel = true; + QString resolvedPath = QQuickFolderListModelPrivate::resolvePath(folder); qCDebug(lcFolderListModel) << "about to emit beginResetModel since our folder was set to" << folder; @@ -424,13 +454,7 @@ void QQuickFolderListModel::setFolder(const QUrl &folder) QFileInfo info(resolvedPath); if (!info.exists() || !info.isDir()) { - d->data.clear(); - endResetModel(); - emit rowCountChanged(); - if (d->status != QQuickFolderListModel::Null) { - d->status = QQuickFolderListModel::Null; - emit statusChanged(); - } + d->finishModelReset(); return; } diff --git a/src/labs/models/qqmltablemodel.cpp b/src/labs/models/qqmltablemodel.cpp index 6b14eeaaef..b368d7cbf7 100644 --- a/src/labs/models/qqmltablemodel.cpp +++ b/src/labs/models/qqmltablemodel.cpp @@ -415,7 +415,8 @@ void QQmlTableModel::doInsert(int rowIndex, const QVariant &row) // Adding rowAsVariant.toList() will add each invidual variant in the list, // which is definitely not what we want. - const QVariant rowAsVariant = row.value<QJSValue>().toVariant(); + const QVariant rowAsVariant = row.userType() == QMetaType::QVariantMap ? row : row.value<QJSValue>().toVariant(); + mRows.insert(rowIndex, rowAsVariant); ++mRowCount; @@ -955,9 +956,11 @@ bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &ro return true; } + const bool isVariantMap = (row.userType() == QMetaType::QVariantMap); + // Don't require each row to be a QJSValue when setting all rows, // as they won't be; they'll be QVariantMap. - if (operation != SetRowsOperation && !validateRowType(functionName, row)) + if (operation != SetRowsOperation && (!isVariantMap && !validateRowType(functionName, row))) return false; if (operation == OtherOperation) { @@ -974,7 +977,7 @@ bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &ro } } - const QVariant rowAsVariant = operation == SetRowsOperation + const QVariant rowAsVariant = operation == SetRowsOperation || isVariantMap ? row : row.value<QJSValue>().toVariant(); if (rowAsVariant.userType() != QMetaType::QVariantMap) { qmlWarning(this) << functionName << ": row manipulation functions " diff --git a/src/labs/platform/qquicklabsplatformmenu.cpp b/src/labs/platform/qquicklabsplatformmenu.cpp index c8e9b854e2..a74348989c 100644 --- a/src/labs/platform/qquicklabsplatformmenu.cpp +++ b/src/labs/platform/qquicklabsplatformmenu.cpp @@ -695,7 +695,7 @@ void QQuickLabsPlatformMenu::open(QQmlV4Function *args) #endif } m_handle->showPopup(window, - QHighDpi::toNativePixels(targetRect, window), + QHighDpi::toNativeLocalPosition(targetRect, window), menuItem ? menuItem->handle() : nullptr); } diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake index 3c1d7870e7..49829d8e61 100644 --- a/src/qml/Qt6QmlMacros.cmake +++ b/src/qml/Qt6QmlMacros.cmake @@ -828,7 +828,7 @@ function(_qt_internal_target_enable_qmllint target) ) set(cmd - ${tool_wrapper} + "${tool_wrapper}" $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmllint> @${qmllint_rsp_path} ) @@ -860,7 +860,7 @@ function(_qt_internal_target_enable_qmllint target) ) set(cmd - ${tool_wrapper} + "${tool_wrapper}" $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmllint> @${qmllint_rsp_path} ) @@ -898,7 +898,7 @@ function(_qt_internal_target_enable_qmllint target) ) set(cmd - ${tool_wrapper} + "${tool_wrapper}" $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmllint> @${qmllint_rsp_path} ) @@ -1069,7 +1069,7 @@ function(_qt_internal_target_enable_qmlcachegen target output_targets_var qmlcac _qt_internal_get_tool_wrapper_script_path(tool_wrapper) set(cmd - ${tool_wrapper} + "${tool_wrapper}" ${qmlcachegen} --resource-name "${qmlcache_resource_name}" -o "${qmlcache_loader_cpp}" @@ -2486,6 +2486,9 @@ function(qt6_generate_foreign_qml_types source_target destination_qml_target) VERBATIM ) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.27") + set_source_files_properties(${additional_sources} PROPERTIES SKIP_LINTING ON) + endif() target_sources(${destination_qml_target} PRIVATE ${additional_sources}) endfunction() diff --git a/src/qml/common/qv4staticvalue_p.h b/src/qml/common/qv4staticvalue_p.h index e9c3554104..5c50df30c6 100644 --- a/src/qml/common/qv4staticvalue_p.h +++ b/src/qml/common/qv4staticvalue_p.h @@ -366,10 +366,10 @@ struct StaticValue QV4_NEARLY_ALWAYS_INLINE void setDouble(double d) { if (qt_is_nan(d)) { // We cannot store just any NaN. It has to be a NaN with only the quiet bit - // set in the upper bits of the mantissa and the sign bit off. + // set in the upper bits of the mantissa and the sign bit either on or off. // qt_qnan() happens to produce such a thing via std::numeric_limits, // but this is actually not guaranteed. Therefore, we make our own. - _val = (quint64(QuickType::NaN) << Tag_Shift); + _val = (quint64(std::signbit(d) ? QuickType::MinusNaN : QuickType::NaN) << Tag_Shift); Q_ASSERT(isNaN()); } else { memcpy(&_val, &d, 8); diff --git a/src/qml/configure.cmake b/src/qml/configure.cmake index 654fc2ec9d..28f9d955cb 100644 --- a/src/qml/configure.cmake +++ b/src/qml/configure.cmake @@ -14,7 +14,7 @@ qt_find_package(LTTngUST PROVIDED_TARGETS LTTng::UST MODULE_NAME qml QMAKE_LIB l qt_find_package(Python REQUIRED) if(Python_Interpreter_FOUND) # Need to make it globally available to the project - set(QT_INTERNAL_DECLARATIVE_PYTHON "${Python_EXECUTABLE}" CACHE STRING "") + set(QT_INTERNAL_DECLARATIVE_PYTHON "${Python_EXECUTABLE}" CACHE STRING "" FORCE) endif() #### Tests diff --git a/src/qml/doc/snippets/qml/function-call-binding.qml b/src/qml/doc/snippets/qml/function-call-binding.qml new file mode 100644 index 0000000000..1dc7d0224c --- /dev/null +++ b/src/qml/doc/snippets/qml/function-call-binding.qml @@ -0,0 +1,18 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick + +//! [rectangle] +Rectangle { + x: rectPosition() + y: rectPosition() + width: 200 + height: 200 + color: "lightblue" + + function rectPosition() { + return enabled ? 0 : 100 + } +} +//! [rectangle] diff --git a/src/qml/doc/src/external-resources.qdoc b/src/qml/doc/src/external-resources.qdoc index c120257247..02c39ee3fa 100644 --- a/src/qml/doc/src/external-resources.qdoc +++ b/src/qml/doc/src/external-resources.qdoc @@ -29,6 +29,10 @@ \title QmlLive Manual */ /*! + \externalpage https://felgo.com/qml-hot-reload + \title Felgo QML Hot Reload Tool +*/ +/*! \externalpage https://doc.qt.io/qtcreator/creator-debugging-qml.html \title Qt Creator: QML Debugger */ diff --git a/src/qml/doc/src/javascript/qmlglobalobject.qdoc b/src/qml/doc/src/javascript/qmlglobalobject.qdoc index 15b9996ff3..1bd03fad54 100644 --- a/src/qml/doc/src/javascript/qmlglobalobject.qdoc +++ b/src/qml/doc/src/javascript/qmlglobalobject.qdoc @@ -13,8 +13,8 @@ additional imports: \list \li The \l{QmlGlobalQtObject}{Qt object}: A QML object that offers helper methods and properties specific to the QML environment. -\li \l {Qt::}{qsTr()}, \l {Qt::}{qsTranslate()}, \l {Qt::}{qsTrId()}, \l {Qt::}{qsTrNoOp()}, - \l {Qt::}{qsTranslateNoOp()}, \l {Qt::}{qsTrIdNoOp()} functions: +\li \l {Qt::}{qsTr()}, \l {Qt::}{qsTranslate()}, \l {Qt::}{qsTrId()}, \l {Qt::}{QT_TR_NOOP()()}, + \l {Qt::}{QT_TRANSLATE_NOOP()}, \l {Qt::}{QT_TRID_NOOP()} functions: QML functions that let you translate \l{Mark Strings for Translation} {strings} and \l{Mark Translatable Data Text Strings}{string literals} in the QML environment. diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc index 998f3b43d9..fa317bdbd9 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -1393,6 +1393,10 @@ Returns -1 if no matching type was found or one of the given parameters was invalid. + \note: qmlTypeId tries to make modules available, even if they were not accessed by any + engine yet. This can introduce overhead the first time a module is accessed. Trying to + find types from a module which does not exist always introduces this overhead. + \sa QML_ELEMENT, QML_NAMED_ELEMENT, QML_SINGLETON, qmlRegisterType(), qmlRegisterSingletonType() */ diff --git a/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc b/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc index 65e3b95f8e..b45ad83fb8 100644 --- a/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc +++ b/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc @@ -98,6 +98,13 @@ In the previous example, \li \c bottomRect.color depends on \c myTextInput.text.length \endlist +In addition, any properties referenced within a JavaScript function that is +itself used as a binding will be re-evaluated. For example, in the snippet +below, whenever the \c enabled property of the \c Rectangle changes, the +bindings for the \c x and \c y properties will be re-evaluated: + +\snippet qml/function-call-binding.qml rectangle + Syntactically, bindings are allowed to be of arbitrary complexity. However, if a binding is overly complex - such as involving multiple lines, or imperative loops - it could indicate that the binding is being used for more than describing diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 342f642d89..99fad7e3c3 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -2446,18 +2446,22 @@ bool convertToIterable(QMetaType metaType, void *data, Source *sequence) return false; const QMetaType elementMetaType = iterable.valueMetaType(); - QVariant element(elementMetaType); for (qsizetype i = 0, end = sequence->getLength(); i < end; ++i) { - if (!ExecutionEngine::metaTypeFromJS(sequence->get(i), elementMetaType, element.data())) - element = QVariant(elementMetaType); + QVariant element(elementMetaType); + ExecutionEngine::metaTypeFromJS(sequence->get(i), elementMetaType, element.data()); iterable.addValue(element, QSequentialIterable::AtEnd); } return true; } -// Converts a JS value to a meta-type. -// data must point to a place that can store a value of the given type. -// Returns true if conversion succeeded, false otherwise. +/*! + * \internal + * + * Converts a JS value to a meta-type. + * \a data must point to a default-constructed instance of \a metaType. + * Returns \c true if conversion succeeded, \c false otherwise. In the latter case, + * \a data is not modified. + */ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, void *data) { // check if it's one of the types we know diff --git a/src/qml/jsruntime/qv4generatorobject.cpp b/src/qml/jsruntime/qv4generatorobject.cpp index 9f77d83ac3..d5519b49e5 100644 --- a/src/qml/jsruntime/qv4generatorobject.cpp +++ b/src/qml/jsruntime/qv4generatorobject.cpp @@ -52,6 +52,7 @@ Heap::FunctionObject *GeneratorFunction::create(ExecutionContext *context, Funct proto->setPrototypeOf(scope.engine->generatorPrototype()); g->defineDefaultProperty(scope.engine->id_prototype(), proto, Attr_NotConfigurable|Attr_NotEnumerable); g->setPrototypeOf(ScopedObject(scope, scope.engine->generatorFunctionCtor()->get(scope.engine->id_prototype()))); + g->d()->canBeTailCalled = false; return g->d(); } @@ -213,6 +214,7 @@ Heap::FunctionObject *MemberGeneratorFunction::create(ExecutionContext *context, proto->setPrototypeOf(scope.engine->generatorPrototype()); g->defineDefaultProperty(scope.engine->id_prototype(), proto, Attr_NotConfigurable|Attr_NotEnumerable); g->setPrototypeOf(ScopedObject(scope, scope.engine->generatorFunctionCtor()->get(scope.engine->id_prototype()))); + g->d()->canBeTailCalled = false; return g->d(); } diff --git a/src/qml/jsruntime/qv4object.cpp b/src/qml/jsruntime/qv4object.cpp index 712666c6aa..0851b2f5ab 100644 --- a/src/qml/jsruntime/qv4object.cpp +++ b/src/qml/jsruntime/qv4object.cpp @@ -744,6 +744,12 @@ ReturnedValue Object::virtualResolveLookupGetter(const Object *object, Execution Heap::Object *obj = object->d(); PropertyKey name = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]); + if (object->as<QV4::ProxyObject>()) { + // proxies invalidate assumptions that we normally maek in lookups + // so we always need to use the fallback path + lookup->getter = Lookup::getterFallback; + return lookup->getter(lookup, engine, *object); + } if (name.isArrayIndex()) { lookup->indexedLookup.index = name.asArrayIndex(); lookup->getter = Lookup::getterIndexed; diff --git a/src/qml/jsruntime/qv4typedarray.cpp b/src/qml/jsruntime/qv4typedarray.cpp index 43dc0fae4f..e87bf957d2 100644 --- a/src/qml/jsruntime/qv4typedarray.cpp +++ b/src/qml/jsruntime/qv4typedarray.cpp @@ -762,8 +762,6 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_fill(const FunctionObject *b, fin = static_cast<uint>(std::min(relativeEnd, dlen)); } - double val = argc ? argv[0].toNumber() : std::numeric_limits<double>::quiet_NaN(); - Value value = Value::fromDouble(val); if (scope.hasException() || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); @@ -771,6 +769,14 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_fill(const FunctionObject *b, uint bytesPerElement = v->bytesPerElement(); uint byteOffset = v->byteOffset(); + Value value; + if (!argc) + value.setDouble(std::numeric_limits<double>::quiet_NaN()); + else if (argv[0].isNumber()) + value = argv[0]; + else + value.setDouble(argv[0].toNumber()); + while (k < fin) { v->d()->type->write(data + byteOffset + k * bytesPerElement, value); k++; diff --git a/src/qml/memory/qv4heap_p.h b/src/qml/memory/qv4heap_p.h index b4bd3c4cd6..2d0ad3d601 100644 --- a/src/qml/memory/qv4heap_p.h +++ b/src/qml/memory/qv4heap_p.h @@ -221,7 +221,7 @@ struct QV4QPointer { private: QtSharedPointer::ExternalRefCountData *d; - QObject *qObject; + T *qObject; }; Q_STATIC_ASSERT(std::is_trivial_v<QV4QPointer<QObject>>); #endif diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index e914da329e..94a91629f3 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -335,7 +335,24 @@ void qmlUnregisterModuleImport(const char *uri, int moduleMajor, //From qqml.h int qmlTypeId(const char *uri, int versionMajor, int versionMinor, const char *qmlName) { - return QQmlMetaType::typeId(uri, QTypeRevision::fromVersion(versionMajor, versionMinor), qmlName); + auto revision = QTypeRevision::fromVersion(versionMajor, versionMinor); + int id = QQmlMetaType::typeId(uri, revision, qmlName); + if (id != -1) + return id; + /* If the module hasn't been imported yet, we might not have the id of a + singleton at this point. To obtain it, we need an engine in order to + to do the resolution steps. + This is expensive, but we assume that users don't constantly query invalid + Types; internal code should use QQmlMetaType API. + */ + QQmlEngine engine; + auto *enginePriv = QQmlEnginePrivate::get(&engine); + auto loadHelper = QQml::makeRefPointer<LoadHelper>(&enginePriv->typeLoader, uri); + auto type = loadHelper->resolveType(qmlName).type; + if (type.availableInVersion(revision)) + return type.index(); + else + return -1; } static bool checkSingletonInstance(QQmlEngine *engine, QObject *instance) diff --git a/src/qml/qml/qqmlabstractbinding_p.h b/src/qml/qml/qqmlabstractbinding_p.h index d4c00ea886..27faad645c 100644 --- a/src/qml/qml/qqmlabstractbinding_p.h +++ b/src/qml/qml/qqmlabstractbinding_p.h @@ -63,7 +63,7 @@ public: void addToObject(); void removeFromObject(); - static void printBindingLoopError(const QQmlProperty &prop); + virtual void printBindingLoopError(const QQmlProperty &prop); inline QQmlAbstractBinding *nextBinding() const; diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index 8bf1a9a7d0..551f55b4be 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -5,6 +5,7 @@ #include "qqmlcontext.h" #include "qqmldata_p.h" +#include "qqmlinfo.h" #include <private/qqmldebugserviceinterfaces_p.h> #include <private/qqmldebugconnector_p.h> @@ -145,7 +146,7 @@ void QQmlBinding::update(QQmlPropertyData::WriteFlags flags) getPropertyData(&d, &vtd); Q_ASSERT(d); QQmlProperty p = QQmlPropertyPrivate::restore(targetObject(), *d, &vtd, nullptr); - QQmlAbstractBinding::printBindingLoopError(p); + printBindingLoopError(p); return; } setUpdatingFlag(true); @@ -167,6 +168,12 @@ void QQmlBinding::update(QQmlPropertyData::WriteFlags flags) setUpdatingFlag(false); } +void QQmlBinding::printBindingLoopError(const QQmlProperty &prop) +{ + qmlWarning(prop.object()) << QString(QLatin1String("Binding loop detected for property \"%1\":\n%2")) + .arg(prop.name(), expressionIdentifier()); +} + QV4::ReturnedValue QQmlBinding::evaluate(bool *isUndefined) { QV4::ExecutionEngine *v4 = engine()->handle(); diff --git a/src/qml/qml/qqmlbinding_p.h b/src/qml/qml/qqmlbinding_p.h index 00aa0d6f58..b2f10b826a 100644 --- a/src/qml/qml/qqmlbinding_p.h +++ b/src/qml/qml/qqmlbinding_p.h @@ -73,6 +73,8 @@ public: QString expression() const override; void update(QQmlPropertyData::WriteFlags flags = QQmlPropertyData::DontRemoveBinding); + void printBindingLoopError(const QQmlProperty &prop) override; + typedef int Identifier; enum { Invalid = -1 diff --git a/src/qml/qml/qqmlbuiltinfunctions.cpp b/src/qml/qml/qqmlbuiltinfunctions.cpp index 99e6f889b7..fdc28d727d 100644 --- a/src/qml/qml/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/qqmlbuiltinfunctions.cpp @@ -31,6 +31,7 @@ #include <private/qv4jsonobject_p.h> #include <private/qv4objectproto_p.h> #include <private/qv4qobjectwrapper_p.h> +#include <private/qv4sequenceobject_p.h> #include <private/qv4stackframe_p.h> #include <QtCore/qstring.h> @@ -230,7 +231,9 @@ The following functions are also on the Qt object. The \c styleHints object provides platform-specific style hints and settings. See the \l QStyleHints documentation for further details. - You should access StyleHints via \l Application::styleHints instead. + You should access StyleHints via \l Application::styleHints instead, as + this provides better type information for tooling such as the + \l {Qt Quick Compiler}. \note The \c styleHints object is only available when using the Qt Quick module. */ @@ -1782,14 +1785,20 @@ static QString serializeArray(Object *array, ExecutionEngine *v4, QSet<QV4::Heap Scope scope(v4); ScopedValue val(scope); QString result; - alreadySeen.insert(array->d()); + + ScopedObject detached(scope); + if (Sequence *reference = array->as<Sequence>()) + detached = ReferenceObject::detached(reference->d()); + else + detached = array; + result += QLatin1Char('['); - const uint length = array->getLength(); + const uint length = detached->getLength(); for (uint i = 0; i < length; ++i) { if (i != 0) result += QLatin1Char(','); - val = array->get(i); + val = detached->get(i); if (val->isManaged() && val->managed()->isArrayLike()) if (!alreadySeen.contains(val->objectValue()->d())) result += serializeArray(val->objectValue(), v4, alreadySeen); @@ -1799,6 +1808,7 @@ static QString serializeArray(Object *array, ExecutionEngine *v4, QSet<QV4::Heap result += val->toQStringNoThrow(); } result += QLatin1Char(']'); + alreadySeen.remove(array->d()); return result; }; @@ -2139,7 +2149,7 @@ ReturnedValue GlobalExtensions::method_qsTranslate(const FunctionObject *b, cons } /*! - \qmlmethod string Qt::qsTranslateNoOp(string context, string sourceText, string disambiguation) + \qmlmethod string Qt::QT_TRANSLATE_NOOP(string context, string sourceText, string disambiguation) Marks \a sourceText for dynamic translation in the given \a context; i.e, the stored \a sourceText will not be altered. @@ -2250,7 +2260,7 @@ ReturnedValue GlobalExtensions::method_qsTr(const FunctionObject *b, const Value } /*! - \qmlmethod string Qt::qsTrNoOp(string sourceText, string disambiguation) + \qmlmethod string Qt::QT_TR_NOOP(string sourceText, string disambiguation) Marks \a sourceText for dynamic translation; i.e, the stored \a sourceText will not be altered. @@ -2331,7 +2341,7 @@ ReturnedValue GlobalExtensions::method_qsTrId(const FunctionObject *b, const Val } /*! - \qmlmethod string Qt::qsTrIdNoOp(string id) + \qmlmethod string Qt::QT_TRID_NOOP(string id) Marks \a id for dynamic translation. diff --git a/src/qml/qml/qqmlcomponent.cpp b/src/qml/qml/qqmlcomponent.cpp index c3f692eeea..5a6fd4c8d3 100644 --- a/src/qml/qml/qqmlcomponent.cpp +++ b/src/qml/qml/qqmlcomponent.cpp @@ -1455,22 +1455,24 @@ void QQmlComponent::create(QQmlIncubator &incubator, QQmlContext *context, QQmlC } /*! - Set top-level \a properties of the \a component. + Set top-level \a properties of the \a object that was created from a + QQmlComponent. This method provides advanced control over component instance creation. In general, programmers should use - \l QQmlComponent::createWithInitialProperties to create a component. + \l QQmlComponent::createWithInitialProperties to create an object instance + from a component. Use this method after beginCreate and before completeCreate has been called. If a provided property does not exist, a warning is issued. \since 5.14 */ -void QQmlComponent::setInitialProperties(QObject *component, const QVariantMap &properties) +void QQmlComponent::setInitialProperties(QObject *object, const QVariantMap &properties) { Q_D(QQmlComponent); for (auto it = properties.constBegin(); it != properties.constEnd(); ++it) - d->setInitialProperty(component, it.key(), it.value()); + d->setInitialProperty(object, it.key(), it.value()); } /* diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 015a00d53b..84347dca11 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -21,6 +21,7 @@ #include <QtCore/qstandardpaths.h> #include <QtCore/qmetaobject.h> #include <QDebug> +#include <private/qqmlcomponent_p.h> #include <QtCore/qcoreapplication.h> #include <QtCore/qcryptographichash.h> #include <QtCore/qdir.h> @@ -1811,6 +1812,24 @@ QJSValue QQmlEnginePrivate::singletonInstance<QJSValue>(const QQmlType &type) return QJSValue(QJSValue::UndefinedValue); } QObject *o = component.beginCreate(q->rootContext()); + auto *compPriv = QQmlComponentPrivate::get(&component); + if (compPriv->state.hasUnsetRequiredProperties()) { + /* We would only get the errors from the component after (complete)Create. + We can't call create, as we need to convertAndInsert before completeCreate (otherwise + tst_qqmllanguage::compositeSingletonCircular fails). + On the other hand, we don't want to call cnovertAndInsert if we have an error + So create the unset required component errors manually. + */ + delete o; + const auto requiredProperties = compPriv->state.requiredProperties(); + QList<QQmlError> errors (requiredProperties->size()); + for (const auto &reqProp: *requiredProperties) + errors.push_back(QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(reqProp)); + warning(errors); + v4engine()->throwError(QLatin1String("Due to the preceding error(s), Singleton \"%1\" could not be loaded.").arg(QString::fromUtf8(type.typeName()))); + return QJSValue(QJSValue::UndefinedValue); + } + value = q->newQObject(o); singletonInstances.convertAndInsert(v4engine(), type, &value); component.completeCreate(); diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index 32377f0502..f8f8b72191 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -1423,7 +1423,7 @@ QTypeRevision QQmlImports::updateQmldirContent( Q_ASSERT(database); Q_ASSERT(errors); - qDebug(lcQmlImport) + qCDebug(lcQmlImport) << "updateQmldirContent:" << qPrintable(baseUrl().toString()) << uri << "to" << qmldirUrl << "as" << prefix; diff --git a/src/qml/qml/qqmllistwrapper.cpp b/src/qml/qml/qqmllistwrapper.cpp index 42f79dcecd..e3c0ba19ae 100644 --- a/src/qml/qml/qqmllistwrapper.cpp +++ b/src/qml/qml/qqmllistwrapper.cpp @@ -17,6 +17,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcIncompatibleElement, "qt.qml.list.incompatible") + using namespace QV4; using namespace Qt::StringLiterals; @@ -127,7 +129,22 @@ bool QmlListWrapper::virtualPut(Managed *m, PropertyKey id, const Value &value, QV4::Scope scope(v4); QV4::ScopedObject so(scope, value.toObject(scope.engine)); if (auto *wrapper = so->as<QV4::QObjectWrapper>()) { - prop->replace(prop, index, wrapper->object()); + QObject *object = wrapper->object(); + if (!object) { + prop->replace(prop, index, object); + return true; + } + + const QMetaType elementType = w->d()->elementType(); + const QMetaObject *elementMeta = elementType.metaObject(); + if (Q_UNLIKELY(!elementMeta || !QQmlMetaObject::canConvert(object, elementMeta))) { + qCWarning(lcIncompatibleElement) + << "Cannot insert" << object << "into a QML list of" << elementType.name(); + prop->replace(prop, index, nullptr); + return true; + } + + prop->replace(prop, index, object); return true; } @@ -242,11 +259,28 @@ ReturnedValue PropertyListPrototype::method_push(const FunctionObject *b, const if (!qIsAtMostUintLimit(length, std::numeric_limits<uint>::max() - argc)) return scope.engine->throwRangeError(QString::fromLatin1("List length out of range.")); + const QMetaType elementType = w->d()->elementType(); + const QMetaObject *elementMeta = elementType.metaObject(); for (int i = 0; i < argc; ++i) { - if (argv[i].isNull()) + if (argv[i].isNull()) { property->append(property, nullptr); - else - property->append(property, argv[i].as<QV4::QObjectWrapper>()->object()); + continue; + } + + QObject *object = argv[i].as<QV4::QObjectWrapper>()->object(); + if (!object) { + property->append(property, nullptr); + continue; + } + + if (Q_UNLIKELY(!elementMeta || !QQmlMetaObject::canConvert(object, elementMeta))) { + qCWarning(lcIncompatibleElement) + << "Cannot append" << object << "to a QML list of" << elementType.name(); + property->append(property, nullptr); + continue; + } + + property->append(property, object); } const auto actualLength = property->count(property); @@ -370,12 +404,29 @@ ReturnedValue PropertyListPrototype::method_splice(const FunctionObject *b, cons } } + const QMetaType elementType = w->d()->elementType(); + const QMetaObject *elementMeta = elementType.metaObject(); for (qsizetype i = 0; i < itemCount; ++i) { const auto arg = argv[i + 2]; - if (arg.isNull()) + if (arg.isNull()) { property->replace(property, start + i, nullptr); - else - property->replace(property, start + i, arg.as<QObjectWrapper>()->object()); + continue; + } + + QObject *object = arg.as<QObjectWrapper>()->object(); + if (!object) { + property->replace(property, start + i, nullptr); + continue; + } + + if (Q_UNLIKELY(!elementMeta || !QQmlMetaObject::canConvert(object, elementMeta))) { + qCWarning(lcIncompatibleElement) + << "Cannot splice" << object << "into a QML list of" << elementType.name(); + property->replace(property, start + i, nullptr); + continue; + } + + property->replace(property, start + i, object); } return newArray->asReturnedValue(); @@ -420,9 +471,24 @@ ReturnedValue PropertyListPrototype::method_unshift(const FunctionObject *b, con for (qsizetype k = len; k > 0; --k) property->replace(property, k + argc - 1, property->at(property, k - 1)); + const QMetaType elementType = w->d()->elementType(); + const QMetaObject *elementMeta = elementType.metaObject(); for (int i = 0; i < argc; ++i) { const auto *wrapper = argv[i].as<QObjectWrapper>(); - property->replace(property, i, wrapper ? wrapper->object() : nullptr); + QObject *object = wrapper ? wrapper->object() : nullptr; + if (!object) { + property->replace(property, i, object); + continue; + } + + if (Q_UNLIKELY(!elementMeta || !QQmlMetaObject::canConvert(object, elementMeta))) { + qCWarning(lcIncompatibleElement) + << "Cannot unshift" << object << "into a QML list of" << elementType.name(); + property->replace(property, i, nullptr); + continue; + } + + property->replace(property, i, object); } return Encode(uint(len + argc)); diff --git a/src/qml/qml/qqmllistwrapper_p.h b/src/qml/qml/qqmllistwrapper_p.h index 21240d6b98..78872a61bb 100644 --- a/src/qml/qml/qqmllistwrapper_p.h +++ b/src/qml/qml/qqmllistwrapper_p.h @@ -16,6 +16,7 @@ // #include <QtCore/qglobal.h> +#include <QtCore/qloggingcategory.h> #include <QtCore/qpointer.h> #include <QtQml/qqmllist.h> @@ -25,6 +26,8 @@ QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcIncompatibleElement) + namespace QV4 { namespace Heap { @@ -34,6 +37,7 @@ struct QmlListWrapper : Object { void destroy(); QV4QPointer<QObject> object; + QMetaType elementType() const { return QQmlMetaType::listValueType(QMetaType(propertyType)); } QQmlListProperty<QObject> &property() { return *reinterpret_cast<QQmlListProperty<QObject>*>(propertyData); } diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index 7ebc602fff..1430c20099 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -19,6 +19,7 @@ #include <private/qv4qobjectwrapper_p.h> #include <private/qqmlbuiltinfunctions_p.h> #include <private/qqmlirbuilder_p.h> +#include <private/qqmllistwrapper_p.h> #include <QtQml/private/qqmllist_p.h> #include <QStringList> @@ -867,17 +868,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->kind() == QQmlAbstractBinding::ValueTypeProxy) + if (valueTypeIndex != -1 + && oldBinding + && oldBinding->kind() == QQmlAbstractBinding::ValueTypeProxy) { 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, {}); @@ -1402,18 +1407,6 @@ static ConvertAndAssignResult tryConvertAndAssign( return {false, false}; } - if (variantMetaType == QMetaType::fromType<QJSValue>()) { - // Handle Qt.binding bindings here to avoid mistaken conversion below - const QJSValue &jsValue = *static_cast<const QJSValue *>(value.constData()); - const QV4::FunctionObject *f - = QJSValuePrivate::asManagedType<QV4::FunctionObject>(&jsValue); - if (f && f->isBinding()) { - QV4::QObjectWrapper::setProperty( - f->engine(), object, &property, f->asReturnedValue()); - return {true, true}; - } - } - // common cases: switch (propertyMetaType.id()) { case QMetaType::Bool: @@ -1498,6 +1491,21 @@ bool iterateQObjectContainer(QMetaType metaType, const void *data, Op op) return true; } +static bool tryAssignBinding( + QObject *object, const QQmlPropertyData &property, const QVariant &value, + QMetaType variantMetaType) { + if (variantMetaType != QMetaType::fromType<QJSValue>()) + return false; + + const QJSValue &jsValue = *static_cast<const QJSValue *>(value.constData()); + const QV4::FunctionObject *f = QJSValuePrivate::asManagedType<QV4::FunctionObject>(&jsValue); + if (!f || !f->isBinding()) + return false; + + QV4::QObjectWrapper::setProperty(f->engine(), object, &property, f->asReturnedValue()); + return true; +} + bool QQmlPropertyPrivate::write( QObject *object, const QQmlPropertyData &property, const QVariant &value, const QQmlRefPointer<QQmlContextData> &context, QQmlPropertyData::WriteFlags flags) @@ -1523,6 +1531,10 @@ bool QQmlPropertyPrivate::write( QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(context); const bool isUrl = propertyMetaType == QMetaType::fromType<QUrl>(); // handled separately + // Handle Qt.binding bindings here to avoid mistaken conversion below + if (tryAssignBinding(object, property, value, variantMetaType)) + return true; + // The cases below are in approximate order of likelyhood: if (propertyMetaType == variantMetaType && !isUrl && propertyMetaType != QMetaType::fromType<QList<QUrl>>() && !property.isQList()) { @@ -1598,8 +1610,11 @@ bool QQmlPropertyPrivate::write( prop.clear(&prop); const auto doAppend = [&](QObject *o) { - if (o && !QQmlMetaObject::canConvert(o, valueMetaObject)) + if (Q_UNLIKELY(o && !QQmlMetaObject::canConvert(o, valueMetaObject))) { + qCWarning(lcIncompatibleElement) + << "Cannot append" << o << "to a QML list of" << listValueType.name(); o = nullptr; + } prop.append(&prop, o); }; diff --git a/src/qml/qml/qqmlpropertytopropertybinding.cpp b/src/qml/qml/qqmlpropertytopropertybinding.cpp index ec77e8c97a..f2edf3b87f 100644 --- a/src/qml/qml/qqmlpropertytopropertybinding.cpp +++ b/src/qml/qml/qqmlpropertytopropertybinding.cpp @@ -91,8 +91,7 @@ void QQmlPropertyToPropertyBinding::update(QQmlPropertyData::WriteFlags flags) // Check for a binding update loop if (Q_UNLIKELY(updatingFlag())) { - QQmlAbstractBinding::printBindingLoopError( - QQmlPropertyPrivate::restore(target, *d, &vtd, nullptr)); + printBindingLoopError(QQmlPropertyPrivate::restore(target, *d, &vtd, nullptr)); return; } diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index d07b70bb16..7db2821a5d 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -2207,10 +2207,12 @@ void QQmlJSImportVisitor::importFromHost(const QString &path, const QString &pre void QQmlJSImportVisitor::importFromQrc(const QString &path, const QString &prefix, const QQmlJS::SourceLocation &location) { - if (const auto &mapper = m_importer->resourceFileMapper()) { - if (mapper->isFile(path)) { + Q_ASSERT(path.startsWith(u':')); + if (const QQmlJSResourceFileMapper *mapper = m_importer->resourceFileMapper()) { + const auto pathNoColon = path.mid(1); + if (mapper->isFile(pathNoColon)) { const auto entry = m_importer->resourceFileMapper()->entry( - QQmlJSResourceFileMapper::resourceFileFilter(path)); + QQmlJSResourceFileMapper::resourceFileFilter(pathNoColon)); const auto scope = m_importer->importFile(entry.filePath); const QString actualPrefix = prefix.isEmpty() ? QFileInfo(entry.resourcePath).baseName() : prefix; diff --git a/src/qmlcompiler/qqmljslinter.cpp b/src/qmlcompiler/qqmljslinter.cpp index 51a1489360..9d80406a03 100644 --- a/src/qmlcompiler/qqmljslinter.cpp +++ b/src/qmlcompiler/qqmljslinter.cpp @@ -552,10 +552,19 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename, QQmlJSLiteralBindingCheck literalCheck; literalCheck.run(&v, &typeResolver); + const QStringList resourcePaths = mapper + ? mapper->resourcePaths(QQmlJSResourceFileMapper::localFileFilter(filename)) + : QStringList(); + const QString resolvedPath = + (resourcePaths.size() == 1) ? u':' + resourcePaths.first() : filename; + + QQmlJSLinterCodegen codegen{ &m_importer, resolvedPath, qmldirFiles, m_logger.get() }; + codegen.setTypeResolver(std::move(typeResolver)); + QScopedPointer<QQmlSA::PassManager> passMan; if (m_enablePlugins) { - passMan.reset(new QQmlSA::PassManager(&v, &typeResolver)); + passMan.reset(new QQmlSA::PassManager(&v, codegen.typeResolver())); for (const Plugin &plugin : m_plugins) { if (!plugin.isValid() || !plugin.isEnabled()) @@ -577,14 +586,6 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename, return; } - const QStringList resourcePaths = mapper - ? mapper->resourcePaths(QQmlJSResourceFileMapper::localFileFilter(filename)) - : QStringList(); - const QString resolvedPath = - (resourcePaths.size() == 1) ? u':' + resourcePaths.first() : filename; - - QQmlJSLinterCodegen codegen { &m_importer, resolvedPath, qmldirFiles, m_logger.get() }; - codegen.setTypeResolver(std::move(typeResolver)); if (passMan) codegen.setPassManager(passMan.get()); QQmlJSSaveFunction saveFunction = [](const QV4::CompiledData::SaveableUnitPointer &, diff --git a/src/qmlcompiler/qqmljsutils_p.h b/src/qmlcompiler/qqmljsutils_p.h index 956d946980..311671cd42 100644 --- a/src/qmlcompiler/qqmljsutils_p.h +++ b/src/qmlcompiler/qqmljsutils_p.h @@ -21,10 +21,12 @@ #include "qqmljsscope_p.h" #include "qqmljsmetatypes_p.h" +#include <QtCore/qdir.h> #include <QtCore/qstack.h> #include <QtCore/qstring.h> -#include <QtCore/qstringview.h> #include <QtCore/qstringbuilder.h> +#include <QtCore/qstringview.h> + #include <private/qduplicatetracker_p.h> #include <optional> @@ -365,6 +367,13 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils static std::variant<QString, QQmlJS::DiagnosticMessage> sourceDirectoryPath(const QQmlJSImporter *importer, const QString &buildDirectoryPath); + + static QStringList cleanPaths(QStringList &&paths) + { + for (QString &path : paths) + path = QDir::cleanPath(path); + return paths; + } }; bool Q_QMLCOMPILER_PRIVATE_EXPORT canStrictlyCompareWithVar( diff --git a/src/qmldom/qqmldomastcreator.cpp b/src/qmldom/qqmldomastcreator.cpp index 07f242b5cc..8ed52534bf 100644 --- a/src/qmldom/qqmldomastcreator.cpp +++ b/src/qmldom/qqmldomastcreator.cpp @@ -895,7 +895,9 @@ public: bool visit(AST::UiEnumMemberList *el) override { - EnumItem it(el->member.toString(), el->value); + EnumItem it(el->member.toString(), el->value, + el->valueToken.isValid() ? EnumItem::ValueKind::ExplicitValue + : EnumItem::ValueKind::ImplicitValue); EnumDecl &eDecl = std::get<EnumDecl>(currentNode().value); Path itPathFromDecl = eDecl.addValue(it); FileLocations::addRegion(createMap(DomType::EnumItem, itPathFromDecl, nullptr), QString(), diff --git a/src/qmldom/qqmldomelements.cpp b/src/qmldom/qqmldomelements.cpp index a196aeabe9..83d41d04bc 100644 --- a/src/qmldom/qqmldomelements.cpp +++ b/src/qmldom/qqmldomelements.cpp @@ -1954,19 +1954,8 @@ void EnumItem::writeOut(DomItem &self, OutWriter &ow) const { ow.ensureNewline(); ow.writeRegion(u"name", name()); - bool hasDefaultValue = false; index_type myIndex = self.pathFromOwner().last().headIndex(); - if (myIndex == 0) - hasDefaultValue = value() == 0; - else if (myIndex > 0) - hasDefaultValue = value() - == self.container() - .index(myIndex - 1) - .field(Fields::value) - .value() - .toDouble(value()) - + 1; - if (!hasDefaultValue) { + if (m_valueKind == ValueKind::ExplicitValue) { QString v = QString::number(value(), 'f', 0); if (abs(value() - v.toDouble()) > 1.e-10) v = QString::number(value()); diff --git a/src/qmldom/qqmldomelements_p.h b/src/qmldom/qqmldomelements_p.h index ec21477ab7..75ced47f5b 100644 --- a/src/qmldom/qqmldomelements_p.h +++ b/src/qmldom/qqmldomelements_p.h @@ -712,8 +712,14 @@ class QMLDOM_EXPORT EnumItem { public: constexpr static DomType kindValue = DomType::EnumItem; - - EnumItem(QString name = QString(), int value = 0) : m_name(name), m_value(value) { } + enum class ValueKind : quint8 { + ImplicitValue, + ExplicitValue + }; + EnumItem(const QString &name = QString(), int value = 0, ValueKind valueKind = ValueKind::ImplicitValue) + : m_name(name), m_value(value), m_valueKind(valueKind) + { + } bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor); @@ -726,6 +732,7 @@ public: private: QString m_name; double m_value; + ValueKind m_valueKind; RegionComments m_comments; }; diff --git a/src/qmlintegration/qqmlintegration.h b/src/qmlintegration/qqmlintegration.h index 7c31b159a5..f513d7013c 100644 --- a/src/qmlintegration/qqmlintegration.h +++ b/src/qmlintegration/qqmlintegration.h @@ -41,12 +41,16 @@ QT_END_NAMESPACE #define QML_ELEMENT \ Q_CLASSINFO("QML.Element", "auto") + #define QML_ANONYMOUS \ Q_CLASSINFO("QML.Element", "anonymous") \ enum class QmlIsAnonymous{yes = true}; \ template<typename, typename> friend struct QML_PRIVATE_NAMESPACE::QmlAnonymous; \ + QT_WARNING_PUSH \ + QT_WARNING_DISABLE_GCC("-Wredundant-decls") \ template<typename... Args> \ friend void QML_REGISTER_TYPES_AND_REVISIONS(const char *uri, int versionMajor, QList<int> *); \ + QT_WARNING_POP \ inline constexpr void qt_qmlMarker_anonymous() {} #define QML_NAMED_ELEMENT(NAME) \ @@ -57,8 +61,11 @@ QT_END_NAMESPACE Q_CLASSINFO("QML.UncreatableReason", REASON) \ enum class QmlIsUncreatable {yes = true}; \ template<typename, typename> friend struct QML_PRIVATE_NAMESPACE::QmlUncreatable; \ + QT_WARNING_PUSH \ + QT_WARNING_DISABLE_GCC("-Wredundant-decls") \ template<typename... Args> \ friend void QML_REGISTER_TYPES_AND_REVISIONS(const char *uri, int versionMajor, QList<int> *); \ + QT_WARNING_POP \ inline constexpr void qt_qmlMarker_uncreatable() {} #define QML_VALUE_TYPE(NAME) \ @@ -76,8 +83,11 @@ QT_END_NAMESPACE Q_CLASSINFO("QML.Singleton", "true") \ enum class QmlIsSingleton {yes = true}; \ template<typename, typename> friend struct QML_PRIVATE_NAMESPACE::QmlSingleton; \ + QT_WARNING_PUSH \ + QT_WARNING_DISABLE_GCC("-Wredundant-decls") \ template<typename... Args> \ friend void QML_REGISTER_TYPES_AND_REVISIONS(const char *uri, int versionMajor, QList<int> *); \ + QT_WARNING_POP \ inline constexpr void qt_qmlMarker_singleton() {} #define QML_ADDED_IN_MINOR_VERSION(VERSION) \ @@ -106,8 +116,11 @@ QT_END_NAMESPACE Q_CLASSINFO("QML.Extended", #EXTENDED_TYPE) \ using QmlExtendedType = EXTENDED_TYPE; \ template<class, class> friend struct QML_PRIVATE_NAMESPACE::QmlExtended; \ + QT_WARNING_PUSH \ + QT_WARNING_DISABLE_GCC("-Wredundant-decls") \ template<typename... Args> \ friend void QML_REGISTER_TYPES_AND_REVISIONS(const char *uri, int versionMajor, QList<int> *); \ + QT_WARNING_POP \ inline constexpr void qt_qmlMarker_extended() {} #define QML_EXTENDED_NAMESPACE(EXTENDED_NAMESPACE) \ @@ -115,8 +128,11 @@ QT_END_NAMESPACE Q_CLASSINFO("QML.ExtensionIsNamespace", "true") \ static constexpr const QMetaObject *qmlExtendedNamespace() { return &EXTENDED_NAMESPACE::staticMetaObject; } \ template<class, class> friend struct QML_PRIVATE_NAMESPACE::QmlExtendedNamespace; \ + QT_WARNING_PUSH \ + QT_WARNING_DISABLE_GCC("-Wredundant-decls") \ template<typename... Args> \ friend void QML_REGISTER_TYPES_AND_REVISIONS(const char *uri, int versionMajor, QList<int> *); \ + QT_WARNING_POP \ inline constexpr void qt_qmlMarker_extendedNamespace() {} #define QML_NAMESPACE_EXTENDED(EXTENDED_NAMESPACE) \ @@ -126,8 +142,11 @@ QT_END_NAMESPACE Q_CLASSINFO("QML.Element", "anonymous") \ enum class QmlIsInterface {yes = true}; \ template<typename, typename> friend struct QML_PRIVATE_NAMESPACE::QmlInterface; \ + QT_WARNING_PUSH \ + QT_WARNING_DISABLE_GCC("-Wredundant-decls") \ template<typename... Args> \ friend void QML_REGISTER_TYPES_AND_REVISIONS(const char *uri, int versionMajor, QList<int> *); \ + QT_WARNING_POP \ inline constexpr void qt_qmlMarker_interface() {} #define QML_IMPLEMENTS_INTERFACES(INTERFACES) \ @@ -140,8 +159,11 @@ QT_END_NAMESPACE using QmlSequenceValueType = VALUE_TYPE; \ enum class QmlIsSequence {yes = true}; \ template<typename, typename> friend struct QML_PRIVATE_NAMESPACE::QmlSequence; \ + QT_WARNING_PUSH \ + QT_WARNING_DISABLE_GCC("-Wredundant-decls") \ template<typename... Args> \ friend void QML_REGISTER_TYPES_AND_REVISIONS(const char *uri, int versionMajor, QList<int> *); \ + QT_WARNING_POP \ inline constexpr void qt_qmlMarker_sequence() {} #define QML_UNAVAILABLE \ diff --git a/src/qmlmodels/qqmldelegatemodel.cpp b/src/qmlmodels/qqmldelegatemodel.cpp index ec75a5775c..13965d316b 100644 --- a/src/qmlmodels/qqmldelegatemodel.cpp +++ b/src/qmlmodels/qqmldelegatemodel.cpp @@ -167,6 +167,7 @@ QQmlDelegateModelPrivate::QQmlDelegateModelPrivate(QQmlContext *ctxt) , m_transaction(false) , m_incubatorCleanupScheduled(false) , m_waitingToFetchMore(false) + , m_maybeResetRoleNames(false) , m_cacheItems(nullptr) , m_items(nullptr) , m_persistedItems(nullptr) @@ -371,6 +372,8 @@ void QQmlDelegateModelPrivate::connectToAbstractItemModel() QObject::connect(aim, &QAbstractItemModel::modelAboutToBeReset, q, &QQmlDelegateModel::_q_modelAboutToBeReset); qmlobject_connect(aim, QAbstractItemModel, SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)), q, QQmlDelegateModel, SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint))); + QObject::connect(aim, &QAbstractItemModel::modelReset, q, &QQmlDelegateModel::handleModelReset); + QObject::connect(aim, &QAbstractItemModel::layoutChanged, q, &QQmlDelegateModel::_q_layoutChanged); } void QQmlDelegateModelPrivate::disconnectFromAbstractItemModel() @@ -400,6 +403,8 @@ void QQmlDelegateModelPrivate::disconnectFromAbstractItemModel() QObject::disconnect(aim, &QAbstractItemModel::modelAboutToBeReset, q, &QQmlDelegateModel::_q_modelAboutToBeReset); QObject::disconnect(aim, SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)), q, SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint))); + QObject::disconnect(aim, &QAbstractItemModel::modelReset, q, &QQmlDelegateModel::handleModelReset); + QObject::disconnect(aim, &QAbstractItemModel::layoutChanged, q, &QQmlDelegateModel::_q_layoutChanged); } void QQmlDelegateModel::setModel(const QVariant &model) @@ -1851,24 +1856,28 @@ void QQmlDelegateModelPrivate::emitChanges() void QQmlDelegateModel::_q_modelAboutToBeReset() { - auto aim = static_cast<QAbstractItemModel *>(sender()); - auto oldRoleNames = aim->roleNames(); - // this relies on the fact that modelAboutToBeReset must be followed - // by a modelReset signal before any further modelAboutToBeReset can occur - QObject::connect(aim, &QAbstractItemModel::modelReset, this, [&, oldRoleNames](){ - auto aim = static_cast<QAbstractItemModel *>(sender()); - if (oldRoleNames == aim->roleNames()) { - // if the rolenames stayed the same (most common case), then we don't have - // to throw away all the setup that we did - handleModelReset(); - } else { - // If they did change, we give up and just start from scratch via setMode - setModel(QVariant::fromValue(model())); - // but we still have to call handleModelReset, otherwise views will - // not refresh - handleModelReset(); - } - }, Qt::SingleShotConnection); + /* + roleNames are generally guaranteed to be stable (given that QAIM has no + change signal for them), except that resetting the model is allowed to + invalidate them (QTBUG-32132). DelegateModel must take this into account by + snapshotting the current roleNames before the model is reset. + Afterwards, if we detect that roleNames has changed, we throw the + current model set up away and rebuild everything from scratch โ it is + unlikely that a more efficient implementation would be worth it. + + If we detect no changes, we simply use the existing logic to handle the + model reset. + + This (role name resetting) logic relies on the fact that + modelAboutToBeReset must be followed by a modelReset signal before any + further modelAboutToBeReset can occur. However, it's possible for user + code to begin the reset before connectToAbstractItemModel is called + (QTBUG-125053), in which case we don't attempt to reset the role names. + */ + Q_D(QQmlDelegateModel); + Q_ASSERT(!d->m_maybeResetRoleNames); + d->m_maybeResetRoleNames = true; + d->m_roleNamesBeforeReset = d->m_adaptorModel.aim()->roleNames(); } void QQmlDelegateModel::handleModelReset() @@ -1878,6 +1887,23 @@ void QQmlDelegateModel::handleModelReset() return; int oldCount = d->m_count; + + if (d->m_maybeResetRoleNames) { + auto aim = d->m_adaptorModel.aim(); + if (!d->m_adaptorModel.adaptsAim() || d->m_adaptorModel.aim() != aim) + return; + + // If the role names stayed the same (most common case), then we don't have + // to throw away all the setup that we did. + // If they did change, we give up and just start from scratch via setModel. + // We do this before handling the reset to ensure that views refresh. + if (aim->roleNames() != d->m_roleNamesBeforeReset) + setModel(QVariant::fromValue(model())); + + d->m_maybeResetRoleNames = false; + d->m_roleNamesBeforeReset.clear(); + } + d->m_adaptorModel.rootIndex = QModelIndex(); if (d->m_complete) { diff --git a/src/qmlmodels/qqmldelegatemodel_p_p.h b/src/qmlmodels/qqmldelegatemodel_p_p.h index a7d22dfaeb..2fab7b35eb 100644 --- a/src/qmlmodels/qqmldelegatemodel_p_p.h +++ b/src/qmlmodels/qqmldelegatemodel_p_p.h @@ -332,6 +332,7 @@ public: QQmlReusableDelegateModelItemsPool m_reusableItemsPool; QList<QQDMIncubationTask *> m_finishedIncubating; QList<QByteArray> m_watchedRoles; + QHash<int, QByteArray> m_roleNamesBeforeReset; QString m_filterGroup; @@ -345,6 +346,7 @@ public: bool m_transaction : 1; bool m_incubatorCleanupScheduled : 1; bool m_waitingToFetchMore : 1; + bool m_maybeResetRoleNames : 1; union { struct { diff --git a/src/qmlmodels/qqmllistmodel.cpp b/src/qmlmodels/qqmllistmodel.cpp index 3bd180d072..2ab8a1795b 100644 --- a/src/qmlmodels/qqmllistmodel.cpp +++ b/src/qmlmodels/qqmllistmodel.cpp @@ -1669,9 +1669,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()) @@ -1687,7 +1690,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) @@ -1700,7 +1707,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); } @@ -1723,16 +1730,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 5c44405626..64f6a07421 100644 --- a/src/qmlmodels/qqmllistmodel_p.h +++ b/src/qmlmodels/qqmllistmodel_p.h @@ -79,6 +79,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 662a12f9e7..6a28a511f1 100644 --- a/src/qmlmodels/qqmllistmodel_p_p.h +++ b/src/qmlmodels/qqmllistmodel_p_p.h @@ -125,13 +125,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; }; } @@ -141,8 +151,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/qmlmodels/qqmltableinstancemodel.cpp b/src/qmlmodels/qqmltableinstancemodel.cpp index dcc15f90a5..6668c28ce2 100644 --- a/src/qmlmodels/qqmltableinstancemodel.cpp +++ b/src/qmlmodels/qqmltableinstancemodel.cpp @@ -122,7 +122,6 @@ QObject *QQmlTableInstanceModel::object(int index, QQmlIncubator::IncubationMode { Q_ASSERT(m_delegate); Q_ASSERT(index >= 0 && index < m_adaptorModel.count()); - Q_ASSERT(m_qmlContext && m_qmlContext->isValid()); QQmlDelegateModelItem *modelItem = resolveModelItem(index); if (!modelItem) @@ -290,7 +289,7 @@ void QQmlTableInstanceModel::incubateModelItem(QQmlDelegateModelItem *modelItem, const bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested); if (sync && modelItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous) modelItem->incubationTask->forceCompletion(); - } else { + } else if (m_qmlContext && m_qmlContext->isValid()) { modelItem->incubationTask = new QQmlTableInstanceModelIncubationTask(this, modelItem, incubationMode); QQmlContext *creationContext = modelItem->delegate->creationContext(); diff --git a/src/qmltest/doc/src/qtquicktest-index.qdoc b/src/qmltest/doc/src/qtquicktest-index.qdoc index b656979273..3fa6ccba6f 100644 --- a/src/qmltest/doc/src/qtquicktest-index.qdoc +++ b/src/qmltest/doc/src/qtquicktest-index.qdoc @@ -94,15 +94,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, @@ -136,12 +188,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/qmltest/quicktest.cpp b/src/qmltest/quicktest.cpp index b4bfd94d17..4e653ba26b 100644 --- a/src/qmltest/quicktest.cpp +++ b/src/qmltest/quicktest.cpp @@ -31,6 +31,8 @@ #include <QtGui/qtextdocument.h> #include <stdio.h> #include <QtGui/QGuiApplication> +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/qpa/qplatformintegration.h> #include <QtCore/QTranslator> #include <QtTest/QSignalSpy> #include <QtQml/QQmlFileSelector> @@ -340,7 +342,7 @@ private: TestCaseEnumerationResult enumerateTestCases( const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, - const Object *object = nullptr) + const QV4::CompiledData::Object *object = nullptr) { QQmlType testCaseType; for (quint32 i = 0, count = compilationUnit->importCount(); i < count; ++i) { @@ -362,7 +364,7 @@ private: if (!object) // Start at root of compilation unit if not enumerating a specific child object = compilationUnit->objectAt(0); - if (object->hasFlag(Object::IsInlineComponentRoot)) + if (object->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot)) return result; if (const auto superTypeUnit = compilationUnit->resolvedTypes.value( @@ -409,7 +411,7 @@ private: for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) { if (binding->type() == QV4::CompiledData::Binding::Type_Object) { - const Object *child = compilationUnit->objectAt(binding->value.objectIndex); + const QV4::CompiledData::Object *child = compilationUnit->objectAt(binding->value.objectIndex); result << enumerateTestCases(compilationUnit, child); } } @@ -649,10 +651,12 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch qWarning().nospace() << "Test '" << QDir::toNativeSeparators(path) << "' window not exposed after show()."; } - view.requestActivate(); - if (!QTest::qWaitForWindowActive(&view)) { - qWarning().nospace() - << "Test '" << QDir::toNativeSeparators(path) << "' window not active after requestActivate()."; + if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) { + view.requestActivate(); + if (!QTest::qWaitForWindowActive(&view)) { + qWarning().nospace() + << "Test '" << QDir::toNativeSeparators(path) << "' window not active after requestActivate()."; + } } if (view.isExposed()) { // Defer property update until event loop has started diff --git a/src/quick/doc/snippets/pointerHandlers/dragHandlerAcceptedButtons.qml b/src/quick/doc/snippets/pointerHandlers/dragHandlerAcceptedButtons.qml new file mode 100644 index 0000000000..9aeceb04f6 --- /dev/null +++ b/src/quick/doc/snippets/pointerHandlers/dragHandlerAcceptedButtons.qml @@ -0,0 +1,81 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +pragma ComponentBehavior: Bound +import QtQuick +import Qt.labs.animation +import Qt.labs.folderlistmodel + +//![0] +Rectangle { + id: canvas + width: 640 + height: 480 + color: "#333" + property int highestZ: 0 + + Repeater { + model: FolderListModel { nameFilters: ["*.qml"] } + + delegate: Rectangle { + required property string fileName + required property url fileUrl + required property int index + + id: frame + x: index * 30; y: index * 30 + width: 320; height: 240 + property bool dragging: ldh.active || rdh.active + onDraggingChanged: if (dragging) z = ++canvas.highestZ + border { width: 2; color: dragging ? "red" : "steelblue" } + color: "beige" + clip: true + + TextEdit { + // drag to select text + id: textEdit + textDocument.source: frame.fileUrl + x: 3; y: 3 + + BoundaryRule on y { + id: ybr + minimum: textEdit.parent.height - textEdit.height; maximum: 0 + minimumOvershoot: 200; maximumOvershoot: 200 + overshootFilter: BoundaryRule.Peak + } + } + + DragHandler { + id: rdh + // right-drag to position the "window" + acceptedButtons: Qt.RightButton + } + + WheelHandler { + target: textEdit + property: "y" + onActiveChanged: if (!active) ybr.returnToBounds() + } + + Rectangle { + anchors.right: parent.right + width: titleText.implicitWidth + 12 + height: titleText.implicitHeight + 6 + border { width: 2; color: parent.border.color } + bottomLeftRadius: 6 + Text { + id: titleText + color: "saddlebrown" + anchors.centerIn: parent + text: frame.fileName + textFormat: Text.PlainText + } + DragHandler { + id: ldh + // left-drag to position the "window" + target: frame + } + } + } + } +} +//![0] diff --git a/src/quick/doc/snippets/pointerHandlers/dragHandlerMargin.qml b/src/quick/doc/snippets/pointerHandlers/dragHandlerMargin.qml new file mode 100644 index 0000000000..7844d118e4 --- /dev/null +++ b/src/quick/doc/snippets/pointerHandlers/dragHandlerMargin.qml @@ -0,0 +1,27 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +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..f3a0cac8d2 --- /dev/null +++ b/src/quick/doc/snippets/pointerHandlers/draggableGridView.qml @@ -0,0 +1,121 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +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/doc/snippets/qml/font.qml b/src/quick/doc/snippets/qml/font.qml new file mode 100644 index 0000000000..5f76d6fc30 --- /dev/null +++ b/src/quick/doc/snippets/qml/font.qml @@ -0,0 +1,22 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick + +Item { +//! [text] + Text { + font.family: "Helvetica" + font.pointSize: 13 + font.bold: true + } +//! [text] + +//! [structured-value-construction] + readonly property font myFont: ({ + family: "Helvetica", + pointSize: 13, + bold: true + }) +//! [structured-value-construction] +} diff --git a/src/quick/doc/snippets/qml/tableview/editdelegate.qml b/src/quick/doc/snippets/qml/tableview/editdelegate.qml index 11de5f5434..c5274e5ac7 100644 --- a/src/quick/doc/snippets/qml/tableview/editdelegate.qml +++ b/src/quick/doc/snippets/qml/tableview/editdelegate.qml @@ -46,7 +46,7 @@ Window { display = text // 'display = text' is short-hand for: // let index = TableView.view.index(row, column) - // TableView.view.model.setData(index, text, Qt.DisplayRole) + // TableView.view.model.setData(index, "display", text) } } } diff --git a/src/quick/doc/snippets/qml/windowPalette.qml b/src/quick/doc/snippets/qml/windowPalette.qml index 0638213c57..5a9219c638 100644 --- a/src/quick/doc/snippets/qml/windowPalette.qml +++ b/src/quick/doc/snippets/qml/windowPalette.qml @@ -17,12 +17,14 @@ Window { palette.active.window: "peachpuff" palette.windowText: "brown" +//![text-item] Text { anchors.centerIn: parent // here we use the Window.active attached property and the Item.palette property color: Window.active ? palette.active.windowText : palette.inactive.windowText text: Window.active ? "active" : "inactive" } +//![text-item] Button { text: "Button" diff --git a/src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc b/src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc index 8719c78b30..87397e43b4 100644 --- a/src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc +++ b/src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc @@ -58,6 +58,15 @@ capable of rendering changes to the code in realtime. It avoids the need to rebuild the application after every code change and install it on the target device. You can also extend it to build a custom runtime that suits your needs. +\section1 Felgo QML Hot Reload + +\l{Felgo QML Hot Reload Tool}{Felgo QML Hot Reload} is a third-party tool that +updates QML and JavaScript code in your running application without recompiling +and redeploying after each change. Unlike Live Reload, it preserves the +application's current state after a reload and can run on multiple devices +simultaneously to test and iterate code. Felgo Hot Reload supports all Qt +target platforms and architectures. + \section1 GammaRay \l{GammaRay Manual}{GammaRay} is a useful utility that provides diagnostic diff --git a/src/quick/doc/src/qmltypereference.qdoc b/src/quick/doc/src/qmltypereference.qdoc index fd47c9fa53..790a1ddc8c 100644 --- a/src/quick/doc/src/qmltypereference.qdoc +++ b/src/quick/doc/src/qmltypereference.qdoc @@ -110,7 +110,7 @@ available when you import \c QtQuick. \section1 SVG Color Reference The following table lists the available - \l {http://www.w3.org/TR/SVG/types.html#ColorKeywords}{SVG colors}: + \l {https://www.w3.org/TR/css-color-3/#svg-color}{SVG colors}: \include svg-colors.qdocinc @@ -157,9 +157,13 @@ available when you import \c QtQuick. \endlist Example: - \qml - Text { font.family: "Helvetica"; font.pointSize: 13; font.bold: true } - \endqml + + \snippet qml/font.qml text + + As \c font is a \l {QML_STRUCTURED_VALUE}{structured value} type, it can + also be constructed with a JavaScript object: + + \snippet qml/font.qml structured-value-construction When integrating with C++, note that any QFont value \l{qtqml-cppintegration-data.html}{passed into QML from C++} is automatically diff --git a/src/quick/handlers/qquickdraghandler.cpp b/src/quick/handlers/qquickdraghandler.cpp index ac6fe1927f..599170f4e2 100644 --- a/src/quick/handlers/qquickdraghandler.cpp +++ b/src/quick/handlers/qquickdraghandler.cpp @@ -50,7 +50,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} */ @@ -98,7 +99,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 @@ -377,6 +378,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/qquickdrag.cpp b/src/quick/items/qquickdrag.cpp index ebc1f33cde..66718b42ed 100644 --- a/src/quick/items/qquickdrag.cpp +++ b/src/quick/items/qquickdrag.cpp @@ -686,11 +686,18 @@ QMimeData *QQuickDragAttachedPrivate::createMimeData() const } else if (mimeType == u"text/html"_s) { mimeData->setHtml(text); } else if (mimeType == u"text/uri-list"_s) { - const QUrl url(text); - if (url.isValid()) - mimeData->setUrls({url}); - else - qmlWarning(q) << text << " is not a valid URI"; + QList<QUrl> urls; + // parse and split according to RFC2483 + const auto lines = text.split(u"\r\n"_s, Qt::SkipEmptyParts); + for (const auto &line : lines) { + const QUrl url(line); + if (url.isValid()) + urls.push_back(url); + else + qmlWarning(q) << line << " is not a valid URI"; + + } + mimeData->setUrls(urls); } else if (mimeType.startsWith(u"text/"_s)) { if (qsizetype charsetIdx = mimeType.lastIndexOf(u";charset="_s); charsetIdx != -1) { charsetIdx += sizeof(";charset=") - 1; diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index 5dcc712c73..77a4bef646 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -1647,7 +1647,6 @@ void QQuickFlickable::wheelEvent(QWheelEvent *event) d->pressed = false; d->scrollingPhase = false; d->draggingEnding(); - event->accept(); returnToBounds(); d->lastPosTime = -1; d->stealMouse = false; diff --git a/src/quick/items/qquickgridview.cpp b/src/quick/items/qquickgridview.cpp index 19eecad94f..8c2bca9304 100644 --- a/src/quick/items/qquickgridview.cpp +++ b/src/quick/items/qquickgridview.cpp @@ -2402,7 +2402,7 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch { Q_Q(QQuickGridView); - if (q->size().isEmpty()) + if (q->size().isNull()) return false; int modelIndex = change.index; diff --git a/src/quick/items/qquickimagebase.cpp b/src/quick/items/qquickimagebase.cpp index 78dfecc42a..b4d21542f4 100644 --- a/src/quick/items/qquickimagebase.cpp +++ b/src/quick/items/qquickimagebase.cpp @@ -304,15 +304,17 @@ void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions) } } - d->pix.load(qmlEngine(this), - loadUrl, - d->sourceClipRect.toRect(), - (loadOptions & HandleDPR) ? d->sourcesize * d->devicePixelRatio : QSize(), - options, - (loadOptions & UseProviderOptions) ? d->providerOptions : QQuickImageProviderOptions(), - d->currentFrame, d->frameCount, - d->devicePixelRatio); - + auto engine = qmlEngine(this); + if (engine) { + d->pix.load(qmlEngine(this), + loadUrl, + d->sourceClipRect.toRect(), + (loadOptions & HandleDPR) ? d->sourcesize * d->devicePixelRatio : QSize(), + options, + (loadOptions & UseProviderOptions) ? d->providerOptions : QQuickImageProviderOptions(), + d->currentFrame, d->frameCount, + d->devicePixelRatio); + } if (d->pix.isLoading()) { if (d->progress != 0.0) { d->progress = 0.0; diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index f2621f9f68..1e4af6838d 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -1481,7 +1481,7 @@ QQuickLayoutMirroringAttached::QQuickLayoutMirroringAttached(QObject *parent) : if (itemPrivate) itemPrivate->extra.value().layoutDirectionAttached = this; else - qmlWarning(parent) << tr("LayoutDirection attached property only works with Items and Windows"); + qmlWarning(parent) << tr("LayoutMirroring attached property only works with Items and Windows"); } QQuickLayoutMirroringAttached * QQuickLayoutMirroringAttached::qmlAttachedProperties(QObject *object) diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp index fea7dba2c3..210759585b 100644 --- a/src/quick/items/qquickitemview.cpp +++ b/src/quick/items/qquickitemview.cpp @@ -10,6 +10,7 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcItemViewDelegateLifecycle, "qt.quick.itemview.lifecycle") +Q_LOGGING_CATEGORY(lcCount, "qt.quick.itemview.count") // Default cacheBuffer for all views. #ifndef QML_VIEW_DEFAULTCACHEBUFFER @@ -223,7 +224,7 @@ void QQuickItemView::setModel(const QVariant &m) this, SLOT(modelUpdated(QQmlChangeSet,bool))); if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model)) QObjectPrivate::connect(dataModel, &QQmlDelegateModel::delegateChanged, d, &QQuickItemViewPrivate::applyDelegateChange); - emit countChanged(); + d->emitCountChanged(); } emit modelChanged(); d->moveReason = QQuickItemViewPrivate::Other; @@ -255,7 +256,7 @@ void QQuickItemView::setDelegate(QQmlComponent *delegate) int oldCount = dataModel->count(); dataModel->setDelegate(delegate); if (oldCount != dataModel->count()) - emit countChanged(); + d->emitCountChanged(); } emit delegateChanged(); d->delegateValidated = false; @@ -1086,8 +1087,7 @@ qreal QQuickItemViewPrivate::calculatedMaxExtent() const void QQuickItemViewPrivate::applyDelegateChange() { releaseVisibleItems(QQmlDelegateModel::NotReusable); - releaseItem(currentItem, QQmlDelegateModel::NotReusable); - currentItem = nullptr; + releaseCurrentItem(QQmlDelegateModel::NotReusable); updateSectionCriteria(); refill(); moveReason = QQuickItemViewPrivate::SetIndex; @@ -1126,6 +1126,14 @@ void QQuickItemViewPrivate::showVisibleItems() const } } +// Simplifies debugging of count. +void QQuickItemViewPrivate::emitCountChanged() +{ + Q_Q(QQuickItemView); + qCDebug(lcCount).nospace() << "about to emit countChanged for " << q << "; count changed to " << q->count(); + emit q->countChanged(); +} + void QQuickItemViewPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &oldGeometry) { @@ -1225,7 +1233,7 @@ void QQuickItemView::modelUpdated(const QQmlChangeSet &changeSet, bool reset) d->updateTrackedItem(); } d->moveReason = QQuickItemViewPrivate::Other; - emit countChanged(); + d->emitCountChanged(); #if QT_CONFIG(quick_viewtransitions) if (d->transitioner && d->transitioner->populateTransition) d->forceLayoutPolish(); @@ -1486,7 +1494,7 @@ void QQuickItemView::componentComplete() d->fixupPosition(); } if (d->model && d->model->count()) - emit countChanged(); + d->emitCountChanged(); } @@ -1653,8 +1661,7 @@ void QQuickItemViewPrivate::updateCurrent(int modelIndex) if (currentItem) { if (currentItem->attached) currentItem->attached->setIsCurrentItem(false); - releaseItem(currentItem, reusableFlag); - currentItem = nullptr; + releaseCurrentItem(reusableFlag); currentIndex = modelIndex; emit q->currentIndexChanged(); emit q->currentItemChanged(); @@ -1715,10 +1722,9 @@ void QQuickItemViewPrivate::clear(bool onDestruction) releasePendingTransition.clear(); #endif - auto oldCurrentItem = currentItem; - releaseItem(currentItem, QQmlDelegateModel::NotReusable); - currentItem = nullptr; - if (oldCurrentItem) + const bool hadCurrentItem = currentItem != nullptr; + releaseCurrentItem(QQmlDelegateModel::NotReusable); + if (hadCurrentItem) emit q->currentItemChanged(); createHighlight(onDestruction); trackedItem = nullptr; @@ -1763,7 +1769,7 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to) Q_Q(QQuickItemView); if (!model || !model->isValid() || !q->isComponentComplete()) return; - if (q->size().isEmpty() && visibleItems.isEmpty()) + if (q->size().isNull() && visibleItems.isEmpty()) return; if (!model->count()) { updateHeader(); @@ -1814,7 +1820,7 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to) } if (prevCount != itemCount) - emit q->countChanged(); + emitCountChanged(); } while (currentChanges.hasPendingChanges() || bufferedChanges.hasPendingChanges()); storeFirstVisibleItemPosition(); } @@ -1861,7 +1867,20 @@ void QQuickItemViewPrivate::layout() // viewBounds contains bounds before any add/remove/move operation to the view QRectF viewBounds(q->contentX(), q->contentY(), q->width(), q->height()); - if (!isValid() && !visibleItems.size()) { + // We use isNull for the size check, because isEmpty returns true + // if either dimension is negative, but apparently we support negative-sized + // views (see tst_QQuickListView::resizeView). + if ((!isValid() && !visibleItems.size()) || q->size().isNull()) { + if (q->size().isNull() && hasPendingChanges()) { + // count() refers to the number of items in the model, not in the view + // (which is why we don't emit for the !visibleItems.size() case). + // If there are pending model changes, emit countChanged in order to + // support the use case of QTBUG-129165, where visible is bound to count > 0 + // and the ListView is in a layout with Layout.preferredHeight bound to + // contentHeight. This ensures that a hidden ListView will become visible. + emitCountChanged(); + } + clear(); setPosition(contentStartOffset()); updateViewport(); @@ -2121,10 +2140,9 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult if (currentChanges.currentRemoved && currentItem) { if (currentItem->item && currentItem->attached) currentItem->attached->setIsCurrentItem(false); - auto oldCurrentItem = currentItem; - releaseItem(currentItem, reusableFlag); - currentItem = nullptr; - if (oldCurrentItem) + const bool hadCurrentItem = currentItem != nullptr; + releaseCurrentItem(reusableFlag); + if (hadCurrentItem) emit q->currentItemChanged(); } if (!currentIndexCleared) @@ -2137,7 +2155,7 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult updateSections(); if (prevItemCount != itemCount) - emit q->countChanged(); + emitCountChanged(); if (!visibleAffected && viewportChanged) updateViewport(); @@ -2491,9 +2509,15 @@ bool QQuickItemViewPrivate::releaseItem(FxViewItem *item, QQmlInstanceModel::Reu return flags != QQmlInstanceModel::Referenced; } -QQuickItem *QQuickItemViewPrivate::createHighlightItem() const +QQuickItem *QQuickItemViewPrivate::createHighlightItem() { - return createComponentItem(highlightComponent, 0.0, true); + QQuickItem *item = nullptr; + if (!inRequest) { + inRequest = true; + item = createComponentItem(highlightComponent, 0.0, true); + inRequest = false; + } + return item; } QQuickItem *QQuickItemViewPrivate::createComponentItem(QQmlComponent *component, qreal zValue, bool createDefault) const diff --git a/src/quick/items/qquickitemview_p_p.h b/src/quick/items/qquickitemview_p_p.h index 301ff6f326..c1188ac4d7 100644 --- a/src/quick/items/qquickitemview_p_p.h +++ b/src/quick/items/qquickitemview_p_p.h @@ -145,9 +145,14 @@ public: void mirrorChange() override; FxViewItem *createItem(int modelIndex,QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested); + bool releaseCurrentItem(QQmlInstanceModel::ReusableFlag reusableFlag) + { + auto oldCurrentItem = std::exchange(currentItem, nullptr); + return releaseItem(oldCurrentItem, reusableFlag); + } virtual bool releaseItem(FxViewItem *item, QQmlInstanceModel::ReusableFlag reusableFlag); - QQuickItem *createHighlightItem() const; + QQuickItem *createHighlightItem(); QQuickItem *createComponentItem(QQmlComponent *component, qreal zValue, bool createDefault = false) const; virtual void initializeComponentItem(QQuickItem *) const; @@ -224,6 +229,8 @@ public: releaseItem(item, reusableFlag); } + void emitCountChanged(); + virtual QQuickItemViewAttached *getAttachedObject(const QObject *) const { return nullptr; } QPointer<QQmlInstanceModel> model; diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp index 41c345bd66..92b66ca5ed 100644 --- a/src/quick/items/qquicklistview.cpp +++ b/src/quick/items/qquicklistview.cpp @@ -1797,6 +1797,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); @@ -3645,7 +3658,7 @@ bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch int modelIndex = change.index; int count = change.count; - if (q->size().isEmpty() && visibleItems.isEmpty()) + if (q->size().isNull() && visibleItems.isEmpty()) return false; qreal tempPos = isContentFlowReversed() ? -position()-size() : position(); @@ -3773,7 +3786,10 @@ bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch continue; } - visibleItems.insert(index, item); + if (index < visibleItems.size()) + visibleItems.insert(index, item); + else // special case of appending an item to the model - as above + visibleItems.append(item); if (index == 0) insertResult->changedFirstItem = true; if (change.isMove()) { diff --git a/src/quick/items/qquickpalettecolorprovider.cpp b/src/quick/items/qquickpalettecolorprovider.cpp index 2d36ff01c5..a9dfe45092 100644 --- a/src/quick/items/qquickpalettecolorprovider.cpp +++ b/src/quick/items/qquickpalettecolorprovider.cpp @@ -116,6 +116,7 @@ bool QQuickPaletteColorProvider::doInheritPalette(const QPalette &palette) { auto inheritedMask = m_requestedPalette.isAllocated() ? m_requestedPalette->resolveMask() | palette.resolveMask() : palette.resolveMask(); + // If a palette was set on this item, it should always win over the palette to be inherited from. QPalette parentPalette = m_requestedPalette.isAllocated() ? m_requestedPalette->resolve(palette) : palette; parentPalette.setResolveMask(inheritedMask); diff --git a/src/quick/items/qquickpathview.cpp b/src/quick/items/qquickpathview.cpp index ee2bf7ac39..9e99ec0601 100644 --- a/src/quick/items/qquickpathview.cpp +++ b/src/quick/items/qquickpathview.cpp @@ -206,10 +206,7 @@ QQmlOpenMetaObjectType *QQuickPathViewPrivate::attachedType() void QQuickPathViewPrivate::clear() { - if (currentItem) { - releaseItem(currentItem); - currentItem = nullptr; - } + releaseCurrentItem(); for (QQuickItem *p : std::as_const(items)) releaseItem(p); @@ -724,14 +721,13 @@ void QQuickPathView::setCurrentIndex(int idx) ? ((idx % d->modelCount) + d->modelCount) % d->modelCount : 0; if (d->model && (idx != d->currentIndex || !d->currentItem)) { - if (d->currentItem) { + const bool hadCurrentItem = d->currentItem != nullptr; + const int oldCurrentIdx = d->currentIndex; + if (hadCurrentItem) { if (QQuickPathViewAttached *att = d->attached(d->currentItem)) att->setIsCurrentItem(false); - d->releaseItem(d->currentItem); + d->releaseCurrentItem(); } - int oldCurrentIdx = d->currentIndex; - QQuickItem *oldCurrentItem = d->currentItem; - d->currentItem = nullptr; d->moveReason = QQuickPathViewPrivate::SetIndex; d->currentIndex = idx; if (d->modelCount) { @@ -743,7 +739,7 @@ void QQuickPathView::setCurrentIndex(int idx) } if (oldCurrentIdx != d->currentIndex) emit currentIndexChanged(); - if (oldCurrentItem != d->currentItem) + if (hadCurrentItem) emit currentItemChanged(); } } @@ -2003,6 +1999,7 @@ void QQuickPathView::refill() startPos = d->highlightRangeStart; // With no items, then "end" is just off the top so we populate via append endIdx = (qRound(d->modelCount - d->offset) - 1) % d->modelCount; + endIdx = qMax(-1, endIdx); // endIdx shouldn't be smaller than -1 endPos = d->positionOfIndex(endIdx); } //Append @@ -2185,8 +2182,7 @@ void QQuickPathView::modelUpdated(const QQmlChangeSet &changeSet, bool reset) } else if (d->currentItem) { if (QQuickPathViewAttached *att = d->attached(d->currentItem)) att->setIsCurrentItem(true); - d->releaseItem(d->currentItem); - d->currentItem = nullptr; + d->releaseCurrentItem(); } d->currentIndex = qMin(r.index, d->modelCount - r.count - 1); currentChanged = true; @@ -2334,7 +2330,7 @@ void QQuickPathViewPrivate::updateCurrent() if (currentItem) { if (QQuickPathViewAttached *att = attached(currentItem)) att->setIsCurrentItem(false); - releaseItem(currentItem); + releaseCurrentItem(); } int oldCurrentIndex = currentIndex; currentIndex = idx; diff --git a/src/quick/items/qquickpathview_p_p.h b/src/quick/items/qquickpathview_p_p.h index 61c0b2ce62..65df3b6be1 100644 --- a/src/quick/items/qquickpathview_p_p.h +++ b/src/quick/items/qquickpathview_p_p.h @@ -67,6 +67,11 @@ public: } QQuickItem *getItem(int modelIndex, qreal z = 0, bool async=false); + void releaseCurrentItem() + { + auto oldCurrentItem = std::exchange(currentItem, nullptr); + releaseItem(oldCurrentItem); + } void releaseItem(QQuickItem *item); QQuickPathViewAttached *attached(QQuickItem *item); QQmlOpenMetaObjectType *attachedType(); diff --git a/src/quick/items/qquickshadereffect.cpp b/src/quick/items/qquickshadereffect.cpp index 2a6d40c8aa..40a2942041 100644 --- a/src/quick/items/qquickshadereffect.cpp +++ b/src/quick/items/qquickshadereffect.cpp @@ -538,6 +538,14 @@ QQuickShaderEffect::~QQuickShaderEffect() { Q_D(QQuickShaderEffect); d->inDestructor = true; + + for (int i = 0; i < QQuickShaderEffectPrivate::NShader; ++i) { + d->disconnectSignals(QQuickShaderEffectPrivate::Shader(i)); + d->clearMappers(QQuickShaderEffectPrivate::Shader(i)); + } + + delete d->m_mgr; + d->m_mgr = nullptr; } /*! @@ -835,12 +843,7 @@ QQuickShaderEffectPrivate::QQuickShaderEffectPrivate() QQuickShaderEffectPrivate::~QQuickShaderEffectPrivate() { - for (int i = 0; i < NShader; ++i) { - disconnectSignals(Shader(i)); - clearMappers(Shader(i)); - } - - delete m_mgr; + Q_ASSERT(m_mgr == nullptr); } void QQuickShaderEffectPrivate::setFragmentShader(const QUrl &fileUrl) diff --git a/src/quick/items/qquickstateoperations.cpp b/src/quick/items/qquickstateoperations.cpp index 1c8bee7253..16127e5a35 100644 --- a/src/quick/items/qquickstateoperations.cpp +++ b/src/quick/items/qquickstateoperations.cpp @@ -1055,17 +1055,24 @@ void QQuickAnchorChanges::reverse() //restore any absolute geometry changed by the state's anchors QQuickAnchors::Anchors stateVAnchors = d->anchorSet->d_func()->usedAnchors & QQuickAnchors::Vertical_Mask; QQuickAnchors::Anchors origVAnchors = targetPrivate->anchors()->usedAnchors() & QQuickAnchors::Vertical_Mask; + QQuickAnchors::Anchors resetVAnchors = d->anchorSet->d_func()->resetAnchors & QQuickAnchors::Vertical_Mask; QQuickAnchors::Anchors stateHAnchors = d->anchorSet->d_func()->usedAnchors & QQuickAnchors::Horizontal_Mask; QQuickAnchors::Anchors origHAnchors = targetPrivate->anchors()->usedAnchors() & QQuickAnchors::Horizontal_Mask; + QQuickAnchors::Anchors resetHAnchors = d->anchorSet->d_func()->resetAnchors & QQuickAnchors::Horizontal_Mask; const QRectF oldGeometry(d->target->position(), d->target->size()); bool stateSetWidth = (stateHAnchors && stateHAnchors != QQuickAnchors::LeftAnchor && stateHAnchors != QQuickAnchors::RightAnchor && stateHAnchors != QQuickAnchors::HCenterAnchor); - // in case of an additive AnchorChange, we _did_ end up modifying the width - stateSetWidth |= ((stateHAnchors & QQuickAnchors::LeftAnchor) && (origHAnchors & QQuickAnchors::RightAnchor)) || - ((stateHAnchors & QQuickAnchors::RightAnchor) && (origHAnchors & QQuickAnchors::LeftAnchor)); + // in case of an additive AnchorChange, we _did_ end up modifying the width, unless opposite + // edge was set to undefined in state + stateSetWidth |= ((stateHAnchors & QQuickAnchors::LeftAnchor) + && (origHAnchors & QQuickAnchors::RightAnchor) + && !(resetHAnchors & QQuickAnchors::RightAnchor)) + || ((stateHAnchors & QQuickAnchors::RightAnchor) + && (origHAnchors & QQuickAnchors::LeftAnchor) + && !(resetHAnchors & QQuickAnchors::LeftAnchor)); bool origSetWidth = (origHAnchors && origHAnchors != QQuickAnchors::LeftAnchor && origHAnchors != QQuickAnchors::RightAnchor && @@ -1081,9 +1088,14 @@ void QQuickAnchorChanges::reverse() stateVAnchors != QQuickAnchors::BottomAnchor && stateVAnchors != QQuickAnchors::VCenterAnchor && stateVAnchors != QQuickAnchors::BaselineAnchor); - // in case of an additive AnchorChange, we _did_ end up modifying the height - stateSetHeight |= ((stateVAnchors & QQuickAnchors::TopAnchor) && (origVAnchors & QQuickAnchors::BottomAnchor)) || - ((stateVAnchors & QQuickAnchors::BottomAnchor) && (origVAnchors & QQuickAnchors::TopAnchor)); + // in case of an additive AnchorChange, we _did_ end up modifying the height, unless opposite + // edge was set to undefined in state + stateSetHeight |= ((stateVAnchors & QQuickAnchors::TopAnchor) + && (origVAnchors & QQuickAnchors::BottomAnchor) + && !(resetVAnchors & QQuickAnchors::BottomAnchor)) + || ((stateVAnchors & QQuickAnchors::BottomAnchor) + && (origVAnchors & QQuickAnchors::TopAnchor) + && !(resetVAnchors & QQuickAnchors::TopAnchor)); bool origSetHeight = (origVAnchors && origVAnchors != QQuickAnchors::TopAnchor && origVAnchors != QQuickAnchors::BottomAnchor && diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 390d246170..1b564df125 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1011,32 +1011,26 @@ \qmlmethod real QtQuick::TableView::implicitColumnWidth(int column) \since 6.2 - Returns the implicit width of the given \a column. If the - column is not loaded (and therefore not visible), the return value - will be \c -1. + Returns the implicit width of the given \a column. This is the largest + \l implicitWidth found among the currently \l{isRowLoaded()}{loaded} + delegate items inside that column. - The implicit width of a column is the largest implicitWidth - found among the currently loaded delegate items inside that column. - Widths returned by the \l columnWidthProvider will not be taken - into account. + If the \a column is not loaded (and therefore not visible), the return value is \c -1. - \sa columnWidthProvider, columnWidth(), isColumnLoaded(), {Row heights and column widths} + \sa columnWidth(), isRowLoaded(), {Row heights and column widths} */ /*! \qmlmethod real QtQuick::TableView::implicitRowHeight(int row) \since 6.2 - Returns the implicit height of the given \a row. If the - row is not loaded (and therefore not visible), the return value - will be \c -1. + Returns the implicit height of the given \a row. This is the largest + \l implicitHeight found among the currently \l{isColumnLoaded()}{loaded} + delegate items inside that row. - The implicit height of a row is the largest implicitHeight - found among the currently loaded delegate items inside that row. - Heights returned by the \l rowHeightProvider will not be taken - into account. + If the \a row is not loaded (and therefore not visible), the return value is \c -1. - \sa rowHeightProvider, rowHeight(), isRowLoaded(), {Row heights and column widths} + \sa rowHeight(), isColumnLoaded(), {Row heights and column widths} */ /*! @@ -1202,13 +1196,15 @@ Convenience function for doing: \code - modelIndex(cell.y, cell.x) + index(cell.y, cell.x) \endcode A cell is simply a \l point that combines row and column into a single type. \note \c {point.x} will map to the column, and \c {point.y} will map to the row. + + \sa index() */ /*! @@ -2155,11 +2151,13 @@ void QQuickTableViewPrivate::updateExtents() const int nextTopRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::TopEdge); const int nextBottomRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::BottomEdge); + QPointF prevOrigin = origin; + QSizeF prevEndExtent = endExtent; + if (syncHorizontally) { const auto syncView_d = syncView->d_func(); origin.rx() = syncView_d->origin.x(); endExtent.rwidth() = syncView_d->endExtent.width(); - hData.markExtentsDirty(); } else if (nextLeftColumn == kEdgeIndexAtEnd) { // There are no more columns to load on the left side of the table. // In that case, we ensure that the origin match the beginning of the table. @@ -2176,7 +2174,6 @@ void QQuickTableViewPrivate::updateExtents() } } origin.rx() = loadedTableOuterRect.left(); - hData.markExtentsDirty(); } else if (loadedTableOuterRect.left() <= origin.x() + cellSpacing.width()) { // The table rect is at the origin, or outside, but we still have more // visible columns to the left. So we try to guesstimate how much space @@ -2186,7 +2183,6 @@ void QQuickTableViewPrivate::updateExtents() const qreal remainingSpacing = columnsRemaining * cellSpacing.width(); const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing; origin.rx() = loadedTableOuterRect.left() - estimatedRemainingWidth; - hData.markExtentsDirty(); } else if (nextRightColumn == kEdgeIndexAtEnd) { // There are no more columns to load on the right side of the table. // In that case, we ensure that the end of the content view match the end of the table. @@ -2204,7 +2200,6 @@ void QQuickTableViewPrivate::updateExtents() } } endExtent.rwidth() = loadedTableOuterRect.right() - q->contentWidth(); - hData.markExtentsDirty(); } else if (loadedTableOuterRect.right() >= q->contentWidth() + endExtent.width() - cellSpacing.width()) { // The right-most column is outside the end of the content view, and we // still have more visible columns in the model. This can happen if the application @@ -2215,14 +2210,12 @@ void QQuickTableViewPrivate::updateExtents() const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing; const qreal pixelsOutsideContentWidth = loadedTableOuterRect.right() - q->contentWidth(); endExtent.rwidth() = pixelsOutsideContentWidth + estimatedRemainingWidth; - hData.markExtentsDirty(); } if (syncVertically) { const auto syncView_d = syncView->d_func(); origin.ry() = syncView_d->origin.y(); endExtent.rheight() = syncView_d->endExtent.height(); - vData.markExtentsDirty(); } else if (nextTopRow == kEdgeIndexAtEnd) { // There are no more rows to load on the top side of the table. // In that case, we ensure that the origin match the beginning of the table. @@ -2239,7 +2232,6 @@ void QQuickTableViewPrivate::updateExtents() } } origin.ry() = loadedTableOuterRect.top(); - vData.markExtentsDirty(); } else if (loadedTableOuterRect.top() <= origin.y() + cellSpacing.height()) { // The table rect is at the origin, or outside, but we still have more // visible rows at the top. So we try to guesstimate how much space @@ -2249,7 +2241,6 @@ void QQuickTableViewPrivate::updateExtents() const qreal remainingSpacing = rowsRemaining * cellSpacing.height(); const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing; origin.ry() = loadedTableOuterRect.top() - estimatedRemainingHeight; - vData.markExtentsDirty(); } else if (nextBottomRow == kEdgeIndexAtEnd) { // There are no more rows to load on the bottom side of the table. // In that case, we ensure that the end of the content view match the end of the table. @@ -2267,7 +2258,6 @@ void QQuickTableViewPrivate::updateExtents() } } endExtent.rheight() = loadedTableOuterRect.bottom() - q->contentHeight(); - vData.markExtentsDirty(); } else if (loadedTableOuterRect.bottom() >= q->contentHeight() + endExtent.height() - cellSpacing.height()) { // The bottom-most row is outside the end of the content view, and we // still have more visible rows in the model. This can happen if the application @@ -2278,7 +2268,6 @@ void QQuickTableViewPrivate::updateExtents() const qreal estimatedRemainingHeight = remainingRowHeigts + remainingSpacing; const qreal pixelsOutsideContentHeight = loadedTableOuterRect.bottom() - q->contentHeight(); endExtent.rheight() = pixelsOutsideContentHeight + estimatedRemainingHeight; - vData.markExtentsDirty(); } if (tableMovedHorizontally || tableMovedVertically) { @@ -2299,12 +2288,23 @@ void QQuickTableViewPrivate::updateExtents() } } - if (hData.minExtentDirty || vData.minExtentDirty) { - qCDebug(lcTableViewDelegateLifecycle) << "move origin and endExtent to:" << origin << endExtent; + if (prevOrigin != origin || prevEndExtent != endExtent) { + if (prevOrigin != origin) + qCDebug(lcTableViewDelegateLifecycle) << "move origin to:" << origin; + if (prevEndExtent != endExtent) + qCDebug(lcTableViewDelegateLifecycle) << "move endExtent to:" << endExtent; // updateBeginningEnd() will let the new extents take effect. This will also change the // visualArea of the flickable, which again will cause any attached scrollbars to adjust // the position of the handle. Note the latter will cause the viewport to move once more. + hData.markExtentsDirty(); + vData.markExtentsDirty(); updateBeginningEnd(); + if (!q->isMoving()) { + // When we adjust the extents, the viewport can sometimes be left suspended in an + // overshooted state. It will bounce back again once the user clicks inside the + // viewport. But this comes across as a bug, so returnToBounds explicitly. + q->returnToBounds(); + } } } @@ -4318,11 +4318,18 @@ void QQuickTableViewPrivate::syncSyncView() q->setRightMargin(syncView->rightMargin()); 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); + } } } @@ -4333,11 +4340,18 @@ void QQuickTableViewPrivate::syncSyncView() q->setBottomMargin(syncView->bottomMargin()); 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); + } } } @@ -4724,7 +4738,7 @@ void QQuickTableViewPrivate::syncViewportRect() auto syncChild_d = syncChild->d_func(); if (syncChild_d->syncHorizontally) w = qMax(w, syncChild->width()); - if (syncChild_d->syncHorizontally) + if (syncChild_d->syncVertically) h = qMax(h, syncChild->height()); } diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index f618a55760..e5976acb1e 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -62,6 +62,7 @@ QQuickTextPrivate::QQuickTextPrivate() , layoutTextElided(false), textHasChanged(true), needToUpdateLayout(false), formatModifiesFontSize(false) , polishSize(false) , updateSizeRecursionGuard(false) + , containsUnscalableGlyphs(false) { implicitAntialiasing = true; } @@ -1697,7 +1698,7 @@ void QQuickText::itemChange(ItemChange change, const ItemChangeData &value) break; case ItemDevicePixelRatioHasChanged: - if (d->renderType == NativeRendering) { + if (d->containsUnscalableGlyphs) { // Native rendering optimizes for a given pixel grid, so its results must not be scaled. // Text layout code respects the current device pixel ratio automatically, we only need // to rerun layout after the ratio changed. @@ -2495,6 +2496,7 @@ QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data Q_D(QQuickText); if (d->text.isEmpty()) { + d->containsUnscalableGlyphs = false; delete oldNode; return nullptr; } @@ -2553,6 +2555,8 @@ QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data } } + d->containsUnscalableGlyphs = node->containsUnscalableGlyphs(); + // The font caches have now been initialized on the render thread, so they have to be // invalidated before we can use them from the main thread again. invalidateFontCaches(); diff --git a/src/quick/items/qquicktext_p_p.h b/src/quick/items/qquicktext_p_p.h index 9870197c31..8e48f93281 100644 --- a/src/quick/items/qquicktext_p_p.h +++ b/src/quick/items/qquicktext_p_p.h @@ -143,6 +143,7 @@ public: bool formatModifiesFontSize:1; bool polishSize:1; // Workaround for problem with polish called after updateSize (QTBUG-42636) bool updateSizeRecursionGuard:1; + bool containsUnscalableGlyphs:1; static const QChar elideChar; static const int largeTextSizeThreshold; diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index f27b537302..4471d6e4b9 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -707,6 +707,7 @@ void QQuickTextEdit::setHAlign(HAlignment align) if (d->setHAlign(align, true) && isComponentComplete()) { d->updateDefaultTextOption(); updateSize(); + updateWholeDocument(); } } @@ -814,6 +815,7 @@ void QQuickTextEditPrivate::mirrorChange() if (!hAlignImplicit && (hAlign == QQuickTextEdit::AlignRight || hAlign == QQuickTextEdit::AlignLeft)) { updateDefaultTextOption(); q->updateSize(); + q->updateWholeDocument(); emit q->effectiveHorizontalAlignmentChanged(); } } @@ -1512,7 +1514,7 @@ void QQuickTextEdit::itemChange(ItemChange change, const ItemChangeData &value) Q_UNUSED(value); switch (change) { case ItemDevicePixelRatioHasChanged: - if (d->renderType == NativeRendering) { + if (d->containsUnscalableGlyphs) { // Native rendering optimizes for a given pixel grid, so its results must not be scaled. // Text layout code respects the current device pixel ratio automatically, we only need // to rerun layout after the ratio changed. @@ -2138,6 +2140,7 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * return oldNode; } + d->containsUnscalableGlyphs = false; if (!oldNode || d->updateType == QQuickTextEditPrivate::UpdateAll) { delete oldNode; oldNode = nullptr; @@ -2216,14 +2219,17 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * d->firstBlockInViewport = -1; d->firstBlockPastViewport = -1; + int frameCount = -1; while (!frames.isEmpty()) { QTextFrame *textFrame = frames.takeFirst(); + ++frameCount; + if (frameCount > 0) + firstDirtyPos = 0; + qCDebug(lcVP) << "frame" << frameCount << textFrame + << "from" << positionToRectangle(textFrame->firstPosition()).topLeft() + << "to" << positionToRectangle(textFrame->lastPosition()).bottomRight(); frames.append(textFrame->childFrames()); frameDecorationsEngine.addFrameDecorations(d->document, textFrame); - - if (textFrame->lastPosition() < firstDirtyPos - || textFrame->firstPosition() >= firstCleanNode.startPos()) - continue; resetEngine(&engine, d->color, d->selectedTextColor, d->selectionColor); if (textFrame->firstPosition() > textFrame->lastPosition() @@ -2307,16 +2313,25 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * } break; // skip rest of blocks in this frame } - if (inView && !block.text().isEmpty() && coveredRegion.isValid()) + if (inView && !block.text().isEmpty() && coveredRegion.isValid()) { d->renderedRegion = d->renderedRegion.united(coveredRegion); + // In case we're going to visit more (nested) frames after this, ensure that we + // don't omit any blocks that fit within the region that we claim as fully rendered. + if (!frames.isEmpty()) + viewport = viewport.united(d->renderedRegion); + } } } bool createdNodeInView = false; if (inView) { if (!engine.hasContents()) { - if (node && !node->parent()) - d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart); + if (node) { + d->containsUnscalableGlyphs = d->containsUnscalableGlyphs + || node->containsUnscalableGlyphs(); + if (!node->parent()) + d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart); + } node = d->createTextNode(); createdNodeInView = true; updateNodeTransform(node, nodeOffset); @@ -2331,6 +2346,8 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * QList<int>::const_iterator lowerBound = std::lower_bound(frameBoundaries.constBegin(), frameBoundaries.constEnd(), block.next().position()); if (node && (currentNodeSize > nodeBreakingSize || lowerBound == frameBoundaries.constEnd() || *lowerBound > nodeStart)) { currentNodeSize = 0; + d->containsUnscalableGlyphs = d->containsUnscalableGlyphs + || node->containsUnscalableGlyphs(); if (!node->parent()) d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart); if (!createdNodeInView) @@ -2341,8 +2358,12 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * ++it; } // loop over blocks in frame } - if (Q_LIKELY(node && !node->parent())) - d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart); + if (Q_LIKELY(node)) { + d->containsUnscalableGlyphs = d->containsUnscalableGlyphs + || node->containsUnscalableGlyphs(); + if (Q_LIKELY(!node->parent())) + d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart); + } } frameDecorationsEngine.addToSceneGraph(rootNode->frameDecorationsNode, QQuickText::Normal, QColor()); // Now prepend the frame decorations since we want them rendered first, with the text nodes and cursor in front. @@ -2943,6 +2964,7 @@ void QQuickTextEditPrivate::addCurrentTextNodeToRoot(QQuickTextNodeEngine *engin it = textNodeMap.insert(it, TextNode(startPos, node)); ++it; root->appendChildNode(node); + ++renderedBlockCount; } QQuickTextNode *QQuickTextEditPrivate::createTextNode() diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h index db07462a5a..114373c1c7 100644 --- a/src/quick/items/qquicktextedit_p_p.h +++ b/src/quick/items/qquicktextedit_p_p.h @@ -96,6 +96,7 @@ public: , selectByMouse(true), canPaste(false), canPasteValid(false), hAlignImplicit(true) , textCached(true), inLayout(false), selectByKeyboard(false), selectByKeyboardSet(false) , hadSelection(false), markdownText(false) + , containsUnscalableGlyphs(false) { } @@ -164,6 +165,7 @@ public: int lineCount; int firstBlockInViewport = -1; // only for the autotest; can be wrong after scrolling sometimes int firstBlockPastViewport = -1; // only for the autotest + int renderedBlockCount = -1; // only for the autotest QRectF renderedRegion; enum UpdateType { @@ -202,6 +204,7 @@ public: bool selectByKeyboardSet:1; bool hadSelection : 1; bool markdownText : 1; + bool containsUnscalableGlyphs : 1; static const int largeTextSizeThreshold; }; diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index e4b7e4197b..08713eb026 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -1568,6 +1568,8 @@ void QQuickTextInput::mousePressEvent(QMouseEvent *event) if (d->sendMouseEventToInputContext(event)) return; + d->hadSelectionOnMousePress = d->hasSelectedText(); + const bool isMouse = QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(event); if (d->selectByMouse && (isMouse @@ -1663,8 +1665,13 @@ void QQuickTextInput::mouseReleaseEvent(QMouseEvent *event) // On a touchscreen or with a stylus, set cursor position and focus on release, not on press; // if Flickable steals the grab in the meantime, the cursor won't move. // Check d->hasSelectedText() to keep touch-and-hold word selection working. - if (!isMouse && !d->hasSelectedText()) + // But if text was selected already on press, deselect it on release. + if (!isMouse && (!d->hasSelectedText() || d->hadSelectionOnMousePress)) d->moveCursor(d->positionAt(event->position()), false); + // On Android, after doing a long-press to start selection, we see a release event, + // even though there was no press event. So reset hadSelectionOnMousePress to avoid + // it getting stuck in true state. + d->hadSelectionOnMousePress = false; if (d->focusOnPress && qGuiApp->styleHints()->setFocusOnTouchRelease()) ensureActiveFocus(Qt::MouseFocusReason); @@ -1778,7 +1785,7 @@ void QQuickTextInput::itemChange(ItemChange change, const ItemChangeData &value) Q_UNUSED(value); switch (change) { case ItemDevicePixelRatioHasChanged: - if (d->renderType == NativeRendering) { + if (d->containsUnscalableGlyphs) { // Native rendering optimizes for a given pixel grid, so its results must not be scaled. // Text layout code respects the current device pixel ratio automatically, we only need // to rerun layout after the ratio changed. @@ -1997,6 +2004,8 @@ QSGNode *QQuickTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData d->textLayoutDirty = false; } + d->containsUnscalableGlyphs = node->containsUnscalableGlyphs(); + invalidateFontCaches(); return node; diff --git a/src/quick/items/qquicktextinput_p_p.h b/src/quick/items/qquicktextinput_p_p.h index 01d90f6b10..2360bf99eb 100644 --- a/src/quick/items/qquicktextinput_p_p.h +++ b/src/quick/items/qquicktextinput_p_p.h @@ -107,6 +107,7 @@ public: , canRedo(false) , hAlignImplicit(true) , selectPressed(false) + , hadSelectionOnMousePress(false) , textLayoutDirty(true) , persistentSelection(false) , hasImState(false) @@ -124,6 +125,7 @@ public: , inLayout(false) , requireImplicitWidth(false) , overwriteMode(false) + , containsUnscalableGlyphs(false) #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) , selectByTouchDrag(false) #endif @@ -259,6 +261,7 @@ public: bool canRedo:1; bool hAlignImplicit:1; bool selectPressed:1; + bool hadSelectionOnMousePress:1; bool textLayoutDirty:1; bool persistentSelection:1; bool hasImState : 1; @@ -276,6 +279,7 @@ public: bool inLayout:1; bool requireImplicitWidth:1; bool overwriteMode:1; + bool containsUnscalableGlyphs:1; #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) bool selectByTouchDrag:1; #endif diff --git a/src/quick/items/qquicktextnode.cpp b/src/quick/items/qquicktextnode.cpp index 2024470265..0a56cae652 100644 --- a/src/quick/items/qquicktextnode.cpp +++ b/src/quick/items/qquicktextnode.cpp @@ -62,6 +62,8 @@ QSGGlyphNode *QQuickTextNode::addGlyphs(const QPointF &position, const QGlyphRun } } + m_containsUnscalableGlyphs = m_containsUnscalableGlyphs || preferNativeGlyphNode; + QSGGlyphNode *node = sg->sceneGraphContext()->createGlyphNode(sg, preferNativeGlyphNode, m_renderTypeQuality); node->setOwnerElement(m_ownerElement); diff --git a/src/quick/items/qquicktextnode_p.h b/src/quick/items/qquicktextnode_p.h index 0538336311..3098c86bb2 100644 --- a/src/quick/items/qquicktextnode_p.h +++ b/src/quick/items/qquicktextnode_p.h @@ -75,6 +75,11 @@ public: void setRenderTypeQuality(int renderTypeQuality) { m_renderTypeQuality = renderTypeQuality; } int renderTypeQuality() const { return m_renderTypeQuality; } + bool containsUnscalableGlyphs() const + { + return m_containsUnscalableGlyphs; + } + QPair<int, int> renderedLineRange() const { return { m_firstLineInViewport, m_firstLinePastViewport }; } private: @@ -85,6 +90,7 @@ private: int m_renderTypeQuality; int m_firstLineInViewport = -1; int m_firstLinePastViewport = -1; + bool m_containsUnscalableGlyphs = false; friend class QQuickTextEdit; friend class QQuickTextEditPrivate; diff --git a/src/quick/items/qquicktextnodeengine.cpp b/src/quick/items/qquicktextnodeengine.cpp index 6463628879..5aac0e3260 100644 --- a/src/quick/items/qquicktextnodeengine.cpp +++ b/src/quick/items/qquicktextnodeengine.cpp @@ -143,6 +143,8 @@ int QQuickTextNodeEngine::addText(const QTextBlock &block, while (textPos < fragmentEnd) { int blockRelativePosition = textPos - block.position(); QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition); + if (!line.isValid()) + break; if (!currentLine().isValid() || line.lineNumber() != currentLine().lineNumber()) { setCurrentLine(line); diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 4606c1231e..9de90f8d82 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -42,6 +42,7 @@ #include <private/qqmldebugserviceinterfaces_p.h> #include <private/qqmldebugconnector_p.h> #include <private/qsgdefaultrendercontext_p.h> +#include <private/qsgsoftwarerenderer_p.h> #if QT_CONFIG(opengl) #include <private/qopengl_p.h> #include <QOpenGLContext> @@ -286,37 +287,31 @@ struct PolishLoopDetector if (itemsToPolish.size() > itemsRemainingBeforeUpdatePolish) { // Detected potential polish loop. ++numPolishLoopsInSequence; - if (numPolishLoopsInSequence >= 1000) { + if (numPolishLoopsInSequence == 10000) { + // We have looped 10,000 times without actually reducing the list of items to + // polish, give up for now. + // This is not a fix, just a remedy so that the application can be somewhat + // responsive. + numPolishLoopsInSequence = 0; + return true; + } + if (numPolishLoopsInSequence >= 1000 && numPolishLoopsInSequence < 1005) { // Start to warn about polish loop after 1000 consecutive polish loops - if (numPolishLoopsInSequence == 100000) { - // We have looped 100,000 times without actually reducing the list of items to - // polish, give up for now. - // This is not a fix, just a remedy so that the application can be somewhat - // responsive. - numPolishLoopsInSequence = 0; - return true; - } else if (numPolishLoopsInSequence < 1005) { - // Show the 5 next items involved in the polish loop. - // (most likely they will be the same 5 items...) - QQuickItem *guiltyItem = itemsToPolish.last(); - qmlWarning(item) << "possible QQuickItem::polish() loop"; - - auto typeAndObjectName = [](QQuickItem *item) { - QString typeName = QQmlMetaType::prettyTypeName(item); - QString objName = item->objectName(); - if (!objName.isNull()) - return QLatin1String("%1(%2)").arg(typeName, objName); - return typeName; - }; - - qmlWarning(guiltyItem) << typeAndObjectName(guiltyItem) - << " called polish() inside updatePolish() of " << typeAndObjectName(item); - - if (numPolishLoopsInSequence == 1004) - // Enough warnings. Reset counter in order to speed things up and re-detect - // more loops - numPolishLoopsInSequence = 0; - } + // Show the 5 next items involved in the polish loop. + // (most likely they will be the same 5 items...) + QQuickItem *guiltyItem = itemsToPolish.last(); + qmlWarning(item) << "possible QQuickItem::polish() loop"; + + auto typeAndObjectName = [](QQuickItem *item) { + QString typeName = QQmlMetaType::prettyTypeName(item); + QString objName = item->objectName(); + if (!objName.isNull()) + return QLatin1String("%1(%2)").arg(typeName, objName); + return typeName; + }; + + qmlWarning(guiltyItem) << typeAndObjectName(guiltyItem) + << " called polish() inside updatePolish() of " << typeAndObjectName(item); } } else { numPolishLoopsInSequence = 0; @@ -519,6 +514,7 @@ void QQuickWindowPrivate::syncSceneGraph() { Q_Q(QQuickWindow); + const bool wasRtDirty = redirect.renderTargetDirty; ensureCustomRenderTarget(); QRhiCommandBuffer *cb = nullptr; @@ -540,7 +536,7 @@ void QQuickWindowPrivate::syncSceneGraph() invalidateFontData(contentItem); } - if (!renderer) { + if (Q_UNLIKELY(!renderer)) { forceUpdate(contentItem); QSGRootNode *rootNode = new QSGRootNode; @@ -550,6 +546,10 @@ void QQuickWindowPrivate::syncSceneGraph() : QSGRendererInterface::RenderMode2DNoDepthBuffer; renderer = context->createRenderer(renderMode); renderer->setRootNode(rootNode); + } else if (Q_UNLIKELY(wasRtDirty) + && q->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) { + auto softwareRenderer = static_cast<QSGSoftwareRenderer *>(renderer); + softwareRenderer->markDirty(); } updateDirtyNodes(); @@ -914,6 +914,22 @@ void QQuickWindowPrivate::cleanup(QSGNode *n) // The confirmExitPopup allows user to save or discard the document, // or to cancel the closing. \endcode + + \section1 Styling + + As with all visual types in Qt Quick, Window supports + \l {palette}{palettes}. However, as with types like \l Text, Window does + not use palettes by default. For example, to change the background color + of the window when the operating system's theme changes, the \l color must + be set: + + \snippet qml/windowPalette.qml declaration-and-color + \codeline + \snippet qml/windowPalette.qml text-item + \snippet qml/windowPalette.qml closing-brace + + Use \l {ApplicationWindow} (and \l {Label}) from \l {Qt Quick Controls} + instead of Window to get automatic styling. */ /*! diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp index ba9952e19d..09eae137bb 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp @@ -375,21 +375,7 @@ void QSGSoftwareInternalImageNode::setVerticalWrapMode(QSGTexture::WrapMode wrap void QSGSoftwareInternalImageNode::update() { - if (m_cachedMirroredPixmapIsDirty) { - if (m_mirrorHorizontally || m_mirrorVertically || m_textureIsLayer) { - QTransform transform( - (m_mirrorHorizontally ? -1 : 1), 0, - 0 , (m_textureIsLayer ? -1 : 1) * (m_mirrorVertically ? -1 : 1), - 0 , 0 - ); - m_cachedMirroredPixmap = pixmap().transformed(transform); - } else { - //Cleanup cached pixmap if necessary - if (!m_cachedMirroredPixmap.isNull()) - m_cachedMirroredPixmap = QPixmap(); - } - m_cachedMirroredPixmapIsDirty = false; - } + updateCachedMirroredPixmap(); } void QSGSoftwareInternalImageNode::preprocess() @@ -423,6 +409,7 @@ void QSGSoftwareInternalImageNode::paint(QPainter *painter) // Disable antialiased clipping. It causes transformed tiles to have gaps. painter->setRenderHint(QPainter::Antialiasing, false); + updateCachedMirroredPixmap(); const QPixmap &pm = m_mirrorHorizontally || m_mirrorVertically || m_textureIsLayer ? m_cachedMirroredPixmap : pixmap(); if (m_innerTargetRect != m_targetRect) { @@ -468,4 +455,23 @@ const QPixmap &QSGSoftwareInternalImageNode::pixmap() const return nullPixmap; } +void QSGSoftwareInternalImageNode::updateCachedMirroredPixmap() +{ + if (m_cachedMirroredPixmapIsDirty) { + if (m_mirrorHorizontally || m_mirrorVertically || m_textureIsLayer) { + QTransform transform( + (m_mirrorHorizontally ? -1 : 1), 0, + 0 , (m_textureIsLayer ? -1 : 1) * (m_mirrorVertically ? -1 : 1), + 0 , 0 + ); + m_cachedMirroredPixmap = pixmap().transformed(transform); + } else { + //Cleanup cached pixmap if necessary + if (!m_cachedMirroredPixmap.isNull()) + m_cachedMirroredPixmap = QPixmap(); + } + m_cachedMirroredPixmapIsDirty = false; + } +} + QT_END_NAMESPACE diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode_p.h index bfbe8420d2..5b5de86708 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode_p.h +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode_p.h @@ -92,7 +92,7 @@ public: const QPixmap &pixmap() const; private: - + void updateCachedMirroredPixmap(); QRectF m_targetRect; QRectF m_innerTargetRect; QRectF m_innerSourceRect; diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp index d1394ee3b1..3d5a6c7705 100644 --- a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp +++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp @@ -922,7 +922,14 @@ Renderer::~Renderer() Element *e = n->element(); if (!e->removed) m_elementsToDelete.add(e); + } else if (n->type() == QSGNode::ClipNodeType) { + delete n->clipInfo(); + } else if (n->type() == QSGNode::RenderNodeType) { + RenderNodeElement *e = n->renderNodeElement(); + if (!e->removed) + m_elementsToDelete.add(e); } + m_nodeAllocator.release(n); } @@ -1681,13 +1688,19 @@ void Renderer::prepareOpaqueBatches() QSGGeometryNode *gnj = ej->node; + const QSGGeometry *gniGeometry = gni->geometry(); + const QSGMaterial *gniMaterial = gni->activeMaterial(); + const QSGGeometry *gnjGeometry = gnj->geometry(); + const QSGMaterial *gnjMaterial = gnj->activeMaterial(); if (gni->clipList() == gnj->clipList() - && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode() - && (gni->geometry()->drawingMode() != QSGGeometry::DrawLines || gni->geometry()->lineWidth() == gnj->geometry()->lineWidth()) - && gni->geometry()->attributes() == gnj->geometry()->attributes() + && gniGeometry->drawingMode() == gnjGeometry->drawingMode() + && (gniGeometry->drawingMode() != QSGGeometry::DrawLines || gniGeometry->lineWidth() == gnjGeometry->lineWidth()) + && gniGeometry->attributes() == gnjGeometry->attributes() + && gniGeometry->indexType() == gnjGeometry->indexType() && gni->inheritedOpacity() == gnj->inheritedOpacity() - && gni->activeMaterial()->type() == gnj->activeMaterial()->type() - && gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) { + && gniMaterial->type() == gnjMaterial->type() + && gniMaterial->compare(gnjMaterial) == 0) + { ej->batch = batch; next->nextInBatch = ej; next = ej; @@ -1788,17 +1801,23 @@ void Renderer::prepareAlphaBatches() if (gnj->geometry()->vertexCount() == 0) continue; + const QSGGeometry *gniGeometry = gni->geometry(); + const QSGMaterial *gniMaterial = gni->activeMaterial(); + const QSGGeometry *gnjGeometry = gnj->geometry(); + const QSGMaterial *gnjMaterial = gnj->activeMaterial(); if (gni->clipList() == gnj->clipList() - && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode() - && (gni->geometry()->drawingMode() != QSGGeometry::DrawLines - || (gni->geometry()->lineWidth() == gnj->geometry()->lineWidth() + && gniGeometry->drawingMode() == gnjGeometry->drawingMode() + && (gniGeometry->drawingMode() != QSGGeometry::DrawLines + || (gniGeometry->lineWidth() == gnjGeometry->lineWidth() // Must not do overlap checks when the line width is not 1, // we have no knowledge how such lines are rasterized. - && gni->geometry()->lineWidth() == 1.0f)) - && gni->geometry()->attributes() == gnj->geometry()->attributes() + && gniGeometry->lineWidth() == 1.0f)) + && gniGeometry->attributes() == gnjGeometry->attributes() + && gniGeometry->indexType() == gnjGeometry->indexType() && gni->inheritedOpacity() == gnj->inheritedOpacity() - && gni->activeMaterial()->type() == gnj->activeMaterial()->type() - && gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) { + && gniMaterial->type() == gnjMaterial->type() + && gniMaterial->compare(gnjMaterial) == 0) + { if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) { ej->batch = batch; next->nextInBatch = ej; @@ -2001,7 +2020,7 @@ void Renderer::uploadBatch(Batch *b) bool canMerge = (g->drawingMode() == QSGGeometry::DrawTriangles || g->drawingMode() == QSGGeometry::DrawTriangleStrip || g->drawingMode() == QSGGeometry::DrawLines || g->drawingMode() == QSGGeometry::DrawPoints) && b->positionAttribute >= 0 - && (g->indexType() == QSGGeometry::UnsignedShortType && g->indexCount() > 0) + && g->indexType() == QSGGeometry::UnsignedShortType && (flags & (QSGMaterial::NoBatching | QSGMaterial_FullMatrix)) == 0 && ((flags & QSGMaterial::RequiresFullMatrixExceptTranslate) == 0 || b->isTranslateOnlyToRoot()) && b->isSafeToBatch(); @@ -2014,6 +2033,8 @@ void Renderer::uploadBatch(Batch *b) int unmergedIndexSize = 0; Element *e = b->first; + // Merged batches always do indexed draw calls. Non-indexed geometry gets + // indices generated automatically, when merged. while (e) { QSGGeometry *eg = e->node->geometry(); b->vertexCount += eg->vertexCount(); @@ -2829,7 +2850,8 @@ void Renderer::updateMaterialDynamicData(ShaderManager::Shader *sms, const Batch *batch, Element *e, int ubufOffset, - int ubufRegionSize) + int ubufRegionSize, + char *directUpdatePtr) { m_current_resource_update_batch = m_resourceUpdates; @@ -2842,8 +2864,12 @@ void Renderer::updateMaterialDynamicData(ShaderManager::Shader *sms, const bool changed = shader->updateUniformData(renderState, material, m_currentMaterial); m_current_uniform_data = nullptr; - if (changed || !batch->ubufDataValid) - m_resourceUpdates->updateDynamicBuffer(batch->ubuf, ubufOffset, ubufRegionSize, pd->masterUniformData.constData()); + if (changed || !batch->ubufDataValid) { + if (directUpdatePtr) + memcpy(directUpdatePtr + ubufOffset, pd->masterUniformData.constData(), ubufRegionSize); + else + m_resourceUpdates->updateDynamicBuffer(batch->ubuf, ubufOffset, ubufRegionSize, pd->masterUniformData.constData()); + } bindings.append(QRhiShaderResourceBinding::uniformBuffer(pd->ubufBinding, pd->ubufStages, @@ -2857,7 +2883,7 @@ void Renderer::updateMaterialDynamicData(ShaderManager::Shader *sms, if (!stages) continue; - QVarLengthArray<QSGTexture *, 4> prevTex = pd->textureBindingTable[binding]; + const QVarLengthArray<QSGTexture *, 4> &prevTex(pd->textureBindingTable[binding]); QVarLengthArray<QSGTexture *, 4> nextTex = prevTex; const int count = pd->combinedImageSamplerCount[binding]; @@ -3138,7 +3164,14 @@ bool Renderer::prepareRenderMergedBatch(Batch *batch, PreparedRenderBatch *rende bool pendingGStatePop = false; updateMaterialStaticData(sms, renderState, material, batch, &pendingGStatePop); - updateMaterialDynamicData(sms, renderState, material, batch, e, 0, ubufSize); + char *directUpdatePtr = nullptr; + if (batch->ubuf->nativeBuffer().slotCount == 0) + directUpdatePtr = batch->ubuf->beginFullDynamicBufferUpdateForCurrentFrame(); + + updateMaterialDynamicData(sms, renderState, material, batch, e, 0, ubufSize, directUpdatePtr); + + if (directUpdatePtr) + batch->ubuf->endFullDynamicBufferUpdateForCurrentFrame(); #ifndef QT_NO_DEBUG if (qsg_test_and_clear_material_failure()) { @@ -3327,6 +3360,11 @@ bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *ren QRhiGraphicsPipeline *ps = nullptr; QRhiGraphicsPipeline *depthPostPassPs = nullptr; e = batch->first; + + char *directUpdatePtr = nullptr; + if (batch->ubuf->nativeBuffer().slotCount == 0) + directUpdatePtr = batch->ubuf->beginFullDynamicBufferUpdateForCurrentFrame(); + while (e) { gn = e->node; @@ -3341,7 +3379,7 @@ bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *ren } QSGMaterialShader::RenderState renderState = state(QSGMaterialShader::RenderState::DirtyStates(int(dirty))); - updateMaterialDynamicData(sms, renderState, material, batch, e, ubufOffset, ubufSize); + updateMaterialDynamicData(sms, renderState, material, batch, e, ubufOffset, ubufSize, directUpdatePtr); #ifndef QT_NO_DEBUG if (qsg_test_and_clear_material_failure()) { @@ -3393,6 +3431,9 @@ bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *ren e = e->nextInBatch; } + if (directUpdatePtr) + batch->ubuf->endFullDynamicBufferUpdateForCurrentFrame(); + if (pendingGStatePop) m_gstate = m_gstateStack.pop(); @@ -3802,7 +3843,10 @@ void Renderer::beginRenderPass(RenderPassContext *) // we have no choice but to set the flag always // (thus triggering using secondary command // buffers with Vulkan) - QRhiCommandBuffer::ExternalContent); + QRhiCommandBuffer::ExternalContent + // We do not use GPU compute at all at the moment, this means we can + // get a small performance gain with OpenGL by declaring this. + | QRhiCommandBuffer::DoNotTrackResourcesForCompute); if (m_renderPassRecordingCallbacks.start) m_renderPassRecordingCallbacks.start(m_renderPassRecordingCallbacks.userData); diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h b/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h index 9a024804a7..c0301d1106 100644 --- a/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h +++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h @@ -798,7 +798,8 @@ private: bool ensurePipelineState(Element *e, const ShaderManager::Shader *sms, bool depthPostPass = false); QRhiTexture *dummyTexture(); void updateMaterialDynamicData(ShaderManager::Shader *sms, QSGMaterialShader::RenderState &renderState, - QSGMaterial *material, const Batch *batch, Element *e, int ubufOffset, int ubufRegionSize); + QSGMaterial *material, const Batch *batch, Element *e, int ubufOffset, int ubufRegionSize, + char *directUpdatePtr); void updateMaterialStaticData(ShaderManager::Shader *sms, QSGMaterialShader::RenderState &renderState, QSGMaterial *material, Batch *batch, bool *gstateChanged); void checkLineWidth(QSGGeometry *g); diff --git a/src/quick/scenegraph/qsgadaptationlayer.cpp b/src/quick/scenegraph/qsgadaptationlayer.cpp index a9a12a720a..ba85eaf1ef 100644 --- a/src/quick/scenegraph/qsgadaptationlayer.cpp +++ b/src/quick/scenegraph/qsgadaptationlayer.cpp @@ -166,7 +166,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/qsgrhishadereffectnode.cpp b/src/quick/scenegraph/qsgrhishadereffectnode.cpp index 9f634067d5..f1db5804e6 100644 --- a/src/quick/scenegraph/qsgrhishadereffectnode.cpp +++ b/src/quick/scenegraph/qsgrhishadereffectnode.cpp @@ -188,7 +188,7 @@ struct QSGRhiShaderMaterialTypeCache QSGMaterialType *type; }; QHash<Key, MaterialType> m_types; - QVector<QSGMaterialType *> m_graveyard; + QHash<Key, QSGMaterialType *> m_graveyard; }; size_t qHash(const QSGRhiShaderMaterialTypeCache::Key &key, size_t seed = 0) @@ -208,6 +208,14 @@ QSGMaterialType *QSGRhiShaderMaterialTypeCache::ref(const QShader &vs, const QSh return it->type; } + auto reuseIt = m_graveyard.constFind(k); + if (reuseIt != m_graveyard.cend()) { + QSGMaterialType *t = reuseIt.value(); + m_types.insert(k, { 1, t }); + m_graveyard.erase(reuseIt); + return t; + } + QSGMaterialType *t = new QSGMaterialType; m_types.insert(k, { 1, t }); return t; @@ -220,7 +228,7 @@ void QSGRhiShaderMaterialTypeCache::unref(const QShader &vs, const QShader &fs) auto it = m_types.find(k); if (it != m_types.end()) { if (!--it->ref) { - m_graveyard.append(it->type); + m_graveyard.insert(k, it->type); m_types.erase(it); } } diff --git a/src/quick/scenegraph/util/qsgrhiatlastexture.cpp b/src/quick/scenegraph/util/qsgrhiatlastexture.cpp index 246335ef69..cdb8509dd2 100644 --- a/src/quick/scenegraph/util/qsgrhiatlastexture.cpp +++ b/src/quick/scenegraph/util/qsgrhiatlastexture.cpp @@ -160,9 +160,20 @@ void AtlasBase::remove(TextureBase *t) Atlas::Atlas(QSGDefaultRenderContext *rc, const QSize &size) : AtlasBase(rc, size) { - // use RGBA texture internally as that is the only one guaranteed to be always supported m_format = QRhiTexture::RGBA8; + // Mirror QSGPlainTexture by playing nice with ARGB32[_Pre], because due to + // legacy that's what most images come in, not the byte-ordered + // RGBA8888[_Pre]. (i.e. with this the behavior matches 5.15) However, + // QSGPlainTexture can make a separate decision for each image (texture), + // the atlas cannot, so the downside is that now images that come in the + // modern byte-ordered formats need a conversion. So perhaps reconsider this + // at some point in the future. +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + if (rc->rhi()->isTextureFormatSupported(QRhiTexture::BGRA8)) + m_format = QRhiTexture::BGRA8; +#endif + m_debug_overlay = qt_sg_envInt("QSG_ATLAS_OVERLAY", 0); // images smaller than this will retain their QImage. @@ -211,8 +222,12 @@ void Atlas::enqueueTextureUpload(TextureBase *t, QRhiResourceUpdateBatch *resour if (image.isNull()) return; - if (image.format() != QImage::Format_RGBA8888_Premultiplied) + if (m_format == QRhiTexture::BGRA8) { + if (image.format() != QImage::Format_RGB32 && image.format() != QImage::Format_ARGB32_Premultiplied) + image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied); + } else if (image.format() != QImage::Format_RGBA8888_Premultiplied) { image = std::move(image).convertToFormat(QImage::Format_RGBA8888_Premultiplied); + } if (m_debug_overlay) { QPainter p(&image); diff --git a/src/quick/util/qquickanimation.cpp b/src/quick/util/qquickanimation.cpp index 548ec8415a..7cbd3ea741 100644 --- a/src/quick/util/qquickanimation.cpp +++ b/src/quick/util/qquickanimation.cpp @@ -130,7 +130,8 @@ void QQuickAbstractAnimationPrivate::commence() QQmlProperties properties; auto *newInstance = q->transition(actions, properties, QQuickAbstractAnimation::Forward); - Q_ASSERT(newInstance != animationInstance); + // transition can return a nullptr; that's the only allowed case were old and new have the same value + Q_ASSERT(newInstance != animationInstance || !newInstance); delete animationInstance; animationInstance = newInstance; @@ -290,6 +291,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/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp index 2ae2bd1a02..6fc50aaa5c 100644 --- a/src/quick/util/qquickdeliveryagent.cpp +++ b/src/quick/util/qquickdeliveryagent.cpp @@ -70,32 +70,56 @@ void QQuickDeliveryAgentPrivate::touchToMouseEvent(QEvent::Type type, const QEve qWarning() << "Unexpected: synthesized an indistinguishable mouse event" << mouseEvent; } -bool QQuickDeliveryAgentPrivate::checkIfDoubleTapped(ulong newPressEventTimestamp, QPoint newPressPos) +/*! + Returns \c false if the time constraint for detecting a double-click is violated. +*/ +bool QQuickDeliveryAgentPrivate::isWithinDoubleClickInterval(ulong timeInterval) { - bool doubleClicked = false; + return timeInterval < static_cast<ulong>(QGuiApplication::styleHints()->mouseDoubleClickInterval()); +} - if (touchMousePressTimestamp > 0) { - QPoint distanceBetweenPresses = newPressPos - touchMousePressPos; - const int doubleTapDistance = QGuiApplication::styleHints()->touchDoubleTapDistance(); - doubleClicked = (qAbs(distanceBetweenPresses.x()) <= doubleTapDistance) && (qAbs(distanceBetweenPresses.y()) <= doubleTapDistance); +/*! + Returns \c false if the spatial constraint for detecting a touchscreen double-tap is violated. +*/ +bool QQuickDeliveryAgentPrivate::isWithinDoubleTapDistance(const QPoint &distanceBetweenPresses) +{ + auto square = [](qint64 v) { return v * v; }; + return square(distanceBetweenPresses.x()) + square(distanceBetweenPresses.y()) < + square(QGuiApplication::styleHints()->touchDoubleTapDistance()); +} - if (doubleClicked) { - ulong timeBetweenPresses = newPressEventTimestamp - touchMousePressTimestamp; - ulong doubleClickInterval = static_cast<ulong>(QGuiApplication::styleHints()-> - mouseDoubleClickInterval()); - doubleClicked = timeBetweenPresses < doubleClickInterval; - } - } +bool QQuickDeliveryAgentPrivate::checkIfDoubleTapped(ulong newPressEventTimestamp, const QPoint &newPressPos) +{ + const bool doubleClicked = isDeliveringTouchAsMouse() && + isWithinDoubleTapDistance(newPressPos - touchMousePressPos) && + isWithinDoubleClickInterval(newPressEventTimestamp - touchMousePressTimestamp); if (doubleClicked) { touchMousePressTimestamp = 0; } else { touchMousePressTimestamp = newPressEventTimestamp; touchMousePressPos = newPressPos; } - return doubleClicked; } +void QQuickDeliveryAgentPrivate::resetIfDoubleTapPrevented(const QEventPoint &pressedPoint) +{ + if (touchMousePressTimestamp > 0 && + (!isWithinDoubleTapDistance(pressedPoint.globalPosition().toPoint() - touchMousePressPos) || + !isWithinDoubleClickInterval(pressedPoint.timestamp() - touchMousePressTimestamp))) { + touchMousePressTimestamp = 0; + touchMousePressPos = QPoint(); + } +} + +/*! \internal + \deprecated events are handled by methods in which the event is an argument + + Accessor for use by legacy methods such as QQuickItem::grabMouse(), + QQuickItem::ungrabMouse(), and QQuickItem::grabTouchPoints() which + are not given sufficient context to do the grabbing. + We should remove eventsInDelivery in Qt 7. +*/ QPointerEvent *QQuickDeliveryAgentPrivate::eventInDelivery() const { if (eventsInDelivery.isEmpty()) @@ -186,9 +210,7 @@ bool QQuickDeliveryAgentPrivate::deliverTouchAsMouse(QQuickItem *item, QTouchEve } else if (touchMouseDevice == device && p.id() == touchMouseId) { if (p.state() & QEventPoint::State::Updated) { if (touchMousePressTimestamp != 0) { - const int doubleTapDistance = QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::TouchDoubleTapDistance).toInt(); - const QPoint moveDelta = p.globalPosition().toPoint() - touchMousePressPos; - if (moveDelta.x() >= doubleTapDistance || moveDelta.y() >= doubleTapDistance) + if (!isWithinDoubleTapDistance(p.globalPosition().toPoint() - touchMousePressPos)) touchMousePressTimestamp = 0; // Got dragged too far, dismiss the double tap } if (QQuickItem *mouseGrabberItem = qmlobject_cast<QQuickItem *>(pointerEvent->exclusiveGrabber(p))) { @@ -2124,6 +2146,11 @@ bool QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent(QPointerEvent *event } for (int i = 0; i < event->pointCount(); ++i) { auto &point = event->point(i); + // Regardless whether a touchpoint could later result in a synth-mouse event: + // if the double-tap time or space constraint has been violated, + // reset state to prevent a double-click event. + if (isTouch && point.state() == QEventPoint::Pressed) + resetIfDoubleTapPrevented(point); QVector<QQuickItem *> targetItemsForPoint = pointerTargets(rootItem, event, point, !isTouch, isTouch); if (targetItems.size()) { targetItems = mergePointerTargets(targetItems, targetItemsForPoint); diff --git a/src/quick/util/qquickdeliveryagent_p_p.h b/src/quick/util/qquickdeliveryagent_p_p.h index 7852ad4673..0fce213291 100644 --- a/src/quick/util/qquickdeliveryagent_p_p.h +++ b/src/quick/util/qquickdeliveryagent_p_p.h @@ -107,7 +107,8 @@ public: bool isDeliveringTouchAsMouse() const { return touchMouseId != -1 && touchMouseDevice; } void cancelTouchMouseSynthesis(); - bool checkIfDoubleTapped(ulong newPressEventTimestamp, QPoint newPressPos); + bool checkIfDoubleTapped(ulong newPressEventTimestamp, const QPoint &newPressPos); + void resetIfDoubleTapPrevented(const QEventPoint &pressedPoint); QPointingDevicePrivate::EventPointData *mousePointData(); QPointerEvent *eventInDelivery() const; @@ -146,6 +147,8 @@ public: static bool isTabletEvent(const QPointerEvent *ev); static bool isEventFromMouseOrTouchpad(const QPointerEvent *ev); static bool isSynthMouse(const QPointerEvent *ev); + static bool isWithinDoubleClickInterval(ulong timeInterval); + static bool isWithinDoubleTapDistance(const QPoint &distanceBetweenPresses); static QQuickPointingDeviceExtra *deviceExtra(const QInputDevice *device); // delivery of pointer events: diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp index f0d9959d87..0cf24bfba2 100644 --- a/src/quick/util/qquickpixmapcache.cpp +++ b/src/quick/util/qquickpixmapcache.cpp @@ -528,7 +528,6 @@ QQuickPixmapReader::~QQuickPixmapReader() delete reply; } jobs.clear(); -#if QT_CONFIG(qml_network) const auto cancelJob = [this](QQuickPixmapReply *reply) { if (reply->loading) { @@ -537,12 +536,13 @@ QQuickPixmapReader::~QQuickPixmapReader() } }; +#if QT_CONFIG(qml_network) for (auto *reply : std::as_const(networkJobs)) cancelJob(reply); +#endif for (auto *reply : std::as_const(asyncResponses)) cancelJob(reply); -#endif if (threadObject()) threadObject()->processJobs(); mutex.unlock(); @@ -550,7 +550,6 @@ QQuickPixmapReader::~QQuickPixmapReader() eventLoopQuitHack->deleteLater(); wait(); -#if QT_CONFIG(qml_network) // While we've been waiting, the other thread may have added // more replies. No one will care about them anymore. @@ -559,16 +558,17 @@ QQuickPixmapReader::~QQuickPixmapReader() reply->data->reply = nullptr; delete reply; }; - +#if QT_CONFIG(qml_network) for (QQuickPixmapReply *reply : std::as_const(networkJobs)) deleteReply(reply); - +#endif for (QQuickPixmapReply *reply : std::as_const(asyncResponses)) deleteReply(reply); +#if QT_CONFIG(qml_network) networkJobs.clear(); - asyncResponses.clear(); #endif + asyncResponses.clear(); } #if QT_CONFIG(qml_network) @@ -729,7 +729,9 @@ void QQuickPixmapReader::processJobs() // cancel any jobs already started reply->close(); } - } else { + } else +#endif + { QQuickImageResponse *asyncResponse = asyncResponses.key(job); if (asyncResponse) { asyncResponses.remove(asyncResponse); @@ -737,7 +739,6 @@ void QQuickPixmapReader::processJobs() } } PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(job->url)); -#endif // deleteLater, since not owned by this thread job->deleteLater(); } diff --git a/src/quickcontrols/basic/ComboBox.qml b/src/quickcontrols/basic/ComboBox.qml index 8cd148e9fe..4d8bbb372d 100644 --- a/src/quickcontrols/basic/ComboBox.qml +++ b/src/quickcontrols/basic/ComboBox.qml @@ -80,6 +80,7 @@ T.ComboBox { height: Math.min(contentItem.implicitHeight, control.Window.height - topMargin - bottomMargin) topMargin: 6 bottomMargin: 6 + palette: control.palette contentItem: ListView { clip: true diff --git a/src/quickcontrols/basic/Tumbler.qml b/src/quickcontrols/basic/Tumbler.qml index d3d1254fea..4bd5c5fed2 100644 --- a/src/quickcontrols/basic/Tumbler.qml +++ b/src/quickcontrols/basic/Tumbler.qml @@ -13,6 +13,8 @@ T.Tumbler { implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) + readonly property real __delegateHeight: availableHeight / visibleItemCount + delegate: Text { text: modelData color: control.visualFocus ? control.palette.highlight : control.palette.text @@ -35,13 +37,12 @@ T.Tumbler { delegate: control.delegate path: Path { startX: control.contentItem.width / 2 - startY: -control.contentItem.delegateHeight / 2 + startY: -control.__delegateHeight / 2 + PathLine { x: control.contentItem.width / 2 - y: (control.visibleItemCount + 1) * control.contentItem.delegateHeight - control.contentItem.delegateHeight / 2 + y: (control.visibleItemCount + 1) * control.__delegateHeight - control.__delegateHeight / 2 } } - - property real delegateHeight: control.availableHeight / control.visibleItemCount } } diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-busyindicator-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-busyindicator-custom.qml index 3eefb11110..6829a5267a 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-busyindicator-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-busyindicator-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic BusyIndicator { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-button-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-button-custom.qml index cf197a18ae..bdc95e615c 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-button-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-button-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic Button { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-checkbox-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-checkbox-custom.qml index a7421af39c..9b1f6ec76a 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-checkbox-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-checkbox-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic CheckBox { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-checkdelegate-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-checkdelegate-custom.qml index a4f4b84d18..de8441b4cf 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-checkdelegate-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-checkdelegate-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic CheckDelegate { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-combobox-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-combobox-custom.qml index 2481f6dcf8..960600eec8 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-combobox-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-combobox-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic ComboBox { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-delaybutton-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-delaybutton-custom.qml index a8b17ab36a..5c5f78ff12 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-delaybutton-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-delaybutton-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic DelayButton { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-dial-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-dial-custom.qml index 49982c1496..26138a2c29 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-dial-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-dial-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic Dial { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-frame-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-frame-custom.qml index de3e1a2327..da0139fa89 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-frame-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-frame-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic Frame { background: Rectangle { diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-groupbox-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-groupbox-custom.qml index 34d9df8a63..fac65ee0d8 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-groupbox-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-groupbox-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic GroupBox { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-headerview.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-headerview.qml index b8f6935010..1ede3fce69 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-headerview.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-headerview.qml @@ -16,7 +16,7 @@ ApplicationWindow { anchors.fill: parent // The background color will show through the cell // spacing, and therefore become the grid line color. - color: Qt.styleHints.appearance === Qt.Light ? palette.mid : palette.midlight + color: Application.styleHints.appearance === Qt.Light ? palette.mid : palette.midlight HorizontalHeaderView { id: horizontalHeader @@ -75,5 +75,5 @@ ApplicationWindow { } } } -//![0] } +//![0] diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-itemdelegate-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-itemdelegate-custom.qml index 3212cbc762..a3a2a5b2e3 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-itemdelegate-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-itemdelegate-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic ItemDelegate { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-itemdelegate.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-itemdelegate.qml index 49c12b1dff..84e174c79d 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-itemdelegate.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-itemdelegate.qml @@ -6,6 +6,7 @@ import QtQuick.Controls //! [1] ListView { + id: listView width: 160 height: 240 @@ -13,7 +14,7 @@ ListView { delegate: ItemDelegate { text: modelData - width: parent.width + width: listView.width onClicked: console.log("clicked:", modelData) required property string modelData diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-label-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-label-custom.qml index 79bc9d3c13..6f34ed4292 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-label-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-label-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic Label { text: qsTr("Label") diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-menu-createObject.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-menu-createObject.qml index 31991ced70..47a900a20a 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-menu-createObject.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-menu-createObject.qml @@ -29,7 +29,7 @@ Row { onClicked: { let menuItem = menuItemComponent.createObject( menu.contentItem, { text: qsTr("New item") }) - menu.addMenu(menuItem) + menu.addItem(menuItem) } } } diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-menu-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-menu-custom.qml index f38d5f370a..25cc379e47 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-menu-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-menu-custom.qml @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic ApplicationWindow { id: window diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-menubar-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-menubar-custom.qml index 2d2d15efdf..c4067f9211 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-menubar-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-menubar-custom.qml @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic ApplicationWindow { id: window diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-menuseparator-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-menuseparator-custom.qml index 2b66187e10..047f5e094d 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-menuseparator-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-menuseparator-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic Item { id: window diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-pageindicator-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-pageindicator-custom.qml index 5372c41f31..7783706f89 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-pageindicator-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-pageindicator-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic PageIndicator { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-pane-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-pane-custom.qml index 96e3db1f37..4a17ec8458 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-pane-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-pane-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic Pane { background: Rectangle { diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-popup-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-popup-custom.qml index 951dfa7d52..80513175c3 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-popup-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-popup-custom.qml @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic import QtQuick.Window Item { diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-progressbar-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-progressbar-custom.qml index a961debd44..61137ae22b 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-progressbar-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-progressbar-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic ProgressBar { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-radiobutton-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-radiobutton-custom.qml index 929fe7e8e2..b6bcac9196 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-radiobutton-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-radiobutton-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic RadioButton { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-radiodelegate-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-radiodelegate-custom.qml index 5eb856b8b5..9b7e25ff12 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-radiodelegate-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-radiodelegate-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic RadioDelegate { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-rangeslider-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-rangeslider-custom.qml index 81bcb0d668..df41d19db5 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-rangeslider-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-rangeslider-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic RangeSlider { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-scrollbar-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-scrollbar-custom.qml index 2a4faede61..15ec0d19e4 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-scrollbar-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-scrollbar-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic ScrollBar { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-scrollindicator-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-scrollindicator-custom.qml index f447fd4c94..dfc414cd86 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-scrollindicator-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-scrollindicator-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic ScrollIndicator { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-scrollview-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-scrollview-custom.qml index 2f23b9fd58..7f92f89245 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-scrollview-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-scrollview-custom.qml @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic Item { width: 200 diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-slider-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-slider-custom.qml index 66e0bd7990..107cfce174 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-slider-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-slider-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic Slider { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-spinbox-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-spinbox-custom.qml index b32106a5bd..f574cf3b32 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-spinbox-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-spinbox-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic SpinBox { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-splitview-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-splitview-custom.qml index 0d5452c25d..5aad185a97 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-splitview-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-splitview-custom.qml @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic Item { width: 200 diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-stackview-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-stackview-custom.qml index 381fbe6b95..ab8865481f 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-stackview-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-stackview-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic StackView { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-swipedelegate-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-swipedelegate-custom.qml index 973511c418..4571966031 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-swipedelegate-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-swipedelegate-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic SwipeDelegate { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-swipeview-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-swipeview-custom.qml index e77bc9a3e8..02aba3ee08 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-swipeview-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-swipeview-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic SwipeView { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-switch-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-switch-custom.qml index 5ca4b96140..3d1bab4c08 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-switch-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-switch-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic Switch { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-switchdelegate-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-switchdelegate-custom.qml index 13cc7b4402..67c864eb2f 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-switchdelegate-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-switchdelegate-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic SwitchDelegate { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-tabbar-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-tabbar-custom.qml index cdce972150..bed40c135b 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-tabbar-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-tabbar-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic TabBar { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-textarea-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-textarea-custom.qml index 39a94e13cc..5efe94692f 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-textarea-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-textarea-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic TextArea { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-textfield-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-textfield-custom.qml index c4d0c88c2a..e8bbc72f27 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-textfield-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-textfield-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic TextField { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-toolbar-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-toolbar-custom.qml index 2aabcbfbe3..edfcd21901 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-toolbar-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-toolbar-custom.qml @@ -3,7 +3,7 @@ import QtQuick import QtQuick.Layouts -import QtQuick.Controls +import QtQuick.Controls.Basic //! [file] ToolBar { diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-toolbutton-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-toolbutton-custom.qml index 6ec46b0adf..4efb01215b 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-toolbutton-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-toolbutton-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic ToolButton { id: control diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-toolseparator-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-toolseparator-custom.qml index 201e04b0e8..2ffb29ba7e 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-toolseparator-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-toolseparator-custom.qml @@ -4,7 +4,7 @@ import QtQuick import QtQuick.Layouts import QtQuick.Window -import QtQuick.Controls +import QtQuick.Controls.Basic //! [file] ToolBar { diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-tooltip-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-tooltip-custom.qml index 089e6d02b9..76f0f85acd 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-tooltip-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-tooltip-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic Item { ToolTip { diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-tooltip-pressandhold.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-tooltip-pressandhold.qml index e209ce9ce9..9857d3899a 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-tooltip-pressandhold.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-tooltip-pressandhold.qml @@ -9,7 +9,7 @@ Button { text: qsTr("Button") ToolTip.visible: pressed - ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval + ToolTip.delay: Application.styleHints.mousePressAndHoldInterval ToolTip.text: qsTr("This tool tip is shown after pressing and holding the button down.") } //! [1] diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-tumbler-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-tumbler-custom.qml index 3487e7316d..473d6a3e48 100644 --- a/src/quickcontrols/doc/snippets/qtquickcontrols-tumbler-custom.qml +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-tumbler-custom.qml @@ -3,7 +3,7 @@ //! [file] import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Basic Tumbler { id: control diff --git a/src/quickcontrols/doc/src/external-pages.qdoc b/src/quickcontrols/doc/src/external-pages.qdoc index 1a8a759134..9dc1c8a9a9 100644 --- a/src/quickcontrols/doc/src/external-pages.qdoc +++ b/src/quickcontrols/doc/src/external-pages.qdoc @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only /*! - \externalpage https://doc.qt.io/qt-5/qtquickcontrols-differences.html + \externalpage https://doc.qt.io/qt-5/qtquickcontrols2-differences.html \title Qt 5.15: Qt Quick Controls vs Qt Quick Controls 1 */ diff --git a/src/quickcontrols/doc/src/includes/qquickheaderview.qdocinc b/src/quickcontrols/doc/src/includes/qquickheaderview.qdocinc index 5115149762..3e22cddb58 100644 --- a/src/quickcontrols/doc/src/includes/qquickheaderview.qdocinc +++ b/src/quickcontrols/doc/src/includes/qquickheaderview.qdocinc @@ -2,18 +2,15 @@ A \1 provides a styled table header. It can either be used as an independent view or header for a \l TableView. -You can add a header for a TableView by assigning a \1 -to the \l {TableView::syncView} property. The header and the table will +You can add a header for a TableView by assigning the TableView to the +\l {TableView::syncView}{syncView} property of \1. The header and the table will then be kept in sync while flicking. -By default, \1 displays -\l {QAbstractItemModel::headerData()}{header data} +By default, \1 displays \l {QAbstractItemModel::headerData()}{header data} from the \l {TableView::syncView}{sync view's} \l {TableView::model}{model}. -If you don't wish to use this model, you can assign a different model to the -\l {TableView::model}{model} property. If you assign a model that is a -QAbstractItemModel, its header data will be used. Otherwise the data in -the model will be used directly (for example, if you assign a model that -is simply an array of strings). +If you don't wish to use header data from that model, or you don't use a +syncView, you can assign a model explicitly to the \l {TableView::model}{model} +property. \note In order to show the header data of a QAbstractItemModel, \1 will internally wrap the model's header data in an independent proxy @@ -55,16 +52,16 @@ model to label the columns. //! [model] This property holds the model providing data for the \1 header view. -When model is not explicitly set, the header will use the syncView's -model once syncView is set. - -If model is a QAbstractTableModel, its \1 headerData() will -be accessed. - -If model is a QAbstractItemModel other than QAbstractTableModel, model's data() -will be accessed. - -Otherwise, the behavior is same as setting TableView::model. +By default, \1 header view displays \l {QAbstractItemModel::headerData()}{header data} +from the \l {TableView::syncView}{sync view's} \l {TableView::model}{model}. +If you don't wish to use header data from that model, or you don't use a +syncView, you can assign a model explicitly to this property. If \a model +is a \l QAbstractTableModel, it's \l {QAbstractItemModel::headerData()}{header data} +will be used. Otherwise, if it's a \l QAbstractItemModel, +\l {QAbstractItemModel::data()}{data} will be used. + +In addition to \l {QAbstractItemModel}{QAbstractItemModels}, you can also assign other +kinds of models to this property, such as JavaScript arrays. \sa TableView {TableView::model} {model} QAbstractTableModel //! [model] diff --git a/src/quickcontrols/doc/src/qtquickcontrols-customize.qdoc b/src/quickcontrols/doc/src/qtquickcontrols-customize.qdoc index 83c22940c8..59a97baa0e 100644 --- a/src/quickcontrols/doc/src/qtquickcontrols-customize.qdoc +++ b/src/quickcontrols/doc/src/qtquickcontrols-customize.qdoc @@ -464,9 +464,9 @@ Next, we add a drop shadow to the \l {Control::}{background} delegate of the Button: - \code + \qml // ... - import QtGraphicalEffects + import QtQuick.Effects import MyStyle // ... @@ -474,14 +474,15 @@ // ... layer.enabled: control.enabled && control.MyStyle.elevation > 0 - layer.effect: DropShadow { - verticalOffset: 1 - color: control.visualFocus ? "#330066ff" : "#aaaaaa" - samples: control.MyStyle.elevation - spread: 0.5 + layer.effect: MultiEffect { + shadowEnabled: true + shadowHorizontalOffset: 3 + shadowVerticalOffset: 3 + shadowColor: control.visualFocus ? "#330066ff" : "#aaaaaa" + shadowBlur: control.pressed ? 0.8 : 0.4 } } - \endcode + \endqml Note that we: @@ -556,7 +557,7 @@ \code import QtQuick - import QtQuick.Controls + import QtQuick.Controls.Basic ApplicationWindow { visible: true @@ -752,7 +753,7 @@ \quotefromfile qtquickcontrols-menu-custom.qml \skipto import QtQuick - \printuntil import QtQuick.Controls + \printuntil import QtQuick.Controls.Basic \skipto Menu \printto eof @@ -767,7 +768,7 @@ \quotefromfile qtquickcontrols-menubar-custom.qml \skipto import QtQuick - \printuntil import QtQuick.Controls + \printuntil import QtQuick.Controls.Basic \skipto MenuBar \printto eof @@ -799,7 +800,7 @@ \quotefromfile qtquickcontrols-popup-custom.qml \skipto import QtQuick - \printuntil import QtQuick.Controls + \printuntil import QtQuick.Controls.Basic \codeline \skipto Popup \printuntil { @@ -1037,7 +1038,7 @@ \quotefromfile qtquickcontrols-tooltip-custom.qml \skipto import QtQuick - \printuntil import QtQuick.Controls + \printuntil import QtQuick.Controls.Basic \skipto ToolTip \printuntil } \printuntil } diff --git a/src/quickcontrols/doc/src/qtquickcontrols-styles.qdoc b/src/quickcontrols/doc/src/qtquickcontrols-styles.qdoc index 037ca15081..38174ab09a 100644 --- a/src/quickcontrols/doc/src/qtquickcontrols-styles.qdoc +++ b/src/quickcontrols/doc/src/qtquickcontrols-styles.qdoc @@ -131,20 +131,34 @@ import QtQuick.Controls \endqml - The QtQuick.Controls plugin will import the style and fallback - style that were set at runtime via one of the following approaches: + The QtQuick.Controls plugin will import the style that was set at runtime + via one of the following approaches: \list \li \l[CPP]{QQuickStyle::setStyle()} \li The \c -style command line argument - \li The \c QT_QUICK_CONTROLS_STYLE environment variable - \li The \c qtquickcontrols2.conf configuration file + \li The \l {Supported Environment Variables in Qt Quick Controls} + {QT_QUICK_CONTROLS_STYLE environment variable} + \li The \l {Qt Quick Controls Configuration File}{qtquickcontrols2.conf + configuration file} \endlist The priority of these approaches follows the order they are listed, from highest to lowest. That is, using \c QQuickStyle to set the style will always take priority over using the command line argument, for example. + Similarly, the fallback style can be set via one of the following methods: + \list + \li \l[CPP]{QQuickStyle::setFallbackStyle()} + \li The \l {Supported Environment Variables in Qt Quick Controls} + {QT_QUICK_CONTROLS_FALLBACK_STYLE environment variable} + \li The \l {Qt Quick Controls Configuration File}{qtquickcontrols2.conf + configuration file} + \endlist + + \note you can only dynamically choose the fallback style if it hasn't been + chosen statically in the main style's qmldir file. + The benefit of run-time style selection is that a single application binary can support multiple styles, meaning that the end user can choose which style to run the application with. diff --git a/src/quickcontrols/fusion/CheckBox.qml b/src/quickcontrols/fusion/CheckBox.qml index 414414804d..a996b67afe 100644 --- a/src/quickcontrols/fusion/CheckBox.qml +++ b/src/quickcontrols/fusion/CheckBox.qml @@ -22,6 +22,7 @@ T.CheckBox { indicator: CheckIndicator { x: control.text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2 y: control.topPadding + (control.availableHeight - height) / 2 + baseLightness: control.enabled ? 1.25 : 1.0 control: control } diff --git a/src/quickcontrols/fusion/ComboBox.qml b/src/quickcontrols/fusion/ComboBox.qml index 6caa7d3f94..d8d888dedc 100644 --- a/src/quickcontrols/fusion/ComboBox.qml +++ b/src/quickcontrols/fusion/ComboBox.qml @@ -111,6 +111,7 @@ T.ComboBox { topMargin: 6 bottomMargin: 6 padding: 1 + palette: control.palette contentItem: ListView { clip: true diff --git a/src/quickcontrols/fusion/Tumbler.qml b/src/quickcontrols/fusion/Tumbler.qml index 447765dce7..4a5ccd8348 100644 --- a/src/quickcontrols/fusion/Tumbler.qml +++ b/src/quickcontrols/fusion/Tumbler.qml @@ -15,6 +15,8 @@ T.Tumbler { implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) + readonly property real __delegateHeight: availableHeight / visibleItemCount + delegate: Text { text: modelData color: control.palette.windowText @@ -34,13 +36,11 @@ T.Tumbler { delegate: control.delegate path: Path { startX: control.contentItem.width / 2 - startY: -control.contentItem.delegateHeight / 2 + startY: -control.__delegateHeight / 2 PathLine { x: control.contentItem.width / 2 - y: (control.visibleItemCount + 1) * control.contentItem.delegateHeight - control.contentItem.delegateHeight / 2 + y: (control.visibleItemCount + 1) * control.__delegateHeight - control.__delegateHeight / 2 } } - - property real delegateHeight: control.availableHeight / control.visibleItemCount } } diff --git a/src/quickcontrols/fusion/impl/CheckIndicator.qml b/src/quickcontrols/fusion/impl/CheckIndicator.qml index 58de99654a..40e3471f10 100644 --- a/src/quickcontrols/fusion/impl/CheckIndicator.qml +++ b/src/quickcontrols/fusion/impl/CheckIndicator.qml @@ -10,13 +10,15 @@ Rectangle { id: indicator property Item control + property real baseLightness: 1.6 + readonly property color pressedColor: Fusion.mergedColors(control.palette.base, control.palette.windowText, 85) readonly property color checkMarkColor: Qt.darker(control.palette.text, 1.2) implicitWidth: 14 implicitHeight: 14 - color: control.down ? indicator.pressedColor : control.palette.base + color: control.down ? indicator.pressedColor : Qt.lighter(control.palette.base, baseLightness) border.color: control.visualFocus ? Fusion.highlightedOutline(control.palette) : Qt.lighter(Fusion.outline(control.palette), 1.1) diff --git a/src/quickcontrols/fusion/impl/RadioIndicator.qml b/src/quickcontrols/fusion/impl/RadioIndicator.qml index 0949b904a9..818b246953 100644 --- a/src/quickcontrols/fusion/impl/RadioIndicator.qml +++ b/src/quickcontrols/fusion/impl/RadioIndicator.qml @@ -17,7 +17,7 @@ Rectangle { implicitHeight: 14 radius: width / 2 - color: control.down ? indicator.pressedColor : control.palette.base + color: control.down ? indicator.pressedColor : Qt.lighter(control.palette.base, 1.75) border.color: control.visualFocus ? Fusion.highlightedOutline(control.palette) : Qt.darker(control.palette.window, 1.5) diff --git a/src/quickcontrols/imagine/Tumbler.qml b/src/quickcontrols/imagine/Tumbler.qml index 7052c6654c..f349168ccd 100644 --- a/src/quickcontrols/imagine/Tumbler.qml +++ b/src/quickcontrols/imagine/Tumbler.qml @@ -20,6 +20,8 @@ T.Tumbler { rightInset: background ? -background.rightInset || 0 : 0 bottomInset: background ? -background.bottomInset || 0 : 0 + readonly property real __delegateHeight: availableHeight / visibleItemCount + delegate: Text { text: modelData font: control.font @@ -39,10 +41,10 @@ T.Tumbler { delegate: control.delegate path: Path { startX: control.contentItem.width / 2 - startY: -control.contentItem.delegateHeight / 2 + startY: -control.__delegateHeight / 2 PathLine { x: control.contentItem.width / 2 - y: (control.visibleItemCount + 1) * control.contentItem.delegateHeight - control.contentItem.delegateHeight / 2 + y: (control.visibleItemCount + 1) * control.__delegateHeight - control.__delegateHeight / 2 } } diff --git a/src/quickcontrols/ios/BusyIndicator.qml b/src/quickcontrols/ios/BusyIndicator.qml index 3fb08070df..cb0a95b861 100644 --- a/src/quickcontrols/ios/BusyIndicator.qml +++ b/src/quickcontrols/ios/BusyIndicator.qml @@ -27,7 +27,7 @@ T.BusyIndicator { contentItem: Image { property int currentImage: 8 source: IOS.url + "busyindicator-frame-0" + currentImage + - (Qt.styleHints.colorScheme === Qt.Light ? "-light.png" : "-dark.png") + (Application.styleHints.colorScheme === Qt.Light ? "-light.png" : "-dark.png") fillMode: Image.PreserveAspectFit NumberAnimation on currentImage { running: control.visible && control.running diff --git a/src/quickcontrols/ios/CheckBox.qml b/src/quickcontrols/ios/CheckBox.qml index 59f1c8bce4..13c6819e38 100644 --- a/src/quickcontrols/ios/CheckBox.qml +++ b/src/quickcontrols/ios/CheckBox.qml @@ -27,8 +27,8 @@ T.CheckBox { states: [ {"checked": control.checkState === Qt.Checked}, {"partially-checked": control.checkState === Qt.PartiallyChecked}, - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } diff --git a/src/quickcontrols/ios/CheckDelegate.qml b/src/quickcontrols/ios/CheckDelegate.qml index b421430311..fd160220fb 100644 --- a/src/quickcontrols/ios/CheckDelegate.qml +++ b/src/quickcontrols/ios/CheckDelegate.qml @@ -32,8 +32,8 @@ T.CheckDelegate { states: [ {"checked": control.checkState === Qt.Checked}, {"partially-checked": control.checkState === Qt.PartiallyChecked}, - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } @@ -55,7 +55,7 @@ T.CheckDelegate { background: Rectangle { implicitHeight: 44 - color: Qt.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base + color: Application.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base NinePatchImage { property real offset: control.icon.source.toString() !== "" ? control.icon.width + control.spacing : 0 x: control.down ? 0 : control.leftPadding + offset @@ -65,8 +65,8 @@ T.CheckDelegate { source: IOS.url + "itemdelegate-background" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"pressed": control.down} ] } diff --git a/src/quickcontrols/ios/ComboBox.qml b/src/quickcontrols/ios/ComboBox.qml index e6136e34c0..d78552ec52 100644 --- a/src/quickcontrols/ios/ComboBox.qml +++ b/src/quickcontrols/ios/ComboBox.qml @@ -49,8 +49,8 @@ T.ComboBox { states: [ {"edge": isFirstItem || isLastItem }, {"single": isSingleItem}, - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"pressed": down} ] } @@ -69,8 +69,8 @@ T.ComboBox { : defaultColor ImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"pressed": control.down} ] } diff --git a/src/quickcontrols/ios/Dial.qml b/src/quickcontrols/ios/Dial.qml index fe21fdccc4..41ce35caf2 100644 --- a/src/quickcontrols/ios/Dial.qml +++ b/src/quickcontrols/ios/Dial.qml @@ -92,8 +92,8 @@ T.Dial { source: IOS.url + "slider-handle" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"disabled": !control.enabled} ] } diff --git a/src/quickcontrols/ios/Dialog.qml b/src/quickcontrols/ios/Dialog.qml index f8f400daf1..5cf877e1ad 100644 --- a/src/quickcontrols/ios/Dialog.qml +++ b/src/quickcontrols/ios/Dialog.qml @@ -39,8 +39,8 @@ T.Dialog { source: IOS.url + "popup-background" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } diff --git a/src/quickcontrols/ios/DialogButtonBox.qml b/src/quickcontrols/ios/DialogButtonBox.qml index 4dd5fec2a5..3aacb32e70 100644 --- a/src/quickcontrols/ios/DialogButtonBox.qml +++ b/src/quickcontrols/ios/DialogButtonBox.qml @@ -50,8 +50,8 @@ T.DialogButtonBox { NinePatchImageSelector on source { states: [ {"pressed": delegate.down}, - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, ] } } @@ -66,8 +66,8 @@ T.DialogButtonBox { source: IOS.url + "dialogbuttonbox-separator" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } diff --git a/src/quickcontrols/ios/Drawer.qml b/src/quickcontrols/ios/Drawer.qml index 2ee955e7bc..c3a8f4cbc5 100644 --- a/src/quickcontrols/ios/Drawer.qml +++ b/src/quickcontrols/ios/Drawer.qml @@ -32,8 +32,8 @@ T.Drawer { source: IOS.url + "drawer-background" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"modal": control.modal} ] } diff --git a/src/quickcontrols/ios/Frame.qml b/src/quickcontrols/ios/Frame.qml index 9179366808..066017d029 100644 --- a/src/quickcontrols/ios/Frame.qml +++ b/src/quickcontrols/ios/Frame.qml @@ -19,6 +19,6 @@ T.Frame { background: Rectangle { radius: 9 - color: Qt.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base + color: Application.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base } } diff --git a/src/quickcontrols/ios/GroupBox.qml b/src/quickcontrols/ios/GroupBox.qml index d3d14e7df8..79214b9933 100644 --- a/src/quickcontrols/ios/GroupBox.qml +++ b/src/quickcontrols/ios/GroupBox.qml @@ -36,6 +36,6 @@ T.GroupBox { width: parent.width height: parent.height - control.topPadding + control.bottomPadding radius: 9 - color: Qt.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base + color: Application.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base } } diff --git a/src/quickcontrols/ios/ItemDelegate.qml b/src/quickcontrols/ios/ItemDelegate.qml index 2b27c86b26..c2bdc1f8ce 100644 --- a/src/quickcontrols/ios/ItemDelegate.qml +++ b/src/quickcontrols/ios/ItemDelegate.qml @@ -36,7 +36,7 @@ T.ItemDelegate { background: Rectangle { implicitHeight: 44 - color: Qt.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base + color: Application.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base NinePatchImage { property real offset: control.icon.source.toString() !== "" ? control.icon.width + control.spacing : 0 x: control.down ? 0 : control.leftPadding + offset @@ -46,8 +46,8 @@ T.ItemDelegate { source: IOS.url + "itemdelegate-background" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"pressed": control.down} ] } diff --git a/src/quickcontrols/ios/Menu.qml b/src/quickcontrols/ios/Menu.qml index 752737f69b..7f65f1b239 100644 --- a/src/quickcontrols/ios/Menu.qml +++ b/src/quickcontrols/ios/Menu.qml @@ -52,8 +52,8 @@ T.Menu { source: IOS.url + "menu-background" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } diff --git a/src/quickcontrols/ios/MenuBar.qml b/src/quickcontrols/ios/MenuBar.qml index b38fa12fcb..ae611667b4 100644 --- a/src/quickcontrols/ios/MenuBar.qml +++ b/src/quickcontrols/ios/MenuBar.qml @@ -24,7 +24,7 @@ T.MenuBar { background: Rectangle { opacity: 0.98 - color: Qt.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base + color: Application.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base Rectangle { height: 1 width: parent.width diff --git a/src/quickcontrols/ios/MenuItem.qml b/src/quickcontrols/ios/MenuItem.qml index 29c2562ca0..46aba80187 100644 --- a/src/quickcontrols/ios/MenuItem.qml +++ b/src/quickcontrols/ios/MenuItem.qml @@ -86,8 +86,8 @@ T.MenuItem { states: [ {"edge": control.isFirstItem || control.isLastItem}, {"single": control.isSingleItem}, - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"pressed": control.down} ] } diff --git a/src/quickcontrols/ios/MenuSeparator.qml b/src/quickcontrols/ios/MenuSeparator.qml index 790d8f0302..b709919f79 100644 --- a/src/quickcontrols/ios/MenuSeparator.qml +++ b/src/quickcontrols/ios/MenuSeparator.qml @@ -18,8 +18,8 @@ T.MenuSeparator { source: IOS.url + "menuseparator-separator" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } diff --git a/src/quickcontrols/ios/PageIndicator.qml b/src/quickcontrols/ios/PageIndicator.qml index 9a059b5df2..ca46a8b87c 100644 --- a/src/quickcontrols/ios/PageIndicator.qml +++ b/src/quickcontrols/ios/PageIndicator.qml @@ -18,8 +18,8 @@ T.PageIndicator { source: IOS.url + "pageindicator-delegate" ImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"current": index === control.currentIndex}, ] } diff --git a/src/quickcontrols/ios/Popup.qml b/src/quickcontrols/ios/Popup.qml index ec76a90267..b614448997 100644 --- a/src/quickcontrols/ios/Popup.qml +++ b/src/quickcontrols/ios/Popup.qml @@ -35,8 +35,8 @@ T.Popup { source: IOS.url + "popup-background" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } diff --git a/src/quickcontrols/ios/ProgressBar.qml b/src/quickcontrols/ios/ProgressBar.qml index 20cf4dc265..36adcb0ed8 100644 --- a/src/quickcontrols/ios/ProgressBar.qml +++ b/src/quickcontrols/ios/ProgressBar.qml @@ -31,8 +31,8 @@ T.ProgressBar { source: IOS.url + "slider-progress" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } @@ -46,8 +46,8 @@ T.ProgressBar { source: IOS.url + "slider-progress" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } @@ -72,8 +72,8 @@ T.ProgressBar { width: control.background.width NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } diff --git a/src/quickcontrols/ios/RadioButton.qml b/src/quickcontrols/ios/RadioButton.qml index d107d9b490..d37c411e4e 100644 --- a/src/quickcontrols/ios/RadioButton.qml +++ b/src/quickcontrols/ios/RadioButton.qml @@ -26,8 +26,8 @@ T.RadioButton { ImageSelector on source { states: [ {"checked": control.checked}, - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } diff --git a/src/quickcontrols/ios/RadioDelegate.qml b/src/quickcontrols/ios/RadioDelegate.qml index 91f74b9b4f..e772a57ef4 100644 --- a/src/quickcontrols/ios/RadioDelegate.qml +++ b/src/quickcontrols/ios/RadioDelegate.qml @@ -32,8 +32,8 @@ T.RadioDelegate { source: IOS.url + "radiodelegate-indicator" ImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } @@ -55,7 +55,7 @@ T.RadioDelegate { background: Rectangle { implicitHeight: 44 - color: Qt.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base + color: Application.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base NinePatchImage { property real offset: control.icon.source.toString() !== "" ? control.icon.width + control.spacing : 0 x: control.down ? 0 : control.leftPadding + offset @@ -65,8 +65,8 @@ T.RadioDelegate { source: IOS.url + "itemdelegate-background" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"pressed": control.down} ] } diff --git a/src/quickcontrols/ios/RangeSlider.qml b/src/quickcontrols/ios/RangeSlider.qml index a8bca2eee9..109436ac0e 100644 --- a/src/quickcontrols/ios/RangeSlider.qml +++ b/src/quickcontrols/ios/RangeSlider.qml @@ -28,8 +28,8 @@ T.RangeSlider { source: IOS.url + "slider-handle" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, ] } } @@ -47,8 +47,8 @@ T.RangeSlider { source: IOS.url + "slider-handle" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, ] } } @@ -67,8 +67,8 @@ T.RangeSlider { width: control.horizontal ? control.background.width : control.background.height NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, ] } @@ -81,8 +81,8 @@ T.RangeSlider { source: IOS.url + "slider-progress" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, ] } } diff --git a/src/quickcontrols/ios/ScrollBar.qml b/src/quickcontrols/ios/ScrollBar.qml index afd2e737b3..f8b7b1d0a6 100644 --- a/src/quickcontrols/ios/ScrollBar.qml +++ b/src/quickcontrols/ios/ScrollBar.qml @@ -24,8 +24,8 @@ T.ScrollBar { source: IOS.url + "scrollindicator-handle" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"horizontal": control.horizontal}, {"vertical": control.vertical} ] diff --git a/src/quickcontrols/ios/ScrollIndicator.qml b/src/quickcontrols/ios/ScrollIndicator.qml index 5af880ca36..49d185b97d 100644 --- a/src/quickcontrols/ios/ScrollIndicator.qml +++ b/src/quickcontrols/ios/ScrollIndicator.qml @@ -18,8 +18,8 @@ T.ScrollIndicator { source: IOS.url + "scrollindicator-handle" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"horizontal": control.horizontal}, {"vertical": control.vertical} ] diff --git a/src/quickcontrols/ios/SelectionRectangle.qml b/src/quickcontrols/ios/SelectionRectangle.qml index 06e540b411..99cbbe65c3 100644 --- a/src/quickcontrols/ios/SelectionRectangle.qml +++ b/src/quickcontrols/ios/SelectionRectangle.qml @@ -21,8 +21,8 @@ T.SelectionRectangle { visible: SelectionRectangle.control.active ImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } diff --git a/src/quickcontrols/ios/Slider.qml b/src/quickcontrols/ios/Slider.qml index fe76303e26..5cc3d126c5 100644 --- a/src/quickcontrols/ios/Slider.qml +++ b/src/quickcontrols/ios/Slider.qml @@ -26,8 +26,8 @@ T.Slider { source: IOS.url + "slider-handle" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"disabled": !control.enabled} ] } @@ -47,8 +47,8 @@ T.Slider { width: control.horizontal ? background.width : background.height NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, ] } @@ -59,8 +59,8 @@ T.Slider { source: IOS.url + "slider-progress" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, ] } } diff --git a/src/quickcontrols/ios/SpinBox.qml b/src/quickcontrols/ios/SpinBox.qml index 4168f6b43e..38f8385eaf 100644 --- a/src/quickcontrols/ios/SpinBox.qml +++ b/src/quickcontrols/ios/SpinBox.qml @@ -53,8 +53,8 @@ T.SpinBox { states: [ {"up": true}, {"pressed": control.up.pressed}, - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } @@ -69,8 +69,8 @@ T.SpinBox { states: [ {"down": true}, {"pressed": control.down.pressed}, - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } @@ -87,8 +87,8 @@ T.SpinBox { y: (parent.height - height) / 2 NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } diff --git a/src/quickcontrols/ios/SwipeDelegate.qml b/src/quickcontrols/ios/SwipeDelegate.qml index 9c6a4c3703..724de07959 100644 --- a/src/quickcontrols/ios/SwipeDelegate.qml +++ b/src/quickcontrols/ios/SwipeDelegate.qml @@ -39,7 +39,7 @@ T.SwipeDelegate { background: Rectangle { implicitHeight: 44 - color: Qt.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base + color: Application.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base NinePatchImage { property real offset: control.icon.source.toString() !== "" ? control.icon.width + control.spacing : 0 x: control.down ? 0 : control.leftPadding + offset @@ -49,8 +49,8 @@ T.SwipeDelegate { source: IOS.url + "itemdelegate-background" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"pressed": control.down} ] } diff --git a/src/quickcontrols/ios/Switch.qml b/src/quickcontrols/ios/Switch.qml index 1b219cddf4..7a9b029fdf 100644 --- a/src/quickcontrols/ios/Switch.qml +++ b/src/quickcontrols/ios/Switch.qml @@ -32,8 +32,8 @@ T.Switch { source: IOS.url + "switch-indicator" ImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"checked": control.checked} ] } @@ -53,8 +53,8 @@ T.Switch { source: IOS.url + "switch-handle" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"disabled": !control.enabled} ] } diff --git a/src/quickcontrols/ios/SwitchDelegate.qml b/src/quickcontrols/ios/SwitchDelegate.qml index 4b7d8bdccf..80e79580de 100644 --- a/src/quickcontrols/ios/SwitchDelegate.qml +++ b/src/quickcontrols/ios/SwitchDelegate.qml @@ -33,8 +33,8 @@ T.SwitchDelegate { source: IOS.url + "switch-indicator" ImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"checked": control.checked} ] } @@ -54,8 +54,8 @@ T.SwitchDelegate { source: IOS.url + "switch-handle" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, {"disabled": !control.enabled} ] } @@ -84,7 +84,7 @@ T.SwitchDelegate { background: Rectangle { implicitHeight: 44 - color: Qt.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base + color: Application.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base NinePatchImage { property real offset: control.icon.source.toString() !== "" ? control.icon.width + control.spacing : 0 x: control.leftPadding + offset @@ -93,8 +93,8 @@ T.SwitchDelegate { source: IOS.url + "itemdelegate-background" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, ] } } diff --git a/src/quickcontrols/ios/TabBar.qml b/src/quickcontrols/ios/TabBar.qml index 0f42ea14e5..99434fa14d 100644 --- a/src/quickcontrols/ios/TabBar.qml +++ b/src/quickcontrols/ios/TabBar.qml @@ -32,7 +32,7 @@ T.TabBar { background: Rectangle { implicitHeight: 49 - color: Qt.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base + color: Application.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base Rectangle { height: 1 width: parent.width diff --git a/src/quickcontrols/ios/ToolBar.qml b/src/quickcontrols/ios/ToolBar.qml index 28b0029018..0c02403630 100644 --- a/src/quickcontrols/ios/ToolBar.qml +++ b/src/quickcontrols/ios/ToolBar.qml @@ -15,7 +15,7 @@ T.ToolBar { background: Rectangle { implicitHeight: 49 - color: Qt.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base + color: Application.styleHints.colorScheme === Qt.Dark ? control.palette.light : control.palette.base Rectangle { height: 1 width: parent.width diff --git a/src/quickcontrols/ios/ToolTip.qml b/src/quickcontrols/ios/ToolTip.qml index 973e819503..8e6502da98 100644 --- a/src/quickcontrols/ios/ToolTip.qml +++ b/src/quickcontrols/ios/ToolTip.qml @@ -45,8 +45,8 @@ T.ToolTip { source: IOS.url + "tooltip-background" NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } diff --git a/src/quickcontrols/ios/TreeViewDelegate.qml b/src/quickcontrols/ios/TreeViewDelegate.qml index 5fc16bee07..7277011613 100644 --- a/src/quickcontrols/ios/TreeViewDelegate.qml +++ b/src/quickcontrols/ios/TreeViewDelegate.qml @@ -46,8 +46,8 @@ T.TreeViewDelegate { source: IOS.url + "arrow-indicator" ImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } @@ -55,15 +55,15 @@ T.TreeViewDelegate { background: Rectangle { implicitHeight: 44 - color: Qt.styleHints.colorScheme === Qt.Dark ? control.palette.dark : control.palette.base + color: Application.styleHints.colorScheme === Qt.Dark ? control.palette.dark : control.palette.base NinePatchImage { height: parent.height width: parent.width source: IOS.url + (control.highlighted ? "itemdelegate-background-pressed" : "itemdelegate-background") NinePatchImageSelector on source { states: [ - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark} + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark} ] } } diff --git a/src/quickcontrols/ios/impl/DialogButtonBoxDelegate.qml b/src/quickcontrols/ios/impl/DialogButtonBoxDelegate.qml index ec506f6c06..1a0bc9d12f 100644 --- a/src/quickcontrols/ios/impl/DialogButtonBoxDelegate.qml +++ b/src/quickcontrols/ios/impl/DialogButtonBoxDelegate.qml @@ -20,7 +20,7 @@ Button { flat: true contentItem: IconLabel { - readonly property var redColor: Qt.styleHints.colorScheme === Qt.Light ? "#ff3b30" : "#ff453a" + readonly property var redColor: Application.styleHints.colorScheme === Qt.Light ? "#ff3b30" : "#ff453a" text: delegate.text font: delegate.font spacing: delegate.spacing @@ -56,8 +56,8 @@ Button { {"vertical": delegate.hasVerticalLayout}, {"last": delegate.hasVerticalLayout && delegate.isLastItem}, {"pressed": delegate.down}, - {"light": Qt.styleHints.colorScheme === Qt.Light}, - {"dark": Qt.styleHints.colorScheme === Qt.Dark}, + {"light": Application.styleHints.colorScheme === Qt.Light}, + {"dark": Application.styleHints.colorScheme === Qt.Dark}, ] } } diff --git a/src/quickcontrols/material/Button.qml b/src/quickcontrols/material/Button.qml index c02d9f426a..8ffe07bbc3 100644 --- a/src/quickcontrols/material/Button.qml +++ b/src/quickcontrols/material/Button.qml @@ -18,8 +18,9 @@ T.Button { topInset: 6 bottomInset: 6 verticalPadding: Material.buttonVerticalPadding - leftPadding: Material.buttonLeftPadding(flat, hasIcon) - rightPadding: Material.buttonRightPadding(flat, hasIcon, text !== "") + leftPadding: Material.buttonLeftPadding(flat, hasIcon && (display !== AbstractButton.TextOnly)) + rightPadding: Material.buttonRightPadding(flat, hasIcon && (display !== AbstractButton.TextOnly), + (text !== "") && (display !== AbstractButton.IconOnly)) spacing: 8 icon.width: 24 diff --git a/src/quickcontrols/material/TreeViewDelegate.qml b/src/quickcontrols/material/TreeViewDelegate.qml index 9f1d444383..7a9976b021 100644 --- a/src/quickcontrols/material/TreeViewDelegate.qml +++ b/src/quickcontrols/material/TreeViewDelegate.qml @@ -42,7 +42,7 @@ T.TreeViewDelegate { y: (parent.height - height) / 2 rotation: control.expanded ? 90 : (control.mirrored ? 180 : 0) source: "qrc:/qt-project.org/imports/QtQuick/Controls/Material/images/arrow-indicator.png" - color: control.palette.windowText + color: control.enabled ? control.Material.foreground : control.Material.hintTextColor defaultColor: "#353637" } } @@ -50,9 +50,16 @@ T.TreeViewDelegate { background: Rectangle { implicitHeight: control.Material.buttonHeight color: control.highlighted - ? control.palette.highlight + ? control.Material.accentColor : (control.treeView.alternatingRows && control.row % 2 !== 0 - ? control.palette.alternateBase : control.palette.base) + ? control.Material.background + // The Material.shade() is used as the alternate background color for rows + // based on the Material.theme value. + : control.Material.shade(control.Material.background, + control.Material.theme === Material.Dark + ? Material.Shade100 // the lighter background color + : Material.Shade700 // the darker background color + )) } contentItem: Label { diff --git a/src/quickcontrols/material/Tumbler.qml b/src/quickcontrols/material/Tumbler.qml index 59320cf52b..48d0c2e739 100644 --- a/src/quickcontrols/material/Tumbler.qml +++ b/src/quickcontrols/material/Tumbler.qml @@ -14,6 +14,8 @@ T.Tumbler { implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) + readonly property real __delegateHeight: availableHeight / visibleItemCount + delegate: Text { text: modelData color: control.Material.foreground @@ -33,13 +35,11 @@ T.Tumbler { delegate: control.delegate path: Path { startX: control.contentItem.width / 2 - startY: -control.contentItem.delegateHeight / 2 + startY: -control.__delegateHeight / 2 PathLine { x: control.contentItem.width / 2 - y: (control.visibleItemCount + 1) * control.contentItem.delegateHeight - control.contentItem.delegateHeight / 2 + y: (control.visibleItemCount + 1) * control.__delegateHeight - control.__delegateHeight / 2 } } - - property real delegateHeight: control.availableHeight / control.visibleItemCount } } diff --git a/src/quickcontrols/material/impl/CursorDelegate.qml b/src/quickcontrols/material/impl/CursorDelegate.qml index 811aa89e36..d1ef157f87 100644 --- a/src/quickcontrols/material/impl/CursorDelegate.qml +++ b/src/quickcontrols/material/impl/CursorDelegate.qml @@ -24,7 +24,7 @@ Rectangle { id: timer running: cursor.parent.activeFocus && !cursor.parent.readOnly && interval != 0 repeat: true - interval: Qt.styleHints.cursorFlashTime / 2 + interval: Application.styleHints.cursorFlashTime / 2 onTriggered: cursor.opacity = !cursor.opacity ? 1 : 0 // force the cursor visible when gaining focus onRunningChanged: cursor.opacity = 1 diff --git a/src/quickcontrols/universal/Tumbler.qml b/src/quickcontrols/universal/Tumbler.qml index 03b5fcca63..e6d7da6e2c 100644 --- a/src/quickcontrols/universal/Tumbler.qml +++ b/src/quickcontrols/universal/Tumbler.qml @@ -14,6 +14,8 @@ T.Tumbler { implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) + readonly property real __delegateHeight: availableHeight / visibleItemCount + delegate: Text { text: modelData font: control.font @@ -33,10 +35,10 @@ T.Tumbler { delegate: control.delegate path: Path { startX: control.contentItem.width / 2 - startY: -control.contentItem.delegateHeight / 2 + startY: -control.__delegateHeight / 2 PathLine { x: control.contentItem.width / 2 - y: (control.visibleItemCount + 1) * control.contentItem.delegateHeight - control.contentItem.delegateHeight / 2 + y: (control.visibleItemCount + 1) * control.__delegateHeight - control.__delegateHeight / 2 } } diff --git a/src/quickcontrolsimpl/qquicktumblerview.cpp b/src/quickcontrolsimpl/qquicktumblerview.cpp index 785791d117..a9606c6c20 100644 --- a/src/quickcontrolsimpl/qquicktumblerview.cpp +++ b/src/quickcontrolsimpl/qquicktumblerview.cpp @@ -159,9 +159,25 @@ void QQuickTumblerView::createView() // the view animates any potential currentIndex change over one second, // which we don't want when the contentItem has just been created. m_listView->setDelegate(m_delegate); + + QQuickTumblerPrivate *tumblerPrivate = QQuickTumblerPrivate::get(m_tumbler); + // Ignore currentIndex change: + // If the view's currentIndex is changed by setHighlightRangeMode(), + // it will be reset later. + tumblerPrivate->ignoreCurrentIndexChanges = true; // Set this after setting the delegate to avoid unexpected currentIndex changes: QTBUG-79150 m_listView->setHighlightRangeMode(QQuickListView::StrictlyEnforceRange); m_listView->setHighlightMoveDuration(1000); + tumblerPrivate->ignoreCurrentIndexChanges = false; + + // Reset the view's current index when creating the view: + // Setting highlight range mode causes geometry change, and + // then the view considers the viewport has moved (viewportMoved()). + // The view will update the currentIndex due to the viewport movement. + // Here, we check that if the view's currentIndex is not the same as it is + // supposed to be (the initial value), and then reset the view's currentIndex. + if (m_listView->currentIndex() != tumblerPrivate->currentIndex) + m_listView->setCurrentIndex(tumblerPrivate->currentIndex); qCDebug(lcTumblerView) << "finished creating ListView"; } diff --git a/src/quickdialogs/quickdialogs/qquickabstractdialog.cpp b/src/quickdialogs/quickdialogs/qquickabstractdialog.cpp index 857a75c7bd..101870ec84 100644 --- a/src/quickdialogs/quickdialogs/qquickabstractdialog.cpp +++ b/src/quickdialogs/quickdialogs/qquickabstractdialog.cpp @@ -53,7 +53,14 @@ Q_LOGGING_CATEGORY(lcDialogs, "qt.quick.dialogs") v | +-----------------+ | | m_handle valid? |--------------------->false - +-----------------+ + +-----------------+ ^ + | | + v | + true | + | | + +-------------------+ | + | m_handle->show()? |------------------->false + +-------------------+ | v true @@ -287,7 +294,25 @@ void QQuickAbstractDialog::open() return; onShow(m_handle.get()); + m_visible = m_handle->show(m_flags, m_modality, m_parentWindow); + if (!m_visible && useNativeDialog()) { + // Fall back to non-native dialog + destroy(); + if (!create(CreateOptions::DontTryNativeDialog)) + return; + + onShow(m_handle.get()); + m_visible = m_handle->show(m_flags, m_modality, m_parentWindow); + + if (m_visible) { + // The conditions that caused the non-native fallback might have + // changed the next time open() is called, so we should try again + // with a native dialog when that happens. + QObject::connect(this, &QQuickAbstractDialog::visibleChanged, + m_handle.get(), [this]{ if (!isVisible()) destroy(); }); + } + } if (m_visible) { m_result = Rejected; // in case an accepted dialog gets re-opened, then closed emit visibleChanged(); @@ -387,15 +412,15 @@ QPlatformTheme::DialogType toPlatformDialogType(QQuickDialogType quickDialogType ? QPlatformTheme::FileDialog : static_cast<QPlatformTheme::DialogType>(quickDialogType); } -bool QQuickAbstractDialog::create() +bool QQuickAbstractDialog::create(CreateOptions createOptions) { qCDebug(lcDialogs) << qmlTypeName(this) << "attempting to create dialog backend of type" << int(m_type) << "with parent window" << m_parentWindow; if (m_handle) return m_handle.get(); - qCDebug(lcDialogs) << "- attempting to create a native dialog"; - if (useNativeDialog()) { + if ((createOptions != CreateOptions::DontTryNativeDialog) && useNativeDialog()) { + qCDebug(lcDialogs) << "- attempting to create a native dialog"; m_handle.reset(QGuiApplicationPrivate::platformTheme()->createPlatformDialogHelper( toPlatformDialogType(m_type))); } diff --git a/src/quickdialogs/quickdialogs/qquickabstractdialog_p.h b/src/quickdialogs/quickdialogs/qquickabstractdialog_p.h index f1d046eb89..e314eb27b4 100644 --- a/src/quickdialogs/quickdialogs/qquickabstractdialog_p.h +++ b/src/quickdialogs/quickdialogs/qquickabstractdialog_p.h @@ -98,7 +98,8 @@ protected: void classBegin() override; void componentComplete() override; - bool create(); + enum class CreateOptions { TryAllDialogTypes = 0, DontTryNativeDialog = 1 }; + bool create(CreateOptions = CreateOptions::TryAllDialogTypes); void destroy(); virtual bool useNativeDialog() const; diff --git a/src/quickdialogs/quickdialogsquickimpl/qml/+Fusion/FileDialog.qml b/src/quickdialogs/quickdialogsquickimpl/qml/+Fusion/FileDialog.qml index 965f56bdc7..3b71a415e2 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qml/+Fusion/FileDialog.qml +++ b/src/quickdialogs/quickdialogsquickimpl/qml/+Fusion/FileDialog.qml @@ -38,10 +38,12 @@ FileDialogImpl { closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent dim: true modal: true - title: qsTr("โ%1โ already exists. Do you want to replace it?").arg(control.fileName) + title: qsTr("Overwrite file?") + width: control.width - control.leftPadding - control.rightPadding - Label { - text: qsTr("A file with the same name already exists in %1.\nReplacing it will overwrite its current contents.").arg(control.currentFolderName) + contentItem: Label { + text: qsTr("โ%1โ already exists.\nDo you want to replace it?").arg(control.fileName) + wrapMode: Text.WordWrap } footer: DialogButtonBox { diff --git a/src/quickdialogs/quickdialogsquickimpl/qml/+Imagine/FileDialog.qml b/src/quickdialogs/quickdialogsquickimpl/qml/+Imagine/FileDialog.qml index 8568be710a..3da17f60ce 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qml/+Imagine/FileDialog.qml +++ b/src/quickdialogs/quickdialogsquickimpl/qml/+Imagine/FileDialog.qml @@ -47,12 +47,12 @@ FileDialogImpl { dim: true modal: true spacing: 12 - title: qsTr("โ%1โ already exists. Do you want to replace it?").arg(control.fileName) + title: qsTr("Overwrite file?") + width: control.width - control.leftPadding - control.rightPadding - Label { - anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("A file with the same name already exists in %1.\nReplacing it will overwrite its current contents.").arg(control.currentFolderName) - horizontalAlignment: Text.AlignHCenter + contentItem: Label { + text: qsTr("โ%1โ already exists.\nDo you want to replace it?").arg(control.fileName) + wrapMode: Text.WordWrap } footer: DialogButtonBox { diff --git a/src/quickdialogs/quickdialogsquickimpl/qml/+Material/FileDialog.qml b/src/quickdialogs/quickdialogsquickimpl/qml/+Material/FileDialog.qml index 9700aeba79..c467e5a40c 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qml/+Material/FileDialog.qml +++ b/src/quickdialogs/quickdialogsquickimpl/qml/+Material/FileDialog.qml @@ -39,10 +39,13 @@ FileDialogImpl { closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent dim: true modal: true - title: qsTr("โ%1โ already exists. Do you want to replace it?").arg(control.fileName) + title: qsTr("Overwrite file?") + width: control.width - control.leftPadding - control.rightPadding + clip: true - Label { - text: qsTr("A file with the same name already exists in %1.\nReplacing it will overwrite its current contents.").arg(control.currentFolderName) + contentItem: Label { + text: qsTr("โ%1โ already exists.\nDo you want to replace it?").arg(control.fileName) + wrapMode: Text.WordWrap } footer: DialogButtonBox { diff --git a/src/quickdialogs/quickdialogsquickimpl/qml/+Universal/FileDialog.qml b/src/quickdialogs/quickdialogsquickimpl/qml/+Universal/FileDialog.qml index 0d2db8b426..b0bc98ee00 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qml/+Universal/FileDialog.qml +++ b/src/quickdialogs/quickdialogsquickimpl/qml/+Universal/FileDialog.qml @@ -37,10 +37,12 @@ FileDialogImpl { closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent dim: true modal: true - title: qsTr("โ%1โ already exists. Do you want to replace it?").arg(control.fileName) + title: qsTr("Overwrite file?") + width: control.width - control.leftPadding - control.rightPadding - Label { - text: qsTr("A file with the same name already exists in %1.\nReplacing it will overwrite its current contents.").arg(control.currentFolderName) + contentItem: Label { + text: qsTr("โ%1โ already exists.\nDo you want to replace it?").arg(control.fileName) + wrapMode: Text.WordWrap } footer: DialogButtonBox { diff --git a/src/quickdialogs/quickdialogsquickimpl/qml/FileDialog.qml b/src/quickdialogs/quickdialogsquickimpl/qml/FileDialog.qml index defb7dd4e2..536634edb6 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qml/FileDialog.qml +++ b/src/quickdialogs/quickdialogsquickimpl/qml/FileDialog.qml @@ -41,10 +41,12 @@ FileDialogImpl { closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent dim: true modal: true - title: qsTr("โ%1โ already exists. Do you want to replace it?").arg(control.fileName) + title: qsTr("Overwrite file?") + width: control.width - control.leftPadding - control.rightPadding - Label { - text: qsTr("A file with the same name already exists in %1.\nReplacing it will overwrite its current contents.").arg(control.currentFolderName) + contentItem: Label { + text: qsTr("โ%1โ already exists.\nDo you want to replace it?").arg(control.fileName) + wrapMode: Text.WordWrap } footer: DialogButtonBox { diff --git a/src/quickdialogs/quickdialogsquickimpl/qml/MessageDialog.qml b/src/quickdialogs/quickdialogsquickimpl/qml/MessageDialog.qml index 5dc7ee7873..562e931a69 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qml/MessageDialog.qml +++ b/src/quickdialogs/quickdialogsquickimpl/qml/MessageDialog.qml @@ -3,6 +3,7 @@ import QtQuick import QtQuick.Controls +import QtQuick.Controls.impl import QtQuick.Dialogs import QtQuick.Dialogs.quickimpl import QtQuick.Layouts diff --git a/src/quicklayouts/qquicklayout.cpp b/src/quicklayouts/qquicklayout.cpp index fc2bcc130c..8a35a8db08 100644 --- a/src/quicklayouts/qquicklayout.cpp +++ b/src/quicklayouts/qquicklayout.cpp @@ -829,20 +829,16 @@ void QQuickLayout::invalidate(QQuickItem * /*childItem*/) d->m_dirtyArrangement = true; if (!qobject_cast<QQuickLayout *>(parentItem())) { - - if (m_inUpdatePolish) - ++m_polishInsideUpdatePolish; - else - m_polishInsideUpdatePolish = 0; - - if (m_polishInsideUpdatePolish <= 2) { - // allow at most two consecutive loops in order to respond to height-for-width - // (e.g QQuickText changes implicitHeight when its width gets changed) - qCDebug(lcQuickLayouts) << "QQuickLayout::invalidate(), polish()"; - polish(); + polish(); + + if (m_inUpdatePolish) { + if (++m_polishInsideUpdatePolish > 2) + // allow at most two consecutive loops in order to respond to height-for-width + // (e.g QQuickText changes implicitHeight when its width gets changed) + qCDebug(lcQuickLayouts) << "Layout polish loop detected for " << this + << ". The polish request will still be scheduled."; } else { - qmlWarning(this).nospace() << "Layout polish loop detected for " << this - << ". Aborting after two iterations."; + m_polishInsideUpdatePolish = 0; } } } @@ -921,7 +917,7 @@ void QQuickLayout::geometryChange(const QRectF &newGeometry, const QRectF &oldGe { Q_D(QQuickLayout); QQuickItem::geometryChange(newGeometry, oldGeometry); - if (d->m_disableRearrange || !isReady()) + if (invalidated() || d->m_disableRearrange || !isReady()) return; qCDebug(lcQuickLayouts) << "QQuickLayout::geometryChange" << newGeometry << oldGeometry; diff --git a/src/quicklayouts/qquicklinearlayout.cpp b/src/quicklayouts/qquicklinearlayout.cpp index c5d7065aa4..0aeda72143 100644 --- a/src/quicklayouts/qquicklinearlayout.cpp +++ b/src/quicklayouts/qquicklinearlayout.cpp @@ -361,26 +361,24 @@ void QQuickGridLayoutBase::invalidate(QQuickItem *childItem) if (!isReady()) return; qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::invalidate()" << this << ", invalidated:" << invalidated(); - if (invalidated()) { - return; - } - qCDebug(lcQuickLayouts) << "d->m_rearranging:" << d->m_rearranging; - if (d->m_rearranging) { - d->m_invalidateAfterRearrange << childItem; - return; - } - if (childItem) { - if (QQuickGridLayoutItem *layoutItem = d->engine.findLayoutItem(childItem)) + if (d->m_rearranging) { + if (!d->m_invalidateAfterRearrange.contains(childItem)) + d->m_invalidateAfterRearrange << childItem; + return; + } + if (QQuickGridLayoutItem *layoutItem = d->engine.findLayoutItem(childItem)) { layoutItem->invalidate(); + } } + // invalidate engine d->engine.invalidate(); qCDebug(lcQuickLayouts) << "calling QQuickLayout::invalidate();"; QQuickLayout::invalidate(); - if (QQuickLayout *parentLayout = qobject_cast<QQuickLayout *>(parentItem())) + if (auto *parentLayout = qobject_cast<QQuickLayout *>(parentItem())) parentLayout->invalidate(this); qCDebug(lcQuickLayouts) << "QQuickGridLayoutBase::invalidate() LEAVING" << this; } @@ -479,8 +477,8 @@ void QQuickGridLayoutBase::rearrange(const QSizeF &size) d->engine.setGeometries(QRectF(QPointF(0,0), size), d->styleInfo); d->m_rearranging = false; - for (QQuickItem *invalid : std::as_const(d->m_invalidateAfterRearrange)) - invalidate(invalid); + for (auto childItem : std::as_const(d->m_invalidateAfterRearrange)) + invalidate(childItem); d->m_invalidateAfterRearrange.clear(); } diff --git a/src/quicktemplates/qquickabstractbutton.cpp b/src/quicktemplates/qquickabstractbutton.cpp index e41c065b33..85028a1d84 100644 --- a/src/quicktemplates/qquickabstractbutton.cpp +++ b/src/quicktemplates/qquickabstractbutton.cpp @@ -155,7 +155,7 @@ bool QQuickAbstractButtonPrivate::handleRelease(const QPointF &point, ulong time pressButtons = Qt::NoButton; const bool touchDoubleClick = pressTouchId != -1 && lastTouchReleaseTimestamp != 0 - && timestamp - lastTouchReleaseTimestamp < qApp->styleHints()->mouseDoubleClickInterval() + && QQuickDeliveryAgentPrivate::isWithinDoubleClickInterval(timestamp - lastTouchReleaseTimestamp) && isDoubleClickConnected(); if (!wasHeld && (keepPressed || q->contains(point))) diff --git a/src/quicktemplates/qquickapplicationwindow.cpp b/src/quicktemplates/qquickapplicationwindow.cpp index f324fc451e..34bebbf021 100644 --- a/src/quicktemplates/qquickapplicationwindow.cpp +++ b/src/quicktemplates/qquickapplicationwindow.cpp @@ -8,6 +8,7 @@ #include "qquicktextarea_p.h" #include "qquicktextfield_p.h" #include "qquicktoolbar_p.h" +#include "qquicktooltip_p.h" #include "qquicktabbar_p.h" #include "qquickdialogbuttonbox_p.h" #include "qquickdeferredexecute_p_p.h" @@ -106,6 +107,7 @@ public: QQmlListProperty<QObject> contentData(); + void updateHasBackgroundFlags(); void relayout(); void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; @@ -136,9 +138,15 @@ public: // Update regular children QQuickWindowPrivate::updateChildrenPalettes(parentPalette); - // And cover one special case - for (auto &&popup : q_func()->findChildren<QQuickPopup *>()) - QQuickPopupPrivate::get(popup)->updateContentPalettes(parentPalette); + // And cover special cases + for (auto &&child : q_func()->findChildren<QObject *>()) { + if (auto *popup = qobject_cast<QQuickPopup *>(child)) + QQuickPopupPrivate::get(popup)->updateContentPalettes(parentPalette); + else if (auto *toolTipAttached = qobject_cast<QQuickToolTipAttached *>(child)) { + if (auto *toolTip = toolTipAttached->toolTip()) + QQuickPopupPrivate::get(toolTip)->updateContentPalettes(parentPalette); + } + } } QQuickDeferredPointer<QQuickItem> background; @@ -167,6 +175,16 @@ static void layoutItem(QQuickItem *item, qreal y, qreal width) } } +void QQuickApplicationWindowPrivate::updateHasBackgroundFlags() +{ + if (!background) + return; + + QQuickItemPrivate *backgroundPrivate = QQuickItemPrivate::get(background); + hasBackgroundWidth = backgroundPrivate->widthValid(); + hasBackgroundHeight = backgroundPrivate->heightValid(); +} + void QQuickApplicationWindowPrivate::relayout() { Q_Q(QQuickApplicationWindow); @@ -202,9 +220,7 @@ void QQuickApplicationWindowPrivate::itemGeometryChanged(QQuickItem *item, QQuic if (!insideRelayout && item == background && change.sizeChange()) { // Any time the background is resized (excluding our own resizing), // we should respect it if it's explicit by storing the values of the flags. - QQuickItemPrivate *backgroundPrivate = QQuickItemPrivate::get(background); - hasBackgroundWidth = backgroundPrivate->widthValid(); - hasBackgroundHeight = backgroundPrivate->heightValid(); + updateHasBackgroundFlags(); } relayout(); @@ -299,8 +315,12 @@ void QQuickApplicationWindowPrivate::executeBackground(bool complete) if (!background || complete) quickBeginDeferred(q, backgroundName(), background); - if (complete) + if (complete) { quickCompleteDeferred(q, backgroundName(), background); + // See comment in setBackground for why we do this here. + updateHasBackgroundFlags(); + relayout(); + } } QQuickApplicationWindow::QQuickApplicationWindow(QWindow *parent) @@ -376,12 +396,15 @@ void QQuickApplicationWindow::setBackground(QQuickItem *background) if (qFuzzyIsNull(background->z())) background->setZ(-1); - QQuickItemPrivate *backgroundPrivate = QQuickItemPrivate::get(background); - d->hasBackgroundWidth = backgroundPrivate->widthValid(); - d->hasBackgroundHeight = backgroundPrivate->heightValid(); + // If the background hasn't finished executing then we don't know if its width and height + // are valid or not, and so relayout would see that they haven't been set yet and override + // any bindings the user might have. + if (!d->background.isExecuting()) { + d->updateHasBackgroundFlags(); - if (isComponentComplete()) - d->relayout(); + if (isComponentComplete()) + d->relayout(); + } } if (!d->background.isExecuting()) emit backgroundChanged(); diff --git a/src/quicktemplates/qquickbuttongroup.cpp b/src/quicktemplates/qquickbuttongroup.cpp index d57b82c8e7..108a1a9ecb 100644 --- a/src/quicktemplates/qquickbuttongroup.cpp +++ b/src/quicktemplates/qquickbuttongroup.cpp @@ -84,6 +84,29 @@ QT_BEGIN_NAMESPACE } \endcode + Another option is to filter the list of children. This is especially handy + if you're using a repeater to populate it, since the repeater will also be + a child of the parent layout: + + \code + ButtonGroup { + buttons: column.children.filter((child) => child !== repeater) + } + + Column { + id: column + + Repeater { + id: repeater + model: [ qsTr("DAB"), qsTr("AM"), qsTr("FM") ] + RadioButton { + required property string modelData + text: modelData + } + } + } + \endcode + More advanced use cases can be handled using the \c addButton() and \c removeButton() methods. diff --git a/src/quicktemplates/qquickcontainer.cpp b/src/quicktemplates/qquickcontainer.cpp index b696b1812c..9d48f3b3a1 100644 --- a/src/quicktemplates/qquickcontainer.cpp +++ b/src/quicktemplates/qquickcontainer.cpp @@ -5,6 +5,7 @@ #include "qquickcontainer_p_p.h" #include <QtQuick/private/qquickflickable_p.h> +#include <QtQuick/private/qquickitemview_p.h> QT_BEGIN_NAMESPACE @@ -210,6 +211,7 @@ void QQuickContainerPrivate::insertItem(int index, QQuickItem *item) updatingCurrent = true; item->setParentItem(effectiveContentItem(q->contentItem())); + maybeCullItem(item); QQuickItemPrivate::get(item)->addItemChangeListener(this, changeTypes); contentModel->insert(index, item); @@ -309,6 +311,38 @@ void QQuickContainerPrivate::reorderItems() } } +void QQuickContainerPrivate::maybeCullItem(QQuickItem *item) +{ + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return; + + // Items like Repeater don't control the visibility of the items they create, + // so we can't expected them to uncull items added dynamically. As mentioned + // below, Repeater _does_ uncull items added to it, but unlike e.g. ListView, + // it shouldn't care if its size becomes zero and so it shouldn't manage + // the culled state of items in the same way. + if (!qobject_cast<QQuickItemView *>(contentItem)) + return; + + // Only cull items if the contentItem has a zero size; otherwise let the + // contentItem manage it. + const bool hasZeroSize = qFuzzyIsNull(width) && qFuzzyIsNull(height); + if (!hasZeroSize) + return; + + QQuickItemPrivate::get(item)->setCulled(true); +} + +void QQuickContainerPrivate::maybeCullItems() +{ + if (!contentItem) + return; + + const QList<QQuickItem *> childItems = effectiveContentItem(contentItem)->childItems(); + for (auto &childItem : childItems) + maybeCullItem(childItem); +} + void QQuickContainerPrivate::_q_currentIndexChanged() { Q_Q(QQuickContainer); @@ -808,6 +842,7 @@ void QQuickContainer::componentComplete() Q_D(QQuickContainer); QQuickControl::componentComplete(); d->reorderItems(); + d->maybeCullItems(); } void QQuickContainer::itemChange(ItemChange change, const ItemChangeData &data) diff --git a/src/quicktemplates/qquickcontainer_p_p.h b/src/quicktemplates/qquickcontainer_p_p.h index 84fe62a75d..e32e840a4b 100644 --- a/src/quicktemplates/qquickcontainer_p_p.h +++ b/src/quicktemplates/qquickcontainer_p_p.h @@ -39,6 +39,8 @@ public: void moveItem(int from, int to, QQuickItem *item); void removeItem(int index, QQuickItem *item); void reorderItems(); + void maybeCullItem(QQuickItem *item); + void maybeCullItems(); void _q_currentIndexChanged(); diff --git a/src/quicktemplates/qquickcontrol.cpp b/src/quicktemplates/qquickcontrol.cpp index 3489c2574b..c1a3ca7be8 100644 --- a/src/quicktemplates/qquickcontrol.cpp +++ b/src/quicktemplates/qquickcontrol.cpp @@ -1488,7 +1488,7 @@ void QQuickControl::setHovered(bool hovered) \qmlproperty bool QtQuick.Controls::Control::hoverEnabled This property determines whether the control accepts hover events. The default value - is \c Qt.styleHints.useHoverEffects. + is \c Application.styleHints.useHoverEffects. Setting this property propagates the value to all child controls that do not have \c hoverEnabled explicitly set. diff --git a/src/quicktemplates/qquickdialogbuttonbox.cpp b/src/quicktemplates/qquickdialogbuttonbox.cpp index 33c5d798fd..fb14d9bcb3 100644 --- a/src/quicktemplates/qquickdialogbuttonbox.cpp +++ b/src/quicktemplates/qquickdialogbuttonbox.cpp @@ -506,7 +506,6 @@ void QQuickDialogButtonBox::setPosition(Position position) This property holds the alignment of the buttons. Possible values: - \value undefined The buttons are resized to fill the available space. \value Qt.AlignLeft The buttons are aligned to the left. \value Qt.AlignHCenter The buttons are horizontally centered. \value Qt.AlignRight The buttons are aligned to the right. @@ -514,12 +513,16 @@ void QQuickDialogButtonBox::setPosition(Position position) \value Qt.AlignVCenter The buttons are vertically centered. \value Qt.AlignBottom The buttons are aligned to the bottom. - The default value is \c undefined. + By default, no specific alignment is set; reading the alignment property yields + a default flag value which compares equal to 0. The property can be reset to this + value by assigning \c{undefined} to it. In this case, the buttons are resized to + fill the available space. - \note This property assumes a horizontal layout of the buttons. The - DialogButtonBox for the \l {iOS Style}{iOS style} uses a vertical layout - when there are more than two buttons, and if set to a value other than - \c undefined, the layout of its buttons will be done horizontally. + \note This property assumes a horizontal layout of the buttons. + Note that when running the \l {iOS Style}{iOS style}, the DialogButtonBox will use + a vertical layout if this property is set to anything other than \c undefined and + there are more than two buttons. + In all other cases, the buttons will be arranged horizontally. */ Qt::Alignment QQuickDialogButtonBox::alignment() const { diff --git a/src/quicktemplates/qquickdrawer.cpp b/src/quicktemplates/qquickdrawer.cpp index 517db920b8..88813db376 100644 --- a/src/quicktemplates/qquickdrawer.cpp +++ b/src/quicktemplates/qquickdrawer.cpp @@ -693,7 +693,7 @@ void QQuickDrawer::setPosition(qreal position) drag actions will open the drawer. Setting the value to \c 0 or less prevents opening the drawer by dragging. - The default value is \c Qt.styleHints.startDragDistance. + The default value is \c Application.styleHints.startDragDistance. \sa interactive */ diff --git a/src/quicktemplates/qquickmenu.cpp b/src/quicktemplates/qquickmenu.cpp index e73a6e57a0..d46a2a862f 100644 --- a/src/quicktemplates/qquickmenu.cpp +++ b/src/quicktemplates/qquickmenu.cpp @@ -1538,6 +1538,28 @@ void QQuickMenu::keyPressEvent(QKeyEvent *event) default: break; } + +#if QT_CONFIG(shortcut) + if (event->modifiers() == Qt::NoModifier) { + for (int i = 0; i < count(); ++i) { + QQuickAbstractButton *item = qobject_cast<QQuickAbstractButton*>(d->itemAt(i)); + if (!item) + continue; + const QKeySequence keySequence = QKeySequence::mnemonic(item->text()); + if (keySequence.isEmpty()) + continue; + // Have to simulate click on the item since + // QQuickAbstractButton::click() is introduced in Qt-6.8 + if (keySequence[0].key() == event->key() && item->isEnabled()) { + auto *p = QQuickAbstractButtonPrivate::get(item); + const QPointF eventPos(p->width / 2, p->height / 2); + p->handlePress(eventPos, 0); + p->handleRelease(eventPos, 0); + break; + } + } + } +#endif } void QQuickMenu::timerEvent(QTimerEvent *event) diff --git a/src/quicktemplates/qquickoverlay.cpp b/src/quicktemplates/qquickoverlay.cpp index 4119a34b84..c9d3cf9892 100644 --- a/src/quicktemplates/qquickoverlay.cpp +++ b/src/quicktemplates/qquickoverlay.cpp @@ -467,6 +467,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/quicktemplates/qquickpopup.cpp b/src/quicktemplates/qquickpopup.cpp index d81da8ece8..e48f944936 100644 --- a/src/quicktemplates/qquickpopup.cpp +++ b/src/quicktemplates/qquickpopup.cpp @@ -295,6 +295,59 @@ Q_LOGGING_CATEGORY(lcPopup, "qt.quick.controls.popup") } } \endcode + + \section1 Polish Behavior of Closed Popups + + When a popup is closed, it has no associated window, and neither do its + child items. This means that any child items will not be + \l {QQuickItem::polish}{polished} until the popup is shown. For this + reason, you cannot, for example, rely on a \l ListView within a closed + \c Popup to update its \c count property: + + \code + import QtQuick + import QtQuick.Controls + + ApplicationWindow { + width: 640 + height: 480 + visible: true + + SomeModel { + id: someModel + } + + Button { + text: view.count + onClicked: popup.open() + } + + Popup { + id: popup + width: 400 + height: 400 + contentItem: ListView { + id: view + model: someModel + delegate: Label { + text: display + + required property string display + } + } + } + } + \endcode + + In the example above, the Button's text will not update when rows are added + to or removed from \c someModel after \l {Component::completed}{component + completion} while the popup is closed. + + Instead, a \c count property can be added to \c SomeModel that is updated + whenever the \l {QAbstractItemModel::}{rowsInserted}, \l + {QAbstractItemModel::}{rowsRemoved}, and \l + {QAbstractItemModel::}{modelReset} signals are emitted. The \c Button can + then bind this property to its \c text. */ /*! @@ -588,8 +641,27 @@ bool QQuickPopupPrivate::prepareExitTransition() if (transitionState != ExitTransition) { // The setFocus(false) call below removes any active focus before we're // able to check it in finalizeExitTransition. - if (!hadActiveFocusBeforeExitTransition) - hadActiveFocusBeforeExitTransition = popupItem->hasActiveFocus(); + if (!hadActiveFocusBeforeExitTransition) { + const auto hasFocusInRoot = [](QQuickItem *item) { + Q_ASSERT(item); + if (!item->window() || item->window()->isActive()) + return item->hasActiveFocus(); + + // fallback for when there's no active window + const auto *da = QQuickItemPrivate::get(item)->deliveryAgentPrivate(); + if (!da || !da->rootItem) + return false; + + QQuickItem *focusItem = da->rootItem; + while (focusItem->isFocusScope() && focusItem->scopedFocusItem()) + focusItem = focusItem->scopedFocusItem(); + + return focusItem == item; + }; + + hadActiveFocusBeforeExitTransition = hasFocusInRoot(popupItem); + } + if (focus) popupItem->setFocus(false, Qt::PopupFocusReason); transitionState = ExitTransition; @@ -654,6 +726,13 @@ void QQuickPopupPrivate::finalizeExitTransition() hadActiveFocusBeforeExitTransition = false; emit q->visibleChanged(); emit q->closed(); +#if QT_CONFIG(accessibility) + const auto type = q->effectiveAccessibleRole() == QAccessible::PopupMenu + ? QAccessible::PopupMenuEnd + : QAccessible::DialogEnd; + QAccessibleEvent ev(q->popupItem(), type); + QAccessible::updateAccessibility(&ev); +#endif if (popupItem) { popupItem->setScale(prevScale); popupItem->setOpacity(prevOpacity); @@ -664,6 +743,13 @@ void QQuickPopupPrivate::opened() { Q_Q(QQuickPopup); emit q->opened(); +#if QT_CONFIG(accessibility) + const auto type = q->effectiveAccessibleRole() == QAccessible::PopupMenu + ? QAccessible::PopupMenuStart + : QAccessible::DialogStart; + QAccessibleEvent ev(q->popupItem(), type); + QAccessible::updateAccessibility(&ev); +#endif } QMarginsF QQuickPopupPrivate::getMargins() const @@ -2567,7 +2653,8 @@ void QQuickPopup::resetBottomInset() } \endcode - \sa Item::palette, Window::palette, ColorGroup, Palette + \b {See also}: \l Item::palette, \l Window::palette, \l ColorGroup, + \l [QML] {Palette} */ bool QQuickPopup::filtersChildMouseEvents() const diff --git a/src/quicktemplates/qquickpopuppositioner.cpp b/src/quicktemplates/qquickpopuppositioner.cpp index aecbc7373c..9628685e06 100644 --- a/src/quicktemplates/qquickpopuppositioner.cpp +++ b/src/quicktemplates/qquickpopuppositioner.cpp @@ -102,6 +102,11 @@ void QQuickPopupPositioner::reposition() // m_parentItem is the parent that the popup should open in, // and popupItem()->parentItem() is the overlay, so the mapToItem() calls below // effectively map the rect to scene coordinates. + + // Animations can cause reposition() to get called when m_parentItem no longer has a window. + if (!m_parentItem->window()) + return; + if (centerInParent) { if (centerInParent != parentItem() && !centerInOverlay) { qmlWarning(m_popup) << "Popup can only be centered within its immediate parent or Overlay.overlay"; diff --git a/src/quicktemplates/qquickrangeslider.cpp b/src/quicktemplates/qquickrangeslider.cpp index 6c4f8d8561..1dc032fece 100644 --- a/src/quicktemplates/qquickrangeslider.cpp +++ b/src/quicktemplates/qquickrangeslider.cpp @@ -698,7 +698,7 @@ void QQuickRangeSlider::setTo(qreal to) This property holds the threshold (in logical pixels) at which a touch drag event will be initiated. The mouse drag threshold won't be affected. - The default value is \c Qt.styleHints.startDragDistance. + The default value is \c Application.styleHints.startDragDistance. \sa QStyleHints diff --git a/src/quicktemplates/qquickslider.cpp b/src/quicktemplates/qquickslider.cpp index 2989735392..c095561d79 100644 --- a/src/quicktemplates/qquickslider.cpp +++ b/src/quicktemplates/qquickslider.cpp @@ -662,7 +662,7 @@ void QQuickSlider::decrease() This property holds the threshold (in logical pixels) at which a touch drag event will be initiated. The mouse drag threshold won't be affected. - The default value is \c Qt.styleHints.startDragDistance. + The default value is \c Application.styleHints.startDragDistance. \sa QStyleHints */ diff --git a/src/quicktemplates/qquicksplitview.cpp b/src/quicktemplates/qquicksplitview.cpp index 70d0cf827d..b0a9104726 100644 --- a/src/quicktemplates/qquicksplitview.cpp +++ b/src/quicktemplates/qquicksplitview.cpp @@ -325,11 +325,14 @@ void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &use // The handle shouldn't cross other handles, so use the right edge of // the first handle to the left as the left edge. qreal leftEdge = 0; - if (m_pressedHandleIndex - 1 >= 0) { - const QQuickItem *leftHandle = m_handleItems.at(m_pressedHandleIndex - 1); - leftEdge = horizontal - ? leftHandle->x() + leftHandle->width() - : leftHandle->y() + leftHandle->height(); + for (int i = m_pressedHandleIndex - 1; i >= 0; --i) { + const QQuickItem *nextHandleToTheLeft = m_handleItems.at(i); + if (nextHandleToTheLeft->isVisible()) { + leftEdge = horizontal + ? nextHandleToTheLeft->x() + nextHandleToTheLeft->width() + : nextHandleToTheLeft->y() + nextHandleToTheLeft->height(); + break; + } } // The mouse can be clicked anywhere in the handle, and if we don't account for diff --git a/src/quicktemplates/qquickswipedelegate.cpp b/src/quicktemplates/qquickswipedelegate.cpp index 8e7940fb93..d617f4b3da 100644 --- a/src/quicktemplates/qquickswipedelegate.cpp +++ b/src/quicktemplates/qquickswipedelegate.cpp @@ -782,7 +782,9 @@ bool QQuickSwipeDelegatePrivate::handleMouseMoveEvent(QQuickItem *item, QMouseEv if (item == q && !pressed) return false; - const qreal distance = (event->globalPosition() - event->points().first().globalPressPosition()).x(); + const qreal distance = (event->globalPosition().x() != qInf() && event->globalPosition().y() != qInf()) ? + (item->mapFromGlobal(event->globalPosition()) - + item->mapFromGlobal(event->points().first().globalPressPosition())).x() : 0; if (!q->keepMouseGrab()) { // We used to use the custom threshold that QQuickDrawerPrivate::grabMouse used, // but since it's larger than what Flickable uses, it results in Flickable diff --git a/src/quicktemplates/qquicktumbler.cpp b/src/quicktemplates/qquicktumbler.cpp index e7f6606de3..cfff191fc1 100644 --- a/src/quicktemplates/qquicktumbler.cpp +++ b/src/quicktemplates/qquicktumbler.cpp @@ -298,6 +298,12 @@ void QQuickTumbler::setModel(const QVariant &model) d->endSetModel(); + if (d->view && d->currentIndexSetDuringModelChange) { + const int viewCurrentIndex = d->view->property("currentIndex").toInt(); + if (viewCurrentIndex != d->currentIndex) + d->view->setProperty("currentIndex", d->currentIndex); + } + d->currentIndexSetDuringModelChange = false; // Don't try to correct the currentIndex if count() isn't known yet. diff --git a/src/quicktestutils/quick/visualtestutils.cpp b/src/quicktestutils/quick/visualtestutils.cpp index 30b6bf1135..c5e41f33f1 100644 --- a/src/quicktestutils/quick/visualtestutils.cpp +++ b/src/quicktestutils/quick/visualtestutils.cpp @@ -159,8 +159,8 @@ bool QQuickVisualTestUtils::compareImages(const QImage &ia, const QImage &ib, QS // No tolerance for error in the alpha. if ((a & 0xff000000) != (b & 0xff000000) || qAbs(qRed(a) - qRed(b)) > tolerance - || qAbs(qRed(a) - qRed(b)) > tolerance - || qAbs(qRed(a) - qRed(b)) > tolerance) { + || qAbs(qGreen(a) - qGreen(b)) > tolerance + || qAbs(qBlue(a) - qBlue(b)) > tolerance) { QDebug(errorMessage) << "Mismatch at:" << x << y << ':' << Qt::hex << Qt::showbase << a << b; return false; diff --git a/src/quicktestutils/quick/visualtestutils_p.h b/src/quicktestutils/quick/visualtestutils_p.h index 60837d8f65..24b3bebef0 100644 --- a/src/quicktestutils/quick/visualtestutils_p.h +++ b/src/quicktestutils/quick/visualtestutils_p.h @@ -15,6 +15,8 @@ // We mean it. // +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/qpa/qplatformintegration.h> #include <QtQml/qqmlexpression.h> #include <QtQuick/private/qquickitem_p.h> @@ -224,6 +226,10 @@ namespace QQuickVisualTestUtils #define QQUICK_VERIFY_POLISH(item) \ QTRY_COMPARE(QQuickItemPrivate::get(item)->polishScheduled, false) +#define SKIP_IF_NO_WINDOW_ACTIVATION \ +if (!(QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))) \ + QSKIP("Window activation is not supported on this platform"); + QT_END_NAMESPACE #endif // QQUICKVISUALTESTUTILS_P_H diff --git a/src/quickwidgets/qquickwidget.cpp b/src/quickwidgets/qquickwidget.cpp index 9cf03a2ca1..c64eff8aba 100644 --- a/src/quickwidgets/qquickwidget.cpp +++ b/src/quickwidgets/qquickwidget.cpp @@ -251,9 +251,7 @@ void QQuickWidgetPrivate::handleWindowChange() QObject::connect(renderControl, SIGNAL(renderRequested()), q, SLOT(triggerUpdate())); QObject::connect(renderControl, SIGNAL(sceneChanged()), q, SLOT(triggerUpdate())); - if (!source.isEmpty()) - execute(); - else if (QQuickItem *sgItem = qobject_cast<QQuickItem *>(root)) + if (QQuickItem *sgItem = qobject_cast<QQuickItem *>(root)) sgItem->setParentItem(offscreenWindow->contentItem()); } @@ -1119,7 +1117,17 @@ void QQuickWidget::createFramebufferObject() else samples = 0; - const QSize fboSize = size() * devicePixelRatio(); + const int minTexSize = d->rhi->resourceLimit(QRhi::TextureSizeMin); + const int maxTexSize = d->rhi->resourceLimit(QRhi::TextureSizeMax); + + QSize fboSize = size() * devicePixelRatio(); + if (fboSize.width() > maxTexSize || fboSize.height() > maxTexSize) { + qWarning("QQuickWidget: Requested backing texture size is %dx%d, but the maximum texture size for the 3D API implementation is %dx%d", + fboSize.width(), fboSize.height(), + maxTexSize, maxTexSize); + } + fboSize.setWidth(qMin(maxTexSize, qMax(minTexSize, fboSize.width()))); + fboSize.setHeight(qMin(maxTexSize, qMax(minTexSize, fboSize.height()))); // Could be a simple hide - show, in which case the previous texture is just fine. if (!d->outputTexture) { diff --git a/tests/auto/qml/ecmascripttests/TestExpectations b/tests/auto/qml/ecmascripttests/TestExpectations index 75fdd1cb0c..7c671040a3 100644 --- a/tests/auto/qml/ecmascripttests/TestExpectations +++ b/tests/auto/qml/ecmascripttests/TestExpectations @@ -249,8 +249,6 @@ built-ins/String/prototype/toLocaleLowerCase/special_casing_conditional.js fails built-ins/String/prototype/toLowerCase/Final_Sigma_U180E.js fails built-ins/String/prototype/toLowerCase/special_casing_conditional.js fails built-ins/TypedArray/prototype/constructor.js fails -built-ins/TypedArray/prototype/fill/fill-values-conversion-operations-consistent-nan.js fails -built-ins/TypedArray/prototype/slice/bit-precision.js fails built-ins/TypedArray/prototype/sort/arraylength-internal.js fails built-ins/TypedArray/prototype/sort/comparefn-call-throws.js fails built-ins/TypedArray/prototype/sort/comparefn-calls.js fails diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 2c5dc7af43..f9bd5c28aa 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -314,6 +314,11 @@ private slots: void deleteDefineCycle(); void deleteFromSparseArray(); + void generatorFunctionInTailCallPosition(); + void generatorMethodInTailCallPosition(); + + void consoleLogSequence(); + public: Q_INVOKABLE QJSValue throwingCppMethod1(); Q_INVOKABLE void throwingCppMethod2(); @@ -6345,6 +6350,71 @@ void tst_QJSEngine::deleteFromSparseArray() QVERIFY(result.property(20000).isUndefined()); } +void tst_QJSEngine::generatorFunctionInTailCallPosition() { + QJSEngine engine; + QJSValue result = engine.evaluate(R"( + "use strict"; + function* gen() { + yield 0; + } + function caller() { return gen(); } + caller(); + )"); + + QVERIFY(!result.isError()); + QVERIFY(!result.isUndefined()); +} + +void tst_QJSEngine::generatorMethodInTailCallPosition() { + QJSEngine engine; + QJSValue result = engine.evaluate(R"( + "use strict"; + class Class { + *gen() { + yield 0; + } + + caller() { return this.gen(); } + } + var c = new Class(); + c.caller(); + )"); + + QVERIFY(!result.isError()); + QVERIFY(!result.isUndefined()); +} + +static unsigned stringListFetchCount = 0; +class StringListProvider : public QObject +{ + Q_OBJECT + Q_PROPERTY(QStringList strings READ strings CONSTANT) + +public: + QStringList strings() const + { + ++stringListFetchCount; + QStringList ret; + for (int i = 0; i < 10; ++i) + ret.append(QString::number(i)); + return ret; + } +}; + +void tst_QJSEngine::consoleLogSequence() +{ + QJSEngine engine; + engine.installExtensions(QJSEngine::ConsoleExtension); + + engine.globalObject().setProperty( + QStringLiteral("object"), engine.newQObject(new StringListProvider)); + + QTest::ignoreMessage(QtDebugMsg, "[0,1,2,3,4,5,6,7,8,9]"); + + engine.evaluate(QStringLiteral("console.log(object.strings)")); + QCOMPARE(stringListFetchCount, 1); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" diff --git a/tests/auto/qml/qmlformat/data/enumWithValues.formatted.qml b/tests/auto/qml/qmlformat/data/enumWithValues.formatted.qml new file mode 100644 index 0000000000..bbf978936f --- /dev/null +++ b/tests/auto/qml/qmlformat/data/enumWithValues.formatted.qml @@ -0,0 +1,12 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Rectangle { + enum AxisAlignment { + Bottom = 0, + Left = 1, + Right = 2 + } +} diff --git a/tests/auto/qml/qmlformat/data/enumWithValues.qml b/tests/auto/qml/qmlformat/data/enumWithValues.qml new file mode 100644 index 0000000000..2dbe7fbac5 --- /dev/null +++ b/tests/auto/qml/qmlformat/data/enumWithValues.qml @@ -0,0 +1,12 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Rectangle{ +enum AxisAlignment{ +Bottom = 0, +Left = 1, +Right = 2 +} +} diff --git a/tests/auto/qml/qmlformat/tst_qmlformat.cpp b/tests/auto/qml/qmlformat/tst_qmlformat.cpp index 0c3a2a276c..877cd92f95 100644 --- a/tests/auto/qml/qmlformat/tst_qmlformat.cpp +++ b/tests/auto/qml/qmlformat/tst_qmlformat.cpp @@ -333,6 +333,9 @@ void TestQmlformat::testFormat_data() QTest::newRow("javascriptBlock") << "javascriptBlock.qml" << "javascriptBlock.formatted.qml" << QStringList{} << RunOption::OnCopy; + QTest::newRow("enumWithValues") + << "enumWithValues.qml" + << "enumWithValues.formatted.qml" << QStringList{} << RunOption::OnCopy; } void TestQmlformat::testFormat() diff --git a/tests/auto/qml/qmllint/CMakeLists.txt b/tests/auto/qml/qmllint/CMakeLists.txt index 422e7a08b3..dafac26475 100644 --- a/tests/auto/qml/qmllint/CMakeLists.txt +++ b/tests/auto/qml/qmllint/CMakeLists.txt @@ -48,3 +48,10 @@ if (TARGET qmljsrootgen) QT_QMLJSROOTGEN_PRESENT ) endif() + +if(QT_FEATURE_process) + add_subdirectory(importRelScript) + add_dependencies(tst_qmllint tst_qmllint_import_rel_script) + target_compile_definitions(tst_qmllint PUBLIC + TST_QMLLINT_IMPORT_REL_SCRIPT_ARGS="@${CMAKE_CURRENT_BINARY_DIR}/importRelScript/.rcc/qmllint/tst_qmllint_import_rel_script.rsp") +endif() diff --git a/tests/auto/qml/qmllint/data/something.qml b/tests/auto/qml/qmllint/data/something.qml new file mode 100644 index 0000000000..38998f606d --- /dev/null +++ b/tests/auto/qml/qmllint/data/something.qml @@ -0,0 +1,2 @@ +import ModuleInImportPath +A {} diff --git a/tests/auto/qml/qmllint/importRelScript/CMakeLists.txt b/tests/auto/qml/qmllint/importRelScript/CMakeLists.txt new file mode 100644 index 0000000000..4b8f26b5bf --- /dev/null +++ b/tests/auto/qml/qmllint/importRelScript/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_policy(SET QTP0001 NEW) + +set(CMAKE_AUTOMOC ON) + +qt_add_qml_module(tst_qmllint_import_rel_script + URI ImportRelScript + QML_FILES + Main.qml + script.js + PLUGIN_TARGET + tst_qmllint_import_rel_script +) + +qt_autogen_tools_initial_setup(tst_qmllint_import_rel_script) +add_dependencies(tst_qmllint_import_rel_script Qt::qmllint) diff --git a/tests/auto/qml/qmllint/importRelScript/Main.qml b/tests/auto/qml/qmllint/importRelScript/Main.qml new file mode 100644 index 0000000000..341f1d730c --- /dev/null +++ b/tests/auto/qml/qmllint/importRelScript/Main.qml @@ -0,0 +1,6 @@ +import QtQml +import "script.js" as JS + +QtObject { + Component.onCompleted: JS.f() +} diff --git a/tests/auto/qml/qmllint/importRelScript/script.js b/tests/auto/qml/qmllint/importRelScript/script.js new file mode 100644 index 0000000000..27b14ed35c --- /dev/null +++ b/tests/auto/qml/qmllint/importRelScript/script.js @@ -0,0 +1,3 @@ +function f() { + return 1 +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index c18c9ff326..56e31dba8f 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -97,10 +97,17 @@ private Q_SLOTS: void testLineEndings(); void ignoreSettingsNotCommandLineOptions(); + void backslashedQmldirPath(); + #if QT_CONFIG(library) void testPlugin(); void quickPlugin(); #endif + +#if QT_CONFIG(process) + void importRelScript(); +#endif + private: enum DefaultImportOption { NoDefaultImports, UseDefaultImports }; enum ContainOption { StringNotContained, StringContained }; @@ -2076,5 +2083,25 @@ void TestQmllint::ignoreSettingsNotCommandLineOptions() QCOMPARE(output, QString()); } +void TestQmllint::backslashedQmldirPath() +{ + const QString qmldirPath + = testFile(u"ImportPath/ModuleInImportPath/qmldir"_s).replace('/', QDir::separator()); + const QString output = runQmllint( + testFile(u"something.qml"_s), true, QStringList{ u"-i"_s, qmldirPath }); + QVERIFY(output.isEmpty()); +} + +#if QT_CONFIG(process) +void TestQmllint::importRelScript() +{ + QProcess proc; + proc.start(m_qmllintPath, { QStringLiteral(TST_QMLLINT_IMPORT_REL_SCRIPT_ARGS) }); + QVERIFY(proc.waitForFinished()); + QVERIFY(proc.readAllStandardOutput().isEmpty()); + QVERIFY(proc.readAllStandardError().isEmpty()); +} +#endif + QTEST_MAIN(TestQmllint) #include "tst_qmllint.moc" diff --git a/tests/auto/qml/qqmlcomponent/data/bindingInRequired.qml b/tests/auto/qml/qqmlcomponent/data/bindingInRequired.qml new file mode 100644 index 0000000000..03d6d0f805 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/bindingInRequired.qml @@ -0,0 +1,16 @@ +import QtQml + +QtObject { + + property Component object: QtObject { + property QtObject obj + } + + property QtObject outer + property QtObject inner + + Component.onCompleted: { + inner = object.createObject(this, { obj: null }) + outer = object.createObject(this, { obj: Qt.binding(() => inner) }) + } +} diff --git a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp index 288b4d1a01..1ccf7a6f23 100644 --- a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp +++ b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp @@ -148,6 +148,7 @@ private slots: void loadFromQrc(); void removeBinding(); void complexObjectArgument(); + void bindingInRequired(); private: QQmlEngine engine; @@ -1506,6 +1507,24 @@ void tst_qqmlcomponent::complexObjectArgument() QCOMPARE(o->objectName(), QStringLiteral("26 - 25")); } +void tst_qqmlcomponent::bindingInRequired() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("bindingInRequired.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QObject *outer = object->property("outer").value<QObject *>(); + QVERIFY(outer); + + QObject *inner = object->property("inner").value<QObject *>(); + QVERIFY(inner); + + QCOMPARE(inner, outer->property("obj").value<QObject *>()); + QVERIFY(!inner->property("obj").value<QObject *>()); +} + QTEST_MAIN(tst_qqmlcomponent) #include "tst_qqmlcomponent.moc" diff --git a/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt b/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt index 705ee6f357..edf0a1165b 100644 --- a/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt +++ b/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt @@ -23,6 +23,7 @@ qt_internal_add_test(tst_qqmldelegatemodel Qt::QmlModelsPrivate Qt::QmlPrivate Qt::Quick + Qt::QuickPrivate Qt::QuickTestUtilsPrivate TESTDATA ${test_data} ) diff --git a/tests/auto/qml/qqmldelegatemodel/data/reset.qml b/tests/auto/qml/qqmldelegatemodel/data/reset.qml new file mode 100644 index 0000000000..0fcd5e8afa --- /dev/null +++ b/tests/auto/qml/qqmldelegatemodel/data/reset.qml @@ -0,0 +1,28 @@ +import QtQuick +import Test + +Window { + id: root + width: 200 + height: 200 + + property alias listView: listView + + ResettableModel { + id: resetModel + } + + ListView { + id: listView + anchors.fill: parent + model: resetModel + + delegate: Rectangle { + implicitWidth: 100 + implicitHeight: 50 + color: "olivedrab" + + required property string display + } + } +} diff --git a/tests/auto/qml/qqmldelegatemodel/data/resetInQAIMConstructor.qml b/tests/auto/qml/qqmldelegatemodel/data/resetInQAIMConstructor.qml new file mode 100644 index 0000000000..cb1f226737 --- /dev/null +++ b/tests/auto/qml/qqmldelegatemodel/data/resetInQAIMConstructor.qml @@ -0,0 +1,28 @@ +import QtQuick +import Test + +Window { + id: root + width: 200 + height: 200 + + property alias listView: listView + + ResetInConstructorModel { + id: resetInConstructorModel + } + + ListView { + id: listView + anchors.fill: parent + model: resetInConstructorModel + + delegate: Rectangle { + implicitWidth: 100 + implicitHeight: 50 + color: "olivedrab" + + required property string display + } + } +} diff --git a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp index 1f85f68891..e9c22ca1e6 100644 --- a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp +++ b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp @@ -3,6 +3,7 @@ #include <QtTest/qtest.h> #include <QtCore/QConcatenateTablesProxyModel> +#include <QtCore/qtimer.h> #include <QtGui/QStandardItemModel> #include <QtQml/qqmlcomponent.h> #include <QtQml/qqmlapplicationengine.h> @@ -10,9 +11,16 @@ #include <QtQmlModels/private/qqmllistmodel_p.h> #include <QtQuick/qquickview.h> #include <QtQuick/qquickitem.h> +#include <QtQuick/private/qquickitemview_p_p.h> +#include <QtQuick/private/qquicklistview_p.h> +#include <QtQuickTest/quicktest.h> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtTest/QSignalSpy> + +using namespace QQuickVisualTestUtils; + class tst_QQmlDelegateModel : public QQmlDataTest { Q_OBJECT @@ -22,6 +30,8 @@ public: private slots: void resettingRolesRespected(); + void resetInQAIMConstructor(); + void reset(); void valueWithoutCallingObjectFirst_data(); void valueWithoutCallingObjectFirst(); void qtbug_86017(); @@ -36,16 +46,9 @@ private slots: void clearCacheDuringInsertion(); }; -class AbstractItemModel : public QAbstractItemModel +class BaseAbstractItemModel : public QAbstractItemModel { - Q_OBJECT public: - AbstractItemModel() - { - for (int i = 0; i < 3; ++i) - mValues.append(QString::fromLatin1("Item %1").arg(i)); - } - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { if (parent.isValid()) @@ -83,10 +86,21 @@ public: return mValues.at(index.row()); } -private: +protected: QVector<QString> mValues; }; +class AbstractItemModel : public BaseAbstractItemModel +{ + Q_OBJECT +public: + AbstractItemModel() + { + for (int i = 0; i < 3; ++i) + mValues.append(QString::fromLatin1("Item %1").arg(i)); + } +}; + tst_QQmlDelegateModel::tst_QQmlDelegateModel() : QQmlDataTest(QT_QMLTEST_DATADIR) { @@ -145,7 +159,109 @@ void tst_QQmlDelegateModel::resettingRolesRespected() QObject *root = engine.rootObjects().constFirst(); QVERIFY(!root->property("success").toBool()); model->change(); - QTRY_VERIFY(root->property("success").toBool()); + QTRY_VERIFY_WITH_TIMEOUT(root->property("success").toBool(), 100); +} + +class ResetInConstructorModel : public BaseAbstractItemModel +{ + Q_OBJECT + QML_ELEMENT + +public: + ResetInConstructorModel() + { + beginResetModel(); + QTimer::singleShot(0, this, &ResetInConstructorModel::finishReset); + } + +private: + void finishReset() + { + mValues.append("First"); + endResetModel(); + } +}; + +void tst_QQmlDelegateModel::resetInQAIMConstructor() +{ + qmlRegisterTypesAndRevisions<ResetInConstructorModel>("Test", 1); + + QQuickApplicationHelper helper(this, "resetInQAIMConstructor.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *listView = window->property("listView").value<QQuickListView *>(); + QVERIFY(listView); + QTRY_VERIFY_WITH_TIMEOUT(listView->itemAtIndex(0), 100); + QQuickItem *firstDelegateItem = listView->itemAtIndex(0); + QVERIFY(firstDelegateItem); + QCOMPARE(firstDelegateItem->property("display").toString(), "First"); +} + +class ResettableModel : public BaseAbstractItemModel +{ + Q_OBJECT + QML_ELEMENT + +public: + ResettableModel() + { + mValues.append("First"); + } + + void callBeginResetModel() + { + beginResetModel(); + mValues.clear(); + } + + void appendData() + { + mValues.append(QString::fromLatin1("Item %1").arg(mValues.size())); + } + + void callEndResetModel() + { + endResetModel(); + } +}; + +// Tests that everything works as expected when calling beginResetModel/endResetModel +// after the QAIM subclass constructor has run. +void tst_QQmlDelegateModel::reset() +{ + qmlRegisterTypesAndRevisions<ResettableModel>("Test", 1); + + QQuickApplicationHelper helper(this, "reset.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *listView = window->property("listView").value<QQuickListView *>(); + QVERIFY(listView); + QQuickItem *firstDelegateItem = listView->itemAtIndex(0); + QVERIFY(firstDelegateItem); + QCOMPARE(firstDelegateItem->property("display").toString(), "First"); + + const auto delegateModel = QQuickItemViewPrivate::get(listView)->model; + QSignalSpy rootIndexChangedSpy(delegateModel, SIGNAL(rootIndexChanged())); + QVERIFY(rootIndexChangedSpy.isValid()); + + auto *model = listView->model().value<ResettableModel *>(); + model->callBeginResetModel(); + model->appendData(); + model->callEndResetModel(); + // This is verifies that handleModelReset isn't called + // more than once during this process, since it unconditionally emits rootIndexChanged. + QCOMPARE(rootIndexChangedSpy.count(), 1); + + QTRY_VERIFY_WITH_TIMEOUT(listView->itemAtIndex(0), 100); + firstDelegateItem = listView->itemAtIndex(0); + QVERIFY(firstDelegateItem); + QCOMPARE(firstDelegateItem->property("display").toString(), "Item 0"); } void tst_QQmlDelegateModel::valueWithoutCallingObjectFirst_data() diff --git a/tests/auto/qml/qqmlecmascript/data/lookupsDoNotBypassProxy.qml b/tests/auto/qml/qqmlecmascript/data/lookupsDoNotBypassProxy.qml new file mode 100644 index 0000000000..321bd21ad8 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/lookupsDoNotBypassProxy.qml @@ -0,0 +1,29 @@ +import QtQml + +QtObject { + function test_proxy() { + let base = { + id: 'baseid', + name: 'basename', + length: 42 + }; + + let handler = { + get: function (ao, prop) { + return Reflect.get(ao, prop); + } + }; + + let r = new Proxy(base, handler); + let validCount = 0; + if (r.id === base.id) + ++validCount; + if (r.length === base.length) + ++validCount; + if (r.name === base.name) + ++validCount; + return validCount; + } + + property int result: test_proxy() +} diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 4023824f75..63655fa60d 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -400,6 +400,7 @@ private slots: void sequenceConversionMethod(); void proxyIteration(); void proxyHandlerTraps(); + void lookupsDoNotBypassProxy(); void gcCrashRegressionTest(); void cmpInThrows(); void frozenQObject(); @@ -921,7 +922,9 @@ void tst_qqmlecmascript::bindingLoop() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("bindingLoop.qml")); - QString warning = component.url().toString() + ":9:9: QML MyQmlObject: Binding loop detected for property \"stringProperty\""; + const auto urlString = component.url().toString(); + const QString warning = urlString + ":9:9: QML MyQmlObject: Binding loop detected for property \"stringProperty\":\n" + + urlString + ":11:13"; QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); QScopedPointer<QObject> object(component.create()); QVERIFY2(object, qPrintable(component.errorString())); @@ -10056,6 +10059,17 @@ void tst_qqmlecmascript::proxyHandlerTraps() QVERIFY(value.isString() && value.toString() == QStringLiteral("SUCCESS")); } +void tst_qqmlecmascript::lookupsDoNotBypassProxy() +{ + QQmlEngine engine; + // we need a component to have a proper compilation to byte code; + // otherwise, we don't actually end up with lookups + QQmlComponent comp(&engine, testFileUrl("lookupsDoNotBypassProxy.qml")); + QVERIFY(comp.isReady()); + std::unique_ptr<QObject> obj { comp.create() }; + QCOMPARE(obj->property("result").toInt(), 3); +} + void tst_qqmlecmascript::cmpInThrows() { QJSEngine engine; diff --git a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp index 00d28cfe60..b82a1f4174 100644 --- a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp +++ b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp @@ -1337,6 +1337,8 @@ void tst_qqmlengine::createComponentOnSingletonDestruction() void tst_qqmlengine::uiLanguage() { + const QRegularExpression bindingLoopWarningRegex(".*QML QtObject: Binding loop detected for property \"textToTranslate\".*"); + { QQmlEngine engine; @@ -1348,19 +1350,19 @@ void tst_qqmlengine::uiLanguage() QQmlComponent component(&engine, testFileUrl("uiLanguage.qml")); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, (component.url().toString() + ":2:1: QML QtObject: Binding loop detected for property \"textToTranslate\"").toLatin1()); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, bindingLoopWarningRegex); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); QVERIFY(engine.uiLanguage().isEmpty()); QCOMPARE(object->property("numberOfTranslationBindingEvaluations").toInt(), 1); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, (component.url().toString() + ":2:1: QML QtObject: Binding loop detected for property \"textToTranslate\"").toLatin1()); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, bindingLoopWarningRegex); engine.setUiLanguage("TestLanguage"); QCOMPARE(object->property("numberOfTranslationBindingEvaluations").toInt(), 2); QCOMPARE(object->property("chosenLanguage").toString(), "TestLanguage"); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, (component.url().toString() + ":2:1: QML QtObject: Binding loop detected for property \"textToTranslate\"").toLatin1()); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, bindingLoopWarningRegex); engine.evaluate("Qt.uiLanguage = \"anotherLanguage\""); QCOMPARE(engine.uiLanguage(), QString("anotherLanguage")); QCOMPARE(object->property("numberOfTranslationBindingEvaluations").toInt(), 3); @@ -1371,7 +1373,7 @@ void tst_qqmlengine::uiLanguage() QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("uiLanguage.qml")); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, (component.url().toString() + ":2:1: QML QtObject: Binding loop detected for property \"textToTranslate\"").toLatin1()); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, bindingLoopWarningRegex); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); diff --git a/tests/auto/qml/qqmlinfo/tst_qqmlinfo.cpp b/tests/auto/qml/qqmlinfo/tst_qqmlinfo.cpp index 4908ca210b..46154640d0 100644 --- a/tests/auto/qml/qqmlinfo/tst_qqmlinfo.cpp +++ b/tests/auto/qml/qqmlinfo/tst_qqmlinfo.cpp @@ -220,7 +220,8 @@ void tst_qqmlinfo::attachedObject() const QString qmlBindingLoopMessageFull = component.url().toString() + ":8:9: " + qmlBindingLoopMessage; QTest::ignoreMessage(QtWarningMsg, qPrintable(qmlBindingLoopMessageFull)); - const QString cppBindingLoopMessage = "QML AttachedObject (parent or ancestor of Attached): Binding loop detected for property \"a\""; + const QString cppBindingLoopMessage = "QML AttachedObject (parent or ancestor of Attached): Binding loop detected for property \"a\":\n" + + component.url().toString() + ":5:5"; const QString cppBindingLoopMessageFull = component.url().toString() + ":4:1: " + cppBindingLoopMessage; QTest::ignoreMessage(QtWarningMsg, qPrintable(cppBindingLoopMessageFull)); diff --git a/tests/auto/qml/qqmllanguage/CMakeLists.txt b/tests/auto/qml/qqmllanguage/CMakeLists.txt index e07f741bf6..fc9bbf17df 100644 --- a/tests/auto/qml/qqmllanguage/CMakeLists.txt +++ b/tests/auto/qml/qqmllanguage/CMakeLists.txt @@ -27,6 +27,8 @@ qt_internal_add_test(tst_qqmllanguage TESTDATA ${test_data} ) +add_subdirectory(testhelper) + #### Keys ignored in scope 1:.:.:qqmllanguage.pro:<TRUE>: # OTHER_FILES = "data/readonlyObjectProperty.qml" # QML_IMPORT_NAME = "StaticTest" 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/SingletonWithRequiredProperties/SingletonWithRequired1.qml b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired1.qml new file mode 100644 index 0000000000..f549e851a3 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired1.qml @@ -0,0 +1,6 @@ +pragma Singleton +import QtQml + +QtObject { + required property int i +} diff --git a/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired2.qml b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired2.qml new file mode 100644 index 0000000000..1f9e7e3a42 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired2.qml @@ -0,0 +1,8 @@ +pragma Singleton +import QtQml + +QtObject { + property QtObject o: QtObject { + required property int i + } +} diff --git a/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/qmldir b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/qmldir new file mode 100644 index 0000000000..46e397ca76 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/qmldir @@ -0,0 +1,4 @@ +module SingletonWithRequiredProperties + +singleton SingletonWithRequired1 1.0 SingletonWithRequired1.qml +singleton SingletonWithRequired2 1.0 SingletonWithRequired2.qml diff --git a/tests/auto/qml/qqmllanguage/data/nestedVectors.qml b/tests/auto/qml/qqmllanguage/data/nestedVectors.qml new file mode 100644 index 0000000000..0bcea52133 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/nestedVectors.qml @@ -0,0 +1,27 @@ +import Test +import QtQml + +NestedVectors { + id: self + + property var list1 + + Component.onCompleted: { + list1 = self.getList() + + let list2 = [] + let data1 = [] + data1.push(2) + data1.push(3) + data1.push(4) + + let data2 = [] + data2.push(5) + data2.push(6) + + list2.push(data1) + list2.push(data2) + + self.setList(list2) + } +} diff --git a/tests/auto/qml/qqmllanguage/testhelper/CMakeLists.txt b/tests/auto/qml/qqmllanguage/testhelper/CMakeLists.txt new file mode 100644 index 0000000000..6a58889335 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/testhelper/CMakeLists.txt @@ -0,0 +1,13 @@ +qt_policy(SET QTP0001 NEW) +qt_add_library(tst_qqmllanguage_qmlmodule STATIC) +qt_autogen_tools_initial_setup(tst_qqmllanguage_qmlmodule) +qt_add_qml_module(tst_qqmllanguage_qmlmodule + URI testhelper + VERSION 1.0 + SOURCES + "declarativelyregistered.h" + "declarativelyregistered.cpp" +) + +qt_autogen_tools_initial_setup(tst_qqmllanguage_qmlmoduleplugin) +target_link_libraries(tst_qqmllanguage PRIVATE tst_qqmllanguage_qmlmoduleplugin) diff --git a/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.cpp b/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.cpp new file mode 100644 index 0000000000..24fcd83d42 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.cpp @@ -0,0 +1,7 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "declarativelyregistered.h" + +PurelyDeclarativeSingleton::PurelyDeclarativeSingleton() = default; + +#include "moc_declarativelyregistered.cpp" diff --git a/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.h b/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.h new file mode 100644 index 0000000000..4845cc68b9 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.h @@ -0,0 +1,20 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DECLARATIVELYREGISTERED_LANGUAGE_H +#define DECLARATIVELYREGISTERED_LANGUAGE_H + +#include <QtCore/qobject.h> +#include <QtQml/qqmlregistration.h> + +class PurelyDeclarativeSingleton : public QObject +{ + Q_OBJECT + QML_SINGLETON + QML_ELEMENT +public: + PurelyDeclarativeSingleton(); +}; + + +#endif diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index e796fb6cd5..12fe042c20 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -160,6 +160,7 @@ void registerTypes() qmlRegisterTypesAndRevisions<ByteArrayReceiver>("Test", 1); qmlRegisterTypesAndRevisions<Counter>("Test", 1); + qmlRegisterTypesAndRevisions<NestedVectors>("Test", 1); } QVariant myCustomVariantTypeConverter(const QString &data) diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index fe1a825e87..bfee2d07c9 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -2608,4 +2608,36 @@ public: } }; +class NestedVectors : public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + NestedVectors(QObject *parent = nullptr) : QObject(parent) + { + std::vector<int> data; + data.push_back(1); + data.push_back(2); + data.push_back(3); + m_list.push_back(data); + data.clear(); + data.push_back(4); + data.push_back(5); + m_list.push_back(data); + } + + Q_INVOKABLE std::vector<std::vector<int>> getList() + { + return m_list; + } + + Q_INVOKABLE void setList(std::vector<std::vector<int>> list) + { + m_list = list; + } + +private: + std::vector<std::vector<int>> m_list; +}; + #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 7dd68df62e..7992896506 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -248,6 +248,8 @@ private slots: void compositeSingletonSelectors(); void compositeSingletonRegistered(); void compositeSingletonCircular(); + void compositeSingletonRequiredProperties(); + void compositeSingletonRequiredProperties_data(); void singletonsHaveContextAndEngine(); @@ -429,6 +431,10 @@ private slots: void typedObjectList(); + void nestedVectors(); + + void overrideInnerBinding(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -1920,9 +1926,9 @@ void tst_qqmllanguage::valueTypes() QQmlComponent component(&engine, testFileUrl("valueTypes.qml")); VERIFY_ERRORS(0); - QString message = component.url().toString() + ":2:1: QML MyTypeObject: Binding loop detected for property \"rectProperty.width\""; - QTest::ignoreMessage(QtWarningMsg, qPrintable(message)); - QTest::ignoreMessage(QtWarningMsg, qPrintable(message)); + const auto bindingLoopRegex = QRegularExpression(".*QML MyTypeObject: Binding loop detected for property \"rectProperty.width\".*"); + QTest::ignoreMessage(QtWarningMsg, bindingLoopRegex); + QTest::ignoreMessage(QtWarningMsg, bindingLoopRegex); QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create())); QVERIFY(object != nullptr); @@ -4867,6 +4873,36 @@ void tst_qqmllanguage::compositeSingletonCircular() QCOMPARE(o->property("value").toInt(), 2); } +void tst_qqmllanguage::compositeSingletonRequiredProperties() +{ + QFETCH(QString, warning); + QFETCH(QString, singletonName); + QQmlEngine engine; + engine.addImportPath(dataDirectory()); + { + QTest::ignoreMessage(QtMsgType::QtWarningMsg, qPrintable(warning)); + std::unique_ptr<QObject> singleton {engine.singletonInstance<QObject *>( + "SingletonWithRequiredProperties", + singletonName + )}; + QVERIFY(!singleton); + } +} + +void tst_qqmllanguage::compositeSingletonRequiredProperties_data() +{ + QTest::addColumn<QString>("warning"); + QTest::addColumn<QString>("singletonName"); + + QString warning1 = testFileUrl("SingletonWithRequiredProperties/SingletonWithRequired1.qml").toString() + + ":5:5: Required property i was not initialized"; + QString warning2 = testFileUrl("SingletonWithRequiredProperties/SingletonWithRequired2.qml").toString() + + ":6:9: Required property i was not initialized"; + + QTest::addRow("toplevelRequired") << warning1 << "SingletonWithRequired1"; + QTest::addRow("subObjectRequired") << warning2 << "SingletonWithRequired2"; +} + void tst_qqmllanguage::singletonsHaveContextAndEngine() { QObject *qmlSingleton = nullptr; @@ -5677,6 +5713,9 @@ void tst_qqmllanguage::retrieveQmlTypeId() QVERIFY(qmlTypeId("Test", 1, 0, "MyExtendedUncreateableBaseClass") >= 0); QVERIFY(qmlTypeId("Test", 1, 0, "MyUncreateableBaseClass") >= 0); QVERIFY(qmlTypeId("Test", 1, 0, "MyTypeObjectSingleton") >= 0); + + // Must also work for declaratively registered types whose module wasn't imported so far + QVERIFY(qmlTypeId("testhelper", 1, 0, "PurelyDeclarativeSingleton") >= 0); } void tst_qqmllanguage::polymorphicFunctionLookup() @@ -8194,6 +8233,37 @@ void tst_qqmllanguage::typedObjectList() QVERIFY(list.at(&list, 0) != nullptr); } +void tst_qqmllanguage::nestedVectors() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("nestedVectors.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + NestedVectors *n = qobject_cast<NestedVectors *>(o.data()); + QVERIFY(n); + + const std::vector<std::vector<int>> expected1 { { 1, 2, 3 }, { 4, 5 } }; + const QVariant list1 = n->property("list1"); + QCOMPARE(e.fromVariant<std::vector<std::vector<int>>>(list1), expected1); + + const std::vector<std::vector<int>> expected2 { { 2, 3, 4 }, { 5, 6 } }; + QCOMPARE(n->getList(), expected2); +} + +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 6fd173fb0c..8f0e657e54 100644 --- a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp +++ b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp @@ -119,6 +119,7 @@ private slots: void objectOwnershipFlip(); void enumsInListElement(); void protectQObjectFromGC(); + void deadModelData(); }; bool tst_qqmllistmodel::compareVariantList(const QVariantList &testList, QVariant object) @@ -1931,6 +1932,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/qqmllistreference/data/compositeListProp.qml b/tests/auto/qml/qqmllistreference/data/compositeListProp.qml index 99965f289b..7aa3d0845a 100644 --- a/tests/auto/qml/qqmllistreference/data/compositeListProp.qml +++ b/tests/auto/qml/qqmllistreference/data/compositeListProp.qml @@ -1,5 +1,14 @@ import QtQml QtObject { - property list<AListItem> items + id: self + property list<AListItem> items: [ self ] + + Component.onCompleted: { + items.push(self); + items.push(null); + items[2] = self; + items.splice(1, 0, self); + items.unshift(self); + } } diff --git a/tests/auto/qml/qqmllistreference/tst_qqmllistreference.cpp b/tests/auto/qml/qqmllistreference/tst_qqmllistreference.cpp index 66c17aa0d5..4d633bb3ab 100644 --- a/tests/auto/qml/qqmllistreference/tst_qqmllistreference.cpp +++ b/tests/auto/qml/qqmllistreference/tst_qqmllistreference.cpp @@ -862,9 +862,31 @@ void tst_qqmllistreference::compositeListProperty() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("compositeListProp.qml")); + + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression("Cannot append QObject_QML_[0-9]+\\(0x[0-9a-f]+\\) " + "to a QML list of AListItem_QMLTYPE_[0-9]+\\*")); + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression("Cannot append QObject_QML_[0-9]+\\(0x[0-9a-f]+\\) " + "to a QML list of AListItem_QMLTYPE_[0-9]+\\*")); + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression("Cannot insert QObject_QML_[0-9]+\\(0x[0-9a-f]+\\) " + "into a QML list of AListItem_QMLTYPE_[0-9]+\\*")); + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression("Cannot splice QObject_QML_[0-9]+\\(0x[0-9a-f]+\\) " + "into a QML list of AListItem_QMLTYPE_[0-9]+\\*")); + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression("Cannot unshift QObject_QML_[0-9]+\\(0x[0-9a-f]+\\) " + "into a QML list of AListItem_QMLTYPE_[0-9]+\\*")); + QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); + QQmlListReference list1(object.data(), "items"); + QCOMPARE(list1.size(), 5); + for (qsizetype i = 0; i < 5; ++i) + QCOMPARE(list1.at(i), nullptr); + QQmlComponent item(&engine, testFileUrl("AListItem.qml")); QScopedPointer<QObject> i1(item.create()); QScopedPointer<QObject> i2(item.create()); @@ -872,7 +894,6 @@ void tst_qqmllistreference::compositeListProperty() QVERIFY(!i2.isNull()); // We know the element type now. - QQmlListReference list1(object.data(), "items"); QVERIFY(list1.listElementType() != nullptr); QVERIFY(list1.append(i1.data())); QVERIFY(list1.replace(0, i2.data())); diff --git a/tests/auto/qml/qqmltablemodel/CMakeLists.txt b/tests/auto/qml/qqmltablemodel/CMakeLists.txt index ebd3e94835..2800fd642c 100644 --- a/tests/auto/qml/qqmltablemodel/CMakeLists.txt +++ b/tests/auto/qml/qqmltablemodel/CMakeLists.txt @@ -23,6 +23,7 @@ qt_internal_add_test(tst_qqmltablemodel Qt::Quick Qt::QuickPrivate Qt::QuickTestUtilsPrivate + Qt::LabsQmlModelsPrivate TESTDATA ${test_data} ) diff --git a/tests/auto/qml/qqmltablemodel/tst_qqmltablemodel.cpp b/tests/auto/qml/qqmltablemodel/tst_qqmltablemodel.cpp index cd6eb3e9c5..860c782fd3 100644 --- a/tests/auto/qml/qqmltablemodel/tst_qqmltablemodel.cpp +++ b/tests/auto/qml/qqmltablemodel/tst_qqmltablemodel.cpp @@ -13,6 +13,7 @@ #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/viewtestutils_p.h> +#include <QtLabsQmlModels/private/qqmltablemodel_p.h> class tst_QQmlTableModel : public QQmlDataTest { @@ -179,6 +180,18 @@ void tst_QQmlTableModel::appendRemoveRow() QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")), QVariant()); QCOMPARE(columnCountSpy.size(), 0); QCOMPARE(rowCountSpy.size(), ++rowCountSignalEmissions); + + QVariantMap variantMap; + variantMap["name"] = QLatin1String("VariantMap"); + variantMap["age"] = int(QMetaType::QVariantMap); + auto *tableModel = view.rootObject()->property("testModel") .value<QQmlTableModel *>(); + tableModel->appendRow(QVariant::fromValue(variantMap)); + QCOMPARE(model->rowCount(), 1); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("VariantMap")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), int(QMetaType::QVariantMap)); + QCOMPARE(columnCountSpy.size(), 0); + QCOMPARE(rowCountSpy.size(), ++rowCountSignalEmissions); } void tst_QQmlTableModel::appendRowToEmptyModel() @@ -432,6 +445,31 @@ void tst_QQmlTableModel::insertRow() QCOMPARE(rowCountSpy.size(), ++rowCountSignalEmissions); QTRY_COMPARE(tableView->rows(), 5); QCOMPARE(tableView->columns(), 2); + + // Pass variant map object to qml table model + QVariantMap variantMap; + variantMap["name"] = QLatin1String("VariantMap"); + variantMap["age"] = int(QMetaType::QVariantMap); + auto *tableModel = view.rootObject()->property("testModel") .value<QQmlTableModel *>(); + tableModel->insertRow(5, QVariant::fromValue(variantMap)); + QCOMPARE(model->rowCount(), 6); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 30); + QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 40); + QCOMPARE(model->data(model->index(5, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("VariantMap")); + QCOMPARE(model->data(model->index(5, 1, QModelIndex()), roleNames.key("display")).toInt(), int(QMetaType::QVariantMap)); + QCOMPARE(columnCountSpy.size(), 0); + QCOMPARE(rowCountSpy.size(), ++rowCountSignalEmissions); + QTRY_COMPARE(tableView->rows(), 6); + QCOMPARE(tableView->columns(), 2); } void tst_QQmlTableModel::moveRow() @@ -716,6 +754,24 @@ void tst_QQmlTableModel::setRow() QCOMPARE(rowCountSpy.size(), ++rowCountSignalEmissions); QTRY_COMPARE(tableView->rows(), 3); QCOMPARE(tableView->columns(), 2); + + QVariantMap variantMap; + variantMap["name"] = QLatin1String("VariantMap"); + variantMap["age"] = int(QMetaType::QVariantMap); + auto *tableModel = view.rootObject()->property("testModel") .value<QQmlTableModel *>(); + tableModel->setRow(0, QVariant::fromValue(variantMap)); + QCOMPARE(model->rowCount(), 3); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("VariantMap")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), int(QMetaType::QVariantMap)); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 30); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Wot")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(columnCountSpy.size(), 0); + QCOMPARE(rowCountSpy.size(), rowCountSignalEmissions); + QTRY_COMPARE(tableView->rows(), 3); + QCOMPARE(tableView->columns(), 2); } void tst_QQmlTableModel::setDataThroughDelegate() diff --git a/tests/auto/qml/qv4estable/tst_qv4estable.cpp b/tests/auto/qml/qv4estable/tst_qv4estable.cpp index 45df62b23e..7d137ae7d2 100644 --- a/tests/auto/qml/qv4estable/tst_qv4estable.cpp +++ b/tests/auto/qml/qv4estable/tst_qv4estable.cpp @@ -18,7 +18,7 @@ void tst_qv4estable::checkRemoveAvoidsHeapBufferOverflow() QV4::ESTable estable; // Fill the ESTable with values so it is at max capacity. - QCOMPARE_EQ(estable.m_capacity, 8); + QCOMPARE_EQ(estable.m_capacity, 8U); for (uint i = 0; i < estable.m_capacity; ++i) { estable.set(QV4::Value::fromUInt32(i), QV4::Value::fromUInt32(i)); } @@ -27,8 +27,8 @@ void tst_qv4estable::checkRemoveAvoidsHeapBufferOverflow() for (uint i = 0; i < estable.m_capacity; ++i) { QVERIFY(estable.m_keys[i].sameValueZero(QV4::Value::fromUInt32(i))); } - QCOMPARE_EQ(estable.m_capacity, 8); - QCOMPARE_EQ(estable.m_size, 8); + QCOMPARE_EQ(estable.m_capacity, 8U); + QCOMPARE_EQ(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. diff --git a/tests/auto/qmltest/listview/tst_listview.qml b/tests/auto/qmltest/listview/tst_listview.qml index e9c91e10f2..039114114b 100644 --- a/tests/auto/qmltest/listview/tst_listview.qml +++ b/tests/auto/qmltest/listview/tst_listview.qml @@ -38,12 +38,16 @@ Item { ListView { id: modelchange + width: 100 + height: 100 model: firstmodel delegate: Text { text: model.name } } ListView { id: modelalter + width: 100 + height: 100 model: altermodel delegate: Text { text: model.name } } diff --git a/tests/auto/qmltest/selftests/tst_findChild.qml b/tests/auto/qmltest/selftests/tst_findChild.qml index c8af04810a..3232191150 100644 --- a/tests/auto/qmltest/selftests/tst_findChild.qml +++ b/tests/auto/qmltest/selftests/tst_findChild.qml @@ -71,6 +71,8 @@ TestCase { } ListView { + width: 100 + height: 100 model: 5 delegate: Item { objectName: "listViewItem" + index diff --git a/tests/auto/quick/doc/how-tos/how-to-qml/tst_how-to-qml.cpp b/tests/auto/quick/doc/how-tos/how-to-qml/tst_how-to-qml.cpp index fdc8981932..4c6e331efa 100644 --- a/tests/auto/quick/doc/how-tos/how-to-qml/tst_how-to-qml.cpp +++ b/tests/auto/quick/doc/how-tos/how-to-qml/tst_how-to-qml.cpp @@ -60,6 +60,7 @@ void tst_HowToQml::activeFocusDebugging() auto *window = qobject_cast<QQuickWindow*>(engine.rootObjects().at(0)); window->show(); + window->requestActivate(); QTest::ignoreMessage(QtDebugMsg, QRegularExpression("activeFocusItem: .*\"ActiveFocusDebuggingMain\"")); QVERIFY(QTest::qWaitForWindowActive(window)); diff --git a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp index 0a126a13e8..ce5473c8a5 100644 --- a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp +++ b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp @@ -101,6 +101,7 @@ private slots: void restartAnimationGroupWhenDirty(); void restartNestedAnimationGroupWhenDirty(); void targetsDeletedNotRemoved(); + void alwaysRunToEndSetFalseRestartBug(); }; #define QTIMED_COMPARE(lhs, rhs) do { \ @@ -2295,6 +2296,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/qquickapplication/tst_qquickapplication.cpp b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp index a440c0c2f8..b3633a9dcc 100644 --- a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp +++ b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp @@ -246,7 +246,7 @@ void tst_qquickapplication::styleHints() { // technically not in QQuickApplication, but testing anyway here QQmlComponent component(&engine); - component.setData("import QtQuick 2.0; Item { property variant styleHints: Qt.styleHints }", QUrl::fromLocalFile("")); + component.setData("import QtQuick 2.0; Item { property variant styleHints: Application.styleHints }", QUrl::fromLocalFile("")); QQuickItem *item = qobject_cast<QQuickItem *>(component.create()); QVERIFY(item); QQuickView view; diff --git a/tests/auto/quick/qquickdragattached/tst_qquickdragattached.cpp b/tests/auto/quick/qquickdragattached/tst_qquickdragattached.cpp index 5ce72a747f..d82ebe77c2 100644 --- a/tests/auto/quick/qquickdragattached/tst_qquickdragattached.cpp +++ b/tests/auto/quick/qquickdragattached/tst_qquickdragattached.cpp @@ -44,6 +44,9 @@ void tst_QQuickDragAttached::setMimeData_data() QTest::addRow("text/uri-list, string") << makeMap("text/uri-list", QString("https://qt-project.org")) << QStringList{"text/uri-list"}; + QTest::addRow("text/uri-list, RFC2483 string") + << makeMap("text/uri-list", QString("https://qt-project.org\r\nhttps://www.test.com")) + << QStringList{"text/uri-list"}; QTest::addRow("text/uri-list, strings") << makeMap("text/uri-list", QStringList{"file://foo", "https://www.test.com"}) << QStringList{"text/uri-list"}; diff --git a/tests/auto/quick/qquickimage/tst_qquickimage.cpp b/tests/auto/quick/qquickimage/tst_qquickimage.cpp index 343bde5f5f..ca2314c336 100644 --- a/tests/auto/quick/qquickimage/tst_qquickimage.cpp +++ b/tests/auto/quick/qquickimage/tst_qquickimage.cpp @@ -180,6 +180,11 @@ void tst_qquickimage::imageSource() QFETCH(bool, cache); QFETCH(QString, error); +#if !QT_CONFIG(qml_network) + if (remote) + QSKIP("Skipping due to lack of QML network feature"); +#endif + TestHTTPServer server; if (remote) { QVERIFY2(server.listen(), qPrintable(server.errorString())); @@ -550,6 +555,10 @@ void tst_qquickimage::tiling_QTBUG_6716_data() void tst_qquickimage::noLoading() { +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + qRegisterMetaType<QQuickImageBase::Status>(); TestHTTPServer server; @@ -692,6 +701,10 @@ void tst_qquickimage::sourceSize_QTBUG_16389() // QTBUG-15690 void tst_qquickimage::nullPixmapPaint() { +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + QScopedPointer<QQuickView> window(new QQuickView(nullptr)); window->setSource(testFileUrl("nullpixmap.qml")); window->show(); @@ -714,6 +727,10 @@ void tst_qquickimage::nullPixmapPaint() void tst_qquickimage::imageCrash_QTBUG_22125() { +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + TestHTTPServer server; QVERIFY2(server.listen(), qPrintable(server.errorString())); server.serveDirectory(dataDirectory(), TestHTTPServer::Delay); @@ -826,6 +843,10 @@ void tst_qquickimage::sourceSizeChanges() QTRY_COMPARE(sourceSizeSpy.size(), 3); // Remote +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + ctxt->setContextProperty("srcImage", server.url("/heart.png")); QTRY_COMPARE(img->status(), QQuickImage::Ready); QTRY_COMPARE(sourceSizeSpy.size(), 4); @@ -959,6 +980,10 @@ void tst_qquickimage::progressAndStatusChanges() QTRY_COMPARE(statusSpy.size(), 1); // Loading remote file +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + ctxt->setContextProperty("srcImage", server.url("/heart.png")); QTRY_COMPARE(obj->status(), QQuickImage::Loading); QTRY_COMPARE(obj->progress(), 0.0); diff --git a/tests/auto/quick/qquickitem/tst_qquickitem.cpp b/tests/auto/quick/qquickitem/tst_qquickitem.cpp index 061dea3ad7..1db150e675 100644 --- a/tests/auto/quick/qquickitem/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem/tst_qquickitem.cpp @@ -1527,7 +1527,7 @@ void tst_qquickitem::polishLoopDetection_data() QTest::newRow("test1.100") << PolishItemSpans({ {1, 100} }) << 0; QTest::newRow("test1.1002") << PolishItemSpans({ {1, 1002} }) << 3; - QTest::newRow("test1.2020") << PolishItemSpans({ {1, 2020} }) << 10; + QTest::newRow("test1.2020") << PolishItemSpans({ {1, 2020} }) << 5; QTest::newRow("test5.1") << PolishItemSpans({ {5, 1} }) << 0; QTest::newRow("test5.10") << PolishItemSpans({ {5, 10} }) << 0; diff --git a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp index ebd1749e68..f6de1b482c 100644 --- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp @@ -2015,7 +2015,7 @@ void tst_QQuickItem::layoutMirroringIllegalParent() { QQmlComponent component(&engine); component.setData("import QtQuick 2.0; QtObject { LayoutMirroring.enabled: true; LayoutMirroring.childrenInherit: true }", QUrl::fromLocalFile("")); - QTest::ignoreMessage(QtWarningMsg, "<Unknown File>:1:21: QML QtObject: LayoutDirection attached property only works with Items and Windows"); + QTest::ignoreMessage(QtWarningMsg, "<Unknown File>:1:21: QML QtObject: LayoutMirroring attached property only works with Items and Windows"); QObject *object = component.create(); QVERIFY(object != nullptr); } diff --git a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml index 8524366f14..5454cf672b 100644 --- a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml +++ b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml @@ -936,7 +936,19 @@ Item { }, layoutWidth: 0, expectedWidths: [0] - } + },{ + tag: "preferred_infinity", // Do not crash/assert when the preferred size is infinity + layout: { + type: "RowLayout", + items: [ + {minimumWidth: 10, preferredWidth: Number.POSITIVE_INFINITY, fillWidth: true}, + {minimumWidth: 20, preferredWidth: Number.POSITIVE_INFINITY, fillWidth: true}, + ] + }, + layoutWidth: 31, // Important that this is between minimum and preferred width of the layout. + expectedWidths: [10, 21] // The result here does not have to be exact. (This + // test is mostly concerned about not crashing). + } ]; } @@ -1178,6 +1190,78 @@ Item { } Component { + id: sizeHintBindingLoopComp + Item { + id: root + anchors.fill: parent + property var customWidth: 100 + RowLayout { + id: col + Item { + id: item + implicitHeight: 80 + implicitWidth: Math.max(col2.implicitWidth, root.customWidth + 20) + ColumnLayout { + id: col2 + width: parent.width + Item { + id: rect + implicitWidth: root.customWidth + implicitHeight: 80 + } + } + } + } + } + } + + function test_sizeHintBindingLoopIssue() { + var item = createTemporaryObject(sizeHintBindingLoopComp, container) + waitForRendering(item) + item.customWidth += 10 + waitForRendering(item) + verify(!LayoutSetup.bindingLoopDetected, "Detected binding loop") + LayoutSetup.resetBindingLoopDetectedFlag() + } + + Component { + id: polishLayoutItemComp + Item { + anchors.fill: parent + implicitHeight: contentLayout.implicitHeight + implicitWidth: contentLayout.implicitWidth + property alias textLayout: contentLayout + RowLayout { + width: parent.width + height: parent.height + ColumnLayout { + id: contentLayout + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.maximumWidth: 200 + Repeater { + model: 2 + Text { + Layout.fillWidth: true + text: "This is a long text causing line breaks to show the bug." + wrapMode: Text.Wrap + } + } + Item { + Layout.fillHeight: true + } + } + } + } + } + + function test_polishLayoutItemIssue() { + var rootItem = createTemporaryObject(polishLayoutItemComp, container) + waitForRendering(rootItem) + var textItem = rootItem.textLayout.children[1] + verify(textItem.y >= rootItem.textLayout.children[0].height) + } + + Component { id: rearrangeNestedLayouts_Component RowLayout { id: layout @@ -1520,8 +1604,8 @@ Item { compare(rootItem.maxWidth, 66) // Should not trigger a binding loop - verify(!BindingLoopDetector.bindingLoopDetected, "Detected binding loop") - BindingLoopDetector.reset() + verify(!LayoutSetup.bindingLoopDetected, "Detected binding loop") + LayoutSetup.resetBindingLoopDetectedFlag() } diff --git a/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp b/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp index 26b01b806d..1576a00c81 100644 --- a/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp +++ b/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp @@ -15,13 +15,12 @@ public: bool wasBindingLoopDetected() const { return mBindingLoopDetected; } public slots: - void reset() { mBindingLoopDetected = false; } + void resetBindingLoopDetectedFlag() { mBindingLoopDetected = false; } void qmlEngineAvailable(QQmlEngine *engine) { connect(engine, &QQmlEngine::warnings, this, &Setup::qmlWarnings); - - qmlRegisterSingletonInstance("org.qtproject.Test", 1, 0, "BindingLoopDetector", this); + qmlRegisterSingletonInstance("org.qtproject.Test", 1, 0, "LayoutSetup", this); } void qmlWarnings(const QList<QQmlError> &warnings) diff --git a/tests/auto/quick/qquicklistview/data/emptymodel.qml b/tests/auto/quick/qquicklistview/data/emptymodel.qml index 3feec691cf..c7f1df31d2 100644 --- a/tests/auto/quick/qquicklistview/data/emptymodel.qml +++ b/tests/auto/quick/qquicklistview/data/emptymodel.qml @@ -6,6 +6,8 @@ Rectangle { } ListView { id: list + width: 100 + height: 100 model: model delegate: Item { } 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 46e1254453..747478fc9a 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -171,6 +171,7 @@ private slots: void headerSnapToItem_data(); void headerSnapToItem(); void snapToItemWithSpacing_QTBUG_59852(); + void snapToItemWithSectionAtStart(); void snapOneItemResize_QTBUG_43555(); void snapOneItem_data(); void snapOneItem(); @@ -5395,6 +5396,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/qquicklistview2/data/nestedSnap.qml b/tests/auto/quick/qquicklistview2/data/nestedSnap.qml new file mode 100644 index 0000000000..d7f064da01 --- /dev/null +++ b/tests/auto/quick/qquicklistview2/data/nestedSnap.qml @@ -0,0 +1,63 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +pragma ComponentBehavior: Bound + +import QtQuick + +ListView { + id: row + + width: 300 + height: 300 + + orientation: Qt.Horizontal + snapMode: ListView.SnapOneItem + highlightRangeMode: ListView.StrictlyEnforceRange + + model: 3 + delegate: ListView { + id: column + objectName: "vertical column " + index + + required property int index + + width: 300 + height: 300 + + orientation: Qt.Vertical + snapMode: ListView.SnapOneItem + highlightRangeMode: ListView.StrictlyEnforceRange + + model: 3 + delegate: Rectangle { + id: cell + + required property int index + + width: 300 + height: 300 + color: "transparent" + border.color: "#000" + border.width: 5 + radius: 15 + + Text { + anchors.centerIn: parent + text: `Row: ${cell.index}` + } + } + + Text { + anchors.verticalCenterOffset: -height + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: `Column: ${column.index}\ncurrentIndex: ${column.currentIndex}` + } + } + + Text { + x: 10; y: 10 + text: `currentIndex: ${row.currentIndex}` + } +} diff --git a/tests/auto/quick/qquicklistview2/data/visibleBoundToCountGreaterThanZero.qml b/tests/auto/quick/qquicklistview2/data/visibleBoundToCountGreaterThanZero.qml new file mode 100644 index 0000000000..e75c779584 --- /dev/null +++ b/tests/auto/quick/qquicklistview2/data/visibleBoundToCountGreaterThanZero.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + property alias listView: listView + + ListView { + id: listView + + visible: count > 0 // actual defect. countChanged never fires so this never turns true + + Layout.fillWidth: true + Layout.preferredHeight: contentHeight // grow with content, initially 0 + + model: ListModel { + id: idModel + } + + delegate: Text { + required property string name + text: name + } + + Timer { + running: true + interval: 10 + repeat: true + onTriggered: idModel.append({name:"Hello"}) + } + } +} diff --git a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp index c240f36a80..e114cc1591 100644 --- a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp +++ b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp @@ -18,6 +18,8 @@ Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") using namespace QQuickViewTestUtils; using namespace QQuickVisualTestUtils; +static const int oneSecondInMs = 1000; + class tst_QQuickListView2 : public QQmlDataTest { Q_OBJECT @@ -35,6 +37,7 @@ private slots: void delegateModelRefresh(); void wheelSnap(); void wheelSnap_data(); + void nestedWheelSnap(); void sectionsNoOverlap(); void metaSequenceAsModel(); @@ -62,6 +65,7 @@ private slots: void changingOrientationResetsPreviousAxisValues_data(); void changingOrientationResetsPreviousAxisValues(); + void visibleBoundToCountGreaterThanZero(); private: void flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to); @@ -838,6 +842,67 @@ void tst_QQuickListView2::wheelSnap_data() << 210.0; } +void tst_QQuickListView2::nestedWheelSnap() +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("nestedSnap.qml"))); + + quint64 timestamp = 10; + auto sendWheelEvent = [×tamp, &window](const QPoint &pixelDelta, Qt::ScrollPhase phase) { + const QPoint pos(100, 100); + QWheelEvent event(pos, window.mapToGlobal(pos), pixelDelta, pixelDelta, Qt::NoButton, + Qt::NoModifier, phase, false, Qt::MouseEventSynthesizedBySystem); + event.setAccepted(false); + event.setTimestamp(timestamp); + QGuiApplication::sendEvent(&window, &event); + timestamp += 50; + }; + + QQuickListView *outerListView = qobject_cast<QQuickListView *>(window.rootObject()); + QTRY_VERIFY(outerListView); + QSignalSpy outerCurrentIndexSpy(outerListView, &QQuickListView::currentIndexChanged); + int movingAtIndex = -1; + + // send horizontal pixel-delta wheel events with phases; confirm that ListView hits the next item boundary + sendWheelEvent({}, Qt::ScrollBegin); + for (int i = 1; i < 4; ++i) { + sendWheelEvent({-50, 0}, Qt::ScrollUpdate); + if (movingAtIndex < 0 && outerListView->isMoving()) + movingAtIndex = i; + } + QVERIFY(outerListView->isDragging()); + sendWheelEvent({}, Qt::ScrollEnd); + QCOMPARE(outerListView->isDragging(), false); + QTRY_COMPARE(outerListView->isMoving(), false); // wait until it stops + qCDebug(lcTests) << "outer got moving after" << movingAtIndex + << "horizontal events; stopped at" << outerListView->contentX() << outerListView->currentIndex(); + QCOMPARE_GT(movingAtIndex, 0); + QCOMPARE(outerListView->contentX(), 300); + QCOMPARE(outerCurrentIndexSpy.size(), 1); + + movingAtIndex = -1; + QQuickListView *innerListView = qobject_cast<QQuickListView *>(outerListView->currentItem()); + QTRY_VERIFY(innerListView); + QSignalSpy innerCurrentIndexSpy(innerListView, &QQuickListView::currentIndexChanged); + + // send vertical pixel-delta wheel events with phases; confirm that ListView hits the next item boundary + sendWheelEvent({}, Qt::ScrollBegin); + for (int i = 1; i < 4; ++i) { + sendWheelEvent({0, -50}, Qt::ScrollUpdate); + if (movingAtIndex < 0 && innerListView->isMoving()) + movingAtIndex = i; + } + QVERIFY(innerListView->isDragging()); + sendWheelEvent({}, Qt::ScrollEnd); + QCOMPARE(innerListView->isDragging(), false); + QTRY_COMPARE(innerListView->isMoving(), false); // wait until it stops + qCDebug(lcTests) << "inner got moving after" << movingAtIndex + << "vertical events; stopped at" << innerListView->contentY() << innerListView->currentIndex(); + QCOMPARE_GT(movingAtIndex, 0); + QCOMPARE(innerListView->contentY(), 300); + QCOMPARE(innerCurrentIndexSpy.size(), 1); +} + class FriendlyItemView : public QQuickItemView { friend class ItemViewAccessor; @@ -1153,6 +1218,23 @@ void tst_QQuickListView2::changingOrientationResetsPreviousAxisValues() // QTBUG QVERIFY(!listView->property("isYReset").toBool()); } +void tst_QQuickListView2::visibleBoundToCountGreaterThanZero() +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("visibleBoundToCountGreaterThanZero.qml"))); + + auto *listView = window.rootObject()->property("listView").value<QQuickListView *>(); + QVERIFY(listView); + + QSignalSpy countChangedSpy(listView, SIGNAL(countChanged())); + QVERIFY(countChangedSpy.isValid()); + + QTRY_COMPARE_GT_WITH_TIMEOUT(listView->count(), 1, oneSecondInMs); + // Using the TRY variant here as well is necessary. + QTRY_COMPARE_GT_WITH_TIMEOUT(countChangedSpy.count(), 1, oneSecondInMs); + QVERIFY(listView->isVisible()); +} + QTEST_MAIN(tst_QQuickListView2) #include "tst_qquicklistview2.moc" diff --git a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp index 6d2aa267dd..d5b3b75215 100644 --- a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp +++ b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp @@ -939,6 +939,9 @@ void tst_QQuickMouseArea::doubleClick() QCOMPARE(window.rootObject()->property("clicked").toInt(), 1); QCOMPARE(window.rootObject()->property("doubleClicked").toInt(), 1); QCOMPARE(window.rootObject()->property("released").toInt(), 2); + + // wait long enough to avoid affecting the next test function + QTest::qWait(QGuiApplication::styleHints()->mouseDoubleClickInterval()); } void tst_QQuickMouseArea::doubleTap() // QTBUG-112434 @@ -970,6 +973,9 @@ void tst_QQuickMouseArea::doubleTap() // QTBUG-112434 QCOMPARE(mouseArea->pressed(), false); QCOMPARE(window.rootObject()->property("clicked").toInt(), 1); + // avoid getting a double-click event next + QTest::qWait(QGuiApplication::styleHints()->mouseDoubleClickInterval()); + // now tap with two fingers simultaneously: only one of them generates synth-mouse QPoint p2 = p1 + QPoint(50, 5); QTest::touchEvent(&window, device).press(2, p1).press(3, p2); @@ -991,8 +997,8 @@ void tst_QQuickMouseArea::doubleTap() // QTBUG-112434 QTest::touchEvent(&window, device).release(4, p1).release(5, p2); QQuickTouchUtils::flush(&window); QCOMPARE(window.rootObject()->property("released").toInt(), 4); - QCOMPARE(window.rootObject()->property("clicked").toInt(), 2); - QCOMPARE(window.rootObject()->property("doubleClicked").toInt(), 2); + QCOMPARE(window.rootObject()->property("clicked").toInt(), 3); + QCOMPARE(window.rootObject()->property("doubleClicked").toInt(), 1); QCOMPARE(mouseArea->pressed(), false); // make sure it doesn't get stuck } diff --git a/tests/auto/quick/qquickpathview/data/qtbug46487.qml b/tests/auto/quick/qquickpathview/data/qtbug46487.qml new file mode 100644 index 0000000000..840d77ffe4 --- /dev/null +++ b/tests/auto/quick/qquickpathview/data/qtbug46487.qml @@ -0,0 +1,28 @@ +import QtQuick 2.0 + +PathView { + id: view + property int delegatesCreated: 0 + property int delegatesDestroyed: 0 + + width: 400 + height: 400 + preferredHighlightBegin: 0.5 + preferredHighlightEnd: 0.5 + pathItemCount: 5 + currentIndex: 1 + model: customModel + delegate: Text { + text: "item: " + index + " of: " + view.count + Component.onCompleted: view.delegatesCreated++; + Component.onDestruction: view.delegatesDestroyed++; + } + path: Path { + startX: 50 + startY: 0 + PathLine { + x: 50 + y: 400 + } + } +} diff --git a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp index 90c3060235..7d41d907fb 100644 --- a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp +++ b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp @@ -133,6 +133,7 @@ private slots: void requiredPropertiesInDelegatePreventUnrelated(); void touchMove(); void mousePressAfterFlick(); + void qtbug46487(); private: QScopedPointer<QPointingDevice> touchDevice = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); @@ -2895,6 +2896,68 @@ void tst_QQuickPathView::mousePressAfterFlick() // QTBUG-115121 QCOMPARE(pressedSpy.size(), 0); } +class CustomModel : public QAbstractListModel +{ +public: + CustomModel(QObject *parent = 0) : QAbstractListModel(parent) { + m_values << 0 << 1 << 2 << 3 << 4; + } + + int rowCount(const QModelIndex &parent = QModelIndex()) const { + Q_UNUSED(parent); + return m_values.count(); + } + QVariant data(const QModelIndex &index, int role) const { + if (index.row() < 0 || m_values.count() <= index.row()) + return QVariant(); + + return m_values[index.row()]; + } + + Q_INVOKABLE void shrink() { + beginResetModel(); + m_values.takeLast(); + m_values.takeLast(); + endResetModel(); + } + +private: + QList<int> m_values; +}; + +void tst_QQuickPathView::qtbug46487() +{ + QScopedPointer<QQuickView> window(createView()); + + CustomModel* model = new CustomModel; + QQmlContext *ctxt = window->rootContext(); + ctxt->setContextProperty("customModel", model); + + window->setSource(testFileUrl("qtbug46487.qml")); + window->show(); + qApp->processEvents(); + + QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); + QVERIFY(pathview); + + QTest::qWait(500); + + // Should create just pathItemCount amount and not destroy any + QCOMPARE(pathview->count(), 5); + QCOMPARE(pathview->property("delegatesCreated").toInt(), 5); + QCOMPARE(pathview->property("delegatesDestroyed").toInt(), 0); + + // Resets the model and removes 2 items. + model->shrink(); + QTest::qWait(500); + + // Should destroy previous items (begin/endResetModel) and + // (re)create 3 new items. + QCOMPARE(pathview->count(), 3); + QCOMPARE(pathview->property("delegatesCreated").toInt(), 5 + 3); + QCOMPARE(pathview->property("delegatesDestroyed").toInt(), 5); +} + QTEST_MAIN(tst_QQuickPathView) #include "tst_qquickpathview.moc" diff --git a/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp b/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp index 590f022e4e..4e0534edf0 100644 --- a/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp +++ b/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp @@ -108,6 +108,11 @@ void tst_qquickpixmapcache::single() QFETCH(bool, exists); QFETCH(bool, neterror); +#if !QT_CONFIG(qml_network) + if (target.scheme() == "http") + QSKIP("Skipping due to lack of QML network feature"); +#endif + QString expectedError; if (neterror) { expectedError = "Error transferring " + target.toString() + " - server replied: Not found"; @@ -196,6 +201,11 @@ void tst_qquickpixmapcache::parallel() QFETCH(int, incache); QFETCH(int, cancel); +#if !QT_CONFIG(qml_network) + if (target1.scheme() == "http" || target2.scheme() == "http") + QSKIP("Skipping due to lack of QML network feature"); +#endif + QList<QUrl> targets; targets << target1 << target2; diff --git a/tests/auto/quick/qquickstates/data/anchorRewindBug3.qml b/tests/auto/quick/qquickstates/data/anchorRewindBug3.qml new file mode 100644 index 0000000000..6d829be363 --- /dev/null +++ b/tests/auto/quick/qquickstates/data/anchorRewindBug3.qml @@ -0,0 +1,38 @@ +import QtQuick 2.0 + +Rectangle { + id: root + width: 100; height: 100 + + Rectangle { + id: rectangle + objectName: "inner" + color: "green" + // Width and height end up to be 50 + // after root Component.onCompleted + width: 75 + height: 75 + anchors.top: root.top + anchors.left: root.left + } + + // Start with anchored state + state: "anchored" + states: [ + State { + name: "anchored" + AnchorChanges { + target: rectangle + anchors.top: undefined + anchors.left: undefined + anchors.right: root.right + anchors.bottom: root.bottom + } + } + ] + + Component.onCompleted: { + rectangle.width = 50 + rectangle.height = 50 + } +} diff --git a/tests/auto/quick/qquickstates/tst_qquickstates.cpp b/tests/auto/quick/qquickstates/tst_qquickstates.cpp index be1361e4ab..7332db93fd 100644 --- a/tests/auto/quick/qquickstates/tst_qquickstates.cpp +++ b/tests/auto/quick/qquickstates/tst_qquickstates.cpp @@ -154,6 +154,7 @@ private slots: void anchorChangesCrash(); void anchorRewindBug(); void anchorRewindBug2(); + void anchorRewind_keepsSize_whenStateResetsDefaultAnchors(); void script(); void restoreEntryValues(); void explicitChanges(); @@ -1087,6 +1088,30 @@ void tst_qquickstates::anchorRewindBug2() QCOMPARE(mover->width(), qreal(50.0)); } +// QTBUG-126057 +void tst_qquickstates::anchorRewind_keepsSize_whenStateResetsDefaultAnchors() +{ + // Arrange + QQmlEngine engine; + + // NOTE: Contains two nested rectangles, inner is by default anchored to the top left corner of + // its parent. A state is initially "anchored" which removes the default anchoring and anchors + // the inner rectangle to the bottom right corner of the parent. The size of the inner rectangle + // is assigned to 50x50 on Component.onCompleted of outer rectangle. + QQmlComponent rectComponent(&engine, testFileUrl("anchorRewindBug3.qml")); + QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create())); + QVERIFY(rect != nullptr); + QQuickRectangle *mover = rect->findChild<QQuickRectangle*>("inner"); + QVERIFY(mover != nullptr); + + // Act + QQuickItemPrivate::get(rect.get())->setState(""); + + // Assert + QCOMPARE(mover->width(), qreal(50.0)); + QCOMPARE(mover->height(), qreal(50.0)); +} + void tst_qquickstates::script() { QQmlEngine engine; diff --git a/tests/auto/quick/qquicktableview/data/invalidateModelContextObject.qml b/tests/auto/quick/qquicktableview/data/invalidateModelContextObject.qml new file mode 100644 index 0000000000..ff552e856c --- /dev/null +++ b/tests/auto/quick/qquicktableview/data/invalidateModelContextObject.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ApplicationWindow { + width: 720 + height: 480 + visible: true + + property alias tableView: tableView + property int modelData: 15 + + Page { + anchors.fill: parent + header: Rectangle { + height: 40 + color: "red" + } + TableView { + id: tableView + anchors.fill: parent + model: modelData + contentY: Math.max(0, contentHeight - height) + contentHeight: 40 * rows + rowHeightProvider: () => 40 + columnWidthProvider: () => 200 + delegate : Rectangle { + width: 40; + height: 40; + color: "green" + Text { + anchors.fill: parent + text: index + } + } + } + } +} diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index 96137877cb..7482367057 100644 --- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -162,12 +162,16 @@ private slots: void checkSyncView_childViews_data(); void checkSyncView_childViews(); void checkSyncView_differentSizedModels(); - void checkSyncView_differentGeometry(); + void checkSyncView_differentGeometry_vertical(); + void checkSyncView_differentGeometry_horizontal(); + void checkSyncView_differentGeometry_both_directions(); void checkSyncView_connect_late_data(); void checkSyncView_connect_late(); void checkSyncView_pageFlicking(); void checkSyncView_emptyModel(); void checkSyncView_topLeftChanged(); + void checkSyncView_dontRelayoutWhileFlicking(); + void checkSyncView_detectTopLeftPositionChanged(); void delegateWithRequiredProperties(); void checkThatFetchMoreIsCalledWhenScrolledToTheEndOfTable(); void replaceModel(); @@ -278,6 +282,7 @@ private slots: void checkScroll_data(); void checkScroll(); void checkRebuildJsModel(); + void invalidateTableInstanceModelContextObject(); }; tst_QQuickTableView::tst_QQuickTableView() @@ -3070,7 +3075,7 @@ void tst_QQuickTableView::checkSyncView_differentSizedModels() QVERIFY(tableViewHVPrivate->loadedColumns.isEmpty()); } -void tst_QQuickTableView::checkSyncView_differentGeometry() +void tst_QQuickTableView::checkSyncView_differentGeometry_vertical() { // Check that you can have two tables in a syncView relation, where // the sync "child" is larger than the sync view. This means that the @@ -3083,46 +3088,106 @@ void tst_QQuickTableView::checkSyncView_differentGeometry() GET_QML_TABLEVIEW(tableViewV); GET_QML_TABLEVIEW(tableViewHV); - tableView->setWidth(40); - tableView->setHeight(40); + tableView->setHeight(90); + tableViewH->setSyncView(nullptr); + tableViewHV->setSyncView(nullptr); auto tableViewModel = TestModelAsVariant(100, 100); tableView->setModel(tableViewModel); - tableViewH->setModel(tableViewModel); tableViewV->setModel(tableViewModel); - tableViewHV->setModel(tableViewModel); WAIT_UNTIL_POLISHED; - // Check that the column widths are in sync - for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) { - QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); - QCOMPARE(tableViewHV->columnWidth(column), tableView->columnWidth(column)); - } + // Check that the row heights are in sync + for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) + QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row)); + + // Flick in a new row + tableView->setContentY(20); // Check that the row heights are in sync - for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) { + for (int row = tableViewV->topRow(); row <= tableViewV->bottomRow(); ++row) QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row)); - QCOMPARE(tableViewHV->rowHeight(row), tableView->rowHeight(row)); - } +} + +void tst_QQuickTableView::checkSyncView_differentGeometry_horizontal() +{ + // Check that you can have two tables in a syncView relation, where + // the sync "child" is larger than the sync view. This means that the + // child will display more rows and columns than the parent. + // In that case, the sync view will anyway need to load the same rows + // and columns as the child, otherwise the column and row sizes + // cannot be determined for the child. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewH); + GET_QML_TABLEVIEW(tableViewV); + GET_QML_TABLEVIEW(tableViewHV); + + tableView->setWidth(90); + tableViewV->setSyncView(nullptr); + tableViewHV->setSyncView(nullptr); + + auto tableViewModel = TestModelAsVariant(100, 100); + + tableView->setModel(tableViewModel); + tableViewH->setModel(tableViewModel); - // Flick a bit, and do the same test again - tableView->setContentX(200); - tableView->setContentY(200); WAIT_UNTIL_POLISHED; // Check that the column widths are in sync - for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) { + for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); - QCOMPARE(tableViewHV->columnWidth(column), tableView->columnWidth(column)); - } + + // Flick in a new column + tableView->setContentX(20); + + // Check that the column widths are in sync + for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) + QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); +} + +void tst_QQuickTableView::checkSyncView_differentGeometry_both_directions() { + // Check that you can have two tables in a syncView relation, where + // the sync "child" is larger than the sync view. This means that the + // child will display more rows and columns than the parent. + // In that case, the sync view will anyway need to load the same rows + // and columns as the child, otherwise the column and row sizes + // cannot be determined for the child. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewH); + GET_QML_TABLEVIEW(tableViewV); + GET_QML_TABLEVIEW(tableViewHV); + + tableView->setWidth(90); + tableView->setHeight(90); + tableViewHV->setSyncView(nullptr); + + auto tableViewModel = TestModelAsVariant(100, 100); + + tableView->setModel(tableViewModel); + tableViewH->setModel(tableViewModel); + tableViewV->setModel(tableViewModel); + + WAIT_UNTIL_POLISHED; // Check that the row heights are in sync - for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) { + for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row)); - QCOMPARE(tableViewHV->rowHeight(row), tableView->rowHeight(row)); - } + // Check that the column widths are in sync + for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) + QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); + + // Flick in a new row + tableView->setContentX(20); + tableView->setContentY(20); + + // Check that the row heights are in sync + for (int row = tableViewV->topRow(); row <= tableViewV->bottomRow(); ++row) + QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row)); + // Check that the column widths are in sync + for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) + QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); } void tst_QQuickTableView::checkSyncView_connect_late_data() @@ -3328,6 +3393,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"); @@ -7523,6 +7667,29 @@ void tst_QQuickTableView::checkRebuildJsModel() QCOMPARE(tableView->property(modelUpdated).toInt(), 1); } +void tst_QQuickTableView::invalidateTableInstanceModelContextObject() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("invalidateModelContextObject.qml")); + + std::unique_ptr<QQuickWindow> window(qobject_cast<QQuickWindow*>(component.create())); + QVERIFY(window); + + auto tableView = window->property("tableView").value<QQuickTableView *>(); + QVERIFY(tableView); + + const int modelData = window->property("modelData").toInt(); + QTRY_COMPARE(tableView->rows(), modelData); + + bool tableViewDestroyed = false; + connect(tableView, &QObject::destroyed, [&] { + tableViewDestroyed = true; + }); + + window.reset(); + QTRY_COMPARE(tableViewDestroyed, true); +} + QTEST_MAIN(tst_QQuickTableView) #include "tst_qquicktableview.moc" diff --git a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp index 63d4f24a54..23d2e006ba 100644 --- a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp +++ b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp @@ -3,14 +3,17 @@ #include <qtest.h> #include <QtTest/QtTest> +#include <QtQuick/QQuickView> #include <QtQuick/QQuickTextDocument> #include <QtQuick/QQuickItem> #include <QtQuick/private/qquicktextedit_p.h> #include <QtQuick/private/qquicktextdocument_p.h> #include <QtGui/QTextDocument> +#include <QtGui/QTextBlock> #include <QtGui/QTextDocumentWriter> #include <QtQml/QQmlEngine> #include <QtQml/QQmlComponent> +#include <QtQuickTest/QtQuickTest> #include <QtQuickTestUtils/private/qmlutils_p.h> class tst_qquicktextdocument : public QQmlDataTest @@ -22,6 +25,8 @@ public: private slots: void textDocumentWriter(); void textDocumentWithImage(); + void changeCharFormatInRange_data(); + void changeCharFormatInRange(); }; QString text = QStringLiteral("foo bar"); @@ -68,6 +73,53 @@ void tst_qquicktextdocument::textDocumentWithImage() QCOMPARE(image, document.resource(QTextDocument::ImageResource, name).value<QImage>()); } +void tst_qquicktextdocument::changeCharFormatInRange_data() +{ + QTest::addColumn<bool>("editBlock"); + + QTest::newRow("begin/end") << true; + QTest::newRow("no edit block") << false; // QTBUG-126886 : don't crash +} + +void tst_qquicktextdocument::changeCharFormatInRange() +{ + QFETCH(bool, editBlock); + QQuickView window(testFileUrl("text.qml")); + window.showNormal(); + QQuickTextEdit *textEdit = qobject_cast<QQuickTextEdit *>(window.rootObject()); + QVERIFY(textEdit); + QVERIFY(textEdit->textDocument()); + + auto *doc = textEdit->textDocument()->textDocument(); + QVERIFY(doc); + + QSignalSpy contentSpy(doc, &QTextDocument::contentsChanged); + const auto data = QStringLiteral("Format String"); + doc->setPlainText(data); + const auto block = doc->findBlockByNumber(0); + + auto formatText = [block, data] { + QTextLayout::FormatRange formatText; + formatText.start = 0; + formatText.length = data.size(); + formatText.format.setForeground(Qt::green); + block.layout()->setFormats({formatText}); + }; + + // change the char format of this block, and verify visual effect + if (editBlock) { + QTextCursor cursor(doc); + cursor.beginEditBlock(); + formatText(); + cursor.endEditBlock(); + } else { + formatText(); + } + + QVERIFY(QQuickTest::qWaitForPolish(textEdit)); + QCOMPARE(contentSpy.size(), editBlock ? 2 : 1); +} + QTEST_MAIN(tst_qquicktextdocument) #include "tst_qquicktextdocument.moc" diff --git a/tests/auto/quick/qquicktextedit/data/hAlignVisual.qml b/tests/auto/quick/qquicktextedit/data/hAlignVisual.qml index 1280a655f0..f532a9aa36 100644 --- a/tests/auto/quick/qquicktextedit/data/hAlignVisual.qml +++ b/tests/auto/quick/qquicktextedit/data/hAlignVisual.qml @@ -4,11 +4,11 @@ Rectangle { width: 200 height: 100 - Text { - objectName: "textItem" + TextEdit { + objectName: "textEditItem" text: "AA\nBBBBBBB\nCCCCCCCCCCCCCCCC" anchors.centerIn: parent - horizontalAlignment: Text.AlignLeft + horizontalAlignment: TextEdit.AlignLeft font.pointSize: 12 font.family: "Times New Roman" } diff --git a/tests/auto/quick/qquicktextedit/data/inFlickable.qml b/tests/auto/quick/qquicktextedit/data/inFlickable.qml index 7a896db29b..183ddd6701 100644 --- a/tests/auto/quick/qquicktextedit/data/inFlickable.qml +++ b/tests/auto/quick/qquicktextedit/data/inFlickable.qml @@ -1,6 +1,7 @@ import QtQuick 2.0 Flickable { + id: flick width: 320; height: 120; contentHeight: text.height TextEdit { id: text @@ -8,4 +9,10 @@ Flickable { font.pixelSize: 20 text: "several\nlines\nof\ntext\n-\ntry\nto\nflick" } + Text { + color: "red" + parent: flick // stay on top + anchors.right: parent.right + text: flick.contentY.toFixed(1) + } } diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index 15f538ac3c..44745f8263 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -8,6 +8,8 @@ #include <QtQuick/QQuickTextDocument> #include <QtQuickTest/QtQuickTest> #include <QTextDocument> +#include <QtGui/qtextobject.h> +#include <QtGui/QTextTable> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcontext.h> #include <QtQml/qqmlexpression.h> @@ -43,6 +45,8 @@ DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD) Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") +// #define DEBUG_WRITE_INPUT + static bool isPlatformWayland() { return !QGuiApplication::platformName().compare(QLatin1String("wayland"), Qt::CaseInsensitive); @@ -143,6 +147,8 @@ private slots: void largeTextObservesViewport(); void largeTextSelection(); void renderingAroundSelection(); + void largeTextTables_data(); + void largeTextTables(); void signal_editingfinished(); @@ -221,7 +227,6 @@ private: void simulateKey(QWindow *, int key, Qt::KeyboardModifiers modifiers = {}); bool isMainFontFixed(); - static bool hasWindowActivation(); QStringList standard; QStringList richText; @@ -972,8 +977,8 @@ void tst_qquicktextedit::hAlignVisual() view.showNormal(); QVERIFY(QTest::qWaitForWindowExposed(&view)); - QQuickText *text = view.rootObject()->findChild<QQuickText*>("textItem"); - QVERIFY(text != nullptr); + QQuickTextEdit *text = view.rootObject()->findChild<QQuickTextEdit*>("textEditItem"); + QVERIFY(text); // Try to check whether alignment works by checking the number of black // pixels in the thirds of the grabbed image. @@ -1000,7 +1005,7 @@ void tst_qquicktextedit::hAlignVisual() } { // HCenter Align - text->setHAlign(QQuickText::AlignHCenter); + text->setHAlign(QQuickTextEdit::AlignHCenter); QImage image = view.grabWindow(); const int left = numberOfNonWhitePixels(centeredSection1, centeredSection2, image); const int mid = numberOfNonWhitePixels(centeredSection2, centeredSection3, image); @@ -1010,7 +1015,7 @@ void tst_qquicktextedit::hAlignVisual() } { // Right Align - text->setHAlign(QQuickText::AlignRight); + text->setHAlign(QQuickTextEdit::AlignRight); QImage image = view.grabWindow(); const int left = numberOfNonWhitePixels(centeredSection1, centeredSection2, image); const int mid = numberOfNonWhitePixels(centeredSection2, centeredSection3, image); @@ -1022,36 +1027,36 @@ void tst_qquicktextedit::hAlignVisual() text->setWidth(200); { - // Left Align + // Right Align QImage image = view.grabWindow(); - int x = qCeil(text->implicitWidth() * view.devicePixelRatio()); - int left = numberOfNonWhitePixels(0, x, image); - int right = numberOfNonWhitePixels(x, image.width() - x, image); - QVERIFY2(left > 0, msgNotGreaterThan(left, 0).constData()); - QCOMPARE(right, 0); + const int x = image.width() - qCeil(text->implicitWidth() * view.devicePixelRatio()); + const int left = numberOfNonWhitePixels(0, x, image); + const int right = numberOfNonWhitePixels(x, image.width() - x, image); + QCOMPARE(left, 0); + QVERIFY2(right > 0, msgNotGreaterThan(left, 0).constData()); } { // HCenter Align - text->setHAlign(QQuickText::AlignHCenter); + text->setHAlign(QQuickTextEdit::AlignHCenter); QImage image = view.grabWindow(); - int x1 = qFloor(image.width() - text->implicitWidth() * view.devicePixelRatio()) / 2; - int x2 = image.width() - x1; - int left = numberOfNonWhitePixels(0, x1, image); - int mid = numberOfNonWhitePixels(x1, x2 - x1, image); - int right = numberOfNonWhitePixels(x2, image.width() - x2, image); + const int x1 = qFloor(image.width() - text->implicitWidth() * view.devicePixelRatio()) / 2; + const int x2 = image.width() - x1; + const int left = numberOfNonWhitePixels(0, x1, image); + const int mid = numberOfNonWhitePixels(x1, x2 - x1, image); + const int right = numberOfNonWhitePixels(x2, image.width(), image); QCOMPARE(left, 0); QVERIFY2(mid > 0, msgNotGreaterThan(left, 0).constData()); QCOMPARE(right, 0); } { - // Right Align - text->setHAlign(QQuickText::AlignRight); + // Left Align + text->setHAlign(QQuickTextEdit::AlignLeft); QImage image = view.grabWindow(); - int x = image.width() - qCeil(text->implicitWidth() * view.devicePixelRatio()); - int left = numberOfNonWhitePixels(0, x, image); - int right = numberOfNonWhitePixels(x, image.width() - x, image); - QCOMPARE(left, 0); - QVERIFY2(right > 0, msgNotGreaterThan(left, 0).constData()); + const int x = qCeil(text->implicitWidth() * view.devicePixelRatio()); + const int left = numberOfNonWhitePixels(0, x, image); + const int right = numberOfNonWhitePixels(x, image.width() - x, image); + QVERIFY2(left > 0, msgNotGreaterThan(left, 0).constData()); + QCOMPARE(right, 0); } } @@ -3385,11 +3390,6 @@ bool tst_qquicktextedit::isMainFontFixed() return ret; } -bool tst_qquicktextedit::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - void tst_qquicktextedit::textInput() { QQuickView view(testFileUrl("inputMethodEvent.qml")); @@ -3951,6 +3951,105 @@ void tst_qquicktextedit::renderingAroundSelection() QTRY_COMPARE(textItem->sortedLinePositions, sortedLinePositions); } +struct OffsetAndExpectedBlocks { + int tableIndex; // which nested frame + qreal tableOffset; // fraction of that frame's height to scroll to + int minExpectedBlockCount; + + OffsetAndExpectedBlocks(int i, qreal o, int c) + : tableIndex(i), tableOffset(o), minExpectedBlockCount(c) {} +}; + +typedef QList<OffsetAndExpectedBlocks> OffsetAndExpectedBlocksList; + +void tst_qquicktextedit::largeTextTables_data() +{ + QTest::addColumn<int>("tables"); + QTest::addColumn<int>("tableCols"); + QTest::addColumn<int>("tableRows"); + QTest::addColumn<OffsetAndExpectedBlocksList>("steps"); + + QTest::newRow("one big table") << 1 << 3 << 70 + << OffsetAndExpectedBlocksList{ + OffsetAndExpectedBlocks(1, 0.75, 150), + OffsetAndExpectedBlocks(1, 0.5, 150)}; + QTest::newRow("short tables") << 5 << 3 << 10 + << OffsetAndExpectedBlocksList{ + OffsetAndExpectedBlocks(4, 0.75, 35), + OffsetAndExpectedBlocks(3, 0.25, 50), + OffsetAndExpectedBlocks(2, 0.75, 50)}; +} + +void tst_qquicktextedit::largeTextTables() // QTBUG-118636 +{ + QFETCH(int, tables); + QFETCH(int, tableCols); + QFETCH(int, tableRows); + QFETCH(OffsetAndExpectedBlocksList, steps); + + QStringList lines; + + lines << QLatin1String("<h1>") + QTest::currentDataTag() + "</h1>"; + for (int t = 0; t < tables; ++t) { + if (t > 0) + lines << QString("<p>table %1</p>").arg(t); + lines << "<table border='1'>"; + for (int r = 0; r < tableRows; ++r) { + lines << " <tr>"; + for (int c = 0; c < tableCols; ++c) + lines << QString(" <td>table %1 cell %2, %3</td>").arg(t).arg(c).arg(r); + lines << " </tr>"; + } + lines << "</table>"; + } + lines << "<p>here endeth the tables</p>"; + QString html = lines.join('\n'); + +#ifdef DEBUG_WRITE_INPUT + QFile f(QLatin1String("/tmp/") + QTest::currentDataTag() + ".html"); + f.open(QFile::WriteOnly); + f.write(html.toUtf8()); + f.close(); +#endif + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("inFlickable.qml"))); + QQuickFlickable *flick = qmlobject_cast<QQuickFlickable *>(window.rootObject()); + QVERIFY(flick); + QQuickTextEdit *te = window.rootObject()->findChild<QQuickTextEdit *>(); + QVERIFY(te); + auto *tePriv = QQuickTextEditPrivate::get(te); + auto font = te->font(); + font.setPixelSize(10); + te->setFont(font); + + te->setTextFormat(QQuickTextEdit::RichText); + te->setText(html); + te->setFlag(QQuickItem::ItemObservesViewport); // this isn't "large text", but test viewporting anyway + + QTextDocument *doc = te->textDocument()->textDocument(); + QList<QTextFrame *> frames = doc->rootFrame()->childFrames(); + frames.prepend(doc->rootFrame()); + qCDebug(lcTests) << "blocks" << doc->blockCount() << "chars" << doc->characterCount() << "frames" << frames; + + for (const OffsetAndExpectedBlocks &oeb : steps) { + QCOMPARE_GT(frames.size(), oeb.tableIndex); + const QTextFrame *textFrame = frames.at(oeb.tableIndex); + const QTextCursor top = textFrame->firstCursorPosition(); + const qreal yTop = te->positionToRectangle(top.position()).top(); + const QTextCursor bottom = textFrame->lastCursorPosition(); + const qreal yBottom = te->positionToRectangle(bottom.position()).bottom(); + const qreal y = yTop + (yBottom - yTop) * oeb.tableOffset; + qCDebug(lcTests) << "frame" << textFrame << "goes from pos" << top.position() << "y" << yTop + << "to pos" << bottom.position() << "y" << yBottom << "; scrolling to" << y + << "which is at" << oeb.tableOffset << "of table height" << (yBottom - yTop); + flick->setContentY(y); + qCDebug(lcTests) << tePriv->renderedRegion << "rendered blocks" << tePriv->renderedBlockCount << ":" + << tePriv->firstBlockInViewport << "to" << tePriv->firstBlockPastViewport; + QTRY_COMPARE_GE(tePriv->renderedBlockCount, oeb.minExpectedBlockCount); + } +} + void tst_qquicktextedit::signal_editingfinished() { QQuickView *window = new QQuickView(nullptr); @@ -6483,8 +6582,8 @@ void tst_qquicktextedit::touchscreenDoesNotSelect() void tst_qquicktextedit::touchscreenSetsFocusAndMovesCursor() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION + QQuickView window; QVERIFY(QQuickTest::showView(window, testFileUrl("twoInAColumn.qml"))); window.requestActivate(); diff --git a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp index 5e2d49afb8..c944406e10 100644 --- a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp +++ b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp @@ -5,6 +5,7 @@ #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/testhttpserver_p.h> #include <QtQuickTestUtils/private/viewtestutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> #include <private/qinputmethod_p.h> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> @@ -212,7 +213,6 @@ private: #if QT_CONFIG(shortcut) void simulateKeys(QWindow *window, const QKeySequence &sequence); #endif - static bool hasWindowActivation(); QQmlEngine engine; QStringList standard; @@ -238,11 +238,6 @@ void tst_qquicktextinput::simulateKeys(QWindow *window, const QList<Key> &keys) } } -bool tst_qquicktextinput::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - #if QT_CONFIG(shortcut) void tst_qquicktextinput::simulateKeys(QWindow *window, const QKeySequence &sequence) @@ -7191,17 +7186,15 @@ void tst_qquicktextinput::touchscreenDoesNotSelect() QTest::touchEvent(&window, touchscreen.data()).press(0, QPoint(x2,y), &window); QTest::touchEvent(&window, touchscreen.data()).release(0, QPoint(x2,y), &window); QQuickTouchUtils::flush(&window); - QCOMPARE(textInputObject->selectedText().isEmpty(), !expectDefaultSelectByMouse); - if (expectDefaultSelectByMouse) - QCOMPARE(textInputObject->cursorPosition(), cursorPos); - else - QCOMPARE_NE(textInputObject->cursorPosition(), cursorPos); + QCOMPARE(textInputObject->selectedText().isEmpty(), true); + QCOMPARE_NE(textInputObject->cursorPosition(), cursorPos); + QVERIFY(textInputObject->selectedText().isEmpty()); } void tst_qquicktextinput::touchscreenSetsFocusAndMovesCursor() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION + QQuickView window; QVERIFY(QQuickTest::showView(window, testFileUrl("twoInAColumn.qml"))); window.requestActivate(); diff --git a/tests/auto/quick/qquickview_extra/data/qtbug_87228.qml b/tests/auto/quick/qquickview_extra/data/qtbug_87228.qml index ff10eba23d..9327daae6b 100644 --- a/tests/auto/quick/qquickview_extra/data/qtbug_87228.qml +++ b/tests/auto/quick/qquickview_extra/data/qtbug_87228.qml @@ -15,6 +15,8 @@ Item { Rectangle { ListView { objectName: "listView" + width: 100 + height: 100 delegate: Text { required property string desc text: desc diff --git a/tests/auto/quick/softwarerenderer/tst_softwarerenderer.cpp b/tests/auto/quick/softwarerenderer/tst_softwarerenderer.cpp index 2d2581a3fa..2124a8ee8f 100644 --- a/tests/auto/quick/softwarerenderer/tst_softwarerenderer.cpp +++ b/tests/auto/quick/softwarerenderer/tst_softwarerenderer.cpp @@ -56,10 +56,8 @@ void tst_SoftwareRenderer::renderTarget() rc.polishItems(); - rc.beginFrame(); rc.sync(); rc.render(); - rc.endFrame(); QImage content = window->grabWindow(); QString errorMessage; @@ -74,10 +72,8 @@ void tst_SoftwareRenderer::renderTarget() rc.polishItems(); - rc.beginFrame(); rc.sync(); rc.render(); - rc.endFrame(); content = window->grabWindow(); QVERIFY2(QQuickVisualTestUtils::compareImages(content, renderTarget2, &errorMessage), diff --git a/tests/auto/quick/touchmouse/tst_touchmouse.cpp b/tests/auto/quick/touchmouse/tst_touchmouse.cpp index d7820996be..179acbc728 100644 --- a/tests/auto/quick/touchmouse/tst_touchmouse.cpp +++ b/tests/auto/quick/touchmouse/tst_touchmouse.cpp @@ -244,6 +244,8 @@ private slots: void strayTouchDoesntAutograb(); + void noDoubleClickWithInterveningTouch(); + protected: bool eventFilter(QObject *, QEvent *event) override { @@ -1303,7 +1305,7 @@ void tst_TouchMouse::touchGrabCausesMouseUngrab() QVERIFY(leftItem); EventItem *rightItem = window.rootObject()->findChild<EventItem*>("rightItem"); - QVERIFY(leftItem); + QVERIFY(rightItem); // Send a touch to the leftItem. But leftItem accepts only mouse events, thus // a mouse event will be synthesized out of this touch and will get accepted by @@ -1660,6 +1662,58 @@ void tst_TouchMouse::strayTouchDoesntAutograb() // QTBUG-107867 QTest::touchEvent(&window, device).release(0, p1).release(1, p1); } +void tst_TouchMouse::noDoubleClickWithInterveningTouch() // QTBUG-116442 +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("twosiblingitems.qml"))); + + EventItem *leftItem = window.rootObject()->findChild<EventItem*>("leftItem"); + QVERIFY(leftItem); + // simulate a MouseArea: don't accept touch + leftItem->setAcceptedMouseButtons(Qt::LeftButton); + leftItem->acceptMouse = true; + + EventItem *rightItem = window.rootObject()->findChild<EventItem*>("rightItem"); + QVERIFY(rightItem); + // simulate an item that reacts to either touch or mouse + rightItem->setAcceptedMouseButtons(Qt::LeftButton); + rightItem->acceptMouse = true; + rightItem->setAcceptTouchEvents(true); + rightItem->acceptTouch = true; + + const QPoint pLeft(80, 200); + const QPoint pRight(240, 200); + + // tap left + QTest::touchEvent(&window, device).press(1, pLeft, &window); + QTest::touchEvent(&window, device).release(1, pLeft, &window); + QQuickTouchUtils::flush(&window); + qCDebug(lcTests) << "left tap" << leftItem->eventList; + QCOMPARE(leftItem->eventList.size(), 3); + QCOMPARE(leftItem->eventList.at(0).type, QEvent::MouseButtonPress); + leftItem->eventList.clear(); + + // tap right + QTest::touchEvent(&window, device).press(1, pRight, &window); + QTest::touchEvent(&window, device).release(1, pRight, &window); + QQuickTouchUtils::flush(&window); + qCDebug(lcTests) << "right tap" << rightItem->eventList; + QCOMPARE(rightItem->eventList.size(), 2); + QCOMPARE(rightItem->eventList.at(0).type, QEvent::TouchBegin); + rightItem->eventList.clear(); + + // tap left again: this is NOT a double-click, even though it's within time and space limits + QTest::touchEvent(&window, device).press(3, pLeft, &window); + QTest::touchEvent(&window, device).release(3, pLeft, &window); + QQuickTouchUtils::flush(&window); + qCDebug(lcTests) << "left tap again" << leftItem->eventList; + QCOMPARE(leftItem->eventList.size(), 3); + QCOMPARE(leftItem->eventList.at(0).type, QEvent::MouseButtonPress); + QCOMPARE(leftItem->eventList.at(1).type, QEvent::MouseButtonRelease); + QCOMPARE(leftItem->eventList.at(2).type, QEvent::UngrabMouse); + leftItem->eventList.clear(); +} + QTEST_MAIN(tst_TouchMouse) #include "tst_touchmouse.moc" diff --git a/tests/auto/quickcontrols/CMakeLists.txt b/tests/auto/quickcontrols/CMakeLists.txt index f4f2e6b2c6..b7669d4f54 100644 --- a/tests/auto/quickcontrols/CMakeLists.txt +++ b/tests/auto/quickcontrols/CMakeLists.txt @@ -24,13 +24,16 @@ add_subdirectory(pressandhold) add_subdirectory(qquickapplicationwindow) add_subdirectory(qquickcontrol) add_subdirectory(qquickcolor) +add_subdirectory(qquickcontainer) add_subdirectory(qquickdrawer) add_subdirectory(qquickheaderview) add_subdirectory(qquickiconimage) add_subdirectory(qquickiconlabel) add_subdirectory(qquickimaginestyle) -add_subdirectory(qquickmaterialstyle) -add_subdirectory(qquickmaterialstyleconf) +if (QT_FEATURE_quickcontrols2_material) + add_subdirectory(qquickmaterialstyle) + add_subdirectory(qquickmaterialstyleconf) +endif() add_subdirectory(qquickmenu) add_subdirectory(qquickmenubar) add_subdirectory(qquickninepatchimage) diff --git a/tests/auto/quickcontrols/controls/basic/CMakeLists.txt b/tests/auto/quickcontrols/controls/basic/CMakeLists.txt index dabab76bbe..74bbf2954e 100644 --- a/tests/auto/quickcontrols/controls/basic/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/basic/CMakeLists.txt @@ -43,6 +43,9 @@ set_source_files_properties(${test_data} HEADER_FILE_ONLY ON ) +set(test_target tst_basic) +include(../shared.cmake) + #### Keys ignored in scope 1:.:.:basic.pro:<TRUE>: # OTHER_FILES = "$$PWD/../data/*.qml" # TEMPLATE = "app" diff --git a/tests/auto/quickcontrols/controls/data/combobox/shader.frag b/tests/auto/quickcontrols/controls/data/combobox/shader.frag new file mode 100644 index 0000000000..fbbef218e6 --- /dev/null +++ b/tests/auto/quickcontrols/controls/data/combobox/shader.frag @@ -0,0 +1,19 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#version 440 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; +layout(binding = 1) uniform sampler2D source; // this item + +layout(std140, binding = 0) uniform buf { + float qt_Opacity; // inherited opacity of this item +}; + + +void main() { + vec4 p = texture(source, qt_TexCoord0); + lowp float g = dot(p.xyz, vec3(0.344, 0.5, 0.156)); + fragColor = vec4(g, g, g, p.a) * qt_Opacity; +} diff --git a/tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml b/tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml index 4560c93dea..296aa2c507 100644 --- a/tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml +++ b/tests/auto/quickcontrols/controls/data/tst_abstractbutton.qml @@ -858,7 +858,7 @@ TestCase { // Ensure that clicked is emitted when no handler is defined for the pressAndHold() signal. // Note that even though signal spies aren't considered in QObject::isSignalConnected(), // we can't use one here to check for pressAndHold(), because otherwise clicked() won't be emitted. - wait(Qt.styleHints.mousePressAndHoldInterval + 100) + wait(Application.styleHints.mousePressAndHoldInterval + 100) mouseRelease(control) compare(clickedSpy.count, 1) } diff --git a/tests/auto/quickcontrols/controls/data/tst_button.qml b/tests/auto/quickcontrols/controls/data/tst_button.qml index 8720b55eff..4ca8ebfd6d 100644 --- a/tests/auto/quickcontrols/controls/data/tst_button.qml +++ b/tests/auto/quickcontrols/controls/data/tst_button.qml @@ -4,6 +4,7 @@ import QtQuick import QtTest import QtQuick.Controls +import Qt.test.controls TestCase { id: testCase @@ -448,12 +449,20 @@ TestCase { verify(!textLabel) compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + if (StyleInfo.styleName === "Material") { + compare(control.leftPadding, Material.buttonLeftPadding(false, true)) + compare(control.rightPadding, Material.buttonRightPadding(false, true, false)) + } break; case Button.TextOnly: verify(!iconImage) verify(textLabel) compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + if (StyleInfo.styleName === "Material") { + compare(control.leftPadding, Material.buttonLeftPadding(false, false)) + compare(control.rightPadding, Material.buttonRightPadding(false, false, true)) + } break; case Button.TextUnderIcon: verify(iconImage) @@ -461,6 +470,10 @@ TestCase { compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) verify(iconImage.y < textLabel.y) + if (StyleInfo.styleName === "Material") { + compare(control.leftPadding, Material.buttonLeftPadding(false, true)) + compare(control.rightPadding, Material.buttonRightPadding(false, true, true)) + } break; case Button.TextBesideIcon: verify(iconImage) @@ -471,6 +484,10 @@ TestCase { verify(iconImage.x < textLabel.x) compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + if (StyleInfo.styleName === "Material") { + compare(control.leftPadding, Material.buttonLeftPadding(false, true)) + compare(control.rightPadding, Material.buttonRightPadding(false, true, true)) + } break; } } diff --git a/tests/auto/quickcontrols/controls/data/tst_buttongroup.qml b/tests/auto/quickcontrols/controls/data/tst_buttongroup.qml index ef97405234..bec2d8ca88 100644 --- a/tests/auto/quickcontrols/controls/data/tst_buttongroup.qml +++ b/tests/auto/quickcontrols/controls/data/tst_buttongroup.qml @@ -304,7 +304,9 @@ TestCase { id: repeater Column { id: column - property ButtonGroup group: ButtonGroup { buttons: column.children } + property ButtonGroup group: ButtonGroup { + buttons: column.children.filter((child) => child !== r) + } property alias repeater: r Repeater { id: r @@ -392,7 +394,7 @@ TestCase { id: checkedButtonColumn Column { id: column - ButtonGroup { buttons: column.children } + ButtonGroup { buttons: column.children.filter((child) => child !== repeater) } Repeater { id: repeater delegate: Button { diff --git a/tests/auto/quickcontrols/controls/data/tst_checkbox.qml b/tests/auto/quickcontrols/controls/data/tst_checkbox.qml index 7d80484123..7a8e563131 100644 --- a/tests/auto/quickcontrols/controls/data/tst_checkbox.qml +++ b/tests/auto/quickcontrols/controls/data/tst_checkbox.qml @@ -201,7 +201,7 @@ TestCase { sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true, "checkState": Qt.Checked }], "pressed"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(sequenceSpy.success) @@ -220,7 +220,7 @@ TestCase { // release outside sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false, "checkState": Qt.Unchecked }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(sequenceSpy.success) diff --git a/tests/auto/quickcontrols/controls/data/tst_combobox.qml b/tests/auto/quickcontrols/controls/data/tst_combobox.qml index 8e671a38a6..0cc4de4d0c 100644 --- a/tests/auto/quickcontrols/controls/data/tst_combobox.qml +++ b/tests/auto/quickcontrols/controls/data/tst_combobox.qml @@ -74,16 +74,7 @@ TestCase { objectName: "ShaderFX" width: rect.width height: rect.height - fragmentShader: " - uniform lowp sampler2D source; // this item - uniform lowp float qt_Opacity; // inherited opacity of this item - varying highp vec2 qt_TexCoord0; - void main() { - lowp vec4 p = texture2D(source, qt_TexCoord0); - lowp float g = dot(p.xyz, vec3(0.344, 0.5, 0.156)); - gl_FragColor = vec4(g, g, g, p.a) * qt_Opacity; - }" - + fragmentShader: "qrc:/data/combobox/shader.frag.qsb" } } } @@ -1956,7 +1947,7 @@ TestCase { // and then that ComboBox loses focus, its currentIndex should change // to the index of the edit text (assuming a match is found). function test_currentIndexChangeOnLostFocus() { - if (Qt.styleHints.tabFocusBehavior !== Qt.TabFocusAllControls) + if (Application.styleHints.tabFocusBehavior !== Qt.TabFocusAllControls) skip("This platform only allows tab focus for text controls") let theModel = [] @@ -2004,11 +1995,16 @@ TestCase { compare(currentIndexSpy.count, 1) } + readonly property font testFont: ({ + family: "Arial", + pixelSize: 12 + }) + Component { - id: appFontTextFieldComponent + id: fixedFontTextFieldComponent TextField { objectName: "appFontTextField" - font: Qt.application.font + font: testCase.testFont // We don't want the background's implicit width to interfere with our tests, // which are about implicit width of the contentItem of ComboBox, which is by default TextField. background: null @@ -2016,14 +2012,14 @@ TestCase { } Component { - id: appFontContentItemComboBoxComponent + id: fixedFontContentItemComboBoxComponent ComboBox { // Override the contentItem so that the font doesn't vary between styles. contentItem: TextField { objectName: "appFontContentItemTextField" // We do this just to be extra sure that the font never comes from the control, - // as we want it to match that of the TextField in the appFontTextFieldComponent. - font: Qt.application.font + // as we want it to match that of the TextField in the fixedFontTextFieldComponent. + font: testCase.testFont background: null } } @@ -2077,14 +2073,14 @@ TestCase { function test_implicitContentWidthPolicy_ContentItemImplicitWidth() { // Set ContentItemImplicitWidth and ensure that implicitContentWidth is as wide as the current item // by comparing it against the implicitWidth of an identical TextField - let control = createTemporaryObject(appFontContentItemComboBoxComponent, testCase, { + let control = createTemporaryObject(fixedFontContentItemComboBoxComponent, testCase, { model: ["Short", "Kinda long"], implicitContentWidthPolicy: ComboBox.ContentItemImplicitWidth }) verify(control) compare(control.implicitContentWidthPolicy, ComboBox.ContentItemImplicitWidth) - let textField = createTemporaryObject(appFontTextFieldComponent, testCase) + let textField = createTemporaryObject(fixedFontTextFieldComponent, testCase) verify(textField) // Don't set any text on textField because we're not accounting for the widest // text here, so we want to compare it against an empty TextField. @@ -2103,14 +2099,14 @@ TestCase { } function test_implicitContentWidthPolicy_WidestText(data) { - let control = createTemporaryObject(appFontContentItemComboBoxComponent, testCase, { + let control = createTemporaryObject(fixedFontContentItemComboBoxComponent, testCase, { model: data.model, implicitContentWidthPolicy: ComboBox.WidestText }) verify(control) compare(control.implicitContentWidthPolicy, ComboBox.WidestText) - let textField = createTemporaryObject(appFontTextFieldComponent, testCase) + let textField = createTemporaryObject(fixedFontTextFieldComponent, testCase) verify(textField) textField.text = "Kinda long" // Note that we don't need to change the current index here, as the implicitContentWidth @@ -2137,7 +2133,7 @@ TestCase { // Changes in font should result in the implicitContentWidth being updated. textField.font.pixelSize *= 2 // We have to change the contentItem's font size manually since we break the - // style's binding to the control's font when we set Qt.application.font to it. + // style's binding to the control's font when we set the fixed font on it. control.contentItem.font.pixelSize *= 2 control.font.pixelSize *= 2 compare(Math.ceil(control.implicitContentWidth), Math.ceil(textField.implicitWidth)) @@ -2148,14 +2144,14 @@ TestCase { } function test_implicitContentWidthPolicy_WidestTextWhenCompleted(data) { - let control = createTemporaryObject(appFontContentItemComboBoxComponent, testCase, { + let control = createTemporaryObject(fixedFontContentItemComboBoxComponent, testCase, { model: data.model, implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted }) verify(control) compare(control.implicitContentWidthPolicy, ComboBox.WidestTextWhenCompleted) - let textField = createTemporaryObject(appFontTextFieldComponent, testCase) + let textField = createTemporaryObject(fixedFontTextFieldComponent, testCase) verify(textField) textField.text = "Kinda long" compare(Math.ceil(control.implicitContentWidth), Math.ceil(textField.implicitWidth)) diff --git a/tests/auto/quickcontrols/controls/data/tst_control.qml b/tests/auto/quickcontrols/controls/data/tst_control.qml index 4a2bc33b33..5c5e557f11 100644 --- a/tests/auto/quickcontrols/controls/data/tst_control.qml +++ b/tests/auto/quickcontrols/controls/data/tst_control.qml @@ -1064,7 +1064,7 @@ TestCase { verify(control) compare(control.hovered, false) - compare(control.hoverEnabled, Qt.styleHints.useHoverEffects) + compare(control.hoverEnabled, Application.styleHints.useHoverEffects) control.hoverEnabled = false @@ -1098,7 +1098,7 @@ TestCase { function test_hoverEnabled() { let control = createTemporaryObject(component, testCase) - compare(control.hoverEnabled, Qt.styleHints.useHoverEffects) + compare(control.hoverEnabled, Application.styleHints.useHoverEffects) let child = component.createObject(control) let grandChild = component.createObject(child) diff --git a/tests/auto/quickcontrols/controls/data/tst_delaybutton.qml b/tests/auto/quickcontrols/controls/data/tst_delaybutton.qml index 27145cd2ab..82d4f56495 100644 --- a/tests/auto/quickcontrols/controls/data/tst_delaybutton.qml +++ b/tests/auto/quickcontrols/controls/data/tst_delaybutton.qml @@ -172,7 +172,7 @@ TestCase { "pressed", "activated"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) tryVerify(function() { return sequenceSpy.success}) @@ -190,7 +190,7 @@ TestCase { sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], ["downChanged", { "down": true }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(sequenceSpy.success) @@ -208,7 +208,7 @@ TestCase { sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], ["downChanged", { "down": true }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(sequenceSpy.success) diff --git a/tests/auto/quickcontrols/controls/data/tst_radiobutton.qml b/tests/auto/quickcontrols/controls/data/tst_radiobutton.qml index 08caac977d..47cbc7b72b 100644 --- a/tests/auto/quickcontrols/controls/data/tst_radiobutton.qml +++ b/tests/auto/quickcontrols/controls/data/tst_radiobutton.qml @@ -154,7 +154,7 @@ TestCase { sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(sequenceSpy.success) @@ -169,7 +169,7 @@ TestCase { // release outside sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(sequenceSpy.success) diff --git a/tests/auto/quickcontrols/controls/data/tst_splitview.qml b/tests/auto/quickcontrols/controls/data/tst_splitview.qml index e835a8440f..a2054000b6 100644 --- a/tests/auto/quickcontrols/controls/data/tst_splitview.qml +++ b/tests/auto/quickcontrols/controls/data/tst_splitview.qml @@ -74,20 +74,19 @@ TestCase { // Note that the indices mentioned here account for handles; they do not // match the indices reported by QQuickSplitView's logging categories. compare(item.x, expectedGeometry.x, "Mismatch in actual vs expected x value of " - + itemType + " at index " + typeSpecificIndex + context) + + itemType + " " + item + " at index " + typeSpecificIndex + context) compare(item.y, expectedGeometry.y, "Mismatch in actual vs expected y value of " - + itemType + " at index " + typeSpecificIndex + context) + + itemType + " " + item + " at index " + typeSpecificIndex + context) compare(item.width, expectedGeometry.width, "Mismatch in actual vs expected width value of " - + itemType + " at index " + typeSpecificIndex + context) + + itemType + " " + item + " at index " + typeSpecificIndex + context) compare(item.height, expectedGeometry.height, "Mismatch in actual vs expected height value of " - + itemType + " at index " + typeSpecificIndex + context) + + itemType + " " + item + " at index " + typeSpecificIndex + context) } } property real defaultHorizontalHandleWidth: 10 property real defaultVerticalHandleHeight: 10 - Component { id: signalSpyComponent SignalSpy {} @@ -96,14 +95,14 @@ TestCase { Component { id: handleComponent Rectangle { - objectName: "handle" + objectName: `handle ${x},${y} ${width}x${height} visible: ${visible}` implicitWidth: defaultHorizontalHandleWidth implicitHeight: defaultVerticalHandleHeight color: "#444" Text { - objectName: "handleText_" + text - text: parent.x + "," + parent.y + " " + parent.width + "x" + parent.height + objectName: text + "_Text" + text: parent.objectName color: "white" anchors.centerIn: parent rotation: 90 @@ -2672,4 +2671,88 @@ TestCase { verify(!firstHandle.SplitHandle.pressed) compare(firstItem.width, 125) } + + Component { + id: hiddenItemComponent + + SplitView { + anchors.fill: parent + handle: handleComponent + orientation: Qt.Horizontal + + component SplitItem: Rectangle { + objectName: labelText + + SplitView.preferredWidth: 50 + SplitView.fillHeight: true + + required property string labelText + + Text { + anchors.fill: parent + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: `${parent.labelText} - width: ${parent.width.toFixed(2)}` + } + } + + SplitItem { + color: "blue" + labelText: "View 1" + } + SplitItem { + color: "red" + labelText: "View 2 (hidden)" + visible: false + } + SplitItem { + color: "purple" + labelText: "View 3" + } + SplitItem { + color: "yellow" + labelText: "View 4" + } + } + } + + function test_resizeHiddenItem() { + let control = createTemporaryObject(hiddenItemComponent, testCase) + verify(control) + + const standardItemWidth = 50 + let expectedGeometries = [ + // First item. + { x: 0, y: 0, width: standardItemWidth, height: control.height }, + // First handle. + { x: standardItemWidth, y: 0, width: defaultHorizontalHandleWidth, height: control.height }, + // The second item and its handle are hidden. + { hidden: true }, + { hidden: true }, + // Third item. + { x: standardItemWidth + defaultHorizontalHandleWidth, y: 0, width: standardItemWidth, height: control.height }, + // Third handle. + { x: (standardItemWidth * 2) + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: control.height }, + // Fourth item. + { x: (standardItemWidth * 2) + (defaultHorizontalHandleWidth * 2), y: 0, + width: control.width - (standardItemWidth * 2) - (defaultHorizontalHandleWidth * 2), height: control.height } + ] + compareSizes(control, expectedGeometries, "before dragging handle") + + // Drag the third handle to the right. + let handles = findHandles(control) + let thirdHandle = handles[2] + // The third (index 4 here) item should get one pixel bigger, and the fourth one pixel smaller. + ++expectedGeometries[4].width + ++expectedGeometries[5].x // handle + ++expectedGeometries[6].x + --expectedGeometries[6].width + // Use individual events rather than mouseDrag because that will move it past the drag threshold, + // which we don't want, since we only want to move by 1 pixel. + mousePress(thirdHandle) + mouseMove(thirdHandle, thirdHandle.width / 2 + 1, thirdHandle.height / 2, 16) + mouseRelease(thirdHandle) + compareSizes(control, expectedGeometries, "after dragging handle") + } } diff --git a/tests/auto/quickcontrols/controls/data/tst_swipedelegate.qml b/tests/auto/quickcontrols/controls/data/tst_swipedelegate.qml index 8d57e37575..ddbd283c87 100644 --- a/tests/auto/quickcontrols/controls/data/tst_swipedelegate.qml +++ b/tests/auto/quickcontrols/controls/data/tst_swipedelegate.qml @@ -14,7 +14,7 @@ TestCase { when: windowShown name: "SwipeDelegate" - readonly property int dragDistance: Math.max(20, Qt.styleHints.startDragDistance + 5) + readonly property int dragDistance: Math.max(20, Application.styleHints.startDragDistance + 5) Component { id: backgroundFillComponent @@ -655,6 +655,20 @@ TestCase { width: 100 height: 120 + property int rotation: 0 + + transform: [ + Rotation { + angle: rotation + origin.x: 0 + origin.y: 0 + }, + Translate { + x: (rotation === 90) ? parent.width : 0 + y: 0 + } + ] + model: ListModel { ListElement { name: "Apple" } ListElement { name: "Orange" } @@ -712,15 +726,20 @@ TestCase { function test_removableDelegates_data() { return [ { tag: "mouse", touch: false }, - { tag: "touch", touch: true } + { tag: "touch", touch: true }, + { tag: "mouse_rotation_90", touch: false, rotation: 90 }, + { tag: "touch_rotation_90", touch: true, rotation: 90 }, ] } - function test_removableDelegates() { - var listView = createTemporaryObject(removableDelegatesComponent, testCase); + function test_removableDelegates(data) { + let listView = createTemporaryObject(removableDelegatesComponent, testCase); verify(listView); compare(listView.count, 3); + if (data.rotation) + listView.rotation = data.rotation; + let touch = data.touch ? touchEvent(listView) : null // Expose the remove button. diff --git a/tests/auto/quickcontrols/controls/data/tst_swipeview.qml b/tests/auto/quickcontrols/controls/data/tst_swipeview.qml index 0c69e26f12..694741ce45 100644 --- a/tests/auto/quickcontrols/controls/data/tst_swipeview.qml +++ b/tests/auto/quickcontrols/controls/data/tst_swipeview.qml @@ -561,7 +561,7 @@ TestCase { } function test_focus() { - if (Qt.styleHints.tabFocusBehavior !== Qt.TabFocusAllControls) + if (Application.styleHints.tabFocusBehavior !== Qt.TabFocusAllControls) skip("This platform only allows tab focus for text controls") var control = createTemporaryObject(focusSwipeViewComponent, testCase) diff --git a/tests/auto/quickcontrols/controls/data/tst_switch.qml b/tests/auto/quickcontrols/controls/data/tst_switch.qml index 407513b914..8d6160cef1 100644 --- a/tests/auto/quickcontrols/controls/data/tst_switch.qml +++ b/tests/auto/quickcontrols/controls/data/tst_switch.qml @@ -210,7 +210,7 @@ TestCase { spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(spy.success) @@ -227,7 +227,7 @@ TestCase { // release on the right spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(spy.success) @@ -246,7 +246,7 @@ TestCase { // release on the left spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(spy.success) @@ -265,7 +265,7 @@ TestCase { // release in the middle spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, 0, 0).commit() compare(control.pressed, true) verify(spy.success) @@ -426,7 +426,7 @@ TestCase { spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, 0).commit() compare(control.position, 1.0) compare(control.checked, true) @@ -462,7 +462,7 @@ TestCase { // press-drag-release from and to outside the indicator spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width - 1).commit() compare(control.position, 0.0) compare(control.checked, false) diff --git a/tests/auto/quickcontrols/controls/data/tst_switchdelegate.qml b/tests/auto/quickcontrols/controls/data/tst_switchdelegate.qml index 42d58b9668..570e89a531 100644 --- a/tests/auto/quickcontrols/controls/data/tst_switchdelegate.qml +++ b/tests/auto/quickcontrols/controls/data/tst_switchdelegate.qml @@ -201,7 +201,7 @@ TestCase { spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(spy.success) @@ -218,7 +218,7 @@ TestCase { // release on the right spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(spy.success) @@ -237,7 +237,7 @@ TestCase { // release on the left spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width / 2, control.height / 2).commit() compare(control.pressed, true) verify(spy.success) @@ -256,7 +256,7 @@ TestCase { // release in the middle spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, 0, 0).commit() compare(control.pressed, true) verify(spy.success) @@ -417,7 +417,7 @@ TestCase { spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], "pressed"] // Don't want to double-click. - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, 0).commit() compare(control.position, 1.0) compare(control.checked, true) @@ -453,7 +453,7 @@ TestCase { // press-drag-release from and to outside the indicator spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], "pressed"] - wait(Qt.styleHints.mouseDoubleClickInterval + 50) + wait(Application.styleHints.mouseDoubleClickInterval + 50) touch.press(0, control, control.width - 1).commit() compare(control.position, 0.0) compare(control.checked, false) diff --git a/tests/auto/quickcontrols/controls/data/tst_tumbler.qml b/tests/auto/quickcontrols/controls/data/tst_tumbler.qml index 0bbade9c43..0491ab039e 100644 --- a/tests/auto/quickcontrols/controls/data/tst_tumbler.qml +++ b/tests/auto/quickcontrols/controls/data/tst_tumbler.qml @@ -1149,25 +1149,46 @@ TestCase { } } - function test_setCurrentIndexOnImperativeModelChange() { - var tumbler = createTemporaryObject(setCurrentIndexOnImperativeModelChangeComponent, testCase); - verify(tumbler); + function test_setCurrentIndexOnImperativeModelChange_data() { + return [ + { tag: "default wrap", setWrap: false, initialWrap: false, newWrap: true }, + { tag: "wrap=false", setWrap: true, initialWrap: false, newWrap: false }, + { tag: "wrap=true", setWrap: true, initialWrap: true, newWrap: true }, + ] + } - tumbler.model = 4 - compare(tumbler.count, 4); - tumblerView = findView(tumbler); - tryCompare(tumblerView, "count", 4); + function test_setCurrentIndexOnImperativeModelChange(data) { + let tumbler = createTemporaryObject(setCurrentIndexOnImperativeModelChangeComponent, testCase, + data.setWrap ? {wrap: data.initialWrap} : {}) + verify(tumbler) - // 4 - 2 = 2 - compare(tumbler.currentIndex, 2); + let model = 4 + let expectedCurrentIndex = model - 2 - ++tumbler.model; - compare(tumbler.count, 5); - compare(tumbler.wrap, true); - tumblerView = findView(tumbler); - tryCompare(tumblerView, "count", 5); - // 5 - 2 = 3 - compare(tumbler.currentIndex, 3); + tumbler.model = model + + compare(tumbler.count, model) + compare(tumbler.wrap, data.initialWrap) + compare(tumbler.currentIndex, expectedCurrentIndex) + + let tumblerView = findView(tumbler) + verify(tumblerView) + tryCompare(tumblerView, "count", model) + tryCompare(tumblerView, "currentIndex", expectedCurrentIndex) + + model = 5 + expectedCurrentIndex = model - 2 + + tumbler.model = model + + compare(tumbler.count, model) + compare(tumbler.wrap, data.newWrap) + compare(tumbler.currentIndex, expectedCurrentIndex) + + tumblerView = findView(tumbler) + verify(tumblerView) + tryCompare(tumblerView, "count", model) + tryCompare(tumblerView, "currentIndex", expectedCurrentIndex) } Component { @@ -1176,35 +1197,59 @@ TestCase { Item { property alias tumbler: tumbler - property int setting: 4 + required property int modelValue + property alias tumblerWrap: tumbler.wrap Tumbler { id: tumbler - model: setting + model: modelValue onModelChanged: currentIndex = model - 2 } } } - function test_setCurrentIndexOnDeclarativeModelChange() { - var root = createTemporaryObject(setCurrentIndexOnDeclarativeModelChangeComponent, testCase); - verify(root); + function test_setCurrentIndexOnDeclarativeModelChange_data() { + return [ + { tag: "default wrap", setWrap: false, initialWrap: false, newWrap: true }, + { tag: "wrap=false", setWrap: true, initialWrap: false, newWrap: false }, + { tag: "wrap=true", setWrap: true, initialWrap: true, newWrap: true }, + ] + } - var tumbler = root.tumbler; - compare(tumbler.count, 4); - compare(tumbler.wrap, false); - tumblerView = findView(tumbler); - tryCompare(tumblerView, "count", 4); - // 4 - 2 = 2 - compare(tumbler.currentIndex, 2); + function test_setCurrentIndexOnDeclarativeModelChange(data) { + let model = 4 + let expectedCurrentIndex = model - 2 - ++root.setting; - compare(tumbler.count, 5); - compare(tumbler.wrap, true); - tumblerView = findView(tumbler); - tryCompare(tumblerView, "count", 5); - // 5 - 2 = 3 - compare(tumbler.currentIndex, 3); + let root = createTemporaryObject(setCurrentIndexOnDeclarativeModelChangeComponent, testCase, + data.setWrap ? {modelValue: model, tumblerWrap: data.initialWrap} + : {modelValue: model}) + verify(root) + + let tumbler = root.tumbler + verify(tumbler) + + compare(tumbler.count, model) + compare(tumbler.wrap, data.initialWrap) + compare(tumbler.currentIndex, expectedCurrentIndex) + + let tumberView = findView(tumbler) + verify(tumbler) + tryCompare(tumberView, "count", model) + tryCompare(tumberView, "currentIndex", expectedCurrentIndex) + + model = 5 + expectedCurrentIndex = model - 2 + + root.modelValue = model + + compare(tumbler.count, model) + compare(tumbler.wrap, data.newWrap) + compare(tumbler.currentIndex, expectedCurrentIndex) + + tumberView = findView(tumbler) + verify(tumbler) + tryCompare(tumberView, "count", model) + tryCompare(tumberView, "currentIndex", expectedCurrentIndex) } function test_displacementAfterResizing() { @@ -1237,15 +1282,46 @@ TestCase { height: 200 delegate: Text {text: modelData} model: 10 - currentIndex: 4 + currentIndex: 1 } } + //QTBUG-127315 + Component { + id: initialCurrentIndexTumblerNoDelegate - function test_initialCurrentIndex() { - var tumbler = createTemporaryObject(initialCurrentIndexTumbler, testCase, {wrap: true}); - compare(tumbler.currentIndex, 4); - tumbler = createTemporaryObject(initialCurrentIndexTumbler, testCase, {wrap: false}); - compare(tumbler.currentIndex, 4); + Tumbler { + anchors.centerIn: parent + width: 60 + height: 200 + model: 10 + } + } + + function test_initialCurrentIndex_data() { + return [ + { tag: "delegate: true, wrap: true, currentIndex: 1", + component: initialCurrentIndexTumbler, wrap: true, currentIndex: 1 }, + { tag: "delegate: true, wrap: false, currentIndex: 1", + component: initialCurrentIndexTumbler, wrap: false, currentIndex: 1 }, + { tag: "delegate: false, wrap: true, currentIndex: 1", + component: initialCurrentIndexTumblerNoDelegate, wrap: true, currentIndex: 1 }, + { tag: "delegate: false, wrap: false, currentIndex: 1", + component: initialCurrentIndexTumblerNoDelegate, wrap: false, currentIndex: 1 }, + { tag: "delegate: true, wrap: true, currentIndex: 4", + component: initialCurrentIndexTumbler, wrap: true, currentIndex: 4 }, + { tag: "delegate: true, wrap: false, currentIndex: 4", + component: initialCurrentIndexTumbler, wrap: false, currentIndex: 4 }, + { tag: "delegate: false, wrap: true, currentIndex: 4", + component: initialCurrentIndexTumblerNoDelegate, wrap: true, currentIndex: 4 }, + { tag: "delegate: false, wrap: false, currentIndex: 4", + component: initialCurrentIndexTumblerNoDelegate, wrap: false, currentIndex: 4 }, + ] + } + + function test_initialCurrentIndex(data) { + let tumbler = createTemporaryObject(data.component, testCase, + {wrap: data.wrap, currentIndex: data.currentIndex}); + compare(tumbler.currentIndex, data.currentIndex); } // QTBUG-109995 diff --git a/tests/auto/quickcontrols/controls/fusion/CMakeLists.txt b/tests/auto/quickcontrols/controls/fusion/CMakeLists.txt index 5495efd858..e3d38e1cf3 100644 --- a/tests/auto/quickcontrols/controls/fusion/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/fusion/CMakeLists.txt @@ -43,6 +43,9 @@ set_source_files_properties(${test_data} HEADER_FILE_ONLY ON ) +set(test_target tst_fusion) +include(../shared.cmake) + #### Keys ignored in scope 1:.:.:fusion.pro:<TRUE>: # OTHER_FILES = "$$PWD/../data/*.qml" # TEMPLATE = "app" diff --git a/tests/auto/quickcontrols/controls/imagine/CMakeLists.txt b/tests/auto/quickcontrols/controls/imagine/CMakeLists.txt index 6efb1a0a45..c296a5b1a4 100644 --- a/tests/auto/quickcontrols/controls/imagine/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/imagine/CMakeLists.txt @@ -43,6 +43,9 @@ set_source_files_properties(${test_data} HEADER_FILE_ONLY ON ) +set(test_target tst_imagine) +include(../shared.cmake) + #### Keys ignored in scope 1:.:.:imagine.pro:<TRUE>: # OTHER_FILES = "$$PWD/../data/*.qml" # TEMPLATE = "app" diff --git a/tests/auto/quickcontrols/controls/ios/CMakeLists.txt b/tests/auto/quickcontrols/controls/ios/CMakeLists.txt index fe6839990f..4e0cb72e68 100644 --- a/tests/auto/quickcontrols/controls/ios/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/ios/CMakeLists.txt @@ -26,3 +26,5 @@ qt_internal_add_test(tst_ios TESTDATA ${test_data} ) +set(test_target tst_ios) +include(../shared.cmake) diff --git a/tests/auto/quickcontrols/controls/macos/CMakeLists.txt b/tests/auto/quickcontrols/controls/macos/CMakeLists.txt index 04d186c63f..3202601316 100644 --- a/tests/auto/quickcontrols/controls/macos/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/macos/CMakeLists.txt @@ -36,3 +36,6 @@ set_source_files_properties(${test_data} PROPERTIES HEADER_FILE_ONLY ON ) + +set(test_target tst_macos) +include(../shared.cmake) diff --git a/tests/auto/quickcontrols/controls/material/CMakeLists.txt b/tests/auto/quickcontrols/controls/material/CMakeLists.txt index 3e87bf1609..506509c774 100644 --- a/tests/auto/quickcontrols/controls/material/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/material/CMakeLists.txt @@ -6,7 +6,7 @@ if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) cmake_minimum_required(VERSION 3.16) project(tst_material LANGUAGES C CXX ASM) - find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST) + find_package(Qt6BuildInternals REQUIRED COMPONENTS ShaderTools STANDALONE_TEST) endif() ##################################################################### @@ -43,6 +43,9 @@ set_source_files_properties(${test_data} HEADER_FILE_ONLY ON ) +set(test_target tst_material) +include(../shared.cmake) + #### Keys ignored in scope 1:.:.:material.pro:<TRUE>: # OTHER_FILES = "$$PWD/../data/*.qml" # TEMPLATE = "app" diff --git a/tests/auto/quickcontrols/controls/shared.cmake b/tests/auto/quickcontrols/controls/shared.cmake new file mode 100644 index 0000000000..ca62b88db6 --- /dev/null +++ b/tests/auto/quickcontrols/controls/shared.cmake @@ -0,0 +1,12 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt6_add_shaders(${test_target} "${test_target}_shaders" + BATCHABLE + PREFIX + "/" + BASE + "../" + FILES + "../data/combobox/shader.frag" +) diff --git a/tests/auto/quickcontrols/controls/universal/CMakeLists.txt b/tests/auto/quickcontrols/controls/universal/CMakeLists.txt index f0c7d2ed80..f39975ab15 100644 --- a/tests/auto/quickcontrols/controls/universal/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/universal/CMakeLists.txt @@ -43,6 +43,9 @@ set_source_files_properties(${test_data} HEADER_FILE_ONLY ON ) +set(test_target tst_universal) +include(../shared.cmake) + #### Keys ignored in scope 1:.:.:universal.pro:<TRUE>: # OTHER_FILES = "$$PWD/../data/*.qml" # TEMPLATE = "app" diff --git a/tests/auto/quickcontrols/controls/windows/CMakeLists.txt b/tests/auto/quickcontrols/controls/windows/CMakeLists.txt index a5ba30a51b..1d6ab1fc57 100644 --- a/tests/auto/quickcontrols/controls/windows/CMakeLists.txt +++ b/tests/auto/quickcontrols/controls/windows/CMakeLists.txt @@ -36,3 +36,6 @@ set_source_files_properties(${test_data} PROPERTIES HEADER_FILE_ONLY ON ) + +set(test_target tst_windows) +include(../shared.cmake) diff --git a/tests/auto/quickcontrols/focus/tst_focus.cpp b/tests/auto/quickcontrols/focus/tst_focus.cpp index ca70146885..78db22ffbc 100644 --- a/tests/auto/quickcontrols/focus/tst_focus.cpp +++ b/tests/auto/quickcontrols/focus/tst_focus.cpp @@ -29,6 +29,7 @@ public: tst_focus(); private slots: + void init() override; void initTestCase() override; void navigation_data(); @@ -50,8 +51,14 @@ tst_focus::tst_focus() { } +void tst_focus::init() +{ + QTest::failOnWarning(QRegularExpression(".?")); +} + void tst_focus::initTestCase() { + SKIP_IF_NO_WINDOW_ACTIVATION QQuickStyle::setStyle("Basic"); QQmlDataTest::initTestCase(); } diff --git a/tests/auto/quickcontrols/palette/data/comboBoxPopupWithApplicationWindow.qml b/tests/auto/quickcontrols/palette/data/comboBoxPopupWithApplicationWindow.qml new file mode 100644 index 0000000000..436d3cdad6 --- /dev/null +++ b/tests/auto/quickcontrols/palette/data/comboBoxPopupWithApplicationWindow.qml @@ -0,0 +1,32 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias topLevelComboBox: topLevelComboBox + property alias popup: popup + property alias comboBoxInPopup: comboBoxInPopup + + ComboBox { + id: topLevelComboBox + model: ["ONE", "TWO", "THREE"] + } + + Popup { + id: popup + width: 200 + height: 200 + visible: true + palette.window: "red" + + ComboBox { + id: comboBoxInPopup + model: ["ONE", "TWO", "THREE"] + } + } +} diff --git a/tests/auto/quickcontrols/palette/data/comboBoxPopupWithThemeDefault.qml b/tests/auto/quickcontrols/palette/data/comboBoxPopupWithThemeDefault.qml new file mode 100644 index 0000000000..592793fa3f --- /dev/null +++ b/tests/auto/quickcontrols/palette/data/comboBoxPopupWithThemeDefault.qml @@ -0,0 +1,17 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias comboBox: comboBox + + ComboBox { + id: comboBox + model: 1 + } +} diff --git a/tests/auto/quickcontrols/palette/data/comboBoxPopupWithWindow.qml b/tests/auto/quickcontrols/palette/data/comboBoxPopupWithWindow.qml new file mode 100644 index 0000000000..d806f30d01 --- /dev/null +++ b/tests/auto/quickcontrols/palette/data/comboBoxPopupWithWindow.qml @@ -0,0 +1,33 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window +import QtQuick.Controls + +Window { + width: 400 + height: 400 + + property alias topLevelComboBox: topLevelComboBox + property alias popup: popup + property alias comboBoxInPopup: comboBoxInPopup + + ComboBox { + id: topLevelComboBox + model: ["ONE", "TWO", "THREE"] + } + + Popup { + id: popup + width: 200 + height: 200 + visible: true + palette.window: "red" + + ComboBox { + id: comboBoxInPopup + model: ["ONE", "TWO", "THREE"] + } + } +} diff --git a/tests/auto/quickcontrols/palette/data/toolTipPaletteUpdate.qml b/tests/auto/quickcontrols/palette/data/toolTipPaletteUpdate.qml new file mode 100644 index 0000000000..3968c54cd0 --- /dev/null +++ b/tests/auto/quickcontrols/palette/data/toolTipPaletteUpdate.qml @@ -0,0 +1,19 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: window + + palette { toolTipBase: "white"; toolTipText: "black"} + + Button { + objectName: "button" + text: qsTr("Button with Tooltip") + + ToolTip.visible: false + ToolTip.text: qsTr("This is a tool tip.") + } +} diff --git a/tests/auto/quickcontrols/palette/tst_palette.cpp b/tests/auto/quickcontrols/palette/tst_palette.cpp index d8f4bfd804..621475b86e 100644 --- a/tests/auto/quickcontrols/palette/tst_palette.cpp +++ b/tests/auto/quickcontrols/palette/tst_palette.cpp @@ -7,17 +7,21 @@ #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtQuickControlsTestUtils/private/controlstestutils_p.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuickTemplates2/private/qquickapplicationwindow_p.h> +#include <QtQuickTemplates2/private/qquickcombobox_p.h> #include <QtQuickTemplates2/private/qquickcontrol_p.h> #include <QtQuickTemplates2/private/qquickcontrol_p_p.h> #include <QtQuickTemplates2/private/qquickpopup_p.h> #include <QtQuickTemplates2/private/qquickpopup_p_p.h> #include <QtQuickTemplates2/private/qquicktheme_p_p.h> #include <QtQuickTemplates2/private/qquickbutton_p.h> +#include <QtQuickTemplates2/private/qquicktooltip_p.h> #include <QtQuickControls2/qquickstyle.h> +using namespace QQuickVisualTestUtils; using namespace QQuickControlsTestUtils; // Need a more descriptive failure message: QTBUG-87039 @@ -55,6 +59,13 @@ private slots: void resolve(); void updateBindingPalette(); + + void comboBoxPopup_data(); + void comboBoxPopup(); + void comboBoxPopupWithThemeDefault_data(); + void comboBoxPopupWithThemeDefault(); + + void toolTipPaletteUpdate(); }; tst_palette::tst_palette() @@ -485,6 +496,120 @@ void tst_palette::updateBindingPalette() QCOMPARE(windowPalette->buttonText(), customPalette->buttonText()); } +void tst_palette::comboBoxPopup_data() +{ + QTest::addColumn<QString>("style"); + QTest::addColumn<QString>("qmlFilePath"); + + QTest::newRow("Window, Basic") << "Basic" << "comboBoxPopupWithWindow.qml"; + QTest::newRow("ApplicationWindow, Basic") << "Basic" << "comboBoxPopupWithApplicationWindow.qml"; + QTest::newRow("Window, Fusion") << "Fusion" << "comboBoxPopupWithWindow.qml"; + QTest::newRow("ApplicationWindow, Fusion") << "Fusion" << "comboBoxPopupWithApplicationWindow.qml"; +} + +// Unlike regular popups, which should inherit their palette from the window and not the parent popup, +// combo box popups should inherit their palette from the combo box itself. +void tst_palette::comboBoxPopup() +{ + QFETCH(QString, style); + QFETCH(QString, qmlFilePath); + + qmlClearTypeRegistrations(); + QQuickStyle::setStyle(style); + + QQuickApplicationHelper helper(this, qmlFilePath); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + const auto *windowPalette = window->property("palette").value<QQuickPalette *>(); + QVERIFY(windowPalette); + + const auto *popup = window->property("popup").value<QQuickPopup *>(); + QVERIFY(popup); + const auto *popupBackground = popup->background(); + QCOMPARE(popupBackground->property("color"), QColorConstants::Red); + QCOMPARE(popupBackground->property("palette").value<QQuickPalette*>()->toQPalette().window().color(), + QColorConstants::Red); + + // This has the default palette. + const auto *topLevelComboBox = window->property("topLevelComboBox").value<QQuickComboBox *>(); + QVERIFY(topLevelComboBox); + const auto *topLevelComboBoxBackground = topLevelComboBox->popup()->background(); + QCOMPARE_NE(topLevelComboBoxBackground->property("color"), QColorConstants::Red); + QCOMPARE_NE(topLevelComboBoxBackground->property("palette").value<QQuickPalette*>()->toQPalette().window().color(), + QColorConstants::Red); + + // The popup that this combo box is in has its window role set to red, + // so the combo box's popup background should be red too. + const auto *comboBoxInPopup = window->property("comboBoxInPopup").value<QQuickComboBox *>(); + QVERIFY(comboBoxInPopup); + const auto *comboBoxInPopupBackground = comboBoxInPopup->popup()->background(); + QCOMPARE(comboBoxInPopupBackground->property("color"), QColorConstants::Red); + QCOMPARE(comboBoxInPopupBackground->property("palette").value<QQuickPalette*>()->toQPalette().window().color(), + QColorConstants::Red); +} + +void tst_palette::comboBoxPopupWithThemeDefault_data() +{ + QTest::addColumn<QString>("style"); + QTest::addColumn<QColor>("expectedComboBoxPopupBackgroundColor"); + + QTest::newRow("Basic") << "Basic" << QColor::fromRgb(0xFFFFFF); + + // We can't test Fusion because it uses the default application palette, + // which is the default-constructed QPalette, so the test would always pass. +} + +void tst_palette::comboBoxPopupWithThemeDefault() +{ + QFETCH(QString, style); + QFETCH(QColor, expectedComboBoxPopupBackgroundColor); + + qmlClearTypeRegistrations(); + QQuickStyle::setStyle(style); + + QQuickApplicationHelper helper(this, "comboBoxPopupWithThemeDefault.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + const auto *comboBox = window->property("comboBox").value<QQuickComboBox *>(); + QVERIFY(comboBox); + const auto *comboBoxBackground = comboBox->popup()->background(); + QCOMPARE(comboBoxBackground->property("color"), expectedComboBoxPopupBackgroundColor); +} + +void tst_palette::toolTipPaletteUpdate() +{ + QQuickApplicationHelper helper(this, "toolTipPaletteUpdate.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *button = window->findChild<QQuickButton *>("button"); + QVERIFY(button); + auto *attachedToolTip = button->findChild<QQuickToolTipAttached *>(); + QVERIFY(attachedToolTip); + auto *toolTip = attachedToolTip->toolTip(); + QVERIFY(toolTip); + + auto windowPalette = QQuickWindowPrivate::get(window)->palette(); + auto toolTipPalette = QQuickPopupPrivate::get(toolTip)->palette(); + + QCOMPARE(toolTipPalette->toolTipBase(), windowPalette->toolTipBase()); + QCOMPARE(toolTipPalette->toolTipText(), windowPalette->toolTipText()); + + windowPalette->setToolTipBase(Qt::blue); + windowPalette->setToolTipText(Qt::red); + + QCOMPARE(toolTipPalette->toolTipBase(), windowPalette->toolTipBase()); + QCOMPARE(toolTipPalette->toolTipText(), windowPalette->toolTipText()); +} + QTEST_MAIN(tst_palette) #include "tst_palette.moc" diff --git a/tests/auto/quickcontrols/qquickapplicationwindow/data/backgroundSize.qml b/tests/auto/quickcontrols/qquickapplicationwindow/data/backgroundSize.qml index 0e4ed277d0..db5470986f 100644 --- a/tests/auto/quickcontrols/qquickapplicationwindow/data/backgroundSize.qml +++ b/tests/auto/quickcontrols/qquickapplicationwindow/data/backgroundSize.qml @@ -9,6 +9,7 @@ ApplicationWindow { height: 400 background: Item { + objectName: "background" implicitWidth: 123 implicitHeight: 456 } diff --git a/tests/auto/quickcontrols/qquickapplicationwindow/data/explicitBackgroundSizeBinding.qml b/tests/auto/quickcontrols/qquickapplicationwindow/data/explicitBackgroundSizeBinding.qml new file mode 100644 index 0000000000..67fdf7808c --- /dev/null +++ b/tests/auto/quickcontrols/qquickapplicationwindow/data/explicitBackgroundSizeBinding.qml @@ -0,0 +1,21 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: window + width: 600 + height: 400 + + property real scaleFactor: 1 + + background: Rectangle { + objectName: "background" + color: "green" + width: window.width * window.scaleFactor + height: window.height * window.scaleFactor + anchors.centerIn: parent + } +} diff --git a/tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp b/tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp index c7a5df4b68..a02ab9c595 100644 --- a/tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp +++ b/tests/auto/quickcontrols/qquickapplicationwindow/tst_qquickapplicationwindow.cpp @@ -12,6 +12,7 @@ #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtGui/private/qguiapplication_p.h> +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> #include <QtQuickTemplates2/private/qquickapplicationwindow_p.h> #include <QtQuickTemplates2/private/qquickoverlay_p.h> #include <QtQuickTemplates2/private/qquickcontrol_p.h> @@ -53,6 +54,7 @@ private slots: void componentComplete(); void opacity(); void backgroundSize(); + void explicitBackgroundSizeBinding(); }; tst_QQuickApplicationWindow::tst_QQuickApplicationWindow() @@ -978,6 +980,23 @@ void tst_QQuickApplicationWindow::backgroundSize() QCOMPARE(background->height(), 678); } +void tst_QQuickApplicationWindow::explicitBackgroundSizeBinding() +{ + QQuickControlsApplicationHelper helper(this, QLatin1String("explicitBackgroundSizeBinding.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *background = window->background(); + QCOMPARE(background->width(), window->width()); + QCOMPARE(background->height(), window->height()); + + window->setProperty("scaleFactor", 0.5); + QCOMPARE(background->width(), window->width() / 2); + QCOMPARE(background->height(), window->height() / 2); +} + QTEST_MAIN(tst_QQuickApplicationWindow) #include "tst_qquickapplicationwindow.moc" diff --git a/tests/auto/quickcontrols/qquickcontainer/CMakeLists.txt b/tests/auto/quickcontrols/qquickcontainer/CMakeLists.txt new file mode 100644 index 0000000000..c79e508cd1 --- /dev/null +++ b/tests/auto/quickcontrols/qquickcontainer/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qquickcontainer LANGUAGES C CXX ASM) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +# Collect test data +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + data/* +) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_qquickcontainer + SOURCES + tst_qquickcontainer.cpp + LIBRARIES + Qt::CorePrivate + Qt::Gui + Qt::QuickControls2 + Qt::QuickControlsTestUtilsPrivate + Qt::QuickPrivate + Qt::QuickTemplates2Private + Qt::QuickTest + TESTDATA ${test_data} +) + +qt_internal_extend_target(tst_qquickcontainer CONDITION ANDROID OR IOS + DEFINES + QT_QMLTEST_DATADIR=":/data" +) + +qt_internal_extend_target(tst_qquickcontainer CONDITION NOT ANDROID AND NOT IOS + DEFINES + QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data" +) diff --git a/tests/auto/quickcontrols/qquickcontainer/data/zeroSizeWithListView.qml b/tests/auto/quickcontrols/qquickcontainer/data/zeroSizeWithListView.qml new file mode 100644 index 0000000000..43c3614866 --- /dev/null +++ b/tests/auto/quickcontrols/qquickcontainer/data/zeroSizeWithListView.qml @@ -0,0 +1,58 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 640 + height: 480 + + property alias container: container + property alias text1: text1 + property alias text2: text2 + property alias text3: text3 + + component TextItem: Text { + font.pointSize: 24 + width: container.width + height: container.height + } + + Component { + id: textComponent + TextItem {} + } + + function addTextItem() { + container.addItem(textComponent.createObject(container, { text: " 4 " })) + } + + Item { + id: root + objectName: "root" + + Container { + id: container + anchors.fill: parent + contentItem: ListView { + model: container.contentModel + snapMode: ListView.SnapOneItem + orientation: ListView.Horizontal + } + + TextItem { + id: text1 + text: "1 " + } + TextItem { + id: text2 + text: " 2 " + } + TextItem { + id: text3 + text: " 3 " + } + } + } +} diff --git a/tests/auto/quickcontrols/qquickcontainer/data/zeroSizeWithRepeater.qml b/tests/auto/quickcontrols/qquickcontainer/data/zeroSizeWithRepeater.qml new file mode 100644 index 0000000000..f78b6e273c --- /dev/null +++ b/tests/auto/quickcontrols/qquickcontainer/data/zeroSizeWithRepeater.qml @@ -0,0 +1,58 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 640 + height: 480 + + property alias container: container + property alias text1: text1 + property alias text2: text2 + property alias text3: text3 + + component TextItem: Text { + font.pointSize: 24 + width: container.width + height: container.height + } + + Component { + id: textComponent + TextItem {} + } + + function addTextItem() { + container.addItem(textComponent.createObject(container, { text: " 4 " })) + } + + Item { + id: root + objectName: "root" + + Container { + id: container + anchors.fill: parent + contentItem: Row { + Repeater { + model: container.contentModel + } + } + + TextItem { + id: text1 + text: "1 " + } + TextItem { + id: text2 + text: " 2 " + } + TextItem { + id: text3 + text: " 3 " + } + } + } +} diff --git a/tests/auto/quickcontrols/qquickcontainer/tst_qquickcontainer.cpp b/tests/auto/quickcontrols/qquickcontainer/tst_qquickcontainer.cpp new file mode 100644 index 0000000000..ce95f2cc37 --- /dev/null +++ b/tests/auto/quickcontrols/qquickcontainer/tst_qquickcontainer.cpp @@ -0,0 +1,107 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest> +#include <QtQuickTemplates2/private/qquickcontainer_p.h> +#include <QtQuickControls2/qquickstyle.h> +#include <QtQuickTest/QtQuickTest> +#include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> +#include <QtQuickControlsTestUtils/private/controlstestutils_p.h> + +using namespace QQuickVisualTestUtils; +using namespace QQuickControlsTestUtils; + +class tst_qquickcontainer : public QQmlDataTest +{ + Q_OBJECT + +public: + tst_qquickcontainer(); + +private slots: + void zeroSize_data(); + void zeroSize(); +}; + +tst_qquickcontainer::tst_qquickcontainer() + : QQmlDataTest(QT_QMLTEST_DATADIR) +{ + qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + + QQuickStyle::setStyle("Basic"); +} + +void tst_qquickcontainer::zeroSize_data() +{ + QTest::addColumn<QString>("qmlFileName"); + QTest::addColumn<bool>("isItemView"); + + QTest::newRow("ListView") << "zeroSizeWithListView.qml" << true; + // See QQuickContainerPrivate::maybeCullItem for why this is false. + QTest::newRow("Repeater") << "zeroSizeWithRepeater.qml" << false; +} + +// Tests that a zero-size Container with a QQuickItemView sub-class culls its items. +// Based on a use case involving SwipeView: QTBUG-125416 +void tst_qquickcontainer::zeroSize() +{ + QFETCH(QString, qmlFileName); + QFETCH(bool, isItemView); + + QQuickControlsApplicationHelper helper(this, qmlFileName); + QVERIFY2(helper.ready, helper.failureMessage()); + centerOnScreen(helper.window); + helper.window->show(); + QVERIFY(QTest::qWaitForWindowExposed(helper.window)); + + auto *text1 = helper.window->property("text1").value<QQuickItem *>(); + QVERIFY(text1); + QCOMPARE(QQuickItemPrivate::get(text1)->culled, isItemView); + + auto *text2 = helper.window->property("text2").value<QQuickItem *>(); + QVERIFY(text2); + QCOMPARE(QQuickItemPrivate::get(text2)->culled, isItemView); + + auto *text3 = helper.window->property("text3").value<QQuickItem *>(); + QVERIFY(text3); + QCOMPARE(QQuickItemPrivate::get(text3)->culled, isItemView); + + // Add an item and check that it's culled appropriately. + QVERIFY(QMetaObject::invokeMethod(helper.window, "addTextItem")); + auto *container = helper.window->property("container").value<QQuickContainer *>(); + QVERIFY(container); + auto *text4 = container->itemAt(3); + QVERIFY(text4); + QCOMPARE(QQuickItemPrivate::get(text4)->culled, isItemView); + + // Give it a non-zero size (via its parent, which it fills). + container->parentItem()->setWidth(text1->implicitWidth()); + container->parentItem()->setHeight(text1->implicitHeight()); + if (isItemView) { + QVERIFY(QQuickTest::qIsPolishScheduled(helper.window)); + QVERIFY(QQuickTest::qWaitForPolish(helper.window)); + } + QCOMPARE(QQuickItemPrivate::get(text1)->culled, false); + // This one won't be culled for views either, because of cacheBuffer (and + // clipping apparently doesn't affect culling, if we were to set clip to true). + QCOMPARE(QQuickItemPrivate::get(text2)->culled, false); + QCOMPARE(QQuickItemPrivate::get(text3)->culled, isItemView); + QCOMPARE(QQuickItemPrivate::get(text4)->culled, isItemView); + + // Go back to a zero size. + container->parentItem()->setWidth(0); + container->parentItem()->setHeight(0); + if (isItemView) { + QVERIFY(QQuickTest::qIsPolishScheduled(helper.window)); + QVERIFY(QQuickTest::qWaitForPolish(helper.window)); + } + QCOMPARE(QQuickItemPrivate::get(text1)->culled, isItemView); + QCOMPARE(QQuickItemPrivate::get(text2)->culled, isItemView); + QCOMPARE(QQuickItemPrivate::get(text3)->culled, isItemView); + QCOMPARE(QQuickItemPrivate::get(text4)->culled, isItemView); +} + +QTEST_MAIN(tst_qquickcontainer) + +#include "tst_qquickcontainer.moc" diff --git a/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp b/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp index ee0c6262d8..3e88bdb2b0 100644 --- a/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp +++ b/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp @@ -1051,8 +1051,7 @@ void tst_QQuickDrawer::interactive_data() void tst_QQuickDrawer::interactive() { - if (!(QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QFETCH(QString, source); QQuickControlsApplicationHelper helper(this, source); diff --git a/tests/auto/quickcontrols/qquickmenu/data/mnemonics.qml b/tests/auto/quickcontrols/qquickmenu/data/mnemonics.qml index 8929b00275..091d087dbb 100644 --- a/tests/auto/quickcontrols/qquickmenu/data/mnemonics.qml +++ b/tests/auto/quickcontrols/qquickmenu/data/mnemonics.qml @@ -5,14 +5,18 @@ import QtQuick import QtQuick.Controls ApplicationWindow { + id: root width: 400 height: 400 + property bool enabled: true + property bool checkable: false property alias menu: menu property alias action: action property alias menuItem: menuItem property alias subMenu: subMenu property alias subMenuItem: subMenuItem + property alias subMenuAction: subMenuAction Menu { id: menu @@ -20,20 +24,33 @@ ApplicationWindow { Action { id: action text: "&Action" + checkable: root.checkable + enabled: root.enabled } MenuItem { id: menuItem text: "Menu &Item" + checkable: root.checkable + enabled: root.enabled } Menu { id: subMenu title: "Sub &Menu" + Action { + id: subMenuAction + text: "S&ub Menu Action" + checkable: root.checkable + enabled: root.enabled + } + MenuItem { id: subMenuItem text: "&Sub Menu Item" + checkable: root.checkable + enabled: root.enabled } } } diff --git a/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp b/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp index fbb4e7d5f9..f54678b686 100644 --- a/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp +++ b/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp @@ -47,6 +47,10 @@ private slots: void contextMenuKeyboard(); void disabledMenuItemKeyNavigation(); void mnemonics(); +#if QT_CONFIG(shortcut) + void checkableMnemonics_data(); + void checkableMnemonics(); +#endif void menuButton(); void addItem(); void menuSeparator(); @@ -87,9 +91,6 @@ private slots: void customMenuCullItems(); void customMenuUseRepeaterAsTheContentItem(); void invalidUrlInImgTag(); - -private: - static bool hasWindowActivation(); }; tst_QQuickMenu::tst_QQuickMenu() @@ -97,11 +98,6 @@ tst_QQuickMenu::tst_QQuickMenu() { } -bool tst_QQuickMenu::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - void tst_QQuickMenu::defaults() { QQuickControlsApplicationHelper helper(this, QLatin1String("applicationwindow.qml")); @@ -146,8 +142,7 @@ void tst_QQuickMenu::count() void tst_QQuickMenu::mouse() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) @@ -278,8 +273,7 @@ void tst_QQuickMenu::pressAndHold() void tst_QQuickMenu::contextMenuKeyboard() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); @@ -468,8 +462,7 @@ void tst_QQuickMenu::contextMenuKeyboard() // QTBUG-70181 void tst_QQuickMenu::disabledMenuItemKeyNavigation() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); @@ -535,8 +528,7 @@ void tst_QQuickMenu::disabledMenuItemKeyNavigation() void tst_QQuickMenu::mnemonics() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION #ifdef Q_OS_MACOS QSKIP("Mnemonics are not used on macOS"); @@ -591,10 +583,207 @@ void tst_QQuickMenu::mnemonics() QCOMPARE(subMenuItemSpy.size(), 1); } +#if QT_CONFIG(shortcut) +namespace CheckableMnemonics { +using MnemonicKey = std::pair<Qt::Key, QString>; + +enum class MenuItemType { + Action, + MenuItem +}; + +enum class SignalName { + CheckedChanged = 0x01, + Triggered = 0x02, +}; +Q_DECLARE_FLAGS(SignalNames, SignalName); + +class ItemSignalSpy +{ +public: + ItemSignalSpy(MenuItemType type, QObject *item) : item(item) + { + switch (type) { + case MenuItemType::Action: + initSignals<QQuickAction>(qobject_cast<QQuickAction *>(item)); + break; + case MenuItemType::MenuItem: + initSignals<QQuickMenuItem>(qobject_cast<QQuickMenuItem *>(item)); + break; + } + } + + [[nodiscard]] bool isValid() const + { + return ((checkedChangedSpy && checkedChangedSpy->isValid()) && + (triggeredSpy && triggeredSpy->isValid())); + } + + [[nodiscard]] int signalSize(SignalName signal) const + { + constexpr int INVALID_SIZE = -1; // makes the test fail even when the signal is not expected + switch (signal) { + case SignalName::CheckedChanged: + return checkedChangedSpy ? checkedChangedSpy->size() : INVALID_SIZE; + case SignalName::Triggered: + return triggeredSpy ? triggeredSpy->size() : INVALID_SIZE; + } + Q_UNREACHABLE_RETURN(INVALID_SIZE); + } + +private: + template<typename Item> + void initSignals(Item *item) + { + checkedChangedSpy = std::make_unique<QSignalSpy>(item, &Item::checkedChanged); + triggeredSpy = std::make_unique<QSignalSpy>(item, &Item::triggered); + } + +private: + QPointer<QObject> item; + std::unique_ptr<QSignalSpy> checkedChangedSpy = nullptr; + std::unique_ptr<QSignalSpy> triggeredSpy = nullptr; +}; + +} + +void tst_QQuickMenu::checkableMnemonics_data() +{ + if (QKeySequence::mnemonic("&A").isEmpty()) + QSKIP("Mnemonics are not enabled"); + + using namespace CheckableMnemonics; + + QTest::addColumn<bool>("checkable"); + QTest::addColumn<bool>("enabled"); + QTest::addColumn<bool>("isSubMenu"); + QTest::addColumn<MenuItemType>("itemType"); + QTest::addColumn<MnemonicKey>("mnemonicKey"); + QTest::addColumn<SignalNames>("expectedSignals"); + + QTest::addRow("checkable_enabled_action") + << true << true << false << MenuItemType::Action << MnemonicKey{Qt::Key_A, "A"} + << SignalNames{SignalName::Triggered, SignalName::CheckedChanged}; + QTest::addRow("checkable_disabled_action") + << true << false << false << MenuItemType::Action << MnemonicKey{Qt::Key_A, "A"} + << SignalNames{}; + QTest::addRow("uncheckable_enabled_action") + << false << true << false << MenuItemType::Action << MnemonicKey{Qt::Key_A, "A"} + << SignalNames{SignalName::Triggered}; + QTest::addRow("uncheckable_disabled_action") + << false << false << false << MenuItemType::Action << MnemonicKey{Qt::Key_A, "A"} + << SignalNames{}; + + QTest::addRow("checkable_enabled_menuItem") + << true << true << false << MenuItemType::MenuItem << MnemonicKey{Qt::Key_I, "I"} + << SignalNames{SignalName::Triggered, SignalName::CheckedChanged}; + QTest::addRow("checkable_disabled_menuItem") + << true << false << false << MenuItemType::MenuItem << MnemonicKey{Qt::Key_I, "I"} + << SignalNames{}; + QTest::addRow("uncheckable_enabled_menuItem") + << false << true << false << MenuItemType::MenuItem << MnemonicKey{Qt::Key_I, "I"} + << SignalNames{SignalName::Triggered}; + QTest::addRow("uncheckable_disabled_menuItem") + << false << false << false << MenuItemType::MenuItem << MnemonicKey{Qt::Key_I, "I"} + << SignalNames{}; + + QTest::addRow("checkable_enabled_subMenuItem") + << true << true << true << MenuItemType::MenuItem << MnemonicKey{Qt::Key_S, "S"} + << SignalNames{SignalName::Triggered, SignalName::CheckedChanged}; + QTest::addRow("checkable_disabled_subMenuItem") + << true << false << true << MenuItemType::MenuItem << MnemonicKey{Qt::Key_S, "S"} + << SignalNames{}; + QTest::addRow("uncheckable_enabled_subMenuItem") + << false << true << true << MenuItemType::MenuItem << MnemonicKey{Qt::Key_S, "S"} + << SignalNames{SignalName::Triggered}; + QTest::addRow("uncheckable_disabled_subMenuItem") + << false << false << true << MenuItemType::MenuItem << MnemonicKey{Qt::Key_S, "S"} + << SignalNames{}; + + QTest::addRow("checkable_enabled_subMenuAction") + << true << true << true << MenuItemType::Action << MnemonicKey{Qt::Key_U, "U"} + << SignalNames{SignalName::Triggered, SignalName::CheckedChanged}; + QTest::addRow("checkable_disabled_subMenuAction") + << true << false << true << MenuItemType::Action << MnemonicKey{Qt::Key_U, "U"} + << SignalNames{}; + QTest::addRow("uncheckable_enabled_subMenuAction") + << false << true << true << MenuItemType::Action << MnemonicKey{Qt::Key_U, "U"} + << SignalNames{SignalName::Triggered}; + QTest::addRow("uncheckable_disabled_subMenuAction") + << false << false << true << MenuItemType::Action << MnemonicKey{Qt::Key_U, "U"} + << SignalNames{}; +} + +// QTBUG-96630 +void tst_QQuickMenu::checkableMnemonics() +{ + using namespace CheckableMnemonics; + + QFETCH(bool, checkable); + QFETCH(bool, enabled); + QFETCH(bool, isSubMenu); + QFETCH(MenuItemType, itemType); + QFETCH(MnemonicKey, mnemonicKey); + QFETCH(SignalNames, expectedSignals); + + QQuickControlsApplicationHelper helper(this, QLatin1String("mnemonics.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + window->setProperty("checkable", checkable); + window->setProperty("enabled", enabled); + + QQuickMenu *menu = window->property("menu").value<QQuickMenu *>(); + QVERIFY(menu); + + auto clickKey = [window](const MnemonicKey &mnemonic) mutable { + QTest::simulateEvent(window, true, mnemonic.first, Qt::NoModifier, mnemonic.second, false); + QTest::simulateEvent(window, false, mnemonic.first, Qt::NoModifier, mnemonic.second, false); + }; + + constexpr auto EMPTY_ITEM_NAME = ""; + const char *itemName = EMPTY_ITEM_NAME; + switch (itemType) { + case MenuItemType::Action: + itemName = isSubMenu ? "subMenuAction" : "action"; + break; + case MenuItemType::MenuItem: + itemName = isSubMenu ? "subMenuItem" : "menuItem"; + break; + } + QCOMPARE_NE(itemName, EMPTY_ITEM_NAME); + + QObject *menuItem = window->property(itemName).value<QObject*>(); + QVERIFY(menuItem); + + menu->open(); + QTRY_VERIFY(menu->isOpened()); + + if (isSubMenu) { + QQuickMenu *subMenu = window->property("subMenu").value<QQuickMenu *>(); + QVERIFY(subMenu); + clickKey(MnemonicKey{Qt::Key_M, "M"}); // "Sub &Menu" + QTRY_VERIFY(subMenu->isOpened()); + } + + const ItemSignalSpy itemSignalSpy(itemType, menuItem); + QVERIFY(itemSignalSpy.isValid()); + + clickKey(mnemonicKey); + QCOMPARE(itemSignalSpy.signalSize(SignalName::CheckedChanged), + expectedSignals & SignalName::CheckedChanged ? 1 : 0); + QCOMPARE(itemSignalSpy.signalSize(SignalName::Triggered), + expectedSignals & SignalName::Triggered ? 1 : 0); +} +#endif + void tst_QQuickMenu::menuButton() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); @@ -648,8 +837,7 @@ void tst_QQuickMenu::addItem() void tst_QQuickMenu::menuSeparator() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, QLatin1String("menuSeparator.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1037,8 +1225,7 @@ void tst_QQuickMenu::actions() #if QT_CONFIG(shortcut) void tst_QQuickMenu::actionShortcuts() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, QLatin1String("actionShortcuts.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1332,8 +1519,7 @@ void tst_QQuickMenu::subMenuKeyboard_data() void tst_QQuickMenu::subMenuKeyboard() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QFETCH(bool, cascade); QFETCH(bool, mirrored); @@ -1461,8 +1647,7 @@ void tst_QQuickMenu::subMenuDisabledKeyboard_data() // QTBUG-69540 void tst_QQuickMenu::subMenuDisabledKeyboard() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QFETCH(bool, cascade); QFETCH(bool, mirrored); @@ -2050,8 +2235,7 @@ void tst_QQuickMenu::menuItemWidthAfterRetranslate() void tst_QQuickMenu::giveMenuItemFocusOnButtonPress() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, QLatin1String("giveMenuItemFocusOnButtonPress.qml")); QVERIFY2(helper.ready, helper.failureMessage()); diff --git a/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp b/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp index bbace286a8..b331fda1a3 100644 --- a/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp +++ b/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp @@ -38,8 +38,6 @@ private slots: void checkHighlightWhenMenuDismissed(); private: - static bool hasWindowActivation(); - QScopedPointer<QPointingDevice> touchScreen = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; @@ -54,11 +52,6 @@ tst_qquickmenubar::tst_qquickmenubar() qputenv("QML_NO_TOUCH_COMPRESSION", "1"); } -bool tst_qquickmenubar::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - void tst_qquickmenubar::delegate() { QQmlApplicationEngine engine(testFileUrl("empty.qml")); @@ -74,8 +67,7 @@ void tst_qquickmenubar::delegate() void tst_qquickmenubar::mouse() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) @@ -286,8 +278,7 @@ void tst_qquickmenubar::touch() void tst_qquickmenubar::keys() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQmlApplicationEngine engine(testFileUrl("menubar.qml")); @@ -478,8 +469,7 @@ void tst_qquickmenubar::keys() void tst_qquickmenubar::mnemonics() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION #if defined(Q_OS_MACOS) or defined(Q_OS_WEBOS) QSKIP("Mnemonics are not used on this platform"); diff --git a/tests/auto/quickcontrols/qquickpopup/BLACKLIST b/tests/auto/quickcontrols/qquickpopup/BLACKLIST index aa31440328..ed8288b5c9 100644 --- a/tests/auto/quickcontrols/qquickpopup/BLACKLIST +++ b/tests/auto/quickcontrols/qquickpopup/BLACKLIST @@ -12,3 +12,8 @@ opensuse-leap [cursorShape] opensuse-leap + +# QTBUG-125237 +[activeFocusItemAfterWindowInactive] +android + diff --git a/tests/auto/quickcontrols/qquickpopup/data/activeFocusAfterWindowInactive.qml b/tests/auto/quickcontrols/qquickpopup/data/activeFocusAfterWindowInactive.qml new file mode 100644 index 0000000000..1d4dc87e41 --- /dev/null +++ b/tests/auto/quickcontrols/qquickpopup/data/activeFocusAfterWindowInactive.qml @@ -0,0 +1,27 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias popup: popup + property alias button: button + + Button { + id: button + text: "button" + focus: true + } + + Popup { + id: popup + focus: true + width: 100 + height: 100 + anchors.centerIn: Overlay.overlay + } +} diff --git a/tests/auto/quickcontrols/qquickpopup/data/resetHoveredForItemsWithinOverlay.qml b/tests/auto/quickcontrols/qquickpopup/data/resetHoveredForItemsWithinOverlay.qml new file mode 100644 index 0000000000..a7e66718ee --- /dev/null +++ b/tests/auto/quickcontrols/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/quickcontrols/qquickpopup/tst_qquickpopup.cpp b/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp index 09c0b29d2f..ee535caa8e 100644 --- a/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp +++ b/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp @@ -65,6 +65,7 @@ private slots: void activeFocusAfterExit(); void activeFocusOnDelayedEnter(); void activeFocusDespiteLowerStackingOrder(); + void activeFocusItemAfterWindowInactive(); void hover_data(); void hover(); void wheel_data(); @@ -102,9 +103,9 @@ private slots: void focusMultiplePopup(); void contentChildrenChange(); void doubleClickInMouseArea(); + void resetHoveredStateForItemsWithinPopup(); private: - static bool hasWindowActivation(); QScopedPointer<QPointingDevice> touchScreen = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; @@ -128,11 +129,6 @@ void tst_QQuickPopup::visible_data() QTest::newRow("ApplicationWindow") << "applicationwindow.qml"; } -bool tst_QQuickPopup::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - void tst_QQuickPopup::visible() { QFETCH(QString, source); @@ -511,8 +507,7 @@ void tst_QQuickPopup::closePolicy_data() void tst_QQuickPopup::closePolicy() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QFETCH(QString, source); QFETCH(const QPointingDevice *, device); @@ -654,8 +649,7 @@ void tst_QQuickPopup::closePolicy_grabberInside() void tst_QQuickPopup::activeFocusOnClose1() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // Test that a popup that never sets focus: true (e.g. ToolTip) doesn't affect // the active focus item when it closes. @@ -700,8 +694,7 @@ void tst_QQuickPopup::activeFocusOnClose1() void tst_QQuickPopup::activeFocusOnClose2() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // Test that a popup that sets focus: true but relinquishes focus (e.g. by // calling forceActiveFocus() on another item) before it closes doesn't @@ -742,8 +735,7 @@ void tst_QQuickPopup::activeFocusOnClose2() void tst_QQuickPopup::activeFocusOnClose3() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // Test that a closing popup that had focus doesn't steal focus from // another popup that the focus was transferred to. @@ -778,8 +770,7 @@ void tst_QQuickPopup::activeFocusOnClose3() void tst_QQuickPopup::activeFocusOnClosingSeveralPopups() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // Test that active focus isn't lost when multiple popup closing simultaneously QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusOnClosingSeveralPopups.qml")); @@ -830,8 +821,7 @@ void tst_QQuickPopup::activeFocusOnClosingSeveralPopups() void tst_QQuickPopup::activeFocusAfterExit() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // Test that after closing a popup the highest one in z-order receives it instead. QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusAfterExit.qml")); @@ -882,8 +872,7 @@ void tst_QQuickPopup::activeFocusAfterExit() void tst_QQuickPopup::activeFocusOnDelayedEnter() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // Test that after opening two popups, first of which has an animation, does not cause // the first one to receive focus after the animation stops. @@ -911,8 +900,7 @@ void tst_QQuickPopup::activeFocusOnDelayedEnter() // key events due to having active focus. void tst_QQuickPopup::activeFocusDespiteLowerStackingOrder() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusOnClose3.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -951,6 +939,51 @@ void tst_QQuickPopup::activeFocusDespiteLowerStackingOrder() QVERIFY(!popup1->hasActiveFocus()); } +void tst_QQuickPopup::activeFocusItemAfterWindowInactive() +{ + SKIP_IF_NO_WINDOW_ACTIVATION + + QQuickControlsApplicationHelper helper(this, QStringLiteral("activeFocusAfterWindowInactive.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + QQuickPopup *popup = helper.appWindow->property("popup").value<QQuickPopup*>(); + QQuickButton *button = helper.appWindow->property("button").value<QQuickButton*>(); + QVERIFY(popup); + QVERIFY(button); + + popup->open(); + QTRY_VERIFY(popup->isVisible()); + QVERIFY(popup->hasActiveFocus()); + QVERIFY(!button->hasActiveFocus()); + + popup->close(); + QTRY_VERIFY(!popup->isVisible()); + QVERIFY(button->hasActiveFocus()); + QCOMPARE(window->activeFocusItem(), button); + + popup->open(); + QTRY_VERIFY(popup->isVisible()); + + QQuickWindow newWindow; + newWindow.setTitle("newFocusWindow"); + newWindow.show(); + newWindow.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&newWindow)); + + popup->close(); + QTRY_VERIFY(!popup->isVisible()); + QTRY_VERIFY(!popup->isOpened()); + QCOMPARE(QGuiApplication::focusWindow(), &newWindow); + + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + QCOMPARE(window->activeFocusItem(), button); +} + void tst_QQuickPopup::hover_data() { QTest::addColumn<QString>("source"); @@ -1389,8 +1422,7 @@ void tst_QQuickPopup::componentComplete() void tst_QQuickPopup::closeOnEscapeWithNestedPopups() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION // Tests the scenario in the Gallery example, where there are nested popups that should // close in the correct order when the Escape key is pressed. @@ -1459,8 +1491,7 @@ void tst_QQuickPopup::closeOnEscapeWithNestedPopups() void tst_QQuickPopup::closeOnEscapeWithVisiblePopup() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, QStringLiteral("closeOnEscapeWithVisiblePopup.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1587,8 +1618,7 @@ void tst_QQuickPopup::qquickview() // QTBUG-73447 void tst_QQuickPopup::disabledPalette() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, "disabledPalette.qml"); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1627,8 +1657,7 @@ void tst_QQuickPopup::disabledPalette() void tst_QQuickPopup::disabledParentPalette() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, "disabledPalette.qml"); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1736,8 +1765,7 @@ void tst_QQuickPopup::setOverlayParentToNull() void tst_QQuickPopup::tabFence() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) QSKIP("This platform only allows tab focus for text controls"); @@ -1849,8 +1877,7 @@ void tst_QQuickPopup::centerInOverlayWithinStackViewItem() void tst_QQuickPopup::destroyDuringExitTransition() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION QQuickControlsApplicationHelper helper(this, "destroyDuringExitTransition.qml"); QVERIFY2(helper.ready, helper.failureMessage()); @@ -2287,6 +2314,40 @@ void tst_QQuickPopup::doubleClickInMouseArea() QCOMPARE(longPressSpy.count(), 0); } +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/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp b/tests/auto/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp index 3788799f8a..1e3e0e46ee 100644 --- a/tests/auto/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp +++ b/tests/auto/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp @@ -12,6 +12,7 @@ #include <QtQuick/qquickview.h> #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/viewtestutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtQuickTemplates2/private/qquicktextarea_p.h> #include <QtQuickControlsTestUtils/private/qtest_quickcontrols_p.h> @@ -29,7 +30,6 @@ private slots: void touchscreenSetsFocusAndMovesCursor(); private: - static bool hasWindowActivation(); QScopedPointer<QPointingDevice> touchDevice = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; @@ -102,8 +102,7 @@ void tst_QQuickTextArea::touchscreenDoesNotSelect() void tst_QQuickTextArea::touchscreenSetsFocusAndMovesCursor() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION qunsetenv("QT_QUICK_CONTROLS_TEXT_SELECTION_BEHAVIOR"); QQuickView window; @@ -158,11 +157,6 @@ void tst_QQuickTextArea::touchscreenSetsFocusAndMovesCursor() QCOMPARE_GT(top->selectedText().size(), 0); } -bool tst_QQuickTextArea::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - QTEST_QUICKCONTROLS_MAIN(tst_QQuickTextArea) #include "tst_qquicktextarea.moc" diff --git a/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp b/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp index 27334f06bb..a42ea91b6d 100644 --- a/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp @@ -304,6 +304,7 @@ bool FileDialogTestHelper::openDialog() void tst_QQuickFileDialogImpl::defaults() { + QTest::failOnWarning(QRegularExpression(".*")); FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); QVERIFY(dialogHelper.waitForWindowActive()); diff --git a/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp b/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp index 6fd64acc1b..b2edbb1cd8 100644 --- a/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickmessagedialogimpl/tst_qquickmessagedialogimpl.cpp @@ -277,6 +277,10 @@ void tst_QQuickMessageDialogImpl::emitCorrectAcceptedAndRejectedSignals() QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); QVERIFY(dialogHelper.waitForWindowActive()); QVERIFY(dialogHelper.openDialog()); + + if (QQuickTest::qIsPolishScheduled(dialogHelper.window())) + QVERIFY(QQuickTest::qWaitForPolish(dialogHelper.window())); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); auto *buttonBox = dialogHelper.quickDialog->findChild<QQuickDialogButtonBox *>("buttonBox"); diff --git a/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp b/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp index 175bec4cb8..73411e5b8a 100644 --- a/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp +++ b/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp @@ -14,6 +14,7 @@ #include <QtQuick/private/qquicktaphandler_p.h> #include <QtQuickTemplates2/private/qquickbutton_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtGui/QWindow> #include <QtGui/QScreen> #include <QtGui/QImage> @@ -142,6 +143,7 @@ private slots: void focusPreserved(); void accessibilityHandlesViewChange(); void cleanupRhi(); + void dontRecreateRootElementOnWindowChange(); private: QPointingDevice *device = QTest::createTouchDevice(); @@ -991,8 +993,7 @@ void tst_qquickwidget::focusOnClickInProxyWidget() void tst_qquickwidget::focusPreserved() { - if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) - QSKIP("Window Activation is not supported."); + SKIP_IF_NO_WINDOW_ACTIVATION if (QGuiApplication::platformName() == "android") QSKIP("Test doesn't exit cleanly on Android and generates many warnings - QTBUG-112696"); @@ -1096,6 +1097,21 @@ void tst_qquickwidget::cleanupRhi() topLevel.create(); } +void tst_qquickwidget::dontRecreateRootElementOnWindowChange() +{ + auto *quickWidget = new QQuickWidget(); + quickWidget->setSource(testFileUrl("rectangle.qml")); + QObject *item = quickWidget->rootObject(); + + bool wasDestroyed = false; + QObject::connect(item, &QObject::destroyed, this, [&] { wasDestroyed = true; }); + + QEvent event(QEvent::WindowChangeInternal); + QCoreApplication::sendEvent(quickWidget, &event); + + QVERIFY(!wasDestroyed); +} + QTEST_MAIN(tst_qquickwidget) #include "tst_qquickwidget.moc" diff --git a/tests/manual/quickcontrols/screenshots/CMakeLists.txt b/tests/manual/quickcontrols/screenshots/CMakeLists.txt index 27a8ac882c..32d465b9d4 100644 --- a/tests/manual/quickcontrols/screenshots/CMakeLists.txt +++ b/tests/manual/quickcontrols/screenshots/CMakeLists.txt @@ -18,7 +18,7 @@ qt_internal_add_manual_test(screenshots SOURCES screenshots.cpp DEFINES - SNIPPETS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../../../src/imports/controls/doc/snippets" + SNIPPETS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/quickcontrols/doc/snippets" LIBRARIES Qt::Gui Qt::Quick diff --git a/tests/manual/quickcontrols/screenshots/screenshots.qml b/tests/manual/quickcontrols/screenshots/screenshots.qml index cd8d52d658..e3bcfa860d 100644 --- a/tests/manual/quickcontrols/screenshots/screenshots.qml +++ b/tests/manual/quickcontrols/screenshots/screenshots.qml @@ -47,7 +47,7 @@ ApplicationWindow { showDirs: false } delegate: ItemDelegate { - width: parent.width + width: snippetsListView.width text: fileName focusPolicy: Qt.NoFocus diff --git a/tests/manual/quickcontrols/testbench/controls/CheckDelegate.qml b/tests/manual/quickcontrols/testbench/controls/CheckDelegate.qml index d64339501f..e8e1194a01 100644 --- a/tests/manual/quickcontrols/testbench/controls/CheckDelegate.qml +++ b/tests/manual/quickcontrols/testbench/controls/CheckDelegate.qml @@ -32,7 +32,7 @@ QtObject { clip: true model: 20 delegate: CheckDelegate { - width: parent.width + width: ListView.view.width text: "CheckDelegate" focusPolicy: Qt.StrongFocus } diff --git a/tests/manual/quickcontrols/testbench/controls/ItemDelegate.qml b/tests/manual/quickcontrols/testbench/controls/ItemDelegate.qml index e8171c36bc..d6ed7c54d2 100644 --- a/tests/manual/quickcontrols/testbench/controls/ItemDelegate.qml +++ b/tests/manual/quickcontrols/testbench/controls/ItemDelegate.qml @@ -29,7 +29,7 @@ QtObject { clip: true model: 20 delegate: ItemDelegate { - width: parent.width + width: ListView.view.width text: "ItemDelegate" focusPolicy: Qt.StrongFocus } diff --git a/tests/manual/quickcontrols/testbench/controls/Menu.qml b/tests/manual/quickcontrols/testbench/controls/Menu.qml index aeadee4f86..a420163042 100644 --- a/tests/manual/quickcontrols/testbench/controls/Menu.qml +++ b/tests/manual/quickcontrols/testbench/controls/Menu.qml @@ -42,15 +42,18 @@ QtObject { MenuItem { text: "Checked" + checkable: true checked: true } MenuItem { text: "Checked + Pressed" + checkable: true checked: true down: true } MenuItem { text: "Checked + Disabled" + checkable: true checked: true enabled: false } diff --git a/tests/manual/quickcontrols/testbench/controls/RadioDelegate.qml b/tests/manual/quickcontrols/testbench/controls/RadioDelegate.qml index 4dab387fcf..364770c187 100644 --- a/tests/manual/quickcontrols/testbench/controls/RadioDelegate.qml +++ b/tests/manual/quickcontrols/testbench/controls/RadioDelegate.qml @@ -31,7 +31,7 @@ QtObject { clip: true model: 20 delegate: RadioDelegate { - width: parent.width + width: ListView.view.width text: "RadioDelegate" focusPolicy: Qt.StrongFocus } diff --git a/tests/manual/quickcontrols/testbench/controls/SwipeDelegate.qml b/tests/manual/quickcontrols/testbench/controls/SwipeDelegate.qml index e04f0bb630..1c4c22c608 100644 --- a/tests/manual/quickcontrols/testbench/controls/SwipeDelegate.qml +++ b/tests/manual/quickcontrols/testbench/controls/SwipeDelegate.qml @@ -48,7 +48,7 @@ QtObject { clip: true model: 20 delegate: SwipeDelegate { - width: parent.width + width: ListView.view.width text: "SwipeDelegate" focusPolicy: Qt.StrongFocus diff --git a/tests/manual/quickcontrols/testbench/controls/SwitchDelegate.qml b/tests/manual/quickcontrols/testbench/controls/SwitchDelegate.qml index 9508b2ce80..2f6b26c47a 100644 --- a/tests/manual/quickcontrols/testbench/controls/SwitchDelegate.qml +++ b/tests/manual/quickcontrols/testbench/controls/SwitchDelegate.qml @@ -31,7 +31,7 @@ QtObject { clip: true model: 20 delegate: SwitchDelegate { - width: parent.width + width: ListView.view.width text: "SwitchDelegate" focusPolicy: Qt.StrongFocus } diff --git a/tests/manual/quickcontrols/testbench/testbench.qml b/tests/manual/quickcontrols/testbench/testbench.qml index 9df1ed873f..1a95de71be 100644 --- a/tests/manual/quickcontrols/testbench/testbench.qml +++ b/tests/manual/quickcontrols/testbench/testbench.qml @@ -139,7 +139,11 @@ Ui.ApplicationWindow { text: "\ue801" font.family: "fontello" visible: searchTextField.length > 0 - onClicked: searchTextField.clear() + onClicked: { + searchTextField.clear() + // textEdited is not emitted for clear(), so we have to set this ourselves. + settings.lastSearchText = "" + } Layout.leftMargin: -5 } diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp index f451596079..54fd1ba0f0 100644 --- a/tools/qmlcachegen/qmlcachegen.cpp +++ b/tools/qmlcachegen/qmlcachegen.cpp @@ -15,11 +15,12 @@ #include <QLoggingCategory> #include <private/qqmlirbuilder_p.h> -#include <private/qqmljsparser_p.h> +#include <private/qqmljscompiler_p.h> #include <private/qqmljslexer_p.h> -#include <private/qqmljsresourcefilemapper_p.h> #include <private/qqmljsloadergenerator_p.h> -#include <private/qqmljscompiler_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qqmljsresourcefilemapper_p.h> +#include <private/qqmljsutils_p.h> #include <private/qresourcerelocater_p.h> #include <algorithm> @@ -266,7 +267,8 @@ int main(int argc, char **argv) logger.setSilent(true); QQmlJSAotCompiler cppCodeGen( - &importer, u':' + inputResourcePath, parser.values(importsOption), &logger); + &importer, u':' + inputResourcePath, + QQmlJSUtils::cleanPaths(parser.values(importsOption)), &logger); if (!qCompileQmlFile(inputFile, saveFunction, &cppCodeGen, &error, /* storeSourceLocation */ true)) { diff --git a/tools/qmllint/main.cpp b/tools/qmllint/main.cpp index c11264452e..8bb748374b 100644 --- a/tools/qmllint/main.cpp +++ b/tools/qmllint/main.cpp @@ -3,9 +3,10 @@ #include "../shared/qqmltoolingsettings.h" -#include <QtQmlCompiler/private/qqmljsresourcefilemapper_p.h> #include <QtQmlCompiler/private/qqmljscompiler_p.h> #include <QtQmlCompiler/private/qqmljslinter_p.h> +#include <QtQmlCompiler/private/qqmljsresourcefilemapper_p.h> +#include <QtQmlCompiler/private/qqmljsutils_p.h> #include <QtCore/qdebug.h> #include <QtCore/qfile.h> @@ -269,7 +270,7 @@ All warnings can be set to three levels: QStringList defaultQmldirFiles; if (parser.isSet(qmldirFilesOption)) { - defaultQmldirFiles = parser.values(qmldirFilesOption); + defaultQmldirFiles = QQmlJSUtils::cleanPaths(parser.values(qmldirFilesOption)); } else if (!parser.isSet(qmlImportNoDefault)){ // If nothing given explicitly, use the qmldir file from the current directory. QFileInfo qmldirFile(QStringLiteral("qmldir")); diff --git a/tools/qmlls/qqmlcodemodel.cpp b/tools/qmlls/qqmlcodemodel.cpp index 9c3521aa57..2dd58ffe45 100644 --- a/tools/qmlls/qqmlcodemodel.cpp +++ b/tools/qmlls/qqmlcodemodel.cpp @@ -571,7 +571,7 @@ QStringList QQmlCodeModel::buildPathsForFileUrl(const QByteArray &url) QString dirName = d.dirName(); QDateTime lastModified; while (d.cdUp() && --iDir > 0) { - for (const QFileInfo &fInfo : d.entryInfoList(QDir::Dirs)) { + for (const QFileInfo &fInfo : d.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { if (fInfo.completeBaseName() == u"build" || fInfo.completeBaseName().startsWith(u"build-%1"_s.arg(dirName))) { if (iDir > 1) @@ -594,7 +594,7 @@ QStringList QQmlCodeModel::buildPathsForFileUrl(const QByteArray &url) res += bPath; if (QFile::exists(bPath + u"/_deps") && bPath.split(u"/_deps/"_s).size() < maxDeps) { QDir d(bPath + u"/_deps"); - for (const QFileInfo &fInfo : d.entryInfoList(QDir::Dirs)) + for (const QFileInfo &fInfo : d.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) buildPaths.append(fInfo.absoluteFilePath()); } } diff --git a/tools/qmltc/main.cpp b/tools/qmltc/main.cpp index 7138343038..d4fca9be62 100644 --- a/tools/qmltc/main.cpp +++ b/tools/qmltc/main.cpp @@ -9,6 +9,7 @@ #include <private/qqmljscompiler_p.h> #include <private/qqmljsresourcefilemapper_p.h> +#include <private/qqmljsutils_p.h> #include <QtCore/qcoreapplication.h> #include <QtCore/qurl.h> @@ -161,7 +162,7 @@ int main(int argc, char **argv) if (!parser.isSet(bareOption)) importPaths.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath)); - QStringList qmldirFiles = parser.values(qmldirOption); + QStringList qmldirFiles = QQmlJSUtils::cleanPaths(parser.values(qmldirOption)); QString outputCppFile; if (!parser.isSet(outputCppOption)) { |