diff options
Diffstat (limited to 'src')
79 files changed, 1398 insertions, 474 deletions
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()); } |