diff options
author | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2024-10-04 14:54:44 +0300 |
---|---|---|
committer | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2024-10-04 14:54:44 +0300 |
commit | 9886ff37350f661aad5b8bfaf2776e8429f208a7 (patch) | |
tree | 6bc704d0ad3973b8a5f2a53b3beef67cb848f9a8 | |
parent | b078003a3c81f1438dcaf932371d564003c99395 (diff) | |
parent | 33e04685b27fc887dea2f71dbdf23850febc54dd (diff) |
Merge tag 'v6.2.10-lts' into tqtc/lts-6.2-opensourcev6.2.10-lts-lgpl
Qt 6.2.10-lts release
Conflicts solved:
dependencies.yaml
Change-Id: Ibdf006fa08cddc80ad30fb9ce1089305729d4ece
141 files changed, 3351 insertions, 660 deletions
diff --git a/.cmake.conf b/.cmake.conf index beccb22d04..31a36e3b07 100644 --- a/.cmake.conf +++ b/.cmake.conf @@ -1,2 +1,2 @@ -set(QT_REPO_MODULE_VERSION "6.2.9") +set(QT_REPO_MODULE_VERSION "6.2.10") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "") diff --git a/.qmake.conf b/.qmake.conf index 1763149193..4c0875fa17 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -5,4 +5,4 @@ DEFINES += QT_NO_JAVA_STYLE_ITERATORS QQC2_SOURCE_TREE = $$PWD -MODULE_VERSION = 6.2.9 +MODULE_VERSION = 6.2.10 diff --git a/dependencies.yaml b/dependencies.yaml index 01e8b423de..1793c7e6b8 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -3,11 +3,11 @@ dependencies: ref: 017d80e12fa50c50fa6751a039d3a7c9e799f34c required: true ../tqtc-qtimageformats: - ref: ab15bcca79372d79271bfda97d93140fd6df9889 + ref: 89e4bf6b362f2bf6b12aa453c19a98a03b190f2c required: false ../tqtc-qtshadertools: - ref: 35662703a974d3929d1ea039c2534271cf60700f + ref: e6be6ff4be1820e5377d78efa388321de216b66f required: false ../tqtc-qtsvg: - ref: b50df0589ace2172bb6cbcf099f9f6cc2578759b + ref: 2dc622e881b357c0899311cc8cc358d8487f5ad4 required: false diff --git a/examples/quickcontrols2/chattutorial/chapter4/sqlcontactmodel.cpp b/examples/quickcontrols2/chattutorial/chapter4/sqlcontactmodel.cpp index 6203ebec1e..017e7f4a97 100644 --- a/examples/quickcontrols2/chattutorial/chapter4/sqlcontactmodel.cpp +++ b/examples/quickcontrols2/chattutorial/chapter4/sqlcontactmodel.cpp @@ -84,7 +84,7 @@ SqlContactModel::SqlContactModel(QObject *parent) : if (!query.exec("SELECT * FROM Contacts")) qFatal("Contacts SELECT query failed: %s", qPrintable(query.lastError().text())); - setQuery(query); + setQuery(std::move(query)); if (lastError().isValid()) qFatal("Cannot set query on SqlContactModel: %s", qPrintable(lastError().text())); } diff --git a/examples/quickcontrols2/chattutorial/chapter5/sqlcontactmodel.cpp b/examples/quickcontrols2/chattutorial/chapter5/sqlcontactmodel.cpp index 6203ebec1e..017e7f4a97 100644 --- a/examples/quickcontrols2/chattutorial/chapter5/sqlcontactmodel.cpp +++ b/examples/quickcontrols2/chattutorial/chapter5/sqlcontactmodel.cpp @@ -84,7 +84,7 @@ SqlContactModel::SqlContactModel(QObject *parent) : if (!query.exec("SELECT * FROM Contacts")) qFatal("Contacts SELECT query failed: %s", qPrintable(query.lastError().text())); - setQuery(query); + setQuery(std::move(query)); if (lastError().isValid()) qFatal("Cannot set query on SqlContactModel: %s", qPrintable(lastError().text())); } diff --git a/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-hovered.png b/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-hovered.png Binary files differindex 26add20cfc..e69de29bb2 100644 --- a/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-hovered.png +++ b/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-hovered.png diff --git a/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-hovered@2x.png b/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-hovered@2x.png Binary files differindex 01d8136d51..e69de29bb2 100644 --- a/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-hovered@2x.png +++ b/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-hovered@2x.png diff --git a/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-pressed.png b/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-pressed.png Binary files differindex 435acd1466..e69de29bb2 100644 --- a/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-pressed.png +++ b/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-pressed.png diff --git a/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-pressed@2x.png b/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-pressed@2x.png Binary files differindex 9bab57904d..e69de29bb2 100644 --- a/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-pressed@2x.png +++ b/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background-pressed@2x.png diff --git a/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background.png b/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background.png Binary files differindex 8aab4d3280..e69de29bb2 100644 --- a/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background.png +++ b/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background.png diff --git a/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background@2x.png b/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background@2x.png Binary files differindex a856971185..e69de29bb2 100644 --- a/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background@2x.png +++ b/examples/quickcontrols2/imagine/automotive/imagine-assets/dial-background@2x.png diff --git a/examples/quickcontrols2/imagine/automotive/qml/automotive.qml b/examples/quickcontrols2/imagine/automotive/qml/automotive.qml index 4734175870..7a8ce2224f 100644 --- a/examples/quickcontrols2/imagine/automotive/qml/automotive.qml +++ b/examples/quickcontrols2/imagine/automotive/qml/automotive.qml @@ -160,7 +160,7 @@ ApplicationWindow { Item {} ColumnLayout { - spacing: 16 + spacing: 12 ButtonGroup { id: viewButtonGroup @@ -176,6 +176,7 @@ ApplicationWindow { Button { text: qsTr("Compact") font.pixelSize: fontSizeExtraSmall + checkable: true checked: true Layout.fillWidth: true @@ -203,13 +204,8 @@ ApplicationWindow { stepSize: 1 Layout.alignment: Qt.AlignHCenter - Layout.minimumWidth: 64 - Layout.minimumHeight: 64 Layout.preferredWidth: 128 Layout.preferredHeight: 128 - Layout.maximumWidth: 128 - Layout.maximumHeight: 128 - Layout.fillHeight: true Label { text: volumeDial.value.toFixed(0) @@ -224,7 +220,7 @@ ApplicationWindow { } RowLayout { - Layout.topMargin: 16 + Layout.topMargin: 8 LargeLabel { id: radioOption @@ -282,18 +278,18 @@ ApplicationWindow { } model: ListModel { - ListElement { name: "V-Radio"; frequency: "105.5 MHz" } - ListElement { name: "World News"; frequency: "93.4 MHz" } - ListElement { name: "TekStep FM"; frequency: "95.0 MHz" } - ListElement { name: "Classic Radio"; frequency: "89.9 MHz" } - ListElement { name: "Buena Vista FM"; frequency: "100.8 MHz" } - ListElement { name: "Drive-by Radio"; frequency: "99.1 MHz" } - ListElement { name: "Unknown #1"; frequency: "104.5 MHz" } - ListElement { name: "Unknown #2"; frequency: "91.2 MHz" } - ListElement { name: "Unknown #3"; frequency: "93.8 MHz" } - ListElement { name: "Unknown #4"; frequency: "80.4 MHz" } - ListElement { name: "Unknown #5"; frequency: "101.1 MHz" } - ListElement { name: "Unknown #6"; frequency: "92.2 MHz" } + ListElement { name: "V-Radio"; frequency: "105.5" } + ListElement { name: "World News"; frequency: "93.4" } + ListElement { name: "TekStep FM"; frequency: "95.0" } + ListElement { name: "Classic Radio"; frequency: "89.9" } + ListElement { name: "Buena Vista FM"; frequency: "100.8" } + ListElement { name: "Drive-by Radio"; frequency: "99.1" } + ListElement { name: "Unknown #1"; frequency: "104.5" } + ListElement { name: "Unknown #2"; frequency: "91.2" } + ListElement { name: "Unknown #3"; frequency: "93.8" } + ListElement { name: "Unknown #4"; frequency: "80.4" } + ListElement { name: "Unknown #5"; frequency: "101.1" } + ListElement { name: "Unknown #6"; frequency: "92.2" } } delegate: ItemDelegate { id: stationDelegate @@ -309,12 +305,16 @@ ApplicationWindow { text: model.name font: stationDelegate.font horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + Layout.fillWidth: true } Label { text: model.frequency font: stationDelegate.font horizontalAlignment: Text.AlignRight + elide: Text.ElideRight + Layout.fillWidth: true } } @@ -325,14 +325,12 @@ ApplicationWindow { Frame { Layout.fillWidth: true - RowLayout { + ColumnLayout { anchors.fill: parent Label { text: qsTr("Sort by") font.pixelSize: fontSizeExtraSmall - - Layout.alignment: Qt.AlignTop } ColumnLayout { diff --git a/src/imports/builtins/jsroot.qmltypes b/src/imports/builtins/jsroot.qmltypes index 0a8b70babe..39878d8cf3 100644 --- a/src/imports/builtins/jsroot.qmltypes +++ b/src/imports/builtins/jsroot.qmltypes @@ -1356,6 +1356,7 @@ Module { Method { name: "formatTime" } Method { name: "formatDateTime" } Method { name: "locale" } + Method { name: "url" } Method { name: "resolvedUrl" } Method { name: "openUrlExternally" } Method { name: "font" } diff --git a/src/labs/folderlistmodel/qquickfolderlistmodel.cpp b/src/labs/folderlistmodel/qquickfolderlistmodel.cpp index b86ddfe397..b05613b410 100644 --- a/src/labs/folderlistmodel/qquickfolderlistmodel.cpp +++ b/src/labs/folderlistmodel/qquickfolderlistmodel.cpp @@ -252,16 +252,16 @@ QString QQuickFolderListModelPrivate::resolvePath(const QUrl &path) Components access names and paths via the following roles: \list - \li \c fileName - \li \c filePath - \li \c fileURL (since Qt 5.2; deprecated since Qt 5.15) - \li \c fileUrl (since Qt 5.15) - \li \c fileBaseName - \li \c fileSuffix - \li \c fileSize - \li \c fileModified - \li \c fileAccessed - \li \c fileIsDir + \li \c fileName (\c string) + \li \c filePath (\c string) + \li \c fileURL (\c url) (since Qt 5.2; deprecated since Qt 5.15) + \li \c fileUrl (\c url) (since Qt 5.15) + \li \c fileBaseName (\c string) + \li \c fileSuffix (\c string) + \li \c fileSize (\c qlonglong) + \li \c fileModified (\c date) + \li \c fileAccessed (\c date) + \li \c fileIsDir (\c bool) \endlist Additionally a file entry can be differentiated from a folder entry via the @@ -304,6 +304,7 @@ QString QQuickFolderListModelPrivate::resolvePath(const QUrl &path) Component { id: fileDelegate + required property string fileName Text { text: fileName } } @@ -395,6 +396,7 @@ QHash<int, QByteArray> QQuickFolderListModel::roleNames() const /*! \qmlproperty int FolderListModel::count + \readonly Returns the number of items in the current folder that match the filter criteria. @@ -419,7 +421,8 @@ QModelIndex QQuickFolderListModel::index(int row, int , const QModelIndex &) con The value must be a \c file: or \c qrc: URL, or a relative URL. - The default value is an invalid URL. + The default value is the application's working directory at the time + when the FolderListModel is first initialized. */ QUrl QQuickFolderListModel::folder() const { @@ -493,6 +496,7 @@ void QQuickFolderListModel::setRootFolder(const QUrl &path) /*! \qmlproperty url FolderListModel::parentFolder + \readonly Returns the URL of the parent of the current \l folder. */ @@ -566,11 +570,11 @@ void QQuickFolderListModel::componentComplete() The \a sortField property contains the field to use for sorting. \c sortField may be one of: - \value Unsorted no sorting is applied - \value Name sort by filename - \value Time sort by time modified - \value Size sort by file size - \value Type sort by file type (extension) + \value FolderListModel.Unsorted no sorting is applied + \value FolderListModel.Name sort by filename (default) + \value FolderListModel.Time sort by time modified + \value FolderListModel.Size sort by file size + \value FolderListModel.Type sort by file type/extension \sa sortReversed */ @@ -818,6 +822,7 @@ void QQuickFolderListModel::setCaseSensitive(bool on) /*! \qmlproperty enumeration FolderListModel::status \since 5.11 + \readonly This property holds the status of folder reading. It can be one of: @@ -884,16 +889,16 @@ void QQuickFolderListModel::setSortCaseSensitive(bool on) are available: \list - \li \c fileName - \li \c filePath - \li \c fileURL (since Qt 5.2; deprecated since Qt 5.15) - \li \c fileUrl (since Qt 5.15) - \li \c fileBaseName - \li \c fileSuffix - \li \c fileSize - \li \c fileModified - \li \c fileAccessed - \li \c fileIsDir + \li \c fileName (\c string) + \li \c filePath (\c string) + \li \c fileURL (\c url) (since Qt 5.2; deprecated since Qt 5.15) + \li \c fileUrl (\c url) (since Qt 5.15) + \li \c fileBaseName (\c string) + \li \c fileSuffix (\c string) + \li \c fileSize (\c qlonglong) + \li \c fileModified (\c date) + \li \c fileAccessed (\c date) + \li \c fileIsDir (\c bool) \endlist */ QVariant QQuickFolderListModel::get(int idx, const QString &property) const diff --git a/src/labs/models/doc/src/qmllabsmodels.qdoc b/src/labs/models/doc/src/qmllabsmodels.qdoc index 598786f1f6..f2b57323dc 100644 --- a/src/labs/models/doc/src/qmllabsmodels.qdoc +++ b/src/labs/models/doc/src/qmllabsmodels.qdoc @@ -36,7 +36,7 @@ To use the types in this module, import the module with the following line: - \code + \qml import Qt.labs.qmlmodels - \endcode + \endqml */ diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake index bda7b9b83a..d805d37feb 100644 --- a/src/qml/Qt6QmlMacros.cmake +++ b/src/qml/Qt6QmlMacros.cmake @@ -1053,7 +1053,7 @@ function(qt6_add_qml_plugin target) if(NOT arg_CLASS_NAME) if(NOT "${arg_BACKING_TARGET}" STREQUAL "") - get_target_property(arg_CLASS_NAME ${target} QT_QML_MODULE_CLASS_NAME) + get_target_property(arg_CLASS_NAME ${arg_BACKING_TARGET} QT_QML_MODULE_CLASS_NAME) endif() if(NOT arg_CLASS_NAME) _qt_internal_compute_qml_plugin_class_name_from_uri("${arg_URI}" arg_CLASS_NAME) @@ -1429,9 +1429,11 @@ function(qt6_target_qml_sources target) get_filename_component(file_out_dir ${file_out} DIRECTORY) file(MAKE_DIRECTORY ${file_out_dir}) - execute_process(COMMAND - ${CMAKE_COMMAND} -E copy_if_different ${file_absolute} ${file_out} - ) + if(EXISTS "${file_absolute}") + execute_process(COMMAND + ${CMAKE_COMMAND} -E copy_if_different ${file_absolute} ${file_out} + ) + endif() add_custom_command(OUTPUT ${file_out} COMMAND ${CMAKE_COMMAND} -E copy ${file_src} ${file_out} @@ -1518,6 +1520,12 @@ function(qt6_target_qml_sources target) get_source_file_property(qml_file_singleton ${qml_file_src} QT_QML_SINGLETON_TYPE) get_source_file_property(qml_file_internal ${qml_file_src} QT_QML_INTERNAL_TYPE) + if (qml_file_singleton AND qml_file_internal) + message(FATAL_ERROR + "${qml_file_src} is marked as both internal and as a " + "singleton, but singletons cannot be internal!") + endif() + if (NOT qml_file_versions) set(qml_file_versions ${qml_module_files_versions}) endif() @@ -1822,28 +1830,6 @@ function(_qt_internal_qml_type_registration target) message(FATAL_ERROR "Need target metatypes.json file") endif() - cmake_policy(PUSH) - - set(registration_cpp_file_dep_args) - if (CMAKE_GENERATOR MATCHES "Ninja" OR - (CMAKE_VERSION VERSION_GREATER_EQUAL 3.20 AND CMAKE_GENERATOR MATCHES "Makefiles")) - if(POLICY CMP0116) - # Without explicitly setting this policy to NEW, we get a warning - # even though we ensure there's actually no problem here. - # See https://gitlab.kitware.com/cmake/cmake/-/issues/21959 - cmake_policy(SET CMP0116 NEW) - set(relative_to_dir ${CMAKE_CURRENT_BINARY_DIR}) - else() - set(relative_to_dir ${CMAKE_BINARY_DIR}) - endif() - set(dependency_file_cpp "${target_binary_dir}/qmltypes/${type_registration_cpp_file_name}.d") - set(registration_cpp_file_dep_args DEPFILE ${dependency_file_cpp}) - file(RELATIVE_PATH cpp_file_name "${relative_to_dir}" "${type_registration_cpp_file}") - file(GENERATE OUTPUT "${dependency_file_cpp}" - CONTENT "${cpp_file_name}: $<IF:$<BOOL:${genex_list}>,\\\n$<JOIN:${genex_list}, \\\n>, \\\n>" - ) - endif() - add_custom_command( OUTPUT ${type_registration_cpp_file} @@ -1863,12 +1849,9 @@ function(_qt_internal_qml_type_registration target) ${CMAKE_COMMAND} -E make_directory "${generated_marker_dir}" COMMAND ${CMAKE_COMMAND} -E touch "${generated_marker_file}" - ${registration_cpp_file_dep_args} COMMENT "Automatic QML type registration for target ${target}" ) - cmake_policy(POP) - # The ${target}_qmllint targets need to depend on the generation of all # *.qmltypes files in the build. We have no way of reliably working out # which QML modules a given target depends on at configure time, so we @@ -2166,7 +2149,8 @@ but this file does not exist. Possible reasons include: # across those libraries to the end target (executable or shared library). # The plugin initializers will be linked via usage requirements from the plugin target. get_target_property(target_type ${target} TYPE) - if(target_type STREQUAL "EXECUTABLE" OR target_type STREQUAL "SHARED_LIBRARY") + if(target_type STREQUAL "EXECUTABLE" OR target_type STREQUAL "SHARED_LIBRARY" + OR target_type STREQUAL "MODULE_LIBRARY") set(link_type "PRIVATE") else() set(link_type "INTERFACE") diff --git a/src/qml/common/qqmljsmemorypool_p.h b/src/qml/common/qqmljsmemorypool_p.h index dcf3fafb67..2a1dc43110 100644 --- a/src/qml/common/qqmljsmemorypool_p.h +++ b/src/qml/common/qqmljsmemorypool_p.h @@ -81,13 +81,12 @@ public: free(_blocks); } - qDeleteAll(strings); } inline void *allocate(size_t size) { size = (size + 7) & ~size_t(7); - if (Q_LIKELY(_ptr && (_ptr + size < _end))) { + if (Q_LIKELY(_ptr && size < size_t(_end - _ptr))) { void *addr = _ptr; _ptr += size; return addr; @@ -105,9 +104,8 @@ public: template <typename Tp, typename... Ta> Tp *New(Ta... args) { return new (this->allocate(sizeof(Tp))) Tp(args...); } - QStringView newString(const QString &string) { - strings.append(new QString(string)); - return QStringView(*strings.last()); + QStringView newString(QString string) { + return strings.emplace_back(std::move(string)); } private: @@ -151,7 +149,7 @@ private: int _blockCount = -1; char *_ptr = nullptr; char *_end = nullptr; - QVector<QString*> strings; + QStringList strings; enum { diff --git a/src/qml/doc/src/cppintegration/topic.qdoc b/src/qml/doc/src/cppintegration/topic.qdoc index 14a27c9a3f..1a77039e7c 100644 --- a/src/qml/doc/src/cppintegration/topic.qdoc +++ b/src/qml/doc/src/cppintegration/topic.qdoc @@ -205,7 +205,8 @@ dynamically load and introspect objects through the Qt meta object system. \include warning.qdocinc For more information on accessing QML objects from C++, see the documentation on -\l{qtqml-cppintegration-interactqmlfromcpp.html}{Interacting with QML Objects from C++}. +\l{qtqml-cppintegration-interactqmlfromcpp.html}{Interacting with QML Objects from C++}, +and the \l {Exposing Data from C++ to QML} section of the Best Practices page. \section1 Data Type Conversion Between QML and C++ diff --git a/src/qml/doc/src/qmllanguageref/modules/identifiedmodules.qdoc b/src/qml/doc/src/qmllanguageref/modules/identifiedmodules.qdoc index 493b031b60..6c235ef388 100644 --- a/src/qml/doc/src/qmllanguageref/modules/identifiedmodules.qdoc +++ b/src/qml/doc/src/qmllanguageref/modules/identifiedmodules.qdoc @@ -177,7 +177,6 @@ An identified module has several restrictions upon it: \li the module must register its types into the module identifier type namespace \li the module may not register types into any other module's namespace -\li clients must specify a version when importing the module \endlist For example, if an identified module is installed into @@ -194,10 +193,9 @@ module com.example.CustomUi \endcode Clients will then be able to import the above module with the following import -statement (assuming that the module registers types into version 1.0 of its -namespace): +statement: \qml -import com.example.CustomUi 1.0 +import com.example.CustomUi \endqml */ diff --git a/src/qml/doc/src/qtqml-writing-a-module.qdoc b/src/qml/doc/src/qtqml-writing-a-module.qdoc index 6ad820552e..a3464926e1 100644 --- a/src/qml/doc/src/qtqml-writing-a-module.qdoc +++ b/src/qml/doc/src/qtqml-writing-a-module.qdoc @@ -116,7 +116,7 @@ You can use the \l Q_IMPORT_QML_PLUGIN macro to create a reference to this symbo Add the following code to the main.cpp: \badcode -#include <QtQml/qqmlextensionplugin.h> +#include <QtQml/QQmlExtensionPlugin> Q_IMPORT_QML_PLUGIN(ExtraModulePlugin) \endcode diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index 2c6c579e4f..cdcb5b3bff 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -509,6 +509,9 @@ static QUrl urlForFileName(const QString &fileName) The script code will be evaluated in the context of the global object. + \note If you need to evaluate inside a QML context, use \l QQmlExpression + instead. + The evaluation of \a program can cause an \l{Script Exceptions}{exception} in the engine; in this case the return value will be the exception that was thrown (typically an \c{Error} object; see @@ -539,6 +542,8 @@ static QUrl urlForFileName(const QString &fileName) exception value will still be returned. Use \c exceptionStackTrace->isEmpty() to distinguish whether the value was a normal or an exceptional return value. + + \sa QQmlExpression::evaluate */ QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, int lineNumber, QStringList *exceptionStackTrace) { diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp index 832eaca8da..f9df4a40c5 100644 --- a/src/qml/jsapi/qjsvalue.cpp +++ b/src/qml/jsapi/qjsvalue.cpp @@ -200,10 +200,11 @@ /*! \enum QJSValue::ObjectConversionBehavior - This enum is used to specify how JavaScript objects without an equivalent + This enum is used to specify how JavaScript objects and symbols without an equivalent native Qt type should be treated when converting to QVariant. \value ConvertJSObjects A best-effort, possibly lossy, conversion is attempted. + Symbols are converted to QString. \value RetainJSObjects The value is retained as QJSValue wrapped in QVariant. */ diff --git a/src/qml/jsruntime/qv4arraydata.cpp b/src/qml/jsruntime/qv4arraydata.cpp index 20501f9d03..224c898536 100644 --- a/src/qml/jsruntime/qv4arraydata.cpp +++ b/src/qml/jsruntime/qv4arraydata.cpp @@ -670,8 +670,8 @@ bool ArrayElementLessThan::operator()(Value v1, Value v2) const return p1s->toQString() < p2s->toQString(); } -template <typename RandomAccessIterator, typename T, typename LessThan> -void sortHelper(RandomAccessIterator start, RandomAccessIterator end, const T &t, LessThan lessThan) +template <typename RandomAccessIterator, typename LessThan> +void sortHelper(RandomAccessIterator start, RandomAccessIterator end, LessThan lessThan) { top: int span = int(end - start); @@ -716,7 +716,7 @@ top: ++low; qSwap(*end, *low); - sortHelper(start, low, t, lessThan); + sortHelper(start, low, lessThan); start = low + 1; ++end; @@ -816,8 +816,36 @@ void ArrayData::sort(ExecutionEngine *engine, Object *thisObject, const Value &c ArrayElementLessThan lessThan(engine, static_cast<const FunctionObject &>(comparefn)); - Value *begin = thisObject->arrayData()->values.values; - sortHelper(begin, begin + len, *begin, lessThan); + const auto thisArrayData = thisObject->arrayData(); + uint startIndex = thisArrayData->mappedIndex(0); + uint endIndex = thisArrayData->mappedIndex(len - 1) + 1; + if (startIndex < endIndex) { + // Values are contiguous. Sort right away. + sortHelper( + thisArrayData->values.values + startIndex, + thisArrayData->values.values + endIndex, + lessThan); + } else { + // Values wrap around the end of the allocation. Close the gap to form a contiguous array. + // We're going to sort anyway. So we don't need to care about order. + + // ArrayElementLessThan sorts empty and undefined to the end of the array anyway, but we + // probably shouldn't rely on the unused slots to be actually undefined or empty. + + const uint gap = startIndex - endIndex; + const uint allocEnd = thisArrayData->values.alloc - 1; + for (uint i = 0; i < gap; ++i) { + const uint from = allocEnd - i; + const uint to = endIndex + i; + if (from < startIndex) + break; + + std::swap(thisArrayData->values.values[from], thisArrayData->values.values[to]); + } + + thisArrayData->offset = 0; + sortHelper(thisArrayData->values.values, thisArrayData->values.values + len, lessThan); + } #ifdef CHECK_SPARSE_ARRAYS thisObject->initSparseArray(); diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index c047298aba..0927687578 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1506,7 +1506,9 @@ QQmlError ExecutionEngine::catchExceptionAsQmlError() // Variant conversion code typedef QSet<QV4::Heap::Object *> V4ObjectSet; -static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, QMetaType typeHint, bool createJSValueForObjects, V4ObjectSet *visitedObjects); +static QVariant toVariant( + QV4::ExecutionEngine *e, const QV4::Value &value, QMetaType typeHint, + bool createJSValueForObjectsAndSymbols, V4ObjectSet *visitedObjects); static QObject *qtObjectFromJS(const QV4::Value &value); static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V4ObjectSet *visitedObjects = nullptr); static bool convertToNativeQObject(const QV4::Value &value, QMetaType targetType, void **result); @@ -1518,7 +1520,9 @@ static QV4::ReturnedValue variantToJS(QV4::ExecutionEngine *v4, const QVariant & return v4->metaTypeToJS(value.metaType(), value.constData()); } -static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, QMetaType metaType, bool createJSValueForObjects, V4ObjectSet *visitedObjects) +static QVariant toVariant( + QV4::ExecutionEngine *e, const QV4::Value &value, QMetaType metaType, + bool createJSValueForObjectsAndSymbols, V4ObjectSet *visitedObjects) { Q_ASSERT (!value.isEmpty()); QV4::Scope scope(e); @@ -1657,6 +1661,9 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, QMet return *ld->d()->locale; #endif if (const QV4::DateObject *d = value.as<DateObject>()) { + // NOTE: since we convert QTime to JS Date, + // round trip will change the variant type (to QDateTime)! + auto dt = d->toQDateTime(); // See ExecutionEngine::metaTypeFromJS()'s handling of QMetaType::Date: if (typeHint == QMetaType::QDate) { @@ -1670,7 +1677,11 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, QMet return d->toQUrl(); if (const ArrayBuffer *d = value.as<ArrayBuffer>()) return d->asByteArray(); - // NOTE: since we convert QTime to JS Date, round trip will change the variant type (to QDateTime)! + if (const Symbol *symbol = value.as<Symbol>()) { + return createJSValueForObjectsAndSymbols + ? QVariant::fromValue(QJSValuePrivate::fromReturnedValue(symbol->asReturnedValue())) + : symbol->descriptiveString(); + } QV4::ScopedObject o(scope, value); Q_ASSERT(o); @@ -1680,16 +1691,17 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, QMet return re->toQRegularExpression(); #endif - if (createJSValueForObjects) + if (createJSValueForObjectsAndSymbols) return QVariant::fromValue(QJSValuePrivate::fromReturnedValue(o->asReturnedValue())); return objectToVariant(e, o, visitedObjects); } -QVariant ExecutionEngine::toVariant(const Value &value, QMetaType typeHint, bool createJSValueForObjects) +QVariant ExecutionEngine::toVariant( + const Value &value, QMetaType typeHint, bool createJSValueForObjectsAndSymbols) { - return ::toVariant(this, value, typeHint, createJSValueForObjects, nullptr); + return ::toVariant(this, value, typeHint, createJSValueForObjectsAndSymbols, nullptr); } static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V4ObjectSet *visitedObjects) @@ -1720,7 +1732,8 @@ static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V int length = a->getLength(); for (int ii = 0; ii < length; ++ii) { v = a->get(ii); - list << ::toVariant(e, v, QMetaType {}, /*createJSValueForObjects*/false, visitedObjects); + list << ::toVariant( + e, v, QMetaType {}, /*createJSValueForObjectsAndSymbols*/false, visitedObjects); } result = list; @@ -1736,7 +1749,9 @@ static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V break; QString key = name->toQStringNoThrow(); - map.insert(key, ::toVariant(e, val, /*type hint*/ QMetaType {}, /*createJSValueForObjects*/false, visitedObjects)); + map.insert(key, ::toVariant( + e, val, /*type hint*/ QMetaType {}, + /*createJSValueForObjectsAndSymbols*/false, visitedObjects)); } result = map; @@ -2428,7 +2443,7 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi const QV4::ArrayObject *a = value.as<QV4::ArrayObject>(); if (a) { *reinterpret_cast<QVariantList *>(data) = a->engine()->toVariant( - *a, /*typeHint*/QMetaType{}, /*createJSValueForObjects*/false).toList(); + *a, /*typeHint*/QMetaType{}, /*createJSValueForObjectsAndSymbols*/false).toList(); return true; } break; @@ -2442,18 +2457,20 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi break; } case QMetaType::QVariant: - if (const QV4::Managed *m = value.as<QV4::Managed>()) - *reinterpret_cast<QVariant*>(data) = m->engine()->toVariant(value, /*typeHint*/QMetaType{}, /*createJSValueForObjects*/false); - else if (value.isNull()) + if (const QV4::Managed *m = value.as<QV4::Managed>()) { + *reinterpret_cast<QVariant*>(data) = m->engine()->toVariant( + value, /*typeHint*/QMetaType{}, /*createJSValueForObjectsAndSymbols*/false); + } else if (value.isNull()) { *reinterpret_cast<QVariant*>(data) = QVariant::fromValue(nullptr); - else if (value.isUndefined()) + } else if (value.isUndefined()) { *reinterpret_cast<QVariant*>(data) = QVariant(); - else if (value.isBoolean()) + } else if (value.isBoolean()) { *reinterpret_cast<QVariant*>(data) = QVariant(value.booleanValue()); - else if (value.isInteger()) + } else if (value.isInteger()) { *reinterpret_cast<QVariant*>(data) = QVariant(value.integerValue()); - else if (value.isDouble()) + } else if (value.isDouble()) { *reinterpret_cast<QVariant*>(data) = QVariant(value.doubleValue()); + } return true; case QMetaType::QJsonValue: *reinterpret_cast<QJsonValue *>(data) = QV4::JsonObject::toJsonValue(value); diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index 0d3b279377..d5e32ebedc 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -682,7 +682,9 @@ public: QQmlError catchExceptionAsQmlError(); // variant conversions - QVariant toVariant(const QV4::Value &value, QMetaType typeHint, bool createJSValueForObjects = true); + QVariant toVariant( + const QV4::Value &value, QMetaType typeHint, + bool createJSValueForObjectsAndSymbols = true); QV4::ReturnedValue fromVariant(const QVariant &); QVariantMap variantMapFromJS(const QV4::Object *o); diff --git a/src/qml/jsruntime/qv4identifierhash.cpp b/src/qml/jsruntime/qv4identifierhash.cpp index d349b7c4fe..e974c2c0ad 100644 --- a/src/qml/jsruntime/qv4identifierhash.cpp +++ b/src/qml/jsruntime/qv4identifierhash.cpp @@ -127,7 +127,7 @@ const IdentifierHashEntry *IdentifierHash::lookup(const QString &str) const if (!d) return nullptr; - PropertyKey id = d->identifierTable->asPropertyKey(str); + PropertyKey id = d->identifierTable->asPropertyKey(str, IdentifierTable::ForceConversionToId); return lookup(id); } @@ -146,7 +146,7 @@ inline const PropertyKey IdentifierHash::toIdentifier(const QString &str) const { Q_ASSERT(d); - return d->identifierTable->asPropertyKey(str); + return d->identifierTable->asPropertyKey(str, IdentifierTable::ForceConversionToId); } inline diff --git a/src/qml/jsruntime/qv4identifiertable.cpp b/src/qml/jsruntime/qv4identifiertable.cpp index 7c5bc3188c..f6d10a575f 100644 --- a/src/qml/jsruntime/qv4identifiertable.cpp +++ b/src/qml/jsruntime/qv4identifiertable.cpp @@ -285,12 +285,17 @@ void IdentifierTable::sweep() size -= freed; } -PropertyKey IdentifierTable::asPropertyKey(const QString &s) +PropertyKey IdentifierTable::asPropertyKey(const QString &s, + IdentifierTable::KeyConversionBehavior conversionBehvior) { uint subtype; - const uint hash = String::createHashValue(s.constData(), s.length(), &subtype); - if (subtype == Heap::String::StringType_ArrayIndex) - return PropertyKey::fromArrayIndex(hash); + uint hash = String::createHashValue(s.constData(), s.size(), &subtype); + if (subtype == Heap::String::StringType_ArrayIndex) { + if (Q_UNLIKELY(conversionBehvior == ForceConversionToId)) + hash = String::createHashValueDisallowingArrayIndex(s.constData(), s.size(), &subtype); + else + return PropertyKey::fromArrayIndex(hash); + } return resolveStringEntry(s, hash, subtype)->identifier; } diff --git a/src/qml/jsruntime/qv4identifiertable_p.h b/src/qml/jsruntime/qv4identifiertable_p.h index b2a9bc1195..c608e5d89e 100644 --- a/src/qml/jsruntime/qv4identifiertable_p.h +++ b/src/qml/jsruntime/qv4identifiertable_p.h @@ -91,7 +91,8 @@ public: return asPropertyKey(str->d()); } - PropertyKey asPropertyKey(const QString &s); + enum KeyConversionBehavior { Default, ForceConversionToId }; + PropertyKey asPropertyKey(const QString &s, KeyConversionBehavior conversionBehavior = Default); PropertyKey asPropertyKey(const char *s, int len); PropertyKey asPropertyKeyImpl(const Heap::String *str); diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index 21a91342fb..9f3c2af174 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -294,8 +294,12 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r bool hasProp = false; QQmlPropertyData *propertyData = nullptr; - QV4::ScopedValue result(scope, QV4::QObjectWrapper::getQmlProperty(v4, context, scopeObject, - name, QV4::QObjectWrapper::CheckRevision, &hasProp, &propertyData)); + + QV4::ScopedObject wrapper(scope, QV4::QObjectWrapper::wrap(v4, scopeObject)); + QV4::ScopedValue result(scope, QV4::QObjectWrapper::getQmlProperty( + v4, context, wrapper->d(), scopeObject, name, + QV4::QObjectWrapper::CheckRevision, &hasProp, + &propertyData)); if (hasProp) { if (hasProperty) *hasProperty = true; @@ -321,9 +325,10 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r if (QObject *contextObject = context->contextObject()) { bool hasProp = false; QQmlPropertyData *propertyData = nullptr; - result = QV4::QObjectWrapper::getQmlProperty(v4, context, contextObject, - name, QV4::QObjectWrapper::CheckRevision, - &hasProp, &propertyData); + QV4::ScopedObject wrapper(scope, QV4::QObjectWrapper::wrap(v4, contextObject)); + result = QV4::QObjectWrapper::getQmlProperty( + v4, context, wrapper->d(), contextObject, name, + QV4::QObjectWrapper::CheckRevision, &hasProp, &propertyData); if (hasProp) { if (hasProperty) *hasProperty = true; @@ -682,8 +687,9 @@ ReturnedValue QQmlContextWrapper::lookupInParentContextHierarchy(Lookup *l, Exec // Search context object if (QObject *contextObject = context->contextObject()) { bool hasProp = false; + QV4::ScopedObject wrapper(scope, QV4::QObjectWrapper::wrap(engine, contextObject)); result = QV4::QObjectWrapper::getQmlProperty( - engine, context, contextObject, name, + engine, context, wrapper->d(), contextObject, name, QV4::QObjectWrapper::CheckRevision, &hasProp); if (hasProp) { if (base) diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 8be952571a..91bace3b01 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -248,7 +248,9 @@ QQmlPropertyData *QObjectWrapper::findProperty( return result; } -ReturnedValue QObjectWrapper::getProperty(ExecutionEngine *engine, QObject *object, QQmlPropertyData *property) +ReturnedValue QObjectWrapper::getProperty( + ExecutionEngine *engine, Heap::Object *wrapper, QObject *object, + QQmlPropertyData *property) { QQmlData::flushPendingBinding(object, property->coreIndex()); @@ -262,13 +264,13 @@ ReturnedValue QObjectWrapper::getProperty(ExecutionEngine *engine, QObject *obje ScopedContext global(scope, engine->qmlContext()); if (!global) global = engine->rootContext(); - return QV4::QObjectMethod::create(global, object, property->coreIndex()); + return QV4::QObjectMethod::create(global, wrapper, property->coreIndex()); } else if (property->isSignalHandler()) { QmlSignalHandler::initProto(engine); return engine->memoryManager->allocate<QV4::QmlSignalHandler>(object, property->coreIndex())->asReturnedValue(); } else { ExecutionContext *global = engine->rootContext(); - return QV4::QObjectMethod::create(global, object, property->coreIndex()); + return QV4::QObjectMethod::create(global, wrapper, property->coreIndex()); } } @@ -287,7 +289,8 @@ ReturnedValue QObjectWrapper::getProperty(ExecutionEngine *engine, QObject *obje } } -static OptionalReturnedValue getDestroyOrToStringMethod(ExecutionEngine *v4, String *name, QObject *qobj, bool *hasProperty = nullptr) +static OptionalReturnedValue getDestroyOrToStringMethod( + ExecutionEngine *v4, String *name, Heap::Object *qobj, bool *hasProperty = nullptr) { int index = 0; if (name->equals(v4->id_destroy())) @@ -345,7 +348,7 @@ ReturnedValue QObjectWrapper::getQmlProperty( ExecutionEngine *v4 = engine(); - if (auto methodValue = getDestroyOrToStringMethod(v4, name, d()->object(), hasProperty)) + if (auto methodValue = getDestroyOrToStringMethod(v4, name, d(), hasProperty)) return *methodValue; QQmlPropertyData local; @@ -373,13 +376,13 @@ ReturnedValue QObjectWrapper::getQmlProperty( if (hasProperty) *hasProperty = true; - return getProperty(v4, d()->object(), result); + return getProperty(v4, d(), d()->object(), result); } ReturnedValue QObjectWrapper::getQmlProperty( QV4::ExecutionEngine *engine, const QQmlRefPointer<QQmlContextData> &qmlContext, - QObject *object, String *name, QObjectWrapper::RevisionMode revisionMode, bool *hasProperty, - QQmlPropertyData **property) + Heap::Object *wrapper, QObject *object, String *name, + QObjectWrapper::RevisionMode revisionMode, bool *hasProperty, QQmlPropertyData **property) { if (QQmlData::wasDeleted(object)) { if (hasProperty) @@ -387,7 +390,7 @@ ReturnedValue QObjectWrapper::getQmlProperty( return QV4::Encode::null(); } - if (auto methodValue = getDestroyOrToStringMethod(engine, name, object, hasProperty)) + if (auto methodValue = getDestroyOrToStringMethod(engine, name, wrapper, hasProperty)) return *methodValue; QQmlData *ddata = QQmlData::get(object, false); @@ -409,7 +412,7 @@ ReturnedValue QObjectWrapper::getQmlProperty( if (property && result != &local) *property = result; - return getProperty(engine, object, result); + return getProperty(engine, wrapper, object, result); } else { // Check if this object is already wrapped. if (!ddata || (ddata->jsWrapper.isUndefined() && @@ -429,13 +432,13 @@ ReturnedValue QObjectWrapper::getQmlProperty( Q_ASSERT(ddata); QV4::Scope scope(engine); - QV4::Scoped<QObjectWrapper> wrapper(scope, wrap(engine, object)); - if (!wrapper) { + QV4::Scoped<QObjectWrapper> rewrapped(scope, wrap(engine, object)); + if (!rewrapped) { if (hasProperty) *hasProperty = false; return QV4::Encode::null(); } - return wrapper->getQmlProperty(qmlContext, name, revisionMode, hasProperty); + return rewrapped->getQmlProperty(qmlContext, name, revisionMode, hasProperty); } @@ -867,7 +870,7 @@ PropertyKey QObjectWrapperOwnPropertyKeyIterator::next(const QV4::Object *o, Pro if (pd) { QQmlPropertyData local; local.load(property); - pd->value = that->getProperty(thatEngine, thatObject, &local); + pd->value = that->getProperty(thatEngine, that->d(), thatObject, &local); } return propName->toPropertyKey(); } @@ -892,7 +895,7 @@ PropertyKey QObjectWrapperOwnPropertyKeyIterator::next(const QV4::Object *o, Pro if (pd) { QQmlPropertyData local; local.load(method); - pd->value = that->getProperty(thatEngine, thatObject, &local); + pd->value = that->getProperty(thatEngine, that->d(), thatObject, &local); } return methodName->toPropertyKey(); } @@ -925,14 +928,14 @@ ReturnedValue QObjectWrapper::virtualResolveLookupGetter(const Object *object, E if (QQmlData::wasDeleted(qobj)) return QV4::Encode::undefined(); - if (auto methodValue = getDestroyOrToStringMethod(engine, name, qobj)) + if (auto methodValue = getDestroyOrToStringMethod(engine, name, This->d())) return *methodValue; QQmlData *ddata = QQmlData::get(qobj, false); if (!ddata || !ddata->propertyCache) { QQmlPropertyData local; QQmlPropertyData *property = QQmlPropertyCache::property(engine->jsEngine(), qobj, name, qmlContext, &local); - return property ? getProperty(engine, qobj, property) : QV4::Encode::undefined(); + return property ? getProperty(engine, This->d(), qobj, property) : QV4::Encode::undefined(); } QQmlPropertyData *property = ddata->propertyCache->property(name.getPointer(), qobj, qmlContext); @@ -1246,6 +1249,8 @@ void QObjectWrapper::destroyObject(bool lastCall) if (ddata && ddata->ownContext) { Q_ASSERT(ddata->ownContext.data() == ddata->context); ddata->ownContext->emitDestruction(); + if (ddata->ownContext->contextObject() == h->object()) + ddata->ownContext->setContextObject(nullptr); ddata->ownContext = nullptr; ddata->context = nullptr; } @@ -1884,12 +1889,24 @@ bool CallArgument::fromValue(QMetaType metaType, QV4::ExecutionEngine *engine, c } else if (callType == QMetaType::QObjectStar) { qobjectPtr = nullptr; type = callType; - if (const QV4::QObjectWrapper *qobjectWrapper = value.as<QV4::QObjectWrapper>()) + if (const QV4::QObjectWrapper *qobjectWrapper = value.as<QV4::QObjectWrapper>()) { qobjectPtr = qobjectWrapper->object(); - else if (const QV4::QQmlTypeWrapper *qmlTypeWrapper = value.as<QV4::QQmlTypeWrapper>()) - queryEngine = qmlTypeWrapper->isSingleton(); - else if (!value.isNull() && !value.isUndefined()) // null and undefined are nullptr + } else if (const QV4::QQmlTypeWrapper *qmlTypeWrapper = value.as<QV4::QQmlTypeWrapper>()) { + if (qmlTypeWrapper->isSingleton()) { + queryEngine = true; + } else if (QObject *obj = qmlTypeWrapper->object()) { + // attached object case + qobjectPtr = obj; + return true; + } else { + // If this is a plain type wrapper without an instance, + // then we got a namespace, and that's a type error + type = QMetaType::UnknownType; + return false; + } + } else if (!value.isNull() && !value.isUndefined()) { // null and undefined are nullptr return false; + } } else if (callType == qMetaTypeId<QVariant>()) { qvariantPtr = new (&allocData) QVariant(scope.engine->toVariant(value, QMetaType {})); type = callType; @@ -2069,35 +2086,49 @@ QV4::ReturnedValue CallArgument::toValue(QV4::ExecutionEngine *engine) } } -ReturnedValue QObjectMethod::create(ExecutionContext *scope, QObject *object, int index) +ReturnedValue QObjectMethod::create(ExecutionContext *scope, Heap::Object *wrapper, int index) { Scope valueScope(scope); - Scoped<QObjectMethod> method(valueScope, valueScope.engine->memoryManager->allocate<QObjectMethod>(scope)); - method->d()->setObject(object); - - method->d()->index = index; + Scoped<QObjectMethod> method( + valueScope, + valueScope.engine->memoryManager->allocate<QObjectMethod>(scope, wrapper, index)); return method.asReturnedValue(); } ReturnedValue QObjectMethod::create(ExecutionContext *scope, Heap::QQmlValueTypeWrapper *valueType, int index) { Scope valueScope(scope); - Scoped<QObjectMethod> method(valueScope, valueScope.engine->memoryManager->allocate<QObjectMethod>(scope)); - method->d()->index = index; - method->d()->valueTypeWrapper.set(valueScope.engine, valueType); + Scoped<QObjectMethod> method( + valueScope, + valueScope.engine->memoryManager->allocate<QObjectMethod>(scope, valueType, index)); return method.asReturnedValue(); } -void Heap::QObjectMethod::init(QV4::ExecutionContext *scope) +void Heap::QObjectMethod::init(QV4::ExecutionContext *scope, Object *object, int methodIndex) { Heap::FunctionObject::init(scope); + wrapper.set(internalClass->engine, object); + index = methodIndex; } const QMetaObject *Heap::QObjectMethod::metaObject() { - if (valueTypeWrapper) - return valueTypeWrapper->metaObject(); - return object()->metaObject(); + Scope scope(internalClass->engine); + if (Scoped<QV4::QQmlValueTypeWrapper> valueWrapper(scope, wrapper); valueWrapper) + return valueWrapper->metaObject(); + if (QObject *self = object()) + return self->metaObject(); + return nullptr; +} + +QObject *Heap::QObjectMethod::object() const +{ + Scope scope(internalClass->engine); + if (Scoped<QV4::QObjectWrapper> objectWrapper(scope, wrapper); objectWrapper) + return objectWrapper->object(); + if (Scoped<QV4::QQmlTypeWrapper> typeWrapper(scope, wrapper); typeWrapper) + return typeWrapper->object(); + return nullptr; } void Heap::QObjectMethod::ensureMethodsCache() @@ -2193,14 +2224,19 @@ ReturnedValue QObjectMethod::callInternal(const Value *thisObject, const Value * d()->ensureMethodsCache(); Scope scope(v4); - QQmlObjectOrGadget object(d()->object()); - - if (!d()->object()) { - if (!d()->valueTypeWrapper) - return Encode::undefined(); - - object = QQmlObjectOrGadget(d()->metaObject(), d()->valueTypeWrapper->gadgetPtr()); - } + Heap::QQmlValueTypeWrapper *valueWrapper = nullptr; + QQmlObjectOrGadget object = [&]() { + QV4::Scope scope(v4); + if (QV4::Scoped<QV4::QObjectWrapper> qobject(scope, d()->wrapper); qobject) + return QQmlObjectOrGadget(qobject->object()); + if (QV4::Scoped<QV4::QQmlTypeWrapper> type(scope, d()->wrapper); type) + return QQmlObjectOrGadget(type->object()); + if (QV4::Scoped<QV4::QQmlValueTypeWrapper> value(scope, d()->wrapper); value) { + valueWrapper = value->d(); + return QQmlObjectOrGadget(valueWrapper->metaObject(), valueWrapper->gadgetPtr()); + } + Q_UNREACHABLE(); + }(); JSCallData cData(thisObject, argv, argc); CallData *callData = cData.callData(scope); @@ -2211,8 +2247,7 @@ ReturnedValue QObjectMethod::callInternal(const Value *thisObject, const Value * // The method might change the value. const auto doCall = [&](const auto &call) { if (!method->isConstant()) { - QV4::Scoped<QQmlValueTypeReference> valueTypeReference( - scope, d()->valueTypeWrapper.get()); + QV4::Scoped<QQmlValueTypeReference> valueTypeReference(scope, valueWrapper); if (valueTypeReference) { QV4::ScopedValue rv(scope, call()); valueTypeReference->d()->writeBack(); @@ -2335,9 +2370,11 @@ ReturnedValue QMetaObjectWrapper::constructInternal(const Value *argv, int argc) objectOrGadget, d()->constructors, d()->constructorCount, v4, callData)) { object = CallPrecise(objectOrGadget, *ctor, v4, callData, QMetaObject::CreateInstance); } - Scoped<QMetaObjectWrapper> metaObject(scope, this); - object->defineDefaultProperty(v4->id_constructor(), metaObject); - object->setPrototypeOf(const_cast<QMetaObjectWrapper*>(this)); + if (object) { + Scoped<QMetaObjectWrapper> metaObject(scope, this); + object->defineDefaultProperty(v4->id_constructor(), metaObject); + object->setPrototypeOf(const_cast<QMetaObjectWrapper*>(this)); + } return object.asReturnedValue(); } diff --git a/src/qml/jsruntime/qv4qobjectwrapper_p.h b/src/qml/jsruntime/qv4qobjectwrapper_p.h index f7bdda5f6f..ca0e5758c4 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper_p.h +++ b/src/qml/jsruntime/qv4qobjectwrapper_p.h @@ -96,32 +96,29 @@ private: }; #define QObjectMethodMembers(class, Member) \ - Member(class, Pointer, QQmlValueTypeWrapper *, valueTypeWrapper) \ + Member(class, Pointer, Object *, wrapper) \ Member(class, NoMark, QV4QPointer<QObject>, qObj) \ Member(class, NoMark, int, index) -DECLARE_HEAP_OBJECT(QObjectMethod, FunctionObject) { - DECLARE_MARKOBJECTS(QObjectMethod); +DECLARE_EXPORTED_HEAP_OBJECT(QObjectMethod, FunctionObject) { + DECLARE_MARKOBJECTS(QObjectMethod) QQmlPropertyData *methods; int methodCount; alignas(alignof(QQmlPropertyData)) std::byte _singleMethod[sizeof(QQmlPropertyData)]; - void init(QV4::ExecutionContext *scope); + void init(QV4::ExecutionContext *scope, Object *wrapper, int index); void destroy() { if (methods != reinterpret_cast<QQmlPropertyData *>(&_singleMethod)) delete[] methods; - qObj.destroy(); FunctionObject::destroy(); } void ensureMethodsCache(); const QMetaObject *metaObject(); - QObject *object() const { return qObj.data(); } - void setObject(QObject *o) { qObj = o; } - + QObject *object() const; }; struct QMetaObjectWrapper : FunctionObject { @@ -160,6 +157,13 @@ struct Q_QML_EXPORT QObjectWrapper : public Object static void initializeBindings(ExecutionEngine *engine); + const QMetaObject *metaObject() const + { + if (QObject *o = object()) + return o->metaObject(); + return nullptr; + } + QObject *object() const { return d()->object(); } ReturnedValue getQmlProperty( @@ -169,8 +173,8 @@ struct Q_QML_EXPORT QObjectWrapper : public Object \ static ReturnedValue getQmlProperty( ExecutionEngine *engine, const QQmlRefPointer<QQmlContextData> &qmlContext, - QObject *object, String *name, RevisionMode revisionMode, bool *hasProperty = nullptr, - QQmlPropertyData **property = nullptr); + Heap::Object *wrapper, QObject *object, String *name, RevisionMode revisionMode, + bool *hasProperty = nullptr, QQmlPropertyData **property = nullptr); static bool setQmlProperty( ExecutionEngine *engine, const QQmlRefPointer<QQmlContextData> &qmlContext, @@ -186,7 +190,9 @@ struct Q_QML_EXPORT QObjectWrapper : public Object void destroyObject(bool lastCall); - static ReturnedValue getProperty(ExecutionEngine *engine, QObject *object, QQmlPropertyData *property); + static ReturnedValue getProperty( + ExecutionEngine *engine, Heap::Object *wrapper, QObject *object, + QQmlPropertyData *property); static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup); static ReturnedValue lookupAttached(Lookup *l, ExecutionEngine *engine, const Value &object); @@ -243,7 +249,7 @@ inline ReturnedValue QObjectWrapper::lookupGetterImpl(Lookup *lookup, ExecutionE if (!o || o->internalClass != lookup->qobjectLookup.ic) return revertLookup(); - const Heap::QObjectWrapper *This = static_cast<const Heap::QObjectWrapper *>(o); + Heap::QObjectWrapper *This = static_cast<Heap::QObjectWrapper *>(o); QObject *qobj = This->object(); if (QQmlData::wasDeleted(qobj)) return QV4::Encode::undefined(); @@ -271,7 +277,7 @@ inline ReturnedValue QObjectWrapper::lookupGetterImpl(Lookup *lookup, ExecutionE return revertLookup(); } - return getProperty(engine, qobj, property); + return getProperty(engine, This, qobj, property); } struct QQmlValueTypeWrapper; @@ -283,7 +289,7 @@ struct Q_QML_EXPORT QObjectMethod : public QV4::FunctionObject enum { DestroyMethod = -1, ToStringMethod = -2 }; - static ReturnedValue create(QV4::ExecutionContext *scope, QObject *object, int index); + static ReturnedValue create(QV4::ExecutionContext *scope, Heap::Object *wrapper, int index); static ReturnedValue create(QV4::ExecutionContext *scope, Heap::QQmlValueTypeWrapper *valueType, int index); int methodIndex() const { return d()->index; } diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index f27754f4a3..98300b1dd0 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -1544,6 +1544,11 @@ static CallArgs createSpreadArguments(Scope &scope, Value *argv, int argc) if (done->booleanValue()) break; ++argCount; + constexpr auto safetyMargin = 100; // leave some space on the stack for actual work with the elements + if (qint64(scope.engine->jsStackLimit - scope.engine->jsStackTop) < safetyMargin) { + scope.engine->throwRangeError(QLatin1String("Too many elements in array to use it with the spread operator")); + return { nullptr, 0 }; + } v = scope.alloc<Scope::Uninitialized>(); } } @@ -1608,7 +1613,7 @@ ReturnedValue Runtime::TailCall::call(JSTypesStackFrame *frame, ExecutionEngine return checkedResult(engine, fo.call(&thisObject, argv, argc)); } - memcpy(frame->jsFrame->args, argv, argc * sizeof(Value)); + memmove(frame->jsFrame->args, argv, argc * sizeof(Value)); frame->init(fo.function(), frame->jsFrame->argValues<Value>(), argc, frame->callerCanHandleTailCall()); frame->setupJSFrame(frame->framePointer(), fo, fo.scope(), thisObject, diff --git a/src/qml/jsruntime/qv4string_p.h b/src/qml/jsruntime/qv4string_p.h index a55f72e241..69584c114e 100644 --- a/src/qml/jsruntime/qv4string_p.h +++ b/src/qml/jsruntime/qv4string_p.h @@ -230,6 +230,12 @@ struct Q_QML_PRIVATE_EXPORT String : public StringOrSymbol { return calculateHashValue(ch, end, subtype); } + static uint createHashValueDisallowingArrayIndex(const QChar *ch, int length, uint *subtype) + { + const QChar *end = ch + length; + return calculateHashValue<String::DisallowArrayIndex>(ch, end, subtype); + } + static uint createHashValue(const char *ch, int length, uint *subtype) { const char *end = ch + length; @@ -243,15 +249,19 @@ protected: static qint64 virtualGetLength(const Managed *m); public: - template <typename T> + enum IndicesBehavior {Default, DisallowArrayIndex}; + template <IndicesBehavior Behavior = Default, typename T> static inline uint calculateHashValue(const T *ch, const T* end, uint *subtype) { // array indices get their number as hash value - uint h = stringToArrayIndex(ch, end); - if (h != UINT_MAX) { - if (subtype) - *subtype = Heap::StringOrSymbol::StringType_ArrayIndex; - return h; + uint h = UINT_MAX; + if constexpr (Behavior != DisallowArrayIndex) { + h = stringToArrayIndex(ch, end); + if (h != UINT_MAX) { + if (subtype) + *subtype = Heap::StringOrSymbol::StringType_ArrayIndex; + return h; + } } while (ch < end) { diff --git a/src/qml/qml/ftw/qrecyclepool_p.h b/src/qml/qml/ftw/qrecyclepool_p.h index 3ba488d586..76381324aa 100644 --- a/src/qml/qml/ftw/qrecyclepool_p.h +++ b/src/qml/qml/ftw/qrecyclepool_p.h @@ -132,8 +132,7 @@ template<typename T, int Step> T *QRecyclePool<T, Step>::New() { T *rv = d->allocate(); - new (rv) T; - return rv; + return new (rv) T; } template<typename T, int Step> @@ -141,8 +140,7 @@ template<typename T1> T *QRecyclePool<T, Step>::New(const T1 &a) { T *rv = d->allocate(); - new (rv) T(a); - return rv; + return new (rv) T(a); } template<typename T, int Step> @@ -150,8 +148,7 @@ template<typename T1> T *QRecyclePool<T, Step>::New(T1 &a) { T *rv = d->allocate(); - new (rv) T(a); - return rv; + return new (rv) T(a); } template<typename T, int Step> diff --git a/src/qml/qml/qqmldata_p.h b/src/qml/qml/qqmldata_p.h index 27088e1032..87d61f5839 100644 --- a/src/qml/qml/qqmldata_p.h +++ b/src/qml/qml/qqmldata_p.h @@ -155,24 +155,24 @@ public: }; struct NotifyList { - quint64 connectionMask; - - quint16 maximumTodoIndex; - quint16 notifiesSize; - - QQmlNotifierEndpoint *todo; - QQmlNotifierEndpoint**notifies; + QAtomicInteger<quint64> connectionMask; + QQmlNotifierEndpoint *todo = nullptr; + QQmlNotifierEndpoint**notifies = nullptr; + quint16 maximumTodoIndex = 0; + quint16 notifiesSize = 0; void layout(); private: void layout(QQmlNotifierEndpoint*); }; - NotifyList *notifyList; + QAtomicPointer<NotifyList> notifyList; - inline QQmlNotifierEndpoint *notify(int index); + inline QQmlNotifierEndpoint *notify(int index) const; void addNotify(int index, QQmlNotifierEndpoint *); int endpointCount(int index); bool signalHasEndpoint(int index) const; - void disconnectNotifiers(); + + enum class DeleteNotifyList { Yes, No }; + void disconnectNotifiers(DeleteNotifyList doDelete); // The context that created the C++ object; not refcounted to prevent cycles QQmlContextData *context = nullptr; @@ -326,23 +326,31 @@ bool QQmlData::wasDeleted(const QObject *object) return ddata && ddata->isQueuedForDeletion; } -QQmlNotifierEndpoint *QQmlData::notify(int index) +inline bool isIndexInConnectionMask(quint64 connectionMask, int index) +{ + return connectionMask & (1ULL << quint64(index % 64)); +} + +QQmlNotifierEndpoint *QQmlData::notify(int index) const { + // Can only happen on "home" thread. We apply relaxed semantics when loading the atomics. + Q_ASSERT(index <= 0xFFFF); - if (!notifyList || !(notifyList->connectionMask & (1ULL << quint64(index % 64)))) { + NotifyList *list = notifyList.loadRelaxed(); + if (!list || !isIndexInConnectionMask(list->connectionMask.loadRelaxed(), index)) return nullptr; - } else if (index < notifyList->notifiesSize) { - return notifyList->notifies[index]; - } else if (index <= notifyList->maximumTodoIndex) { - notifyList->layout(); - } - if (index < notifyList->notifiesSize) { - return notifyList->notifies[index]; - } else { - return nullptr; + if (index < list->notifiesSize) + return list->notifies[index]; + + if (index <= list->maximumTodoIndex) { + list->layout(); + if (index < list->notifiesSize) + return list->notifies[index]; } + + return nullptr; } /* @@ -351,7 +359,19 @@ QQmlNotifierEndpoint *QQmlData::notify(int index) */ inline bool QQmlData::signalHasEndpoint(int index) const { - return notifyList && (notifyList->connectionMask & (1ULL << quint64(index % 64))); + // This can be called from any thread. + // We still use relaxed semantics. If we're on a thread different from the "home" thread + // of the QQmlData, two interesting things might happen: + // + // 1. The list might go away while we hold it. In that case we are dealing with an object whose + // QObject dtor is being executed concurrently. This is UB already without the notify lists. + // Therefore, we don't need to consider it. + // 2. The connectionMask may be amended or zeroed while we are looking at it. In that case + // we "misreport" the endpoint. Since ordering of events across threads is inherently + // nondeterministic, either result is correct in that case. We can accept it. + + NotifyList *list = notifyList.loadRelaxed(); + return list && isIndexInConnectionMask(list->connectionMask.loadRelaxed(), index); } bool QQmlData::hasBindingBit(int coreIndex) const diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index ff8e2de238..e14931ddba 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -529,7 +529,7 @@ void QQmlPrivate::qdeclarativeelement_destructor(QObject *o) // Disconnect the notifiers now - during object destruction this would be too late, since // the disconnect call wouldn't be able to call disconnectNotify(), as it isn't possible to // get the metaobject anymore. - d->disconnectNotifiers(); + d->disconnectNotifiers(QQmlData::DeleteNotifyList::No); } } @@ -590,7 +590,10 @@ void QQmlData::signalEmitted(QAbstractDeclarativeData *, QObject *object, int in // QQmlEngine to emit signals from a different thread. These signals are then automatically // marshalled back onto the QObject's thread and handled by QML from there. This is tested // by the qqmlecmascript::threadSignal() autotest. - if (!ddata->notifyList) + + // Relaxed semantics here. If we're on a different thread we might schedule a useless event, + // but that should be rare. + if (!ddata->notifyList.loadRelaxed()) return; auto objectThreadData = QObjectPrivate::get(object)->threadData.loadRelaxed(); @@ -1447,49 +1450,73 @@ void QQmlData::releaseDeferredData() void QQmlData::addNotify(int index, QQmlNotifierEndpoint *endpoint) { - if (!notifyList) { - notifyList = (NotifyList *)malloc(sizeof(NotifyList)); - notifyList->connectionMask = 0; - notifyList->maximumTodoIndex = 0; - notifyList->notifiesSize = 0; - notifyList->todo = nullptr; - notifyList->notifies = nullptr; + // Can only happen on "home" thread. We apply relaxed semantics when loading the atomics. + + NotifyList *list = notifyList.loadRelaxed(); + + if (!list) { + list = new NotifyList; + // We don't really care when this change takes effect on other threads. The notifyList can + // only become non-null once in the life time of a QQmlData. It becomes null again when the + // underlying QObject is deleted. At that point any interaction with the QQmlData is UB + // anyway. So, for all intents and purposese, the list becomes non-null once and then stays + // non-null "forever". We can apply relaxed semantics. + notifyList.storeRelaxed(list); } Q_ASSERT(!endpoint->isConnected()); index = qMin(index, 0xFFFF - 1); - notifyList->connectionMask |= (1ULL << quint64(index % 64)); - if (index < notifyList->notifiesSize) { + // Likewise, we don't really care _when_ the change in the connectionMask is propagated to other + // threads. Cross-thread event ordering is inherently nondeterministic. Therefore, when querying + // the conenctionMask in the presence of concurrent modification, any result is correct. + list->connectionMask.storeRelaxed( + list->connectionMask.loadRelaxed() | (1ULL << quint64(index % 64))); - endpoint->next = notifyList->notifies[index]; + if (index < list->notifiesSize) { + endpoint->next = list->notifies[index]; if (endpoint->next) endpoint->next->prev = &endpoint->next; - endpoint->prev = ¬ifyList->notifies[index]; - notifyList->notifies[index] = endpoint; - + endpoint->prev = &list->notifies[index]; + list->notifies[index] = endpoint; } else { - notifyList->maximumTodoIndex = qMax(int(notifyList->maximumTodoIndex), index); + list->maximumTodoIndex = qMax(int(list->maximumTodoIndex), index); - endpoint->next = notifyList->todo; + endpoint->next = list->todo; if (endpoint->next) endpoint->next->prev = &endpoint->next; - endpoint->prev = ¬ifyList->todo; - notifyList->todo = endpoint; + endpoint->prev = &list->todo; + list->todo = endpoint; } } -void QQmlData::disconnectNotifiers() +void QQmlData::disconnectNotifiers(QQmlData::DeleteNotifyList doDelete) { - if (notifyList) { - while (notifyList->todo) - notifyList->todo->disconnect(); - for (int ii = 0; ii < notifyList->notifiesSize; ++ii) { - while (QQmlNotifierEndpoint *ep = notifyList->notifies[ii]) + // Can only happen on "home" thread. We apply relaxed semantics when loading the atomics. + if (NotifyList *list = notifyList.loadRelaxed()) { + while (QQmlNotifierEndpoint *todo = list->todo) + todo->disconnect(); + for (int ii = 0; ii < list->notifiesSize; ++ii) { + while (QQmlNotifierEndpoint *ep = list->notifies[ii]) ep->disconnect(); } - free(notifyList->notifies); - free(notifyList); - notifyList = nullptr; + free(list->notifies); + + if (doDelete == DeleteNotifyList::Yes) { + // We can only get here from QQmlData::destroyed(), and that can only come from the + // the QObject dtor. If you're still sending signals at that point you have UB already + // without any threads. Therefore, it's enough to apply relaxed semantics. + notifyList.storeRelaxed(nullptr); + delete list; + } else { + // We can use relaxed semantics here. The worst thing that can happen is that some + // signal is falsely reported as connected. Signal connectedness across threads + // is not quite deterministic anyway. + list->connectionMask.storeRelaxed(0); + list->maximumTodoIndex = 0; + list->notifiesSize = 0; + list->notifies = nullptr; + + } } } @@ -1573,7 +1600,7 @@ void QQmlData::destroyed(QObject *object) guard->objectDestroyed(object); } - disconnectNotifiers(); + disconnectNotifiers(DeleteNotifyList::Yes); if (extendedData) delete extendedData; diff --git a/src/qml/qml/qqmlengine_p.h b/src/qml/qml/qqmlengine_p.h index 77ea35d034..15315a9000 100644 --- a/src/qml/qml/qqmlengine_p.h +++ b/src/qml/qml/qqmlengine_p.h @@ -130,9 +130,16 @@ public: }; struct QPropertyChangeTrigger : QPropertyObserver { - QPropertyChangeTrigger(QQmlJavaScriptExpression *expression) : QPropertyObserver(&QPropertyChangeTrigger::trigger), m_expression(expression) {} - QQmlJavaScriptExpression * m_expression; - QObject *target = nullptr; + Q_DISABLE_COPY_MOVE(QPropertyChangeTrigger) + + QPropertyChangeTrigger(QQmlJavaScriptExpression *expression) + : QPropertyObserver(&QPropertyChangeTrigger::trigger) + , m_expression(expression) + { + } + + QPointer<QObject> target; + QQmlJavaScriptExpression *m_expression; int propertyIndex = 0; static void trigger(QPropertyObserver *, QUntypedPropertyData *); diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index d04d6751cd..e33d942833 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -1519,6 +1519,15 @@ QTypeRevision QQmlImportsPrivate::addFileImport( // The url for the path containing files for this import QString url = resolveLocalUrl(base, uri); + if (url.isEmpty()) { + QQmlError error; + error.setDescription( + QQmlImportDatabase::tr("Cannot resolve URL for import \"%1\"").arg(uri)); + error.setUrl(baseUrl); + errors->prepend(error); + return QTypeRevision(); + } + if (!url.endsWith(Slash) && !url.endsWith(Backslash)) url += Slash; diff --git a/src/qml/qml/qqmljavascriptexpression.cpp b/src/qml/qml/qqmljavascriptexpression.cpp index f937a3fff8..d156862ea4 100644 --- a/src/qml/qml/qqmljavascriptexpression.cpp +++ b/src/qml/qml/qqmljavascriptexpression.cpp @@ -390,19 +390,35 @@ void QQmlPropertyCapture::captureProperty( captureNonBindableProperty(o, propertyData->notifyIndex(), propertyData->coreIndex(), doNotify); } +bool QQmlJavaScriptExpression::needsPropertyChangeTrigger(QObject *target, int propertyIndex) +{ + TriggerList **prev = &qpropertyChangeTriggers; + TriggerList *current = qpropertyChangeTriggers; + while (current) { + if (!current->target) { + *prev = current->next; + QRecyclePool<TriggerList>::Delete(current); + current = *prev; + } else if (current->target == target && current->propertyIndex == propertyIndex) { + return false; // already installed + } else { + prev = ¤t->next; + current = current->next; + } + } + + return true; +} + void QQmlPropertyCapture::captureTranslation() { // use a unique invalid index to avoid needlessly querying the metaobject for // the correct index of of the translationLanguage property int const invalidIndex = -2; - for (auto trigger = expression->qpropertyChangeTriggers; trigger; - trigger = trigger->next) { - if (trigger->target == engine && trigger->propertyIndex == invalidIndex) - return; // already installed + if (expression->needsPropertyChangeTrigger(engine, invalidIndex)) { + auto trigger = expression->allocatePropertyChangeTrigger(engine, invalidIndex); + trigger->setSource(QQmlEnginePrivate::get(engine)->translationLanguage); } - auto trigger = expression->allocatePropertyChangeTrigger(engine, invalidIndex); - - trigger->setSource(QQmlEnginePrivate::get(engine)->translationLanguage); } void QQmlPropertyCapture::captureBindableProperty( @@ -412,16 +428,14 @@ void QQmlPropertyCapture::captureBindableProperty( // the automatic capturing process already takes care of everything if (!expression->mustCaptureBindableProperty()) return; - for (auto trigger = expression->qpropertyChangeTriggers; trigger; - trigger = trigger->next) { - if (trigger->target == o && trigger->propertyIndex == c) - return; // already installed + + if (expression->needsPropertyChangeTrigger(o, c)) { + auto trigger = expression->allocatePropertyChangeTrigger(o, c); + QUntypedBindable bindable; + void *argv[] = { &bindable }; + metaObjectForBindable->metacall(o, QMetaObject::BindableProperty, c, argv); + bindable.observe(trigger); } - auto trigger = expression->allocatePropertyChangeTrigger(o, c); - QUntypedBindable bindable; - void *argv[] = { &bindable }; - metaObjectForBindable->metacall(o, QMetaObject::BindableProperty, c, argv); - bindable.observe(trigger); } void QQmlPropertyCapture::captureNonBindableProperty(QObject *o, int n, int c, bool doNotify) diff --git a/src/qml/qml/qqmljavascriptexpression_p.h b/src/qml/qml/qqmljavascriptexpression_p.h index bfdc922729..4edfeda633 100644 --- a/src/qml/qml/qqmljavascriptexpression_p.h +++ b/src/qml/qml/qqmljavascriptexpression_p.h @@ -166,6 +166,8 @@ public: QQmlEngine *engine() const { return m_context ? m_context->engine() : nullptr; } bool hasUnresolvedNames() const { return m_context && m_context->hasUnresolvedNames(); } + + bool needsPropertyChangeTrigger(QObject *target, int propertyIndex); QPropertyChangeTrigger *allocatePropertyChangeTrigger(QObject *target, int propertyIndex); protected: diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index fdf74f4873..80a7ca1e1a 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -282,7 +282,7 @@ void QQmlMetaType::clone(QMetaObjectBuilder &builder, const QMetaObject *mo, } } - // Clone Q_ENUMS + // Clone enums registered with the metatype system for (int ii = mo->enumeratorOffset(); ii < mo->enumeratorCount(); ++ii) { QMetaEnum enumerator = mo->enumerator(ii); diff --git a/src/qml/qml/qqmlpropertybinding.cpp b/src/qml/qml/qqmlpropertybinding.cpp index 0cc544e266..51dee8fa5f 100644 --- a/src/qml/qml/qqmlpropertybinding.cpp +++ b/src/qml/qml/qqmlpropertybinding.cpp @@ -163,7 +163,8 @@ QUntypedPropertyBinding QQmlPropertyBinding::createFromBoundFunction(const QQmlP void QQmlPropertyBindingJS::expressionChanged() { - if (!asBinding()->propertyDataPtr) + auto binding = asBinding(); + if (!binding->propertyDataPtr) return; if (QQmlData::wasDeleted(asBinding()->target())) return; @@ -185,8 +186,9 @@ void QQmlPropertyBindingJS::expressionChanged() return; } m_error.setTag(InEvaluationLoop); - asBinding()->evaluateRecursive(); - asBinding()->notifyRecursive(); + PendingBindingObserverList bindingObservers; + binding->evaluateRecursive(bindingObservers); + binding->notifyNonRecursive(bindingObservers); m_error.setTag(NoTag); } @@ -263,10 +265,17 @@ void QQmlPropertyBinding::handleUndefinedAssignment(QQmlEnginePrivate *ep, void setIsUndefined(true); //suspend binding evaluation state for reset and subsequent read auto state = QtPrivate::suspendCurrentBindingStatus(); - prop.reset(); + prop.reset(); // May re-allocate the bindingData QVariant currentValue = QVariant(prop.propertyMetaType(), propertyDataPtr); QtPrivate::restoreBindingStatus(state); writeBackCurrentValue(std::move(currentValue)); + + // Re-fetch binding data + bindingData = storage->bindingData(propertyDataPtr); + if (!bindingData) + bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType()); + bindingDataPointer = QPropertyBindingDataPointer {bindingData}; + // reattach the binding (without causing a new notification) if (Q_UNLIKELY(bindingData->d() & QtPrivate::QPropertyBindingData::BindingBit)) { qCWarning(lcQQPropertyBinding) << "Resetting " << prop.name() << "due to the binding becoming undefined caused a new binding to be installed\n" @@ -278,7 +287,7 @@ void QQmlPropertyBinding::handleUndefinedAssignment(QQmlEnginePrivate *ep, void firstObserver = bindingDataPointer.firstObserver(); bindingData->d_ref() = reinterpret_cast<quintptr>(this) | QtPrivate::QPropertyBindingData::BindingBit; if (firstObserver) - bindingDataPointer.setObservers(firstObserver.ptr); + prependObserver(firstObserver); } else { QQmlError qmlError; auto location = jsExpression()->sourceLocation(); diff --git a/src/qml/qml/qqmltypewrapper.cpp b/src/qml/qml/qqmltypewrapper.cpp index 0580af56f0..93736b0002 100644 --- a/src/qml/qml/qqmltypewrapper.cpp +++ b/src/qml/qml/qqmltypewrapper.cpp @@ -85,19 +85,31 @@ bool QQmlTypeWrapper::isSingleton() const return d()->type().isSingleton(); } +const QMetaObject *QQmlTypeWrapper::metaObject() const +{ + const QQmlType type = d()->type(); + if (!type.isValid()) + return nullptr; + + if (type.isSingleton()) + return type.metaObject(); + + return type.attachedPropertiesType(QQmlEnginePrivate::get(engine()->qmlEngine())); +} + QObject *QQmlTypeWrapper::object() const { const QQmlType type = d()->type(); if (!type.isValid()) return nullptr; - QQmlEngine *qmlEngine = engine()->qmlEngine(); + QQmlEnginePrivate *qmlEngine = QQmlEnginePrivate::get(engine()->qmlEngine()); if (type.isSingleton()) - return QQmlEnginePrivate::get(qmlEngine)->singletonInstance<QObject *>(type); + return qmlEngine->singletonInstance<QObject *>(type); return qmlAttachedPropertiesObject( d()->object, - type.attachedPropertiesFunction(QQmlEnginePrivate::get(qmlEngine))); + type.attachedPropertiesFunction(qmlEngine)); } QObject* QQmlTypeWrapper::singletonObject() const @@ -226,7 +238,9 @@ ReturnedValue QQmlTypeWrapper::virtualGet(const Managed *m, PropertyKey id, cons // check for property. bool ok; - const ReturnedValue result = QV4::QObjectWrapper::getQmlProperty(v4, context, qobjectSingleton, name, QV4::QObjectWrapper::IgnoreRevision, &ok); + const ReturnedValue result = QV4::QObjectWrapper::getQmlProperty( + v4, context, w->d(), qobjectSingleton, name, + QV4::QObjectWrapper::IgnoreRevision, &ok); if (hasProperty) *hasProperty = ok; @@ -267,8 +281,11 @@ ReturnedValue QQmlTypeWrapper::virtualGet(const Managed *m, PropertyKey id, cons QObject *ao = qmlAttachedPropertiesObject( object, type.attachedPropertiesFunction(QQmlEnginePrivate::get(v4->qmlEngine()))); - if (ao) - return QV4::QObjectWrapper::getQmlProperty(v4, context, ao, name, QV4::QObjectWrapper::IgnoreRevision, hasProperty); + if (ao) { + return QV4::QObjectWrapper::getQmlProperty( + v4, context, w->d(), ao, name, QV4::QObjectWrapper::IgnoreRevision, + hasProperty); + } // Fall through to base implementation } diff --git a/src/qml/qml/qqmltypewrapper_p.h b/src/qml/qml/qqmltypewrapper_p.h index 71e9ee04eb..56d50bbf83 100644 --- a/src/qml/qml/qqmltypewrapper_p.h +++ b/src/qml/qml/qqmltypewrapper_p.h @@ -102,6 +102,7 @@ struct Q_QML_EXPORT QQmlTypeWrapper : Object V4_NEEDS_DESTROY bool isSingleton() const; + const QMetaObject *metaObject() const; QObject *object() const; QObject *singletonObject() const; diff --git a/src/qml/qml/qqmlvaluetypewrapper_p.h b/src/qml/qml/qqmlvaluetypewrapper_p.h index ed58157c26..a473fc5458 100644 --- a/src/qml/qml/qqmlvaluetypewrapper_p.h +++ b/src/qml/qml/qqmlvaluetypewrapper_p.h @@ -163,6 +163,7 @@ public: int typeId() const; QMetaType type() const; bool write(QObject *target, int propertyIndex) const; + const QMetaObject *metaObject() const { return d()->metaObject(); } QQmlPropertyData dataForPropertyKey(PropertyKey id) const; diff --git a/src/qmlcompiler/qqmljsimporter.cpp b/src/qmlcompiler/qqmljsimporter.cpp index a9ee30de49..9ccdf42587 100644 --- a/src/qmlcompiler/qqmljsimporter.cpp +++ b/src/qmlcompiler/qqmljsimporter.cpp @@ -403,7 +403,7 @@ QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper() QStringLiteral("jsroot.qmltypes") }; const auto importBuiltins = [&](const QStringList &imports) { for (auto const &dir : imports) { - QDirIterator it { dir, qmltypesFiles, QDir::NoFilter, QDirIterator::Subdirectories }; + QDirIterator it { dir, qmltypesFiles, QDir::NoFilter }; while (it.hasNext() && !qmltypesFiles.isEmpty()) { readQmltypes(it.next(), &result.objects, &result.dependencies); qmltypesFiles.removeOne(it.fileName()); diff --git a/src/qmlcompiler/qqmljsscope.cpp b/src/qmlcompiler/qqmljsscope.cpp index c817cd61be..ee55fa214a 100644 --- a/src/qmlcompiler/qqmljsscope.cpp +++ b/src/qmlcompiler/qqmljsscope.cpp @@ -70,21 +70,35 @@ static auto getQQmlJSScopeFromSmartPtr(const From &p) -> decltype(p.get()) template<typename QQmlJSScopePtr, typename Action> static bool searchBaseAndExtensionTypes(QQmlJSScopePtr type, const Action &check) { + if (!type) + return false; + // NB: among other things, getQQmlJSScopeFromSmartPtr() also resolves const // vs non-const pointer issue, so use it's return value as the type using T = decltype( getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(std::declval<QQmlJSScope::ConstPtr>())); + const bool isValueType = (type->accessSemantics() == QQmlJSScope::AccessSemantics::Value); + QDuplicateTracker<T> seen; for (T scope = type; scope && !seen.hasSeen(scope); scope = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(scope->baseType())) { - // Extensions override their base types - for (T extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(scope->extensionType()); - extension && !seen.hasSeen(extension); - extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(extension->baseType())) { + QDuplicateTracker<T> seenExtensions; + // Extensions override the types they extend. However, usually base + // types of extensions are ignored. The unusual cases are when we + // have a value type or when we have the QObject type, in which case + // we also study the extension's base type hierarchy. + const bool isQObject = scope->internalName() == QLatin1String("QObject"); + T extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(scope->extensionType()); + do { + if (!extension || seenExtensions.hasSeen(extension)) + break; + if (check(extension)) return true; - } + + extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(extension->baseType()); + } while (isValueType || isQObject); if (check(scope)) return true; diff --git a/src/qmldom/qqmldomreformatter.cpp b/src/qmldom/qqmldomreformatter.cpp index ee48a71c29..a2bbb25581 100644 --- a/src/qmldom/qqmldomreformatter.cpp +++ b/src/qmldom/qqmldomreformatter.cpp @@ -585,16 +585,27 @@ protected: return false; } + + void outputScope(VariableScope scope) { + switch (scope) { + case VariableScope::Const: + out("const "); + break; + case VariableScope::Let: + out("let "); + break; + case VariableScope::Var: + out("var "); + break; + default: + break; + } + } + bool visit(PatternElement *ast) override { if (ast->isForDeclaration) { - if (ast->scope == VariableScope::Var) { - out("var "); - } else if (ast->scope == VariableScope::Let) { - out("let "); - } else if (ast->scope == VariableScope::Const) { - out("const "); - } + outputScope(ast->scope); } accept(ast->bindingTarget); switch (ast->type) { @@ -678,7 +689,7 @@ protected: if (ast->initialiser) { accept(ast->initialiser); } else if (ast->declarations) { - out("var "); + outputScope(ast->declarations->declaration->scope); accept(ast->declarations); } out("; "); // ast->firstSemicolonToken @@ -867,12 +878,15 @@ protected: out(ast->identifierToken); } out(ast->lparenToken); - if (ast->isArrowFunction && ast->formals && ast->formals->next) + const bool needParentheses = ast->formals && + (ast->formals->next || + (ast->formals->element && ast->formals->element->bindingTarget)); + if (ast->isArrowFunction && needParentheses) out("("); int baseIndent = lw.increaseIndent(1); accept(ast->formals); lw.decreaseIndent(1, baseIndent); - if (ast->isArrowFunction && ast->formals && ast->formals->next) + if (ast->isArrowFunction && needParentheses) out(")"); out(ast->rparenToken); if (ast->isArrowFunction && !ast->formals) @@ -963,7 +977,11 @@ protected: bool visit(FormalParameterList *ast) override { for (FormalParameterList *it = ast; it; it = it->next) { - out(it->element->bindingIdentifier.toString()); // TODO + // compare FormalParameterList::finish + if (auto id = it->element->bindingIdentifier.toString(); !id.startsWith(u"arg#")) + out(id); + if (it->element->bindingTarget) + accept(it->element->bindingTarget); if (it->next) out(", "); } diff --git a/src/qmlmodels/qqmldelegatemodel.cpp b/src/qmlmodels/qqmldelegatemodel.cpp index 377a4c1e6f..d3cf23b490 100644 --- a/src/qmlmodels/qqmldelegatemodel.cpp +++ b/src/qmlmodels/qqmldelegatemodel.cpp @@ -1259,6 +1259,9 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ addCacheItem(cacheItem, it); reuseItem(cacheItem, index, flags); cacheItem->referenceObject(); + + if (index == m_compositor.count(group) - 1) + requestMoreIfNecessary(); return cacheItem->object; } diff --git a/src/quick/doc/images/qml-item-canvas-startAngle.png b/src/quick/doc/images/qml-item-canvas-startAngle.png Binary files differindex bf82c3aa4b..7930284896 100644 --- a/src/quick/doc/images/qml-item-canvas-startAngle.png +++ b/src/quick/doc/images/qml-item-canvas-startAngle.png diff --git a/src/quick/doc/snippets/qml/images/qt-logo.png b/src/quick/doc/snippets/qml/images/qt-logo.png Binary files differnew file mode 100644 index 0000000000..30c621c9c6 --- /dev/null +++ b/src/quick/doc/snippets/qml/images/qt-logo.png diff --git a/src/quick/doc/snippets/qml/itemGrab.qml b/src/quick/doc/snippets/qml/item/itemGrab.qml index e26e3dc55e..78fc53833b 100644 --- a/src/quick/doc/snippets/qml/itemGrab.qml +++ b/src/quick/doc/snippets/qml/item/itemGrab.qml @@ -54,44 +54,33 @@ Item { width: 320 height: 480 -//! [grab-source] +//! [grab-to-file] Rectangle { - id: source + id: sourceRectangle width: 100 height: 100 gradient: Gradient { GradientStop { position: 0; color: "steelblue" } GradientStop { position: 1; color: "black" } } + + Component.onCompleted: { + sourceRectangle.grabToImage(function(result) { + result.saveToFile("something.png") + }) + } } -//! [grab-source] +//! [grab-to-file] -//! [grab-image-target] +//! [grab-to-image] Image { id: image } -//! [grab-image-target] - Timer { - repeat: false - running: true - interval: 1000 - onTriggered: { -//! [grab-to-file] - // ... - source.grabToImage(function(result) { - result.saveToFile("something.png"); - }); -//! [grab-to-file] - -//! [grab-to-cache] - - // ... - source.grabToImage(function(result) { - image.source = result.url; - }, - Qt.size(50, 50)); -//! [grab-to-cache] - } - } +Component.onCompleted: { + sourceRectangle.grabToImage(function(result) { + image.source = result.url + }, Qt.size(50, 50)) +} +//! [grab-to-image] } diff --git a/src/quick/doc/snippets/qml/nestedWindowTransientParent.qml b/src/quick/doc/snippets/qml/nestedWindowTransientParent.qml new file mode 100644 index 0000000000..1946ee4af0 --- /dev/null +++ b/src/quick/doc/snippets/qml/nestedWindowTransientParent.qml @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick + +//![0] +Window { + // visible is false by default + Window { + transientParent: null + visible: true + } +//![0] + + id: outer + Timer { + interval: 2000 + running: true + onTriggered: outer.visible = true + } +//![1] +} +//![1] diff --git a/src/quick/doc/snippets/qml/splashWindow.qml b/src/quick/doc/snippets/qml/splashWindow.qml new file mode 100644 index 0000000000..17d4c674e3 --- /dev/null +++ b/src/quick/doc/snippets/qml/splashWindow.qml @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [entire] +import QtQuick + +Window { + id: mainWindow + title: "Main Window" + color: "#456" + property real defaultSpacing: 10 + + property Splash splash: Splash { + onTimeout: mainWindow.show() + } + + component Splash: Window { + id: splash + + // a splash screen has no titlebar + flags: Qt.SplashScreen + // the transparent color lets background behind the image edges show through + color: "transparent" + modality: Qt.ApplicationModal // in case another application window is showing + title: "Splash Window" // for the taskbar/dock, task switcher etc. + visible: true + + // here we use the Screen attached property to center the splash window + //! [screen-properties] + x: (Screen.width - splashImage.width) / 2 + y: (Screen.height - splashImage.height) / 2 + //! [screen-properties] + width: splashImage.width + height: splashImage.height + + property int timeoutInterval: 2000 + signal timeout + + Image { + id: splashImage + source: "images/qt-logo.png" + } + + TapHandler { + onTapped: splash.timeout() + } + + Timer { + interval: splash.timeoutInterval; running: true; repeat: false + onTriggered: { + splash.visible = false + splash.timeout() + } + } + } +} +//! [entire] diff --git a/src/quick/doc/snippets/qml/windowActiveAttached.qml b/src/quick/doc/snippets/qml/windowActiveAttached.qml new file mode 100644 index 0000000000..9d2ddc2b99 --- /dev/null +++ b/src/quick/doc/snippets/qml/windowActiveAttached.qml @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//![entire] +import QtQuick + +Text { + text: Window.active ? "active" : "inactive" +} +//![entire] diff --git a/src/quick/doc/snippets/qml/windowPalette.qml b/src/quick/doc/snippets/qml/windowPalette.qml new file mode 100644 index 0000000000..c566d61dbb --- /dev/null +++ b/src/quick/doc/snippets/qml/windowPalette.qml @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//![entire] +import QtQuick +import QtQuick.Controls + +//![declaration-and-color] +Window { + visible: true + + // here we use the Window.active and Window.palette ordinary properties + color: active ? palette.active.window : palette.inactive.window +//![declaration-and-color] + + // colors that are not customized here come from SystemPalette + palette.active.window: "peachpuff" + palette.windowText: "brown" + + Text { + anchors.centerIn: parent + // here we use the Window.active attached property and the Item.palette property + color: Window.active ? palette.active.windowText : palette.inactive.windowText + text: Window.active ? "active" : "inactive" + } + + Button { + text: "Button" + anchors { + bottom: parent.bottom + bottomMargin: 6 + horizontalCenter: parent.horizontalCenter + } + } +//![closing-brace] +} +//![closing-brace] +//![entire] diff --git a/src/quick/doc/snippets/qml/windowVisibility.qml b/src/quick/doc/snippets/qml/windowVisibility.qml new file mode 100644 index 0000000000..b5f89a3894 --- /dev/null +++ b/src/quick/doc/snippets/qml/windowVisibility.qml @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [entire] +import QtQuick +import QtQuick.Controls + +Window { + id: win + flags: Qt.Window | Qt.WindowFullscreenButtonHint + visibility: fullscreenButton.checked ? Window.FullScreen : Window.Windowed + + Button { + id: fullscreenButton + anchors { + right: parent.right + top: parent.top + margins: 6 + } + width: height + checkable: true + Binding on checked { value: win.visibility === Window.FullScreen } + text: "âļ" + ToolTip.visible: hovered + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval + ToolTip.text: win.visibility === Window.FullScreen ? qsTr("restore") : qsTr("fill screen") + } +} +//! [entire] diff --git a/src/quick/doc/src/concepts/positioning/righttoleft.qdoc b/src/quick/doc/src/concepts/positioning/righttoleft.qdoc index 574dfcb58f..da96d31258 100644 --- a/src/quick/doc/src/concepts/positioning/righttoleft.qdoc +++ b/src/quick/doc/src/concepts/positioning/righttoleft.qdoc @@ -171,6 +171,16 @@ This will append the following declaration to the translation file, where you ca </context> \endcode +Next, add the following bindings to the root QML component of your application: +\code +LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft +LayoutMirroring.childrenInherit: true +\endcode + +The first binding ensures that the UI will be mirrored appropriately when a +right-to-left locale is set. The second binding ensures that child items of the +root component will also respect mirroring. + You can test that the layout direction works as expected by running your Qt Quick application with the compiled translation file: diff --git a/src/quick/doc/src/examples.qdoc b/src/quick/doc/src/examples.qdoc index b0cfa247ec..2f6c87e840 100644 --- a/src/quick/doc/src/examples.qdoc +++ b/src/quick/doc/src/examples.qdoc @@ -123,7 +123,6 @@ Creator. \b{QML Types and Controls} \list \li \l{Qt Quick Controls - Gallery}{Controls Gallery} - \li \l{Calendar Example} \li \l{tableview/gameoflife}{TableView} \li \l{Qt Quick Examples - Text}{Text and Fonts} \li \l{Qt Quick Examples - Toggle Switch}{Custom Toggle Switch} diff --git a/src/quick/items/context2d/qquickcontext2d.cpp b/src/quick/items/context2d/qquickcontext2d.cpp index 7503e3ada9..ea1a2372db 100644 --- a/src/quick/items/context2d/qquickcontext2d.cpp +++ b/src/quick/items/context2d/qquickcontext2d.cpp @@ -2404,8 +2404,8 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_strokeRect(const QV4::Func \image qml-item-canvas-startAngle.png - The \a anticlockwise parameter is \c true for each arc in the figure above - because they are all drawn in the anticlockwise direction. + The \a anticlockwise parameter is \c false for each arc in the figure above + because they are all drawn in the clockwise direction. \sa arcTo, {http://www.w3.org/TR/2dcontext/#dom-context-2d-arc}{W3C's 2D Context Standard for arc()} diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index 556ccd8e0f..39a0d6659a 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -1393,10 +1393,14 @@ void QQuickFlickablePrivate::handleMoveEvent(QPointerEvent *event) const QVector2D velocity = firstPointLocalVelocity(event); bool overThreshold = false; - if (q->yflick()) - overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, firstPoint); - if (q->xflick()) - overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, firstPoint); + if (event->pointCount() == 1) { + if (q->yflick()) + overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, firstPoint); + if (q->xflick()) + overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, firstPoint); + } else { + qCDebug(lcFilter) << q->objectName() << "ignoring multi-touch" << event; + } drag(currentTimestamp, event->type(), pos, deltas, overThreshold, false, false, velocity); } @@ -2567,11 +2571,17 @@ bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *ev QQuickDeliveryAgentPrivate::isTabletEvent(event))) return false; // don't filter hover events or wheel events, for example Q_ASSERT_X(receiver != this, "", "Flickable received a filter event for itself"); - qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver; Q_D(QQuickFlickable); // If a touch event contains a new press point, don't steal right away: watch the movements for a while if (isTouch && static_cast<QTouchEvent *>(event)->touchPointStates().testFlag(QEventPoint::State::Pressed)) d->stealMouse = false; + // If multiple touchpoints are within bounds, don't grab: it's probably meant for multi-touch interaction in some child + if (event->pointCount() > 1) { + qCDebug(lcFilter) << objectName() << "ignoring multi-touch" << event << "for" << receiver; + d->stealMouse = false; + } else { + qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver; + } const auto &firstPoint = event->points().first(); if (event->pointCount() == 1 && event->exclusiveGrabber(firstPoint) == this) { diff --git a/src/quick/items/qquickgridview.cpp b/src/quick/items/qquickgridview.cpp index d94ccddcbc..916ae61cf5 100644 --- a/src/quick/items/qquickgridview.cpp +++ b/src/quick/items/qquickgridview.cpp @@ -1673,6 +1673,7 @@ void QQuickGridView::setCellWidth(qreal cellWidth) d->updateViewport(); emit cellWidthChanged(); d->forceLayoutPolish(); + QQuickFlickable::setContentX(d->contentXForPosition(d->position())); } } @@ -1690,6 +1691,7 @@ void QQuickGridView::setCellHeight(qreal cellHeight) d->updateViewport(); emit cellHeightChanged(); d->forceLayoutPolish(); + QQuickFlickable::setContentY(d->contentYForPosition(d->position())); } } /*! diff --git a/src/quick/items/qquickimage.cpp b/src/quick/items/qquickimage.cpp index 88f58cc0fa..52300bebe0 100644 --- a/src/quick/items/qquickimage.cpp +++ b/src/quick/items/qquickimage.cpp @@ -286,6 +286,7 @@ void QQuickImagePrivate::setPixmap(const QQuickPixmap &pixmap) \value Image.TileVertically the image is stretched horizontally and tiled vertically \value Image.TileHorizontally the image is stretched vertically and tiled horizontally \value Image.Pad the image is not transformed + \br \table diff --git a/src/quick/items/qquickitemgrabresult.cpp b/src/quick/items/qquickitemgrabresult.cpp index 68c5f3db97..dc5750dc0a 100644 --- a/src/quick/items/qquickitemgrabresult.cpp +++ b/src/quick/items/qquickitemgrabresult.cpp @@ -381,17 +381,15 @@ QSharedPointer<QQuickItemGrabResult> QQuickItem::grabToImage(const QSize &target * * If the grab could not be initiated, the function returns \c false. * - * The following snippet shows how to grab an item and store the results to - * a file. + * The following snippet shows how to grab an item and store the results in + * a file: * - * \snippet qml/itemGrab.qml grab-source - * \snippet qml/itemGrab.qml grab-to-file + * \snippet qml/item/itemGrab.qml grab-to-file * * The following snippet shows how to grab an item and use the results in - * another image element. + * another image element: * - * \snippet qml/itemGrab.qml grab-image-target - * \snippet qml/itemGrab.qml grab-to-cache + * \snippet qml/item/itemGrab.qml grab-to-image * * \note This function will render the item to an offscreen surface and * copy that surface from the GPU's memory into the CPU's memory, which can diff --git a/src/quick/items/qquickitemviewtransition.cpp b/src/quick/items/qquickitemviewtransition.cpp index 6b03d6c16b..5342adaddf 100644 --- a/src/quick/items/qquickitemviewtransition.cpp +++ b/src/quick/items/qquickitemviewtransition.cpp @@ -125,6 +125,8 @@ void QQuickItemViewTransitionJob::startTransition(QQuickItemViewTransitionableIt actions << QQuickStateAction(item->item, QLatin1String("x"), QVariant(to.x())); actions << QQuickStateAction(item->item, QLatin1String("y"), QVariant(to.y())); + actions[0].fromValue = item->itemX(); + actions[1].fromValue = item->itemY(); m_transitioner->runningJobs << this; QQuickTransitionManager::transition(actions, trans, item->item); } diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp index a4c0a8b740..9e7a8f4803 100644 --- a/src/quick/items/qquicklistview.cpp +++ b/src/quick/items/qquicklistview.cpp @@ -2213,6 +2213,10 @@ QQuickItemViewAttached *QQuickListViewPrivate::getAttachedObject(const QObject * \note While an item is in the pool, it might still be alive and respond to connected signals and bindings. + \note For an item to be pooled, it needs to be completely flicked out of the bounds + of the view, \e including the extra margins set with \l {ListView::}{cacheBuffer.} + Some items will also never be pooled or reused, such as \l currentItem. + The following example shows a delegate that animates a spinning rectangle. When it is pooled, the animation is temporarily paused: diff --git a/src/quick/items/qquickpalette.cpp b/src/quick/items/qquickpalette.cpp index 5e95a92238..c2e3245011 100644 --- a/src/quick/items/qquickpalette.cpp +++ b/src/quick/items/qquickpalette.cpp @@ -61,8 +61,8 @@ QT_BEGIN_NAMESPACE \ingroup qtquick-visual \brief The QQuickPalette class contains color groups for each QML item state. - A palette consists of three color groups: Active, Disabled, and Inactive. - Active color group is the default group, its colors are used for other groups + A palette consists of three color groups: \c active, \c disabled, and \c inactive. + The \c active color group is the default group: its colors are used for other groups if colors of these groups aren't explicitly specified. In the following example, color is applied for all color groups: @@ -104,18 +104,19 @@ QT_BEGIN_NAMESPACE \endcode It is also possible to specify colors like this: - \code - palette { - buttonText: "azure" - button: "khaki" - disabled { - buttonText: "lavender" - button: "coral" - } - } - \endcode - This approach is convenient when you need to specify a whole palette with all color groups. + \snippet qtquickcontrols-custom-palette-buttons.qml palette + + This approach is especially convenient when you need to specify a whole + palette with all color groups; but as with the other cases above, the + colors that are not specified are initialized from SystemPalette, or + potentially the \l {Styling Qt Quick Controls}{Qt Quick Controls style}, + if one is in use. + + \note Some Controls styles use some palette colors, but many styles use + independent colors. + + \sa Window::palette, Item::palette, Popup::palette, SystemPalette */ /*! diff --git a/src/quick/items/qquickscreen.cpp b/src/quick/items/qquickscreen.cpp index 913003ef15..914ebd3047 100644 --- a/src/quick/items/qquickscreen.cpp +++ b/src/quick/items/qquickscreen.cpp @@ -75,6 +75,8 @@ QT_BEGIN_NAMESPACE Note that the Screen type is not valid at Component.onCompleted, because the Item or Window has not been displayed on a screen by this time. + + \sa {Qt Quick Examples - Window and Screen} */ /*! diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index e35c128001..41fbcc7c48 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -396,7 +396,12 @@ QString QQuickTextEdit::text() const remarkably better performance for modifying especially large rich text content. - \sa clear(), textFormat + Note that some keyboards use a predictive function. In this case, + the text being composed by the input method is not part of this property. + The part of the text related to the predictions is underlined and stored in + the \l preeditText property. + + \sa clear(), preeditText, textFormat */ void QQuickTextEdit::setText(const QString &text) { @@ -428,6 +433,11 @@ void QQuickTextEdit::setText(const QString &text) \since 5.7 This property contains partial text input from an input method. + + To turn off partial text that results from predictions, set the \c Qt.ImhNoPredictiveText + flag in inputMethodHints. + + \sa inputMethodHints */ QString QQuickTextEdit::preeditText() const { diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index 0096158d58..f41ab2f626 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -125,7 +125,13 @@ void QQuickTextInput::componentComplete() The text in the TextInput. - \sa clear() + Note that some keyboards use a predictive function. In this case, + the text being composed by the input method is not part of this property. + The part of the text related to the predictions is underlined and stored in + the \l preeditText property. To get whole text displayed in the TextInput + use \l displayText property. + + \sa clear(), displayText, preeditText */ QString QQuickTextInput::text() const { @@ -2391,7 +2397,10 @@ QString QQuickTextInput::displayText() const This property contains partial text input from an input method. - \sa displayText + To turn off partial text that results from predictions, set the \c Qt.ImhNoPredictiveText + flag in inputMethodHints. + + \sa displayText, inputMethodHints */ QString QQuickTextInput::preeditText() const { diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 1a57fdd4f2..597cd300df 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -3269,10 +3269,12 @@ void QQuickWindow::endExternalCommands() whether it's a dialog, popup, or a regular window, and whether it should have a title bar, etc. - The flags which you read from this property might differ from the ones + The flags that you read from this property might differ from the ones that you set if the requested flags could not be fulfilled. - \sa Qt::WindowFlags + \snippet qml/splashWindow.qml entire + + \sa Qt::WindowFlags, {Qt Quick Examples - Window and Screen} */ /*! @@ -3357,10 +3359,12 @@ void QQuickWindow::endExternalCommands() visibility property you will always get the actual state, never \c AutomaticVisibility. - When a window is not visible its visibility is Hidden, and setting + When a window is not visible, its visibility is \c Hidden, and setting visibility to \l {QWindow::}{Hidden} is the same as setting \l visible to \c false. - \sa visible + \snippet qml/windowVisibility.qml entire + + \sa visible, {Qt Quick Examples - Window and Screen} \since 5.1 */ @@ -3466,17 +3470,8 @@ void QQuickWindow::endExternalCommands() Item or Window within which it was declared, you can remove that relationship by setting \c transientParent to \c null: - \qml - import QtQuick.Window 2.13 - - Window { - // visible is false by default - Window { - transientParent: null - visible: true - } - } - \endqml + \snippet qml/nestedWindowTransientParent.qml 0 + \snippet qml/nestedWindowTransientParent.qml 1 In order to cause the window to be centered above its transient parent by default, depending on the window manager, it may also be necessary to set @@ -3522,6 +3517,9 @@ void QQuickWindow::endExternalCommands() The active status of the window. + \snippet qml/windowPalette.qml declaration-and-color + \snippet qml/windowPalette.qml closing-brace + \sa requestActivate() */ @@ -3535,14 +3533,7 @@ void QQuickWindow::endExternalCommands() Here is an example which changes a label to show the active state of the window in which it is shown: - \qml - import QtQuick 2.4 - import QtQuick.Window 2.2 - - Text { - text: Window.active ? "active" : "inactive" - } - \endqml + \snippet qml/windowActiveAttached.qml entire */ /*! @@ -4150,10 +4141,11 @@ void QQuickWindow::setTextRenderType(QQuickWindow::TextRenderType renderType) palette which serves as a default for all application windows. You can also set the default palette for windows by passing a custom palette to QGuiApplication::setPalette(), before loading any QML. - ApplicationWindow propagates explicit palette properties to child controls. If you change a specific - property on the window's palette, that property propagates to all child controls in the window, + Window propagates explicit palette properties to child items and controls, overriding any system defaults for that property. + \snippet qml/windowPalette.qml entire + \sa Item::palette, Popup::palette, ColorGroup, SystemPalette //! internal \sa QQuickAbstractPaletteProvider, QQuickPalette */ diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp index 4ddef6b4c2..a9299ff17e 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp @@ -143,6 +143,18 @@ void QSGSoftwareRenderContext::initializeIfNeeded() void QSGSoftwareRenderContext::invalidate() { + qDeleteAll(m_texturesToDelete); + m_texturesToDelete.clear(); + + qDeleteAll(m_textures); + m_textures.clear(); + + qDeleteAll(m_fontEnginesToClean); + m_fontEnginesToClean.clear(); + + qDeleteAll(m_glyphCaches); + m_glyphCaches.clear(); + m_sg->renderContextInvalidated(this); emit invalidated(); } diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp index c6877369c0..ab70d8e09f 100644 --- a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp +++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp @@ -3251,7 +3251,7 @@ bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *ren // unmerged batch since the material (and so the shaders) is the same. QSGGeometry *g = gn->geometry(); QSGMaterial *material = gn->activeMaterial(); - ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material, g); + ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material, g, m_renderMode); if (!sms) return false; diff --git a/src/quick/scenegraph/util/qsgsimpletexturenode.cpp b/src/quick/scenegraph/util/qsgsimpletexturenode.cpp index 8dbfcad38b..b8f0f336f2 100644 --- a/src/quick/scenegraph/util/qsgsimpletexturenode.cpp +++ b/src/quick/scenegraph/util/qsgsimpletexturenode.cpp @@ -292,7 +292,7 @@ void QSGSimpleTextureNode::setTextureCoordinatesTransform(QSGSimpleTextureNode:: return; d->texCoordMode = mode; qsgsimpletexturenode_update(&m_geometry, texture(), m_rect, d->sourceRect, d->texCoordMode); - markDirty(DirtyMaterial); + markDirty(DirtyGeometry | DirtyMaterial); } /*! diff --git a/src/quick/util/qquickanimation.cpp b/src/quick/util/qquickanimation.cpp index 63573847af..af60f5b02b 100644 --- a/src/quick/util/qquickanimation.cpp +++ b/src/quick/util/qquickanimation.cpp @@ -2567,7 +2567,38 @@ void QQuickPropertyAnimation::setProperties(const QString &prop) QQmlListProperty<QObject> QQuickPropertyAnimation::targets() { Q_D(QQuickPropertyAnimation); - return QQmlListProperty<QObject>(this, &(d->targets)); + using ListPtr = QList<QPointer<QObject>> *; + using LP = QQmlListProperty<QObject>; + LP::AppendFunction appendFn = [](LP *prop, QObject *value) + { + static_cast<ListPtr>(prop->data)->append(value); + }; + LP::CountFunction countFn = [](LP *prop) + { + return static_cast<ListPtr>(prop->data)->size(); + }; + + LP::AtFunction atFn = [](LP *prop, qsizetype index) -> QObject * + { + return static_cast<ListPtr>(prop->data)->at(index); + }; + + LP::ClearFunction clearFN = [](LP *prop) + { + return static_cast<ListPtr>(prop->data)->clear(); + }; + + LP::ReplaceFunction replaceFn = [](LP *prop, qsizetype index, QObject *value) + { + static_cast<ListPtr>(prop->data)->replace(index, value); + }; + + LP::RemoveLastFunction removeLastFn = [](LP *prop) + { + static_cast<ListPtr>(prop->data)->removeLast(); + }; + + return QQmlListProperty<QObject>(this, &(d->targets), appendFn, countFn, atFn, clearFN, replaceFn, removeLastFn); } /*! @@ -2639,7 +2670,7 @@ QQuickStateActions QQuickPropertyAnimation::createTransitionActions(QQuickStateA if (!d->propertyName.isEmpty()) props << d->propertyName; - QList<QObject*> targets = d->targets; + QList<QPointer<QObject>> targets = d->targets; if (d->target) targets.append(d->target); @@ -2668,10 +2699,14 @@ QQuickStateActions QQuickPropertyAnimation::createTransitionActions(QQuickStateA for (int i = 0; i < props.count(); ++i) { for (int j = 0; j < targets.count(); ++j) { + const auto& guarded = targets.at(j); + if (guarded.isNull()) + continue; + QObject *target = guarded.get(); QQuickStateAction myAction; QString errorMessage; const QString &propertyName = props.at(i); - myAction.property = d->createProperty(targets.at(j), propertyName, this, &errorMessage); + myAction.property = d->createProperty(target, propertyName, this, &errorMessage); if (myAction.property.isValid()) { if (usingDefaultProperties) successfullyCreatedDefaultProperty = true; diff --git a/src/quick/util/qquickanimation_p_p.h b/src/quick/util/qquickanimation_p_p.h index 56121221bf..97bf156d0d 100644 --- a/src/quick/util/qquickanimation_p_p.h +++ b/src/quick/util/qquickanimation_p_p.h @@ -279,7 +279,7 @@ public: QObject *target; QString propertyName; QString properties; - QList<QObject *> targets; + QList<QPointer<QObject>> targets; QList<QObject *> exclude; QString defaultProperties; diff --git a/src/quick/util/qquickapplication.cpp b/src/quick/util/qquickapplication.cpp index fdd9cb2f13..62e792199d 100644 --- a/src/quick/util/qquickapplication.cpp +++ b/src/quick/util/qquickapplication.cpp @@ -235,6 +235,7 @@ QQuickApplication::QQuickApplication(QObject *parent) connect(guiApp, &QGuiApplication::applicationDisplayNameChanged, this, &QQuickApplication::displayNameChanged); + connect(guiApp, &QGuiApplication::primaryScreenChanged, this, &QQuickApplication::updateScreens); connect(guiApp, &QGuiApplication::screenAdded, this, &QQuickApplication::updateScreens); connect(guiApp, &QGuiApplication::screenRemoved, this, &QQuickApplication::updateScreens); updateScreens(); diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp index b8167ebed3..a1f3fc64fd 100644 --- a/src/quick/util/qquickdeliveryagent.cpp +++ b/src/quick/util/qquickdeliveryagent.cpp @@ -1833,7 +1833,7 @@ QVector<QQuickItem *> QQuickDeliveryAgentPrivate::pointerTargets(QQuickItem *ite qCDebug(lcPtrLoc) << q << "point" << point.id() << point.scenePosition() << "->" << itemPos << ": relevant?" << relevant << "to" << item << point; // if the item clips, we can potentially return early if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { - if (!relevant) + if (!item->clipRect().contains(itemPos)) return targets; } @@ -2116,11 +2116,6 @@ void QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(QQuickItem *item, b if (item->acceptTouchEvents()) { qCDebug(lcTouch) << "considering delivering" << &touchEvent << " to " << item; - // If any parent filters the event, we're done. - hasFiltered.clear(); - if (sendFilteredPointerEvent(&touchEvent, item)) - return; - // Deliver the touch event to the given item qCDebug(lcTouch) << "actually delivering" << &touchEvent << " to " << item; QCoreApplication::sendEvent(item, &touchEvent); diff --git a/src/quick/util/qquicksystempalette.cpp b/src/quick/util/qquicksystempalette.cpp index 7ccb19b5d3..01f72eab09 100644 --- a/src/quick/util/qquicksystempalette.cpp +++ b/src/quick/util/qquicksystempalette.cpp @@ -63,8 +63,8 @@ public: The SystemPalette type provides access to the Qt application palettes. This provides information about the standard colors used for application windows, buttons and other features. These colors - are grouped into three \e {color groups}: \c Active, \c Inactive, - and \c Disabled. See the QPalette documentation for details about + are grouped into three \e {color groups}: \c active, \c inactive, + and \c disabled. See the QPalette documentation for details about color groups and the properties provided by SystemPalette. This can be used to color items in a way that provides a more diff --git a/src/quickcontrols2/doc/snippets/qtquickcontrols-custom-palette-buttons.qml b/src/quickcontrols2/doc/snippets/qtquickcontrols-custom-palette-buttons.qml new file mode 100644 index 0000000000..82180fe465 --- /dev/null +++ b/src/quickcontrols2/doc/snippets/qtquickcontrols-custom-palette-buttons.qml @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//![entire] +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ApplicationWindow { + visible: true + + //![palette] + palette { + buttonText: "red" + button: "khaki" + + disabled { + buttonText: "lavender" + button: "coral" + } + } + //![palette] + + ColumnLayout { + id: layout + anchors.fill: parent + anchors.margins: 3 + Button { + text: qsTr("Disabled button") + enabled: false + } + + Button { + text: qsTr("Enabled button") + } + + TextField { + Layout.fillWidth: true + placeholderText: "type something here" + } + } +} +//![entire] diff --git a/src/quickcontrols2/doc/src/includes/qquickheaderview.qdocinc b/src/quickcontrols2/doc/src/includes/qquickheaderview.qdocinc new file mode 100644 index 0000000000..7e12f53a6d --- /dev/null +++ b/src/quickcontrols2/doc/src/includes/qquickheaderview.qdocinc @@ -0,0 +1,76 @@ +//! [detailed-description] +A \1 provides a styled table header. +It can either be used as an independent view or header for a \l TableView. + +You can add a header for a TableView by assigning a \1 +to the \l {TableView::syncView} property. The header and the table will +then be kept in sync while flicking. + +By default, \1 displays +\l {QAbstractItemModel::headerData()}{header data} +from the \l {TableView::syncView}{sync view's} \l {TableView::model}{model}. +If you don't wish to use this model, you can assign a different model to the +\l {TableView::model}{model} property. If you assign a model that is a +QAbstractItemModel, its header data will be used. Otherwise the data in +the model will be used directly (for example, if you assign a model that +is simply an array of strings). + +By default, \l textRole is set to \c "display", meaning that data from the +model's \l {Qt::ItemDataRole}{Qt::DisplayRole} will be used. You can set +this to another role name in order to have that data displayed instead. + +The application is responsible for placing the header at the +correct location in the scene. You can add as many headers as you +want to a single TableView, which can be useful if you for example want +to place headers on all four sides of the table. + +The following snippet shows how you can add a horizontal and vertical header +view to a table view: + +\snippet qtquickcontrols-headerview.qml 0 + +A \1 will have +\l {TableView::resizableColumns}{resizableColumns} set to \c true by default. +//! [detailed-description] + +//! [syncView] +This property holds the TableView to synchronize with. + +Once this property is bound to another TableView, both header and table +will synchronize with regard to column widths, column spacing, and flicking +\1. + +If the \l model is not explicitly set, then the header will use the syncView's +model to label the columns. + +\sa model TableView +//! [syncView] + +//! [model] +This property holds the model providing data for the \1 header view. + +When model is not explicitly set, the header will use the syncView's +model once syncView is set. + +If model is a QAbstractTableModel, its \1 headerData() will +be accessed. + +If model is a QAbstractItemModel other than QAbstractTableModel, model's data() +will be accessed. + +Otherwise, the behavior is same as setting TableView::model. + +\sa TableView {TableView::model} {model} QAbstractTableModel +//! [model] + +//! [textRole] +This property holds the model role used to display text in each header cell. + +When the model has multiple roles, textRole can be set to determine which +role should be displayed. + +If model is a QAbstractItemModel then it will default to "display"; otherwise +it is empty. + +\sa QAbstractItemModel::roleNames() +//! [textRole] diff --git a/src/quickdialogs2/quickdialogs2/CMakeLists.txt b/src/quickdialogs2/quickdialogs2/CMakeLists.txt index f6cf1d8325..f05c4c83a9 100644 --- a/src/quickdialogs2/quickdialogs2/CMakeLists.txt +++ b/src/quickdialogs2/quickdialogs2/CMakeLists.txt @@ -9,6 +9,7 @@ qt_internal_add_qml_module(QuickDialogs2 PLUGIN_TARGET qtquickdialogsplugin DEPENDENCIES QtQuick/auto + QtQuick.Dialogs.quickimpl/auto SOURCES qquickabstractdialog.cpp qquickabstractdialog_p.h diff --git a/src/quicktemplates2/qquickdeferredexecute.cpp b/src/quicktemplates2/qquickdeferredexecute.cpp index 817415c492..635b7c142a 100644 --- a/src/quicktemplates2/qquickdeferredexecute.cpp +++ b/src/quicktemplates2/qquickdeferredexecute.cpp @@ -75,6 +75,15 @@ static bool beginDeferred(QQmlEnginePrivate *enginePriv, const QQmlProperty &pro int propertyIndex = property.index(); int wasInProgress = enginePriv->inProgressCreations; + /* we don't want deferred properties to suddenly depend on arbitrary + other properties which might have trigerred the construction of + objects as a consequence of a read. + */ + auto bindingStatus = QtPrivate::suspendCurrentBindingStatus(); + auto cleanup = qScopeGuard([&](){ + QtPrivate::restoreBindingStatus(bindingStatus); + }); + for (auto dit = ddata->deferredData.rbegin(); dit != ddata->deferredData.rend(); ++dit) { QQmlData::DeferredData *deferData = *dit; @@ -139,6 +148,15 @@ void completeDeferred(QObject *object, const QString &property) QQmlData *data = QQmlData::get(object); QQmlComponentPrivate::DeferredState *state = deferredStates()->take(qHash(object, property)); if (data && state && !data->wasDeleted(object)) { + /* we don't want deferred properties to suddenly depend on arbitrary + other properties which might have trigerred the construction of + objects as a consequence of a read. + */ + auto bindingStatus = QtPrivate::suspendCurrentBindingStatus(); + auto cleanup = qScopeGuard([&](){ + QtPrivate::restoreBindingStatus(bindingStatus); + }); + QQmlEnginePrivate *ep = QQmlEnginePrivate::get(data->context->engine()); QQmlComponentPrivate::completeDeferred(ep, state); } diff --git a/src/quicktemplates2/qquickheaderview.cpp b/src/quicktemplates2/qquickheaderview.cpp index 94d5635ad1..298dbfbc05 100644 --- a/src/quicktemplates2/qquickheaderview.cpp +++ b/src/quicktemplates2/qquickheaderview.cpp @@ -47,16 +47,9 @@ \inherits TableView \brief Provides a horizontal header view to accompany a \l TableView. - A HorizontalHeaderView provides labeling of the columns of a \l TableView. - To add a horizontal header to a TableView, bind the - \l {HorizontalHeaderView::syncView} {syncView} property to the TableView: + \include qquickheaderview.qdocinc {detailed-description} {HorizontalHeaderView} - \snippet qtquickcontrols2-headerview-simple.qml horizontal - - The header displays data from the {syncView}'s model by default, but can - also have its own model. If the model is a QAbstractTableModel, then - the header will display the model's horizontal headerData(); otherwise, - the model's data(). + \sa VerticalHeaderView */ /*! @@ -66,112 +59,45 @@ \inherits TableView \brief Provides a vertical header view to accompany a \l TableView. - A VerticalHeaderView provides labeling of the rows of a \l TableView. - To add a vertical header to a TableView, bind the - \l {VerticalHeaderView::syncView} {syncView} property to the TableView: - - \snippet qtquickcontrols2-headerview-simple.qml vertical + \include qquickheaderview.qdocinc {detailed-description} {VerticalHeaderView} - The header displays data from the {syncView}'s model by default, but can - also have its own model. If the model is a QAbstractTableModel, then - the header will display the model's vertical headerData(); otherwise, - the model's data(). + \sa HorizontalHeaderView */ /*! \qmlproperty TableView QtQuick::HorizontalHeaderView::syncView - This property holds the TableView to synchronize with. - - Once this property is bound to another TableView, both header and table - will synchronize with regard to column widths, column spacing, and flicking - horizontally. - - If the \l model is not explicitly set, then the header will use the syncView's - model to label the columns. - - \sa model TableView + \include qquickheaderview.qdocinc {syncView} {horizontally} */ /*! \qmlproperty TableView QtQuick::VerticalHeaderView::syncView - This property holds the TableView to synchronize with. - - Once this property is bound to another TableView, both header and table - will synchronize with regard to row heights, row spacing, and flicking - vertically. - - If the \l model is not explicitly set, then the header will use the syncView's - model to label the rows. - - \sa model TableView + \include qquickheaderview.qdocinc {syncView} {vertically} */ /*! \qmlproperty QVariant QtQuick::HorizontalHeaderView::model - This property holds the model providing data for the horizontal header view. - - When model is not explicitly set, the header will use the syncView's - model once syncView is set. - - If model is a QAbstractTableModel, its horizontal headerData() will - be accessed. - - If model is a QAbstractItemModel other than QAbstractTableModel, model's data() - will be accessed. - - Otherwise, the behavior is same as setting TableView::model. - - \sa TableView {TableView::model} {model} QAbstractTableModel + \include qquickheaderview.qdocinc {model} {horizontal} */ /*! \qmlproperty QVariant QtQuick::VerticalHeaderView::model - This property holds the model providing data for the vertical header view. - - When model is not explicitly set, it will be synchronized with syncView's model - once syncView is set. - - If model is a QAbstractTableModel, its vertical headerData() will - be accessed. - - If model is a QAbstractItemModel other than QAbstractTableModel, model's data() - will be accessed. - - Otherwise, the behavior is same as setting TableView::model. - - \sa TableView {TableView::model} {model} QAbstractTableModel + \include qquickheaderview.qdocinc {model} {vertical} */ /*! \qmlproperty QString QtQuick::HorizontalHeaderView::textRole - This property holds the model role used to display text in each header cell. - - When the model has multiple roles, textRole can be set to determine which - role should be displayed. - - If model is a QAbstractItemModel then it will default to "display"; otherwise - it is empty. - - \sa QAbstractItemModel::roleNames() + \include qquickheaderview.qdocinc {textRole} */ /*! \qmlproperty QString QtQuick::VerticalHeaderView::textRole - This property holds the model role used to display text in each header cell. - - When the model has multiple roles, textRole can be set to determine which - role should be displayed. - - If model is a QAbstractItemModel then it will default to "display"; otherwise - it is empty. - - \sa QAbstractItemModel::roleNames() + \include qquickheaderview.qdocinc {textRole} */ QT_BEGIN_NAMESPACE diff --git a/src/quicktemplates2/qquickmenu.cpp b/src/quicktemplates2/qquickmenu.cpp index e045375f89..373e78f71c 100644 --- a/src/quicktemplates2/qquickmenu.cpp +++ b/src/quicktemplates2/qquickmenu.cpp @@ -745,6 +745,12 @@ QQuickMenu::~QQuickMenu() // been destroyed before that is called. while (d->contentModel->count() > 0) d->removeItem(0, d->itemAt(0)); + + if (d->contentItem) { + const auto children = d->contentItem->childItems(); + for (QQuickItem *child : std::as_const(children)) + QQuickItemPrivate::get(child)->removeItemChangeListener(d, QQuickItemPrivate::SiblingOrder); + } } /*! diff --git a/src/quicktemplates2/qquickpopup.cpp b/src/quicktemplates2/qquickpopup.cpp index 4ac642ccc4..de862cfc11 100644 --- a/src/quicktemplates2/qquickpopup.cpp +++ b/src/quicktemplates2/qquickpopup.cpp @@ -340,6 +340,7 @@ void QQuickPopupPrivate::closeOrReject() dialog->reject(); else q->close(); + touchId = -1; } bool QQuickPopupPrivate::tryClose(const QPointF &pos, QQuickPopup::ClosePolicy flags) diff --git a/src/quicktemplates2/qquickswipeview.cpp b/src/quicktemplates2/qquickswipeview.cpp index 3277ba524f..8a18d6266d 100644 --- a/src/quicktemplates2/qquickswipeview.cpp +++ b/src/quicktemplates2/qquickswipeview.cpp @@ -110,7 +110,7 @@ class QQuickSwipeViewPrivate : public QQuickContainerPrivate Q_DECLARE_PUBLIC(QQuickSwipeView) public: - void resizeItem(QQuickItem *item); + void resizeItem(int index, QQuickItem *item); void resizeItems(); static QQuickSwipeViewPrivate *get(QQuickSwipeView *view); @@ -144,25 +144,29 @@ public: int currentIndex = -1; }; +void QQuickSwipeViewPrivate::resizeItem(int index, QQuickItem *item) +{ + QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors; + // TODO: expose QQuickAnchorLine so we can test for other conflicting anchors + if (anchors && (anchors->fill() || anchors->centerIn()) && !item->property("_q_QQuickSwipeView_warned").toBool()) { + qmlWarning(item) << "SwipeView has detected conflicting anchors. Unable to layout the item."; + item->setProperty("_q_QQuickSwipeView_warned", true); + } + if (orientation == Qt::Horizontal) + item->setPosition({index * (contentItem->width() + spacing), 0}); + else + item->setPosition({0, index * (contentItem->height() + spacing)}); + item->setSize(QSizeF(contentItem->width(), contentItem->height())); +} + void QQuickSwipeViewPrivate::resizeItems() { Q_Q(QQuickSwipeView); const int count = q->count(); for (int i = 0; i < count; ++i) { QQuickItem *item = itemAt(i); - if (item) { - QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors; - // TODO: expose QQuickAnchorLine so we can test for other conflicting anchors - if (anchors && (anchors->fill() || anchors->centerIn()) && !item->property("_q_QQuickSwipeView_warned").toBool()) { - qmlWarning(item) << "SwipeView has detected conflicting anchors. Unable to layout the item."; - item->setProperty("_q_QQuickSwipeView_warned", true); - } - if (orientation == Qt::Horizontal) - item->setPosition({i * (contentItem->width() + spacing), 0}); - else - item->setPosition({0, i * (contentItem->height() + spacing)}); - item->setSize(QSizeF(contentItem->width(), contentItem->height())); - } + if (item) + resizeItem(i, item); } } @@ -301,6 +305,13 @@ QQuickSwipeViewAttached *QQuickSwipeView::qmlAttachedProperties(QObject *object) return new QQuickSwipeViewAttached(object); } +void QQuickSwipeView::componentComplete() +{ + Q_D(QQuickSwipeView); + QQuickContainer::componentComplete(); + d->resizeItems(); +} + void QQuickSwipeView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) { Q_D(QQuickSwipeView); @@ -312,7 +323,7 @@ void QQuickSwipeView::itemAdded(int index, QQuickItem *item) { Q_D(QQuickSwipeView); if (isComponentComplete()) - item->setSize(QSizeF(d->contentItem->width(), d->contentItem->height())); + d->resizeItem(index, item); QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(qmlAttachedPropertiesObject<QQuickSwipeView>(item)); if (attached) QQuickSwipeViewAttachedPrivate::get(attached)->update(this, index); diff --git a/src/quicktemplates2/qquickswipeview_p.h b/src/quicktemplates2/qquickswipeview_p.h index 8a09a37e22..7723a58374 100644 --- a/src/quicktemplates2/qquickswipeview_p.h +++ b/src/quicktemplates2/qquickswipeview_p.h @@ -93,6 +93,7 @@ Q_SIGNALS: Q_REVISION(2, 2) void orientationChanged(); protected: + void componentComplete() override; void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; void itemAdded(int index, QQuickItem *item) override; void itemMoved(int index, QQuickItem *item) override; diff --git a/src/quicktestutils/quick/viewtestutils.cpp b/src/quicktestutils/quick/viewtestutils.cpp index 9051eb15a9..cd6196e677 100644 --- a/src/quicktestutils/quick/viewtestutils.cpp +++ b/src/quicktestutils/quick/viewtestutils.cpp @@ -32,6 +32,7 @@ #include <QtQuick/QQuickView> #include <QtQuick/QQuickView> #include <QtGui/QScreen> +#include <QtGui/qpa/qwindowsysteminterface.h> #include <QtTest/QTest> @@ -471,6 +472,10 @@ namespace QQuickTouchUtils { } +namespace QTest { + int Q_TESTLIB_EXPORT defaultMouseDelay(); +} + namespace QQuickTest { /*! \internal @@ -528,6 +533,88 @@ namespace QQuickTest { return false; return true; } + + // TODO maybe move the generic pointerPress/Move/Release functions to QTestLib later on + + static Qt::MouseButton pressedTabletButton = Qt::NoButton; + static Qt::KeyboardModifiers pressedTabletModifiers = Qt::NoModifier; + + void pointerPress(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p, + Qt::MouseButton button, Qt::KeyboardModifiers modifiers) + { + switch (dev->type()) { + case QPointingDevice::DeviceType::Mouse: + case QPointingDevice::DeviceType::TouchPad: + QTest::mousePress(window, button, modifiers, p); + break; + case QPointingDevice::DeviceType::TouchScreen: + QTest::touchEvent(window, const_cast<QPointingDevice *>(dev)).press(pointId, p, window); + QQuickTouchUtils::flush(window); + break; + case QPointingDevice::DeviceType::Puck: + case QPointingDevice::DeviceType::Stylus: + case QPointingDevice::DeviceType::Airbrush: + QTest::lastMouseTimestamp += QTest::defaultMouseDelay(); + pressedTabletButton = button; + pressedTabletModifiers = modifiers; + QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, p, window->mapToGlobal(p), + button, 0.8, 0, 0, 0, 0, 0, modifiers); + break; + default: + qWarning() << "can't send a press event from" << dev; + break; + } + } + + void pointerMove(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p) + { + switch (dev->type()) { + case QPointingDevice::DeviceType::Mouse: + case QPointingDevice::DeviceType::TouchPad: + QTest::mouseMove(window, p); + break; + case QPointingDevice::DeviceType::TouchScreen: + QTest::touchEvent(window, const_cast<QPointingDevice *>(dev)).move(pointId, p, window); + QQuickTouchUtils::flush(window); + break; + case QPointingDevice::DeviceType::Puck: + case QPointingDevice::DeviceType::Stylus: + case QPointingDevice::DeviceType::Airbrush: + QTest::lastMouseTimestamp += QTest::defaultMouseDelay(); + QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, p, window->mapToGlobal(p), + pressedTabletButton, 0, 0, 0, 0, 0, 0, pressedTabletModifiers); + break; + default: + qWarning() << "can't send a move event from" << dev; + break; + } + } + + void pointerRelease(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p, + Qt::MouseButton button, Qt::KeyboardModifiers modifiers) + { + switch (dev->type()) { + case QPointingDevice::DeviceType::Mouse: + case QPointingDevice::DeviceType::TouchPad: + QTest::mouseRelease(window, button, modifiers, p); + break; + case QPointingDevice::DeviceType::TouchScreen: + QTest::touchEvent(window, const_cast<QPointingDevice *>(dev)).release(pointId, p, window); + QQuickTouchUtils::flush(window); + break; + case QPointingDevice::DeviceType::Puck: + case QPointingDevice::DeviceType::Stylus: + case QPointingDevice::DeviceType::Airbrush: + QTest::lastMouseTimestamp += QTest::defaultMouseDelay(); + QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, p, window->mapToGlobal(p), + Qt::NoButton, 0, 0, 0, 0, 0, 0, modifiers); + break; + default: + qWarning() << "can't send a press event from" << dev; + break; + } + } + } QT_END_NAMESPACE diff --git a/src/quicktestutils/quick/viewtestutils_p.h b/src/quicktestutils/quick/viewtestutils_p.h index fd38844446..feedaef76f 100644 --- a/src/quicktestutils/quick/viewtestutils_p.h +++ b/src/quicktestutils/quick/viewtestutils_p.h @@ -203,6 +203,17 @@ namespace QQuickTest { [[nodiscard]] bool initView(QQuickView &v, const QUrl &url, bool moveMouseOut = true, QByteArray *errorMessage = nullptr); [[nodiscard]] bool showView(QQuickView &v, const QUrl &url); + + void pointerPress(const QPointingDevice *dev, QQuickWindow *window, + int pointId, const QPoint &p, Qt::MouseButton button = Qt::LeftButton, + Qt::KeyboardModifiers modifiers = Qt::NoModifier); + + void pointerMove(const QPointingDevice *dev, QQuickWindow *window, int pointId, + const QPoint &p); + + void pointerRelease(const QPointingDevice *dev, QQuickWindow *window, int pointId, + const QPoint &p, Qt::MouseButton button = Qt::LeftButton, + Qt::KeyboardModifiers modifiers = Qt::NoModifier); } QT_END_NAMESPACE diff --git a/src/quickwidgets/qquickwidget.cpp b/src/quickwidgets/qquickwidget.cpp index d316d12588..c593ca2a81 100644 --- a/src/quickwidgets/qquickwidget.cpp +++ b/src/quickwidgets/qquickwidget.cpp @@ -1466,6 +1466,8 @@ void QQuickWidget::mouseMoveEvent(QMouseEvent *e) // top-level window always. QMouseEvent mappedEvent(e->type(), e->position(), e->position(), e->globalPosition(), e->button(), e->buttons(), e->modifiers(), e->source()); + // It's not just the timestamp but also the globalPressPosition, velocity etc. + mappedEvent.setTimestamp(e->timestamp()); QCoreApplication::sendEvent(d->offscreenWindow, &mappedEvent); e->setAccepted(mappedEvent.isAccepted()); } @@ -1481,10 +1483,12 @@ void QQuickWidget::mouseDoubleClickEvent(QMouseEvent *e) // See QTBUG-25831 QMouseEvent pressEvent(QEvent::MouseButtonPress, e->position(), e->position(), e->globalPosition(), e->button(), e->buttons(), e->modifiers(), e->source()); + pressEvent.setTimestamp(e->timestamp()); QCoreApplication::sendEvent(d->offscreenWindow, &pressEvent); e->setAccepted(pressEvent.isAccepted()); QMouseEvent mappedEvent(e->type(), e->position(), e->position(), e->globalPosition(), e->button(), e->buttons(), e->modifiers(), e->source()); + mappedEvent.setTimestamp(e->timestamp()); QCoreApplication::sendEvent(d->offscreenWindow, &mappedEvent); } @@ -1544,6 +1548,7 @@ void QQuickWidget::mousePressEvent(QMouseEvent *e) QMouseEvent mappedEvent(e->type(), e->position(), e->position(), e->globalPosition(), e->button(), e->buttons(), e->modifiers(), e->source()); + mappedEvent.setTimestamp(e->timestamp()); QCoreApplication::sendEvent(d->offscreenWindow, &mappedEvent); e->setAccepted(mappedEvent.isAccepted()); } @@ -1557,6 +1562,7 @@ void QQuickWidget::mouseReleaseEvent(QMouseEvent *e) QMouseEvent mappedEvent(e->type(), e->position(), e->position(), e->globalPosition(), e->button(), e->buttons(), e->modifiers(), e->source()); + mappedEvent.setTimestamp(e->timestamp()); QCoreApplication::sendEvent(d->offscreenWindow, &mappedEvent); e->setAccepted(mappedEvent.isAccepted()); } diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index fd4a6748c1..d9730fbb58 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -13,7 +13,9 @@ endif() if(TARGET Qt::Quick) add_subdirectory(quicktest) endif() -add_subdirectory(core) +if (TARGET Qt::QuickTest) + add_subdirectory(core) +endif() add_subdirectory(qmldevtools) add_subdirectory(toolsupport) if(NOT UIKIT AND NOT ANDROID AND NOT QNX) # FIXME: QTBUG-92591 QTBUG-100202 diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt index ddd5f633e7..c4c8d8382c 100644 --- a/tests/auto/cmake/CMakeLists.txt +++ b/tests/auto/cmake/CMakeLists.txt @@ -65,6 +65,7 @@ if(TARGET Qt::Qml) _qt_internal_test_expect_pass(test_plugins) endif() _qt_internal_test_expect_pass(empty_qmldir) + _qt_internal_test_expect_fail(test_internal_singleton) endif() if(TARGET Qt::Quick) diff --git a/tests/auto/cmake/test_internal_singleton/CMakeLists.txt b/tests/auto/cmake/test_internal_singleton/CMakeLists.txt new file mode 100644 index 0000000000..f1db46a0f9 --- /dev/null +++ b/tests/auto/cmake/test_internal_singleton/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + + +cmake_minimum_required(VERSION 3.19) + +project(test_internal_singleton) + +find_package(Qt6 REQUIRED COMPONENTS Qml) + +qt_standard_project_setup() + +set_source_files_properties(Test.qml PROPERTIES + QT_QML_SINGLETON_TYPE TRUE + QT_QML_INTERNAL_TYPE TRUE +) + +qt_add_qml_module(test_internal_singleton + URI Controls + VERSION 1.0 + QML_FILES + Test.qml +) diff --git a/tests/auto/cmake/test_internal_singleton/Test.qml b/tests/auto/cmake/test_internal_singleton/Test.qml new file mode 100644 index 0000000000..a2eb03bd4e --- /dev/null +++ b/tests/auto/cmake/test_internal_singleton/Test.qml @@ -0,0 +1,4 @@ +pragma singleton +import QtQml + +QtObject {} diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index a5e8713700..81ac086294 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -108,6 +108,8 @@ private slots: void collectGarbageNestedWrappersTwoEngines(); void gcWithNestedDataStructure(); void stacktrace(); + void unshiftAndSort(); + void unshiftAndPushAndSort(); void numberParsing_data(); void numberParsing(); void automaticSemicolonInsertion(); @@ -284,6 +286,11 @@ private slots: void thisInConstructor(); void forOfAndGc(); + void symbolToVariant(); + + void garbageCollectedObjectMethodBase(); + void spreadNoOverflow(); + public: Q_INVOKABLE QJSValue throwingCppMethod1(); Q_INVOKABLE void throwingCppMethod2(); @@ -995,6 +1002,17 @@ private: int m_called = 1; }; +class TestQMetaObject2 : public QObject +{ + Q_OBJECT +public: + Q_INVOKABLE TestQMetaObject2(int a) : m_called(a) {} + int called() const { return m_called; } + +private: + int m_called = 1; +}; + void tst_QJSEngine::newQObjectPropertyCache() { QScopedPointer<QObject> obj(new QObject); @@ -1069,6 +1087,18 @@ void tst_QJSEngine::newQMetaObject() { QCOMPARE(metaObject.property("C").toInt(), 2); } + { + QJSEngine engine; + const QJSValue metaObject = engine.newQMetaObject(&TestQMetaObject2::staticMetaObject); + engine.globalObject().setProperty(QLatin1String("Example"), metaObject); + + const QJSValue invalid = engine.evaluate(QLatin1String("new Example()")); + QVERIFY(invalid.isError()); + QCOMPARE(invalid.toString(), QLatin1String("Error: Insufficient arguments")); + + const QJSValue valid = engine.evaluate(QLatin1String("new Example(123)")); + QCOMPARE(qjsvalue_cast<TestQMetaObject2 *>(valid)->called(), 123); + } } void tst_QJSEngine::exceptionInSlot() @@ -2018,6 +2048,81 @@ void tst_QJSEngine::stacktrace() } } +void tst_QJSEngine::unshiftAndSort() +{ + QJSEngine engine; + QJSValue func = engine.evaluate(R"""( + (function (objectArr, currIdx) { + objectArr.unshift({"sortIndex": currIdx}); + objectArr.sort(function(a, b) { + if (a.sortIndex > b.sortIndex) + return 1; + if (a.sortIndex < b.sortIndex) + return -1; + return 0; + }); + return objectArr; + }) + )"""); + QVERIFY(func.isCallable()); + QJSValue objectArr = engine.newArray(); + + for (int i = 0; i < 5; ++i) { + objectArr = func.call({objectArr, i}); + QVERIFY2(!objectArr.isError(), qPrintable(objectArr.toString())); + const int length = objectArr.property("length").toInt(); + + // It did add one element + QCOMPARE(length, i + 1); + + for (int x = 0; x < length; ++x) { + // We didn't sort cruft into the array. + QVERIFY(!objectArr.property(x).isUndefined()); + + // The array is actually sorted. + QCOMPARE(objectArr.property(x).property("sortIndex").toInt(), x); + } + } +} + +void tst_QJSEngine::unshiftAndPushAndSort() +{ + QJSEngine engine; + QJSValue func = engine.evaluate(R"""( + (function (objectArr, currIdx) { + objectArr.unshift({"sortIndex": currIdx}); + objectArr.push({"sortIndex": currIdx + 1}); + objectArr.sort(function(a, b) { + if (a.sortIndex > b.sortIndex) + return 1; + if (a.sortIndex < b.sortIndex) + return -1; + return 0; + }); + return objectArr; + }) + )"""); + QVERIFY(func.isCallable()); + QJSValue objectArr = engine.newArray(); + + for (int i = 0; i < 20; i += 2) { + objectArr = func.call({objectArr, i}); + QVERIFY2(!objectArr.isError(), qPrintable(objectArr.toString())); + const int length = objectArr.property("length").toInt(); + + // It did add 2 elements + QCOMPARE(length, i + 2); + + for (int x = 0; x < length; ++x) { + // We didn't sort cruft into the array. + QVERIFY(!objectArr.property(x).isUndefined()); + + // The array is actually sorted. + QCOMPARE(objectArr.property(x).property("sortIndex").toInt(), x); + } + } +} + void tst_QJSEngine::numberParsing_data() { QTest::addColumn<QString>("string"); @@ -5589,6 +5694,120 @@ void tst_QJSEngine::forOfAndGc() QTRY_VERIFY(o->property("count").toInt() > 32768); } +void tst_QJSEngine::symbolToVariant() +{ + QJSEngine engine; + const QJSValue val = engine.newSymbol("asymbol"); + QCOMPARE(val.toVariant(), QStringLiteral("Symbol(asymbol)")); + + const QVariant retained = val.toVariant(QJSValue::RetainJSObjects); + QCOMPARE(retained.metaType(), QMetaType::fromType<QJSValue>()); + QVERIFY(retained.value<QJSValue>().strictlyEquals(val)); + + QCOMPARE(val.toVariant(QJSValue::ConvertJSObjects), QStringLiteral("Symbol(asymbol)")); +} + +class PACHelper : public QObject { + Q_OBJECT +public: + Q_INVOKABLE bool shExpMatch(const QString &, const QString &) { return false; } + Q_INVOKABLE QString dnsResolve(const QString &) { return QString{}; } +}; + +class ProxyAutoConf { +public: + void exposeQObjectMethodsAsGlobal(QJSEngine *engine, QObject *object) + { + QJSValue helper = engine->newQObject(object); + QJSValue g = engine->globalObject(); + QJSValueIterator it(helper); + while (it.hasNext()) { + it.next(); + if (!it.value().isCallable()) + continue; + g.setProperty(it.name(), it.value()); + } + } + + bool parse(const QString & pacBytes) + { + jsFindProxyForURL = QJSValue(); + engine = std::make_unique<QJSEngine>(); + exposeQObjectMethodsAsGlobal(engine.get(), new PACHelper); + engine->evaluate(pacBytes); + jsFindProxyForURL = engine->globalObject().property(QStringLiteral("FindProxyForURL")); + return true; + } + + QString findProxyForUrl(const QString &url, const QString &host) + { + QJSValueList args; + args << url << host; + engine->collectGarbage(); + QJSValue callResult = jsFindProxyForURL.call(args); + return callResult.toString().trimmed(); + } + +private: + std::unique_ptr<QJSEngine> engine; + QJSValue jsFindProxyForURL; +}; + +QString const pacstring = R"js( +function FindProxyForURL(host) { + list_split_all = Array( + "oneoneoneoneo.oneo.oneo.oneoneo.one", + "twotwotwotwotw.otwo.twot.wotwotw.otw", + "threethreethr.eeth.reet.hreethr.eet", + "fourfourfourfo.urfo.urfo.urfourf.our", + "fivefivefivef.ivef.ivef.ivefive.fiv", + "sixsixsixsixsi.xsix.sixs.ixsixsi.xsi", + "sevensevenseve.nsev.ense.venseve.nse", + "eight.eighteigh.tei", + "*.nin.eninen.ine" + ) + list_myip_direct = + "10.254.0.0/255.255.0.0" + for (i = 0; i < list_split_all.length; ++i) + for (j = 0; j < list_myip_direct.length; ++j) + shExpMatch(host, list_split_all) + shExpMatch() + dnsResolve()} +)js"; + +void tst_QJSEngine::garbageCollectedObjectMethodBase() +{ + ProxyAutoConf proxyConf; + bool pac_read = false; + + const auto processUrl = [&](QString const &url, QString const &host) + { + if (!pac_read) { + proxyConf.parse(pacstring); + pac_read = true; + } + return proxyConf.findProxyForUrl(url, host); + }; + + const QString url = QStringLiteral("https://servername.domain.test"); + const QString host = QStringLiteral("servername.domain.test"); + + for (size_t i = 0; i < 5; ++i) { + auto future = std::async(processUrl, url, host); + QCOMPARE(future.get(), QLatin1String("Error: Insufficient arguments")); + } +} + +void tst_QJSEngine::spreadNoOverflow() +{ + QJSEngine engine; + + const QString program = QString::fromLatin1("var a = [] ;a.length = 555840;Math.max(...a)"); + const QJSValue result = engine.evaluate(program); + QVERIFY(result.isError()); + QCOMPARE(result.errorType(), QJSValue::RangeError); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" diff --git a/tests/auto/qml/qmlformat/data/arrowFunctionWithBinding.formatted.qml b/tests/auto/qml/qmlformat/data/arrowFunctionWithBinding.formatted.qml new file mode 100644 index 0000000000..2a47b2f152 --- /dev/null +++ b/tests/auto/qml/qmlformat/data/arrowFunctionWithBinding.formatted.qml @@ -0,0 +1,8 @@ +import QtQuick + +Item { Component.onCompleted: { let f = ([]) => {}; + let g = ([a]) => {}; + let h = ([a, b]) => {}; + let i = ([a, ...b]) => {}; + } +} diff --git a/tests/auto/qml/qmlformat/data/arrowFunctionWithBinding.qml b/tests/auto/qml/qmlformat/data/arrowFunctionWithBinding.qml new file mode 100644 index 0000000000..2a47b2f152 --- /dev/null +++ b/tests/auto/qml/qmlformat/data/arrowFunctionWithBinding.qml @@ -0,0 +1,8 @@ +import QtQuick + +Item { Component.onCompleted: { let f = ([]) => {}; + let g = ([a]) => {}; + let h = ([a, b]) => {}; + let i = ([a, ...b]) => {}; + } +} diff --git a/tests/auto/qml/qmlformat/data/forWithLet.formatted.qml b/tests/auto/qml/qmlformat/data/forWithLet.formatted.qml new file mode 100644 index 0000000000..5fecc1d180 --- /dev/null +++ b/tests/auto/qml/qmlformat/data/forWithLet.formatted.qml @@ -0,0 +1,8 @@ +import QtQml + +QtObject { + function foo() { + for (let i = 0; i < 5; ++i) + console.log(i); + } +} diff --git a/tests/auto/qml/qmlformat/data/forWithLet.qml b/tests/auto/qml/qmlformat/data/forWithLet.qml new file mode 100644 index 0000000000..afed76ebce --- /dev/null +++ b/tests/auto/qml/qmlformat/data/forWithLet.qml @@ -0,0 +1,8 @@ +import QtQml + +QtObject { +function foo() { +for (let i = 0; i < 5; ++i) +console.log(i); +} +} diff --git a/tests/auto/qml/qmlformat/tst_qmlformat.cpp b/tests/auto/qml/qmlformat/tst_qmlformat.cpp index 3837e193e5..29a14900d2 100644 --- a/tests/auto/qml/qmlformat/tst_qmlformat.cpp +++ b/tests/auto/qml/qmlformat/tst_qmlformat.cpp @@ -257,6 +257,11 @@ void TestQmlformat::testFormat_data() QTest::newRow("ecmaScriptClassInQml") << "ecmaScriptClassInQml.qml" << "ecmaScriptClassInQml.formatted.qml" << QStringList {}; + QTest::newRow("arrowFunctionWithBinding") + << "arrowFunctionWithBinding.qml" + << "arrowFunctionWithBinding.formatted.qml" << QStringList{}; + QTest::newRow("forWithLet") << "forWithLet.qml" + << "forWithLet.formatted.qml" << QStringList {}; } void TestQmlformat::testFormat() diff --git a/tests/auto/qml/qmllint/data/bad_builtins/builtins.qmltypes b/tests/auto/qml/qmllint/data/bad_builtins/builtins.qmltypes new file mode 100644 index 0000000000..96f98dd14e --- /dev/null +++ b/tests/auto/qml/qmllint/data/bad_builtins/builtins.qmltypes @@ -0,0 +1,513 @@ +import QtQuick.tooling 1.2 + +// This file describes the plugin-supplied types contained in the library. +// It is used for QML tooling purposes only. +// +// This file was auto-generated by: +// 'qmlplugindump -builtins' + +Module { + dependencies: [] + Component { + name: "Qt" + Enum { + name: "GlobalColor" + values: { + "color0": 0, + "color1": 1, + "black": 2, + "white": 3, + "darkGray": 4, + "gray": 5, + "lightGray": 6, + "red": 7, + "green": 8, + "blue": 9, + "cyan": 10, + "magenta": 11, + "yellow": 12, + "darkRed": 13, + "darkGreen": 14, + "darkBlue": 15, + "darkCyan": 16, + "darkMagenta": 17, + "darkYellow": 18, + "transparent": 19 + } + } + Enum { + name: "KeyboardModifiers" + values: { + "NoModifier": 0, + "ShiftModifier": 33554432, + "ControlModifier": 67108864, + "AltModifier": 134217728, + "MetaModifier": 268435456, + "KeypadModifier": 536870912, + "GroupSwitchModifier": 1073741824, + "KeyboardModifierMask": -33554432 + } + } + Enum { + name: "MouseButtons" + values: { + "NoButton": 0, + "LeftButton": 1, + "RightButton": 2, + "MidButton": 4, // For backwards compatibility + "MiddleButton": 4, + "BackButton": 8, + "XButton1": 8, + "ExtraButton1": 8, + "ForwardButton": 16, + "XButton2": 16, + "ExtraButton2": 16, + "TaskButton": 32, + "ExtraButton3": 32, + "ExtraButton4": 64, + "ExtraButton5": 128, + "ExtraButton6": 256, + "ExtraButton7": 512, + "ExtraButton8": 1024, + "ExtraButton9": 2048, + "ExtraButton10": 4096, + "ExtraButton11": 8192, + "ExtraButton12": 16384, + "ExtraButton13": 32768, + "ExtraButton14": 65536, + "ExtraButton15": 131072, + "ExtraButton16": 262144, + "ExtraButton17": 524288, + "ExtraButton18": 1048576, + "ExtraButton19": 2097152, + "ExtraButton20": 4194304, + "ExtraButton21": 8388608, + "ExtraButton22": 16777216, + "ExtraButton23": 33554432, + "ExtraButton24": 67108864, + "AllButtons": 134217727, + "MaxMouseButton": 67108864, + "MouseButtonMask": -1 + } + } + Enum { + name: "Orientation" + values: { + "Horizontal": 1, + "Vertical": 2 + } + } + Enum { + name: "Orientations" + values: { + "Horizontal": 1, + "Vertical": 2 + } + } + Enum { + name: "FocusPolicy" + values: { + "NoFocus": 0, + "TabFocus": 1, + "ClickFocus": 2, + "StrongFocus": 11, + "WheelFocus": 15 + } + } + Enum { + name: "TabFocusBehavior" + values: { + "NoTabFocus": 0, + "TabFocusTextControls": 1, + "TabFocusListControls": 2, + "TabFocusAllControls": 255 + } + } + Enum { + name: "SortOrder" + values: { + "AscendingOrder": 0, + "DescendingOrder": 1 + } + } + Enum { + name: "SplitBehavior" + values: { + "KeepEmptyParts": 0, + "SkipEmptyParts": 1 + } + } + Enum { + name: "Alignment" + values: { + "AlignLeft": 1, + "AlignLeading": 1, + "AlignRight": 2, + "AlignTrailing": 2, + "AlignHCenter": 4, + "AlignJustify": 8, + "AlignAbsolute": 16, + "AlignHorizontal_Mask": 31, + "AlignTop": 32, + "AlignBottom": 64, + "AlignVCenter": 128, + "AlignBaseline": 256, + "AlignVertical_Mask": 480, + "AlignCenter": 132 + } + } + Enum { + name: "TextFlag" + values: { + "TextSingleLine": 256, + "TextDontClip": 512, + "TextExpandTabs": 1024, + "TextShowMnemonic": 2048, + "TextWordWrap": 4096, + "TextWrapAnywhere": 8192, + "TextDontPrint": 16384, + "TextIncludeTrailingSpaces": 134217728, + "TextHideMnemonic": 32768, + "TextJustificationForced": 65536, + "TextForceLeftToRight": 131072, + "TextForceRightToLeft": 262144, + "TextLongestVariant": 524288, + "TextBypassShaping": 1048576 + } + } + Enum { + name: "TextElideMode" + values: { + "ElideLeft": 0, + "ElideRight": 1, + "ElideMiddle": 2, + "ElideNone": 3 + } + } + Enum { + name: "WindowType" + values: { + "Widget": 0, + "Window": 1, + "Dialog": 3, + "Sheet": 5, + "Drawer": 7, + "Popup": 9, + "Tool": 11, + "ToolTip": 13, + "SplashScreen": 15, + "Desktop": 17, + "SubWindow": 18, + "ForeignWindow": 33, + "CoverWindow": 65, + "WindowType_Mask": 255, + "MSWindowsFixedSizeDialogHint": 256, + "MSWindowsOwnDC": 512, + "BypassWindowManagerHint": 1024, + "X11BypassWindowManagerHint": 1024, + "FramelessWindowHint": 2048, + "WindowTitleHint": 4096, + "WindowSystemMenuHint": 8192, + "WindowMinimizeButtonHint": 16384, + "WindowMaximizeButtonHint": 32768, + "WindowMinMaxButtonsHint": 49152, + "WindowContextHelpButtonHint": 65536, + "WindowShadeButtonHint": 131072, + "WindowStaysOnTopHint": 262144, + "WindowTransparentForInput": 524288, + "WindowOverridesSystemGestures": 1048576, + "WindowDoesNotAcceptFocus": 2097152, + "MaximizeUsingFullscreenGeometryHint": 4194304, + "CustomizeWindowHint": 33554432, + "WindowStaysOnBottomHint": 67108864, + "WindowCloseButtonHint": 134217728, + "MacWindowToolBarButtonHint": 268435456, + "BypassGraphicsProxyWidget": 536870912, + "NoDropShadowWindowHint": 1073741824, + "WindowFullscreenButtonHint": -2147483648 + } + } + Enum { + name: "WindowFlags" + values: { + "Widget": 0, + "Window": 1, + "Dialog": 3, + "Sheet": 5, + "Drawer": 7, + "Popup": 9, + "Tool": 11, + "ToolTip": 13, + "SplashScreen": 15, + "Desktop": 17, + "SubWindow": 18, + "ForeignWindow": 33, + "CoverWindow": 65, + "WindowType_Mask": 255, + "MSWindowsFixedSizeDialogHint": 256, + "MSWindowsOwnDC": 512, + "BypassWindowManagerHint": 1024, + "X11BypassWindowManagerHint": 1024, + "FramelessWindowHint": 2048, + "WindowTitleHint": 4096, + "WindowSystemMenuHint": 8192, + "WindowMinimizeButtonHint": 16384, + "WindowMaximizeButtonHint": 32768, + "WindowMinMaxButtonsHint": 49152, + "WindowContextHelpButtonHint": 65536, + "WindowShadeButtonHint": 131072, + "WindowStaysOnTopHint": 262144, + "WindowTransparentForInput": 524288, + "WindowOverridesSystemGestures": 1048576, + "WindowDoesNotAcceptFocus": 2097152, + "MaximizeUsingFullscreenGeometryHint": 4194304, + "CustomizeWindowHint": 33554432, + "WindowStaysOnBottomHint": 67108864, + "WindowCloseButtonHint": 134217728, + "MacWindowToolBarButtonHint": 268435456, + "BypassGraphicsProxyWidget": 536870912, + "NoDropShadowWindowHint": 1073741824, + "WindowFullscreenButtonHint": -2147483648 + } + } + Enum { + name: "WindowState" + values: { + "WindowNoState": 0, + "WindowMinimized": 1, + "WindowMaximized": 2, + "WindowFullScreen": 4, + "WindowActive": 8 + } + } + Enum { + name: "WindowStates" + values: { + "WindowNoState": 0, + "WindowMinimized": 1, + "WindowMaximized": 2, + "WindowFullScreen": 4, + "WindowActive": 8 + } + } + Enum { + name: "ApplicationState" + values: { + "ApplicationSuspended": 0, + "ApplicationHidden": 1, + "ApplicationInactive": 2, + "ApplicationActive": 4 + } + } + Enum { + name: "ScreenOrientation" + values: { + "PrimaryOrientation": 0, + "PortraitOrientation": 1, + "LandscapeOrientation": 2, + "InvertedPortraitOrientation": 4, + "InvertedLandscapeOrientation": 8 + } + } + Enum { + name: "ScreenOrientations" + values: { + "PrimaryOrientation": 0, + "PortraitOrientation": 1, + "LandscapeOrientation": 2, + "InvertedPortraitOrientation": 4, + "InvertedLandscapeOrientation": 8 + } + } + Enum { + name: "WidgetAttribute" + values: { + "WA_Disabled": 0, + "WA_UnderMouse": 1, + "WA_MouseTracking": 2, + "WA_ContentsPropagated": 3, + "WA_OpaquePaintEvent": 4, + "WA_NoBackground": 4, + "WA_StaticContents": 5, + "WA_LaidOut": 7, + "WA_PaintOnScreen": 8, + "WA_NoSystemBackground": 9, + "WA_UpdatesDisabled": 10, + "WA_Mapped": 11, + "WA_MacNoClickThrough": 12, + "WA_InputMethodEnabled": 14, + "WA_WState_Visible": 15, + "WA_WState_Hidden": 16, + "WA_ForceDisabled": 32, + "WA_KeyCompression": 33, + "WA_PendingMoveEvent": 34, + "WA_PendingResizeEvent": 35, + "WA_SetPalette": 36, + "WA_SetFont": 37, + "WA_SetCursor": 38, + "WA_NoChildEventsFromChildren": 39, + "WA_WindowModified": 41, + "WA_Resized": 42, + "WA_Moved": 43, + "WA_PendingUpdate": 44, + "WA_InvalidSize": 45, + "WA_MacBrushedMetal": 46, + "WA_MacMetalStyle": 46, + "WA_CustomWhatsThis": 47, + "WA_LayoutOnEntireRect": 48, + "WA_OutsideWSRange": 49, + "WA_GrabbedShortcut": 50, + "WA_TransparentForMouseEvents": 51, + "WA_PaintUnclipped": 52, + "WA_SetWindowIcon": 53, + "WA_NoMouseReplay": 54, + "WA_DeleteOnClose": 55, + "WA_RightToLeft": 56, + "WA_SetLayoutDirection": 57, + "WA_NoChildEventsForParent": 58, + "WA_ForceUpdatesDisabled": 59, + "WA_WState_Created": 60, + "WA_WState_CompressKeys": 61, + "WA_WState_InPaintEvent": 62, + "WA_WState_Reparented": 63, + "WA_WState_ConfigPending": 64, + "WA_WState_Polished": 66, + "WA_WState_DND": 67, + "WA_WState_OwnSizePolicy": 68, + "WA_WState_ExplicitShowHide": 69, + "WA_ShowModal": 70, + "WA_MouseNoMask": 71, + "WA_GroupLeader": 72, + "WA_NoMousePropagation": 73, + "WA_Hover": 74, + "WA_InputMethodTransparent": 75, + "WA_QuitOnClose": 76, + "WA_KeyboardFocusChange": 77, + "WA_AcceptDrops": 78, + "WA_DropSiteRegistered": 79, + "WA_ForceAcceptDrops": 79, + "WA_WindowPropagation": 80, + "WA_NoX11EventCompression": 81, + "WA_TintedBackground": 82, + "WA_X11OpenGLOverlay": 83, + "WA_AlwaysShowToolTips": 84, + "WA_MacOpaqueSizeGrip": 85, + "WA_SetStyle": 86, + "WA_SetLocale": 87, + "WA_MacShowFocusRect": 88, + "WA_MacNormalSize": 89, + "WA_MacSmallSize": 90, + "WA_MacMiniSize": 91, + "WA_LayoutUsesWidgetRect": 92, + "WA_StyledBackground": 93, + "WA_MSWindowsUseDirect3D": 94, + "WA_CanHostQMdiSubWindowTitleBar": 95, + "WA_MacAlwaysShowToolWindow": 96, + "WA_StyleSheet": 97, + "WA_ShowWithoutActivating": 98, + "WA_X11BypassTransientForHint": 99, + "WA_NativeWindow": 100, + "WA_DontCreateNativeAncestors": 101, + "WA_MacVariableSize": 102, + "WA_DontShowOnScreen": 103, + "WA_X11NetWmWindowTypeDesktop": 104, + "WA_X11NetWmWindowTypeDock": 105, + "WA_X11NetWmWindowTypeToolBar": 106, + "WA_X11NetWmWindowTypeMenu": 107, + "WA_X11NetWmWindowTypeUtility": 108, + "WA_X11NetWmWindowTypeSplash": 109, + "WA_X11NetWmWindowTypeDialog": 110, + "WA_X11NetWmWindowTypeDropDownMenu": 111, + "WA_X11NetWmWindowTypePopupMenu": 112, + "WA_X11NetWmWindowTypeToolTip": 113, + "WA_X11NetWmWindowTypeNotification": 114, + "WA_X11NetWmWindowTypeCombo": 115, + "WA_X11NetWmWindowTypeDND": 116, + "WA_MacFrameworkScaled": 117, + "WA_SetWindowModality": 118, + "WA_WState_WindowOpacitySet": 119, + "WA_TranslucentBackground": 120, + "WA_AcceptTouchEvents": 121, + "WA_WState_AcceptedTouchBeginEvent": 122, + "WA_TouchPadAcceptSingleTouchEvents": 123, + "WA_X11DoNotAcceptFocus": 126, + "WA_MacNoShadow": 127, + "WA_AlwaysStackOnTop": 128, + "WA_TabletTracking": 129, + "WA_ContentsMarginsRespectsSafeArea": 130, + "WA_StyleSheetTarget": 131, + "WA_AttributeCount": 132 + } + } + Enum { + name: "ApplicationAttribute" + values: { + "AA_ImmediateWidgetCreation": 0, + "AA_MSWindowsUseDirect3DByDefault": 1, + "AA_DontShowIconsInMenus": 2, + "AA_NativeWindows": 3, + "AA_DontCreateNativeWidgetSiblings": 4, + "AA_PluginApplication": 5, + "AA_MacPluginApplication": 5, + "AA_DontUseNativeMenuBar": 6, + "AA_MacDontSwapCtrlAndMeta": 7, + "AA_Use96Dpi": 8, + "AA_X11InitThreads": 10, + "AA_SynthesizeTouchForUnhandledMouseEvents": 11, + "AA_SynthesizeMouseForUnhandledTouchEvents": 12, + "AA_UseHighDpiPixmaps": 13, + "AA_ForceRasterWidgets": 14, + "AA_UseDesktopOpenGL": 15, + "AA_UseOpenGLES": 16, + "AA_UseSoftwareOpenGL": 17, + "AA_ShareOpenGLContexts": 18, + "AA_SetPalette": 19, + "AA_EnableHighDpiScaling": 20, + "AA_DisableHighDpiScaling": 21, + "AA_UseStyleSheetPropagationInWidgetStyles": 22, + "AA_DontUseNativeDialogs": 23, + "AA_SynthesizeMouseForUnhandledTabletEvents": 24, + "AA_CompressHighFrequencyEvents": 25, + "AA_DontCheckOpenGLContextThreadAffinity": 26, + "AA_DisableShaderDiskCache": 27, + "AA_DontShowShortcutsInContextMenus": 28, + "AA_CompressTabletEvents": 29, + "AA_DisableWindowContextHelpButton": 30, + "AA_DisableSessionManager": 31, + "AA_AttributeCount": 32 + } + } + Enum { + name: "ImageConversionFlags" + values: { + "ColorMode_Mask": 3, + "AutoColor": 0, + "ColorOnly": 3, + "MonoOnly": 2, + "AlphaDither_Mask": 12, + "ThresholdAlphaDither": 0, + "OrderedAlphaDither": 4, + "DiffuseAlphaDither": 8, + "NoAlpha": 12, + "Dither_Mask": 48, + "DiffuseDither": 0, + "OrderedDither": 16, + "ThresholdDither": 32, + "DitherMode_Mask": 192, + "AutoDither": 0, + "PreferDither": 64, + "AvoidDither": 128, + "NoOpaqueDetection": 256, + "NoFormatConversion": 512 + } + } + Enum { + name: "BGMode" + values: { + "TransparentMode": 0, + "OpaqueMode": 1 + } + } + } + Component { name: "QEasingCurve"; prototype: "QQmlEasingValueType" } +} diff --git a/tests/auto/qml/qqmlcontext/data/gcDeletesContextObject.qml b/tests/auto/qml/qqmlcontext/data/gcDeletesContextObject.qml new file mode 100644 index 0000000000..a478a587df --- /dev/null +++ b/tests/auto/qml/qqmlcontext/data/gcDeletesContextObject.qml @@ -0,0 +1,5 @@ +import QtQml +QtObject { + property Component c: MyItem {} + property QtObject o: c.createObject() +} diff --git a/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp b/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp index e586604862..75bf580947 100644 --- a/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp +++ b/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp @@ -75,6 +75,8 @@ private slots: void contextObjectHierarchy(); void destroyContextProperty(); + void numericContextProperty(); + private: QQmlEngine engine; }; @@ -1004,6 +1006,14 @@ void tst_qqmlcontext::destroyContextProperty() // TODO: Or are we? } +void tst_qqmlcontext::numericContextProperty() +{ + QQmlEngine engine; + auto context = engine.rootContext(); + context->setContextProperty(QLatin1String("11"), 42); + QCOMPARE(context->contextProperty(QLatin1String("11")).toInt(), 42); +} + QTEST_MAIN(tst_qqmlcontext) #include "tst_qqmlcontext.moc" diff --git a/tests/auto/qml/qqmlecmascript/CMakeLists.txt b/tests/auto/qml/qqmlecmascript/CMakeLists.txt index 70f010c082..bb1040db16 100644 --- a/tests/auto/qml/qqmlecmascript/CMakeLists.txt +++ b/tests/auto/qml/qqmlecmascript/CMakeLists.txt @@ -21,6 +21,7 @@ qt_internal_add_test(tst_qqmlecmascript Qt::Network Qt::QmlPrivate Qt::QuickTestUtilsPrivate + Qt::QuickPrivate TESTDATA ${test_data} ) diff --git a/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs.qml b/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs.qml new file mode 100644 index 0000000000..7f1b5b0317 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs.qml @@ -0,0 +1,8 @@ +import QtQml + +QtObject { + id: root + required property QtObject invokableObject + + Component.onCompleted: root.invokableObject.method_QObject(Component) +} diff --git a/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs2.qml b/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs2.qml new file mode 100644 index 0000000000..1904740b26 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs2.qml @@ -0,0 +1,9 @@ +import QtQml +import QtQml as NS + +QtObject { + id: root + required property QtObject invokableObject + + Component.onCompleted: root.invokableObject.method_QObject(NS) +} diff --git a/tests/auto/qml/qqmlecmascript/data/qpropertyBindingNoQPropertyCapture.qml b/tests/auto/qml/qqmlecmascript/data/qpropertyBindingNoQPropertyCapture.qml new file mode 100644 index 0000000000..d3a151efe3 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/qpropertyBindingNoQPropertyCapture.qml @@ -0,0 +1,19 @@ +import QtQuick + +Item { + objectName: "redRectangle" + id: redRectangle + + property bool b: false + function toggle() { b = !b } + width: b ? 600 : 500 + + Item { + id: blueRectangle + objectName: "blueRectangle" + // width: b ? (100 + redRectangle.width / 2) : 25 + width: b ? redRectangle.width : 25 + } + + property int blueRectangleWidth: blueRectangle.width +} diff --git a/tests/auto/qml/qqmlecmascript/data/qpropertyResetCorrectlyLinked.qml b/tests/auto/qml/qqmlecmascript/data/qpropertyResetCorrectlyLinked.qml new file mode 100644 index 0000000000..490fec2dc8 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/qpropertyResetCorrectlyLinked.qml @@ -0,0 +1,8 @@ +import QtQuick + +Item { + property var val: undefined + property var observes: width + width: val + implicitWidth: 200 +} diff --git a/tests/auto/qml/qqmlecmascript/data/restoreObserverAfterReset.qml b/tests/auto/qml/qqmlecmascript/data/restoreObserverAfterReset.qml new file mode 100644 index 0000000000..2933d9b4d5 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/restoreObserverAfterReset.qml @@ -0,0 +1,20 @@ +import QtQuick + +Item { + height: undefined + implicitHeight: 30 + property int steps: 0 + + Behavior on height { + NumberAnimation { + duration: 500 + } + } + + onHeightChanged: ++steps + + Component.onCompleted: { + height = Qt.binding(() => implicitHeight); + implicitHeight = 60; + } +} diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index f47c7132a2..e976ec46a3 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -52,6 +52,8 @@ #include <private/qqmlabstractbinding_p.h> #include <private/qqmlvaluetypeproxybinding_p.h> #include <QtCore/private/qproperty_p.h> +#include <QtQuick/qquickwindow.h> +#include <QtQuick/private/qquickitem_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/testhttpserver_p.h> @@ -312,6 +314,7 @@ private slots: void bindingBoundFunctions(); void qpropertyAndQtBinding(); void qpropertyBindingReplacement(); + void qpropertyBindingNoQPropertyCapture(); void deleteRootObjectInCreation(); void onDestruction(); void onDestructionViaGC(); @@ -393,6 +396,8 @@ private slots: void qpropertyBindingHandlesUndefinedCorrectly(); void qpropertyBindingHandlesUndefinedWithoutResetCorrectly_data(); void qpropertyBindingHandlesUndefinedWithoutResetCorrectly(); + void qpropertyBindingRestoresObserverAfterReset(); + void qpropertyBindingObserverCorrectlyLinkedAfterReset(); void hugeRegexpQuantifiers(); void singletonTypeWrapperLookup(); void getThisObject(); @@ -3071,6 +3076,26 @@ void tst_qqmlecmascript::callQtInvokables() QCOMPARE(o->actuals().count(), 1); QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr)); + { + o->reset(); + QQmlComponent comp(&qmlengine, testFileUrl("qmlTypeWrapperArgs.qml")); + QScopedPointer<QObject> root {comp.createWithInitialProperties({{"invokableObject", QVariant::fromValue(o)}}) }; + QVERIFY(root); + QCOMPARE(o->error(), false); + QCOMPARE(o->invoked(), 13); + QCOMPARE(o->actuals().count(), 1); + QCOMPARE(o->actuals().at(0).value<QObject *>()->metaObject()->className(), "QQmlComponentAttached"); + } + + { + o->reset(); + QQmlComponent comp(&qmlengine, testFileUrl("qmlTypeWrapperArgs2.qml")); + QScopedPointer<QObject> root {comp.createWithInitialProperties({{"invokableObject", QVariant::fromValue(o)}}) }; + QVERIFY(root); + QCOMPARE(o->error(), false); + QCOMPARE(o->invoked(), -1); // no function got called due to incompatible arguments + } + o->reset(); QVERIFY(EVALUATE_VALUE("object.method_QObject(undefined)", QV4::Primitive::undefinedValue())); QCOMPARE(o->error(), false); @@ -7640,6 +7665,28 @@ void tst_qqmlecmascript::qpropertyBindingReplacement() QCOMPARE(root->objectName(), u"overwritten"_qs); } +void tst_qqmlecmascript::qpropertyBindingNoQPropertyCapture() +{ + + QQmlEngine engine; + QQmlComponent comp(&engine, testFileUrl("qpropertyBindingNoQPropertyCapture.qml")); + std::unique_ptr<QObject> root(comp.create()); + QVERIFY2(root, qPrintable(comp.errorString())); + auto redRectangle = root.get(); + + QQmlProperty blueRectangleWidth(redRectangle, "blueRectangleWidth", &engine); + + auto toggle = [&](){ + QMetaObject::invokeMethod(root.get(), "toggle"); + }; + + QCOMPARE(blueRectangleWidth.read().toInt(), 25); + toggle(); + QCOMPARE(blueRectangleWidth.read().toInt(), 600); + toggle(); + QCOMPARE(blueRectangleWidth.read().toInt(), 25); +} + void tst_qqmlecmascript::deleteRootObjectInCreation() { QQmlEngine engine; @@ -9195,6 +9242,32 @@ void tst_qqmlecmascript::qpropertyBindingHandlesUndefinedWithoutResetCorrectly() QCOMPARE(root->property("value2").toInt(), 2); } +void tst_qqmlecmascript::qpropertyBindingRestoresObserverAfterReset() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("restoreObserverAfterReset.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QTRY_COMPARE(o->property("height").toDouble(), 60.0); + QVERIFY(o->property("steps").toInt() > 3); +} + +void tst_qqmlecmascript::qpropertyBindingObserverCorrectlyLinkedAfterReset() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("qpropertyResetCorrectlyLinked.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + std::unique_ptr<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->property("width"), 200); + auto item = qobject_cast<QQuickItem *>(o.get()); + auto itemPriv = QQuickItemPrivate::get(item); + QBindingStorage *storage = qGetBindingStorage(itemPriv); + QPropertyBindingDataPointer ptr { storage->bindingData(&itemPriv->width) }; + QCOMPARE(ptr.observerCount(), 1); +} + void tst_qqmlecmascript::hugeRegexpQuantifiers() { QJSEngine engine; diff --git a/tests/auto/qml/qqmlimport/data/fileDotSlashImport.qml b/tests/auto/qml/qqmlimport/data/fileDotSlashImport.qml new file mode 100644 index 0000000000..83dbd566f3 --- /dev/null +++ b/tests/auto/qml/qqmlimport/data/fileDotSlashImport.qml @@ -0,0 +1,6 @@ +import QtQml +import 'file://./MyModuleName' as MyModuleName + +QtObject { + objectName: MyModuleName.Font.exampleVar +} diff --git a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp index 7b6c33bcc9..35b035ae9c 100644 --- a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp +++ b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp @@ -61,6 +61,7 @@ private slots: void preferResourcePath(); void invalidFileImport_data(); void invalidFileImport(); + void invalidImportUrl(); }; void tst_QQmlImport::cleanup() @@ -136,6 +137,18 @@ void tst_QQmlImport::invalidFileImport() "but not absolute paths or resource paths.").arg(import))); } +void tst_QQmlImport::invalidImportUrl() +{ + QQmlEngine engine; + const QUrl url = testFileUrl("fileDotSlashImport.qml"); + QQmlComponent component(&engine, url); + QVERIFY(component.isError()); + QCOMPARE( + component.errorString(), + url.toString() + QLatin1String( + ":2 Cannot resolve URL for import \"file://./MyModuleName\"\n")); +} + void tst_QQmlImport::testDesignerSupported() { QQuickView *window = new QQuickView(); diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index 92e24bc332..46be3766ec 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -125,6 +125,8 @@ void registerTypes() qmlRegisterTypesAndRevisions<Large>("Test", 1); qmlRegisterTypesAndRevisions<Foo>("Test", 1); + + qmlRegisterTypesAndRevisions<Counter>("Test", 1); } QVariant myCustomVariantTypeConverter(const QString &data) diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index e43424c9ca..f06c60f46d 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -1866,4 +1866,48 @@ private: void registerTypes(); +class CounterAttachedBaseType: public QObject +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY (int value READ value NOTIFY valueChanged) + +public: + CounterAttachedBaseType(QObject *parent = nullptr) : QObject(parent) {} + + int value() { return m_value; } + Q_SIGNAL void valueChanged(); + +protected: + int m_value = 98; +}; + + +class CounterAttachedType: public CounterAttachedBaseType +{ + Q_OBJECT + QML_ANONYMOUS + +public: + CounterAttachedType(QObject *parent = nullptr) : CounterAttachedBaseType(parent) {} + + Q_INVOKABLE void increase() { + ++m_value; + Q_EMIT valueChanged(); + } +}; + +class Counter : public QObject +{ + Q_OBJECT + QML_ATTACHED(CounterAttachedBaseType) + QML_ELEMENT + +public: + static CounterAttachedBaseType *qmlAttachedProperties(QObject *o) + { + return new CounterAttachedType(o); + } +}; + #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 047df2f6d0..0d26772c6c 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -388,6 +388,8 @@ private slots: void bindingAliasToComponentUrl(); void signalInlineComponentArg(); + void callMethodOfAttachedDerived(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -6669,6 +6671,27 @@ void tst_qqmllanguage::signalInlineComponentArg() } } +void tst_qqmllanguage::callMethodOfAttachedDerived() +{ + QQmlEngine engine; + QQmlComponent c(&engine); + c.setData(R"( + import QtQml + import Test + + QtObject { + Component.onCompleted: Counter.increase() + property int v: Counter.value + } + )", QUrl()); + + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("v").toInt(), 99); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" diff --git a/tests/auto/qml/qqmlproperty/data/invalidateQPropertyChangeTriggers.qml b/tests/auto/qml/qqmlproperty/data/invalidateQPropertyChangeTriggers.qml new file mode 100644 index 0000000000..bfa832c1c8 --- /dev/null +++ b/tests/auto/qml/qqmlproperty/data/invalidateQPropertyChangeTriggers.qml @@ -0,0 +1,50 @@ +import QtQml + +QtObject { + id: root + objectName: column.text + + property Component c: Component { + id: comp + QtObject { } + } + + property QtObject rectItem: null + + property bool running: false + + property Timer t: Timer { + id: column + interval: 200 + running: root.running + repeat: true + + property string text: { + let item = root.rectItem + let result = rectItem ? rectItem.objectName : "Create Object" + return result + } + + onTriggered: { + let rectItem = root.rectItem + + // If rectItem exists destory it. + if (rectItem) { + rectItem.destroy() + return + } + + // Otherwise create a new object + let newRectItem = comp.createObject(column, {}) + + + // Setting the objectName before setting root.rectItem seems to work. + // newRectItem.width = 1200 + root.rectItem = newRectItem + + // But setting the objectName after setting root.rectItem seems to + // cause the issue. + newRectItem.objectName = "1300" + } + } +} diff --git a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp index 7ea9b4183e..896150b33e 100644 --- a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp +++ b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp @@ -206,6 +206,8 @@ private slots: void bindToNonQObjectTarget(); + void invalidateQPropertyChangeTriggers(); + private: QQmlEngine engine; }; @@ -2376,6 +2378,33 @@ void tst_qqmlproperty::bindToNonQObjectTarget() QVERIFY(!o.isNull()); } +void tst_qqmlproperty::invalidateQPropertyChangeTriggers() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("invalidateQPropertyChangeTriggers.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> root(component.create()); + QVERIFY(!root.isNull()); + + QStringList names; + QObject::connect(root.data(), &QObject::objectNameChanged, [&](const QString &name) { + if (names.length() == 10) + root->setProperty("running", false); + else + names.append(name); + }); + + root->setProperty("running", true); + QTRY_VERIFY(!root->property("running").toBool()); + + QCOMPARE(names, (QStringList { + u""_qs, u"1300"_qs, u"Create Object"_qs, + u""_qs, u"1300"_qs, u"Create Object"_qs, + u""_qs, u"1300"_qs, u"Create Object"_qs, + u""_qs + })); +} + QTEST_MAIN(tst_qqmlproperty) #include "tst_qqmlproperty.moc" diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/BLACKLIST b/tests/auto/quick/pointerhandlers/flickableinterop/BLACKLIST index 4bf1b58b9a..d2a270279b 100644 --- a/tests/auto/quick/pointerhandlers/flickableinterop/BLACKLIST +++ b/tests/auto/quick/pointerhandlers/flickableinterop/BLACKLIST @@ -15,4 +15,6 @@ android android [touchAndDragHandlerOnFlickable] android - +# QTBUG-113226 +[pinchHandlerOnFlickable] +* diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/data/pinchOnFlickable.qml b/tests/auto/quick/pointerhandlers/flickableinterop/data/pinchOnFlickable.qml new file mode 100644 index 0000000000..a4867d502c --- /dev/null +++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/pinchOnFlickable.qml @@ -0,0 +1,49 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick + +Flickable { + id: root + width: 800 + height: 480 + contentWidth: 1000 + contentHeight: 600 + + Rectangle { + id: pinchable + objectName: "pinchable" + border.color: "black" + color: pinch.active ? "salmon" : "peachpuff" + x: 100 + y: 100 + width: 200 + height: 200 + radius: 80 + PinchHandler { + id: pinch + } + PointHandler { + id: p1 + target: Rectangle { + parent: pinchable + color: "green" + visible: p1.active + x: p1.point.position.x - width / 2 + y: p1.point.position.y - height / 2 + width: 9; height: width; radius: width / 2 + } + } + PointHandler { + id: p0 + target: Rectangle { + parent: pinchable + color: "red" + visible: p0.active + x: p0.point.position.x - width / 2 + y: p0.point.position.y - height / 2 + width: 9; height: width; radius: width / 2 + } + } + } +} diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp b/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp index 5925965c55..0bf6b3fdcc 100644 --- a/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp +++ b/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp @@ -80,6 +80,7 @@ private slots: void touchDragSliderAndFlickable(); void touchAndDragHandlerOnFlickable_data(); void touchAndDragHandlerOnFlickable(); + void pinchHandlerOnFlickable(); private: void createView(QScopedPointer<QQuickView> &window, const char *fileName); @@ -807,6 +808,77 @@ void tst_FlickableInterop::touchAndDragHandlerOnFlickable() touchSeq.release(1, p1, window).commit(); } +void tst_FlickableInterop::pinchHandlerOnFlickable() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("pinchOnFlickable.qml"))); + QQuickFlickable *flickable = qmlobject_cast<QQuickFlickable*>(window.rootObject()); + QVERIFY(flickable); + QQuickPointerHandler *pinchHandler = flickable->findChild<QQuickPointerHandler*>(); + QVERIFY(pinchHandler); + QQuickItem *pinchable = pinchHandler->target(); + QVERIFY(pinchable); + + QSignalSpy flickMoveSpy(flickable, &QQuickFlickable::movementStarted); + QSignalSpy grabChangedSpy(touchDevice, &QPointingDevice::grabChanged); + + QObject *grabber = nullptr; + connect(touchDevice, &QPointingDevice::grabChanged, + [&grabber](QObject *g, QPointingDevice::GrabTransition transition, const QPointerEvent *, const QEventPoint &) { + if (transition == QPointingDevice::GrabTransition::GrabExclusive) + grabber = g; + }); + + QPoint p0 = pinchable->mapToScene({50, 100}).toPoint(); + QPoint p1 = pinchable->mapToScene({150, 100}).toPoint(); + QTest::QTouchEventSequence touch = QTest::touchEvent(&window, touchDevice); + + touch.press(0, p0, &window).press(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); + int activeStep = -1; + int grabTransitionCount = 0; + // drag two fingers down: PinchHandler moves the item; Flickable doesn't grab, because there are 2 points + for (int i = 0; i < 4; ++i) { + p0 += QPoint(0, dragThreshold); + p1 += QPoint(0, dragThreshold); + touch.move(0, p0, &window).move(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); + if (pinchHandler->active() && activeStep < 0) { + qCDebug(lcPointerTests) << "pinch began at step" << i; + activeStep = i; + QCOMPARE(grabber, pinchHandler); + grabTransitionCount = grabChangedSpy.count(); + } + } + QVERIFY(pinchHandler->active()); + QCOMPARE(grabChangedSpy.count(), grabTransitionCount); + QCOMPARE(grabber, pinchHandler); + qreal scale = pinchable->scale(); + QCOMPARE(scale, 1); + qreal rot = pinchable->rotation(); + QCOMPARE(rot, 0); + // start expanding and rotating + for (int i = 0; i < 4; ++i) { + p0 += QPoint(-5, 10); + p1 += QPoint(5, -10); + touch.move(0, p0, &window).move(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); + QVERIFY(pinchHandler->active()); + // PinchHandler keeps grab: no more transitions + QCOMPARE(grabChangedSpy.count(), grabTransitionCount); + QCOMPARE(grabber, pinchHandler); + QVERIFY(pinchable->scale() > scale); + scale = pinchable->scale(); + QVERIFY(pinchable->rotation() < rot); + rot = pinchable->rotation(); + } + touch.release(0, p0, &window).release(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); + QTRY_COMPARE(pinchHandler->active(), false); + QCOMPARE(flickMoveSpy.count(), 0); // Flickable never moved +} + QTEST_MAIN(tst_FlickableInterop) #include "tst_flickableinterop.moc" diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/clip.qml b/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/clip.qml new file mode 100644 index 0000000000..7bc3907c8c --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/data/clip.qml @@ -0,0 +1,36 @@ +import QtQuick +import Qt.test 1.0 + +Item { + width: 200 + height: 200 + + Rectangle { + id: circle + y: 0 + width: 100 + height: width + radius: width/2 + color: "#3e1" + clip: true + + // Rectangle contains() is not affected by its 'radius' property + containmentMask: QtObject { + property alias radius: circle.radius + function contains(point: point) : bool { + return (Math.pow(point.x - radius, 2) + Math.pow(point.y - radius, 2)) < Math.pow(radius, 2) + } + } + EventHandler { + objectName: "circle eventHandler" + } + Rectangle { + width: circle.width/2 + height: width + color: "red" + EventHandler { + objectName: "eventHandler" + } + } + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp index 9cab8f8ed6..51c94ea8c8 100644 --- a/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickpointerhandler/tst_qquickpointerhandler.cpp @@ -252,6 +252,7 @@ private slots: void handlerInWindow(); void dynamicCreationInWindow(); void cppConstruction(); + void clip(); protected: bool eventFilter(QObject *, QEvent *event) override @@ -734,6 +735,63 @@ void tst_PointerHandlers::cppConstruction() QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); } +void tst_PointerHandlers::clip() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "clip.qml"); + QQuickView * window = windowPtr.data(); + QVERIFY(window); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + EventHandler *handler = window->contentItem()->findChild<EventHandler*>("eventHandler"); + EventHandler *circleHandler = window->contentItem()->findChild<EventHandler*>("circle eventHandler"); + + QCOMPARE(handler->pressEventCount, 0); + QCOMPARE(circleHandler->pressEventCount, 0); + QCOMPARE(handler->releaseEventCount, 0); + QCOMPARE(circleHandler->releaseEventCount, 0); + + const QPoint rectPt = QPoint(1, 1); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, rectPt); + QCOMPARE(handler->pressEventCount, 1); + QCOMPARE(circleHandler->pressEventCount, 0); + + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, rectPt); + QCOMPARE(handler->releaseEventCount, 1); + QCOMPARE(circleHandler->releaseEventCount, 0); + + + handler->pressEventCount = 0; + circleHandler->pressEventCount = 0; + handler->releaseEventCount = 0; + circleHandler->releaseEventCount = 0; + + const QPoint rectAndCirclePt = QPoint(49 ,49); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, rectAndCirclePt); + QCOMPARE(handler->pressEventCount, 1); + QCOMPARE(circleHandler->pressEventCount, 1); + + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, rectAndCirclePt); + QCOMPARE(handler->releaseEventCount, 1); + QCOMPARE(circleHandler->releaseEventCount, 1); + + + handler->pressEventCount = 0; + circleHandler->pressEventCount = 0; + handler->releaseEventCount = 0; + circleHandler->releaseEventCount = 0; + + const QPoint circlePt = QPoint(51 ,51); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, circlePt); + QCOMPARE(handler->pressEventCount, 0); + QCOMPARE(circleHandler->pressEventCount, 1); + + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, circlePt); + QCOMPARE(handler->releaseEventCount, 0); + QCOMPARE(circleHandler->releaseEventCount, 1); +} + QTEST_MAIN(tst_PointerHandlers) #include "tst_qquickpointerhandler.moc" diff --git a/tests/auto/quick/qquickanimations/data/targetsDeletedWithoutRemoval.qml b/tests/auto/quick/qquickanimations/data/targetsDeletedWithoutRemoval.qml new file mode 100644 index 0000000000..e31caa1905 --- /dev/null +++ b/tests/auto/quick/qquickanimations/data/targetsDeletedWithoutRemoval.qml @@ -0,0 +1,29 @@ +import QtQuick + +Item { + id: root + + property list<Translate> targets + property alias animTargets: animation.targets + + Component { + id: trComponent + Translate {} + } + + Component.onCompleted: { + const target = trComponent.createObject(this); + targets.push(target); + target.destroy(); + // give event loop some time to actually stop the animation and destroy the target + Qt.callLater(animation.start); + } + + NumberAnimation { + id: animation + targets: root.targets + property: "x" + running: false + to: 100 + } +} diff --git a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp index 313f478820..30b934eeec 100644 --- a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp +++ b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp @@ -118,6 +118,7 @@ private slots: void alwaysRunToEndInSequentialAnimationBug(); void cleanupWhenRenderThreadStops(); void infiniteLoopsWithoutFrom(); + void targetsDeletedNotRemoved(); }; #define QTIMED_COMPARE(lhs, rhs) do { \ for (int ii = 0; ii < 5; ++ii) { \ @@ -2060,6 +2061,26 @@ void tst_qquickanimations::infiniteLoopsWithoutFrom() animation->stop(); } +void tst_qquickanimations::targetsDeletedNotRemoved() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("targetsDeletedWithoutRemoval.qml")); + QScopedPointer<QObject> obj(component.create()); + QVERIFY2(obj.get(), qPrintable(component.errorString())); + { + QQmlListReference ref(obj.get(), "targets"); + QVERIFY(ref.isValid()); + QCOMPARE(ref.size(), 1); + QTRY_COMPARE(ref.at(0), nullptr); + } + { + QQmlListReference ref(obj.get(), "animTargets"); + QVERIFY(ref.isValid()); + QCOMPARE(ref.size(), 1); + QCOMPARE(ref.at(0), nullptr); + } +} + QTEST_MAIN(tst_qquickanimations) #include "tst_qquickanimations.moc" diff --git a/tests/auto/quick/qquickgridview/data/qtbug92998.qml b/tests/auto/quick/qquickgridview/data/qtbug92998.qml new file mode 100644 index 0000000000..d7db5fb843 --- /dev/null +++ b/tests/auto/quick/qquickgridview/data/qtbug92998.qml @@ -0,0 +1,53 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 + +Item { + width: 450 + height: 650 + GridView { + objectName: "gridview" + id: gridView + width: 450 + height: 650 + layoutDirection: Qt.RightToLeft + property int cells: calcCells(width) + cellWidth: width / cells + cellHeight: cellWidth + + delegate: Component { + Item { + width: gridView.cellWidth + height: gridView.cellHeight + Rectangle { + anchors { + fill: parent + margins: 10 + } + color: "green" + } + } + } + model: [ + { number: "1" }, + { number: "2" }, + { number: "3" }, + { number: "4" }, + { number: "5" }, + { number: "6" }, + { number: "7" }, + { number: "8" }, + { number: "9" }, + { number: "10" }, + { number: "11" }, + { number: "12" }, + { number: "13" }, + { number: "14" }, + { number: "15" }, + { number: "16" }]; + function calcCells(w) { + var rw = 120; + var c = Math.max(1, Math.round(w / rw)); + return c; + } + } +} diff --git a/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp b/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp index 7e319fb28a..9532aa546c 100644 --- a/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp +++ b/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp @@ -215,6 +215,7 @@ private slots: void positionViewAtBeginningAfterResizingCells(); void QTBUG_48870_fastModelUpdates(); void QTBUG_86255(); + void resizeDynamicCellWidthRtL(); void keyNavigationEnabled(); void releaseItems(); @@ -6839,6 +6840,25 @@ void tst_QQuickGridView::QTBUG_86255() QTRY_COMPARE(view->isFlicking(), false); } +void tst_QQuickGridView::resizeDynamicCellWidthRtL() +{ + QScopedPointer<QQuickView> window(createView()); + QTRY_VERIFY(window); + window->setSource(testFileUrl("qtbug92998.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QQuickGridView *gridview = findItem<QQuickGridView>(window->rootObject(), "gridview"); + QTRY_VERIFY(gridview != nullptr); + QVERIFY(QQuickTest::qWaitForItemPolished(gridview)); + gridview->setWidth(460); + QVERIFY(QQuickTest::qWaitForItemPolished(gridview)); + QTRY_COMPARE(gridview->contentX(), 0.f); + gridview->setWidth(360); + QVERIFY(QQuickTest::qWaitForItemPolished(gridview)); + QTRY_COMPARE(gridview->contentX(), 0.f); +} + void tst_QQuickGridView::releaseItems() { QScopedPointer<QQuickView> view(createView()); diff --git a/tests/auto/quick/qquicklistview2/data/fetchMore.qml b/tests/auto/quick/qquicklistview2/data/fetchMore.qml new file mode 100644 index 0000000000..4ce53e4d28 --- /dev/null +++ b/tests/auto/quick/qquicklistview2/data/fetchMore.qml @@ -0,0 +1,21 @@ +import QtQuick +import org.qtproject.Test + +ListView { + id: listView + width: 300 + height: 150 + flickDeceleration: 10000 + + model: FetchMoreModel + delegate: Text { + height: 50 + text: model.display + } + + Text { + anchors.right: parent.right + text: "count " + listView.count + color: listView.moving ? "red" : "blue" + } +} diff --git a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp index 4c6f9c70e2..a7f64caaf0 100644 --- a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp +++ b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp @@ -66,6 +66,9 @@ private slots: void isCurrentItem_DelegateModel(); void isCurrentItem_NoRegressionWithDelegateModelGroups(); + void fetchMore_data(); + void fetchMore(); + private: void flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to); QScopedPointer<QPointingDevice> touchDevice = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); @@ -260,29 +263,31 @@ void tst_QQuickListView2::tapDelegateDuringFlicking_data() { QTest::addColumn<QByteArray>("qmlFile"); QTest::addColumn<QQuickFlickable::BoundsBehavior>("boundsBehavior"); + QTest::addColumn<bool>("expectCanceled"); QTest::newRow("Button StopAtBounds") << QByteArray("buttonDelegate.qml") - << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds); + << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds) << false; QTest::newRow("MouseArea StopAtBounds") << QByteArray("mouseAreaDelegate.qml") - << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds); + << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds) << true; QTest::newRow("Button DragOverBounds") << QByteArray("buttonDelegate.qml") - << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds); + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds) << false; QTest::newRow("MouseArea DragOverBounds") << QByteArray("mouseAreaDelegate.qml") - << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds); + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds) << true; QTest::newRow("Button OvershootBounds") << QByteArray("buttonDelegate.qml") - << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds); + << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds) << false; QTest::newRow("MouseArea OvershootBounds") << QByteArray("mouseAreaDelegate.qml") - << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds); + << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds) << true; QTest::newRow("Button DragAndOvershootBounds") << QByteArray("buttonDelegate.qml") - << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds); + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds) << false; QTest::newRow("MouseArea DragAndOvershootBounds") << QByteArray("mouseAreaDelegate.qml") - << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds); + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds) << true; } void tst_QQuickListView2::tapDelegateDuringFlicking() // QTBUG-103832 { QFETCH(QByteArray, qmlFile); QFETCH(QQuickFlickable::BoundsBehavior, boundsBehavior); + QFETCH(bool, expectCanceled); QQuickView window; QVERIFY(QQuickTest::showView(window, testFileUrl(qmlFile.constData()))); @@ -295,7 +300,16 @@ void tst_QQuickListView2::tapDelegateDuringFlicking() // QTBUG-103832 QVERIFY(listView->isFlicking()); // we want to test the case when it's still moving while we tap // @y = 400 we pressed the 4th delegate; started flicking, and the press was canceled QCOMPARE(listView->property("pressedDelegates").toList().first(), 4); - QCOMPARE(listView->property("canceledDelegates").toList().first(), 4); + // At first glance one would expect MouseArea and Button would be consistent about this; + // but in fact, before ListView takes over the grab via filtering, + // Button.pressed transitions to false because QQuickAbstractButtonPrivate::handleMove + // sees that the touchpoint has strayed outside its bounds, but it does NOT emit the canceled signal + if (expectCanceled) { + const QVariantList canceledDelegates = listView->property("canceledDelegates").toList(); + QCOMPARE(canceledDelegates.size(), 1); + QCOMPARE(canceledDelegates.first(), 4); + } + QCOMPARE(listView->property("releasedDelegates").toList().size(), 0); // press a delegate during flicking (at y > 501 + 100, so likely delegate 6) QTest::touchEvent(&window, touchDevice.data()).press(0, {100, 100}); @@ -318,7 +332,7 @@ void tst_QQuickListView2::tapDelegateDuringFlicking() // QTBUG-103832 QVERIFY(lastPressed > 5); QCOMPARE(releasedDelegates.last(), lastPressed); QCOMPARE(tappedDelegates.last(), lastPressed); - QCOMPARE(canceledDelegates.count(), 1); // only the first press was canceled, not the second + QCOMPARE(canceledDelegates.size(), expectCanceled ? 1 : 0); // only the first press was canceled, not the second } void tst_QQuickListView2::flickDuringFlicking_data() @@ -464,6 +478,81 @@ void tst_QQuickListView2::isCurrentItem_NoRegressionWithDelegateModelGroups() QCOMPARE(item3->property("isCurrent").toBool(), false); } +class TestFetchMoreModel : public QAbstractListModel +{ + Q_OBJECT + +public: + QVariant data(const QModelIndex& index, int role) const override + { + if (role == Qt::DisplayRole) + return QString::number(index.row()); + return {}; + } + + int columnCount(const QModelIndex&) const override { return 1; } + + int rowCount(const QModelIndex& parent) const override + { + return parent.isValid() ? 0 : m_lines; + } + + QModelIndex parent(const QModelIndex&) const override { return {}; } + + bool canFetchMore(const QModelIndex &) const override { return true; } + + void fetchMore(const QModelIndex & parent) override + { + if (Q_UNLIKELY(parent.isValid())) + return; + beginInsertRows(parent, m_lines, m_lines); + m_lines++; + endInsertRows(); + } + + int m_lines = 3; +}; + +void tst_QQuickListView2::fetchMore_data() +{ + QTest::addColumn<bool>("reuseItems"); + QTest::addColumn<int>("cacheBuffer"); + + QTest::newRow("no reuseItems, default buffer") << false << -1; + QTest::newRow("reuseItems, default buffer") << true << -1; + QTest::newRow("no reuseItems, no buffer") << false << 0; + QTest::newRow("reuseItems, no buffer") << true << 0; + QTest::newRow("no reuseItems, buffer 100 px") << false << 100; + QTest::newRow("reuseItems, buffer 100 px") << true << 100; +} + +void tst_QQuickListView2::fetchMore() // QTBUG-95107 +{ + QFETCH(bool, reuseItems); + QFETCH(int, cacheBuffer); + + TestFetchMoreModel model; + qmlRegisterSingletonInstance("org.qtproject.Test", 1, 0, "FetchMoreModel", &model); + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("fetchMore.qml"))); + auto *listView = qobject_cast<QQuickListView*>(window.rootObject()); + QVERIFY(listView); + listView->setReuseItems(reuseItems); + if (cacheBuffer >= 0) + listView->setCacheBuffer(cacheBuffer); + + for (int i = 0; i < 3; ++i) { + const int rowCount = listView->count(); + if (lcTests().isDebugEnabled()) QTest::qWait(1000); + listView->flick(0, -5000); + QTRY_VERIFY(!listView->isMoving()); + qCDebug(lcTests) << "after flick: contentY" << listView->contentY() + << "rows" << rowCount << "->" << listView->count(); + QVERIFY(listView->count() > rowCount); + QVERIFY(model.m_lines >= listView->count()); // fetchMore() was called + } +} + QTEST_MAIN(tst_QQuickListView2) #include "tst_qquicklistview2.moc" diff --git a/tests/auto/quick/qquickmultipointtoucharea/data/inFlickable.qml b/tests/auto/quick/qquickmultipointtoucharea/data/inFlickable.qml index 32733613b3..fada2b3370 100644 --- a/tests/auto/quick/qquickmultipointtoucharea/data/inFlickable.qml +++ b/tests/auto/quick/qquickmultipointtoucharea/data/inFlickable.qml @@ -43,8 +43,8 @@ Rectangle { TouchPoint { id: point2; objectName: "point2" } ] - onCanceled: root.cancelCount = touchPoints.length - onTouchUpdated: root.touchCount = touchPoints.length + onCanceled: (touchPoints) => root.cancelCount = touchPoints.length + onTouchUpdated: (touchPoints) => root.touchCount = touchPoints.length Text { text: "â " diff --git a/tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp b/tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp index 299241b768..3e6a7fce16 100644 --- a/tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp +++ b/tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp @@ -577,6 +577,10 @@ void tst_QQuickMultiPointTouchArea::nested() void tst_QQuickMultiPointTouchArea::inFlickable() { +#ifdef Q_OS_ANDROID + QSKIP("On Android, QPointingDevicePrivate::queryPointById() sometimes fails to find a point 'in play' (QTBUG-117079)"); +#endif + QScopedPointer<QQuickView> window(createAndShowView("inFlickable.qml")); QVERIFY(window->rootObject() != nullptr); @@ -595,7 +599,7 @@ void tst_QQuickMultiPointTouchArea::inFlickable() QPoint p1(20,100); QPoint p2(40,100); - // moving one point vertically + // moving one point vertically: flickable gets the grab QTest::touchEvent(window.data(), device).press(0, p1); QQuickTouchUtils::flush(window.data()); @@ -619,7 +623,8 @@ void tst_QQuickMultiPointTouchArea::inFlickable() QTRY_VERIFY(!flickable->isMoving()); - // moving two points vertically + // moving two points vertically: MPTAs handle them, Flickable ignores multi-touch. + // The stray mouse events simulate OS-level synth-from-touch, and should not interfere. p1 = QPoint(20,100); QTest::touchEvent(window.data(), device).press(0, p1).press(1, p2); QTest::mousePress(window.data(), Qt::LeftButton, Qt::NoModifier, p1); @@ -641,11 +646,11 @@ void tst_QQuickMultiPointTouchArea::inFlickable() i, p1.x(), p1.y(), p2.x(), p2.y(), flickable->contentY()); } - QVERIFY(flickable->contentY() < 0); - QCOMPARE(point11->pressed(), false); - QCOMPARE(point12->pressed(), false); - QCOMPARE(window->rootObject()->property("cancelCount").toInt(), 2); - QCOMPARE(window->rootObject()->property("touchCount").toInt(), 0); + QCOMPARE(flickable->contentY(), 0); + QCOMPARE(point11->pressed(), true); + QCOMPARE(point12->pressed(), true); + QCOMPARE(window->rootObject()->property("cancelCount").toInt(), 0); + QCOMPARE(window->rootObject()->property("touchCount").toInt(), 2); QTest::touchEvent(window.data(), device).release(0, p1).release(1, p2); QTest::mouseRelease(window.data(), Qt::LeftButton, Qt::NoModifier, p1); @@ -680,10 +685,8 @@ void tst_QQuickMultiPointTouchArea::inFlickable() i, p1.x(), p1.y(), p2.x(), p2.y(), flickable->contentY()); } - QEXPECT_FAIL("", "currently flickable does grab the actual mouse", Continue); QCOMPARE(flickable->contentY(), qreal(0)); QCOMPARE(point11->pressed(), true); - QEXPECT_FAIL("", "currently flickable does grab the actual mouse", Continue); QCOMPARE(point12->pressed(), true); QTest::touchEvent(window.data(), device).release(0, p1).release(1, p2); @@ -694,12 +697,16 @@ void tst_QQuickMultiPointTouchArea::inFlickable() // test that dragging out of a Flickable containing a MPTA doesn't harm Flickable's state. void tst_QQuickMultiPointTouchArea::inFlickable2() { + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer<QQuickView> window(createAndShowView("inFlickable2.qml")); QVERIFY(window->rootObject() != nullptr); QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>("flickable"); QVERIFY(flickable != nullptr); + QQuickMultiPointTouchArea *mpta = window->rootObject()->findChild<QQuickMultiPointTouchArea*>(); + QVERIFY(mpta); + QQuickTouchPoint *point11 = window->rootObject()->findChild<QQuickTouchPoint*>("point1"); QVERIFY(point11); @@ -712,25 +719,12 @@ void tst_QQuickMultiPointTouchArea::inFlickable2() QQuickTouchUtils::flush(window.data()); QTest::mousePress(window.data(), Qt::LeftButton, Qt::NoModifier, p1); - p1 += QPoint(15,0); - QTest::touchEvent(window.data(), device).move(0, p1); - QQuickTouchUtils::flush(window.data()); - QTest::mouseMove(window.data(), p1); - - p1 += QPoint(15,0); - QTest::touchEvent(window.data(), device).move(0, p1); - QQuickTouchUtils::flush(window.data()); - QTest::mouseMove(window.data(), p1); - - p1 += QPoint(15,0); - QTest::touchEvent(window.data(), device).move(0, p1); - QQuickTouchUtils::flush(window.data()); - QTest::mouseMove(window.data(), p1); - - p1 += QPoint(15,0); - QTest::touchEvent(window.data(), device).move(0, p1); - QQuickTouchUtils::flush(window.data()); - QTest::mouseMove(window.data(), p1); + for (int i = 0; i < 4; ++i) { + p1 += QPoint(dragThreshold, 0); + QTest::touchEvent(window.data(), device).move(0, p1); + QQuickTouchUtils::flush(window.data()); + QTest::mouseMove(window.data(), p1); + } QVERIFY(!flickable->isMoving()); QVERIFY(point11->pressed()); @@ -743,27 +737,21 @@ void tst_QQuickMultiPointTouchArea::inFlickable2() QTRY_VERIFY(!flickable->isMoving()); // Check that we can still move the Flickable + QSignalSpy gestureStartedSpy(mpta, &QQuickMultiPointTouchArea::gestureStarted); p1 = QPoint(50,100); QTest::touchEvent(window.data(), device).press(0, p1); QQuickTouchUtils::flush(window.data()); QCOMPARE(point11->pressed(), true); - p1 += QPoint(0,15); - QTest::touchEvent(window.data(), device).move(0, p1); - QQuickTouchUtils::flush(window.data()); - - p1 += QPoint(0,15); - QTest::touchEvent(window.data(), device).move(0, p1); - QQuickTouchUtils::flush(window.data()); - - p1 += QPoint(0,15); - QTest::touchEvent(window.data(), device).move(0, p1); - QQuickTouchUtils::flush(window.data()); - - p1 += QPoint(0,15); - QTest::touchEvent(window.data(), device).move(0, p1); - QQuickTouchUtils::flush(window.data()); + for (int i = 0; i < 4; ++i) { + p1 += QPoint(0, dragThreshold); + QTest::touchEvent(window.data(), device).move(0, p1); + QQuickTouchUtils::flush(window.data()); + // QTBUG-113653: gestureStarted is emitted when touch delta exceeds drag threshold, + // regardless of the filtering Flickable parent + QCOMPARE(gestureStartedSpy.size(), i > 0 ? 1 : 0); + } QVERIFY(flickable->contentY() < 0); QVERIFY(flickable->isMoving()); diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp index 773ccdac99..992709b865 100644 --- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp +++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp @@ -445,6 +445,9 @@ public: , touchDevice(QTest::createTouchDevice()) , touchDeviceWithVelocity(QTest::createTouchDevice(QInputDevice::DeviceType::TouchScreen, QInputDevice::Capability::Position | QPointingDevice::Capability::Velocity)) + , tabletStylusDevice(QPointingDevicePrivate::tabletDevice(QInputDevice::DeviceType::Stylus, + QPointingDevice::PointerType::Pen, + QPointingDeviceUniqueId::fromNumericId(1234567890))) { QQuickWindow::setDefaultAlphaBuffer(true); } @@ -566,8 +569,9 @@ private slots: void visibleVsVisibility(); private: - QPointingDevice *touchDevice; - QPointingDevice *touchDeviceWithVelocity; + QPointingDevice *touchDevice; // TODO make const after fixing QTBUG-107864 + const QPointingDevice *touchDeviceWithVelocity; + const QPointingDevice *tabletStylusDevice; }; #if QT_CONFIG(opengl) @@ -3637,16 +3641,16 @@ void tst_qquickwindow::cleanupGrabsOnRelease() void tst_qquickwindow::subclassWithPointerEventVirtualOverrides_data() { - QTest::addColumn<QPointingDevice::DeviceType>("deviceType"); + QTest::addColumn<const QPointingDevice *>("device"); - QTest::newRow("mouse click") << QPointingDevice::DeviceType::Mouse; - QTest::newRow("touch tap") << QPointingDevice::DeviceType::TouchScreen; - QTest::newRow("stylus tap") << QPointingDevice::DeviceType::Stylus; + QTest::newRow("mouse click") << QPointingDevice::primaryPointingDevice(); + QTest::newRow("touch tap") << static_cast<const QPointingDevice*>(touchDevice); // TODO QTBUG-107864 + QTest::newRow("stylus tap") << tabletStylusDevice; } void tst_qquickwindow::subclassWithPointerEventVirtualOverrides() // QTBUG-97859 { - QFETCH(QPointingDevice::DeviceType, deviceType); + QFETCH(const QPointingDevice *, device); PointerRecordingWindow window; window.resize(250, 250); @@ -3654,32 +3658,23 @@ void tst_qquickwindow::subclassWithPointerEventVirtualOverrides() // QTBUG-97859 window.setTitle(QTest::currentTestFunction()); window.show(); QVERIFY(QTest::qWaitForWindowActive(&window)); - const qint64 stylusId = 1234567890; - const QPoint pos(120, 120); - switch (static_cast<QPointingDevice::DeviceType>(deviceType)) { + + QQuickTest::pointerPress(device, &window, 0, pos); + QQuickTest::pointerRelease(device, &window, 0, pos); + + switch (device->type()) { case QPointingDevice::DeviceType::Mouse: - QTest::mouseClick(&window, Qt::LeftButton, Qt::NoModifier, pos); - QTRY_COMPARE(window.m_mouseEvents.count(), 3); // separate move before press - QCOMPARE(window.m_events.count(), 3); + QTRY_COMPARE(window.m_mouseEvents.size(), 3); // separate move before press + QCOMPARE(window.m_events.size(), 3); break; case QPointingDevice::DeviceType::TouchScreen: - QTest::touchEvent(&window, touchDevice).press(0, pos, &window); - QTest::touchEvent(&window, touchDevice).release(0, pos, &window); - QTRY_COMPARE(window.m_touchEvents.count(), 2); - QCOMPARE(window.m_events.count(), 2); + QTRY_COMPARE(window.m_touchEvents.size(), 2); + QCOMPARE(window.m_events.size(), 2); break; case QPointingDevice::DeviceType::Stylus: - // press (pressure is 0.8) - QWindowSystemInterface::handleTabletEvent(&window, pos, window.mapToGlobal(pos), - int(QInputDevice::DeviceType::Stylus), int(QPointingDevice::PointerType::Pen), - Qt::LeftButton, 0.8, 0, 0, 0, 0, 0, stylusId, Qt::NoModifier); - // release (pressure is 0) - QWindowSystemInterface::handleTabletEvent(&window, pos, window.mapToGlobal(pos), - int(QInputDevice::DeviceType::Stylus), int(QPointingDevice::PointerType::Pen), - Qt::NoButton, 0, 0, 0, 0, 0, 0, stylusId, Qt::NoModifier); - QTRY_COMPARE(window.m_tabletEvents.count(), 2); - QVERIFY(window.m_events.count() >= window.m_tabletEvents.count()); // tablet + synth-mouse events + QTRY_COMPARE(window.m_tabletEvents.size(), 2); + QVERIFY(window.m_events.size() >= window.m_tabletEvents.size()); // tablet + synth-mouse events break; default: break; diff --git a/tests/auto/quick/touchmouse/tst_touchmouse.cpp b/tests/auto/quick/touchmouse/tst_touchmouse.cpp index 66dde3e26c..c80ceddcc3 100644 --- a/tests/auto/quick/touchmouse/tst_touchmouse.cpp +++ b/tests/auto/quick/touchmouse/tst_touchmouse.cpp @@ -738,7 +738,7 @@ void tst_TouchMouse::touchButtonOnFlickable() QTRY_COMPARE(eventItem2->touchUngrabCount, 1); qCDebug(lcTests) << "expected delivered events: press(touch) move(touch)" << eventItem2->eventList; - QCOMPARE(eventItem2->eventList.size(), 2); + QCOMPARE(eventItem2->eventList.size(), 3); QCOMPARE(eventItem2->eventList.at(1).type, QEvent::TouchUpdate); QCOMPARE(grabMonitor.exclusiveGrabber, flickable); // both EventItem and Flickable handled the actual touch, so synth-mouse doesn't happen diff --git a/tests/auto/quickcontrols2/CMakeLists.txt b/tests/auto/quickcontrols2/CMakeLists.txt index 903732bf79..4004fbecdf 100644 --- a/tests/auto/quickcontrols2/CMakeLists.txt +++ b/tests/auto/quickcontrols2/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(controls) endif() add_subdirectory(cursor) add_subdirectory(customization) +add_subdirectory(deferred) add_subdirectory(designer) if(NOT ANDROID) # QTBUG-100258 add_subdirectory(focus) diff --git a/tests/auto/quickcontrols2/controls/data/tst_swipeview.qml b/tests/auto/quickcontrols2/controls/data/tst_swipeview.qml index 254c876f08..61dbecd660 100644 --- a/tests/auto/quickcontrols2/controls/data/tst_swipeview.qml +++ b/tests/auto/quickcontrols2/controls/data/tst_swipeview.qml @@ -691,7 +691,10 @@ TestCase { id: translucentPages SwipeView { spacing: 10 - padding: 10 + leftPadding: 10 + topPadding: 10 + rightPadding: 10 + bottomPadding: 10 Text { text: "page 0" } Text { text: "page 1"; font.pointSize: 16 } Text { text: "page 2"; font.pointSize: 24 } @@ -702,18 +705,33 @@ TestCase { function test_initialPositions() { // QTBUG-102487 const control = createTemporaryObject(translucentPages, testCase, {width: 320, height: 200}) verify(control) + compare(control.contentItem.width, control.width - control.leftPadding - control.rightPadding) + compare(control.spacing, 10) + compare(control.orientation, Qt.Horizontal) - for (var i = 0; i < control.count; ++i) { + for (let i = 0; i < control.count; ++i) { const page = control.itemAt(i) - // control.contentItem.width + control.spacing == 310; except Imagine style has contentItem.width == 320 - compare(page.x, i * 310) + compare(page.x, i * (control.contentItem.width + control.spacing)) compare(page.y, 0) + compare(page.width, control.contentItem.width) + compare(page.height, control.contentItem.height) } control.orientation = Qt.Vertical - for (var i = 0; i < control.count; ++i) { + for (let i = 0; i < control.count; ++i) { const page = control.itemAt(i) compare(page.y, i * (control.contentItem.height + control.spacing)) compare(page.x, 0) + compare(page.width, control.contentItem.width) + compare(page.height, control.contentItem.height) } + + // QTBUG-115468: add a page after startup and check that that works too. + control.orientation = Qt.Horizontal + let page4 = page.createObject(control, { text: "page 4", "font.pointSize": 40 }) + control.insertItem(control.count, page4) + compare(page4.x, (control.count - 1) * 310) + compare(page4.y, 0) + compare(page4.width, control.contentItem.width) + compare(page4.height, control.contentItem.height) } } diff --git a/tests/auto/quickcontrols2/controls/data/tst_tumbler.qml b/tests/auto/quickcontrols2/controls/data/tst_tumbler.qml index fff6fa7933..0fc2e87b8a 100644 --- a/tests/auto/quickcontrols2/controls/data/tst_tumbler.qml +++ b/tests/auto/quickcontrols2/controls/data/tst_tumbler.qml @@ -1278,4 +1278,46 @@ TestCase { tumbler = createTemporaryObject(initialCurrentIndexTumbler, testCase, {wrap: false}); compare(tumbler.currentIndex, 4); } + + // QTBUG-109995 + Component { + id: flickTumbler + Flickable { + width: 50 + height: 200 + interactive: true + contentHeight: 400 + property alias tumblerItem: noWrapTumbler + Tumbler { + id: noWrapTumbler + anchors.fill: parent + model: 20 + wrap: false + } + } + } + + function test_flick() { + let control = createTemporaryObject(flickTumbler, testCase) + verify(control) + + let tumbler = control.tumblerItem + compare(tumbler.count, 20) + compare(tumbler.wrap, false) + + let touch = touchEvent(tumbler) + let tumblerView = findView(tumbler) + let delegateHeight = tumblerView.children[0].children[0].height + + // Move delegates through touch operation and check the current index + touch.press(0, tumblerView, control.width / 2, control.height / 2).commit() + // Move slowly, otherwise its considered as flick which cause current index + // to be varied according to its velocity + var scrollOffset = control.height / 2 + for (; scrollOffset > delegateHeight / 2; scrollOffset-=5) { + touch.move(0, tumblerView, control.width / 2, scrollOffset).commit() + } + touch.release(0, tumblerView, control.width / 2, scrollOffset).commit() + tryCompare(tumblerView, "currentIndex", 2) + } } diff --git a/tests/auto/quickcontrols2/deferred/CMakeLists.txt b/tests/auto/quickcontrols2/deferred/CMakeLists.txt new file mode 100644 index 0000000000..900732ef4e --- /dev/null +++ b/tests/auto/quickcontrols2/deferred/CMakeLists.txt @@ -0,0 +1,25 @@ +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + data/*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_qquickdeferred + SOURCES + tst_qquickdeferred.cpp + LIBRARIES + Qt::Qml + Qt::QmlPrivate + Qt::QuickTemplates2Private + Qt::QuickTestUtilsPrivate + TESTDATA ${test_data} +) + +qt_internal_extend_target(tst_qquickdeferred CONDITION ANDROID OR IOS + DEFINES + QT_QMLTEST_DATADIR=\\\":/data\\\" +) + +qt_internal_extend_target(tst_qquickdeferred CONDITION NOT ANDROID AND NOT IOS + DEFINES + QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\" +) diff --git a/tests/auto/quickcontrols2/deferred/data/noSpuriousBinding.qml b/tests/auto/quickcontrols2/deferred/data/noSpuriousBinding.qml new file mode 100644 index 0000000000..9f6a174cf4 --- /dev/null +++ b/tests/auto/quickcontrols2/deferred/data/noSpuriousBinding.qml @@ -0,0 +1,15 @@ +import test +import QtQuick + +Item { + id: root + property bool toggle: true + property int counter: 0 + + x: toggle ? 100 : tester.objectProperty.x + + DeferredPropertyTester { + id: tester + objectProperty: Item { x: { console.log("hi"); return 300; } } + } +} diff --git a/tests/auto/quickcontrols2/deferred/tst_qquickdeferred.cpp b/tests/auto/quickcontrols2/deferred/tst_qquickdeferred.cpp new file mode 100644 index 0000000000..4d8096aa46 --- /dev/null +++ b/tests/auto/quickcontrols2/deferred/tst_qquickdeferred.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtTest/qtest.h> +#include <QQmlEngine> +#include <QtQuick/qquickitem.h> +#include <QtQuickTemplates2/private/qquickdeferredexecute_p_p.h> + +class DeferredPropertyTester : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickItem *objectProperty READ objectProperty WRITE setObjectProperty NOTIFY objectChanged) + Q_CLASSINFO("DeferredPropertyNames", "objectProperty") + +public: + DeferredPropertyTester() {} + + + QQuickItem *objectProperty() { + + if (!m_object.wasExecuted()) { + quickBeginDeferred(this, "objectProperty", m_object); + quickCompleteDeferred(this, "objectProperty", m_object); + } + + return m_object; + } + void setObjectProperty(QQuickItem *obj) { + if (m_object == obj) + return; + m_object = obj; + if (!m_object.isExecuting()) // first read + emit objectChanged(); + } + +signals: + void objectChanged(); + +private: + QQuickDeferredPointer<QQuickItem> m_object = nullptr; +}; + +class tst_qquickdeferred : public QQmlDataTest +{ + Q_OBJECT +public: + tst_qquickdeferred() : QQmlDataTest(QT_QMLTEST_DATADIR) {} +private slots: + void noSpuriousBinding(); +}; + + + +void tst_qquickdeferred::noSpuriousBinding() { + qmlRegisterType<DeferredPropertyTester>("test", 1, 0, "DeferredPropertyTester"); + + QQmlEngine engine; + QQmlComponent comp(&engine, testFileUrl("noSpuriousBinding.qml")); + std::unique_ptr<QObject> root(comp.create()); + QVERIFY2(root, qPrintable(comp.errorString())); + root->setProperty("toggle", false); +} + +QTEST_MAIN(tst_qquickdeferred) + +#include "tst_qquickdeferred.moc" diff --git a/tests/auto/quickcontrols2/qquickmenu/tst_qquickmenu.cpp b/tests/auto/quickcontrols2/qquickmenu/tst_qquickmenu.cpp index 9e37541137..0176c2db35 100644 --- a/tests/auto/quickcontrols2/qquickmenu/tst_qquickmenu.cpp +++ b/tests/auto/quickcontrols2/qquickmenu/tst_qquickmenu.cpp @@ -84,7 +84,9 @@ private slots: void menuSeparator(); void repeater(); void order(); +#if QT_CONFIG(cursor) void popup(); +#endif void actions(); #if QT_CONFIG(shortcut) void actionShortcuts(); @@ -819,8 +821,15 @@ void tst_QQuickMenu::order() } } +#if QT_CONFIG(cursor) void tst_QQuickMenu::popup() { +#if defined(Q_OS_ANDROID) + QSKIP("Setting cursor position is not supported on Android"); +#endif + if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) + QSKIP("Setting cursor position is not supported on Wayland"); + // Try moving the cursor from the current position // Skip if it fails since the test relies on moving the cursor const QPoint point = QCursor::pos() + QPoint(1, 1); @@ -851,8 +860,6 @@ void tst_QQuickMenu::popup() QQuickItem *button = window->property("button").value<QQuickItem *>(); QVERIFY(button); - // Android does not support settings cursor position -#if QT_CONFIG(cursor) && !defined(Q_OS_ANDROID) QPoint oldCursorPos = QCursor::pos(); QPoint cursorPos = window->mapToGlobal(QPoint(11, 22)); QCursor::setPos(cursorPos); @@ -981,8 +988,8 @@ void tst_QQuickMenu::popup() QCursor::setPos(oldCursorPos); QTRY_COMPARE(QCursor::pos(), oldCursorPos); -#endif } +#endif // QT_CONFIG(cursor) void tst_QQuickMenu::actions() { diff --git a/tests/auto/quickcontrols2/qquickpopup/tst_qquickpopup.cpp b/tests/auto/quickcontrols2/qquickpopup/tst_qquickpopup.cpp index d2cfda9375..a587c085a4 100644 --- a/tests/auto/quickcontrols2/qquickpopup/tst_qquickpopup.cpp +++ b/tests/auto/quickcontrols2/qquickpopup/tst_qquickpopup.cpp @@ -44,6 +44,7 @@ #include <QtQuick/qquickview.h> #include <QtQuick/private/qquickpalette_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/viewtestutils_p.h> #include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtQuickTemplates2/private/qquickapplicationwindow_p.h> #include <QtQuickTemplates2/private/qquickcombobox_p.h> @@ -123,6 +124,7 @@ private slots: private: static bool hasWindowActivation(); + QScopedPointer<QPointingDevice> touchScreen = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; tst_QQuickPopup::tst_QQuickPopup() @@ -246,7 +248,6 @@ void tst_QQuickPopup::overlay() QFETCH(bool, modal); QFETCH(bool, dim); - QScopedPointer<QPointingDevice> device(QTest::createTouchDevice()); QQuickControlsApplicationHelper helper(this, source); QVERIFY2(helper.ready, helper.failureMessage()); @@ -337,13 +338,13 @@ void tst_QQuickPopup::overlay() QVERIFY(overlay->isVisible()); - QTest::touchEvent(window, device.data()).press(0, QPoint(1, 1)); + QTest::touchEvent(window, touchScreen.data()).press(0, QPoint(1, 1)); QCOMPARE(overlayPressedSignal.count(), ++overlayPressCount); QCOMPARE(overlayReleasedSignal.count(), overlayReleaseCount); QCOMPARE(overlayAttachedPressedSignal.count(), overlayPressCount); QCOMPARE(overlayAttachedReleasedSignal.count(), overlayReleaseCount); - QTest::touchEvent(window, device.data()).release(0, QPoint(1, 1)); + QTest::touchEvent(window, touchScreen.data()).release(0, QPoint(1, 1)); QCOMPARE(overlayPressedSignal.count(), overlayPressCount); QCOMPARE(overlayReleasedSignal.count(), ++overlayReleaseCount); QCOMPARE(overlayAttachedPressedSignal.count(), overlayPressCount); @@ -358,28 +359,28 @@ void tst_QQuickPopup::overlay() QVERIFY(overlay->isVisible()); QVERIFY(!button->isPressed()); - QTest::touchEvent(window, device.data()).press(0, button->mapToScene(QPointF(1, 1)).toPoint()); + QTest::touchEvent(window, touchScreen.data()).press(0, button->mapToScene(QPointF(1, 1)).toPoint()); QVERIFY(popup->isVisible()); QVERIFY(overlay->isVisible()); QCOMPARE(button->isPressed(), !modal); QCOMPARE(overlayPressedSignal.count(), ++overlayPressCount); QCOMPARE(overlayReleasedSignal.count(), overlayReleaseCount); - QTest::touchEvent(window, device.data()).stationary(0).press(1, button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint()); + QTest::touchEvent(window, touchScreen.data()).stationary(0).press(1, button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint()); QVERIFY(popup->isVisible()); QVERIFY(overlay->isVisible()); QCOMPARE(button->isPressed(), !modal); QCOMPARE(overlayPressedSignal.count(), ++overlayPressCount); QCOMPARE(overlayReleasedSignal.count(), overlayReleaseCount); - QTest::touchEvent(window, device.data()).release(0, button->mapToScene(QPointF(1, 1)).toPoint()).stationary(1); + QTest::touchEvent(window, touchScreen.data()).release(0, button->mapToScene(QPointF(1, 1)).toPoint()).stationary(1); QTRY_VERIFY(!popup->isVisible()); QVERIFY(!overlay->isVisible()); QVERIFY(!button->isPressed()); QCOMPARE(overlayPressedSignal.count(), overlayPressCount); QCOMPARE(overlayReleasedSignal.count(), ++overlayReleaseCount); - QTest::touchEvent(window, device.data()).release(1, button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint()); + QTest::touchEvent(window, touchScreen.data()).release(1, button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint()); QVERIFY(!popup->isVisible()); QVERIFY(!overlay->isVisible()); QVERIFY(!button->isPressed()); @@ -484,21 +485,21 @@ void tst_QQuickPopup::closePolicy_data() QTest::addColumn<QString>("source"); QTest::addColumn<QQuickPopup::ClosePolicy>("closePolicy"); - QTest::newRow("Window:NoAutoClose") << "window.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::NoAutoClose); - QTest::newRow("Window:CloseOnPressOutside") << "window.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnPressOutside); - QTest::newRow("Window:CloseOnPressOutsideParent") << "window.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnPressOutsideParent); - QTest::newRow("Window:CloseOnPressOutside|Parent") << "window.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnPressOutside | QQuickPopup::CloseOnPressOutsideParent); - QTest::newRow("Window:CloseOnReleaseOutside") << "window.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnReleaseOutside); - QTest::newRow("Window:CloseOnReleaseOutside|Parent") << "window.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnReleaseOutside | QQuickPopup::CloseOnReleaseOutsideParent); - QTest::newRow("Window:CloseOnEscape") << "window.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnEscape); - - QTest::newRow("ApplicationWindow:NoAutoClose") << "applicationwindow.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::NoAutoClose); - QTest::newRow("ApplicationWindow:CloseOnPressOutside") << "applicationwindow.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnPressOutside); - QTest::newRow("ApplicationWindow:CloseOnPressOutsideParent") << "applicationwindow.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnPressOutsideParent); - QTest::newRow("ApplicationWindow:CloseOnPressOutside|Parent") << "applicationwindow.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnPressOutside | QQuickPopup::CloseOnPressOutsideParent); - QTest::newRow("ApplicationWindow:CloseOnReleaseOutside") << "applicationwindow.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnReleaseOutside); - QTest::newRow("ApplicationWindow:CloseOnReleaseOutside|Parent") << "applicationwindow.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnReleaseOutside | QQuickPopup::CloseOnReleaseOutsideParent); - QTest::newRow("ApplicationWindow:CloseOnEscape") << "applicationwindow.qml"<< static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnEscape); + QTest::newRow("Window:NoAutoClose") << "window.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::NoAutoClose); + QTest::newRow("Window:CloseOnPressOutside") << "window.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnPressOutside); + QTest::newRow("Window:CloseOnPressOutsideParent") << "window.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnPressOutsideParent); + QTest::newRow("Window:CloseOnPressOutside|Parent") << "window.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnPressOutside | QQuickPopup::CloseOnPressOutsideParent); + QTest::newRow("Window:CloseOnReleaseOutside") << "window.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnReleaseOutside); + QTest::newRow("Window:CloseOnReleaseOutside|Parent") << "window.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnReleaseOutside | QQuickPopup::CloseOnReleaseOutsideParent); + QTest::newRow("Window:CloseOnEscape") << "window.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnEscape); + + QTest::newRow("ApplicationWindow:NoAutoClose") << "applicationwindow.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::NoAutoClose); + QTest::newRow("ApplicationWindow:CloseOnPressOutside") << "applicationwindow.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnPressOutside); + QTest::newRow("ApplicationWindow:CloseOnPressOutsideParent") << "applicationwindow.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnPressOutsideParent); + QTest::newRow("ApplicationWindow:CloseOnPressOutside|Parent") << "applicationwindow.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnPressOutside | QQuickPopup::CloseOnPressOutsideParent); + QTest::newRow("ApplicationWindow:CloseOnReleaseOutside") << "applicationwindow.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnReleaseOutside); + QTest::newRow("ApplicationWindow:CloseOnReleaseOutside|Parent") << "applicationwindow.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnReleaseOutside | QQuickPopup::CloseOnReleaseOutsideParent); + QTest::newRow("ApplicationWindow:CloseOnEscape") << "applicationwindow.qml" << static_cast<QQuickPopup::ClosePolicy>(QQuickPopup::CloseOnEscape); } void tst_QQuickPopup::closePolicy() @@ -534,56 +535,64 @@ void tst_QQuickPopup::closePolicy() // wait for dimmer QTest::qWait(50); - // press outside popup and its parent - QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); - if (closePolicy.testFlag(QQuickPopup::CloseOnPressOutside) || closePolicy.testFlag(QQuickPopup::CloseOnPressOutsideParent)) - QTRY_VERIFY(!popup->isVisible()); - else - QVERIFY(popup->isOpened()); + for (int i = 0; i < 2; ++i) { + // press outside popup and its parent + QTest::touchEvent(window, touchScreen.data()).press(0, {1, 1}); + QQuickTouchUtils::flush(window); + if (closePolicy.testFlag(QQuickPopup::CloseOnPressOutside) || closePolicy.testFlag(QQuickPopup::CloseOnPressOutsideParent)) + QTRY_VERIFY(!popup->isVisible()); + else + QVERIFY(popup->isOpened()); - popup->open(); - QVERIFY(popup->isVisible()); - QTRY_VERIFY(popup->isOpened()); + popup->open(); + QVERIFY(popup->isVisible()); + QTRY_VERIFY(popup->isOpened()); - // release outside popup and its parent - QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); - if (closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutside) || closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutsideParent)) - QTRY_VERIFY(!popup->isVisible()); - else - QVERIFY(popup->isOpened()); + // release outside popup and its parent + QTest::touchEvent(window, touchScreen.data()).release(0, {1, 1}); + QQuickTouchUtils::flush(window); + if (closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutside) || closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutsideParent)) + QTRY_VERIFY(!popup->isVisible()); + else + QVERIFY(popup->isOpened()); - popup->open(); - QVERIFY(popup->isVisible()); - QTRY_VERIFY(popup->isOpened()); + popup->open(); + QVERIFY(popup->isVisible()); + QTRY_VERIFY(popup->isOpened()); - // press outside popup but inside its parent - QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(button->x() + 1, button->y() + 1)); - if (closePolicy.testFlag(QQuickPopup::CloseOnPressOutside) && !closePolicy.testFlag(QQuickPopup::CloseOnPressOutsideParent)) - QTRY_VERIFY(!popup->isVisible()); - else - QVERIFY(popup->isOpened()); + // press outside popup but inside its parent + QTest::touchEvent(window, touchScreen.data()).press(0, QPoint(button->x() + 1, button->y() + 1)); + QQuickTouchUtils::flush(window); + if (closePolicy.testFlag(QQuickPopup::CloseOnPressOutside) && !closePolicy.testFlag(QQuickPopup::CloseOnPressOutsideParent)) + QTRY_VERIFY(!popup->isVisible()); + else + QVERIFY(popup->isOpened()); - popup->open(); - QVERIFY(popup->isVisible()); - QTRY_VERIFY(popup->isOpened()); + popup->open(); + QVERIFY(popup->isVisible()); + QTRY_VERIFY(popup->isOpened()); - // release outside popup but inside its parent - QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(button->x() + 1, button->y() + 1)); - if (closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutside) && !closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutsideParent)) - QTRY_VERIFY(!popup->isVisible()); - else - QVERIFY(popup->isOpened()); + // release outside popup but inside its parent + QTest::touchEvent(window, touchScreen.data()).release(0, QPoint(button->x() + 1, button->y() + 1)); + QQuickTouchUtils::flush(window); + if (closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutside) && !closePolicy.testFlag(QQuickPopup::CloseOnReleaseOutsideParent)) + QTRY_VERIFY(!popup->isVisible()); + else + QVERIFY(popup->isOpened()); - popup->open(); - QVERIFY(popup->isVisible()); - QTRY_VERIFY(popup->isOpened()); + popup->open(); + QVERIFY(popup->isVisible()); + QTRY_VERIFY(popup->isOpened()); - // press inside and release outside - QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(button->x() + popup->x() + 1, - button->y() + popup->y() + 1)); - QVERIFY(popup->isOpened()); - QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); - QVERIFY(popup->isOpened()); + // press inside and release outside + QTest::touchEvent(window, touchScreen.data()).press(0, QPoint(button->x() + popup->x() + 1, + button->y() + popup->y() + 1)); + QQuickTouchUtils::flush(window); + QVERIFY(popup->isOpened()); + QTest::touchEvent(window, touchScreen.data()).release(0, {1, 1}); + QQuickTouchUtils::flush(window); + QVERIFY(popup->isOpened()); + } // escape QTest::keyClick(window, Qt::Key_Escape); |