diff options
23 files changed, 644 insertions, 316 deletions
diff --git a/cmake/QtModuleHelpers.cmake b/cmake/QtModuleHelpers.cmake index 95272d3932..886e338b20 100644 --- a/cmake/QtModuleHelpers.cmake +++ b/cmake/QtModuleHelpers.cmake @@ -17,9 +17,10 @@ function(qt_internal_add_module target) qt_internal_module_info(module "${target}") + # TODO: Remove GENERATE_METATYPES once it's not used anymore. # Process arguments: qt_parse_all_arguments(arg "qt_add_module" - "NO_MODULE_HEADERS;STATIC;DISABLE_TOOLS_EXPORT;EXCEPTIONS;INTERNAL_MODULE;NO_SYNC_QT;NO_PRIVATE_MODULE;HEADER_MODULE;GENERATE_METATYPES;NO_CONFIG_HEADER_FILE;SKIP_DEPENDS_INCLUDE;NO_ADDITIONAL_TARGET_INFO" + "NO_MODULE_HEADERS;STATIC;DISABLE_TOOLS_EXPORT;EXCEPTIONS;INTERNAL_MODULE;NO_SYNC_QT;NO_PRIVATE_MODULE;HEADER_MODULE;GENERATE_METATYPES;NO_GENERATE_METATYPES;NO_CONFIG_HEADER_FILE;SKIP_DEPENDS_INCLUDE;NO_ADDITIONAL_TARGET_INFO" "MODULE_INCLUDE_NAME;CONFIG_MODULE_NAME;PRECOMPILED_HEADER;CONFIGURE_FILE_PATH;${__default_target_info_args}" "${__default_private_args};${__default_public_args};${__default_private_module_args};QMAKE_MODULE_CONFIG;EXTRA_CMAKE_FILES;EXTRA_CMAKE_INCLUDES;NO_PCH_SOURCES" ${ARGN}) @@ -498,15 +499,24 @@ set(QT_CMAKE_EXPORT_NAMESPACE ${QT_CMAKE_EXPORT_NAMESPACE})") endif() # Generate metatypes - if (${arg_GENERATE_METATYPES}) - set(metatypes_install_dir ${INSTALL_LIBDIR}/metatypes) - set(args) - if (NOT QT_WILL_INSTALL) - set(args COPY_OVER_INSTALL INSTALL_DIR "${QT_BUILD_DIR}/${metatypes_install_dir}") - else() - set(args INSTALL_DIR "${metatypes_install_dir}") + if(${arg_GENERATE_METATYPES}) + # No mention of NO_GENERATE_METATYPES. You should not use it. + message(WARNING "GENERATE_METATYPES is on by default for Qt modules. Please remove the manual specification.") + endif() + if (NOT ${arg_NO_GENERATE_METATYPES}) + get_target_property(target_type ${target} TYPE) + if (NOT target_type STREQUAL "INTERFACE_LIBRARY") + set(metatypes_install_dir ${INSTALL_LIBDIR}/metatypes) + set(args) + if (NOT QT_WILL_INSTALL) + set(args COPY_OVER_INSTALL INSTALL_DIR "${QT_BUILD_DIR}/${metatypes_install_dir}") + else() + set(args INSTALL_DIR "${metatypes_install_dir}") + endif() + qt6_extract_metatypes(${target} ${args}) + elseif(${arg_GENERATE_METATYPES}) + message(FATAL_ERROR "Meta types generation does not work on interface libraries") endif() - qt6_extract_metatypes(${target} ${args}) endif() qt_internal_get_min_new_policy_cmake_version(min_new_policy_version) qt_internal_get_max_new_policy_cmake_version(max_new_policy_version) diff --git a/config.tests/cmake_zstd/check_zstd.cmake b/config.tests/cmake_zstd/check_zstd.cmake new file mode 100644 index 0000000000..267494f90b --- /dev/null +++ b/config.tests/cmake_zstd/check_zstd.cmake @@ -0,0 +1,5 @@ +file(ARCHIVE_CREATE + OUTPUT cmake_zstd.zstd + PATHS "${CMAKE_CURRENT_LIST_FILE}" + FORMAT raw + COMPRESSION Zstd) diff --git a/configure.cmake b/configure.cmake index 840d5f586c..cfd355dcfe 100644 --- a/configure.cmake +++ b/configure.cmake @@ -830,6 +830,24 @@ qt_feature("zstd" PRIVATE LABEL "Zstandard support" CONDITION ZSTD_FOUND ) +# special case begin +# Check whether CMake was built with zstd support. +# See https://gitlab.kitware.com/cmake/cmake/-/issues/21552 +if(NOT DEFINED CACHE{QT_CMAKE_ZSTD_SUPPORT}) + set(QT_CMAKE_ZSTD_SUPPORT FALSE CACHE INTERNAL "") + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18") + execute_process(COMMAND "${CMAKE_COMMAND}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/config.tests/cmake_zstd/check_zstd.cmake" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/config.tests" + OUTPUT_QUIET ERROR_QUIET + RESULT_VARIABLE qt_check_zstd_exit_code) + if(qt_check_zstd_exit_code EQUAL 0) + set(QT_CMAKE_ZSTD_SUPPORT TRUE CACHE INTERNAL "") + endif() + unset(qt_check_zstd_exit_code) + endif() +endif() +# special case end qt_feature("thread" PUBLIC SECTION "Kernel" LABEL "Thread support" diff --git a/examples/widgets/tools/settingseditor/settingstree.cpp b/examples/widgets/tools/settingseditor/settingstree.cpp index 04af8ce3b9..bcbf3744a5 100644 --- a/examples/widgets/tools/settingseditor/settingstree.cpp +++ b/examples/widgets/tools/settingseditor/settingstree.cpp @@ -214,7 +214,7 @@ void SettingsTree::updateChildItems(QTreeWidgetItem *parent) if (value.userType() == QMetaType::UnknownType) { child->setText(1, "Invalid"); } else { - if (value.type() == QVariant::String) { + if (value.typeId() == QMetaType::QString) { const QString stringValue = value.toString(); if (m_typeChecker->boolExp.match(stringValue).hasMatch()) { value.setValue(stringValue.compare("true", Qt::CaseInsensitive) == 0); diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 3b4fdd1b46..3c6b20636a 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -32,6 +32,7 @@ endif() qt_internal_add_module(Core QMAKE_MODULE_CONFIG moc resources + NO_GENERATE_METATYPES # metatypes are extracted manually below EXCEPTIONS SOURCES global/archdetect.cpp @@ -1186,6 +1187,11 @@ if(QT_FEATURE_mimetype AND QT_FEATURE_mimetype_database) ) else() if(QT_FEATURE_zstd) + if(NOT QT_CMAKE_ZSTD_SUPPORT) + message(FATAL_ERROR + "CMake was not built with zstd support. " + "Rebuild CMake or set QT_AVOID_CMAKE_ARCHIVING_API=ON.") + endif() set(qmime_db_compression Zstd) string(APPEND qmime_db_content "#define MIME_DATABASE_IS_ZSTD\n") else() diff --git a/src/corelib/doc/src/external-resources.qdoc b/src/corelib/doc/src/external-resources.qdoc index 583e668be4..abd359ac33 100644 --- a/src/corelib/doc/src/external-resources.qdoc +++ b/src/corelib/doc/src/external-resources.qdoc @@ -78,7 +78,7 @@ /*! \externalpage https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html - \title Oracle: JNI Functions + \title Java: JNI Functions */ /*! @@ -90,3 +90,13 @@ \externalpage https://developer.android.com/training/articles/perf-jni#local-and-global-references \title JNI tips: Local and global references */ + +/*! + \externalpage https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#attach_current_thread + \title Java: AttachCurrentThread +*/ + +/*! + \externalpage https://docs.oracle.com/en/java/javase/13/docs/specs/jni/functions.html#interface-function-table + \title Java: Interface Function Table +*/ diff --git a/src/corelib/doc/src/objectmodel/bindableproperties.qdoc b/src/corelib/doc/src/objectmodel/bindableproperties.qdoc index 8b50c65d7c..1bb20935d0 100644 --- a/src/corelib/doc/src/objectmodel/bindableproperties.qdoc +++ b/src/corelib/doc/src/objectmodel/bindableproperties.qdoc @@ -50,8 +50,7 @@ The binding expression computes the value by reading other QProperty values. Behind the scenes this dependency is tracked. Whenever a change in any property's dependency is detected, the binding expression is re-evaluated and the new - result is applied to the property. This happens lazily, by marking the binding - as dirty and evaluating it only when the property's value is requested. For example: + result is applied to the property. For example: \code QProperty<QString> firstname("John"); @@ -63,20 +62,19 @@ qDebug() << fullname.value(); // Prints "John Smith age: 41" - firstname = "Emma"; // Marks binding expression as dirty + firstname = "Emma"; // Triggers binding reevaluation - qDebug() << fullname.value(); // Re-evaluates the binding expression and prints "Emma Smith age: 41" + qDebug() << fullname.value(); // Prints the new value "Emma Smith age: 41" // Birthday is coming up - age.setValue(age.value() + 1); + age.setValue(age.value() + 1); // Triggers re-evaluation - qDebug() << fullname.value(); // Re-evaluates the binding expression and prints "Emma Smith age: 42" + qDebug() << fullname.value(); // Prints "Emma Smith age: 42" \endcode When a new value is assigned to the \c firstname property, the binding - expression for \c fullname is marked as dirty. So when the last \c qDebug() statement - tries to read the name value of the \c fullname property, the expression is - evaluated again, \c firstname() will be called again and return the new value. + expression for \c fullname is reevaluated. So when the last \c qDebug() statement + tries to read the name value of the \c fullname property, the new value is returned. Since bindings are C++ functions, they may do anything that's possible in C++. This includes calling other functions. If those functions access values diff --git a/src/corelib/kernel/qjnienvironment.cpp b/src/corelib/kernel/qjnienvironment.cpp index 522c97bdc1..f7a5fcb686 100644 --- a/src/corelib/kernel/qjnienvironment.cpp +++ b/src/corelib/kernel/qjnienvironment.cpp @@ -60,6 +60,8 @@ QT_BEGIN_NAMESPACE Since \c JNIEnv doesn't do much error checking, such as exception checking and clearing, QJniEnvironment allows you to do that easily. + For more information about JNIEnv, see \l {Java: Interface Function Table}. + \note This API has been designed and tested for use with Android. It has not been tested for other platforms. */ @@ -266,7 +268,7 @@ bool QJniEnvironment::registerNativeMethods(const char *className, JNINativeMeth In contrast to \l QJniObject, which handles exceptions internally, if you make JNI calls directly via \c JNIEnv, you need to clear any potential exceptions after the call using this function. For more information about - \c JNIEnv calls that can throw an exception, see \l {Oracle: JNI Functions}{JNI Functions}. + \c JNIEnv calls that can throw an exception, see \l {Java: JNI Functions}{JNI Functions}. \return \c true when a pending exception was cleared. */ @@ -293,7 +295,7 @@ bool QJniEnvironment::checkAndClearExceptions(QJniEnvironment::OutputMode output In contrast to \l QJniObject, which handles exceptions internally, if you make JNI calls directly via \c JNIEnv, you need to clear any potential exceptions after the call using this function. For more information about - \c JNIEnv calls that can throw an exception, see \l {Oracle: JNI Functions}{JNI Functions}. + \c JNIEnv calls that can throw an exception, see \l {Java: JNI Functions}{JNI Functions}. \return \c true when a pending exception was cleared. */ diff --git a/src/corelib/kernel/qjniobject.cpp b/src/corelib/kernel/qjniobject.cpp index 9bd71c061e..418a404bb4 100644 --- a/src/corelib/kernel/qjniobject.cpp +++ b/src/corelib/kernel/qjniobject.cpp @@ -160,7 +160,7 @@ QT_BEGIN_NAMESPACE \l {JNI Design Overview: Global and Local References}. Local references created outside a native method scope must be deleted manually, since the garbage collector will not free them automatically because we are using - \c AttachCurrentThread. For more information, see + \l {Java: AttachCurrentThread}{AttachCurrentThread}. For more information, see \l {JNI tips: Local and global references}. If you want to keep a Java object alive you need to either create a new global diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index d03991eac1..a5354532d2 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -66,16 +66,17 @@ void QPropertyBindingPrivatePtr::reset(QtPrivate::RefCounted *ptr) noexcept void QPropertyBindingDataPointer::addObserver(QPropertyObserver *observer) { - if (auto *binding = bindingPtr()) { - observer->prev = &binding->firstObserver.ptr; - observer->next = binding->firstObserver.ptr; + if (auto *b = binding()) { + observer->prev = &b->firstObserver.ptr; + observer->next = b->firstObserver.ptr; if (observer->next) observer->next->prev = &observer->next; - binding->firstObserver.ptr = observer; + b->firstObserver.ptr = observer; } else { - Q_ASSERT(!(ptr->d_ptr & QPropertyBindingData::BindingBit)); - auto firstObserver = reinterpret_cast<QPropertyObserver*>(ptr->d_ptr); - observer->prev = reinterpret_cast<QPropertyObserver**>(&ptr->d_ptr); + auto &d = ptr->d_ref(); + Q_ASSERT(!(d & QPropertyBindingData::BindingBit)); + auto firstObserver = reinterpret_cast<QPropertyObserver*>(d); + observer->prev = reinterpret_cast<QPropertyObserver**>(&d); observer->next = firstObserver; if (observer->next) observer->next->prev = &observer->next; @@ -83,6 +84,182 @@ void QPropertyBindingDataPointer::addObserver(QPropertyObserver *observer) setFirstObserver(observer); } +/*! + \internal + + QPropertyDelayedNotifications is used to manage delayed notifications in grouped property updates. + It acts as a pool allocator for QPropertyProxyBindingData, and has methods to manage delayed + notifications. + + \sa beginPropertyUpdateGroup, endPropertyUpdateGroup + */ +struct QPropertyDelayedNotifications +{ + // we can't access the dynamic page size as we need a constant value + // use 4096 as a sensible default + static constexpr inline auto PageSize = 4096; + int ref = 0; + QPropertyDelayedNotifications *next = nullptr; // in case we have more than size dirty properties... + qsizetype used = 0; + // Size chosen to avoid allocating more than one page of memory, while still ensuring + // that we can store many delayed properties without doing further allocations + static constexpr qsizetype size = (PageSize - 3*sizeof(void *))/sizeof(QPropertyProxyBindingData); + QPropertyProxyBindingData delayedProperties[size]; + + /*! + \internal + This method is called when a property attempts to notify its observers while inside of a + property update group. Instead of actually notifying, it replaces \a bindingData's d_ptr + with a QPropertyProxyBindingData. + \a bindingData and \a propertyData are the binding data and property data of the property + whose notify call gets delayed. + \sa QPropertyBindingData::notifyObservers + */ + void addProperty(const QPropertyBindingData *bindingData, QUntypedPropertyData *propertyData) { + if (bindingData->isNotificationDelayed()) + return; + auto *data = this; + while (data->used == size) { + if (!data->next) + // add a new page + data->next = new QPropertyDelayedNotifications; + data = data->next; + } + auto *delayed = data->delayedProperties + data->used; + *delayed = QPropertyProxyBindingData { bindingData->d_ptr, bindingData, propertyData }; + ++data->used; + // preserve the binding bit for faster access + quintptr bindingBit = bindingData->d_ptr & QPropertyBindingData::BindingBit; + bindingData->d_ptr = reinterpret_cast<quintptr>(delayed) | QPropertyBindingData::DelayedNotificationBit | bindingBit; + Q_ASSERT(bindingData->d_ptr > 3); + if (!bindingBit) { + if (auto observer = reinterpret_cast<QPropertyObserver *>(delayed->d_ptr)) + observer->prev = reinterpret_cast<QPropertyObserver **>(&delayed->d_ptr); + } + } + + /*! + \internal + Called in Qt::endPropertyUpdateGroup. For the QPropertyProxyBindingData at position + \a index, it + \list + \li restores the original binding data that was modified in addProperty and + \li evaluates any bindings which depend on properties that were changed inside + the group. + \endlist + Change notifications are sent later with notify (following the logic of separating + binding updates and notifications used in non-deferred updates). + */ + void evaluateBindings(int index) { + auto *delayed = delayedProperties + index; + auto *bindingData = delayed->originalBindingData; + if (!bindingData) + return; + + bindingData->d_ptr = delayed->d_ptr; + Q_ASSERT(!(bindingData->d_ptr & QPropertyBindingData::DelayedNotificationBit)); + if (!bindingData->hasBinding()) { + if (auto observer = reinterpret_cast<QPropertyObserver *>(bindingData->d_ptr)) + observer->prev = reinterpret_cast<QPropertyObserver **>(&bindingData->d_ptr); + } + + QPropertyBindingDataPointer bindingDataPointer{bindingData}; + QPropertyObserverPointer observer = bindingDataPointer.firstObserver(); + if (observer) + observer.evaluateBindings(); + } + + /*! + \internal + Called in Qt::endPropertyUpdateGroup. For the QPropertyProxyBindingData at position + \a i, it + \list + \li resets the proxy binding data and + \li sends any pending notifications. + \endlist + */ + void notify(int index) { + auto *delayed = delayedProperties + index; + auto *bindingData = delayed->originalBindingData; + if (!bindingData) + return; + + delayed->originalBindingData = nullptr; + delayed->d_ptr = 0; + + QPropertyBindingDataPointer bindingDataPointer{bindingData}; + QPropertyObserverPointer observer = bindingDataPointer.firstObserver(); + if (observer) + observer.notify(delayed->propertyData); + } +}; + +static thread_local QPropertyDelayedNotifications *groupUpdateData = nullptr; + +/*! + \since 6.2 + + \relates template<typename T> QProperty<T> + + Marks the beginning of a property update group. Inside this group, + changing a property does neither immediately update any dependent properties + nor does it trigger change notifications. + Those are instead deferred until the group is ended by a call to endPropertyUpdateGroup. + + Groups can be nested. In that case, the deferral ends only after the outermost group has been + ended. + + \note Change notifications are only send after all property values affected by the group have + been updated to their new values. This allows re-establishing a class invariant if multiple + properties need to be updated, preventing any external observer from noticing an inconsistent + state. + + \sa Qt::endPropertyUpdateGroup + */ +void Qt::beginPropertyUpdateGroup() +{ + if (!groupUpdateData) + groupUpdateData = new QPropertyDelayedNotifications; + ++groupUpdateData->ref; +} + +/*! + \since 6.2 + \relates template<typename T> QProperty<T> + + Ends a property update group. If the outermost group has been ended, and deferred + binding evaluations and notifications happen now. + + \warning Calling endPropertyUpdateGroup without a preceding call to beginPropertyUpdateGroup + results in undefined behavior. + + \sa Qt::beginPropertyUpdateGroup + */ +void Qt::endPropertyUpdateGroup() +{ + auto *data = groupUpdateData; + Q_ASSERT(data->ref); + if (--data->ref) + return; + groupUpdateData = nullptr; + // update all delayed properties + auto start = data; + while (data) { + for (int i = 0; i < data->used; ++i) + data->evaluateBindings(i); + data = data->next; + } + // notify all delayed properties + data = start; + while (data) { + for (int i = 0; i < data->used; ++i) + data->notify(i); + auto *next = data->next; + delete data; + data = next; + } +} + QPropertyBindingPrivate::~QPropertyBindingPrivate() { if (firstObserver) @@ -98,43 +275,16 @@ void QPropertyBindingPrivate::unlinkAndDeref() destroyAndFreeMemory(this); } -void QPropertyBindingPrivate::markDirtyAndNotifyObservers() +void QPropertyBindingPrivate::evaluateRecursive() { - if (eagerlyUpdating) { + if (updating) { error = QPropertyBindingError(QPropertyBindingError::BindingLoop); if (isQQmlPropertyBinding) errorCallBack(this); return; } - if (dirty) - return; - dirty = true; - - eagerlyUpdating = true; - QScopeGuard guard([&](){eagerlyUpdating = false;}); - bool knownToHaveChanged = false; - if (requiresEagerEvaluation()) { - // these are compat properties that we will need to evaluate eagerly - if (!evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr)) - return; - knownToHaveChanged = true; - } - if (firstObserver) - firstObserver.notify(this, propertyDataPtr, knownToHaveChanged); - if (hasStaticObserver) - staticObserverCallback(propertyDataPtr); -} - -bool QPropertyBindingPrivate::evaluateIfDirtyAndReturnTrueIfValueChanged_helper(const QUntypedPropertyData *data, QBindingStatus *status) -{ - Q_ASSERT(dirty); - if (updating) { - error = QPropertyBindingError(QPropertyBindingError::BindingLoop); - if (isQQmlPropertyBinding) - errorCallBack(this); - return false; - } + QScopedValueRollback<bool> updateGuard(updating, true); /* * Evaluating the binding might lead to the binding being broken. This can @@ -145,23 +295,42 @@ bool QPropertyBindingPrivate::evaluateIfDirtyAndReturnTrueIfValueChanged_helper( * that the object is still alive when updateGuard's dtor runs. */ QPropertyBindingPrivatePtr keepAlive {this}; - QScopedValueRollback<bool> updateGuard(updating, true); - BindingEvaluationState evaluationFrame(this, status); + BindingEvaluationState evaluationFrame(this); + auto bindingFunctor = reinterpret_cast<std::byte *>(this) + + QPropertyBindingPrivate::getSizeEnsuringAlignment(); bool changed = false; - - Q_ASSERT(propertyDataPtr == data); - QUntypedPropertyData *mutable_data = const_cast<QUntypedPropertyData *>(data); - if (hasBindingWrapper) { - changed = staticBindingWrapper(metaType, mutable_data, {vtable, reinterpret_cast<std::byte *>(this)+QPropertyBindingPrivate::getSizeEnsuringAlignment()}); + changed = staticBindingWrapper(metaType, propertyDataPtr, + {vtable, bindingFunctor}); } else { - changed = vtable->call(metaType, mutable_data, reinterpret_cast<std::byte *>(this)+ QPropertyBindingPrivate::getSizeEnsuringAlignment()); + changed = vtable->call(metaType, propertyDataPtr, bindingFunctor); } + // If there was a change, we must set pendingNotify. + // If there was not, we must not clear it, as that only should happen in notifyRecursive + pendingNotify = pendingNotify || changed; + if (!changed || !firstObserver) + return; + + firstObserver.noSelfDependencies(this); + firstObserver.evaluateBindings(); +} - dirty = false; - return changed; +void QPropertyBindingPrivate::notifyRecursive() +{ + if (!pendingNotify) + return; + pendingNotify = false; + Q_ASSERT(!updating); + updating = true; + if (firstObserver) { + firstObserver.noSelfDependencies(this); + firstObserver.notify(propertyDataPtr); + } + if (hasStaticObserver) + staticObserverCallback(propertyDataPtr); + updating = false; } QUntypedPropertyBinding::QUntypedPropertyBinding() = default; @@ -232,7 +401,7 @@ QPropertyBindingData::~QPropertyBindingData() observer.unlink(); observer = next; } - if (auto binding = d.bindingPtr()) + if (auto binding = d.binding()) binding->unlinkAndDeref(); } @@ -247,42 +416,38 @@ QUntypedPropertyBinding QPropertyBindingData::setBinding(const QUntypedPropertyB QPropertyBindingDataPointer d{this}; QPropertyObserverPointer observer; - if (auto *existingBinding = d.bindingPtr()) { + auto &data = d_ref(); + if (auto *existingBinding = d.binding()) { if (existingBinding == newBinding.data()) return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(oldBinding.data())); - if (existingBinding->isEagerlyUpdating()) { + if (existingBinding->isUpdating()) { existingBinding->setError({QPropertyBindingError::BindingLoop, QStringLiteral("Binding set during binding evaluation!")}); return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(oldBinding.data())); } oldBinding = QPropertyBindingPrivatePtr(existingBinding); observer = static_cast<QPropertyBindingPrivate *>(oldBinding.data())->takeObservers(); static_cast<QPropertyBindingPrivate *>(oldBinding.data())->unlinkAndDeref(); - d_ptr = 0; + data = 0; } else { observer = d.firstObserver(); } if (newBinding) { newBinding.data()->addRef(); - d_ptr = reinterpret_cast<quintptr>(newBinding.data()); - d_ptr |= BindingBit; + data = reinterpret_cast<quintptr>(newBinding.data()); + data |= BindingBit; auto newBindingRaw = static_cast<QPropertyBindingPrivate *>(newBinding.data()); - newBindingRaw->setDirty(true); newBindingRaw->setProperty(propertyDataPtr); if (observer) newBindingRaw->prependObserver(observer); newBindingRaw->setStaticObserver(staticObserverCallback, guardCallback); - if (newBindingRaw->requiresEagerEvaluation()) { - newBindingRaw->setEagerlyUpdating(true); - auto changed = newBindingRaw->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr); - if (changed) - observer.notify(newBindingRaw, propertyDataPtr, /*knownToHaveChanged=*/true); - newBindingRaw->setEagerlyUpdating(false); - } + + newBindingRaw->evaluateRecursive(); + newBindingRaw->notifyRecursive(); } else if (observer) { d.setObservers(observer.ptr); } else { - d_ptr &= ~QPropertyBindingData::BindingBit; + data = 0; } if (oldBinding) @@ -293,8 +458,7 @@ QUntypedPropertyBinding QPropertyBindingData::setBinding(const QUntypedPropertyB QPropertyBindingData::QPropertyBindingData(QPropertyBindingData &&other) : d_ptr(std::exchange(other.d_ptr, 0)) { - QPropertyBindingDataPointer d{this}; - d.fixupFirstObserverAfterMove(); + QPropertyBindingDataPointer::fixupAfterMove(this); } static thread_local QBindingStatus bindingStatus; @@ -333,24 +497,20 @@ QPropertyBindingPrivate *QPropertyBindingPrivate::currentlyEvaluatingBinding() return currentState ? currentState->binding : nullptr; } -void QPropertyBindingData::evaluateIfDirty(const QUntypedPropertyData *property) const +// ### Unused, kept for BC with 6.0 +void QPropertyBindingData::evaluateIfDirty(const QUntypedPropertyData *) const { - QPropertyBindingDataPointer d{this}; - QPropertyBindingPrivate *binding = d.bindingPtr(); - if (!binding) - return; - binding->evaluateIfDirtyAndReturnTrueIfValueChanged(property); } void QPropertyBindingData::removeBinding_helper() { QPropertyBindingDataPointer d{this}; - auto *existingBinding = d.bindingPtr(); + auto *existingBinding = d.binding(); Q_ASSERT(existingBinding); auto observer = existingBinding->takeObservers(); - d_ptr = 0; + d_ref() = 0; if (observer) d.setObservers(observer.ptr); existingBinding->unlinkAndDeref(); @@ -370,22 +530,25 @@ void QPropertyBindingData::registerWithCurrentlyEvaluatingBinding_helper(Binding QPropertyBindingDataPointer d{this}; QPropertyObserverPointer dependencyObserver = currentState->binding->allocateDependencyObserver(); - dependencyObserver.setBindingToMarkDirty(currentState->binding); + dependencyObserver.setBindingToNotify(currentState->binding); dependencyObserver.observeProperty(d); } void QPropertyBindingData::notifyObservers(QUntypedPropertyData *propertyDataPtr) const { + if (isNotificationDelayed()) + return; QPropertyBindingDataPointer d{this}; - if (QPropertyObserverPointer observer = d.firstObserver()) - observer.notify(d.bindingPtr(), propertyDataPtr); -} - -void QPropertyBindingData::markDirty() -{ - QPropertyBindingDataPointer d{this}; - if (auto *binding = d.bindingPtr()) - binding->setDirty(true); + QPropertyObserverPointer observer = d.firstObserver(); + if (!observer) + return; + auto *delay = groupUpdateData; + if (delay) { + delay->addProperty(this, propertyDataPtr); + return; + } + observer.evaluateBindings(); + observer.notify(propertyDataPtr); } int QPropertyBindingDataPointer::observerCount() const @@ -425,7 +588,7 @@ QPropertyObserver::~QPropertyObserver() QPropertyObserver::QPropertyObserver(QPropertyObserver &&other) noexcept { - bindingToMarkDirty = std::exchange(other.bindingToMarkDirty, {}); + binding = std::exchange(other.binding, {}); next = std::exchange(other.next, {}); prev = std::exchange(other.prev, {}); if (next) @@ -441,9 +604,9 @@ QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) noexc QPropertyObserverPointer d{this}; d.unlink(); - bindingToMarkDirty = nullptr; + binding = nullptr; - bindingToMarkDirty = std::exchange(other.bindingToMarkDirty, {}); + binding = std::exchange(other.binding, {}); next = std::exchange(other.next, {}); prev = std::exchange(other.prev, {}); if (next) @@ -480,10 +643,10 @@ void QPropertyObserverPointer::setAliasedProperty(QUntypedPropertyData *property ptr->next.setTag(QPropertyObserver::ObserverNotifiesAlias); } -void QPropertyObserverPointer::setBindingToMarkDirty(QPropertyBindingPrivate *binding) +void QPropertyObserverPointer::setBindingToNotify(QPropertyBindingPrivate *binding) { Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder); - ptr->bindingToMarkDirty = binding; + ptr->binding = binding; ptr->next.setTag(QPropertyObserver::ObserverNotifiesBinding); } @@ -516,14 +679,8 @@ struct [[nodiscard]] QPropertyObserverNodeProtector { /*! \internal \a propertyDataPtr is a pointer to the observed property's property data - In case that property has a binding, \a triggeringBinding points to the binding's QPropertyBindingPrivate - \a alreadyKnownToHaveChanged is an optional parameter, which is needed in the case - of eager evaluation: - There, we have already evaluated the binding, and thus the change detection for the - ObserverNotifiesChangeHandler case would not work. Thus we instead pass the knowledge of - whether the value has changed we obtained when evaluating the binding eagerly along */ -void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding, QUntypedPropertyData *propertyDataPtr, bool knownToHaveChanged) +void QPropertyObserverPointer::notify(QUntypedPropertyData *propertyDataPtr) { auto observer = const_cast<QPropertyObserver*>(ptr); /* @@ -557,22 +714,17 @@ void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding observer = next->next.data(); continue; } - // both evaluateIfDirtyAndReturnTrueIfValueChanged and handlerToCall might modify the list + // handlerToCall might modify the list QPropertyObserverNodeProtector protector(observer); - if (!knownToHaveChanged && triggeringBinding) { - if (!triggeringBinding->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr)) - return; - knownToHaveChanged = true; - } handlerToCall(observer, propertyDataPtr); next = protector.next(); break; } case QPropertyObserver::ObserverNotifiesBinding: { - auto bindingToMarkDirty = observer->bindingToMarkDirty; + auto bindingToNotify = observer->binding; QPropertyObserverNodeProtector protector(observer); - bindingToMarkDirty->markDirtyAndNotifyObservers(); + bindingToNotify->notifyRecursive(); next = protector.next(); break; } @@ -586,6 +738,39 @@ void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding } } +#ifndef QT_NO_DEBUG +void QPropertyObserverPointer::noSelfDependencies(QPropertyBindingPrivate *binding) +{ + auto observer = const_cast<QPropertyObserver*>(ptr); + // See also comment in notify() + while (observer) { + if (QPropertyObserver::ObserverTag(observer->next.tag()) == QPropertyObserver::ObserverNotifiesBinding) + Q_ASSERT(observer->binding != binding); + + observer = observer->next.data(); + } + +} +#endif + +void QPropertyObserverPointer::evaluateBindings() +{ + auto observer = const_cast<QPropertyObserver*>(ptr); + // See also comment in notify() + while (observer) { + QPropertyObserver *next = observer->next.data(); + + if (QPropertyObserver::ObserverTag(observer->next.tag()) == QPropertyObserver::ObserverNotifiesBinding) { + auto bindingToEvaluate = observer->binding; + QPropertyObserverNodeProtector protector(observer); + bindingToEvaluate->evaluateRecursive(); + next = protector.next(); + } + + observer = next; + } +} + void QPropertyObserverPointer::observeProperty(QPropertyBindingDataPointer property) { if (ptr->prev) @@ -1873,18 +2058,26 @@ QBindingStorage::~QBindingStorage() QBindingStoragePrivate(d).destroy(); } +// ### Unused, retained for BC with 6.0 void QBindingStorage::maybeUpdateBindingAndRegister_helper(const QUntypedPropertyData *data) const { + registerDependency_helper(data); +} + +void QBindingStorage::registerDependency_helper(const QUntypedPropertyData *data) const +{ Q_ASSERT(bindingStatus); + // Use ::bindingStatus to get the binding from TLS. This is required, so that reads from + // another thread do not register as dependencies + auto *currentBinding = QT_PREPEND_NAMESPACE(bindingStatus).currentlyEvaluatingBinding; QUntypedPropertyData *dd = const_cast<QUntypedPropertyData *>(data); - auto storage = QBindingStoragePrivate(d).get(dd, /*create=*/ bindingStatus->currentlyEvaluatingBinding != nullptr); + auto storage = QBindingStoragePrivate(d).get(dd, /*create=*/ currentBinding != nullptr); if (!storage) return; - if (auto *binding = storage->binding()) - binding->evaluateIfDirtyAndReturnTrueIfValueChanged(const_cast<QUntypedPropertyData *>(data), bindingStatus); - storage->registerWithCurrentlyEvaluatingBinding(bindingStatus->currentlyEvaluatingBinding); + storage->registerWithCurrentlyEvaluatingBinding(currentBinding); } + QPropertyBindingData *QBindingStorage::bindingData_helper(const QUntypedPropertyData *data) const { return QBindingStoragePrivate(d).get(data); @@ -1921,6 +2114,13 @@ bool isAnyBindingEvaluating() { return bindingStatus.currentlyEvaluatingBinding != nullptr; } + +bool isPropertyInBindingWrapper(const QUntypedPropertyData *property) +{ + return bindingStatus.currentCompatProperty && + bindingStatus.currentCompatProperty->property == property; +} + } // namespace QtPrivate end QT_END_NAMESPACE diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h index 717c6713d4..a6cb7ada48 100644 --- a/src/corelib/kernel/qproperty.h +++ b/src/corelib/kernel/qproperty.h @@ -63,6 +63,11 @@ QT_BEGIN_NAMESPACE +namespace Qt { +Q_CORE_EXPORT void beginPropertyUpdateGroup(); +Q_CORE_EXPORT void endPropertyUpdateGroup(); +} + template <typename T> class QPropertyData : public QUntypedPropertyData { @@ -217,6 +222,7 @@ protected: using ChangeHandler = void (*)(QPropertyObserver*, QUntypedPropertyData *); private: + friend struct QPropertyDelayedNotifications; friend struct QPropertyObserverNodeProtector; friend class QPropertyObserver; friend struct QPropertyObserverPointer; @@ -229,7 +235,7 @@ private: QtPrivate::QTagPreservingPointerToPointer<QPropertyObserver, ObserverTag> prev; union { - QPropertyBindingPrivate *bindingToMarkDirty = nullptr; + QPropertyBindingPrivate *binding = nullptr; ChangeHandler changeHandler; QUntypedPropertyData *aliasedPropertyData; }; @@ -329,8 +335,6 @@ public: parameter_type value() const { - if (d.hasBinding()) - d.evaluateIfDirty(this); d.registerWithCurrentlyEvaluatingBinding(); return this->val; } @@ -389,9 +393,7 @@ public: QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding) { - QPropertyBinding<T> oldBinding(d.setBinding(newBinding, this)); - notify(); - return oldBinding; + return QPropertyBinding<T>(d.setBinding(newBinding, this)); } bool setBinding(const QUntypedPropertyBinding &newBinding) @@ -402,11 +404,6 @@ public: return true; } - void markDirty() { - d.markDirty(); - notify(); - } - #ifndef Q_CLANG_QDOC template <typename Functor> QPropertyBinding<T> setBinding(Functor &&f, @@ -852,11 +849,11 @@ public: bool isEmpty() { return !d; } - void maybeUpdateBindingAndRegister(const QUntypedPropertyData *data) const + void registerDependency(const QUntypedPropertyData *data) const { - if (!d && !bindingStatus->currentlyEvaluatingBinding) + if (!bindingStatus->currentlyEvaluatingBinding) return; - maybeUpdateBindingAndRegister_helper(data); + registerDependency_helper(data); } QtPrivate::QPropertyBindingData *bindingData(const QUntypedPropertyData *data) const { @@ -864,6 +861,9 @@ public: return nullptr; return bindingData_helper(data); } + // ### Qt 7: remove unused BIC shim + void maybeUpdateBindingAndRegister(const QUntypedPropertyData *data) const { registerDependency(data); } + QtPrivate::QPropertyBindingData *bindingData(QUntypedPropertyData *data, bool create) { if (!d && !create) @@ -871,6 +871,8 @@ public: return bindingData_helper(data, create); } private: + void registerDependency_helper(const QUntypedPropertyData *data) const; + // ### Unused, but keep for BC void maybeUpdateBindingAndRegister_helper(const QUntypedPropertyData *data) const; QtPrivate::QPropertyBindingData *bindingData_helper(const QUntypedPropertyData *data) const; QtPrivate::QPropertyBindingData *bindingData_helper(QUntypedPropertyData *data, bool create); @@ -923,7 +925,7 @@ public: parameter_type value() const { - qGetBindingStorage(owner())->maybeUpdateBindingAndRegister(this); + qGetBindingStorage(owner())->registerDependency(this); return this->val; } @@ -987,7 +989,6 @@ public: { QtPrivate::QPropertyBindingData *bd = qGetBindingStorage(owner())->bindingData(this, true); QUntypedPropertyBinding oldBinding(bd->setBinding(newBinding, this, HasSignal ? &signalCallBack : nullptr)); - notify(bd); return static_cast<QPropertyBinding<T> &>(oldBinding); } @@ -1018,15 +1019,6 @@ public: return bd && bd->binding() != nullptr; } - void markDirty() { - QBindingStorage *storage = qGetBindingStorage(owner()); - auto bd = storage->bindingData(this, /*create=*/false); - if (bd) { // if we have no BindingData, nobody can listen anyway - bd->markDirty(); - notify(bd); - } - } - QPropertyBinding<T> binding() const { auto *bd = qGetBindingStorage(owner())->bindingData(this); @@ -1130,7 +1122,7 @@ public: parameter_type value() const { - qGetBindingStorage(owner())->maybeUpdateBindingAndRegister(this); + qGetBindingStorage(owner())->registerDependency(this); return (owner()->*Getter)(); } @@ -1176,15 +1168,13 @@ public: return *storage->bindingData(const_cast<QObjectComputedProperty *>(this), true); } - void markDirty() { + void notify() { // computed property can't store a binding, so there's nothing to mark auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner())); auto bd = storage->bindingData(const_cast<QObjectComputedProperty *>(this), false); if (bd) - bindingData().notifyObservers(this); + bd->notifyObservers(this); } - -private: }; #define Q_OBJECT_COMPUTED_PROPERTY(Class, Type, name, ...) \ diff --git a/src/corelib/kernel/qproperty_p.h b/src/corelib/kernel/qproperty_p.h index d459433528..58a000e9bf 100644 --- a/src/corelib/kernel/qproperty_p.h +++ b/src/corelib/kernel/qproperty_p.h @@ -71,19 +71,18 @@ struct Q_AUTOTEST_EXPORT QPropertyBindingDataPointer { const QtPrivate::QPropertyBindingData *ptr = nullptr; - QPropertyBindingPrivate *bindingPtr() const + QPropertyBindingPrivate *binding() const { - if (ptr->d_ptr & QtPrivate::QPropertyBindingData::BindingBit) - return reinterpret_cast<QPropertyBindingPrivate*>(ptr->d_ptr - QtPrivate::QPropertyBindingData::BindingBit); - return nullptr; + return ptr->binding(); } void setObservers(QPropertyObserver *observer) { - observer->prev = reinterpret_cast<QPropertyObserver**>(&(ptr->d_ptr)); - ptr->d_ptr = reinterpret_cast<quintptr>(observer); + auto &d = ptr->d_ref(); + observer->prev = reinterpret_cast<QPropertyObserver**>(&d); + d = reinterpret_cast<quintptr>(observer); } - void fixupFirstObserverAfterMove() const; + static void fixupAfterMove(QtPrivate::QPropertyBindingData *ptr); void addObserver(QPropertyObserver *observer); void setFirstObserver(QPropertyObserver *observer); QPropertyObserverPointer firstObserver() const; @@ -104,11 +103,17 @@ struct QPropertyObserverPointer void unlink(); - void setBindingToMarkDirty(QPropertyBindingPrivate *binding); + void setBindingToNotify(QPropertyBindingPrivate *binding); void setChangeHandler(QPropertyObserver::ChangeHandler changeHandler); void setAliasedProperty(QUntypedPropertyData *propertyPtr); - void notify(QPropertyBindingPrivate *triggeringBinding, QUntypedPropertyData *propertyDataPtr, bool knownToHaveChanged = false); + void notify(QUntypedPropertyData *propertyDataPtr); +#ifndef QT_NO_DEBUG + void noSelfDependencies(QPropertyBindingPrivate *binding); +#else + void noSelfDependencies(QPropertyBindingPrivate *) {} +#endif + void evaluateBindings(); void observeProperty(QPropertyBindingDataPointer property); explicit operator bool() const { return ptr != nullptr; } @@ -172,14 +177,12 @@ private: private: - // a dependent property has changed, and the binding needs to be reevaluated on access - bool dirty = false; // used to detect binding loops for lazy evaluated properties bool updating = false; bool hasStaticObserver = false; + bool pendingNotify = false; bool hasBindingWrapper:1; // used to detect binding loops for eagerly evaluated properties - bool eagerlyUpdating:1; bool isQQmlPropertyBinding:1; const QtPrivate::BindingFunctionVTable *vtable; @@ -230,13 +233,11 @@ public: // public because the auto-tests access it, too. size_t dependencyObserverCount = 0; - bool isEagerlyUpdating() {return eagerlyUpdating;} - void setEagerlyUpdating(bool b) {eagerlyUpdating = b;} + bool isUpdating() {return updating;} QPropertyBindingPrivate(QMetaType metaType, const QtPrivate::BindingFunctionVTable *vtable, const QPropertyBindingSourceLocation &location, bool isQQmlPropertyBinding=false) : hasBindingWrapper(false) - , eagerlyUpdating(false) , isQQmlPropertyBinding(isQQmlPropertyBinding) , vtable(vtable) , location(location) @@ -245,7 +246,6 @@ public: ~QPropertyBindingPrivate(); - void setDirty(bool d) { dirty = d; } void setProperty(QUntypedPropertyData *propertyPtr) { propertyDataPtr = propertyPtr; } void setStaticObserver(QtPrivate::QPropertyObserverCallback callback, QtPrivate::QPropertyBindingWrapper bindingWrapper) { @@ -312,13 +312,8 @@ public: void unlinkAndDeref(); - void markDirtyAndNotifyObservers(); - bool evaluateIfDirtyAndReturnTrueIfValueChanged(const QUntypedPropertyData *data, QBindingStatus *status = nullptr) - { - if (!dirty) - return false; - return evaluateIfDirtyAndReturnTrueIfValueChanged_helper(data, status); - } + void evaluateRecursive(); + void notifyRecursive(); static QPropertyBindingPrivate *get(const QUntypedPropertyBinding &binding) { return static_cast<QPropertyBindingPrivate *>(binding.d.data()); } @@ -334,8 +329,6 @@ public: clearDependencyObservers(); } - bool requiresEagerEvaluation() const { return hasBindingWrapper; } - static QPropertyBindingPrivate *currentlyEvaluatingBinding(); bool hasCustomVTable() const @@ -353,36 +346,44 @@ public: delete[] reinterpret_cast<std::byte *>(priv); } } - -private: - bool evaluateIfDirtyAndReturnTrueIfValueChanged_helper(const QUntypedPropertyData *data, QBindingStatus *status = nullptr); }; inline void QPropertyBindingDataPointer::setFirstObserver(QPropertyObserver *observer) { - if (auto *binding = bindingPtr()) { - binding->firstObserver.ptr = observer; + if (auto *b = binding()) { + b->firstObserver.ptr = observer; return; } - ptr->d_ptr = reinterpret_cast<quintptr>(observer); + auto &d = ptr->d_ref(); + d = reinterpret_cast<quintptr>(observer); } -inline void QPropertyBindingDataPointer::fixupFirstObserverAfterMove() const +inline void QPropertyBindingDataPointer::fixupAfterMove(QtPrivate::QPropertyBindingData *ptr) { + auto &d = ptr->d_ref(); + if (ptr->isNotificationDelayed()) { + QPropertyProxyBindingData *proxyData + = reinterpret_cast<QPropertyProxyBindingData*>(d & ~QtPrivate::QPropertyBindingData::BindingBit); + proxyData->originalBindingData = ptr; + } // If QPropertyBindingData has been moved, and it has an observer - // we have to adjust the firstObesrver's prev pointer to point to + // we have to adjust the firstObserver's prev pointer to point to // the moved to QPropertyBindingData's d_ptr - if (ptr->d_ptr & QtPrivate::QPropertyBindingData::BindingBit) + if (d & QtPrivate::QPropertyBindingData::BindingBit) return; // nothing to do if the observer is stored in the binding - if (auto observer = firstObserver()) - observer.ptr->prev = reinterpret_cast<QPropertyObserver **>(&(ptr->d_ptr)); + if (auto observer = reinterpret_cast<QPropertyObserver *>(d)) + observer->prev = reinterpret_cast<QPropertyObserver **>(&d); } inline QPropertyObserverPointer QPropertyBindingDataPointer::firstObserver() const { - if (auto *binding = bindingPtr()) - return binding->firstObserver; - return { reinterpret_cast<QPropertyObserver *>(ptr->d_ptr) }; + if (auto *b = binding()) + return b->firstObserver; + return { reinterpret_cast<QPropertyObserver *>(ptr->d()) }; +} + +namespace QtPrivate { + Q_CORE_EXPORT bool isPropertyInBindingWrapper(const QUntypedPropertyData *property); } template<typename Class, typename T, auto Offset, auto Setter, auto Signal=nullptr> @@ -414,10 +415,10 @@ class QObjectCompatProperty : public QPropertyData<T> (thisData->owner()->*Setter)(copy.valueBypassingBindings()); return true; } - inline bool inBindingWrapper(const QBindingStorage *storage) const + bool inBindingWrapper(const QBindingStorage *storage) const { - return storage->bindingStatus->currentCompatProperty && - storage->bindingStatus->currentCompatProperty->property == this; + return storage->bindingStatus->currentCompatProperty + && QtPrivate::isPropertyInBindingWrapper(this); } public: @@ -434,7 +435,7 @@ public: const QBindingStorage *storage = qGetBindingStorage(owner()); // make sure we don't register this binding as a dependency to itself if (!inBindingWrapper(storage)) - storage->maybeUpdateBindingAndRegister(this); + storage->registerDependency(this); return this->val; } @@ -511,15 +512,6 @@ public: return bd && bd->binding() != nullptr; } - void markDirty() { - QBindingStorage *storage = qGetBindingStorage(owner()); - auto *bd = storage->bindingData(this, false); - if (bd) { - bd->markDirty(); - notify(bd); - } - } - void removeBindingUnlessInWrapper() { QBindingStorage *storage = qGetBindingStorage(owner()); @@ -576,6 +568,7 @@ public: auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner())); return *storage->bindingData(const_cast<QObjectCompatProperty *>(this), true); } + private: void notify(const QtPrivate::QPropertyBindingData *binding) { diff --git a/src/corelib/kernel/qpropertyprivate.h b/src/corelib/kernel/qpropertyprivate.h index 896ad17395..f59221c4df 100644 --- a/src/corelib/kernel/qpropertyprivate.h +++ b/src/corelib/kernel/qpropertyprivate.h @@ -143,7 +143,6 @@ private: QtPrivate::RefCounted *d; }; - class QUntypedPropertyBinding; class QPropertyBindingPrivate; struct QPropertyBindingDataPointer; @@ -158,6 +157,24 @@ public: template <typename T> class QPropertyData; +// Used for grouped property evaluations +namespace QtPrivate { +class QPropertyBindingData; +} +struct QPropertyDelayedNotifications; +struct QPropertyProxyBindingData +{ + // acts as QPropertyBindingData::d_ptr + quintptr d_ptr; + /* + The two members below store the original binding data and property + data pointer of the property which gets proxied. + They are set in QPropertyDelayedNotifications::addProperty + */ + const QtPrivate::QPropertyBindingData *originalBindingData; + QUntypedPropertyData *propertyData; +}; + namespace QtPrivate { struct BindingEvaluationState; @@ -218,6 +235,16 @@ struct QPropertyBindingFunction { using QPropertyObserverCallback = void (*)(QUntypedPropertyData *); using QPropertyBindingWrapper = bool(*)(QMetaType, QUntypedPropertyData *dataPtr, QPropertyBindingFunction); +/*! + \internal + A property normally consists of the actual property value and metadata for the binding system. + QPropertyBindingData is the latter part. It stores a pointer to either + - a (potentially empty) linked list of notifiers, in case there is no binding set, + - an actual QUntypedPropertyBinding when the property has a binding, + - or a pointer to QPropertyProxyBindingData when notifications occur inside a grouped update. + + \sa QPropertyDelayedNotifications, beginPropertyUpdateGroup + */ class Q_CORE_EXPORT QPropertyBindingData { // Mutable because the address of the observer of the currently evaluating binding is stored here, for @@ -225,6 +252,7 @@ class Q_CORE_EXPORT QPropertyBindingData mutable quintptr d_ptr = 0; friend struct QT_PREPEND_NAMESPACE(QPropertyBindingDataPointer); friend class QT_PREPEND_NAMESPACE(QQmlPropertyBinding); + friend struct QT_PREPEND_NAMESPACE(QPropertyDelayedNotifications); Q_DISABLE_COPY(QPropertyBindingData) public: QPropertyBindingData() = default; @@ -232,9 +260,13 @@ public: QPropertyBindingData &operator=(QPropertyBindingData &&other) = delete; ~QPropertyBindingData(); - static inline constexpr quintptr BindingBit = 0x1; // Is d_ptr pointing to a binding (1) or list of notifiers (0)? + // Is d_ptr pointing to a binding (1) or list of notifiers (0)? + static inline constexpr quintptr BindingBit = 0x1; + // Is d_ptr pointing to QPropertyProxyBindingData (1) or to an actual binding/list of notifiers? + static inline constexpr quintptr DelayedNotificationBit = 0x2; bool hasBinding() const { return d_ptr & BindingBit; } + bool isNotificationDelayed() const { return d_ptr & DelayedNotificationBit; } QUntypedPropertyBinding setBinding(const QUntypedPropertyBinding &newBinding, QUntypedPropertyData *propertyDataPtr, @@ -243,14 +275,14 @@ public: QPropertyBindingPrivate *binding() const { - if (d_ptr & BindingBit) - return reinterpret_cast<QPropertyBindingPrivate*>(d_ptr - BindingBit); + quintptr dd = d(); + if (dd & BindingBit) + return reinterpret_cast<QPropertyBindingPrivate*>(dd - BindingBit); return nullptr; } - void evaluateIfDirty(const QUntypedPropertyData *property) const; - void markDirty(); + void evaluateIfDirty(const QUntypedPropertyData *) const; // ### Kept for BC reasons, unused void removeBinding() { @@ -267,6 +299,25 @@ public: void registerWithCurrentlyEvaluatingBinding() const; void notifyObservers(QUntypedPropertyData *propertyDataPtr) const; private: + /*! + \internal + Returns a reference to d_ptr, except when d_ptr points to a proxy. + In that case, a reference to proxy->d_ptr is returned instead. + + To properly support proxying, direct access to d_ptr only occcurs when + - a function actually deals with proxying (e.g. + QPropertyDelayedNotifications::addProperty), + - only the tag value is accessed (e.g. hasBinding) or + - inside a constructor. + */ + quintptr &d_ref() const + { + quintptr &d = d_ptr; + if (isNotificationDelayed()) + return reinterpret_cast<QPropertyProxyBindingData *>(d_ptr & ~(BindingBit|DelayedNotificationBit))->d_ptr; + return d; + } + quintptr d() const { return d_ref(); } void registerWithCurrentlyEvaluatingBinding_helper(BindingEvaluationState *currentBinding) const; void removeBinding_helper(); }; diff --git a/src/corelib/kernel/qtimer.cpp b/src/corelib/kernel/qtimer.cpp index 333e6c24ba..10e26fcdef 100644 --- a/src/corelib/kernel/qtimer.cpp +++ b/src/corelib/kernel/qtimer.cpp @@ -241,7 +241,7 @@ void QTimer::start() stop(); d->nulltimer = (!d->inter && d->single); d->id = QObject::startTimer(d->inter, d->type); - d->isActiveData.markDirty(); + d->isActiveData.notify(); } /*! @@ -278,7 +278,7 @@ void QTimer::stop() if (d->id != INV_TIMER) { QObject::killTimer(d->id); d->id = INV_TIMER; - d->isActiveData.markDirty(); + d->isActiveData.notify(); } } @@ -763,9 +763,8 @@ void QTimer::setInterval(int msec) // No need to call markDirty() for d->isActiveData here, // as timer state actually does not change } - if (intervalChanged) - d->inter.markDirty(); + d->inter.notify(); } int QTimer::interval() const diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h index d95d3cd0fb..9ebdbc4a18 100644 --- a/src/corelib/kernel/qvariant.h +++ b/src/corelib/kernel/qvariant.h @@ -333,7 +333,7 @@ class Q_CORE_EXPORT QVariant explicit QVariant(Type type) : QVariant(QMetaType(int(type))) {} - QT_DEPRECATED_VERSION_X_6_0("Use metaType().") + QT_DEPRECATED_VERSION_X_6_0("Use typeId() or metaType().") Type type() const { int type = d.typeId(); diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp index a690aac936..5a340530a9 100644 --- a/src/corelib/text/qstring.cpp +++ b/src/corelib/text/qstring.cpp @@ -2190,6 +2190,16 @@ inline char qToLower(char ch) \sa fromLatin1(), fromLocal8Bit(), fromUtf8() */ +/*! \fn QString::QString(const char8_t *str) + + Constructs a string initialized with the UTF-8 string \a str. The + given const char8_t pointer is converted to Unicode using the + fromUtf8() function. + + \since 6.1 + \sa fromLatin1(), fromLocal8Bit(), fromUtf8() +*/ + /*! \fn QString QString::fromStdString(const std::string &str) Returns a copy of the \a str string. The given string is converted @@ -5325,6 +5335,14 @@ QString QString::fromLocal8Bit(QByteArrayView ba) */ /*! + \fn QString QString::fromUtf8(const char8_t *str) + \overload + \since 6.1 + + This overload is only available when compiling in C++20 mode. +*/ + +/*! \fn QString QString::fromUtf8(const char8_t *str, qsizetype size) \overload \since 6.0 diff --git a/src/corelib/text/qstring.h b/src/corelib/text/qstring.h index c6d97c9d51..5e215625c6 100644 --- a/src/corelib/text/qstring.h +++ b/src/corelib/text/qstring.h @@ -386,6 +386,12 @@ public: QString(QChar c); QString(qsizetype size, QChar c); inline QString(QLatin1String latin1); +#if defined(__cpp_char8_t) || defined(Q_CLANG_QDOC) + Q_WEAK_OVERLOAD + inline QString(const char8_t *str) + : QString(fromUtf8(str)) + {} +#endif inline QString(const QString &) noexcept; inline ~QString(); QString &operator=(QChar c); @@ -747,6 +753,9 @@ public: } #if defined(__cpp_char8_t) || defined(Q_CLANG_QDOC) Q_WEAK_OVERLOAD + static inline QString fromUtf8(const char8_t *str) + { return fromUtf8(reinterpret_cast<const char *>(str)); } + Q_WEAK_OVERLOAD static inline QString fromUtf8(const char8_t *str, qsizetype size) { return fromUtf8(reinterpret_cast<const char *>(str), size); } #endif diff --git a/src/corelib/tools/qhash.cpp b/src/corelib/tools/qhash.cpp index 092985887f..51e2a18961 100644 --- a/src/corelib/tools/qhash.cpp +++ b/src/corelib/tools/qhash.cpp @@ -2578,11 +2578,10 @@ size_t qHash(long double key, size_t seed) noexcept hashes. A multi-valued hash is a hash that allows multiple values with the same key. - Because QMultiHash inherits QHash, all of QHash's functionality also - applies to QMultiHash. For example, you can use isEmpty() to test + QMultiHash mostly mirrors QHash's API. For example, you can use isEmpty() to test whether the hash is empty, and you can traverse a QMultiHash using QHash's iterator classes (for example, QHashIterator). But opposed to - QHash, it provides an insert() function will allow the insertion of + QHash, it provides an insert() function that allows the insertion of multiple items with the same key. The replace() function corresponds to QHash::insert(). It also provides convenient operator+() and operator+=(). diff --git a/src/corelib/tools/qlist.h b/src/corelib/tools/qlist.h index 61a02f795b..6aafb86c01 100644 --- a/src/corelib/tools/qlist.h +++ b/src/corelib/tools/qlist.h @@ -137,10 +137,10 @@ public: // libstdc++ shipped with gcc < 11 does not have a fix for defect LWG 3346 #if __cplusplus >= 202002L && (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 11) using iterator_category = std::contiguous_iterator_tag; + using element_type = value_type; #else using iterator_category = std::random_access_iterator_tag; #endif - using element_type = value_type; using pointer = T *; using reference = T &; @@ -179,10 +179,10 @@ public: // libstdc++ shipped with gcc < 11 does not have a fix for defect LWG 3346 #if __cplusplus >= 202002L && (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 11) using iterator_category = std::contiguous_iterator_tag; + using element_type = const value_type; #else using iterator_category = std::random_access_iterator_tag; #endif - using element_type = const value_type; using pointer = const T *; using reference = const T &; diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp index 675e87b322..b31b880693 100644 --- a/src/gui/text/qtextengine.cpp +++ b/src/gui/text/qtextengine.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtGui module of the Qt Toolkit. @@ -258,7 +258,7 @@ struct QBidiAlgorithm { for (int i = 0; i < length; ++i) { int pos = i; char32_t uc = text[i].unicode(); - if (QChar::isHighSurrogate(uc) && i < length - 1) { + if (QChar::isHighSurrogate(uc) && i < length - 1 && text[i + 1].isLowSurrogate()) { ++i; analysis[i].bidiDirection = QChar::DirNSM; uc = QChar::surrogateToUcs4(ushort(uc), text[i].unicode()); diff --git a/src/plugins/imageformats/jpeg/qjpeghandler.cpp b/src/plugins/imageformats/jpeg/qjpeghandler.cpp index beef18f260..a17afc0f69 100644 --- a/src/plugins/imageformats/jpeg/qjpeghandler.cpp +++ b/src/plugins/imageformats/jpeg/qjpeghandler.cpp @@ -61,13 +61,6 @@ // including jpeglib.h seems to be a little messy extern "C" { -// jpeglib.h->jmorecfg.h tries to typedef int boolean; but this conflicts with -// some Windows headers that may or may not have been included -#ifdef HAVE_BOOLEAN -# undef HAVE_BOOLEAN -#endif -#define boolean jboolean - #define XMD_H // shut JPEGlib up #include <jpeglib.h> #ifdef const diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp index 848226713c..0b05353bf0 100644 --- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp +++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp @@ -95,7 +95,9 @@ private slots: void noFakeDependencies(); void bindablePropertyWithInitialization(); - void markDirty(); + void noDoubleNotification(); + void groupedNotifications(); + void groupedNotificationConsistency(); }; void tst_QProperty::functorBinding() @@ -129,8 +131,8 @@ void tst_QProperty::multipleDependencies() QProperty<int> sum; sum.setBinding([&]() { return firstDependency + secondDependency; }); - QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 0); - QCOMPARE(QPropertyBindingDataPointer::get(secondDependency).observerCount(), 0); + QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 1); + QCOMPARE(QPropertyBindingDataPointer::get(secondDependency).observerCount(), 1); QCOMPARE(sum.value(), int(3)); QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 1); @@ -260,12 +262,12 @@ void tst_QProperty::avoidDependencyAllocationAfterFirstEval() QCOMPARE(propWithBinding.value(), int(11)); - QVERIFY(QPropertyBindingDataPointer::get(propWithBinding).bindingPtr()); - QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).bindingPtr()->dependencyObserverCount, 2u); + QVERIFY(QPropertyBindingDataPointer::get(propWithBinding).binding()); + QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).binding()->dependencyObserverCount, 2u); firstDependency = 100; QCOMPARE(propWithBinding.value(), int(110)); - QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).bindingPtr()->dependencyObserverCount, 2u); + QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).binding()->dependencyObserverCount, 2u); } void tst_QProperty::boolProperty() @@ -484,23 +486,22 @@ class BindingLoopTester : public QObject void tst_QProperty::bindingLoop() { - QScopedPointer<QProperty<int>> firstProp; + QProperty<int> firstProp; QProperty<int> secondProp([&]() -> int { - return firstProp ? firstProp->value() : 0; + return firstProp.value(); }); QProperty<int> thirdProp([&]() -> int { return secondProp.value(); }); - firstProp.reset(new QProperty<int>()); - firstProp->setBinding([&]() -> int { - return secondProp.value(); + firstProp.setBinding([&]() -> int { + return secondProp.value() + thirdProp.value(); }); - QCOMPARE(thirdProp.value(), 0); - QCOMPARE(secondProp.binding().error().type(), QPropertyBindingError::BindingLoop); + thirdProp.setValue(10); + QCOMPARE(firstProp.binding().error().type(), QPropertyBindingError::BindingLoop); { @@ -1200,7 +1201,9 @@ void tst_QProperty::qobjectObservers() MyQObject object; int onValueChangedCalled = 0; { - auto handler = object.bindableFoo().onValueChanged([&onValueChangedCalled]() { ++onValueChangedCalled;}); + auto handler = object.bindableFoo().onValueChanged([&onValueChangedCalled]() { + ++onValueChangedCalled; + }); QCOMPARE(onValueChangedCalled, 0); object.setFoo(10); @@ -1606,80 +1609,103 @@ void tst_QProperty::bindablePropertyWithInitialization() QCOMPARE(tester.prop3().anotherValue, 20); } -class MarkDirtyTester : public QObject +void tst_QProperty::noDoubleNotification() { - Q_OBJECT -public: - Q_PROPERTY(int value1 READ value1 WRITE setValue1 BINDABLE bindableValue1) - Q_PROPERTY(int value2 READ value2 WRITE setValue1 BINDABLE bindableValue2) - Q_PROPERTY(int computed READ computed BINDABLE bindableComputed) - - inline static int staticValue = 0; + /* dependency graph for this test + x --> y means y depends on x + a-->b-->d + \ ^ + \->c--/ + */ + QProperty<int> a(0); + QProperty<int> b; + b.setBinding([&](){ return a.value(); }); + QProperty<int> c; + c.setBinding([&](){ return a.value(); }); + QProperty<int> d; + d.setBinding([&](){ return b.value() + c.value(); }); + int nNotifications = 0; + int expected = 0; + auto connection = d.subscribe([&](){ + ++nNotifications; + QCOMPARE(d.value(), expected); + }); + QCOMPARE(nNotifications, 1); + expected = 2; + a = 1; + QCOMPARE(nNotifications, 2); + expected = 4; + a = 2; + QCOMPARE(nNotifications, 3); +} - int value1() const {return m_value1;} - void setValue1(int val) {m_value1 = val;} - QBindable<int> bindableValue1() {return { &m_value1 };} +void tst_QProperty::groupedNotifications() +{ + QProperty<int> a(0); + QProperty<int> b; + b.setBinding([&](){ return a.value(); }); + QProperty<int> c; + c.setBinding([&](){ return a.value(); }); + QProperty<int> d; + QProperty<int> e; + e.setBinding([&](){ return b.value() + c.value() + d.value(); }); + int nNotifications = 0; + int expected = 0; + auto connection = e.subscribe([&](){ + ++nNotifications; + QCOMPARE(e.value(), expected); + }); + QCOMPARE(nNotifications, 1); + + expected = 2; + Qt::beginPropertyUpdateGroup(); + a = 1; + QCOMPARE(b.value(), 0); + QCOMPARE(c.value(), 0); + QCOMPARE(d.value(), 0); + QCOMPARE(nNotifications, 1); + Qt::endPropertyUpdateGroup(); + QCOMPARE(b.value(), 1); + QCOMPARE(c.value(), 1); + QCOMPARE(e.value(), 2); + QCOMPARE(nNotifications, 2); + + expected = 7; + Qt::beginPropertyUpdateGroup(); + a = 2; + d = 3; + QCOMPARE(b.value(), 1); + QCOMPARE(c.value(), 1); + QCOMPARE(d.value(), 3); + QCOMPARE(nNotifications, 2); + Qt::endPropertyUpdateGroup(); + QCOMPARE(b.value(), 2); + QCOMPARE(c.value(), 2); + QCOMPARE(e.value(), 7); + QCOMPARE(nNotifications, 3); - int value2() const {return m_value2;} - void setValue2(int val) { m_value2.setValue(val); } - QBindable<int> bindableValue2() {return { &m_value2 };} - int computed() const { return staticValue + m_value1; } - QBindable<int> bindableComputed() {return {&m_computed};} +} - void incrementStaticValue() { - ++staticValue; - m_computed.markDirty(); - } +void tst_QProperty::groupedNotificationConsistency() +{ + QProperty<int> i(0); + QProperty<int> j(0); + bool areEqual = true; - void markValue1Dirty() { - m_value1.markDirty(); - } + auto observer = i.onValueChanged([&](){ + areEqual = i == j; + }); - void markValue2Dirty() { - m_value2.markDirty(); - } -private: - Q_OBJECT_BINDABLE_PROPERTY(MarkDirtyTester, int, m_value1, nullptr) - Q_OBJECT_COMPAT_PROPERTY(MarkDirtyTester, int, m_value2, &MarkDirtyTester::setValue2) - Q_OBJECT_COMPUTED_PROPERTY(MarkDirtyTester, int, m_computed, &MarkDirtyTester::computed) -}; + i = 1; + j = 1; + QVERIFY(!areEqual); // value changed runs before j = 1 -void tst_QProperty::markDirty() -{ - { - QProperty<int> testProperty; - int changeCounter = 0; - auto handler = testProperty.onValueChanged([&](){++changeCounter;}); - testProperty.markDirty(); - QCOMPARE(changeCounter, 1); - } - { - MarkDirtyTester dirtyTester; - int computedChangeCounter = 0; - int value1ChangeCounter = 0; - auto handler = dirtyTester.bindableComputed().onValueChanged([&](){ - computedChangeCounter++; - }); - auto handler2 = dirtyTester.bindableValue1().onValueChanged([&](){ - value1ChangeCounter++; - }); - dirtyTester.incrementStaticValue(); - QCOMPARE(computedChangeCounter, 1); - QCOMPARE(dirtyTester.computed(), 1); - dirtyTester.markValue1Dirty(); - QCOMPARE(value1ChangeCounter, 1); - QCOMPARE(computedChangeCounter, 1); - } - { - MarkDirtyTester dirtyTester; - int changeCounter = 0; - auto handler = dirtyTester.bindableValue2().onValueChanged([&](){ - changeCounter++; - }); - dirtyTester.markValue2Dirty(); - QCOMPARE(changeCounter, 1); - } + Qt::beginPropertyUpdateGroup(); + i = 2; + j = 2; + Qt::endPropertyUpdateGroup(); + QVERIFY(areEqual); // value changed runs after everything has been evaluated } QTEST_MAIN(tst_QProperty); diff --git a/util/unicode/unicode.pro b/util/unicode/unicode.pro index 0250c2a43a..a8c941cf8c 100644 --- a/util/unicode/unicode.pro +++ b/util/unicode/unicode.pro @@ -1,3 +1,4 @@ SOURCES += main.cpp QT = core CONFIG += console +DEFINES += QT_FORCE_ASSERTS |