diff options
| author | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2026-05-25 13:55:19 +0300 |
|---|---|---|
| committer | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2026-05-25 13:55:19 +0300 |
| commit | 3fd4f41cd306ee97aaf47400fba75cc874048215 (patch) | |
| tree | 6c60d274e67e90b3a44ac1c8c476a91274beb962 | |
| parent | 22032227d16c39211e2ebceef97d21f4d89c7c87 (diff) | |
| parent | 94cf5162f8817fb7883ad56ac704e752e5dab5a4 (diff) | |
Merge tag 'v6.5.9-lts' into tqtc/lts-6.5-opensourcev6.5.9-lts-lgpl
Qt 6.5.9-lts release
Conflicts solved:
dependencies.yaml
Change-Id: I6f892bad653449a1dd0e0c6e66cc74eee8657cda
171 files changed, 2302 insertions, 612 deletions
diff --git a/.cmake.conf b/.cmake.conf index 791e84b56a..856ec9c329 100644 --- a/.cmake.conf +++ b/.cmake.conf @@ -1,4 +1,4 @@ -set(QT_REPO_MODULE_VERSION "6.5.8") +set(QT_REPO_MODULE_VERSION "6.5.9") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_LEAN_HEADERS=1") diff --git a/cmake/QtDeclarativeSetup.cmake b/cmake/QtDeclarativeSetup.cmake index 165a71e09a..b174756343 100644 --- a/cmake/QtDeclarativeSetup.cmake +++ b/cmake/QtDeclarativeSetup.cmake @@ -3,9 +3,9 @@ # Create a header containing a hash that describes this library. For a # released version of Qt, we'll use the .tag file that is updated by git -# archive with the tree hash. For unreleased versions, we'll ask git -# rev-parse. If none of this works, we use CMake to hash all the files -# in the src/qml/ directory. +# archive with the tree hash. For unreleased versions, we'll do +# "git show -s --format=format:%T" to get the tree hash. If none of this +# works, we use CMake to hash all the files in the src/qml/ directory. # Skip recreation of the hash when doing a developer build. function(qt_declarative_write_tag_header target_name) set(out_file "${CMAKE_CURRENT_BINARY_DIR}/qml_compile_hash_p.h") @@ -27,7 +27,7 @@ function(qt_declarative_write_tag_header target_name) set(QML_COMPILE_HASH "${tag_contents}") elseif(git_path AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../.git") execute_process( - COMMAND ${git_path} rev-parse HEAD + COMMAND ${git_path} show -s --format=format:%T HEAD OUTPUT_VARIABLE QML_COMPILE_HASH OUTPUT_STRIP_TRAILING_WHITESPACE WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/dependencies.yaml b/dependencies.yaml index 7a078aea27..fcbad32e87 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -1,16 +1,16 @@ dependencies: ../tqtc-qtbase: - ref: 0fe1875684a77d985277eaf58741e64ef6514bca + ref: ee5586e7ad1fa061b9cdb529dba9cd131678f4b7 required: true ../tqtc-qtimageformats: - ref: a293ff4fff3101beacf74ea5ad98cde575d3cb72 + ref: dc8311f1d12bd0e0a560493f0d3e797a4ff376fc required: false ../tqtc-qtlanguageserver: - ref: 82ebb8fba7417cea5cada61c9200f40fe3873c7e + ref: 693d3ed6424afd701ec9c004b00fa230a3d79366 required: false ../tqtc-qtshadertools: - ref: 1045af7131573c7db4580df0d28663269b535e69 + ref: 1f3cc2e23adf1943131465aa0ada1feb12563fb9 required: false ../tqtc-qtsvg: - ref: 93dc5a03c1e296ac8618471bbdfedf8303442b63 + ref: f69e56c3fffde393ac5bf889ebd933d41e991876 required: false diff --git a/src/plugins/qmllint/quick/quicklintplugin.cpp b/src/plugins/qmllint/quick/quicklintplugin.cpp index 010ace51e5..c1775ce8b8 100644 --- a/src/plugins/qmllint/quick/quicklintplugin.cpp +++ b/src/plugins/qmllint/quick/quicklintplugin.cpp @@ -52,6 +52,14 @@ void ForbiddenChildrenPropertyValidatorPass::run(const QQmlSA::Element &element) { for (const auto elementPair : m_types.asKeyValueRange()) { const QQmlSA::Element &type = elementPair.first; + const QQmlSA::Element parentScope = element->parentScope(); + + // If the parent's default property is not what we think it is, then we can't say whether + // the element in question is actually a visual child of the (document) parent scope. + const auto defaultProperty = parentScope->property(parentScope->defaultPropertyName()); + if (defaultProperty != type->property(type->defaultPropertyName())) + continue; + if (!element->parentScope()->inherits(type)) continue; diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp index c4b42ed1be..e4020f0b95 100644 --- a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp @@ -19,7 +19,7 @@ void QQmlPreviewBlacklist::whitelist(const QString &path) bool QQmlPreviewBlacklist::isBlacklisted(const QString &path) const { - return path.isEmpty() ? true : m_root.containedPrefixLeaf(path, 0) > 0; + return path.isEmpty() ? true : m_root.findPrefix(path, 0) == Node::MatchedLeaf; } void QQmlPreviewBlacklist::clear() @@ -138,33 +138,49 @@ void QQmlPreviewBlacklist::Node::remove(const QString &path, int offset) if (offset == path.size()) return; - auto it = m_next.find(path.at(offset)); - if (it != m_next.end()) - (*it)->remove(path, ++offset); + Node *&node = m_next[path.at(offset++)]; + if (node) { + node->remove(path, offset); + } else { + QString inserted; + inserted.resize(path.size() - offset); + std::copy(path.begin() + offset, path.end(), inserted.begin()); + node = new Node(inserted, {}, false); + } } -int QQmlPreviewBlacklist::Node::containedPrefixLeaf(const QString &path, int offset) const +QQmlPreviewBlacklist::Node::PrefixResult QQmlPreviewBlacklist::Node::findPrefix( + const QString &path, int offset) const { - if (offset == path.size()) - return (m_mine.isEmpty() && m_isLeaf) ? offset : -1; + if (offset == path.size()) { + if (!m_mine.isEmpty()) + return Unmatched; + return m_isLeaf ? MatchedLeaf : MatchedBranch; + } for (auto it = m_mine.begin(), end = m_mine.end(); it != end; ++it) { if (path.at(offset) != *it) - return -1; + return Unmatched; - if (++offset == path.size()) - return (++it == end && m_isLeaf) ? offset : -1; + if (++offset == path.size()) { + if (++it != end) + return Unmatched; + return m_isLeaf ? MatchedLeaf : MatchedBranch; + } } const QChar c = path.at(offset); - if (m_isLeaf && c == '/') - return offset; + const auto it = m_next.find(c); + if (it != m_next.end()) { + const PrefixResult result = (*it)->findPrefix(path, offset + 1); + if (result != Unmatched) + return result; + } - auto it = m_next.find(c); - if (it == m_next.end()) - return -1; + if (c == '/') + return m_isLeaf ? MatchedLeaf : MatchedBranch; - return (*it)->containedPrefixLeaf(path, ++offset); + return Unmatched; } QQmlPreviewBlacklist::Node::Node(const QString &mine, diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h index 715acf4ae6..4aca70b612 100644 --- a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h @@ -33,6 +33,12 @@ public: private: class Node { public: + enum PrefixResult { + MatchedLeaf, + MatchedBranch, + Unmatched, + }; + Node(); Node(const Node &other); Node(Node &&other) noexcept; @@ -45,7 +51,7 @@ private: void split(QString::iterator it, QString::iterator end); void insert(const QString &path, int offset); void remove(const QString &path, int offset); - int containedPrefixLeaf(const QString &path, int offset) const; + PrefixResult findPrefix(const QString &path, int offset) const; private: Node(const QString &mine, const QHash<QChar, Node *> &next = QHash<QChar, Node *>(), diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake index 49829d8e61..91229d8d1f 100644 --- a/src/qml/Qt6QmlMacros.cmake +++ b/src/qml/Qt6QmlMacros.cmake @@ -2918,14 +2918,14 @@ but this file does not exist. Possible reasons include: endif() _qt_internal_get_tool_wrapper_script_path(tool_wrapper) - set(import_scanner_args ${tool_wrapper} ${tool_path} ${cmd_args}) + set(import_scanner_args "${tool_path}" ${cmd_args}) # Run qmlimportscanner to generate the cmake file that records the import entries if(scan_at_build_time) add_custom_command( OUTPUT "${imports_file}" COMMENT "Running qmlimportscanner for ${target}" - COMMAND ${import_scanner_args} + COMMAND ${tool_wrapper} ${import_scanner_args} WORKING_DIRECTORY ${target_source_dir} DEPENDS ${tool_path} @@ -2940,11 +2940,14 @@ but this file does not exist. Possible reasons include: message(VERBOSE "Running qmlimportscanner for ${target}.") list(JOIN import_scanner_args " " import_scanner_args_string) message(DEBUG "qmlimportscanner command: ${import_scanner_args_string}") - execute_process( + + # Pack arguments to avoid escaping issues. + set(import_scanner_execute_process_args COMMAND ${import_scanner_args} - WORKING_DIRECTORY ${target_source_dir} + WORKING_DIRECTORY "${target_source_dir}" RESULT_VARIABLE result ) + _qt_internal_execute_proccess_in_qt_env(import_scanner_execute_process_args) if(result) message(FATAL_ERROR "Failed to scan target ${target} for QML imports: ${result}" diff --git a/src/qml/common/qqmltranslation.cpp b/src/qml/common/qqmltranslation.cpp index 7120071b1a..639cf9b559 100644 --- a/src/qml/common/qqmltranslation.cpp +++ b/src/qml/common/qqmltranslation.cpp @@ -3,6 +3,8 @@ #include "private/qqmltranslation_p.h" +QT_BEGIN_NAMESPACE + QQmlTranslation::QQmlTranslation(const Data &d) : data(d) { } QQmlTranslation::QQmlTranslation() : data(nullptr) { } @@ -123,3 +125,5 @@ QString QQmlTranslation::QsTrIdData::idForQmlDebug() const { return QString::fromUtf8(id); } + +QT_END_NAMESPACE diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h index 8dffb52960..c2f481f297 100644 --- a/src/qml/common/qv4compileddata_p.h +++ b/src/qml/common/qv4compileddata_p.h @@ -1554,7 +1554,7 @@ public: if (index < data->stringTableSize) return data->stringAtInternal(index); - const uint dynamicIndex = index - data->stringTableSize; + const qsizetype dynamicIndex = index - data->stringTableSize; Q_ASSERT(dynamicIndex < dynamicStrings.size()); return dynamicStrings.at(dynamicIndex); } diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index bf36c790f1..655f3835cb 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -758,6 +758,15 @@ void tryGeneratingTranslationBindingBase(QStringView base, QQmlJS::AST::Argument } args = args->next; + // QT_TR_NOOP can have a disambiguation string, QT_TRID_NOOP can't + if (args && base == QLatin1String("QT_TR_NOOP")) { + // we have a disambiguation string; we don't need to do anything with it + if (QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) + args = args->next; + else // second argument is not a string, stop + return; + } + if (args) return; // too many arguments, stop @@ -780,6 +789,14 @@ void tryGeneratingTranslationBindingBase(QStringView base, QQmlJS::AST::Argument } args = args->next; + if (args) { + // we have a disambiguation string; we don't need to do anything with it + if (QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) + args = args->next; + else // third argument is not a string, stop + return; + } + if (args) return; // too many arguments, stop diff --git a/src/qml/doc/src/cmake/policy/qtp0001.qdoc b/src/qml/doc/src/cmake/policy/qtp0001.qdoc index 93e829b99e..64d7b8abc1 100644 --- a/src/qml/doc/src/cmake/policy/qtp0001.qdoc +++ b/src/qml/doc/src/cmake/policy/qtp0001.qdoc @@ -19,10 +19,10 @@ a default \l {QML Import Path}{import path}, and its types can be found without manual calls to \l QQmlEngine::addImportPath. The \c OLD behavior of this policy is that, the \c RESOURCE_PREFIX argument for -\c{qt_add_qml_module()} defaults to \c{":/"}. +\c{qt_add_qml_module()} defaults to \c{"/"}. The \c NEW behavior of this policy is that the \c RESOURCE_PREFIX argument -for \c{qt_add_qml_module()} defaults to \c{":/qt/qml/"}. The new behavior +for \c{qt_add_qml_module()} defaults to \c{"/qt/qml/"}. The new behavior ensures that modules are put into the \l{QML Import Path} and can be found without further setup. diff --git a/src/qml/doc/src/cmake/qt_add_qml_module.qdoc b/src/qml/doc/src/cmake/qt_add_qml_module.qdoc index 353505c78f..7452af10dc 100644 --- a/src/qml/doc/src/cmake/qt_add_qml_module.qdoc +++ b/src/qml/doc/src/cmake/qt_add_qml_module.qdoc @@ -447,6 +447,10 @@ qt_add_qml_module(someTarget ) \endcode +If \l QTP0001 is enabled (e.g. via +\c {qt_standard_project_setup(REQUIRES 6.5)}), the default value is +\c "/qt/qml/", otherwise it is \c {"/"}. + \target NO_RESOURCE_TARGET_PATH When various files are added to the compiled-in resources, they are placed under a path formed by concatenating the \c RESOURCE_PREFIX and the target path. diff --git a/src/qml/doc/src/cppintegration/data.qdoc b/src/qml/doc/src/cppintegration/data.qdoc index e2d69eb414..cba6a122b9 100644 --- a/src/qml/doc/src/cppintegration/data.qdoc +++ b/src/qml/doc/src/cppintegration/data.qdoc @@ -91,9 +91,6 @@ when passed from C++ to QML and vice-versa: \row \li QVector2D, QVector3D, QVector4D \li \l vector2d, \l vector3d, \l vector4d - \row - \li Enums declared with Q_ENUM() - \li \l enumeration \endtable \note Classes provided by the \l {Qt GUI} module, such as QColor, QFont, @@ -142,16 +139,23 @@ additional features. See the \l {qtqml-javascript-hostenvironment.html} {JavaScript Host Environment} for further details.) -\section2 QVariantList and QVariantMap to JavaScript Array and Object +\section2 QVariantList and QVariantMap to JavaScript Array-like and Object The QML engine provides automatic type conversion between QVariantList and -JavaScript arrays, and between QVariantMap and JavaScript objects. +JavaScript array-likes, and between QVariantMap and JavaScript objects. + +An \e{array-like}, in ECMAScript terms, is an object used like an array. The +array-likes produced by conversion from QVariantList (and other C++ sequential +containers) are not only that but also offer the common array methods +and automatically synchronize the \e length property. They can be used just +like JavaScript Arrays for any practical purposes. The \e{Array.isArray()} +method still returns false for them, though. For example, the function defined in QML below expects two arguments, an array and an object, and prints their contents using the standard JavaScript syntax for array and object item access. The C++ code below calls this function, passing a QVariantList and a QVariantMap, which are automatically -converted to JavaScript array and object values, repectively: +converted to JavaScript array-like and object values, respectively: \table \row @@ -177,23 +181,24 @@ type or method parameter, the value can be created as a JavaScript array or object in QML, and is automatically converted to a QVariantList or QVariantMap when it is passed to C++. -Mind that QVariantList and QVariantMap properties of C++ types are stored as -values and cannot be changed in place by QML code. You can only replace the -whole map or list, but not manipulate its contents. The following code does -not work if the property \c l is a QVariantList: +Since Qt 6.5, QVariantList properties of C++ types can be changed in place by +QML code. Mind, however, that this does not hold for QVariantMap properties of +C++ types. Those are stored as values and cannot be changed in place by QML +code. You can only replace the whole map, but not manipulate its contents. The +following code does not work if the property \c m is a QVariantMap: \code -MyListExposingItem { - l: [1, 2, 3] - Component.onCompleted: l[0] = 10 +MyMapExposingItem { + m: ({ one: 1 }) + Component.onCompleted: m.ten = 10 } \endcode The following code does work: \code -MyListExposingItem { - l: [1, 2, 3] - Component.onCompleted: l = [10, 2, 3] +MyMapExposingItem { + m: ({ one: 1 }) + Component.onCompleted: m = { one: 1, ten: 10 } } \endcode diff --git a/src/qml/doc/src/cppintegration/definetypes.qdoc b/src/qml/doc/src/cppintegration/definetypes.qdoc index 1e31dc5e90..ef0f831299 100644 --- a/src/qml/doc/src/cppintegration/definetypes.qdoc +++ b/src/qml/doc/src/cppintegration/definetypes.qdoc @@ -394,17 +394,17 @@ types without breaking existing programs. The REVISION tag is used to mark the \c root property as added in revision 1 of the type. Methods such as Q_INVOKABLE's, signals and slots can also be -tagged for a revision using the \c Q_REVISION(x) macro: +tagged for a revision using the \l Q_REVISION macro: \code class CppType : public BaseType { Q_OBJECT - Q_PROPERTY(int root READ root WRITE setRoot NOTIFY rootChanged REVISION 1) + Q_PROPERTY(int root READ root WRITE setRoot NOTIFY rootChanged REVISION(1, 0)) QML_ELEMENT signals: - Q_REVISION(1) void rootChanged(); + Q_REVISION(1, 0) void rootChanged(); }; \endcode diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc index fa317bdbd9..5d9c7a0b87 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -404,10 +404,10 @@ */ /*! - \macro QML_EXTENDED_NAMESPACE(EXTENDED_NAMESPACE) + \macro QML_EXTENDED_NAMESPACE(EXTENSION_NAMESPACE) \relates QQmlEngine - Declares that the enclosing type uses \a EXTENDED_NAMESPACE as an extension to + Declares that the enclosing \b type uses \a EXTENSION_NAMESPACE as an extension to provide further enumerations in QML. This takes effect if the type is exposed to QML using a \l QML_ELEMENT or \l QML_NAMED_ELEMENT() macro. The enumerations need to be exposed to the metaobject system for this to work. @@ -435,31 +435,85 @@ } \endqml - \note EXTENDED_NAMESPACE can also be a QObject or QGadget; in that case - and in contrast to + \note \a EXTENSION_NAMESPACE can also be a QObject or QGadget; in that case - and in contrast to QML_EXTENDED, which also exposes methods and properties - only its enumerations are exposed. - \note \a EXTENDED_NAMESPACE must have a metaobject; i.e. it must either be a namespace which + \note \a EXTENSION_NAMESPACE must have a metaobject; i.e. it must either be a namespace which contains the Q_NAMESPACE macro or a QObject/QGadget. \include {qualified-class-name.qdocinc} {class name must be qualified} - \sa QML_ELEMENT, QML_NAMED_ELEMENT(), QML_EXTENDED(), + \sa QML_NAMESPACE_EXTENDED(), QML_ELEMENT, QML_NAMED_ELEMENT(), QML_EXTENDED(), {Registering Extension Objects}, Q_ENUM, Q_ENUM_NS */ /*! + \macro QML_NAMESPACE_EXTENDED(EXTENSION_NAMESPACE) + \relates QQmlEngine + + Behaves the same way as \l QML_EXTENDED_NAMESPACE with the distinction that what is being + extended is a namespace and not a type. + + Declares that the enclosing \b namespace uses \a EXTENSION_NAMESPACE as an extension to + provide further enumerations in QML. This takes effect if the extended namespace is exposed to + QML using a \l QML_ELEMENT or \l QML_NAMED_ELEMENT() macro. The enumerations need to be exposed + to the metaobject system for this to work. + + For example, in the following C++ code, + \code + namespace NS2 { + Q_NAMESPACE + + enum class E2 { D = 3, E, F }; + Q_ENUM_NS(E2) + } + + namespace NS1 { + Q_NAMESPACE + QML_ELEMENT + + enum class E1 { A, B, C }; + Q_ENUM_NS(E1) + + // Extends NS1 with NS2 + QML_NAMESPACE_EXTENDED(NS2) + } + \endcode + + the namespace \c NS1 is extended with \c NS2 and the \c E2 enum becomes available within \c NS1 + from QML. + \qml + Item { + Component.onCompleted: console.log(NS1.E1.A, NS1.E2.D) + } + \endqml + + \note \a EXTENSION_NAMESPACE can also be a QObject or QGadget; in that case - and in contrast to + QML_EXTENDED, which also exposes methods and properties - only its enumerations + are exposed. + + \note \a EXTENSION_NAMESPACE must have a metaobject; i.e. it must either be a namespace which + contains the Q_NAMESPACE macro or a QObject/QGadget. + + \include {qualified-class-name.qdocinc} {class name must be qualified} + + \sa QML_EXTENDED_NAMESPACE(), QML_ELEMENT, QML_NAMED_ELEMENT(), QML_EXTENDED(), + {Registering Extension Objects}, Q_ENUM, Q_ENUM_NS +*/ + +/*! \macro QML_FOREIGN(FOREIGN_TYPE) \relates QQmlEngine Declares that any \l QML_ELEMENT, \l QML_NAMED_ELEMENT(), \l QML_ANONYMOUS, \l QML_INTERFACE, \l QML_UNCREATABLE(), \l QML_SINGLETON, \l QML_ADDED_IN_MINOR_VERSION(), \l QML_REMOVED_IN_MINOR_VERSION(), - \l QML_EXTENDED(), or \l QML_EXTENDED_NAMESPACE() macros - in the enclosing C++ type do not apply to the enclosing type but instead to - \a FOREIGN_TYPE. The enclosing type still needs to be registered with the - \l {The Meta-Object System}{meta object system} using a \l Q_GADGET or - \l Q_OBJECT macro. + \l QML_EXTENDED(), or \l QML_EXTENDED_NAMESPACE(), or \l QML_NAMESPACE_EXTENDED() + macros in the enclosing C++ type do not apply to the enclosing type but + instead to \a FOREIGN_TYPE. The enclosing type still needs to be registered + with the \l {The Meta-Object System}{meta object system} using a \l Q_GADGET + or \l Q_OBJECT macro. This is useful for registering types that cannot be amended to add the macros, for example because they belong to 3rdparty libraries. diff --git a/src/qml/doc/src/qmllanguageref/syntax/objectattributes.qdoc b/src/qml/doc/src/qmllanguageref/syntax/objectattributes.qdoc index 83dbfaa6e7..08e211415b 100644 --- a/src/qml/doc/src/qmllanguageref/syntax/objectattributes.qdoc +++ b/src/qml/doc/src/qmllanguageref/syntax/objectattributes.qdoc @@ -127,8 +127,8 @@ Rectangle { \section4 Valid Types in Custom Property Definitions -Any of the \l {QML Value Types} aside from the \l enumeration type can be used -as custom property types. For example, these are all valid property declarations: +Any of the \l {QML Value Types} can be used as custom property types. For +example, these are all valid property declarations: \qml Item { @@ -1124,7 +1124,8 @@ Text { } \endqml -More information on enumeration usage in QML can be found in the \l {QML Value Types} \l enumeration documentation. +More information on enumeration usage in QML can be found in the documentation on +\l {QML Enumerations}. The ability to declare enumerations in QML was introduced in Qt 5.10. diff --git a/src/qml/doc/src/qmllanguageref/typesystem/enumerations.qdoc b/src/qml/doc/src/qmllanguageref/typesystem/enumerations.qdoc new file mode 100644 index 0000000000..0dbc96a5a5 --- /dev/null +++ b/src/qml/doc/src/qmllanguageref/typesystem/enumerations.qdoc @@ -0,0 +1,74 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only +/*! +\page qtqml-typesystem-enumerations.html +\title QML Enumerations +\brief Description of QML Enumerations + +Enumerations used in QML can be defined either in QML or in C++. + +For information on defining enumerations in QML, see +\l{QML Object Attributes#enumeration-attributes}{Enumeration Attributes}. + +When defined in C++, enumerations exposed to QML must be marked with the +\l{Q_ENUM} or \l{Q_ENUM_NS} macros and be part of a type exposed to QML via the +\l{QML_NAMED_ELEMENT} or \l{QML_ELEMENT} macros. For more details, see +\l{Data Type Conversion Between QML and C++#enumeration-types}{Enumeration Types}. + +Only named QML types can hold enumerations usable in QML. Each enumeration must +have a surrounding named type. \l{QML Namespaces} are also QML types and can hold +enums. + +As a result, an enumeration value can be referred to as \c {<Type>.<Value>}. For +example, the \l Text type has an \c AlignRight enumeration value: + +\qml +Text { horizontalAlignment: Text.AlignRight } +\endqml + +Enumeration values are properties of the type reference produced by the type +name. You can also retrieve them using JavaScript’s square bracket syntax, +though this is error-prone and not recommended: + +\qml +// Avoid this if possible +Text { horizontalAlignment: Text["AlignRight"] } +\endqml + +\section1 Using Enumerations in QML + +Enumerations are not separate types in QML but are properties of their +surrounding types. An enumeration value is represented as the underlying type of +the enumeration. For most enumerations, it is safe to use JavaScript's +\e Number type or QML's \e double type to store them. However, this does not +always work for 64-bit integers, as their range exceeds the safe integer range +of 64-bit doubles. Therefore, enumerations with values outside the safe integer +range (-(2^53 - 1) to 2^53 - 1, inclusive) cannot be safely used in QML. For +enumerations with values that fit into the numeric range of a 32-bit signed +integer, you can safely use the QML \e int type as storage. + +For example: + +\qml +import QtQuick + +Item { + // refer to Text.AlignRight using an int type + property int enumValue: textItem.horizontalAlignment + + signal valueEmitted(int someValue) + + Text { + id: textItem + horizontalAlignment: Text.AlignRight + } + + // emit valueEmitted() signal, which expects an int, with Text.AlignRight + Component.onCompleted: valueEmitted(Text.AlignRight) +} +\endqml + +\sa {QML Value Types} +\sa {QML Object Attributes#enumeration-attributes}{Enumeration Attributes} +\sa {Data Type Conversion Between QML and C++#enumeration-types}{Enumeration Types} +*/ diff --git a/src/qml/doc/src/qmllanguageref/typesystem/valuetypes.qdoc b/src/qml/doc/src/qmllanguageref/typesystem/valuetypes.qdoc index 8999697eb1..1dc7122dbb 100644 --- a/src/qml/doc/src/qmllanguageref/typesystem/valuetypes.qdoc +++ b/src/qml/doc/src/qmllanguageref/typesystem/valuetypes.qdoc @@ -42,7 +42,6 @@ document, with the following exceptions: \list \li \c void, which marks the absence of a value \li \c list must be used in conjunction with an object or value type as element - \li \c enumeration cannot be used directly as the enumeration must be defined by a registered QML object type \endlist \section2 Built-in Value Types Provided By The QML Language @@ -539,60 +538,3 @@ property is only invoked when the property is reassigned to a different object v \sa {QML Value Types} */ - -/*! - \qmlvaluetype enumeration - \ingroup qmlvaluetypes - \brief a named enumeration value. - - The \c enumeration type refers to a named enumeration value. - - Each named value can be referred to as \c {<Type>.<value>}. For - example, the \l Text type has an \c AlignRight enumeration value: - - \qml - Text { horizontalAlignment: Text.AlignRight } - \endqml - - (For backwards compatibility, the enumeration value may also be - specified as a string, e.g. "AlignRight". This form is not - recommended for new code.) - - When integrating with C++, note that any \c enum value - \l{qtqml-cppintegration-data.html}{passed into QML from C++} is automatically - converted into an \c enumeration value, and vice-versa. - - This value type is provided by the QML language. Some enumeration values - are provided by the QtQuick import. - - \section1 Using the enumeration Type in QML - - The \c enumeration type is a representation of a C++ \c enum type. It is - not possible to refer to the \c enumeration type in QML itself; instead, the - \l int or \l var types can be used when referring to \c enumeration values - from QML code. - - For example: - - \qml - import QtQuick 2.0 - - Item { - // refer to Text.AlignRight using an int type - property int enumValue: textItem.horizontalAlignment - - signal valueEmitted(int someValue) - - Text { - id: textItem - horizontalAlignment: Text.AlignRight - } - - // emit valueEmitted() signal, which expects an int, with Text.AlignRight - Component.onCompleted: valueEmitted(Text.AlignRight) - } - \endqml - - \sa {QML Value Types} - \sa {qtqml-syntax-objectattributes.html#enumeration-attributes}{Enumeration Attributes} -*/ diff --git a/src/qml/doc/src/qt6-changes.qdoc b/src/qml/doc/src/qt6-changes.qdoc index 4f95103a4f..92f78c0e88 100644 --- a/src/qml/doc/src/qt6-changes.qdoc +++ b/src/qml/doc/src/qt6-changes.qdoc @@ -47,6 +47,10 @@ Qt.resolvedUrl can be used in both Qt 5 and 6. + As a porting aid, the \c{QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT} environment variable can be set + to 1 to obtain the Qt 5 behavior. This is possible since Qt 6.2.2. However, it is recommended + to only use this to unblock a port, and to use \c Qt.resolvedUrl as explained above. + \section2 Variant Properties \c variant properties, which have been marked as obsolete since Qt 5, are now treated in exactly diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp index f622c3fb23..8717462729 100644 --- a/src/qml/jsapi/qjsvalue.cpp +++ b/src/qml/jsapi/qjsvalue.cpp @@ -418,6 +418,17 @@ QJSValue::ErrorType QJSValue::errorType() const Returns true if this QJSValue is an object of the Array class; otherwise returns false. + \note This method is the equivalent of \e Array.isArray() in JavaScript. You + can use it to identify JavaScript arrays, but it will return \c false + for any array-like objects that are not JavaScript arrays. This includes + QML \e list objects for either value types or object types, JavaScript + typed arrays, JavaScript ArrayBuffer objects, and any custom array-like + objects you may create yourself. All of these \e behave like JavaScript + arrays, though: They generally expose the same methods and the + subscript operator can be used on them. Therefore, using this method to + determine whether an object could be used like an array is not + advisable. + \sa QJSEngine::newArray() */ bool QJSValue::isArray() const @@ -1006,7 +1017,7 @@ bool QJSValue::equals(const QJSValue& other) const \header \li Type \li Result \row \li Undefined \li true \row \li Null \li true - \row \li Boolean \li true if both values are true, false otherwise + \row \li Boolean \li true if values are both true or both false, false otherwise \row \li Number \li false if either value is NaN (Not-a-Number); true if values are equal, false otherwise \row \li String \li true if both values are exactly the same sequence of characters, false otherwise \row \li Object \li true if both values refer to the same object, false otherwise diff --git a/src/qml/jsruntime/qv4objectiterator.cpp b/src/qml/jsruntime/qv4objectiterator.cpp index eac2aca059..573f42956d 100644 --- a/src/qml/jsruntime/qv4objectiterator.cpp +++ b/src/qml/jsruntime/qv4objectiterator.cpp @@ -159,7 +159,7 @@ PropertyKey ForInIteratorObject::nextProperty() const } c = c->getPrototypeOf(); - d()->current.set(scope.engine, c->d()); + d()->current.set(scope.engine, c ? c->d() : nullptr); if (!c) break; delete d()->iterator; diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index ecd947ccc3..e19c57fd40 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -277,9 +277,13 @@ ReturnedValue QObjectWrapper::getProperty( QQmlEnginePrivate *ep = engine->qmlEngine() ? QQmlEnginePrivate::get(engine->qmlEngine()) : nullptr; - if (ep && ep->propertyCapture && !property->isConstant()) - if (!property->isBindable() || ep->propertyCapture->expression->mustCaptureBindableProperty()) - ep->propertyCapture->captureProperty(object, property->coreIndex(), property->notifyIndex()); + if (ep && ep->propertyCapture && !property->isConstant()) { + if (!property->notifiesViaBindable() + || ep->propertyCapture->expression->mustCaptureBindableProperty()) { + ep->propertyCapture->captureProperty( + object, property->coreIndex(), property->notifyIndex()); + } + } if (property->isVarProperty()) { QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(object); @@ -516,7 +520,7 @@ void QObjectWrapper::setProperty( ScopedContext ctx(scope, f->scope()); // binding assignment. - if (property->isBindable()) { + if (property->acceptsQBinding()) { const QQmlPropertyIndex idx(property->coreIndex(), /*not a value type*/-1); auto [targetObject, targetIndex] = QQmlPropertyPrivate::findAliasTarget(object, idx); QUntypedPropertyBinding binding; @@ -527,6 +531,7 @@ void QObjectWrapper::setProperty( } else { binding = QQmlPropertyBinding::create(property, f->function(), object, callingQmlContext, ctx, targetObject, targetIndex); + } QUntypedBindable bindable; void *argv = {&bindable}; @@ -1088,6 +1093,7 @@ struct QObjectSlotDispatcher : public QtPrivate::QSlotObjectBase PersistentValue function; PersistentValue thisObject; QMetaMethod signal; + qsizetype maxNumArguments; QObjectSlotDispatcher() : QtPrivate::QSlotObjectBase(&impl) @@ -1113,14 +1119,16 @@ struct QObjectSlotDispatcher : public QtPrivate::QSlotObjectBase QQmlMetaObject::ArgTypeStorage<9> storage; QQmlMetaObject::methodParameterTypes(This->signal, &storage, nullptr); - int argCount = storage.size(); + const qsizetype argCount = std::min(storage.size(), This->maxNumArguments); Scope scope(v4); ScopedFunctionObject f(scope, This->function.value()); JSCallArguments jsCallData(scope, argCount); - *jsCallData.thisObject = This->thisObject.isUndefined() ? v4->globalObject->asReturnedValue() : This->thisObject.value(); - for (int ii = 0; ii < argCount; ++ii) { + *jsCallData.thisObject = This->thisObject.isUndefined() + ? v4->globalObject->asReturnedValue() + : This->thisObject.value(); + for (qsizetype ii = 0; ii < argCount; ++ii) { QMetaType type = storage[ii]; if (type == QMetaType::fromType<QVariant>()) { jsCallData.args[ii] = v4->fromVariant(*((QVariant *)metaArgs[ii + 1])); @@ -1251,8 +1259,22 @@ ReturnedValue QObjectWrapper::method_connect(const FunctionObject *b, const Valu QPair<QObject *, int> functionData = QObjectMethod::extractQtMethod(f); // align with disconnect if (QObject *receiver = functionData.first) { + if (functionData.second == -1) { + slot->maxNumArguments = std::numeric_limits<qsizetype>::max(); + } else { + // This means we are connecting to QObjectMethod which complains about extra arguments. + Heap::QObjectMethod *d = static_cast<Heap::QObjectMethod *>(f->d()); + const QMetaObject *metaObject = receiver->metaObject(); + d->ensureMethodsCache(metaObject); + slot->maxNumArguments = std::accumulate(d->methods, d->methods + d->methodCount, 0, + [metaObject](int a, const QQmlPropertyData &b) { + return std::max(a, metaObject->method(b.coreIndex()).parameterCount()); + }); + } + QObjectPrivate::connect(signalObject, signalIndex, receiver, slot, Qt::AutoConnection); } else { + slot->maxNumArguments = std::numeric_limits<qsizetype>::max(); qCInfo(lcObjectConnect, "Could not find receiver of the connection, using sender as receiver. Disconnect " "explicitly (or delete the sender) to make sure the connection is removed."); diff --git a/src/qml/jsruntime/qv4stringobject.cpp b/src/qml/jsruntime/qv4stringobject.cpp index 2613cff99a..6454898b81 100644 --- a/src/qml/jsruntime/qv4stringobject.cpp +++ b/src/qml/jsruntime/qv4stringobject.cpp @@ -777,7 +777,14 @@ ReturnedValue StringPrototype::method_replace(const FunctionObject *b, const Val nMatchOffsets += re->captureCount() * 2; if (!regExp->global()) break; - offset = qMax(offset, matchOffsets[oldSize + 1]) + 1; + + const uint matchBegin = matchOffsets[oldSize]; + const uint matchEnd = matchOffsets[oldSize + 1]; + + // If we have a zero-sized match, don't match at the same place again. + const uint matchOffset = (matchBegin == matchEnd) ? matchEnd + 1 : matchEnd; + + offset = std::max(offset + 1, matchOffset); } if (regExp->global()) { regExp->setLastIndex(0); diff --git a/src/qml/jsruntime/qv4value_p.h b/src/qml/jsruntime/qv4value_p.h index 259334e12c..643d6c16a9 100644 --- a/src/qml/jsruntime/qv4value_p.h +++ b/src/qml/jsruntime/qv4value_p.h @@ -46,6 +46,11 @@ struct Q_QML_PRIVATE_EXPORT Value : public StaticValue return {staticValue._val}; } + static constexpr Value undefinded() + { + return fromStaticValue(Encode::undefined()); + } + inline bool isString() const; inline bool isStringOrSymbol() const; inline bool isSymbol() const; @@ -412,6 +417,9 @@ struct HeapValue : Value { void set(EngineBase *e, HeapBasePtr b) { WriteBarrier::write(e, base(), data_ptr(), b->asReturnedValue()); } + void set(EngineBase *e, ReturnedValue rv) { + WriteBarrier::write(e, base(), data_ptr(), rv); + } }; template <size_t o> diff --git a/src/qml/qml/qqmlanybinding_p.h b/src/qml/qml/qqmlanybinding_p.h index 66d2fc573d..5ff19dff89 100644 --- a/src/qml/qml/qqmlanybinding_p.h +++ b/src/qml/qml/qqmlanybinding_p.h @@ -71,19 +71,31 @@ public: */ static QQmlAnyBinding ofProperty(QObject *object, QQmlPropertyIndex index) { - QQmlAnyBinding binding; + const auto result = [](auto &&result) -> QQmlAnyBinding { + QQmlAnyBinding binding; + binding = std::forward<decltype(result)>(result); + return binding; + }; + Q_ASSERT(object); - auto coreIndex = index.coreIndex(); + const auto coreIndex = index.coreIndex(); + // we don't support bindable properties on value types so far - if (!index.hasValueTypeIndex() - && QQmlData::ensurePropertyCache(object)->property(coreIndex)->isBindable()) { - auto metaProp = object->metaObject()->property(coreIndex); - QUntypedBindable bindable = metaProp.bindable(object); - binding = bindable.binding(); - } else { - binding = QQmlPropertyPrivate::binding(object, index); + if (index.hasValueTypeIndex()) + return result(QQmlPropertyPrivate::binding(object, index)); + + if (QQmlPropertyCache::ConstPtr propertyCache = QQmlData::ensurePropertyCache(object)) { + const QQmlPropertyData *property = propertyCache->property(coreIndex); + return (property->acceptsQBinding()) + ? result(property->propertyBindable(object).binding()) + : result(QQmlPropertyPrivate::binding(object, index)); } - return binding; + + const QMetaObject *metaObject = object->metaObject(); + const QMetaProperty metaProp = metaObject->property(coreIndex); + return metaProp.isBindable() + ? result(metaProp.bindable(object).binding()) + : result(QQmlPropertyPrivate::binding(object, index)); } /*! diff --git a/src/qml/qml/qqmlbuiltinfunctions.cpp b/src/qml/qml/qqmlbuiltinfunctions.cpp index 0251a72049..47326e9836 100644 --- a/src/qml/qml/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/qqmlbuiltinfunctions.cpp @@ -132,7 +132,7 @@ of their use. \list \li \l{Qt::createComponent()}{object Qt.createComponent(url)} - \li \l{Qt::createQmlObject()}{object Qt.createQmlObject(string qml, object parent, string filepath)} + \li \l{Qt::createQmlObject()}{object Qt.createQmlObject(string qml, object parent, url url)} \endlist @@ -1286,12 +1286,18 @@ void QtObject::exit(int retCode) const } /*! -\qmlmethod object Qt::createQmlObject(string qml, object parent, string filepath) +\qmlmethod object Qt::createQmlObject(string qml, object parent, url url) -Returns a new object created from the given \a qml string which will have the specified \a parent, -or \c null if there was an error in creating the object. +Compiles the given \a qml string into a component and then returns a new object created from +that component. The new object will have the specified \a parent. Returns \c null if there was +an error in creating the component or the object. -If \a filepath is specified, it will be used for error reporting for the created object. +If \a url is specified, it will be used as URL of the component. This is useful for error +reporting. + +\warning The new component will shadow any existing component of the same URL. You should not +pass a URL of an existing component. In particular, by passing the URL of the surrounding QML +file, you prevent access to the surrounding component from the new one. Example (where \c parentItem is the id of an existing QML item): diff --git a/src/qml/qml/qqmlcomponent.cpp b/src/qml/qml/qqmlcomponent.cpp index 7b2107b858..60e5dd4c9b 100644 --- a/src/qml/qml/qqmlcomponent.cpp +++ b/src/qml/qml/qqmlcomponent.cpp @@ -352,7 +352,7 @@ static void removePendingQPropertyBinding( if (QQmlData *ddata = QQmlData::get(o)) { const QQmlPropertyData *propData = ddata->propertyCache->property( propertyName, o, ddata->outerContext); - if (propData && propData->isBindable()) + if (propData && propData->acceptsQBinding()) creator->removePendingBinding(o, propData->coreIndex()); return; } @@ -694,6 +694,9 @@ QQmlComponent::QQmlComponent(QQmlEngine *engine, QV4::ExecutableCompilationUnit Sets the QQmlComponent to use the given QML \a data. If \a url is provided, it is used to set the component name and to provide a base path for items resolved by this component. + + \warning The new component will shadow any existing component of + the same URL. You should not pass a URL of an existing component. */ void QQmlComponent::setData(const QByteArray &data, const QUrl &url) { diff --git a/src/qml/qml/qqmljavascriptexpression.cpp b/src/qml/qml/qqmljavascriptexpression.cpp index d7cf38984b..c4e646a55d 100644 --- a/src/qml/qml/qqmljavascriptexpression.cpp +++ b/src/qml/qml/qqmljavascriptexpression.cpp @@ -315,7 +315,7 @@ void QQmlPropertyCapture::captureProperty(QObject *o, int c, int n, bool doNotif const QMetaObject *metaObjectForBindable = nullptr; if (auto const propCache = (ddata ? ddata->propertyCache.data() : nullptr)) { Q_ASSERT(propCache->property(c)); - if (propCache->property(c)->isBindable()) + if (propCache->property(c)->notifiesViaBindable()) metaObjectForBindable = propCache->metaObject(); } else { const QMetaObject *m = o->metaObject(); @@ -340,7 +340,7 @@ void QQmlPropertyCapture::captureProperty( Q_ASSERT(expression); - if (propertyData->isBindable()) { + if (propertyData->notifiesViaBindable()) { if (const QMetaObject *metaObjectForBindable = propertyCache->metaObject()) { captureBindableProperty(o, metaObjectForBindable, propertyData->coreIndex()); return; diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index 119053828f..0f92663268 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -918,7 +918,7 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper && !_valueTypeProperty; if (allowedToRemoveBinding) { - if (bindingProperty->isBindable()) { + if (bindingProperty->acceptsQBinding()) { removePendingBinding(_bindingTarget, bindingProperty->coreIndex()); } else { QQmlPropertyPrivate::removeBinding( @@ -935,7 +935,7 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper _bindingTarget, signalIndex, context, _scopeObject, runtimeFunction, currentQmlContext()); - if (bindingProperty->isBindable()) { + if (bindingProperty->notifiesViaBindable()) { auto target = _bindingTarget; if (bindingProperty->isAlias()) { // If the property is an alias, we cannot obtain the bindable interface directly with qt_metacall @@ -959,7 +959,7 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper QQmlBoundSignal *bs = new QQmlBoundSignal(_bindingTarget, signalIndex, _scopeObject, engine); bs->takeExpression(expr); } - } else if (bindingProperty->isBindable()) { + } else if (bindingProperty->acceptsQBinding()) { QUntypedPropertyBinding qmlBinding; if (binding->isTranslationBinding()) { qmlBinding = QQmlTranslationPropertyBinding::create(bindingProperty, compilationUnit, binding); diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index e8d5b0c8cb..f09024d330 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -300,7 +300,7 @@ void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name, for (auto idContext = context; idContext; idContext = idContext->parent()) { const int objectId = idContext->propertyIndex(pathName.toString()); if (objectId != -1 && objectId < idContext->numIdValues()) { - currentObject = context->idValue(objectId); + currentObject = idContext->idValue(objectId); break; } } @@ -711,7 +711,7 @@ bool QQmlProperty::isBindable() const if (!d->object) return false; if (d->core.isValid()) - return d->core.isBindable(); + return d->core.notifiesViaBindable(); return false; } @@ -1365,7 +1365,9 @@ struct BindingFixer BindingFixer(QObject *object, const QQmlPropertyData &property, QQmlPropertyData::WriteFlags flags) { - if (!property.isBindable() || !(flags & QQmlPropertyData::DontRemoveBinding)) + // Even if QML cannot install bindings on this property, there may be a C++-created binding. + // If the property can notify via a bindable, there is a bindable that can hold a binding. + if (!property.notifiesViaBindable() || !(flags & QQmlPropertyData::DontRemoveBinding)) return; QUntypedBindable bindable; diff --git a/src/qml/qml/qqmlpropertycache.cpp b/src/qml/qml/qqmlpropertycache.cpp index 3f3f6540d5..c7cfde0b1b 100644 --- a/src/qml/qml/qqmlpropertycache.cpp +++ b/src/qml/qml/qqmlpropertycache.cpp @@ -1013,7 +1013,7 @@ void QQmlPropertyCache::toMetaObjectBuilder(QMetaObjectBuilder &builder) const property.setReadable(true); property.setWritable(data->isWritable()); property.setResettable(data->isResettable()); - property.setBindable(data->isBindable()); + property.setBindable(data->notifiesViaBindable()); property.setAlias(data->isAlias()); } diff --git a/src/qml/qml/qqmlpropertycachecreator_p.h b/src/qml/qml/qqmlpropertycachecreator_p.h index 375971f7fb..dafefc904b 100644 --- a/src/qml/qml/qqmlpropertycachecreator_p.h +++ b/src/qml/qml/qqmlpropertycachecreator_p.h @@ -758,7 +758,7 @@ inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataFor *type = QMetaType(); bool writable = false; bool resettable = false; - bool bindable = false; + bool notifiesViaBindable = false; propertyFlags->setIsAlias(true); @@ -845,7 +845,7 @@ inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataFor *type = resolveType(property->propType()); writable = property->isWritable(); resettable = property->isResettable(); - bindable = property->isBindable(); + notifiesViaBindable = property->notifiesViaBindable(); if (property->isVarProperty()) propertyFlags->type = QQmlPropertyData::Flags::QVariantType; @@ -893,7 +893,8 @@ inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataFor resettable = writable && valueTypeMetaProperty.isResettable(); writable = writable && valueTypeMetaProperty.isWritable(); - bindable = valueTypeMetaProperty.isBindable(); + // Do not update notifiesViaBindable. The core property counts for notifications. + propertyFlags->setIsDeepAlias(true); } } } @@ -901,7 +902,7 @@ inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataFor propertyFlags->setIsWritable( writable && !alias.hasFlag(QV4::CompiledData::Alias::IsReadOnly)); propertyFlags->setIsResettable(resettable); - propertyFlags->setIsBindable(bindable); + propertyFlags->setIsBindable(notifiesViaBindable); return QQmlError(); } diff --git a/src/qml/qml/qqmlpropertydata_p.h b/src/qml/qml/qqmlpropertydata_p.h index 5fe88327af..8eae7b6fbe 100644 --- a/src/qml/qml/qqmlpropertydata_p.h +++ b/src/qml/qml/qqmlpropertydata_p.h @@ -70,7 +70,7 @@ public: // Lastly, isDirect and isOverridden apply to both functions and non-functions private: unsigned isConst : 1; // Property: has CONST flag/Method: is const - unsigned isVMEFunction : 1; // Function was added by QML + unsigned isDeepAliasORisVMEFunction : 1; // Alias points into value type OR Function was added by QML unsigned isWritableORhasArguments : 1; // Has WRITE function OR Function takes arguments unsigned isResettableORisSignal : 1; // Has RESET function OR Function is a signal unsigned isAliasORisVMESignal : 1; // Is a QML alias to another property OR Signal was added by QML @@ -111,6 +111,11 @@ public: isAliasORisVMESignal = b; } + void setIsDeepAlias(bool b) { + Q_ASSERT(type != FunctionType); + isDeepAliasORisVMEFunction = b; + } + void setIsFinal(bool b) { Q_ASSERT(type != FunctionType); isFinalORisV4Function = b; @@ -132,8 +137,9 @@ public: void setIsVMEFunction(bool b) { Q_ASSERT(type == FunctionType); - isVMEFunction = b; + isDeepAliasORisVMEFunction = b; } + void setHasArguments(bool b) { Q_ASSERT(type == FunctionType); isWritableORhasArguments = b; @@ -204,7 +210,7 @@ public: bool isQJSValue() const { return m_flags.type == Flags::QJSValueType; } bool isVarProperty() const { return m_flags.type == Flags::VarPropertyType; } bool isQVariant() const { return m_flags.type == Flags::QVariantType; } - bool isVMEFunction() const { return isFunction() && m_flags.isVMEFunction; } + bool isVMEFunction() const { return isFunction() && m_flags.isDeepAliasORisVMEFunction; } bool hasArguments() const { return isFunction() && m_flags.isWritableORhasArguments; } bool isSignal() const { return isFunction() && m_flags.isResettableORisSignal; } bool isVMESignal() const { return isFunction() && m_flags.isAliasORisVMESignal; } @@ -214,7 +220,9 @@ public: void setOverload(bool onoff) { m_flags.isOverload = onoff; } bool isCloned() const { return isFunction() && m_flags.isRequiredORisCloned; } bool isConstructor() const { return isFunction() && m_flags.isConstructorORisBindable; } - bool isBindable() const { return !isFunction() && m_flags.isConstructorORisBindable; } + + bool notifiesViaBindable() const { return !isFunction() && m_flags.isConstructorORisBindable; } + bool acceptsQBinding() const { return notifiesViaBindable() && !m_flags.isDeepAliasORisVMEFunction; } bool hasOverride() const { return overrideIndex() >= 0; } bool hasRevision() const { return revision() != QTypeRevision::zero(); } @@ -351,6 +359,17 @@ public: return true; } + QUntypedBindable propertyBindable(QObject *target) const + { + QUntypedBindable result; + void *argv[] = { &result }; + if (hasStaticMetaCallFunction()) + staticMetaCallFunction()(target, QMetaObject::BindableProperty, relativePropertyIndex(), argv); + else + doMetacall<QMetaObject::BindableProperty>(target, coreIndex(), argv); + return result; + } + static Flags defaultSignalFlags() { Flags f; @@ -410,7 +429,7 @@ bool QQmlPropertyData::operator==(const QQmlPropertyData &other) const QQmlPropertyData::Flags::Flags() : otherBits(0) , isConst(false) - , isVMEFunction(false) + , isDeepAliasORisVMEFunction(false) , isWritableORhasArguments(false) , isResettableORisSignal(false) , isAliasORisVMESignal(false) @@ -427,7 +446,7 @@ QQmlPropertyData::Flags::Flags() bool QQmlPropertyData::Flags::operator==(const QQmlPropertyData::Flags &other) const { return isConst == other.isConst && - isVMEFunction == other.isVMEFunction && + isDeepAliasORisVMEFunction == other.isDeepAliasORisVMEFunction && isWritableORhasArguments == other.isWritableORhasArguments && isResettableORisSignal == other.isResettableORisSignal && isAliasORisVMESignal == other.isAliasORisVMESignal && diff --git a/src/qml/qml/qqmlpropertytopropertybinding.cpp b/src/qml/qml/qqmlpropertytopropertybinding.cpp index f2edf3b87f..8ef99b0ddb 100644 --- a/src/qml/qml/qqmlpropertytopropertybinding.cpp +++ b/src/qml/qml/qqmlpropertytopropertybinding.cpp @@ -104,7 +104,7 @@ void QQmlPropertyToPropertyBinding::update(QQmlPropertyData::WriteFlags flags) const QMetaProperty property = sourceMetaObject->property(m_sourcePropertyIndex); if (!property.isConstant()) { captureProperty(sourceMetaObject, QMetaObjectPrivate::signalIndex(property.notifySignal()), - property.isBindable(), !vtd.isValid() && d->isBindable()); + property.isBindable(), !vtd.isValid() && d->acceptsQBinding()); } QQmlPropertyPrivate::writeValueProperty( diff --git a/src/qml/qml/qqmltypecompiler.cpp b/src/qml/qml/qqmltypecompiler.cpp index e011856807..6a1aab2d12 100644 --- a/src/qml/qml/qqmltypecompiler.cpp +++ b/src/qml/qml/qqmltypecompiler.cpp @@ -345,7 +345,8 @@ bool SignalHandlerResolver::resolveSignalHandlerExpressions( QV4::CompiledData::Binding::Flag flag = QV4::CompiledData::Binding::IsSignalHandlerExpression; - const bool isPropertyObserver = !signalPropertyData && qPropertyData && qPropertyData->isBindable(); + const bool isPropertyObserver + = !signalPropertyData && qPropertyData && qPropertyData->notifiesViaBindable(); if (signal && !(qPropertyData && qPropertyData->isAlias() && isPropertyObserver)) { int sigIndex = propertyCache->methodIndexToSignalIndex(signal->coreIndex()); sigIndex = propertyCache->originalClone(sigIndex); diff --git a/src/qml/qml/qqmltypedata.cpp b/src/qml/qml/qqmltypedata.cpp index 0c88f25690..d3cfeb3108 100644 --- a/src/qml/qml/qqmltypedata.cpp +++ b/src/qml/qml/qqmltypedata.cpp @@ -683,6 +683,11 @@ void QQmlTypeData::dataReceived(const SourceCodeData &data) void QQmlTypeData::initializeFromCachedUnit(const QQmlPrivate::CachedQmlUnit *unit) { + if (unit->qmlData->qmlUnit()->nObjects == 0) { + setError(QQmlTypeLoader::tr("Cached QML Unit has no objects")); + return; + } + m_document.reset(new QmlIR::Document(isDebugging())); QQmlIRLoader loader(unit->qmlData, m_document.data()); loader.load(); diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index 72ad77b41c..68c1163c8c 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -502,6 +502,11 @@ void QQmlTypeLoader::Blob::importQmldirScripts( for (const QQmlDirParser::Script &script : qmldirScripts) { const QUrl scriptUrl = qmldirUrl.resolved(QUrl(script.fileName)); QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl); + + // Self-import via qmldir is OK-ish. We ignore it. + if (blob.data() == this) + continue; + addDependency(blob.data()); scriptImported(blob, import->location, script.nameSpace, import->qualifier); } diff --git a/src/qml/qml/qqmltypenamecache_p.h b/src/qml/qml/qqmltypenamecache_p.h index 6b3f9094f1..c401338afa 100644 --- a/src/qml/qml/qqmltypenamecache_p.h +++ b/src/qml/qml/qqmltypenamecache_p.h @@ -222,8 +222,7 @@ private: template<typename Key> Result typeSearch(const QVector<QQmlTypeModuleVersion> &modules, Key key) const { - QVector<QQmlTypeModuleVersion>::const_iterator end = modules.constEnd(); - for (QVector<QQmlTypeModuleVersion>::const_iterator it = modules.constBegin(); it != end; ++it) { + for (auto it = modules.crbegin(), end = modules.crend(); it != end; ++it) { QQmlType type = it->type(key); if (type.isValid()) return Result(type); diff --git a/src/qml/qml/qqmlvmemetaobject.cpp b/src/qml/qml/qqmlvmemetaobject.cpp index 21a50b5561..94c2c254c0 100644 --- a/src/qml/qml/qqmlvmemetaobject.cpp +++ b/src/qml/qml/qqmlvmemetaobject.cpp @@ -1054,6 +1054,11 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void * QQmlGadgetPtrWrapper *valueType = QQmlGadgetPtrWrapper::instance( ctxt->engine(), pd->propType()); if (valueType) { + + // For value type aliases, the core property provides the bindable. + if (c == QMetaObject::BindableProperty) + return QMetaObject::metacall(target, c, coreIndex, a); + removePendingBinding(target, coreIndex, encodedIndex); valueType->read(target, coreIndex); int rv = QMetaObject::metacall(valueType, c, valueTypePropertyIndex, a); diff --git a/src/qml/qmldirparser/qqmldirparser.cpp b/src/qml/qmldirparser/qqmldirparser.cpp index e6a5691d3d..022d082546 100644 --- a/src/qml/qmldirparser/qqmldirparser.cpp +++ b/src/qml/qmldirparser/qqmldirparser.cpp @@ -347,23 +347,16 @@ bool QQmlDirParser::parse(const QString &source) _linkTarget = sections[1]; } else if (sectionCount == 2) { // No version specified (should only be used for relative qmldir files) - const Component entry(sections[0], sections[1], QTypeRevision()); - _components.insert(entry.typeName, entry); + insertComponentOrScript(sections[0], sections[1], QTypeRevision()); } else if (sectionCount == 3) { const QTypeRevision version = parseVersion(sections[1]); if (version.isValid()) { - const QString &fileName = sections[2]; - - if (fileName.endsWith(QLatin1String(".js")) || fileName.endsWith(QLatin1String(".mjs"))) { - // A 'js' extension indicates a namespaced script import - const Script entry(sections[0], fileName, version); - _scripts.append(entry); - } else { - const Component entry(sections[0], fileName, version); - _components.insert(entry.typeName, entry); - } + insertComponentOrScript(sections[0], sections[2], version); } else { - reportError(lineNumber, 0, QStringLiteral("invalid version %1, expected <major>.<minor>").arg(sections[1])); + reportError( + lineNumber, 0, + QStringLiteral("invalid version %1, expected <major>.<minor>") + .arg(sections[1])); } } else { reportError(lineNumber, 0, @@ -508,6 +501,16 @@ void QQmlDirParser::reportError(quint16 line, quint16 column, const QString &des _errors.append(error); } +void QQmlDirParser::insertComponentOrScript( + const QString &name, const QString &fileName, QTypeRevision version) +{ + // A 'js' extension indicates a namespaced script import + if (fileName.endsWith(QLatin1String(".js")) || fileName.endsWith(QLatin1String(".mjs"))) + _scripts.append(Script(name, fileName, version)); + else + _components.insert(name, Component(name, fileName, version)); +} + void QQmlDirParser::setError(const QQmlJS::DiagnosticMessage &e) { _errors.clear(); diff --git a/src/qml/qmldirparser/qqmldirparser_p.h b/src/qml/qmldirparser/qqmldirparser_p.h index 43409b7b41..07f783ab25 100644 --- a/src/qml/qmldirparser/qqmldirparser_p.h +++ b/src/qml/qmldirparser/qqmldirparser_p.h @@ -140,6 +140,8 @@ public: private: bool maybeAddComponent(const QString &typeName, const QString &fileName, const QString &version, QHash<QString,Component> &hash, int lineNumber = -1, bool multi = true); void reportError(quint16 line, quint16 column, const QString &message); + void insertComponentOrScript( + const QString &name, const QString &fileName, QTypeRevision version); private: QList<QQmlJS::DiagnosticMessage> _errors; diff --git a/src/qmlcompiler/qqmljscompilepass_p.h b/src/qmlcompiler/qqmljscompilepass_p.h index b553f9b380..b35adfb66a 100644 --- a/src/qmlcompiler/qqmljscompilepass_p.h +++ b/src/qmlcompiler/qqmljscompilepass_p.h @@ -32,6 +32,8 @@ public: enum RegisterShortcuts { InvalidRegister = -1, Accumulator = QV4::CallData::Accumulator, + This = QV4::CallData::This, + NewTarget = QV4::CallData::NewTarget, FirstArgument = QV4::CallData::OffsetCount }; diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 653cc5581e..4bc215add7 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -183,12 +183,12 @@ bool QQmlJSImportVisitor::isTypeResolved(const QQmlJSScope::ConstPtr &type) return isTypeResolved(type, handleUnresolvedType); } -static bool mayBeUnresolvedGeneralizedGroupedProperty(const QQmlJSScope::ConstPtr &scope) +static bool mayBeUnresolvedGroupedProperty(const QQmlJSScope::ConstPtr &scope) { return scope->scopeType() == QQmlJSScope::GroupedPropertyScope && !scope->baseType(); } -void QQmlJSImportVisitor::resolveAliasesAndIds() +void QQmlJSImportVisitor::resolveAliases() { QQueue<QQmlJSScope::Ptr> objects; objects.enqueue(m_exportedRootScope); @@ -263,10 +263,15 @@ void QQmlJSImportVisitor::resolveAliasesAndIds() newProperty.setIsWritable(targetProperty.isWritable()); newProperty.setIsPointer(targetProperty.isPointer()); - if (!typeScope.isNull()) { - object->setPropertyLocallyRequired( - newProperty.propertyName(), - typeScope->isPropertyRequired(targetProperty.propertyName())); + const bool onlyId = !property.aliasExpression().contains(u'.'); + if (onlyId) { + newProperty.setAliasTargetScope(type); + newProperty.setAliasTargetName(QStringLiteral("id-only-alias")); + } else { + const auto &ownerScope = QQmlJSScope::ownerOfProperty( + typeScope, targetProperty.propertyName()).scope; + newProperty.setAliasTargetScope(ownerScope); + newProperty.setAliasTargetName(targetProperty.propertyName()); } if (const QString internalName = type->internalName(); !internalName.isEmpty()) @@ -274,23 +279,13 @@ void QQmlJSImportVisitor::resolveAliasesAndIds() Q_ASSERT(newProperty.index() >= 0); // this property is already in object object->addOwnProperty(newProperty); + m_aliasDefinitions.append({ object, property.propertyName() }); } } const auto childScopes = object->childScopes(); - for (const auto &childScope : childScopes) { - if (mayBeUnresolvedGeneralizedGroupedProperty(childScope)) { - const QString name = childScope->internalName(); - if (object->isNameDeferred(name)) { - const QQmlJSScope::ConstPtr deferred = m_scopesById.scope(name, childScope); - if (!deferred.isNull()) { - QQmlJSScope::resolveGeneralizedGroup( - childScope, deferred, m_rootScopeImports, &m_usedTypes); - } - } - } + for (const auto &childScope : childScopes) objects.enqueue(childScope); - } if (doRequeue) requeue.enqueue(object); @@ -314,6 +309,33 @@ void QQmlJSImportVisitor::resolveAliasesAndIds() } } +void QQmlJSImportVisitor::resolveGroupProperties() +{ + QQueue<QQmlJSScope::Ptr> objects; + objects.enqueue(m_exportedRootScope); + + while (!objects.isEmpty()) { + const QQmlJSScope::Ptr object = objects.dequeue(); + const auto childScopes = object->childScopes(); + for (const auto &childScope : childScopes) { + if (mayBeUnresolvedGroupedProperty(childScope)) { + const QString name = childScope->internalName(); + if (object->isNameDeferred(name)) { + const QQmlJSScope::ConstPtr deferred = m_scopesById.scope(name, childScope); + if (!deferred.isNull()) { + QQmlJSScope::resolveGroup( + childScope, deferred, m_rootScopeImports, &m_usedTypes); + } + } else if (const QQmlJSScope::ConstPtr propType = object->property(name).type()) { + QQmlJSScope::resolveGroup( + childScope, propType, m_rootScopeImports, &m_usedTypes); + } + } + objects.enqueue(childScope); + } + } +} + QString QQmlJSImportVisitor::implicitImportDirectory( const QString &localFile, QQmlJSResourceFileMapper *mapper) { @@ -407,7 +429,8 @@ void QQmlJSImportVisitor::endVisit(UiProgram *) checkDeprecation(scope); } - resolveAliasesAndIds(); + resolveAliases(); + resolveGroupProperties(); for (const auto &scope : m_objectDefinitionScopes) checkGroupedAndAttachedScopes(scope); @@ -744,9 +767,30 @@ void QQmlJSImportVisitor::processPropertyBindingObjects() } } +void QQmlJSImportVisitor::populatePropertyAliases() +{ + for (const auto &alias : std::as_const(m_aliasDefinitions)) { + const auto &[aliasScope, aliasName] = alias; + if (aliasScope.isNull()) + continue; + + auto property = aliasScope->ownProperty(aliasName); + if (!property.isValid() || !property.aliasTargetScope()) + continue; + + Property target(property.aliasTargetScope(), property.aliasTargetName()); + + do { + m_propertyAliases[target].append(alias); + property = target.scope->property(target.name); + target = Property(property.aliasTargetScope(), property.aliasTargetName()); + } while (property.isAlias()); + } +} + void QQmlJSImportVisitor::checkRequiredProperties() { - for (const auto &required : m_requiredProperties) { + for (const auto &required : std::as_const(m_requiredProperties)) { if (!required.scope->hasProperty(required.name)) { m_logger->log( QStringLiteral("Property \"%1\" was marked as required but does not exist.") @@ -755,95 +799,149 @@ void QQmlJSImportVisitor::checkRequiredProperties() } } - for (const auto &defScope : m_objectDefinitionScopes) { - if (defScope->parentScope() == m_globalScope || defScope->isInlineComponent() || defScope->isComponentRootElement()) + const auto isInComponent = [this](const QQmlJSScope::ConstPtr &requiredScope) { + const auto compType = m_rootScopeImports.type(u"Component"_s).scope; + for (auto s = requiredScope; s; s = s->parentScope()) { + if (s->isWrappedInImplicitComponent() || s->baseType() == compType) + return true; + } + return false; + }; + + const auto requiredHasBinding = [](const QList<QQmlJSScope::ConstPtr> &scopesToSearch, + const QString &propName) { + for (const auto &scope : scopesToSearch) { + const auto &[begin, end] = scope->ownPropertyBindings(propName); + for (auto it = begin; it != end; ++it) { + if (!scope->property(propName).isAlias()) + return true; + } + } + + return false; + }; + + const auto requiredUsedInRootAlias = [&](const QQmlJSScope::ConstPtr &defScope, + const QQmlJSScope::ConstPtr &requiredScope, + const QString &propName) { + if (defScope->filePath() == requiredScope->filePath()) { + QQmlJSScope::ConstPtr fileRootScope = requiredScope; + while (fileRootScope->parentScope() != m_globalScope) + fileRootScope = fileRootScope->parentScope(); + + const auto &rootProperties = fileRootScope->ownProperties(); + for (const auto &p : rootProperties) { + if (p.isAlias() && p.aliasTargetScope() == requiredScope + && p.aliasTargetName() == propName) { + return true; + } + } + } + + return false; + }; + + const auto requiredSetThroughAlias = [&](const QList<QQmlJSScope::ConstPtr> &scopesToSearch, + const QQmlJSScope::ConstPtr &requiredScope, + const QString &propName) { + const auto &propertyDefScope = QQmlJSScope::ownerOfProperty(requiredScope, propName); + const auto &propertyAliases = m_propertyAliases[{ propertyDefScope.scope, propName }]; + for (const auto &alias : propertyAliases) { + for (const auto &s : scopesToSearch) { + if (s->hasOwnPropertyBindings(alias.name)) + return true; + } + } + return false; + }; + + const auto warn = [this](const QList<QQmlJSScope::ConstPtr> scopesToSearch, + QQmlJSScope::ConstPtr prevRequiredScope, + const QString &propName, + QQmlJSScope::ConstPtr defScope, + QQmlJSScope::ConstPtr requiredScope, + QQmlJSScope::ConstPtr descendant) { + const QQmlJSScope::ConstPtr propertyScope = scopesToSearch.size() > 1 + ? scopesToSearch.at(scopesToSearch.size() - 2) + : QQmlJSScope::ConstPtr(); + + const QString propertyScopeName = !propertyScope.isNull() + ? getScopeName(propertyScope, QQmlJSScope::QMLScope) + : u"here"_s; + + std::optional<QQmlJSFixSuggestion> suggestion; + + QString message = QStringLiteral("Component is missing required property %1 from %2") + .arg(propName) + .arg(propertyScopeName); + if (requiredScope != descendant) { + const QString requiredScopeName = prevRequiredScope + ? getScopeName(prevRequiredScope, QQmlJSScope::QMLScope) + : u"here"_s; + + if (!prevRequiredScope.isNull()) { + auto sourceScope = prevRequiredScope->baseType(); + suggestion = QQmlJSFixSuggestion{ + "%1:%2:%3: Property marked as required in %4."_L1 + .arg(sourceScope->filePath()) + .arg(sourceScope->sourceLocation().startLine) + .arg(sourceScope->sourceLocation().startColumn) + .arg(requiredScopeName), + sourceScope->sourceLocation() + }; + suggestion->setFilename(sourceScope->filePath()); + } else { + message += " (marked as required by %1)"_L1.arg(requiredScopeName); + } + } + + m_logger->log(message, qmlRequired, defScope->sourceLocation(), true, true, suggestion); + }; + + populatePropertyAliases(); + + for (const auto &[_, defScope] : m_scopesByIrLocation.asKeyValueRange()) { + if (defScope->parentScope() == m_globalScope || defScope->isInlineComponent() + || defScope->isComponentRootElement()) { continue; + } QVector<QQmlJSScope::ConstPtr> scopesToSearch; for (QQmlJSScope::ConstPtr scope = defScope; scope; scope = scope->baseType()) { - scopesToSearch << scope; - const auto ownProperties = scope->ownProperties(); - for (auto propertyIt = ownProperties.constBegin(); - propertyIt != ownProperties.constEnd(); ++propertyIt) { - const QString propName = propertyIt.key(); - - QQmlJSScope::ConstPtr prevRequiredScope; - for (QQmlJSScope::ConstPtr requiredScope : scopesToSearch) { - if (requiredScope->isPropertyLocallyRequired(propName)) { - bool found = - std::find_if(scopesToSearch.constBegin(), scopesToSearch.constEnd(), - [&](QQmlJSScope::ConstPtr scope) { - return scope->hasPropertyBindings(propName); - }) - != scopesToSearch.constEnd(); - - if (!found) { - const QString scopeId = m_scopesById.id(defScope, scope); - bool propertyUsedInRootAlias = false; - if (!scopeId.isEmpty()) { - for (const QQmlJSMetaProperty &property : - m_exportedRootScope->ownProperties()) { - if (!property.isAlias()) - continue; - - QStringList aliasExpression = - property.aliasExpression().split(u'.'); - - if (aliasExpression.size() != 2) - continue; - if (aliasExpression[0] == scopeId - && aliasExpression[1] == propName) { - propertyUsedInRootAlias = true; - break; - } - } - } - - if (propertyUsedInRootAlias) - continue; - - const QQmlJSScope::ConstPtr propertyScope = scopesToSearch.size() > 1 - ? scopesToSearch.at(scopesToSearch.size() - 2) - : QQmlJSScope::ConstPtr(); - - const QString propertyScopeName = !propertyScope.isNull() - ? getScopeName(propertyScope, QQmlJSScope::QMLScope) - : u"here"_s; - - const QString requiredScopeName = prevRequiredScope - ? getScopeName(prevRequiredScope, QQmlJSScope::QMLScope) - : u"here"_s; - - std::optional<QQmlJSFixSuggestion> suggestion; - - QString message = - QStringLiteral( - "Component is missing required property %1 from %2") - .arg(propName) - .arg(propertyScopeName); - if (requiredScope != scope) { - if (!prevRequiredScope.isNull()) { - auto sourceScope = prevRequiredScope->baseType(); - suggestion = QQmlJSFixSuggestion { - "%1:%2:%3: Property marked as required in %4"_L1 - .arg(sourceScope->filePath()) - .arg(sourceScope->sourceLocation().startLine) - .arg(sourceScope->sourceLocation().startColumn) - .arg(requiredScopeName), - sourceScope->sourceLocation() - }; - suggestion->setFilename(sourceScope->filePath()); - } else { - message += QStringLiteral(" (marked as required by %1)") - .arg(requiredScopeName); - } - } - - m_logger->log(message, qmlRequired, defScope->sourceLocation(), true, - true, suggestion); + const auto descendants = QList<QQmlJSScope::ConstPtr>() << scope << scope->descendantScopes(); + for (QQmlJSScope::ConstPtr descendant : std::as_const(descendants)) { + if (descendant->scopeType() != QQmlJSScope::QMLScope) + continue; + scopesToSearch << descendant; + const auto ownProperties = descendant->ownProperties(); + for (auto propertyIt = ownProperties.constBegin(); + propertyIt != ownProperties.constEnd(); ++propertyIt) { + const QString propName = propertyIt.key(); + + QQmlJSScope::ConstPtr prevRequiredScope; + for (QQmlJSScope::ConstPtr requiredScope : std::as_const(scopesToSearch)) { + if (isInComponent(requiredScope)) + continue; + + if (!requiredScope->isPropertyLocallyRequired(propName)) { + prevRequiredScope = requiredScope; + continue; } + + if (requiredHasBinding(scopesToSearch, propName)) + continue; + + if (requiredUsedInRootAlias(defScope, requiredScope, propName)) + continue; + + if (requiredSetThroughAlias(scopesToSearch, requiredScope, propName)) + continue; + + warn(scopesToSearch, prevRequiredScope, propName, defScope, + requiredScope, descendant); + + prevRequiredScope = requiredScope; } - prevRequiredScope = requiredScope; } } } diff --git a/src/qmlcompiler/qqmljsimportvisitor_p.h b/src/qmlcompiler/qqmljsimportvisitor_p.h index 8565e47cf1..8e4a87c46d 100644 --- a/src/qmlcompiler/qqmljsimportvisitor_p.h +++ b/src/qmlcompiler/qqmljsimportvisitor_p.h @@ -178,25 +178,47 @@ protected: // the content of QmlIR::Object::functionsAndExpressions QHash<QQmlJSScope::ConstPtr, QList<QString>> m_functionsAndExpressions; - struct FunctionOrExpressionIdentifier + template <bool scopeIsConst = true> + struct ScopeAndNameT { - QQmlJSScope::ConstPtr scope; - QString name; - friend bool operator==(const FunctionOrExpressionIdentifier &x, - const FunctionOrExpressionIdentifier &y) + using Scope = std::conditional_t<scopeIsConst, QQmlJSScope::ConstPtr, QQmlJSScope::Ptr>; + + ScopeAndNameT() = default; + ScopeAndNameT(const Scope &scope, const QString &name) : scope(scope), name(name) { } + ScopeAndNameT(const ScopeAndNameT &) = default; + ScopeAndNameT(ScopeAndNameT &&) = default; + ScopeAndNameT &operator=(const ScopeAndNameT &) = default; + ScopeAndNameT &operator=(ScopeAndNameT &&) = default; + ~ScopeAndNameT() = default; + + // Create const from non-const + ScopeAndNameT(typename std::enable_if<scopeIsConst, ScopeAndNameT<false>>::type &nonConst) + : scope(nonConst.scope), name(nonConst.name) + { + } + + friend bool operator==(const ScopeAndNameT &lhs, const ScopeAndNameT &rhs) { - return x.scope == y.scope && x.name == y.name; + return lhs.scope == rhs.scope && lhs.name == rhs.name; } - friend bool operator!=(const FunctionOrExpressionIdentifier &x, - const FunctionOrExpressionIdentifier &y) + friend bool operator!=(const ScopeAndNameT &lhs, const ScopeAndNameT &rhs) { - return !(x == y); + return !(lhs == rhs); } - friend size_t qHash(const FunctionOrExpressionIdentifier &x, size_t seed = 0) + friend size_t qHash(const ScopeAndNameT &san, size_t seed = 0) { - return qHashMulti(seed, x.scope, x.name); + return qHashMulti(seed, san.scope, san.name); } + + Scope scope; + QString name; }; + using ConstScopeAndName = ScopeAndNameT<true>; + using ScopeAndName = ScopeAndNameT<false>; + + using FunctionOrExpressionIdentifier = ConstScopeAndName; + using Property = ConstScopeAndName; + using Alias = ConstScopeAndName; // tells whether last-processed UiScriptBinding is truly a script binding bool m_thisScriptBindingIsJavaScript = false; @@ -314,6 +336,8 @@ protected: QVector<QQmlJSScope::Ptr> m_objectDefinitionScopes; QHash<QQmlJSScope::Ptr, QVector<WithVisibilityScope<QString>>> m_propertyBindings; + QVector<Alias> m_aliasDefinitions; + QHash<Property, QList<Alias>> m_propertyAliases; QHash<QQmlJS::SourceLocation, QQmlJSMetaSignalHandler> m_signalHandlers; QSet<QQmlJSScope::ConstPtr> m_literalScopesToCheck; @@ -324,7 +348,9 @@ private: const QQmlJSScope::ConstPtr &signalScope, const QQmlJS::SourceLocation &location, const QString &handlerName, const QStringList &handlerParameters); void importBaseModules(); - void resolveAliasesAndIds(); + void resolveAliases(); + void populatePropertyAliases(); + void resolveGroupProperties(); void handleIdDeclaration(QQmlJS::AST::UiScriptBinding *scriptBinding); void visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr); diff --git a/src/qmlcompiler/qqmljsmetatypes_p.h b/src/qmlcompiler/qqmljsmetatypes_p.h index 33e5c08e42..a5b08b4fa0 100644 --- a/src/qmlcompiler/qqmljsmetatypes_p.h +++ b/src/qmlcompiler/qqmljsmetatypes_p.h @@ -328,6 +328,8 @@ class QQmlJSMetaProperty QString m_notify; QString m_privateClass; QString m_aliasExpr; + QString m_aliasTargetName; + QWeakPointer<const QQmlJSScope> m_aliasTargetScope; QWeakPointer<const QQmlJSScope> m_type; QVector<QQmlJSAnnotation> m_annotations; bool m_isList = false; @@ -385,6 +387,18 @@ public: QString aliasExpression() const { return m_aliasExpr; } bool isAlias() const { return !m_aliasExpr.isEmpty(); } // exists for convenience + void setAliasTargetName(const QString &name) { m_aliasTargetName = name; } + QString aliasTargetName() const { return m_aliasTargetName; } + + void setAliasTargetScope(const QSharedPointer<const QQmlJSScope> &scope) + { + m_aliasTargetScope = scope; + } + QSharedPointer<const QQmlJSScope> aliasTargetScope() const + { + return m_aliasTargetScope.toStrongRef(); + } + void setIsFinal(bool isFinal) { m_isFinal = isFinal; } bool isFinal() const { return m_isFinal; } diff --git a/src/qmlcompiler/qqmljsscope.cpp b/src/qmlcompiler/qqmljsscope.cpp index fcb6fab857..5067210261 100644 --- a/src/qmlcompiler/qqmljsscope.cpp +++ b/src/qmlcompiler/qqmljsscope.cpp @@ -674,7 +674,7 @@ void QQmlJSScope::resolveList(const QQmlJSScope::Ptr &self, const QQmlJSScope::C self->m_listType = listType; } -void QQmlJSScope::resolveGeneralizedGroup( +void QQmlJSScope::resolveGroup( const Ptr &self, const ConstPtr &baseType, const QQmlJSScope::ContextualTypes &contextualTypes, QSet<QString> *usedTypes) { @@ -1200,6 +1200,29 @@ QQmlJSScope::InlineComponentOrDocumentRootName QQmlJSScope::enclosingInlineCompo return RootDocumentNameType(); } +QVector<QQmlJSScope::ConstPtr> QQmlJSScope::childScopes() const +{ + QVector<QQmlJSScope::ConstPtr> result; + result.reserve(m_childScopes.size()); + for (const auto &child : m_childScopes) + result.append(child); + return result; +} + +QVector<QQmlJSScope::ConstPtr> QQmlJSScope::descendantScopes() const +{ + QVector<QQmlJSScope::ConstPtr> descendants; + QVector<QQmlJSScope::ConstPtr> toVisit(m_childScopes.cbegin(), m_childScopes.cend()); + + while (!toVisit.isEmpty()) { + QQmlJSScope::ConstPtr scope = toVisit.takeLast(); + descendants << scope; + toVisit << scope->childScopes(); + } + + return descendants; +} + /*! \internal diff --git a/src/qmlcompiler/qqmljsscope_p.h b/src/qmlcompiler/qqmljsscope_p.h index 8dfbebbd06..ce7eeb5f6f 100644 --- a/src/qmlcompiler/qqmljsscope_p.h +++ b/src/qmlcompiler/qqmljsscope_p.h @@ -596,14 +596,8 @@ QT_WARNING_POP return m_childScopes; } - QVector<QQmlJSScope::ConstPtr> childScopes() const - { - QVector<QQmlJSScope::ConstPtr> result; - result.reserve(m_childScopes.size()); - for (const auto &child : m_childScopes) - result.append(child); - return result; - } + QVector<QQmlJSScope::ConstPtr> childScopes() const; + QVector<QQmlJSScope::ConstPtr> descendantScopes() const; static QTypeRevision resolveTypes( const Ptr &self, const QQmlJSScope::ContextualTypes &contextualTypes, @@ -615,7 +609,7 @@ QT_WARNING_POP const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &intType); static void resolveList( const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &arrayType); - static void resolveGeneralizedGroup( + static void resolveGroup( const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &baseType, const QQmlJSScope::ContextualTypes &contextualTypes, QSet<QString> *usedTypes = nullptr); diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp index e938e622f7..06b49d067d 100644 --- a/src/qmlcompiler/qqmljstypepropagator.cpp +++ b/src/qmlcompiler/qqmljstypepropagator.cpp @@ -2403,6 +2403,11 @@ QQmlJSRegisterContent QQmlJSTypePropagator::checkedInputRegister(int reg) if (regIt == m_state.registers.end()) { if (isArgument(reg)) return argumentType(reg); + if (reg == This) + return m_typeResolver->globalType(m_function->qmlScope); + // over-approximation: needed in qmllint to not crash on `eval()`-calls + if (reg == NewTarget) + return m_typeResolver->globalType(m_typeResolver->varType()); setError(u"Type error: could not infer the type of an expression"_s); return {}; diff --git a/src/qmldom/qqmldomelements.cpp b/src/qmldom/qqmldomelements.cpp index 83d41d04bc..1b66542fba 100644 --- a/src/qmldom/qqmldomelements.cpp +++ b/src/qmldom/qqmldomelements.cpp @@ -1182,6 +1182,11 @@ std::shared_ptr<ScriptExpression> Binding::scriptExpressionValue() return nullptr; } +void Binding::setValue(std::unique_ptr<BindingValue> &&value) +{ + m_value = std::move(value); +} + Path Binding::addAnnotation(Path selfPathFromOwner, const QmlObject &annotation, QmlObject **aPtr) { return appendUpdatableElementInQList(selfPathFromOwner.field(Fields::annotations), diff --git a/src/qmldom/qqmldomelements_p.h b/src/qmldom/qqmldomelements_p.h index 75ced47f5b..a6bf60334f 100644 --- a/src/qmldom/qqmldomelements_p.h +++ b/src/qmldom/qqmldomelements_p.h @@ -532,7 +532,7 @@ public: std::shared_ptr<ScriptExpression> scriptExpressionValue(); QList<QmlObject> annotations() const { return m_annotations; } void setAnnotations(QList<QmlObject> annotations) { m_annotations = annotations; } - void setValue(std::unique_ptr<BindingValue> &&value) { m_value = std::move(value); } + void setValue(std::unique_ptr<BindingValue> &&value); Path addAnnotation(Path selfPathFromOwner, const QmlObject &a, QmlObject **aPtr = nullptr); const RegionComments &comments() const { return m_comments; } RegionComments &comments() { return m_comments; } diff --git a/src/qmldom/qqmldomtop.cpp b/src/qmldom/qqmldomtop.cpp index 3c1670f09f..c88449a11f 100644 --- a/src/qmldom/qqmldomtop.cpp +++ b/src/qmldom/qqmldomtop.cpp @@ -1613,24 +1613,24 @@ std::shared_ptr<ModuleIndex> DomEnvironment::moduleIndexWithUri(DomItem &self, Q // use the overload which does not care about changing m_moduleIndexWithUri to find a candidate - auto [candidate, origin] = moduleIndexWithUriHelper(self, uri, majorVersion, options); + auto candidate = moduleIndexWithUriHelper(self, uri, majorVersion, options); // A ModuleIndex from m_moduleIndexWithUri can always be returned - if (candidate && origin == ModuleLookupResult::FromGlobal) - return candidate; + if (candidate.module && candidate.fromBase == ModuleLookupResult::FromGlobal) + return std::move(candidate.module); // If we don't want to modify anything, return the candidate that we have found (if any) if (changeable == Changeable::ReadOnly) - return candidate; + return std::move(candidate.module); // Else we want to create a modifyable version - std::shared_ptr<ModuleIndex> newModulePtr = [&, candidate = candidate](){ + std::shared_ptr<ModuleIndex> newModulePtr = [&] { // which is a completely new module in case we don't have candidate - if (!candidate) + if (!candidate.module) return std::make_shared<ModuleIndex>(uri, majorVersion); // or a copy of the candidate otherwise - DomItem existingModObj = self.copy(candidate); - return candidate->makeCopy(existingModObj); + DomItem existingModObj = self.copy(candidate.module); + return candidate.module->makeCopy(existingModObj); }(); DomItem newModule = self.copy(newModulePtr); diff --git a/src/qmlmodels/qqmllistmodel.cpp b/src/qmlmodels/qqmllistmodel.cpp index 2ab8a1795b..7bca0b8f7e 100644 --- a/src/qmlmodels/qqmllistmodel.cpp +++ b/src/qmlmodels/qqmllistmodel.cpp @@ -3013,8 +3013,9 @@ bool QQmlListModelParser::definesEmptyList(const QString &s) The names used for roles must begin with a lower-case letter and should be common to all elements in a given model. Values must be simple constants; either - strings (quoted and optionally within a call to QT_TR_NOOP), boolean values - (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter). + strings (quoted and optionally within a call to + \l [QML] {Qt::} {QT_TR_NOOP()}, boolean values (true, false), numbers, or + enumeration values (such as AlignText.AlignHCenter). Beginning with Qt 5.11 ListElement also allows assigning a function declaration to a role. This allows the definition of ListElements with callable actions. diff --git a/src/qmltyperegistrar/qmetatypesjsonprocessor.cpp b/src/qmltyperegistrar/qmetatypesjsonprocessor.cpp index 2b0faa8f4d..84fa9022c3 100644 --- a/src/qmltyperegistrar/qmetatypesjsonprocessor.cpp +++ b/src/qmltyperegistrar/qmetatypesjsonprocessor.cpp @@ -318,9 +318,18 @@ void MetaTypesJsonProcessor::sortTypes(QVector<QJsonObject> &types) QString MetaTypesJsonProcessor::resolvedInclude(const QString &include) { - return (m_privateIncludes && include.endsWith(QLatin1String("_p.h"))) - ? QLatin1String("private/") + include - : include; + if (!m_privateIncludes) + return include; + + if (include.endsWith(QLatin1String("_p.h"))) + return QLatin1String("private/") + include; + + if (include.startsWith(QLatin1String("qplatform")) + || include.startsWith(QLatin1String("qwindowsystem"))) { + return QLatin1String("qpa/") + include; + } + + return include; } void MetaTypesJsonProcessor::processTypes(const QJsonObject &types) diff --git a/src/quick/doc/snippets/pointerHandlers/wheelHandlerAcceptedModifiers.qml b/src/quick/doc/snippets/pointerHandlers/wheelHandlerAcceptedModifiers.qml new file mode 100644 index 0000000000..1a8db7b636 --- /dev/null +++ b/src/quick/doc/snippets/pointerHandlers/wheelHandlerAcceptedModifiers.qml @@ -0,0 +1,20 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +//![entire] +import QtQuick + +Rectangle { + width: 170; height: 120 + color: "green"; antialiasing: true + + WheelHandler { + property: "rotation" + acceptedModifiers: Qt.ControlModifier + } + + WheelHandler { + property: "scale" + acceptedModifiers: Qt.NoModifier + } +} +//![entire] diff --git a/src/quick/doc/snippets/pointerHandlers/wheelHandlerMargin.qml b/src/quick/doc/snippets/pointerHandlers/wheelHandlerMargin.qml new file mode 100644 index 0000000000..82a4fd744b --- /dev/null +++ b/src/quick/doc/snippets/pointerHandlers/wheelHandlerMargin.qml @@ -0,0 +1,15 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +//![entire] +import QtQuick + +Rectangle { + width: 170; height: 120 + color: "green"; antialiasing: true + + WheelHandler { + property: "rotation" + margin: 10 + } +} +//![entire] diff --git a/src/quick/doc/src/concepts/input/focus.qdoc b/src/quick/doc/src/concepts/input/focus.qdoc index 8c6f73024b..9f5a97101f 100644 --- a/src/quick/doc/src/concepts/input/focus.qdoc +++ b/src/quick/doc/src/concepts/input/focus.qdoc @@ -76,7 +76,7 @@ The MyWidget code: \snippet qml/focus/MyWidget.qml mywidget We want the first \c MyWidget object to have the focus, so we set its -\c focus property to \c true. However, by running the code, we can confirm that +\c focus property to \c true. However, by running the code, it can happen that the second widget receives the focus. \image declarative-qmlfocus2.png @@ -85,9 +85,9 @@ Looking at both \c MyWidget and \c window code, the problem is evident - there are three types that set the \c focus property to \c true. The two \c {MyWidget}s set the \c focus to \c true and the \c window component also sets the focus. Ultimately, only one type can have keyboard focus, and the system has -to decide which type receives the focus. When the second \c MyWidget is created, -it receives the focus because it is the last type to set its \c focus -property to \c true. +to decide which type receives the focus. Since QML does not guarantee which element +will have its properties initialized first, it might be that the last \c MyWidget +gets the initial focus. This problem is due to visibility. The \c MyWidget component would like to have the focus, but it cannot control the focus when it is imported or reused. diff --git a/src/quick/doc/src/qmltypereference.qdoc b/src/quick/doc/src/qmltypereference.qdoc index 790a1ddc8c..1c9f7d8dc9 100644 --- a/src/quick/doc/src/qmltypereference.qdoc +++ b/src/quick/doc/src/qmltypereference.qdoc @@ -144,15 +144,15 @@ available when you import \c QtQuick. The following properties are also available: \list - \li \l enumeration \c font.weight + \li \l {QML Enumerations}{enumeration} \c font.weight \li \l bool \c font.overline \li \l bool \c font.strikeout - \li \l enumeration \c font.capitalization + \li \l {QML Enumerations}{enumeration} \c font.capitalization \li \l real \c font.letterSpacing \li \l real \c font.wordSpacing \li \l bool \c font.kerning \li \l bool \c font.preferShaping - \li \l enumeration \c font.hintingPreference + \li \l {QML Enumerations}{enumeration} \c font.hintingPreference \li \l string \c font.styleName \endlist diff --git a/src/quick/handlers/qquickwheelhandler.cpp b/src/quick/handlers/qquickwheelhandler.cpp index b7a42904c6..b051362e2b 100644 --- a/src/quick/handlers/qquickwheelhandler.cpp +++ b/src/quick/handlers/qquickwheelhandler.cpp @@ -517,8 +517,16 @@ QMetaProperty &QQuickWheelHandlerPrivate::targetMetaProperty() const return metaProperty; } +/*! \internal + \qmlproperty flags QtQuick::WheelHandler::acceptedButtons + + This overrides QtQuick::PointerDeviceHandler::acceptedButtons + and hides it from the documentation as the property is not relevant for + WheelHandler. +*/ + /*! - \qmlproperty flags WheelHandler::acceptedDevices + \qmlproperty flags QtQuick::WheelHandler::acceptedDevices The types of pointing devices that can activate this handler. @@ -536,6 +544,108 @@ QMetaProperty &QQuickWheelHandlerPrivate::targetMetaProperty() const \c acceptedDevices remains set to its default value. */ +/*! + \qmlproperty flags QtQuick::WheelHandler::acceptedModifiers + + If this property is set, it will require the given keyboard modifiers to + be pressed in order to react to wheel events, and otherwise ignore them. + + If this property is set to \c Qt.KeyboardModifierMask (the default value), + the WheelHandler ignores the modifier keys. + + For example, an \l [QML] Item could have two handlers, one of which is + enabled only if the required keyboard modifier is pressed, while the other + ignores events if any modifier is pressed: + + \snippet pointerHandlers/wheelHandlerAcceptedModifiers.qml entire + + 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 +*/ + +/*! \internal + \qmlproperty flags QtQuick::WheelHandler::acceptedPointerTypes + + This overrides QtQuick::PointerDeviceHandler::acceptedPointerTypes + and hides it from the documentation as the property is not relevant for + WheelHandler. +*/ + +/*! + \readonly + \qmlproperty bool QtQuick::WheelHandler::active + + This holds \c true whenever the WheelHandler has recently seen a + QWheelEvent, is keeping its properties up-to-date, and actively manipulating + its \l target (if any). + + \sa activeTimeout +*/ + +/*! \internal + \qmlproperty flags QtQuick::WheelHandler::cursorShape + + This overrides QtQuick::PointerHandler::cursorShape + and hides it from the documentation as the property is not relevant for + WheelHandler. +*/ + +/*! \internal + \qmlproperty flags QtQuick::WheelHandler::dragThreshold + + This overrides QtQuick::PointerHandler::dragThreshold + and hides it from the documentation as the property is not relevant for + WheelHandler. +*/ + +/*! \internal + \qmlproperty flags QtQuick::WheelHandler::grabPermissions + + This overrides QtQuick::PointerHandler::grabPermissions + and hides it from the documentation as the property is not relevant for + WheelHandler. +*/ + +/*! + \qmlproperty real QtQuick::WheelHandler::margin + + The margin beyond the bounds of the \l {PointerHandler::parent}{parent} + item within which the WheelHandler can react. For example if \c margin + is set to \c 10, you could place the cursor up to 10 pixels outside the + visible edge of the item, and it will still react to the wheel: + + \snippet pointerHandlers/wheelHandlerMargin.qml entire + + The default value is \c 0. +*/ + +/*! \internal + \qmlsignal QtQuick::WheelHandler::grabChanged(PointerDevice::GrabTransition transition, eventPoint point) + + This overrides QtQuick::PointerHandler::grabChanged + and hides it from the documentation as the signal is not relevant for + WheelHandler. +*/ + +/*! \internal + \qmlsignal QtQuick::WheelHandler::canceled(eventPoint point) + + This overrides QtQuick::PointerHandler::canceled + and hides it from the documentation as the signal is not relevant for + WheelHandler. +*/ + QT_END_NAMESPACE #include "moc_qquickwheelhandler_p.cpp" diff --git a/src/quick/items/context2d/qquickcontext2d.cpp b/src/quick/items/context2d/qquickcontext2d.cpp index 0ce3ef879b..362f37f509 100644 --- a/src/quick/items/context2d/qquickcontext2d.cpp +++ b/src/quick/items/context2d/qquickcontext2d.cpp @@ -3927,7 +3927,7 @@ void QQuickContext2D::addArcTo(const QPointF& p1, const QPointF& p2, qreal radiu // The points p0, p1, and p2 are on the same straight line (HTML5, 4.8.11.1.8) // We could have used areCollinear() here, but since we're reusing // the variables computed above later on we keep this logic. - if (qFuzzyCompare(std::abs(cos_phi), 1.0)) { + if (qFuzzyCompare(std::abs(cos_phi), qreal(1.0))) { m_path.lineTo(p1); return; } diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index 82587a178a..ec339ccc3b 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -6316,7 +6316,7 @@ qreal QQuickItem::opacity() const void QQuickItem::setOpacity(qreal newOpacity) { Q_D(QQuickItem); - qreal o = qBound<qreal>(0, newOpacity, 1); + qreal o = std::clamp(newOpacity, qreal(0.0), qreal(1.0)); if (d->opacity() == o) return; diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp index 063c48260a..7c5ef85566 100644 --- a/src/quick/items/qquicklistview.cpp +++ b/src/quick/items/qquicklistview.cpp @@ -1704,18 +1704,23 @@ void QQuickListViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExte break; } } - FxViewItem *topItem = snapItemAt(tempPosition + snapOffset + highlightRangeStart); - if (strictHighlightRange && currentItem && (!topItem || (topItem->index != currentIndex && fixupMode == Immediate))) { - // StrictlyEnforceRange always keeps an item in range + + // If there are pending changes, the item returned from snapItemAt might get deleted as + // soon as applyPendingChanges() is called (from e.g. updateHighlight()). + // Therefore, apply the pending changes before we call snapItemAt() + if (strictHighlightRange) updateHighlight(); - topItem = currentItem; - } + + FxViewItem *topItem = snapItemAt(tempPosition + snapOffset + highlightRangeStart); FxViewItem *bottomItem = snapItemAt(tempPosition + snapOffset + highlightRangeEnd); - if (strictHighlightRange && currentItem && (!bottomItem || (bottomItem->index != currentIndex && fixupMode == Immediate))) { + if (strictHighlightRange && currentItem) { // StrictlyEnforceRange always keeps an item in range - updateHighlight(); - bottomItem = currentItem; + if (!topItem || (topItem->index != currentIndex && fixupMode == Immediate)) + topItem = currentItem; + if (!bottomItem || (bottomItem->index != currentIndex && fixupMode == Immediate)) + bottomItem = currentItem; } + qreal pos = 0; bool isInBounds = -position() > maxExtent && -position() <= minExtent; diff --git a/src/quick/items/qquickpalette.cpp b/src/quick/items/qquickpalette.cpp index 824a0bf096..2563bec022 100644 --- a/src/quick/items/qquickpalette.cpp +++ b/src/quick/items/qquickpalette.cpp @@ -45,7 +45,7 @@ static constexpr bool is_valid(QPalette::ColorGroup cg) noexcept /*! \qmltype Palette \instantiates QQuickPalette - \inherits QQuickColorGroup + \inherits ColorGroup \inqmlmodule QtQuick \ingroup qtquick-visual \brief Contains color groups for each QML item state. diff --git a/src/quick/items/qquickselectable_p.h b/src/quick/items/qquickselectable_p.h index 726352719f..03c6cca717 100644 --- a/src/quick/items/qquickselectable_p.h +++ b/src/quick/items/qquickselectable_p.h @@ -23,6 +23,8 @@ QT_BEGIN_NAMESPACE class Q_QUICK_PRIVATE_EXPORT QQuickSelectable { public: + virtual ~QQuickSelectable(); + virtual QQuickItem *selectionPointerHandlerTarget() const = 0; virtual bool startSelection(const QPointF &pos) = 0; diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 1b564df125..3cc63cece0 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1413,6 +1413,8 @@ QT_BEGIN_NAMESPACE +QQuickSelectable::~QQuickSelectable() { } + Q_LOGGING_CATEGORY(lcTableViewDelegateLifecycle, "qt.quick.tableview.lifecycle") #define Q_TABLEVIEW_UNREACHABLE(output) { dumpTable(); qWarning() << "output:" << output; Q_UNREACHABLE(); } @@ -4550,7 +4552,7 @@ void QQuickTableViewPrivate::positionViewAtRow(int row, Qt::Alignment alignment, Qt::Alignment verticalAlignment = alignment & (Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom); Q_TABLEVIEW_ASSERT(verticalAlignment, alignment); - if (syncHorizontally) { + if (syncVertically) { syncView->d_func()->positionViewAtRow(row, verticalAlignment, offset, subRect); } else { if (!scrollToRow(row, verticalAlignment, offset, subRect)) { @@ -4570,7 +4572,7 @@ void QQuickTableViewPrivate::positionViewAtColumn(int column, Qt::Alignment alig Qt::Alignment horizontalAlignment = alignment & (Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight); Q_TABLEVIEW_ASSERT(horizontalAlignment, alignment); - if (syncVertically) { + if (syncHorizontally) { syncView->d_func()->positionViewAtColumn(column, horizontalAlignment, offset, subRect); } else { if (!scrollToColumn(column, horizontalAlignment, offset, subRect)) { diff --git a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp index c9385630d9..bf195a90b3 100644 --- a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp +++ b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp @@ -123,6 +123,10 @@ QRegion QSGAbstractSoftwareRenderer::optimizeRenderList() // Objective is to update the dirty status and rects. for (auto i = m_renderableNodes.rbegin(); i != m_renderableNodes.rend(); ++i) { auto node = *i; + // Track the original version of isDirty() as it can change if + // when we subtract dirty regions but still need to mark the previously + // dirty region as dirty. + const bool wasDirty = node->isDirty(); if (!m_dirtyRegion.isEmpty()) { // See if the current dirty regions apply to the current node node->addDirtyRegion(m_dirtyRegion, true); @@ -156,7 +160,11 @@ QRegion QSGAbstractSoftwareRenderer::optimizeRenderList() // if isAlpha, add node's dirty rect to m_dirtyRegion m_dirtyRegion += node->dirtyRegion(); } - // if previousDirtyRegion has content outside of boundingRect add to m_dirtyRegion + } + + if (wasDirty) { + // If this node started out dirty, make sure its previous region is + // added to the dirty region so that it gets cleared properly. QRegion prevDirty = node->previousDirtyRegion(); if (!prevDirty.isNull()) m_dirtyRegion += prevDirty; diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp index 3d5a6c7705..9249b5377f 100644 --- a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp +++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp @@ -21,10 +21,6 @@ QT_BEGIN_NAMESPACE -#ifndef QT_NO_DEBUG -Q_QUICK_PRIVATE_EXPORT bool qsg_test_and_clear_material_failure(); -#endif - int qt_sg_envInt(const char *name, int defaultValue); namespace QSGBatchRenderer @@ -3173,19 +3169,6 @@ bool Renderer::prepareRenderMergedBatch(Batch *batch, PreparedRenderBatch *rende if (directUpdatePtr) batch->ubuf->endFullDynamicBufferUpdateForCurrentFrame(); -#ifndef QT_NO_DEBUG - if (qsg_test_and_clear_material_failure()) { - qDebug("QSGMaterial::updateState triggered an error (merged), batch will be skipped:"); - Element *ee = e; - while (ee) { - qDebug() << " -" << ee->node; - ee = ee->nextInBatch; - } - QSGNodeDumper::dump(rootNode()); - qFatal("Aborting: scene graph is invalid..."); - } -#endif - m_gstate.drawMode = QSGGeometry::DrawingMode(g->drawingMode()); m_gstate.lineWidth = g->lineWidth(); @@ -3381,16 +3364,6 @@ bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *ren QSGMaterialShader::RenderState renderState = state(QSGMaterialShader::RenderState::DirtyStates(int(dirty))); updateMaterialDynamicData(sms, renderState, material, batch, e, ubufOffset, ubufSize, directUpdatePtr); -#ifndef QT_NO_DEBUG - if (qsg_test_and_clear_material_failure()) { - qDebug("QSGMaterial::updateState() triggered an error (unmerged), batch will be skipped:"); - qDebug() << " - offending node is" << e->node; - QSGNodeDumper::dump(rootNode()); - qFatal("Aborting: scene graph is invalid..."); - return false; - } -#endif - ubufOffset += aligned(ubufSize, m_ubufAlignment); const QSGGeometry::DrawingMode prevDrawMode = m_gstate.drawMode; diff --git a/src/quick/scenegraph/coreapi/qsgmaterial.cpp b/src/quick/scenegraph/coreapi/qsgmaterial.cpp index 408cb0e1ff..bdb65c846c 100644 --- a/src/quick/scenegraph/coreapi/qsgmaterial.cpp +++ b/src/quick/scenegraph/coreapi/qsgmaterial.cpp @@ -6,21 +6,6 @@ QT_BEGIN_NAMESPACE -#ifndef QT_NO_DEBUG -bool qsg_material_failure = false; -bool qsg_test_and_clear_material_failure() -{ - bool fail = qsg_material_failure; - qsg_material_failure = false; - return fail; -} - -void qsg_set_material_failure() -{ - qsg_material_failure = true; -} -#endif - /*! \group qtquick-scenegraph-materials \title Qt Quick Scene Graph Material Classes diff --git a/src/quick/scenegraph/coreapi/qsgnode.cpp b/src/quick/scenegraph/coreapi/qsgnode.cpp index 8569f9d0fe..a0f7957752 100644 --- a/src/quick/scenegraph/coreapi/qsgnode.cpp +++ b/src/quick/scenegraph/coreapi/qsgnode.cpp @@ -7,6 +7,8 @@ #include "qsgnodeupdater_p.h" #include "qsgmaterial.h" +#include <algorithm> + #include "limits.h" QT_BEGIN_NAMESPACE @@ -1309,7 +1311,7 @@ const qreal OPACITY_THRESHOLD = 0.001; void QSGOpacityNode::setOpacity(qreal opacity) { - opacity = qBound<qreal>(0, opacity, 1); + opacity = std::clamp(opacity, qreal(0.0), qreal(1.0)); if (m_opacity == opacity) return; DirtyState dirtyState = DirtyOpacity; diff --git a/src/quick/scenegraph/qsgcontext.cpp b/src/quick/scenegraph/qsgcontext.cpp index 7668db7a69..2e02713b93 100644 --- a/src/quick/scenegraph/qsgcontext.cpp +++ b/src/quick/scenegraph/qsgcontext.cpp @@ -142,7 +142,7 @@ public: { m_time = 0; m_timer.start(); - m_wallTime.restart(); + m_wallTime.start(); QAnimationDriver::start(); } @@ -184,7 +184,7 @@ public: if (m_lag > 10 && m_bad > 2) { m_mode = TimerMode; qCDebug(QSG_LOG_INFO, "animation driver switched to timer mode"); - m_wallTime.restart(); + m_wallTime.start(); } } else { m_lag = 0; @@ -265,7 +265,7 @@ public: void start() override { - m_wallTime.restart(); + m_wallTime.start(); QAnimationDriver::start(); } diff --git a/src/quick/scenegraph/qsgrhilayer.cpp b/src/quick/scenegraph/qsgrhilayer.cpp index 5299cb54ce..77ad687e61 100644 --- a/src/quick/scenegraph/qsgrhilayer.cpp +++ b/src/quick/scenegraph/qsgrhilayer.cpp @@ -115,7 +115,16 @@ void QSGRhiLayer::setSize(const QSize &pixelSize) if (pixelSize == m_pixelSize) return; - m_pixelSize = pixelSize; + const int textureSizeMax = m_rhi->resourceLimit(QRhi::TextureSizeMax); + m_pixelSize = pixelSize.boundedTo(QSize(textureSizeMax, textureSizeMax)); + + if (Q_UNLIKELY(m_pixelSize != pixelSize)) { + qWarning("QSGRhiLayer: Unsupported size requested: [%d, %d]. " + "Maximum texture size: %d", + pixelSize.width(), + pixelSize.height(), + textureSizeMax); + } if (m_live && m_pixelSize.isNull()) releaseResources(); diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp index 1b33ae8f39..d2a160c420 100644 --- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp +++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp @@ -234,8 +234,8 @@ public: , stopEventProcessing(false) { sgrc = static_cast<QSGDefaultRenderContext *>(renderContext); -#if (defined(Q_OS_QNX) && defined(Q_PROCESSOR_X86)) || defined(Q_OS_INTEGRITY) - // The SDP 6.6.0 x86 MESA driver requires a larger stack than the default. +#if defined(Q_OS_QNX) || defined(Q_OS_INTEGRITY) + // The render thread requires a larger stack than the default (256k). setStackSize(1024 * 1024); #endif } diff --git a/src/quick/util/qquickforeignutils.cpp b/src/quick/util/qquickforeignutils.cpp index 2dfe9fb5aa..34b3225abe 100644 --- a/src/quick/util/qquickforeignutils.cpp +++ b/src/quick/util/qquickforeignutils.cpp @@ -40,7 +40,7 @@ QT_BEGIN_NAMESPACE \li \l point \c eventPoint.scenePosition: see also \l QEventPoint::scenePosition \li \l ulong \c eventPoint.pressTimestamp: see also \l QEventPoint::pressTimestamp \li \l point \c eventPoint.scenePressPosition: see also \l QEventPoint::scenePressPosition - \li \l enumeration \c eventPoint.state: see also \l QEventPoint::state + \li \l {QML Enumerations}{enumeration} \c eventPoint.state: see also \l QEventPoint::state \li \l real \c eventPoint.timeHeld: see also \l QEventPoint::timeHeld \li \l ulong \c eventPoint.timestamp: see also \l QEventPoint::timestamp \li \l pointingDeviceUniqueId \c eventPoint.uniqueId: see also \l QEventPoint::uniqueId diff --git a/src/quick/util/qquickpath.cpp b/src/quick/util/qquickpath.cpp index 7562b74d46..17529317d5 100644 --- a/src/quick/util/qquickpath.cpp +++ b/src/quick/util/qquickpath.cpp @@ -1982,6 +1982,8 @@ void QQuickPathArc::addToPath(QPainterPath &path, const QQuickPathData &data) { const QPointF &startPoint = path.currentPosition(); const QPointF &endPoint = positionForCurve(data, startPoint); + if (startPoint == endPoint) + return; QQuickSvgParser::pathArc(path, _radiusX, _radiusY, diff --git a/src/quickcontrols/ios/Dial.qml b/src/quickcontrols/ios/Dial.qml index 41ce35caf2..303b791004 100644 --- a/src/quickcontrols/ios/Dial.qml +++ b/src/quickcontrols/ios/Dial.qml @@ -15,40 +15,41 @@ T.Dial { implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) + // The handle extends outside background bounds, + // so we need to include that in the insets calculation leftInset: handle ? handle.width / 2 : 0 rightInset: handle ? handle.width / 2 : 0 topInset: handle ? handle.height / 2 : 0 bottomInset: handle ? handle.height / 2 : 0 background: Item { - implicitWidth: 104 - implicitHeight: 104 - x: control.leftInset + (control.availableWidth - width) / 2 - y: control.topInset + (control.availableHeight - height) / 2 + implicitWidth: _groove.implicitWidth + implicitHeight: _groove.implicitHeight - Rectangle { + readonly property int _strokeWidth: 4 + + readonly property Rectangle _groove: Rectangle { + parent: control.background x: (parent.width - width) / 2 y: (parent.height - height) / 2 - implicitWidth: parent.implicitWidth - implicitHeight: parent.implicitHeight - width: Math.max(50, Math.min(control.background.width, control.background.height)) + implicitWidth: 104 + implicitHeight: 104 + width: Math.min(parent.width, parent.height) height: width color: "transparent" border.color: control.palette.mid - border.width: 4 + border.width: control.background._strokeWidth radius: width * 0.5 z: -1 - - opacity: control.enabled? 1 : 0.5 + opacity: control.enabled ? 1 : 0.5 } - Shape { - x: (parent.width - width) / 2 - y: (parent.height - height) / 2 - implicitWidth: parent.implicitWidth - implicitHeight: parent.implicitHeight - width: Math.max(50, Math.min(control.background.width, control.background.height)) - height: width + readonly property Shape _track: Shape { + parent: control.background + x: control.background._groove.x + y: control.background._groove.y + width: control.background._groove.width + height: control.background._groove.height layer.enabled: true layer.samples: 4 @@ -56,13 +57,12 @@ T.Dial { fillColor: "transparent" strokeColor: control.palette.button strokeWidth: 4 - capStyle: ShapePath.RoundCap PathAngleArc { - centerX: control.background.children[0].width / 2 - centerY: control.background.children[0].height / 2 - radiusX: control.background.children[0].width / 2 - 2 + centerX: control.background._track.width / 2 + centerY: control.background._track.height / 2 + radiusX: (control.background._track.width - control.background._strokeWidth) / 2 radiusY: radiusX startAngle: -230 sweepAngle: 140 + control.angle @@ -74,13 +74,16 @@ T.Dial { handle: Item { height: dialHandle.height - dialHandle.topInset - dialHandle.bottomInset width: dialHandle.width - dialHandle.rightInset - dialHandle.leftInset - x: control.background.x + control.background.width / 2 - width / 2 - y: control.background.y + control.background.height / 2 - height / 2 + x: control.background ? control.background.x + (control.background.width - width) / 2 : 0 + y: control.background ? control.background.y + (control.background.height - height) / 2 : 0 transform: [ Translate { - x: Math.cos((angle - 90) * Math.PI / 180) * Math.min(control.background.width, control.background.height) * 0.5; - y: Math.sin((angle - 90) * Math.PI / 180) * Math.min(control.background.width, control.background.height) * 0.5; + readonly property real radius: !control.background ? 0 + : (Math.min(control.background.width, control.background.height) + - control.background._strokeWidth) / 2 + x: Math.cos((angle - 90) * Math.PI / 180) * radius + y: Math.sin((angle - 90) * Math.PI / 180) * radius } ] diff --git a/src/quickcontrols/material/impl/qquickmaterialtextcontainer.cpp b/src/quickcontrols/material/impl/qquickmaterialtextcontainer.cpp index ab07c9d9b2..53f8fe8618 100644 --- a/src/quickcontrols/material/impl/qquickmaterialtextcontainer.cpp +++ b/src/quickcontrols/material/impl/qquickmaterialtextcontainer.cpp @@ -308,7 +308,7 @@ void QQuickMaterialTextContainer::paint(QPainter *painter) // Draw the focus line at the bottom for filled containers. if (m_filled) { - if (!qFuzzyCompare(m_focusAnimationProgress, 1.0)) { + if (!qFuzzyCompare(m_focusAnimationProgress, qreal(1.0))) { // Draw the enabled active indicator line (#10) that's at the bottom when it's not focused: // https://m3.material.io/components/text-fields/specs#6d654d1d-262e-4697-858c-9a75e8e7c81d // Don't bother drawing it when the animation has finished, as the focused active indicator diff --git a/src/quickcontrols/material/qquickmaterialstyle.cpp b/src/quickcontrols/material/qquickmaterialstyle.cpp index f8ced5821b..ffa877b2b3 100644 --- a/src/quickcontrols/material/qquickmaterialstyle.cpp +++ b/src/quickcontrols/material/qquickmaterialstyle.cpp @@ -10,6 +10,8 @@ #include <QtQml/qqmlinfo.h> #include <QtQuickControls2/private/qquickstyle_p.h> +#include <algorithm> + QT_BEGIN_NAMESPACE static const QRgb colors[][14] = { @@ -1184,14 +1186,16 @@ QColor QQuickMaterialStyle::color(QQuickMaterialStyle::Color color, QQuickMateri static QColor lighterShade(const QColor &color, qreal amount) { QColor hsl = color.toHsl(); - hsl.setHslF(hsl.hueF(), hsl.saturationF(), qBound<qreal>(0.0, hsl.lightnessF() + amount, 1.0), color.alphaF()); + hsl.setHslF(hsl.hueF(), hsl.saturationF(), + std::clamp(hsl.lightnessF() + amount, qreal(0.0), qreal(1.0)), color.alphaF()); return hsl.convertTo(color.spec()); } static QColor darkerShade(const QColor &color, qreal amount) { QColor hsl = color.toHsl(); - hsl.setHslF(hsl.hueF(), hsl.saturationF(), qBound<qreal>(0.0, hsl.lightnessF() - amount, 1.0), color.alphaF()); + hsl.setHslF(hsl.hueF(), hsl.saturationF(), + std::clamp(hsl.lightnessF() - amount, qreal(0.0), qreal(1.0)), color.alphaF()); return hsl.convertTo(color.spec()); } diff --git a/src/quickcontrolsimpl/qquickanimatednode.cpp b/src/quickcontrolsimpl/qquickanimatednode.cpp index 542955235b..95d6ab0457 100644 --- a/src/quickcontrolsimpl/qquickanimatednode.cpp +++ b/src/quickcontrolsimpl/qquickanimatednode.cpp @@ -31,7 +31,7 @@ int QQuickAnimatedNode::currentTime() const void QQuickAnimatedNode::setCurrentTime(int time) { m_currentTime = time; - m_timer.restart(); + m_timer.start(); } int QQuickAnimatedNode::duration() const @@ -71,7 +71,7 @@ void QQuickAnimatedNode::start(int duration) m_running = true; m_currentLoop = 0; - m_timer.restart(); + m_timer.start(); if (duration > 0) m_duration = duration; diff --git a/src/quickdialogs/quickdialogs/qquickabstractdialog.cpp b/src/quickdialogs/quickdialogs/qquickabstractdialog.cpp index 101870ec84..ffbf5da45f 100644 --- a/src/quickdialogs/quickdialogs/qquickabstractdialog.cpp +++ b/src/quickdialogs/quickdialogs/qquickabstractdialog.cpp @@ -157,6 +157,8 @@ QWindow *QQuickAbstractDialog::parentWindow() const void QQuickAbstractDialog::setParentWindow(QWindow *window) { qCDebug(lcDialogs) << "set parent window to" << window; + m_parentWindowExplicitlySet = bool(window); + if (m_parentWindow == window) return; @@ -164,6 +166,17 @@ void QQuickAbstractDialog::setParentWindow(QWindow *window) emit parentWindowChanged(); } +void QQuickAbstractDialog::resetParentWindow() +{ + m_parentWindowExplicitlySet = false; + + if (!m_parentWindow) + return; + + m_parentWindow = nullptr; + emit parentWindowChanged(); +} + /*! \qmlproperty string QtQuick.Dialogs::Dialog::title @@ -295,7 +308,7 @@ void QQuickAbstractDialog::open() onShow(m_handle.get()); - m_visible = m_handle->show(m_flags, m_modality, m_parentWindow); + m_visible = m_handle->show(m_flags, m_modality, windowForOpen()); if (!m_visible && useNativeDialog()) { // Fall back to non-native dialog destroy(); @@ -303,7 +316,7 @@ void QQuickAbstractDialog::open() return; onShow(m_handle.get()); - m_visible = m_handle->show(m_flags, m_modality, m_parentWindow); + m_visible = m_handle->show(m_flags, m_modality, windowForOpen()); if (m_visible) { // The conditions that caused the non-native fallback might have @@ -335,6 +348,8 @@ void QQuickAbstractDialog::close() onHide(m_handle.get()); m_handle->hide(); m_visible = false; + if (!m_parentWindowExplicitlySet) + m_parentWindow = nullptr; emit visibleChanged(); if (m_result == Accepted) @@ -389,16 +404,22 @@ void QQuickAbstractDialog::componentComplete() qCDebug(lcDialogs) << "componentComplete"; m_complete = true; - if (!m_parentWindow) { - qCDebug(lcDialogs) << "- no parent window; searching for one"; - setParentWindow(findParentWindow()); - } + if (!m_visibleRequested) + return; + + m_visibleRequested = false; - if (m_visibleRequested) { - qCDebug(lcDialogs) << "visible was bound to true before component completion; opening dialog"; + if (windowForOpen()) { open(); - m_visibleRequested = false; + return; } + + // Since visible were set to true by the user, we want the dialog to be open by default. + // There is no guarantee that the dialog will work when it exists in a object tree that lacks a window, + // and since qml components are sometimes instantiated before they're given a window + // (which is the case when using QQuickView), we want to delay the call to open(), until the window is provided. + if (const auto parentItem = findParentItem()) + connect(parentItem, &QQuickItem::windowChanged, this, &QQuickAbstractDialog::deferredOpen, Qt::SingleShotConnection); } static const char *qmlTypeName(const QObject *object) @@ -486,21 +507,33 @@ void QQuickAbstractDialog::onHide(QPlatformDialogHelper *dialog) Q_UNUSED(dialog); } -QWindow *QQuickAbstractDialog::findParentWindow() const +QQuickItem *QQuickAbstractDialog::findParentItem() const { QObject *obj = parent(); while (obj) { - QWindow *window = qobject_cast<QWindow *>(obj); - if (window) - return window; QQuickItem *item = qobject_cast<QQuickItem *>(obj); - if (item && item->window()) - return item->window(); + if (item) + return item; obj = obj->parent(); } return nullptr; } +QWindow *QQuickAbstractDialog::windowForOpen() const +{ + if (m_parentWindowExplicitlySet) + return m_parentWindow; + else if (auto parentItem = findParentItem()) + return parentItem->window(); + return m_parentWindow; +} + +void QQuickAbstractDialog::deferredOpen(QWindow *window) +{ + m_parentWindow = window; + open(); +} + QT_END_NAMESPACE #include "moc_qquickabstractdialog_p.cpp" diff --git a/src/quickdialogs/quickdialogs/qquickabstractdialog_p.h b/src/quickdialogs/quickdialogs/qquickabstractdialog_p.h index e314eb27b4..9b00bc4a23 100644 --- a/src/quickdialogs/quickdialogs/qquickabstractdialog_p.h +++ b/src/quickdialogs/quickdialogs/qquickabstractdialog_p.h @@ -31,13 +31,14 @@ QT_BEGIN_NAMESPACE class QWindow; class QPlatformDialogHelper; +class QQuickItem; class Q_QUICKDIALOGS2_PRIVATE_EXPORT QQuickAbstractDialog : public QObject, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(QQmlListProperty<QObject> data READ data FINAL) - Q_PROPERTY(QWindow *parentWindow READ parentWindow WRITE setParentWindow NOTIFY parentWindowChanged FINAL) + Q_PROPERTY(QWindow *parentWindow READ parentWindow WRITE setParentWindow NOTIFY parentWindowChanged RESET resetParentWindow FINAL) Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged FINAL) Q_PROPERTY(Qt::WindowFlags flags READ flags WRITE setFlags NOTIFY flagsChanged FINAL) Q_PROPERTY(Qt::WindowModality modality READ modality WRITE setModality NOTIFY modalityChanged FINAL) @@ -58,6 +59,7 @@ public: QWindow *parentWindow() const; void setParentWindow(QWindow *window); + void resetParentWindow(); QString title() const; void setTitle(const QString &title); @@ -107,12 +109,10 @@ protected: virtual void onShow(QPlatformDialogHelper *dialog); virtual void onHide(QPlatformDialogHelper *dialog); - QWindow *findParentWindow() const; + QQuickItem *findParentItem() const; + QWindow *windowForOpen() const; + void deferredOpen(QWindow *window); - bool m_visibleRequested = false; - bool m_visible = false; - bool m_complete = false; - bool m_firstShow = true; StandardCode m_result = Rejected; QWindow *m_parentWindow = nullptr; QString m_title; @@ -121,6 +121,11 @@ protected: QQuickDialogType m_type = QQuickDialogType::FileDialog; QList<QObject *> m_data; std::unique_ptr<QPlatformDialogHelper> m_handle; + bool m_visibleRequested = false; + bool m_visible = false; + bool m_complete = false; + bool m_parentWindowExplicitlySet = false; + bool m_firstShow = true; }; QT_END_NAMESPACE diff --git a/src/quicknativestyle/util/FocusFrame.qml b/src/quicknativestyle/util/FocusFrame.qml index eebee62c31..2a6ca12bed 100644 --- a/src/quicknativestyle/util/FocusFrame.qml +++ b/src/quicknativestyle/util/FocusFrame.qml @@ -5,24 +5,15 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -Item { +Rectangle { id: root - // It's important that this item has a zero size. Otherwise, if the parent of the - // targetItem is e.g a layout, we will change the layout if we parent this item inside it. - width: 0 - height: 0 - // Stack on top of all siblings of the targetItem - z: 100 - function moveToItem(item, margins, radius) { if (!item) { targetItem = null; parent = null; - visible = false; return; } - visible = true parent = item.parent targetItem = item leftOffset = margins.left @@ -45,20 +36,18 @@ Item { // systemFrameColor is set to NSColor.keyboardFocusIndicatorColor from cpp property color systemFrameColor - Rectangle { - id: focusFrame - z: 10 - x: targetItem ? targetItem.x + leftOffset - frameSize - root.x : 0 - y: targetItem ? targetItem.y + topOffset - frameSize - root.y : 0 - width: targetItem ? targetItem.width - leftOffset - rightOffset + (frameSize * 2) : 0 - height: targetItem ? targetItem.height - topOffset - bottomOffset + (frameSize * 2) : 0 - radius: frameRadius - visible: targetItem && targetItem.visible - color: "transparent" + x: targetItem ? targetItem.x + leftOffset - frameSize : 0 + y: targetItem ? targetItem.y + topOffset - frameSize : 0 + // Stack on top of all siblings of the targetItem + z: 100 + width: targetItem ? targetItem.width - leftOffset - rightOffset + (frameSize * 2) : 0 + height: targetItem ? targetItem.height - topOffset - bottomOffset + (frameSize * 2) : 0 + radius: frameRadius + visible: targetItem && targetItem.visible + color: "transparent" - border.color: systemFrameColor - border.width: frameSize - } + border.color: systemFrameColor + border.width: frameSize ParallelAnimation { id: animation @@ -71,7 +60,7 @@ Item { easing.type: Easing.OutCubic } NumberAnimation { - target: focusFrame + target: root property: "opacity" duration: 300 from: 0 diff --git a/src/quicknativestyle/util/qquickmacfocusframe.mm b/src/quicknativestyle/util/qquickmacfocusframe.mm index 8ebaf223be..bd01895831 100644 --- a/src/quicknativestyle/util/qquickmacfocusframe.mm +++ b/src/quicknativestyle/util/qquickmacfocusframe.mm @@ -56,6 +56,7 @@ void QQuickMacFocusFrame::moveToItem(QQuickItem *item) return; createFocusFrame(context); } + QQuickItemPrivate::get(m_focusFrame.get())->setTransparentForPositioner(true); const QQuickFocusFrameDescription &config = getDescriptionForItem(item); QMetaObject::invokeMethod(m_focusFrame.data(), "moveToItem", diff --git a/src/quicktemplates/CMakeLists.txt b/src/quicktemplates/CMakeLists.txt index 3c92a96fbd..2b2badd182 100644 --- a/src/quicktemplates/CMakeLists.txt +++ b/src/quicktemplates/CMakeLists.txt @@ -104,6 +104,8 @@ qt_internal_add_qml_module(QuickTemplates2 qquickswitchdelegate.cpp qquickswitchdelegate_p.h qquicktabbar.cpp qquicktabbar_p.h qquicktabbutton.cpp qquicktabbutton_p.h + qquicktemplatesutils.cpp + qquicktemplatesutils_p.h qquicktextarea.cpp qquicktextarea_p.h qquicktextarea_p_p.h qquicktextfield.cpp qquicktextfield_p.h diff --git a/src/quicktemplates/qquickabstractbutton.cpp b/src/quicktemplates/qquickabstractbutton.cpp index 85028a1d84..61c13d749e 100644 --- a/src/quicktemplates/qquickabstractbutton.cpp +++ b/src/quicktemplates/qquickabstractbutton.cpp @@ -113,6 +113,8 @@ void QQuickAbstractButtonPrivate::setMovePoint(const QPointF &point) bool QQuickAbstractButtonPrivate::handlePress(const QPointF &point, ulong timestamp) { Q_Q(QQuickAbstractButton); + if (pressed) + return true; QQuickControlPrivate::handlePress(point, timestamp); setPressPoint(point); q->setPressed(true); diff --git a/src/quicktemplates/qquickapplicationwindow.cpp b/src/quicktemplates/qquickapplicationwindow.cpp index 34bebbf021..2708c5f3f8 100644 --- a/src/quicktemplates/qquickapplicationwindow.cpp +++ b/src/quicktemplates/qquickapplicationwindow.cpp @@ -5,6 +5,7 @@ #include "qquickcontentitem_p.h" #include "qquickpopup_p_p.h" #include "qquickcontrol_p_p.h" +#include "qquicktemplatesutils_p.h" #include "qquicktextarea_p.h" #include "qquicktextfield_p.h" #include "qquicktoolbar_p.h" @@ -270,7 +271,7 @@ static QQuickItem *findActiveFocusControl(QQuickWindow *window) { QQuickItem *item = window->activeFocusItem(); while (item) { - if (qobject_cast<QQuickControl *>(item) || qobject_cast<QQuickTextField *>(item) || qobject_cast<QQuickTextArea *>(item)) + if (QQuickTemplatesUtils::isInteractiveControlType(item)) return item; item = item->parentItem(); } diff --git a/src/quicktemplates/qquickcontrol.cpp b/src/quicktemplates/qquickcontrol.cpp index c1a3ca7be8..42cdcee3ed 100644 --- a/src/quicktemplates/qquickcontrol.cpp +++ b/src/quicktemplates/qquickcontrol.cpp @@ -8,6 +8,7 @@ #include <QtGui/qguiapplication.h> #include "qquicklabel_p.h" #include "qquicklabel_p_p.h" +#include "qquicktemplatesutils_p.h" #include "qquicktextarea_p.h" #include "qquicktextarea_p_p.h" #include "qquicktextfield_p.h" @@ -722,12 +723,12 @@ bool QQuickControlPrivate::calcHoverEnabled(const QQuickItem *item) if (qobject_cast<const QQuickPopupItem *>(p)) break; - if (const QQuickControl *control = qobject_cast<const QQuickControl *>(p)) - return control->isHoverEnabled(); - - QVariant v = p->property("hoverEnabled"); - if (v.isValid() && v.userType() == QMetaType::Bool) - return v.toBool(); + if (QQuickTemplatesUtils::isInteractiveControlType(p)) { + const QVariant hoverEnabledProperty = p->property("hoverEnabled"); + Q_ASSERT(hoverEnabledProperty.isValid()); + Q_ASSERT(hoverEnabledProperty.userType() == QMetaType::Bool); + return hoverEnabledProperty.toBool(); + } p = p->parentItem(); } diff --git a/src/quicktemplates/qquickdrawer.cpp b/src/quicktemplates/qquickdrawer.cpp index 88813db376..8ab1d4dbc9 100644 --- a/src/quicktemplates/qquickdrawer.cpp +++ b/src/quicktemplates/qquickdrawer.cpp @@ -14,6 +14,8 @@ #include <QtQuick/private/qquicktransition_p.h> #include <QtQuickTemplates2/private/qquickoverlay_p.h> +#include <algorithm> + QT_BEGIN_NAMESPACE /*! @@ -674,7 +676,7 @@ qreal QQuickDrawer::position() const void QQuickDrawer::setPosition(qreal position) { Q_D(QQuickDrawer); - position = qBound<qreal>(0.0, position, 1.0); + position = std::clamp(position, qreal(0.0), qreal(1.0)); if (qFuzzyCompare(d->position, position)) return; diff --git a/src/quicktemplates/qquickpopup.cpp b/src/quicktemplates/qquickpopup.cpp index bc573a3313..deb5651391 100644 --- a/src/quicktemplates/qquickpopup.cpp +++ b/src/quicktemplates/qquickpopup.cpp @@ -995,12 +995,17 @@ void QQuickPopupPrivate::toggleOverlay() void QQuickPopupPrivate::updateContentPalettes(const QPalette& parentPalette) { // Inherit parent palette to all child objects - inheritPalette(parentPalette); - + if (providesPalette()) + inheritPalette(parentPalette); // Inherit parent palette to items within popup (such as headers and footers) QQuickItemPrivate::get(popupItem)->updateChildrenPalettes(parentPalette); } +void QQuickPopupPrivate::updateChildrenPalettes(const QPalette& parentPalette) +{ + updateContentPalettes(parentPalette); +} + void QQuickPopupPrivate::showDimmer() { // use QQmlProperty instead of QQuickItem::setOpacity() to trigger QML Behaviors diff --git a/src/quicktemplates/qquickpopup_p_p.h b/src/quicktemplates/qquickpopup_p_p.h index 9da91a4e26..c1935587a2 100644 --- a/src/quicktemplates/qquickpopup_p_p.h +++ b/src/quicktemplates/qquickpopup_p_p.h @@ -122,6 +122,8 @@ public: QPalette defaultPalette() const override; + void updateChildrenPalettes(const QPalette &parentPalette) override; + enum TransitionState { NoTransition, EnterTransition, ExitTransition }; diff --git a/src/quicktemplates/qquickslider.cpp b/src/quicktemplates/qquickslider.cpp index c095561d79..b922bbae08 100644 --- a/src/quicktemplates/qquickslider.cpp +++ b/src/quicktemplates/qquickslider.cpp @@ -7,6 +7,8 @@ #include <QtQuick/private/qquickwindow_p.h> +#include <algorithm> + QT_BEGIN_NAMESPACE /*! @@ -124,13 +126,13 @@ qreal QQuickSliderPrivate::positionAt(const QPointF &point) const if (!qFuzzyIsNull(extent)) pos = (q->height() - point.y() - q->bottomPadding() - offset) / extent; } - return qBound<qreal>(0.0, pos, 1.0); + return std::clamp(pos, qreal(0.0), qreal(1.0)); } void QQuickSliderPrivate::setPosition(qreal pos) { Q_Q(QQuickSlider); - pos = qBound<qreal>(0.0, pos, 1.0); + pos = std::clamp(pos, qreal(0.0), qreal(1.0)); if (qFuzzyCompare(position, pos)) return; diff --git a/src/quicktemplates/qquickswipedelegate.cpp b/src/quicktemplates/qquickswipedelegate.cpp index d617f4b3da..3c8df36564 100644 --- a/src/quicktemplates/qquickswipedelegate.cpp +++ b/src/quicktemplates/qquickswipedelegate.cpp @@ -16,6 +16,8 @@ #include <QtQuick/private/qquicktransition_p.h> #include <QtQuick/private/qquicktransitionmanager_p_p.h> +#include <algorithm> + QT_BEGIN_NAMESPACE /*! @@ -589,7 +591,7 @@ qreal QQuickSwipe::position() const void QQuickSwipe::setPosition(qreal position) { Q_D(QQuickSwipe); - const qreal adjustedPosition = qBound<qreal>(-1.0, position, 1.0); + const qreal adjustedPosition = std::clamp(position, qreal(-1.0), qreal(1.0)); if (adjustedPosition == d->position) return; diff --git a/src/quicktemplates/qquickswitch.cpp b/src/quicktemplates/qquickswitch.cpp index 1b2473cbdb..3234934e5e 100644 --- a/src/quicktemplates/qquickswitch.cpp +++ b/src/quicktemplates/qquickswitch.cpp @@ -9,6 +9,8 @@ #include <QtQuick/private/qquickwindow_p.h> #include <QtQuick/private/qquickevents_p_p.h> +#include <algorithm> + QT_BEGIN_NAMESPACE /*! @@ -122,7 +124,7 @@ qreal QQuickSwitch::position() const void QQuickSwitch::setPosition(qreal position) { Q_D(QQuickSwitch); - position = qBound<qreal>(0.0, position, 1.0); + position = std::clamp(position, qreal(0.0), qreal(1.0)); if (qFuzzyCompare(d->position, position)) return; diff --git a/src/quicktemplates/qquickswitchdelegate.cpp b/src/quicktemplates/qquickswitchdelegate.cpp index f17faf8aa9..adfcc69f16 100644 --- a/src/quicktemplates/qquickswitchdelegate.cpp +++ b/src/quicktemplates/qquickswitchdelegate.cpp @@ -5,6 +5,8 @@ #include "qquickitemdelegate_p_p.h" +#include <algorithm> + QT_BEGIN_NAMESPACE /*! @@ -119,7 +121,7 @@ qreal QQuickSwitchDelegate::position() const void QQuickSwitchDelegate::setPosition(qreal position) { Q_D(QQuickSwitchDelegate); - position = qBound<qreal>(0.0, position, 1.0); + position = std::clamp(position, qreal(0.0), qreal(1.0)); if (qFuzzyCompare(d->position, position)) return; diff --git a/src/quicktemplates/qquicktemplatesutils.cpp b/src/quicktemplates/qquicktemplatesutils.cpp new file mode 100644 index 0000000000..e998904cac --- /dev/null +++ b/src/quicktemplates/qquicktemplatesutils.cpp @@ -0,0 +1,35 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qquicktemplatesutils_p.h" + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> +#include <QtQuickTemplates2/private/qquicktextarea_p.h> +#include <QtQuickTemplates2/private/qquicktextfield_p.h> + +QT_BEGIN_NAMESPACE + +namespace QQuickTemplatesUtils { + +/*! + \internal + + Returns \c true if \a item is a \c QQuickControl or similar interactive + type that should be a QQuickControl-subclass but cannot due to existing + inheritance; e.g. \c QQuickTextField or \c QQuickTextArea. + + \c QQuickPopup is not considered a controls type for this function due to + the original use cases that resulted in this being factored out. + + \c QQuickLabel is not interactive. +*/ +bool isInteractiveControlType(const QQuickItem *item) +{ + return qobject_cast<const QQuickControl *>(item) + || qobject_cast<const QQuickTextField *>(item) + || qobject_cast<const QQuickTextArea *>(item); +} + +} // namespace QQuickTemplatesUtils + +QT_END_NAMESPACE diff --git a/src/quicktemplates/qquicktemplatesutils_p.h b/src/quicktemplates/qquicktemplatesutils_p.h new file mode 100644 index 0000000000..8be302c096 --- /dev/null +++ b/src/quicktemplates/qquicktemplatesutils_p.h @@ -0,0 +1,28 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQUICKTEMPLATESUTILS_P_H +#define QQUICKTEMPLATESUTILS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/qquickitem.h> + +QT_BEGIN_NAMESPACE + +namespace QQuickTemplatesUtils { +bool isInteractiveControlType(const QQuickItem *item); +} + +QT_END_NAMESPACE + +#endif // QQUICKTEMPLATESUTILS_P_H diff --git a/src/quicktestutils/quick/viewtestutils.cpp b/src/quicktestutils/quick/viewtestutils.cpp index 1263d7667c..f44dc21ef5 100644 --- a/src/quicktestutils/quick/viewtestutils.cpp +++ b/src/quicktestutils/quick/viewtestutils.cpp @@ -484,11 +484,19 @@ namespace QQuickTest { { if (!initView(view, url)) return false; + const QPoint framePos(view.framePosition()); view.show(); if (!QTest::qWaitForWindowExposed(&view)) return false; if (!view.rootObject()) return false; + if (view.flags().testFlag(Qt::FramelessWindowHint)) + return true; + const bool positionOk = QTest::qWaitFor([&]{ return framePos != view.position(); }); + if (!positionOk) { + qCritical() << "Position failed to update"; + return false; + } return true; } diff --git a/src/quicktestutils/quick/visualtestutils_p.h b/src/quicktestutils/quick/visualtestutils_p.h index 24b3bebef0..c57868ee68 100644 --- a/src/quicktestutils/quick/visualtestutils_p.h +++ b/src/quicktestutils/quick/visualtestutils_p.h @@ -230,6 +230,13 @@ namespace QQuickVisualTestUtils if (!(QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))) \ QSKIP("Window activation is not supported on this platform"); +#define SKIP_IF_NO_MOUSE_HOVER \ +do { \ + if ((QGuiApplication::platformName() == QLatin1String("offscreen")) \ + || (QGuiApplication::platformName() == QLatin1String("minimal"))) \ + QSKIP("Mouse hovering is not supported on the offscreen/minimal platforms"); \ +} while (false) + QT_END_NAMESPACE #endif // QQUICKVISUALTESTUTILS_P_H diff --git a/tests/auto/qml/debugger/qqmlpreview/tst_qqmlpreview.cpp b/tests/auto/qml/debugger/qqmlpreview/tst_qqmlpreview.cpp index 802adaee14..47d28cb9a4 100644 --- a/tests/auto/qml/debugger/qqmlpreview/tst_qqmlpreview.cpp +++ b/tests/auto/qml/debugger/qqmlpreview/tst_qqmlpreview.cpp @@ -295,6 +295,12 @@ void tst_QQmlPreview::blacklist() QVERIFY(!blacklist3.isBlacklisted("/usr/src")); QVERIFY(!blacklist3.isBlacklisted("/opt/share")); QVERIFY(!blacklist3.isBlacklisted("/opt")); + + QQmlPreviewBlacklist blacklist4; + blacklist4.whitelist(":/some/directory/with/file.qml"); + blacklist4.blacklist(":/some/directory/with"); + QVERIFY(blacklist4.isBlacklisted(":/some/directory/with")); + QVERIFY(!blacklist4.isBlacklisted(":/some/directory/with/file.qml")); } void tst_QQmlPreview::error() diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 00854ccb43..a3dc327cc2 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -6426,6 +6426,13 @@ void tst_QJSEngine::multiMatchingRegularExpression() QVERIFY(result.isString()); QCOMPARE(result.toString(), "33.312.345,897"_L1); + + const QJSValue result2 = engine.evaluate(R"( + "4F159D7AD40255D94A5B7EB9AAACD7408C79245D".replace(/(....)/g, '$1 ') + )"); + + QVERIFY(result2.isString()); + QCOMPARE(result2.toString(), "4F15 9D7A D402 55D9 4A5B 7EB9 AAAC D740 8C79 245D "_L1); } QTEST_MAIN(tst_QJSEngine) diff --git a/tests/auto/qml/qmlcppcodegen/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/CMakeLists.txt index e2a939ac87..485c88c8fe 100644 --- a/tests/auto/qml/qmlcppcodegen/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/CMakeLists.txt @@ -11,6 +11,8 @@ qt_internal_add_test(tst_qmlcppcodegen Qt::GuiPrivate codegen_test_module codegen_test_moduleplugin + confused_test_module + confused_test_moduleplugin ) qt_internal_add_test(tst_qmlcppcodegen_interpreted @@ -21,6 +23,8 @@ qt_internal_add_test(tst_qmlcppcodegen_interpreted Qt::GuiPrivate codegen_test_module codegen_test_moduleplugin + confused_test_module + confused_test_moduleplugin DEFINES QT_TEST_FORCE_INTERPRETER ) diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 620818c5d6..d80783078f 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -1,6 +1,8 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause +add_subdirectory(Confused) + set(cpp_sources ambiguous.h birthdayparty.cpp birthdayparty.h diff --git a/tests/auto/qml/qmlcppcodegen/data/Confused/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/Confused/CMakeLists.txt new file mode 100644 index 0000000000..1d03252ff9 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/Confused/CMakeLists.txt @@ -0,0 +1,27 @@ +qt_add_library(confused_test_module STATIC) +qt_autogen_tools_initial_setup(confused_test_module) + +qt_policy(SET QTP0001 NEW) +# Not QTP0004, since we have a manually written qmldir in a strange place + +set_source_files_properties("Test/broken.js" + PROPERTIES QT_RESOURCE_ALIAS "Test/broken.qml" +) + +qt_add_qml_module(confused_test_module + URI Confused + VERSION 1.0 + QML_FILES + Main.qml + Main2.qml + Test/test.js + Test/broken.js + RESOURCES + Test/qmldir +) + +target_link_libraries(confused_test_module + PRIVATE Qt6::Qml +) + +qt_autogen_tools_initial_setup(confused_test_moduleplugin) diff --git a/tests/auto/qml/qmlcppcodegen/data/Confused/Main.qml b/tests/auto/qml/qmlcppcodegen/data/Confused/Main.qml new file mode 100644 index 0000000000..b1020610cf --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/Confused/Main.qml @@ -0,0 +1,6 @@ +import QtQml +import "Test" as T + +QtObject { + Component.onCompleted: T.Test.Print() +} diff --git a/tests/auto/qml/qmlcppcodegen/data/Confused/Main2.qml b/tests/auto/qml/qmlcppcodegen/data/Confused/Main2.qml new file mode 100644 index 0000000000..1878122b91 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/Confused/Main2.qml @@ -0,0 +1,6 @@ +import QtQml +import "Test" as T + +QtObject { + Component.onCompleted: T.Broken.Print() +} diff --git a/tests/auto/qml/qmlcppcodegen/data/Confused/Test/broken.js b/tests/auto/qml/qmlcppcodegen/data/Confused/Test/broken.js new file mode 100644 index 0000000000..682e1d6e89 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/Confused/Test/broken.js @@ -0,0 +1,3 @@ +.pragma library + +function Print() { console.log("Hello from Broken") } diff --git a/tests/auto/qml/qmlcppcodegen/data/Confused/Test/qmldir b/tests/auto/qml/qmlcppcodegen/data/Confused/Test/qmldir new file mode 100644 index 0000000000..5315ead60c --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/Confused/Test/qmldir @@ -0,0 +1,2 @@ +Test test.js +Broken broken.qml diff --git a/tests/auto/qml/qmlcppcodegen/data/Confused/Test/test.js b/tests/auto/qml/qmlcppcodegen/data/Confused/Test/test.js new file mode 100644 index 0000000000..b571c1657e --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/Confused/Test/test.js @@ -0,0 +1,3 @@ +.pragma library + +function Print() { console.log("Hello from Test") } diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index 6a61f08986..dae83e2c86 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -21,6 +21,7 @@ using namespace Qt::StringLiterals; Q_IMPORT_QML_PLUGIN(TestTypesPlugin) +Q_IMPORT_QML_PLUGIN(ConfusedPlugin) class tst_QmlCppCodegen : public QObject { @@ -44,6 +45,7 @@ private slots: void scopedEnum(); void sequenceToIterable(); void compositeTypeMethod(); + void confusedModule(); void excessiveParameters(); void jsImport(); void jsmoduleImport(); @@ -513,6 +515,26 @@ void tst_QmlCppCodegen::compositeTypeMethod() QTRY_VERIFY(spy.size() > 0); } +void tst_QmlCppCodegen::confusedModule() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/Confused/Main.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QTest::ignoreMessage(QtDebugMsg, "Hello from Test"); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QQmlComponent component2(&engine, QUrl(u"qrc:/qt/qml/Confused/Main2.qml"_s)); + QVERIFY2(!component2.isError(), component2.errorString().toUtf8()); + // TODO: We would like to have a better error here, but we currently cannot propagate it. + QTest::ignoreMessage( + QtWarningMsg, + "qrc:/qt/qml/Confused/Main2.qml:5: " + "TypeError: Property 'Print' of object [object Object] is not a function"); + QScopedPointer<QObject> object2(component2.create()); + QVERIFY(!object2.isNull()); +} + void tst_QmlCppCodegen::excessiveParameters() { QQmlEngine engine; diff --git a/tests/auto/qml/qmllint/data/GPane.qml b/tests/auto/qml/qmllint/data/GPane.qml new file mode 100644 index 0000000000..af2cac1e30 --- /dev/null +++ b/tests/auto/qml/qmllint/data/GPane.qml @@ -0,0 +1,10 @@ +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + default property alias paneData: content.data + + Column { + id: content + } +} diff --git a/tests/auto/qml/qmllint/data/aliasGroup.qml b/tests/auto/qml/qmllint/data/aliasGroup.qml new file mode 100644 index 0000000000..b3b59251a2 --- /dev/null +++ b/tests/auto/qml/qmllint/data/aliasGroup.qml @@ -0,0 +1,20 @@ +import QtQml + +QtObject { + id: root + + property QtObject o: SampleDataStoreManagerSettingsSection { + contentControl.contentItem: QtObject { objectName: "a" } + } + + component SampleDataStoreManagerSettingsSection: QtObject { + property alias contentControl: sectionContentItem + + property QtObject o: QtObject { + id: sectionContentItem + property QtObject contentItem + + } + } +} + diff --git a/tests/auto/qml/qmllint/data/aliasToRequiredPropertyIsNotRequiredItself.qml b/tests/auto/qml/qmllint/data/aliasToRequiredPropertyIsNotRequiredItself.qml new file mode 100644 index 0000000000..247758a7b3 --- /dev/null +++ b/tests/auto/qml/qmllint/data/aliasToRequiredPropertyIsNotRequiredItself.qml @@ -0,0 +1,30 @@ +import QtQml + +QtObject { + // Not at the root level + property QtObject o1: QtObject { + component Comp1 : QtObject { + required property int i1 + } + + property Comp1 c1: Comp1 { + id: compId1 + i1: 1 + } + + property alias aliasToRequired1: compId1.i1 + } + + + // At the root level + component Comp2 : QtObject { + required property int i2 + } + + property Comp2 c2: Comp2 { + id: compId2 + i2: 2 + } + + property alias aliasToRequired2: compId2.i2 +} diff --git a/tests/auto/qml/qmllint/data/deceptiveLayout.qml b/tests/auto/qml/qmllint/data/deceptiveLayout.qml new file mode 100644 index 0000000000..b5683ba899 --- /dev/null +++ b/tests/auto/qml/qmllint/data/deceptiveLayout.qml @@ -0,0 +1,10 @@ +import QtQuick +import QtQuick.Layouts + +GPane { + Layout.alignment: Qt.AlignHCenter + + Text { + width: parent.width + } +} diff --git a/tests/auto/qml/qmllint/data/evals.qml b/tests/auto/qml/qmllint/data/evals.qml new file mode 100644 index 0000000000..d3d858cf8a --- /dev/null +++ b/tests/auto/qml/qmllint/data/evals.qml @@ -0,0 +1,6 @@ +import QtQuick + +Item { + function f() { eval(); } + function g() { eval("1 + 1"); } +} diff --git a/tests/auto/qml/qmllint/data/missingRequiredPropertyOnObjectDefinitionBinding.qml b/tests/auto/qml/qmllint/data/missingRequiredPropertyOnObjectDefinitionBinding.qml new file mode 100644 index 0000000000..723fccf09a --- /dev/null +++ b/tests/auto/qml/qmllint/data/missingRequiredPropertyOnObjectDefinitionBinding.qml @@ -0,0 +1,7 @@ +import QtQml + +QtObject { + property QtObject o: QtObject { + required property int i + } +} diff --git a/tests/auto/qml/qmllint/data/setRequiredPropertyThroughAlias.qml b/tests/auto/qml/qmllint/data/setRequiredPropertyThroughAlias.qml new file mode 100644 index 0000000000..de7ad1b820 --- /dev/null +++ b/tests/auto/qml/qmllint/data/setRequiredPropertyThroughAlias.qml @@ -0,0 +1,18 @@ +import QtQml + +QtObject { + id: root + + component C : QtObject { + required property int i + } + + component C2 : C { + id: c2 + property alias ai: c2.i + } + + property C2 c2: C2 { + ai: 1 + } +} diff --git a/tests/auto/qml/qmllint/data/setRequiredPropertyThroughAliasOfAlias.qml b/tests/auto/qml/qmllint/data/setRequiredPropertyThroughAliasOfAlias.qml new file mode 100644 index 0000000000..75450d8d49 --- /dev/null +++ b/tests/auto/qml/qmllint/data/setRequiredPropertyThroughAliasOfAlias.qml @@ -0,0 +1,20 @@ +import QtQml + +QtObject { + id: root + + component C : QtObject { + id: c + required property int i + property alias ai: c.i + } + + component C2 : C { + id: c2 + property alias aai: c2.ai + } + + property C2 c2: C2 { + aai: 1 + } +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 690a3a272a..3bb3853d6a 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -1011,9 +1011,7 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)", "Cannot assign literal of type null to double") } } }; QTest::newRow("missingRequiredAlias") << QStringLiteral("missingRequiredAlias.qml") - << Result { { Message { - QStringLiteral("Component is missing required property requiredAlias from " - "RequiredWithRootLevelAlias") } } }; + << Result{ { Message{ u"Component is missing required property foo from Item"_s } } }; QTest::newRow("missingSingletonPragma") << QStringLiteral("missingSingletonPragma.qml") << Result { { Message { QStringLiteral( @@ -1108,6 +1106,9 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)", QTest::newRow("StoreNameMethod") << QStringLiteral("storeNameMethod.qml") << Result { { Message { QStringLiteral("Cannot assign to method foo") } } }; + QTest::newRow("missingRequiredOnObjectDefinitionBinding") + << QStringLiteral("missingRequiredPropertyOnObjectDefinitionBinding.qml") + << Result{ { { uR"(Component is missing required property i from here)"_s, 4, 26 } } }; } void TestQmllint::dirtyQmlCode() @@ -1289,6 +1290,16 @@ void TestQmllint::cleanQmlCode_data() QTest::newRow("groupedAttachedLayout") << QStringLiteral("groupedAttachedLayout.qml"); QTest::newRow("constInvokable") << QStringLiteral("useConstInvokable.qml"); QTest::newRow("scopedAndUnscopedEnums") << QStringLiteral("enumValid.qml"); + + QTest::addRow("deceptiveLayout") << u"deceptiveLayout.qml"_s; + QTest::newRow("aliasGroup") << QStringLiteral("aliasGroup.qml"); + QTest::newRow("aliasToRequiredProperty") + << QStringLiteral("aliasToRequiredPropertyIsNotRequiredItself.qml"); + QTest::newRow("setRequiredTroughAlias") << QStringLiteral("setRequiredPropertyThroughAlias.qml"); + QTest::newRow("setRequiredTroughAliasOfAlias") + << QStringLiteral("setRequiredPropertyThroughAliasOfAlias.qml"); + QTest::newRow("evals") + << QStringLiteral("evals.qml"); } void TestQmllint::cleanQmlCode() diff --git a/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp b/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp index 9e2ba24969..cd2f752e4b 100644 --- a/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp +++ b/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp @@ -5,6 +5,7 @@ #include <QtCore/QScopedPointer> #include <QtQml/private/qqmlanybinding_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtCore/private/qobject_p.h> #include "withbindable.h" class tst_qqmlanybinding : public QQmlDataTest @@ -18,6 +19,7 @@ private slots: void basicActions_data(); void basicActions(); void unboundQQmlPropertyBindingDoesNotCrash(); + void ofDynamicMetaObject(); }; tst_qqmlanybinding::tst_qqmlanybinding() @@ -127,6 +129,47 @@ void tst_qqmlanybinding::unboundQQmlPropertyBindingDoesNotCrash() QCOMPARE(prop.read(), 1); } +class QObjectDynamicMetaObject : public QDynamicMetaObjectData +{ +public: +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) + const QMetaObject *toDynamicMetaObject(QObject *) const final + { + return &QObject::staticMetaObject; + } +#else + QMetaObject *toDynamicMetaObject(QObject *) final + { + return const_cast<QMetaObject *>(&QObject::staticMetaObject); + } +#endif + + int metaCall(QObject *o, QMetaObject::Call c, int id, void **argv) final + { + return o->qt_metacall(c, id, argv); + } +}; + +void tst_qqmlanybinding::ofDynamicMetaObject() +{ + QObject o; + const QQmlPropertyIndex objectNameIndex( + QObject::staticMetaObject.indexOfProperty("objectName")); + QObjectPrivate::get(&o)->metaObject = new QObjectDynamicMetaObject; + + QQmlAnyBinding a = QQmlAnyBinding::ofProperty(&o, objectNameIndex); + QVERIFY(!a); + + QPropertyBinding<QString> binding + = Qt::makePropertyBinding([]() { return QStringLiteral("foo"); }); + o.bindableObjectName().setBinding(binding); + QQmlAnyBinding b = QQmlAnyBinding::ofProperty(&o, objectNameIndex); + QVERIFY(b); + + QCOMPARE(QPropertyBindingPrivate::get(b.asUntypedPropertyBinding()), + QPropertyBindingPrivate::get(binding)); +} + QTEST_MAIN(tst_qqmlanybinding) #include "tst_qqmlanybinding.moc" diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 63655fa60d..1612848dfb 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -3733,6 +3733,8 @@ void tst_qqmlecmascript::attachedPropertyScope() void tst_qqmlecmascript::scriptConnect() { + QTest::failOnWarning(QRegularExpression(".*")); + QQmlEngine engine; { @@ -3784,8 +3786,6 @@ void tst_qqmlecmascript::scriptConnect() QCOMPARE(object->methodCalled(), false); - QTest::ignoreMessage(QtWarningMsg, QRegularExpression("When matching arguments for MyQmlObject_QML_[0-9]+::methodNoArgs\\(\\):")); - QTest::ignoreMessage(QtWarningMsg, QRegularExpression("Too many arguments, ignoring 5")); emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton); QCOMPARE(object->methodCalled(), true); } @@ -3799,8 +3799,6 @@ void tst_qqmlecmascript::scriptConnect() QVERIFY(object != nullptr); QCOMPARE(object->methodCalled(), false); - QTest::ignoreMessage(QtWarningMsg, QRegularExpression("When matching arguments for MyQmlObject_QML_[0-9]+::methodNoArgs\\(\\):")); - QTest::ignoreMessage(QtWarningMsg, QRegularExpression("Too many arguments, ignoring 5")); emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton); QCOMPARE(object->methodCalled(), true); } @@ -4625,7 +4623,7 @@ void tst_qqmlecmascript::singletonTypeImportOrder() QQmlComponent component(&engine, testFileUrl("singletontype/singletonTypeImportOrder.qml")); QScopedPointer<QObject> object(component.create()); QVERIFY2(object, qPrintable(component.errorString())); - QCOMPARE(object->property("v").toInt(), 1); + QCOMPARE(object->property("v").toInt(), 2); } void tst_qqmlecmascript::singletonTypeResolution() diff --git a/tests/auto/qml/qqmllanguage/CMakeLists.txt b/tests/auto/qml/qqmllanguage/CMakeLists.txt index fc9bbf17df..c36ee66579 100644 --- a/tests/auto/qml/qqmllanguage/CMakeLists.txt +++ b/tests/auto/qml/qqmllanguage/CMakeLists.txt @@ -70,3 +70,7 @@ set_target_properties(tst_qqmllanguage PROPERTIES ) _qt_internal_qml_type_registration(tst_qqmllanguage) + +add_subdirectory(OtherModuleTest) +target_link_libraries(tst_qqmllanguage PUBLIC other_module) +target_link_libraries(tst_qqmllanguage PUBLIC other_moduleplugin) diff --git a/tests/auto/qml/qqmllanguage/OtherModuleTest/CMakeLists.txt b/tests/auto/qml/qqmllanguage/OtherModuleTest/CMakeLists.txt new file mode 100644 index 0000000000..84a2a46ec1 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/OtherModuleTest/CMakeLists.txt @@ -0,0 +1,15 @@ +qt_add_library(other_module STATIC) +qt_autogen_tools_initial_setup(other_module) + +qt_policy(set QTP0001 NEW) + +qt6_add_qml_module(other_module + URI OtherModuleTest + VERSION 1.0 + SOURCES + other.h +) + +qt_autogen_tools_initial_setup(other_moduleplugin) + +add_dependencies(tst_qqmllanguage other_module other_moduleplugin) diff --git a/tests/auto/qml/qqmllanguage/OtherModuleTest/other.h b/tests/auto/qml/qqmllanguage/OtherModuleTest/other.h new file mode 100644 index 0000000000..b6cb53044a --- /dev/null +++ b/tests/auto/qml/qqmllanguage/OtherModuleTest/other.h @@ -0,0 +1,50 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef OTHER_H +#define OTHER_H + +#include <qqmlregistration.h> +#include <QQmlEngine> + +namespace YepNamespaceB { +class YepAttached : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + +public: + YepAttached(QObject *parent) : QObject(parent) { } + Q_INVOKABLE QString s() const { return QStringLiteral("OtherModuleTest Attached Type"); } +}; +class Yep : public QObject +{ + Q_OBJECT + QML_ATTACHED(YepAttached) + QML_ELEMENT + +public: + static YepAttached *qmlAttachedProperties(QObject *object) { return new YepAttached(object); } +}; + +class YepSingleton : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +public: + Q_INVOKABLE QString s() const { return QStringLiteral("OtherModuleTest Singleton"); } +}; + +class MyObject : public QObject +{ + Q_OBJECT + QML_ELEMENT + +public: + Q_INVOKABLE QString s() const { return QStringLiteral("OtherModuleTest"); } +}; +} // namespace YepNamespaceB + +#endif // OTHER_H diff --git a/tests/auto/qml/qqmllanguage/data/JsSelfImport/CustomPrinter.qml b/tests/auto/qml/qqmllanguage/data/JsSelfImport/CustomPrinter.qml new file mode 100644 index 0000000000..92bfded464 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/JsSelfImport/CustomPrinter.qml @@ -0,0 +1,8 @@ +pragma Singleton +import QtQml + +QtObject { + function ppp() : string { + return "customPrint" + } +} diff --git a/tests/auto/qml/qqmllanguage/data/JsSelfImport/JsLib.js b/tests/auto/qml/qqmllanguage/data/JsSelfImport/JsLib.js new file mode 100644 index 0000000000..d793783280 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/JsSelfImport/JsLib.js @@ -0,0 +1,6 @@ +.pragma library +.import JsSelfImport as A + +function func() { + return A.CustomPrinter.ppp() +} diff --git a/tests/auto/qml/qqmllanguage/data/JsSelfImport/Main.qml b/tests/auto/qml/qqmllanguage/data/JsSelfImport/Main.qml new file mode 100644 index 0000000000..486768eb9c --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/JsSelfImport/Main.qml @@ -0,0 +1,6 @@ +import QtQml +import JsSelfImport + +QtObject { + objectName: JsLib.func() +} diff --git a/tests/auto/qml/qqmllanguage/data/JsSelfImport/qmldir b/tests/auto/qml/qqmllanguage/data/JsSelfImport/qmldir new file mode 100644 index 0000000000..2a0d8973fa --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/JsSelfImport/qmldir @@ -0,0 +1,5 @@ +module JsSelfImport +singleton CustomPrinter 254.0 CustomPrinter.qml +JsLib 254.0 JsLib.js +Main 254.0 Main.qml + diff --git a/tests/auto/qml/qqmllanguage/data/aliasOfBindableValueTypeProperty.qml b/tests/auto/qml/qqmllanguage/data/aliasOfBindableValueTypeProperty.qml new file mode 100644 index 0000000000..f8740e71e0 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/aliasOfBindableValueTypeProperty.qml @@ -0,0 +1,21 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQml +import Test + +BindablePoint { + id: root + property int aaChanges: 0 + property int bbChanges: 0 + property alias aa: root.point.x + property alias bb: root.point.y + onAaChanged: ++aaChanges + bb: objectName + onBbChanged: ++bbChanges + objectName: "14" + + function reassign() { + bb = Qt.binding(function() { return 16 + 0.5 }); + } +} diff --git a/tests/auto/qml/qqmllanguage/data/asCastTypeResolutionImportOrderAB.qml b/tests/auto/qml/qqmllanguage/data/asCastTypeResolutionImportOrderAB.qml new file mode 100644 index 0000000000..3548bc7a84 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/asCastTypeResolutionImportOrderAB.qml @@ -0,0 +1,12 @@ +import QtQml + +import StaticTest as ST + +import OtherModuleTest +import StaticTest + +QtObject { + property QtObject mo: ST.MyObject { } + // Both StaticTest and OtherModuleTest provide MyObject + property string s: (mo as MyObject).s() +} diff --git a/tests/auto/qml/qqmllanguage/data/asCastTypeResolutionImportOrderBA.qml b/tests/auto/qml/qqmllanguage/data/asCastTypeResolutionImportOrderBA.qml new file mode 100644 index 0000000000..537677a89e --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/asCastTypeResolutionImportOrderBA.qml @@ -0,0 +1,12 @@ +import QtQml + +import StaticTest as ST + +import StaticTest +import OtherModuleTest + +QtObject { + property QtObject mo: ST.MyObject { } + // Both StaticTest and OtherModuleTest provide MyObject + property string s: (mo as MyObject).s() +} diff --git a/tests/auto/qml/qqmllanguage/data/attachedTypeResolutionImportOrder.qml b/tests/auto/qml/qqmllanguage/data/attachedTypeResolutionImportOrder.qml new file mode 100644 index 0000000000..c0a3c0e896 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/attachedTypeResolutionImportOrder.qml @@ -0,0 +1,9 @@ +import StaticTest +import OtherModuleTest + +import QtQml + +QtObject { + // Both StaticTest and OtherModuleTest provide Yep + property string s: Yep.s() +} diff --git a/tests/auto/qml/qqmllanguage/data/singletonResolutionImportOrder.qml b/tests/auto/qml/qqmllanguage/data/singletonResolutionImportOrder.qml new file mode 100644 index 0000000000..f8f24524e9 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/singletonResolutionImportOrder.qml @@ -0,0 +1,9 @@ +import StaticTest +import OtherModuleTest + +import QtQml + +QtObject { + // Both StaticTest and OtherModuleTest provide YepSingleton + property string s: YepSingleton.s() +} diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index c65cbe329d..ca554d895e 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -165,6 +165,8 @@ void registerTypes() qmlRegisterTypesAndRevisions<Counter>("Test", 1); qmlRegisterTypesAndRevisions<NestedVectors>("Test", 1); + + qmlRegisterTypesAndRevisions<BindablePoint>("Test", 1); } QVariant myCustomVariantTypeConverter(const QString &data) diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index bfee2d07c9..438eda3cef 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -2640,4 +2640,58 @@ private: std::vector<std::vector<int>> m_list; }; +namespace YepNamespaceA { +class YepAttached : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + +public: + YepAttached(QObject *parent) : QObject(parent) { } + Q_INVOKABLE QString s() const { return QStringLiteral("StaticTest Attached Type"); } +}; +class Yep : public QObject +{ + Q_OBJECT + QML_ATTACHED(YepAttached) + QML_ELEMENT + +public: + static YepAttached *qmlAttachedProperties(QObject *object) { return new YepAttached(object); } +}; + +class YepSingleton : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +public: + Q_INVOKABLE QString s() const { return QStringLiteral("StaticTest Singleton"); } +}; + +class MyObject : public QObject +{ + Q_OBJECT + QML_ELEMENT + +public: + Q_INVOKABLE QString s() const { return QStringLiteral("StaticTest"); } +}; +} // namespace YepNamespaceA + +class BindablePoint : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QPointF point BINDABLE bindablePoint READ default WRITE default) +public: + BindablePoint(QObject *parent = nullptr) : QObject(parent), m_point(QPointF(101, 102)) {} + + QBindable<QPointF> bindablePoint() { return QBindable<QPointF>(&m_point); } + +private: + QProperty<QPointF> m_point; +}; + #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index b7dff336af..df89dcff39 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <qtest.h> #include <QtQml/qqmlengine.h> +#include <QtQml/qqmlextensionplugin.h> #include <QtQml/qqmlcomponent.h> #include <QtQml/qqmlincubator.h> #include <QtCore/qiterable.h> @@ -43,6 +44,8 @@ using namespace Qt::StringLiterals; +Q_IMPORT_QML_PLUGIN(OtherModuleTestPlugin) + DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES) static inline bool isCaseSensitiveFileSystem(const QString &path) { @@ -435,6 +438,15 @@ private slots: void overrideInnerBinding(); + void jsSelfImport(); + + void singletonResultionImportOrder(); + void attachedTypeResultionImportOrder(); + void asCastTypeResolutionImportOrderAB(); + void asCastTypeResolutionImportOrderBA(); + + void aliasOfBindableValueTypeProperty(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -8277,6 +8289,110 @@ void tst_qqmllanguage::overrideInnerBinding() QCOMPARE(font.family(), "Ariallll"); } +void tst_qqmllanguage::jsSelfImport() +{ + QQmlEngine engine; + engine.addImportPath(dataDirectory()); + QQmlComponent c(&engine, "JsSelfImport", "Main"); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->objectName(), "customPrint"); +} + +void tst_qqmllanguage::singletonResultionImportOrder() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("singletonResolutionImportOrder.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->property("s").toString(), "OtherModuleTest Singleton"); +} + +void tst_qqmllanguage::attachedTypeResultionImportOrder() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("attachedTypeResolutionImportOrder.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->property("s").toString(), "OtherModuleTest Attached Type"); +} + +void tst_qqmllanguage::asCastTypeResolutionImportOrderAB() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("asCastTypeResolutionImportOrderAB.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->property("s").toString(), "StaticTest"); +} + +void tst_qqmllanguage::asCastTypeResolutionImportOrderBA() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("asCastTypeResolutionImportOrderBA.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QTest::ignoreMessage(QtWarningMsg, + QRegularExpression(".*TypeError: Cannot call method 's' of null")); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); +} + +void tst_qqmllanguage::aliasOfBindableValueTypeProperty() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("aliasOfBindableValueTypeProperty.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("aa"), 101); + QCOMPARE(o->property("bb"), 14); + QCOMPARE(o->property("aaChanges"), 1); + QCOMPARE(o->property("bbChanges"), 1); + + o->setObjectName("15"); + QCOMPARE(o->property("aa"), 101); + QCOMPARE(o->property("bb"), 15); + QCOMPARE(o->property("aaChanges"), 2); + QCOMPARE(o->property("bbChanges"), 2); + + o->setProperty("point", QPointF(17, 18)); + QCOMPARE(o->property("aa"), 17); + QCOMPARE(o->property("bb"), 18); + QCOMPARE(o->property("aaChanges"), 3); + QCOMPARE(o->property("bbChanges"), 3); + + BindablePoint *b = qobject_cast<BindablePoint *>(o.data()); + QVERIFY(b); + b->bindablePoint().setValue(QPointF(19, 20)); + QCOMPARE(o->property("aa"), 19); + QCOMPARE(o->property("bb"), 20); + QCOMPARE(o->property("aaChanges"), 4); + QCOMPARE(o->property("bbChanges"), 4); + + QMetaObject::invokeMethod(o.data(), "reassign"); + QCOMPARE(o->property("aa"), 19); + QCOMPARE(o->property("bb"), 16.5); + QCOMPARE(o->property("aaChanges"), 5); + QCOMPARE(o->property("bbChanges"), 5); + + const QMetaObject *mo = o->metaObject(); + const int aaIndex = mo->indexOfProperty("aa"); + QVERIFY(aaIndex >= 0); + + // Querying the bindable of a value type alias results in the bindable of the core property. + QUntypedBindable bindable; + void *args[] { &bindable }; + QCOMPARE(o->metaObject()->metacall(o.data(), QMetaObject::BindableProperty, aaIndex, args), -1); + QVERIFY(bindable.isValid()); + QCOMPARE(bindable.metaType(), QMetaType::fromType<QPointF>()); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" diff --git a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp index 8f0e657e54..4010be56b3 100644 --- a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp +++ b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp @@ -273,18 +273,28 @@ void tst_qqmllistmodel::static_i18n_data() << QVariant(QString::fromUtf8("na\303\257ve")) << QString(); + QTest::newRow("QT_TR_NOOP_disambig") + << QString::fromUtf8("ListElement { foo: QT_TR_NOOP(\"na\303\257ve\", \"disambig\") }") + << QVariant(QString::fromUtf8("na\303\257ve")) + << QString(); + QTest::newRow("QT_TRANSLATE_NOOP") << "ListElement { foo: QT_TRANSLATE_NOOP(\"MyListModel\", \"hello\") }" << QVariant(QString("hello")) << QString(); + QTest::newRow("QT_TRANSLATE_NOOP_disambig") + << "ListElement { foo: QT_TRANSLATE_NOOP(\"MyListModel\", \"hello\", \"greeting\") }" + << QVariant(QString("hello")) + << QString(); + QTest::newRow("QT_TRID_NOOP") << QString::fromUtf8("ListElement { foo: QT_TRID_NOOP(\"qtn_1st_text\") }") << QVariant(QString("qtn_1st_text")) << QString(); QTest::newRow("QT_TR_NOOP extra param") - << QString::fromUtf8("ListElement { foo: QT_TR_NOOP(\"hello\",\"world\") }") + << QString::fromUtf8("ListElement { foo: QT_TR_NOOP(\"hello\",\"world\", \"!\") }") << QVariant(QString()) << QString("ListElement: cannot use script for property value"); diff --git a/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp b/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp index 67d15e5043..7b42e09b0d 100644 --- a/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp +++ b/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp @@ -447,11 +447,12 @@ void tst_qqmllocale::toString_data() functionCallScript = "locale.toString(123.456)"; QTest::newRow(qPrintable(functionCallScript)) << "de_DE" << functionCallScript << "123,456" << QString(); - functionCallScript = "locale.toString(123.456, 'e')"; + // Exponent case should match what's asked for: + functionCallScript = "locale.toString(123.456, 'E')"; QTest::newRow(qPrintable(functionCallScript)) << "de_DE" << functionCallScript << "1,234560E+02" << QString(); functionCallScript = "locale.toString(123.456, 'e', 1)"; - QTest::newRow(qPrintable(functionCallScript)) << "de_DE" << functionCallScript << "1,2E+02" << QString(); + QTest::newRow(qPrintable(functionCallScript)) << "de_DE" << functionCallScript << "1,2e+02" << QString(); // Test toString(Date, string) and toString(Date[, FormatType]). const QDateTime midnight2000(QDate(2000, 1, 1), QTime(0, 0)); diff --git a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp index 235de5f406..396f876fe4 100644 --- a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp +++ b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp @@ -2418,6 +2418,7 @@ void tst_qqmlproperty::initFlags_data() QTest::addColumn<bool>("passObject"); QTest::addColumn<QString>("name"); QTest::addColumn<QQmlPropertyPrivate::InitFlags>("flags"); + QTest::addColumn<bool>("useInnerObject"); const QString names[] = { QStringLiteral("foo"), @@ -2443,7 +2444,10 @@ void tst_qqmlproperty::initFlags_data() for (const auto &flagSet : flagSets) { const QString rowName = QStringLiteral("%1,%2,%3") .arg(passObject).arg(name).arg(flagSet.toInt()); - QTest::addRow("%s", qPrintable(rowName)) << passObject << name << flagSet; + QTest::addRow("%s", qPrintable(rowName + ",outer")) + << passObject << name << flagSet << false; + QTest::addRow("%s", qPrintable(rowName + ",inner")) + << passObject << name << flagSet << true; } } } @@ -2454,6 +2458,7 @@ void tst_qqmlproperty::initFlags() QFETCH(bool, passObject); QFETCH(QString, name); QFETCH(QQmlPropertyPrivate::InitFlags, flags); + QFETCH(bool, useInnerObject); QQmlEngine engine; QQmlComponent c(&engine); @@ -2465,15 +2470,31 @@ void tst_qqmlproperty::initFlags() property int bar: 12 property alias abar: self.bar } - )", QUrl()); - QVERIFY(c.isReady()); + )", QUrl("outer")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer<QObject> o(c.create()); QVERIFY(!o.isNull()); - QQmlRefPointer<QQmlContextData> context = QQmlContextData::get(qmlContext(o.data())); + QObject *object = nullptr; + + QScopedPointer<QObject> i; + if (useInnerObject) { + QQmlComponent c2(&engine); + c2.setData(R"( + import QtQml + QtObject {} + )", QUrl("inner")); + QVERIFY2(c2.isReady(), qPrintable(c2.errorString())); + i.reset(c2.create(qmlContext(o.data()))); + object = i.data(); + } else { + object = o.data(); + } + + QQmlRefPointer<QQmlContextData> context = QQmlContextData::get(qmlContext(object)); const QQmlProperty property = QQmlPropertyPrivate::create( - passObject ? o.data() : nullptr, name, context, flags); + passObject ? object : nullptr, name, context, flags); const bool usesId = name.startsWith(QStringLiteral("self.")); const bool hasSignal = name.endsWith(QStringLiteral("foo")); @@ -2485,6 +2506,8 @@ void tst_qqmlproperty::initFlags() QVERIFY(!property.isValid()); } else if (hasSignal && !(flags & QQmlPropertyPrivate::InitFlag::AllowSignal)) { QVERIFY(!property.isValid()); + } else if (useInnerObject && passObject) { + QVERIFY(!property.isValid()); } else { QVERIFY(property.isValid()); if (name.endsWith(QStringLiteral("bar"))) { diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/BLACKLIST b/tests/auto/quick/pointerhandlers/flickableinterop/BLACKLIST index c4cfd085af..30c72e1d02 100644 --- a/tests/auto/quick/pointerhandlers/flickableinterop/BLACKLIST +++ b/tests/auto/quick/pointerhandlers/flickableinterop/BLACKLIST @@ -1,22 +1,9 @@ [touchAndDragHandlerOnFlickable] windows gcc -opensuse-leap [touchDragFlickableBehindSlider] windows gcc [touchDragFlickableBehindButton] windows gcc -# QTBUG-95887 -[mouseDragSlider] -opensuse-leap -# QTBUG-95887 -[touchDragFlickableBehindButton] -opensuse-leap -# QTBUG-95887 -[mouseClickButton] -opensuse-leap -# QTBUG-95887 -[mouseDragFlickableBehindSlider] -opensuse-leap # QTBUG-103061 [touchDragFlickableBehindItemWithHandlers] android diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp b/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp index 1a9a139aef..edcfaeea7b 100644 --- a/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp +++ b/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp @@ -71,6 +71,7 @@ void tst_FlickableInterop::createView(QScopedPointer<QQuickView> &window, const window.reset(new QQuickView); window->setSource(testFileUrl(fileName)); QTRY_COMPARE(window->status(), QQuickView::Ready); + window.data()->setFlag(Qt::FramelessWindowHint); QQuickViewTestUtils::centerOnScreen(window.data()); QQuickViewTestUtils::moveMouseAway(window.data()); diff --git a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp index 920bf77978..b3b2259319 100644 --- a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp @@ -458,10 +458,13 @@ void tst_HoverHandler::window() // QTBUG-98717 { QQmlEngine engine; QQmlComponent component(&engine); + const QPoint pos(30, 30); component.loadUrl(testFileUrl("windowCursorShape.qml")); QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(component.create())); QVERIFY(!window.isNull()); + window->setFramePosition(pos); window->show(); + QTRY_COMPARE(window->framePosition(), pos); QVERIFY(QTest::qWaitForWindowExposed(window.data())); #if QT_CONFIG(cursor) if (isPlatformWayland()) diff --git a/tests/auto/quick/qquickanimators/BLACKLIST b/tests/auto/quick/qquickanimators/BLACKLIST index f0da331c9f..6e9e3f5047 100644 --- a/tests/auto/quick/qquickanimators/BLACKLIST +++ b/tests/auto/quick/qquickanimators/BLACKLIST @@ -1,4 +1,3 @@ # QTBUG-89023 [testTransitionsWithImplicitFrom] ubuntu-22.04 -opensuse-leap diff --git a/tests/auto/quick/qquickanimators/tst_qquickanimators.cpp b/tests/auto/quick/qquickanimators/tst_qquickanimators.cpp index 92f75620bd..baea27c036 100644 --- a/tests/auto/quick/qquickanimators/tst_qquickanimators.cpp +++ b/tests/auto/quick/qquickanimators/tst_qquickanimators.cpp @@ -139,7 +139,7 @@ void tst_Animators::testTransitionsWithImplicitFrom() // transition to the "right" state rectangle->setState("right"); - QTRY_VERIFY(!controller->m_runningAnimators.isEmpty()); + QVERIFY(QTest::qWaitFor([&]{ return !controller->m_runningAnimators.isEmpty();})); auto r_job = *controller->m_runningAnimators.constBegin(); QVERIFY(r_job); QCOMPARE(r_job->from(), 0); @@ -153,7 +153,7 @@ void tst_Animators::testTransitionsWithImplicitFrom() // transition back to the "left" state rectangle->setState("left"); - QTRY_VERIFY(!controller->m_runningAnimators.isEmpty()); + QVERIFY(QTest::qWaitFor([&]{ return !controller->m_runningAnimators.isEmpty();})); auto l_job = *controller->m_runningAnimators.constBegin(); QVERIFY(l_job); QCOMPARE(l_job->from(), 100); // this was not working in older Qt versions diff --git a/tests/auto/quick/qquickapplication/BLACKLIST b/tests/auto/quick/qquickapplication/BLACKLIST index 1b7464e7c4..b8bc4363f1 100644 --- a/tests/auto/quick/qquickapplication/BLACKLIST +++ b/tests/auto/quick/qquickapplication/BLACKLIST @@ -1,3 +1,2 @@ -[active] -opensuse-42.3 -opensuse-leap +[state] +opensuse-leap # QTBUG-122031 diff --git a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp index b3633a9dcc..0bac4baaea 100644 --- a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp +++ b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp @@ -91,7 +91,8 @@ void tst_qquickapplication::active() } else { // Otherwise, app activation is triggered by window activation. window.show(); - window.requestActivate(); + if (QGuiApplication::platformName().toLower() != "xcb") + window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); QCOMPARE(QGuiApplication::focusWindow(), &window); QVERIFY(item->property("active").toBool()); diff --git a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp index 4ce2b7e3c6..58269a6463 100644 --- a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp +++ b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp @@ -1753,8 +1753,8 @@ void tst_qquickflickable::margins() QTRY_COMPARE(obj->contentX(), -30.); // Reduce top margin - obj->setTopMargin(20); - QTRY_COMPARE(obj->contentY(), -20.); + obj->setTopMargin(10); + QTRY_COMPARE(obj->contentY(), -10.); // position to the far right, including margin obj->setContentX(1600 + 50 - obj->width()); diff --git a/tests/auto/quick/qquicklistview/BLACKLIST b/tests/auto/quick/qquicklistview/BLACKLIST index e4da77acba..a686534d20 100644 --- a/tests/auto/quick/qquicklistview/BLACKLIST +++ b/tests/auto/quick/qquicklistview/BLACKLIST @@ -1,6 +1,3 @@ -[enforceRange_withoutHighlight] -opensuse-42.3 -opensuse-leap #QTBUG-53863 [populateTransitions] opensuse-42.1 diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index c43278c553..b63dec7320 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -2009,6 +2009,7 @@ void tst_QQuickListView::enforceRange_withoutHighlight() QTRY_COMPARE(listview->contentY(), expectedPos); expectedPos += 20 + 10; // scroll past 1st section and section delegate of 2nd section + QTRY_VERIFY(window.data()->isActive()); QTest::keyClick(window.data(), Qt::Key_Down); QTRY_COMPARE(listview->contentY(), expectedPos); diff --git a/tests/auto/quick/qquickloader/tst_qquickloader.cpp b/tests/auto/quick/qquickloader/tst_qquickloader.cpp index 7e9aa8c100..389f037166 100644 --- a/tests/auto/quick/qquickloader/tst_qquickloader.cpp +++ b/tests/auto/quick/qquickloader/tst_qquickloader.cpp @@ -4,6 +4,8 @@ #include <QSignalSpy> +#include <QtCore/QElapsedTimer> + #include <QtQml/QQmlContext> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index 7482367057..5942588c8d 100644 --- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -197,6 +197,8 @@ private slots: void positionViewAtLastRow(); void positionViewAtLastColumn_data(); void positionViewAtLastColumn(); + void positionViewAtRow_syncView(); + void positionViewAtColumn_syncView(); void itemAtCell_data(); void itemAtCell(); void leftRightTopBottomProperties_data(); @@ -4370,6 +4372,80 @@ void tst_QQuickTableView::positionViewAtLastColumn() } } +void tst_QQuickTableView::positionViewAtRow_syncView() +{ + // Check that if you call positionViewAtRow on a sync child, both + // the syncView and the syncChild will be positioned. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewV); + + auto model = TestModelAsVariant(100, 100); + tableView->setModel(model); + tableViewV->setModel(model); + QCOMPARE(tableViewV->syncDirection(), Qt::Vertical); + WAIT_UNTIL_POLISHED; + + QCOMPARE(tableView->topRow(), 0); + QCOMPARE(tableView->leftColumn(), 0); + QCOMPARE(tableViewV->topRow(), 0); + QCOMPARE(tableViewV->leftColumn(), 0); + + tableViewV->positionViewAtRow(50, QQuickTableView::AlignTop); + WAIT_UNTIL_POLISHED; + + QCOMPARE(tableView->leftColumn(), 0); + QCOMPARE(tableView->topRow(), 50); + QCOMPARE(tableViewV->leftColumn(), 0); + QCOMPARE(tableViewV->topRow(), 50); + + // Trying to position the sync child in an unsynced + // direction should only move the sync child. + tableViewV->positionViewAtColumn(90, QQuickTableView::AlignLeft); + WAIT_UNTIL_POLISHED_ARG(tableViewV); + + QCOMPARE(tableView->leftColumn(), 0); + QCOMPARE(tableView->topRow(), 50); + QCOMPARE(tableViewV->leftColumn(), 90); + QCOMPARE(tableViewV->topRow(), 50); +} + +void tst_QQuickTableView::positionViewAtColumn_syncView() +{ + // Check that if you call positionViewAtColumn on a sync child, both + // the syncView and the syncChild will be positioned. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewH); + + auto model = TestModelAsVariant(100, 100); + tableView->setModel(model); + tableViewH->setModel(model); + QCOMPARE(tableViewH->syncDirection(), Qt::Horizontal); + WAIT_UNTIL_POLISHED; + + QCOMPARE(tableView->topRow(), 0); + QCOMPARE(tableView->leftColumn(), 0); + QCOMPARE(tableViewH->topRow(), 0); + QCOMPARE(tableViewH->leftColumn(), 0); + + tableViewH->positionViewAtColumn(50, QQuickTableView::AlignLeft); + WAIT_UNTIL_POLISHED; + + QCOMPARE(tableView->leftColumn(), 50); + QCOMPARE(tableView->topRow(), 0); + QCOMPARE(tableViewH->leftColumn(), 50); + QCOMPARE(tableViewH->topRow(), 0); + + // Trying to position the sync child in an unsynced + // direction should only move the sync child. + tableViewH->positionViewAtRow(90, QQuickTableView::AlignTop); + WAIT_UNTIL_POLISHED_ARG(tableViewH); + + QCOMPARE(tableView->leftColumn(), 50); + QCOMPARE(tableView->topRow(), 0); + QCOMPARE(tableViewH->leftColumn(), 50); + QCOMPARE(tableViewH->topRow(), 90); +} + void tst_QQuickTableView::itemAtCell_data() { QTest::addColumn<QPoint>("cell"); diff --git a/tests/auto/quick/qquicktext/tst_qquicktext.cpp b/tests/auto/quick/qquicktext/tst_qquicktext.cpp index 797c8b16cf..4ba1c3bec6 100644 --- a/tests/auto/quick/qquicktext/tst_qquicktext.cpp +++ b/tests/auto/quick/qquicktext/tst_qquicktext.cpp @@ -4283,7 +4283,7 @@ void tst_qquicktext::baselineOffset_data() QTest::newRow("scaled font") << "hello world" << "hello\nworld" - << QByteArray("width: 200; minimumPointSize: 1; font.pointSize: 64; fontSizeMode: Text.HorizontalFit") + << QByteArray("width: 200; minimumPointSize: 1; font.pointSize: 10000; fontSizeMode: Text.HorizontalFit") << &expectedBaselineScaled << &expectedBaselineTop; @@ -4379,7 +4379,7 @@ void tst_qquicktext::baselineOffset_data() QTest::newRow("scaled font with padding") << "hello world" << "hello\nworld" - << QByteArray("width: 200; topPadding: 10; bottomPadding: 20; minimumPointSize: 1; font.pointSize: 64; fontSizeMode: Text.HorizontalFit") + << QByteArray("width: 200; topPadding: 10; bottomPadding: 20; minimumPointSize: 1; font.pointSize: 10000; fontSizeMode: Text.HorizontalFit") << &expectedBaselineScaled << &expectedBaselineTop; diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index 44745f8263..399627c4f1 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -2873,7 +2873,7 @@ void tst_qquicktextedit::cursorVisible() view.show(); view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); - QCOMPARE(&view, qGuiApp->focusWindow()); + QTRY_COMPARE(&view, qGuiApp->focusWindow()); QCOMPARE(edit.isCursorVisible(), false); @@ -4282,8 +4282,8 @@ void tst_qquicktextedit::preeditCursorRectangle() QQuickTextEdit *edit = qobject_cast<QQuickTextEdit *>(view.rootObject()); QVERIFY(edit); + QTRY_VERIFY(edit->findChild<QQuickItem *>("cursor") != nullptr); QQuickItem *cursor = edit->findChild<QQuickItem *>("cursor"); - QVERIFY(cursor); QSignalSpy editSpy(edit, SIGNAL(cursorRectangleChanged())); QSignalSpy panelSpy(qGuiApp->inputMethod(), SIGNAL(cursorRectangleChanged())); @@ -5714,7 +5714,7 @@ void tst_qquicktextedit::undo() window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); - QVERIFY(textEdit->hasActiveFocus()); + QTRY_VERIFY(textEdit->hasActiveFocus()); QVERIFY(!textEdit->canUndo()); QSignalSpy spy(textEdit, SIGNAL(canUndoChanged())); @@ -6185,7 +6185,7 @@ void tst_qquicktextedit::emptytags_QTBUG_22058() window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); QQuickTextEdit *input = qobject_cast<QQuickTextEdit *>(qvariant_cast<QObject *>(window.rootObject()->property("inputField"))); - QVERIFY(input->hasActiveFocus()); + QTRY_VERIFY(input->hasActiveFocus()); QInputMethodEvent event("", QList<QInputMethodEvent::Attribute>()); event.setCommitString("<b>Bold<"); @@ -6435,7 +6435,7 @@ void tst_qquicktextedit::keyEventPropagation() QSignalSpy upSpy(root, SIGNAL(keyUp(int))); QQuickTextEdit *textEdit = root->findChild<QQuickTextEdit *>(); - QVERIFY(textEdit->hasActiveFocus()); + QTRY_VERIFY(textEdit->hasActiveFocus()); simulateKey(&view, Qt::Key_Back); QCOMPARE(downSpy.size(), 1); QCOMPARE(upSpy.size(), 1); diff --git a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp index c944406e10..c6781c03f5 100644 --- a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp +++ b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp @@ -7044,8 +7044,8 @@ void tst_qquicktextinput::checkCursorDelegateWhenPaddingChanged() QQuickTextInput *textInput = view.rootObject()->findChild<QQuickTextInput *>("textInput"); QVERIFY(textInput); + QTRY_VERIFY(textInput->findChild<QQuickItem *>("cursorDelegate")); QQuickItem *cursorDelegate = textInput->findChild<QQuickItem *>("cursorDelegate"); - QVERIFY(cursorDelegate); QCOMPARE(cursorDelegate->x(), textInput->leftPadding()); QCOMPARE(cursorDelegate->y(), textInput->topPadding()); diff --git a/tests/auto/quickcontrols/controls/data/tst_button.qml b/tests/auto/quickcontrols/controls/data/tst_button.qml index 4ca8ebfd6d..f363e1b294 100644 --- a/tests/auto/quickcontrols/controls/data/tst_button.qml +++ b/tests/auto/quickcontrols/controls/data/tst_button.qml @@ -169,14 +169,19 @@ TestCase { let control1 = createTemporaryObject(button, testCase) verify(control1) + let pressedChangedCount1 = 0 let pressedCount1 = 0 - let pressedSpy1 = signalSpy.createObject(control1, {target: control1, signalName: "pressedChanged"}) + let pressedChangedSpy1 = signalSpy.createObject(control1, {target: control1, signalName: "pressedChanged"}) + verify(pressedChangedSpy1.valid) + + let pressedSpy1 = signalSpy.createObject(control1, {target: control1, signalName: "pressed"}) verify(pressedSpy1.valid) let touch = touchEvent(control1) touch.press(0, control1, 0, 0).commit().move(0, control1, control1.width - 1, control1.height - 1).commit() + compare(pressedChangedSpy1.count, ++pressedChangedCount1) compare(pressedSpy1.count, ++pressedCount1) compare(control1.pressed, true) @@ -185,34 +190,61 @@ TestCase { touch.stationary(0).move(1, control1).commit() touch.stationary(0).release(1).commit() + compare(pressedChangedSpy1.count, pressedChangedCount1) compare(pressedSpy1.count, pressedCount1) compare(control1.pressed, true) let control2 = createTemporaryObject(button, testCase, {y: control1.height}) verify(control2) + let pressedChangedCount2 = 0 let pressedCount2 = 0 - let pressedSpy2 = signalSpy.createObject(control2, {target: control2, signalName: "pressedChanged"}) + let pressedChangedSpy2 = signalSpy.createObject(control2, {target: control2, signalName: "pressedChanged"}) + verify(pressedChangedSpy2.valid) + + let pressedSpy2 = signalSpy.createObject(control2, {target: control2, signalName: "pressed"}) verify(pressedSpy2.valid) // press the second button touch.stationary(0).press(2, control2, 0, 0).commit() + compare(pressedChangedSpy2.count, ++pressedChangedCount2) compare(pressedSpy2.count, ++pressedCount2) compare(control2.pressed, true) - compare(pressedSpy1.count, pressedCount1) + compare(pressedChangedSpy1.count, pressedChangedCount1) + compare(pressedSpy1.count, pressedChangedCount1) compare(control1.pressed, true) // release both buttons touch.release(0, control1).release(2, control2).commit() - compare(pressedSpy2.count, ++pressedCount2) + compare(pressedChangedSpy2.count, ++pressedChangedCount2) + compare(pressedSpy2.count, pressedCount2) compare(control2.pressed, false) + compare(pressedChangedSpy1.count, ++pressedChangedCount1) + compare(control1.pressed, false) + + // press two buttons with two fingers, then release: each should only be pressed once + touch.press(1, control1, 0, 0).commit() + + compare(pressedChangedSpy1.count, ++pressedChangedCount1) compare(pressedSpy1.count, ++pressedCount1) + compare(control1.pressed, true) + + touch.stationary(1).press(2, control2).commit() + + compare(pressedChangedSpy2.count, ++pressedChangedCount2) + compare(pressedSpy2.count, ++pressedCount2) + compare(control2.pressed, true) + + touch.release(1, control1).release(2, control2).commit() compare(control1.pressed, false) + compare(control2.pressed, false) + compare(pressedSpy1.count, pressedCount1) + compare(pressedSpy2.count, pressedCount2) } function test_keys() { diff --git a/tests/auto/quickcontrols/controls/data/tst_tumbler.qml b/tests/auto/quickcontrols/controls/data/tst_tumbler.qml index 0491ab039e..2b0e90586d 100644 --- a/tests/auto/quickcontrols/controls/data/tst_tumbler.qml +++ b/tests/auto/quickcontrols/controls/data/tst_tumbler.qml @@ -804,7 +804,7 @@ TestCase { } if (distanceToReachContentY != 0) - mouseRelease(tumbler, tumblerXCenter(), itemCenterPos(1) + (data.contentY - defaultListViewTumblerOffset), Qt.LeftButton); + mouseRelease(tumbler, tumblerXCenter(), itemCenterPos(1).y + (data.contentY - defaultListViewTumblerOffset), Qt.LeftButton); } function test_listViewFlickAboveBounds_data() { diff --git a/tests/auto/quickcontrols/palette/data/inheritPaletteForPopupWithinItemView.qml b/tests/auto/quickcontrols/palette/data/inheritPaletteForPopupWithinItemView.qml new file mode 100644 index 0000000000..21e808aa2c --- /dev/null +++ b/tests/auto/quickcontrols/palette/data/inheritPaletteForPopupWithinItemView.qml @@ -0,0 +1,30 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: window + width: 200 + height: 200 + + property alias listView: listView + + ListView { + id: listView + anchors.fill: parent + model: 1 + delegate: Item { + property alias button: button + Popup { + id: popup + contentItem: Button { + id: button + text: "Test" + } + } + Component.onCompleted: { popup.open() } + } + } +} diff --git a/tests/auto/quickcontrols/palette/tst_palette.cpp b/tests/auto/quickcontrols/palette/tst_palette.cpp index 621475b86e..ddb9d08ff2 100644 --- a/tests/auto/quickcontrols/palette/tst_palette.cpp +++ b/tests/auto/quickcontrols/palette/tst_palette.cpp @@ -66,6 +66,7 @@ private slots: void comboBoxPopupWithThemeDefault(); void toolTipPaletteUpdate(); + void inheritPaletteForPopupWithinItemView(); }; tst_palette::tst_palette() @@ -610,6 +611,36 @@ void tst_palette::toolTipPaletteUpdate() QCOMPARE(toolTipPalette->toolTipText(), windowPalette->toolTipText()); } +void tst_palette::inheritPaletteForPopupWithinItemView() +{ + QQuickApplicationHelper helper(this, "inheritPaletteForPopupWithinItemView.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickItem *listView = window->property("listView").value<QQuickItem *>(); + QVERIFY(listView); + QQuickItem *contentItem = listView->property("contentItem").value<QQuickItem *>(); + QVERIFY(contentItem); + QQuickItem *item = contentItem->childItems().value(0); + QVERIFY(item); + auto *button = item->property("button").value<QQuickButton *>(); + QVERIFY(button); + + auto windowPalette = QQuickWindowPrivate::get(window)->palette(); + auto buttonPalette = QQuickItemPrivate::get(button)->palette(); + + QCOMPARE(windowPalette->window(), buttonPalette->window()); + QCOMPARE(windowPalette->buttonText(), buttonPalette->buttonText()); + + windowPalette->setWindow(Qt::red); + windowPalette->setButtonText(Qt::blue); + + QCOMPARE(windowPalette->window(), buttonPalette->window()); + QCOMPARE(windowPalette->buttonText(), buttonPalette->buttonText()); +} + QTEST_MAIN(tst_palette) #include "tst_palette.moc" diff --git a/tests/auto/quickcontrols/pointerhandlers/data/controlandmousearea.qml b/tests/auto/quickcontrols/pointerhandlers/data/controlandmousearea.qml index 00c680453a..730b187e2b 100644 --- a/tests/auto/quickcontrols/pointerhandlers/data/controlandmousearea.qml +++ b/tests/auto/quickcontrols/pointerhandlers/data/controlandmousearea.qml @@ -12,6 +12,7 @@ Item { MouseArea { id: outerMouseArea + objectName: "outerMouseArea" x: 10 y: 10 width: 200 @@ -25,14 +26,18 @@ Item { Button { id: buttonInTheMiddle + objectName: "buttonInTheMiddle" width: parent.width - 20 height: parent.height - 20 anchors.right: parent.right anchors.bottom: parent.bottom text: hovered ? "hovered" : "" + // Explicitly set this, as it's false on platforms like Android. + hoverEnabled: true MouseArea { id: innerMouseArea + objectName: "innerMouseArea" width: parent.width - 20 height: parent.height - 20 anchors.right: parent.right diff --git a/tests/auto/quickcontrols/pointerhandlers/tst_pointerhandlers.cpp b/tests/auto/quickcontrols/pointerhandlers/tst_pointerhandlers.cpp index 025c0f838c..7c411d3658 100644 --- a/tests/auto/quickcontrols/pointerhandlers/tst_pointerhandlers.cpp +++ b/tests/auto/quickcontrols/pointerhandlers/tst_pointerhandlers.cpp @@ -51,6 +51,8 @@ tst_pointerhandlers::tst_pointerhandlers() void tst_pointerhandlers::hover_controlInsideControl() { + SKIP_IF_NO_MOUSE_HOVER; + // Test that if you move the mouse over a control that is // a child of another control, both controls end up hovered. // A control should basically not block (accept) hover events. @@ -102,6 +104,8 @@ void tst_pointerhandlers::hover_controlInsideControl() void tst_pointerhandlers::hover_controlAndMouseArea() { + SKIP_IF_NO_MOUSE_HOVER; + QQuickView view(testFileUrl("controlandmousearea.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); diff --git a/tests/auto/quickcontrols/qquickcontrol/data/hoverInMouseArea.qml b/tests/auto/quickcontrols/qquickcontrol/data/hoverInMouseArea.qml new file mode 100644 index 0000000000..e0ef55336b --- /dev/null +++ b/tests/auto/quickcontrols/qquickcontrol/data/hoverInMouseArea.qml @@ -0,0 +1,40 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias control: control + property alias textArea: textArea + property alias textField: textField + + MouseArea { + anchors.fill: parent + + Column { + Control { + id: control + implicitWidth: 100 + implicitHeight: 30 + hoverEnabled: true + background: Rectangle { + color: control.hovered ? "salmon" : "grey" + } + } + + TextArea { + id: textArea + hoverEnabled: true + } + + TextField { + id: textField + hoverEnabled: true + } + } + } +} diff --git a/tests/auto/quickcontrols/qquickcontrol/tst_qquickcontrol.cpp b/tests/auto/quickcontrols/qquickcontrol/tst_qquickcontrol.cpp index ddb4b7ff8b..334cbed67e 100644 --- a/tests/auto/quickcontrols/qquickcontrol/tst_qquickcontrol.cpp +++ b/tests/auto/quickcontrols/qquickcontrol/tst_qquickcontrol.cpp @@ -7,6 +7,8 @@ #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtQuickTemplates2/private/qquickbutton_p.h> +#include <QtQuickTemplates2/private/qquicktextarea_p.h> +#include <QtQuickTemplates2/private/qquicktextfield_p.h> #include <QtQuickControlsTestUtils/private/qtest_quickcontrols_p.h> #include <QtQuick/private/qquicktext_p_p.h> @@ -24,6 +26,7 @@ private slots: void flickable(); void fractionalFontSize(); void resizeBackgroundKeepsBindings(); + void hoverInMouseArea(); private: QScopedPointer<QPointingDevice> touchDevice; @@ -106,6 +109,34 @@ void tst_QQuickControl::resizeBackgroundKeepsBindings() QVERIFY(background->bindableHeight().hasBinding()); } +void tst_QQuickControl::hoverInMouseArea() +{ + SKIP_IF_NO_MOUSE_HOVER; + + QQuickApplicationHelper helper(this, QStringLiteral("hoverInMouseArea.qml")); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + const auto *control = window->property("control").value<QQuickControl *>(); + QVERIFY(control); + PointLerper pointLerper(window); + pointLerper.move(mapCenterToWindow(control)); + // It's necessary to use PointLerper here, + // otherwise the isHovered checks are flaky on most platforms. + QVERIFY(control->isHovered()); + + const auto *textArea = window->property("textArea").value<QQuickTextArea *>(); + QVERIFY(textArea); + pointLerper.move(mapCenterToWindow(textArea)); + QVERIFY(textArea->isHovered()); + + const auto *textField = window->property("textField").value<QQuickTextField *>(); + QVERIFY(textField); + pointLerper.move(mapCenterToWindow(textField)); + QVERIFY(textField->isHovered()); +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickControl) #include "tst_qquickcontrol.moc" diff --git a/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp b/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp index bec3a1f27c..041c3d7ab9 100644 --- a/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp +++ b/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp @@ -146,11 +146,8 @@ void tst_QQuickMenu::count() void tst_QQuickMenu::mouse() { - SKIP_IF_NO_WINDOW_ACTIVATION - - if ((QGuiApplication::platformName() == QLatin1String("offscreen")) - || (QGuiApplication::platformName() == QLatin1String("minimal"))) - QSKIP("Mouse hovering not functional on offscreen/minimal platforms"); + SKIP_IF_NO_WINDOW_ACTIVATION; + SKIP_IF_NO_MOUSE_HOVER; QQuickControlsApplicationHelper helper(this, QLatin1String("applicationwindow.qml")); QVERIFY2(helper.ready, helper.failureMessage()); @@ -1332,9 +1329,7 @@ void tst_QQuickMenu::subMenuMouse_data() void tst_QQuickMenu::subMenuMouse() { - if ((QGuiApplication::platformName() == QLatin1String("offscreen")) - || (QGuiApplication::platformName() == QLatin1String("minimal"))) - QSKIP("Mouse hovering not functional on offscreen/minimal platforms"); + SKIP_IF_NO_MOUSE_HOVER; QFETCH(bool, cascade); @@ -1451,9 +1446,7 @@ void tst_QQuickMenu::subMenuDisabledMouse_data() // QTBUG-69540 void tst_QQuickMenu::subMenuDisabledMouse() { - if ((QGuiApplication::platformName() == QLatin1String("offscreen")) - || (QGuiApplication::platformName() == QLatin1String("minimal"))) - QSKIP("Mouse hovering not functional on offscreen/minimal platforms"); + SKIP_IF_NO_MOUSE_HOVER; QFETCH(bool, cascade); @@ -2021,9 +2014,7 @@ void tst_QQuickMenu::disableWhenTriggered_data() // Tests that the menu is dismissed when a menu item sets "enabled = false" in onTriggered(). void tst_QQuickMenu::disableWhenTriggered() { - if ((QGuiApplication::platformName() == QLatin1String("offscreen")) - || (QGuiApplication::platformName() == QLatin1String("minimal"))) - QSKIP("Mouse hovering not functional on offscreen/minimal platforms"); + SKIP_IF_NO_MOUSE_HOVER; QFETCH(int, menuItemIndex); QFETCH(int, subMenuItemIndex); diff --git a/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp b/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp index b331fda1a3..ee593e1701 100644 --- a/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp +++ b/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp @@ -67,11 +67,8 @@ void tst_qquickmenubar::delegate() void tst_qquickmenubar::mouse() { - SKIP_IF_NO_WINDOW_ACTIVATION - - if ((QGuiApplication::platformName() == QLatin1String("offscreen")) - || (QGuiApplication::platformName() == QLatin1String("minimal"))) - QSKIP("Mouse highlight not functional on offscreen/minimal platforms"); + SKIP_IF_NO_WINDOW_ACTIVATION; + SKIP_IF_NO_MOUSE_HOVER; QQmlApplicationEngine engine(testFileUrl("menubar.qml")); @@ -720,9 +717,7 @@ void tst_qquickmenubar::addRemove() void tst_qquickmenubar::checkHighlightWhenMenuDismissed() { - if ((QGuiApplication::platformName() == QLatin1String("offscreen")) - || (QGuiApplication::platformName() == QLatin1String("minimal"))) - QSKIP("Mouse highlight not functional on offscreen/minimal platforms"); + SKIP_IF_NO_MOUSE_HOVER; QQmlApplicationEngine engine(testFileUrl("checkHighlightWhenDismissed.qml")); QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0))); diff --git a/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp b/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp index b046be8d44..966f908e80 100644 --- a/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp +++ b/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp @@ -1431,6 +1431,7 @@ void tst_QQuickPopup::closeOnEscapeWithNestedPopups() QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; window->show(); + window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); // The stack view should have two items, and it should pop the second when escape is pressed @@ -1498,6 +1499,7 @@ void tst_QQuickPopup::closeOnEscapeWithVisiblePopup() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); + window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickPopup *popup = window->findChild<QQuickPopup *>("popup"); @@ -1626,6 +1628,7 @@ void tst_QQuickPopup::disabledPalette() QQuickWindow *window = helper.window; window->show(); + window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickPopup *popup = window->property("popup").value<QQuickPopup*>(); @@ -1665,6 +1668,7 @@ void tst_QQuickPopup::disabledParentPalette() QQuickWindow *window = helper.window; window->show(); + window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickPopup *popup = window->property("popup").value<QQuickPopup*>(); @@ -1776,6 +1780,7 @@ void tst_QQuickPopup::tabFence() QQuickWindow *window = helper.window; window->show(); + window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickPopup *popup = window->property("dialog").value<QQuickPopup*>(); @@ -1885,6 +1890,7 @@ void tst_QQuickPopup::destroyDuringExitTransition() QQuickWindow *window = helper.window; window->show(); + window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); { diff --git a/tests/auto/quickcontrols/qquicktreeviewdelegate/BLACKLIST b/tests/auto/quickcontrols/qquicktreeviewdelegate/BLACKLIST deleted file mode 100644 index ed6d7fd2cf..0000000000 --- a/tests/auto/quickcontrols/qquicktreeviewdelegate/BLACKLIST +++ /dev/null @@ -1,6 +0,0 @@ -# perhaps related to QTBUG-103072 -[dragToSelect] -android -# perhaps related to QTBUG-103064 -[pressAndHoldToSelect] -android diff --git a/tests/auto/quickdialogs/qquickcolordialogimpl/data/colorDialogWithoutWindow.qml b/tests/auto/quickdialogs/qquickcolordialogimpl/data/colorDialogWithoutWindow.qml new file mode 100644 index 0000000000..5b9bab708c --- /dev/null +++ b/tests/auto/quickdialogs/qquickcolordialogimpl/data/colorDialogWithoutWindow.qml @@ -0,0 +1,27 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +Rectangle { + width: 480 + height: 640 + color: dialog.selectedColor + + property alias dialog: dialog + + function doneAccepted() { + dialog.done(ColorDialog.Accepted) + } + + function doneRejected() { + dialog.done(ColorDialog.Rejected) + } + + ColorDialog { + id: dialog + objectName: "ColorDialog" + } +} diff --git a/tests/auto/quickdialogs/qquickcolordialogimpl/data/colorDialogWithoutWindowVisibleTrue.qml b/tests/auto/quickdialogs/qquickcolordialogimpl/data/colorDialogWithoutWindowVisibleTrue.qml new file mode 100644 index 0000000000..b146d61513 --- /dev/null +++ b/tests/auto/quickdialogs/qquickcolordialogimpl/data/colorDialogWithoutWindowVisibleTrue.qml @@ -0,0 +1,28 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +Rectangle { + width: 480 + height: 640 + color: dialog.selectedColor + + property alias dialog: dialog + + function doneAccepted() { + dialog.done(ColorDialog.Accepted) + } + + function doneRejected() { + dialog.done(ColorDialog.Rejected) + } + + ColorDialog { + id: dialog + objectName: "ColorDialog" + visible: true + } +} diff --git a/tests/auto/quickdialogs/qquickcolordialogimpl/data/windowSwapping.qml b/tests/auto/quickdialogs/qquickcolordialogimpl/data/windowSwapping.qml new file mode 100644 index 0000000000..5215feb243 --- /dev/null +++ b/tests/auto/quickdialogs/qquickcolordialogimpl/data/windowSwapping.qml @@ -0,0 +1,58 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +Window { + width: 480 + height: 640 + + property alias dialog: dialog + + function getSubWindow1 () { + return subwindow1 + } + + function getSubWindow2 () { + return subwindow2 + } + + function goToSubWindow1() { + dialog.close() + dialog.parentWindow = subwindow1 + dialog.open() + } + + function goToSubWindow2() { + dialog.close() + dialog.parentWindow = subwindow2 + dialog.open() + } + + function resetParentWindow() { + dialog.close() + dialog.parentWindow = undefined + dialog.open() + } + + Window { + id: subwindow1 + width: 480 + height: 640 + visible: true + } + + Window { + id: subwindow2 + width: 480 + height: 640 + visible: true + } + + ColorDialog { + id: dialog + objectName: "ColorDialog" + } +} diff --git a/tests/auto/quickdialogs/qquickcolordialogimpl/tst_qquickcolordialogimpl.cpp b/tests/auto/quickdialogs/qquickcolordialogimpl/tst_qquickcolordialogimpl.cpp index c8a1eb2231..9a2bd299d3 100644 --- a/tests/auto/quickdialogs/qquickcolordialogimpl/tst_qquickcolordialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickcolordialogimpl/tst_qquickcolordialogimpl.cpp @@ -4,6 +4,7 @@ #include <QtTest/qtest.h> #include <QtQuickTest/quicktest.h> #include <QtTest/qsignalspy.h> +#include <QtQuick/qquickview.h> #include <QtQuick/private/qquicklistview_p.h> #include <QtQuickControlsTestUtils/private/controlstestutils_p.h> #include <QtQuickControlsTestUtils/private/dialogstestutils_p.h> @@ -53,6 +54,9 @@ private slots: void changeColorFromTextFields(); void windowTitle_data(); void windowTitle(); + void workingInsideQQuickViewer_data(); + void workingInsideQQuickViewer(); + void dialogCanMoveBetweenWindows(); private: bool closePopup(DialogTestHelper<QQuickColorDialog, QQuickColorDialogImpl> *dialogHelper, @@ -456,6 +460,67 @@ void tst_QQuickColorDialogImpl::windowTitle() CLOSE_DIALOG("Ok"); } +void tst_QQuickColorDialogImpl::workingInsideQQuickViewer_data() +{ + QTest::addColumn<bool>("initialVisible"); + QTest::addColumn<QString>("urlToQmlFile"); + QTest::newRow("visible: false") << false << "colorDialogWithoutWindow.qml"; + QTest::newRow("visible: true") << true << "colorDialogWithoutWindowVisibleTrue.qml"; +} + +void tst_QQuickColorDialogImpl::workingInsideQQuickViewer() // QTBUG-106598 +{ + QFETCH(bool, initialVisible); + QFETCH(QString, urlToQmlFile); + + QQuickView dialogView; + dialogView.setSource(testFileUrl(urlToQmlFile)); + dialogView.show(); + + auto dialog = dialogView.findChild<QQuickColorDialog *>("ColorDialog"); + QVERIFY(dialog); + QCOMPARE(dialog->isVisible(), initialVisible); + + dialog->open(); + QVERIFY(dialog->isVisible()); +} + +void tst_QQuickColorDialogImpl::dialogCanMoveBetweenWindows() +{ + DialogTestHelper<QQuickColorDialog, QQuickColorDialogImpl> dialogHelper(this, "windowSwapping.qml"); + QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage()); + QVERIFY(dialogHelper.waitForWindowActive()); + + QVERIFY(dialogHelper.openDialog()); + QTRY_VERIFY(dialogHelper.isQuickDialogOpen()); + + QCOMPARE(dialogHelper.quickDialog->parent(), dialogHelper.window()); + QVariant subWindow1; + QVariant subWindow2; + + QMetaObject::invokeMethod(dialogHelper.window(), "getSubWindow1", Q_RETURN_ARG(QVariant, subWindow1)); + QMetaObject::invokeMethod(dialogHelper.window(), "getSubWindow2", Q_RETURN_ARG(QVariant, subWindow2)); + + // Confirm that the dialog can swap to different windows + QMetaObject::invokeMethod(dialogHelper.window(), "goToSubWindow1"); + QCOMPARE(dialogHelper.dialog->parentWindow(), qvariant_cast<QQuickWindow *>(subWindow1)); + + QMetaObject::invokeMethod(dialogHelper.window(), "goToSubWindow2"); + QCOMPARE(dialogHelper.dialog->parentWindow(), qvariant_cast<QQuickWindow *>(subWindow2)); + + QMetaObject::invokeMethod(dialogHelper.window(), "resetParentWindow"); + QTRY_COMPARE(dialogHelper.quickDialog->parent(), dialogHelper.window()); + + dialogHelper.quickDialog->contentItem()->window()->close(); + QVERIFY(dialogHelper.openDialog()); + dialogHelper.quickDialog->contentItem()->window()->show(); + + QVERIFY(QTest::qWaitForWindowActive(dialogHelper.quickDialog->contentItem()->window())); + QVERIFY(QQuickTest::qWaitForPolish(dialogHelper.quickDialog->contentItem()->window())); + + CLOSE_DIALOG("Ok"); +} + QTEST_MAIN(tst_QQuickColorDialogImpl) #include "tst_qquickcolordialogimpl.moc" diff --git a/tests/benchmarks/qml/librarymetrics_performance/tst_librarymetrics_performance.cpp b/tests/benchmarks/qml/librarymetrics_performance/tst_librarymetrics_performance.cpp index f64a44d66a..7a10eaba5b 100644 --- a/tests/benchmarks/qml/librarymetrics_performance/tst_librarymetrics_performance.cpp +++ b/tests/benchmarks/qml/librarymetrics_performance/tst_librarymetrics_performance.cpp @@ -6,6 +6,7 @@ #include <QQmlEngine> #include <QQmlComponent> #include <QDebug> +#include <QElapsedTimer> // This benchmark produces performance statistics // for the standard set of elements, properties and expressions which diff --git a/examples/qml/qmldom/CMakeLists.txt b/tests/manual/qmldom/CMakeLists.txt index 383f685bd9..8de61c2261 100644 --- a/examples/qml/qmldom/CMakeLists.txt +++ b/tests/manual/qmldom/CMakeLists.txt @@ -12,14 +12,12 @@ endif() set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/qml/qmldomloadeditwrite") -find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Qml) +find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Qml QmlDomPrivate) add_compile_definitions( QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/../../../tests/auto/qmldom/domdata" ) -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../../src/qmldom/standalone qmldom) - qt_add_executable(qmldomloadeditwrite qmldomloadeditwrite.cpp ) @@ -27,7 +25,7 @@ qt_add_executable(qmldomloadeditwrite target_link_libraries(qmldomloadeditwrite PUBLIC Qt6::Core Qt6::Qml - qmldomlib + Qt::QmlDomPrivate ) install(TARGETS qmldomloadeditwrite diff --git a/examples/qml/qmldom/qmldomloadeditwrite.cpp b/tests/manual/qmldom/qmldomloadeditwrite.cpp index 7c7609651b..68a6b993f3 100644 --- a/examples/qml/qmldom/qmldomloadeditwrite.cpp +++ b/tests/manual/qmldom/qmldomloadeditwrite.cpp @@ -1,15 +1,15 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause // common declarations -#include "qmldom/qqmldomitem_p.h" +#include <QtQmlDom/private/qqmldomitem_p.h> // comparisons of two DomItems -#include "qmldom/qqmldomcompare_p.h" +#include <QtQmlDom/private/qqmldomcompare_p.h> // field filters to compare only selected fields (ignore for example location changes) -#include "qmldom/qqmldomfieldfilter_p.h" +#include <QtQmlDom/private/qqmldomfieldfilter_p.h> // needed to edit and cast to concrete type (PropertyDefinition, ScriptExpression,...) -#include "qmldom/qqmldomelements_p.h" +#include <QtQmlDom/private/qqmldomelements_p.h> // cast of the top level items (DomEnvironments,...) -#include "qmldom/qqmldomtop_p.h" +#include <QtQmlDom/private/qqmldomtop_p.h> #include <QtTest/QtTest> #include <QCborValue> diff --git a/tools/qmlcachegen/CMakeLists.txt b/tools/qmlcachegen/CMakeLists.txt index 3f58390f2d..5923937364 100644 --- a/tools/qmlcachegen/CMakeLists.txt +++ b/tools/qmlcachegen/CMakeLists.txt @@ -1,8 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -# Generated from qmlcachegen.pro. - ##################################################################### ## qmlcachegen Tool: ##################################################################### @@ -20,37 +18,7 @@ qt_internal_add_tool(${target_name} LIBRARIES Qt::QmlCompilerPrivate ) -qt_internal_return_unless_building_tools() - -#### Keys ignored in scope 1:.:.:qmlcachegen.pro:<TRUE>: -# CMAKE_BIN_DIR = "$$cmakeRelativePath($$[QT_HOST_BINS], $$[QT_INSTALL_PREFIX])" -# QMAKE_SUBSTITUTES = "cmake_config_file" -# QMAKE_TARGET_DESCRIPTION = "QML" "Cache" "Generator" -# _OPTION = "host_build" -# build_integration.files = "qmlcache.prf" "qtquickcompiler.prf" -# build_integration.path = "$$[QT_HOST_DATA]/mkspecs/features" -# cmake_build_integration.files = "$$cmake_config_file.output" -# cmake_build_integration.path = "$$[QT_INSTALL_LIBS]/cmake/Qt6QuickCompiler" -# cmake_config_file.input = "$$PWD/Qt6QuickCompilerConfig.cmake.in" -# cmake_config_file.output = "$$MODULE_BASE_OUTDIR/lib/cmake/Qt6QuickCompiler/Qt6QuickCompilerConfig.cmake" - -## Scopes: -##################################################################### - -#### Keys ignored in scope 2:.:.:qmlcachegen.pro:prefix_build: -# INSTALLS = "cmake_build_integration" "build_integration" -#### Keys ignored in scope 3:.:.:qmlcachegen.pro:else: -# COPIES = "cmake_build_integration" "build_integration" - -#### Keys ignored in scope 4:.:.:qmlcachegen.pro:CMAKE_BIN_DIR___contains___^\\.\\./._x_: -# CMAKE_BIN_DIR = "$$[QT_HOST_BINS]/" -# CMAKE_BIN_DIR_IS_ABSOLUTE = "True" - -#### Keys ignored in scope 5:.:.:qmlcachegen.pro:QMAKE_HOST.os___equals___Windows: -# CMAKE_BIN_SUFFIX = ".exe" - -# special case begin # Install public prf files. set(qmlcachegen_mkspecs "${CMAKE_CURRENT_SOURCE_DIR}/qmlcache.prf" @@ -60,4 +28,5 @@ set(mkspecs_install_dir "${INSTALL_MKSPECSDIR}") qt_path_join(mkspecs_install_dir "${QT_INSTALL_DIR}" "${mkspecs_install_dir}" "features") qt_copy_or_install(FILES "${qmlcachegen_mkspecs}" DESTINATION ${mkspecs_install_dir}) -# special case end + +qt_internal_return_unless_building_tools() |
