diff options
Diffstat (limited to 'src')
216 files changed, 1970 insertions, 682 deletions
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 e2dfb6ca77..f6619d8d00 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -1467,6 +1467,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 28a2523561..94b4a023c6 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) { |
