Merge integration refs/builds/qtci/dev/1618585460

This commit is contained in:
Qt CI Bot 2021-04-16 18:14:35 +00:00
commit 5839dee30f
23 changed files with 650 additions and 322 deletions

View File

@ -17,9 +17,10 @@
function(qt_internal_add_module target) function(qt_internal_add_module target)
qt_internal_module_info(module "${target}") qt_internal_module_info(module "${target}")
# TODO: Remove GENERATE_METATYPES once it's not used anymore.
# Process arguments: # Process arguments:
qt_parse_all_arguments(arg "qt_add_module" 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}" "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}) "${__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() endif()
# Generate metatypes # Generate metatypes
if (${arg_GENERATE_METATYPES}) if(${arg_GENERATE_METATYPES})
set(metatypes_install_dir ${INSTALL_LIBDIR}/metatypes) # No mention of NO_GENERATE_METATYPES. You should not use it.
set(args) message(WARNING "GENERATE_METATYPES is on by default for Qt modules. Please remove the manual specification.")
if (NOT QT_WILL_INSTALL) endif()
set(args COPY_OVER_INSTALL INSTALL_DIR "${QT_BUILD_DIR}/${metatypes_install_dir}") if (NOT ${arg_NO_GENERATE_METATYPES})
else() get_target_property(target_type ${target} TYPE)
set(args INSTALL_DIR "${metatypes_install_dir}") 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() endif()
qt6_extract_metatypes(${target} ${args})
endif() endif()
qt_internal_get_min_new_policy_cmake_version(min_new_policy_version) qt_internal_get_min_new_policy_cmake_version(min_new_policy_version)
qt_internal_get_max_new_policy_cmake_version(max_new_policy_version) qt_internal_get_max_new_policy_cmake_version(max_new_policy_version)

View File

@ -0,0 +1,5 @@
file(ARCHIVE_CREATE
OUTPUT cmake_zstd.zstd
PATHS "${CMAKE_CURRENT_LIST_FILE}"
FORMAT raw
COMPRESSION Zstd)

View File

@ -830,6 +830,24 @@ qt_feature("zstd" PRIVATE
LABEL "Zstandard support" LABEL "Zstandard support"
CONDITION ZSTD_FOUND 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 qt_feature("thread" PUBLIC
SECTION "Kernel" SECTION "Kernel"
LABEL "Thread support" LABEL "Thread support"

View File

@ -214,7 +214,7 @@ void SettingsTree::updateChildItems(QTreeWidgetItem *parent)
if (value.userType() == QMetaType::UnknownType) { if (value.userType() == QMetaType::UnknownType) {
child->setText(1, "Invalid"); child->setText(1, "Invalid");
} else { } else {
if (value.type() == QVariant::String) { if (value.typeId() == QMetaType::QString) {
const QString stringValue = value.toString(); const QString stringValue = value.toString();
if (m_typeChecker->boolExp.match(stringValue).hasMatch()) { if (m_typeChecker->boolExp.match(stringValue).hasMatch()) {
value.setValue(stringValue.compare("true", Qt::CaseInsensitive) == 0); value.setValue(stringValue.compare("true", Qt::CaseInsensitive) == 0);

View File

@ -32,6 +32,7 @@ endif()
qt_internal_add_module(Core qt_internal_add_module(Core
QMAKE_MODULE_CONFIG moc resources QMAKE_MODULE_CONFIG moc resources
NO_GENERATE_METATYPES # metatypes are extracted manually below
EXCEPTIONS EXCEPTIONS
SOURCES SOURCES
global/archdetect.cpp global/archdetect.cpp
@ -1186,6 +1187,11 @@ if(QT_FEATURE_mimetype AND QT_FEATURE_mimetype_database)
) )
else() else()
if(QT_FEATURE_zstd) 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) set(qmime_db_compression Zstd)
string(APPEND qmime_db_content "#define MIME_DATABASE_IS_ZSTD\n") string(APPEND qmime_db_content "#define MIME_DATABASE_IS_ZSTD\n")
else() else()

View File

@ -78,7 +78,7 @@
/*! /*!
\externalpage https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html \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 \externalpage https://developer.android.com/training/articles/perf-jni#local-and-global-references
\title JNI tips: 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
*/

View File

@ -50,8 +50,7 @@
The binding expression computes the value by reading other QProperty values. 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 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 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 result is applied to the property. For example:
as dirty and evaluating it only when the property's value is requested. For example:
\code \code
QProperty<QString> firstname("John"); QProperty<QString> firstname("John");
@ -63,20 +62,19 @@
qDebug() << fullname.value(); // Prints "John Smith age: 41" 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 // 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 \endcode
When a new value is assigned to the \c firstname property, the binding 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 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 expression is tries to read the name value of the \c fullname property, the new value is returned.
evaluated again, \c firstname() will be called again and return the new value.
Since bindings are C++ functions, they may do anything that's possible Since bindings are C++ functions, they may do anything that's possible
in C++. This includes calling other functions. If those functions access values in C++. This includes calling other functions. If those functions access values

View File

@ -60,6 +60,8 @@ QT_BEGIN_NAMESPACE
Since \c JNIEnv doesn't do much error checking, such as exception checking and clearing, Since \c JNIEnv doesn't do much error checking, such as exception checking and clearing,
QJniEnvironment allows you to do that easily. 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. \note This API has been designed and tested for use with Android.
It has not been tested for other platforms. 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 In contrast to \l QJniObject, which handles exceptions internally, if you
make JNI calls directly via \c JNIEnv, you need to clear any potential make JNI calls directly via \c JNIEnv, you need to clear any potential
exceptions after the call using this function. For more information about 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. \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 In contrast to \l QJniObject, which handles exceptions internally, if you
make JNI calls directly via \c JNIEnv, you need to clear any potential make JNI calls directly via \c JNIEnv, you need to clear any potential
exceptions after the call using this function. For more information about 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. \return \c true when a pending exception was cleared.
*/ */

View File

@ -160,7 +160,7 @@ QT_BEGIN_NAMESPACE
\l {JNI Design Overview: Global and Local References}. Local references \l {JNI Design Overview: Global and Local References}. Local references
created outside a native method scope must be deleted manually, since created outside a native method scope must be deleted manually, since
the garbage collector will not free them automatically because we are using 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}. \l {JNI tips: Local and global references}.
If you want to keep a Java object alive you need to either create a new global If you want to keep a Java object alive you need to either create a new global

View File

@ -66,16 +66,17 @@ void QPropertyBindingPrivatePtr::reset(QtPrivate::RefCounted *ptr) noexcept
void QPropertyBindingDataPointer::addObserver(QPropertyObserver *observer) void QPropertyBindingDataPointer::addObserver(QPropertyObserver *observer)
{ {
if (auto *binding = bindingPtr()) { if (auto *b = binding()) {
observer->prev = &binding->firstObserver.ptr; observer->prev = &b->firstObserver.ptr;
observer->next = binding->firstObserver.ptr; observer->next = b->firstObserver.ptr;
if (observer->next) if (observer->next)
observer->next->prev = &observer->next; observer->next->prev = &observer->next;
binding->firstObserver.ptr = observer; b->firstObserver.ptr = observer;
} else { } else {
Q_ASSERT(!(ptr->d_ptr & QPropertyBindingData::BindingBit)); auto &d = ptr->d_ref();
auto firstObserver = reinterpret_cast<QPropertyObserver*>(ptr->d_ptr); Q_ASSERT(!(d & QPropertyBindingData::BindingBit));
observer->prev = reinterpret_cast<QPropertyObserver**>(&ptr->d_ptr); auto firstObserver = reinterpret_cast<QPropertyObserver*>(d);
observer->prev = reinterpret_cast<QPropertyObserver**>(&d);
observer->next = firstObserver; observer->next = firstObserver;
if (observer->next) if (observer->next)
observer->next->prev = &observer->next; observer->next->prev = &observer->next;
@ -83,6 +84,182 @@ void QPropertyBindingDataPointer::addObserver(QPropertyObserver *observer)
setFirstObserver(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() QPropertyBindingPrivate::~QPropertyBindingPrivate()
{ {
if (firstObserver) if (firstObserver)
@ -98,44 +275,17 @@ void QPropertyBindingPrivate::unlinkAndDeref()
destroyAndFreeMemory(this); destroyAndFreeMemory(this);
} }
void QPropertyBindingPrivate::markDirtyAndNotifyObservers() void QPropertyBindingPrivate::evaluateRecursive()
{ {
if (eagerlyUpdating) {
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) { if (updating) {
error = QPropertyBindingError(QPropertyBindingError::BindingLoop); error = QPropertyBindingError(QPropertyBindingError::BindingLoop);
if (isQQmlPropertyBinding) if (isQQmlPropertyBinding)
errorCallBack(this); errorCallBack(this);
return false; return;
} }
QScopedValueRollback<bool> updateGuard(updating, true);
/* /*
* Evaluating the binding might lead to the binding being broken. This can * Evaluating the binding might lead to the binding being broken. This can
* cause ref to reach zero at the end of the function. However, the * cause ref to reach zero at the end of the function. However, the
@ -145,23 +295,42 @@ bool QPropertyBindingPrivate::evaluateIfDirtyAndReturnTrueIfValueChanged_helper(
* that the object is still alive when updateGuard's dtor runs. * that the object is still alive when updateGuard's dtor runs.
*/ */
QPropertyBindingPrivatePtr keepAlive {this}; 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; bool changed = false;
Q_ASSERT(propertyDataPtr == data);
QUntypedPropertyData *mutable_data = const_cast<QUntypedPropertyData *>(data);
if (hasBindingWrapper) { if (hasBindingWrapper) {
changed = staticBindingWrapper(metaType, mutable_data, {vtable, reinterpret_cast<std::byte *>(this)+QPropertyBindingPrivate::getSizeEnsuringAlignment()}); changed = staticBindingWrapper(metaType, propertyDataPtr,
{vtable, bindingFunctor});
} else { } 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;
dirty = false; firstObserver.noSelfDependencies(this);
return changed; firstObserver.evaluateBindings();
}
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; QUntypedPropertyBinding::QUntypedPropertyBinding() = default;
@ -232,7 +401,7 @@ QPropertyBindingData::~QPropertyBindingData()
observer.unlink(); observer.unlink();
observer = next; observer = next;
} }
if (auto binding = d.bindingPtr()) if (auto binding = d.binding())
binding->unlinkAndDeref(); binding->unlinkAndDeref();
} }
@ -247,42 +416,38 @@ QUntypedPropertyBinding QPropertyBindingData::setBinding(const QUntypedPropertyB
QPropertyBindingDataPointer d{this}; QPropertyBindingDataPointer d{this};
QPropertyObserverPointer observer; QPropertyObserverPointer observer;
if (auto *existingBinding = d.bindingPtr()) { auto &data = d_ref();
if (auto *existingBinding = d.binding()) {
if (existingBinding == newBinding.data()) if (existingBinding == newBinding.data())
return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(oldBinding.data())); return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(oldBinding.data()));
if (existingBinding->isEagerlyUpdating()) { if (existingBinding->isUpdating()) {
existingBinding->setError({QPropertyBindingError::BindingLoop, QStringLiteral("Binding set during binding evaluation!")}); existingBinding->setError({QPropertyBindingError::BindingLoop, QStringLiteral("Binding set during binding evaluation!")});
return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(oldBinding.data())); return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(oldBinding.data()));
} }
oldBinding = QPropertyBindingPrivatePtr(existingBinding); oldBinding = QPropertyBindingPrivatePtr(existingBinding);
observer = static_cast<QPropertyBindingPrivate *>(oldBinding.data())->takeObservers(); observer = static_cast<QPropertyBindingPrivate *>(oldBinding.data())->takeObservers();
static_cast<QPropertyBindingPrivate *>(oldBinding.data())->unlinkAndDeref(); static_cast<QPropertyBindingPrivate *>(oldBinding.data())->unlinkAndDeref();
d_ptr = 0; data = 0;
} else { } else {
observer = d.firstObserver(); observer = d.firstObserver();
} }
if (newBinding) { if (newBinding) {
newBinding.data()->addRef(); newBinding.data()->addRef();
d_ptr = reinterpret_cast<quintptr>(newBinding.data()); data = reinterpret_cast<quintptr>(newBinding.data());
d_ptr |= BindingBit; data |= BindingBit;
auto newBindingRaw = static_cast<QPropertyBindingPrivate *>(newBinding.data()); auto newBindingRaw = static_cast<QPropertyBindingPrivate *>(newBinding.data());
newBindingRaw->setDirty(true);
newBindingRaw->setProperty(propertyDataPtr); newBindingRaw->setProperty(propertyDataPtr);
if (observer) if (observer)
newBindingRaw->prependObserver(observer); newBindingRaw->prependObserver(observer);
newBindingRaw->setStaticObserver(staticObserverCallback, guardCallback); newBindingRaw->setStaticObserver(staticObserverCallback, guardCallback);
if (newBindingRaw->requiresEagerEvaluation()) {
newBindingRaw->setEagerlyUpdating(true); newBindingRaw->evaluateRecursive();
auto changed = newBindingRaw->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr); newBindingRaw->notifyRecursive();
if (changed)
observer.notify(newBindingRaw, propertyDataPtr, /*knownToHaveChanged=*/true);
newBindingRaw->setEagerlyUpdating(false);
}
} else if (observer) { } else if (observer) {
d.setObservers(observer.ptr); d.setObservers(observer.ptr);
} else { } else {
d_ptr &= ~QPropertyBindingData::BindingBit; data = 0;
} }
if (oldBinding) if (oldBinding)
@ -293,8 +458,7 @@ QUntypedPropertyBinding QPropertyBindingData::setBinding(const QUntypedPropertyB
QPropertyBindingData::QPropertyBindingData(QPropertyBindingData &&other) : d_ptr(std::exchange(other.d_ptr, 0)) QPropertyBindingData::QPropertyBindingData(QPropertyBindingData &&other) : d_ptr(std::exchange(other.d_ptr, 0))
{ {
QPropertyBindingDataPointer d{this}; QPropertyBindingDataPointer::fixupAfterMove(this);
d.fixupFirstObserverAfterMove();
} }
static thread_local QBindingStatus bindingStatus; static thread_local QBindingStatus bindingStatus;
@ -333,24 +497,20 @@ QPropertyBindingPrivate *QPropertyBindingPrivate::currentlyEvaluatingBinding()
return currentState ? currentState->binding : nullptr; 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() void QPropertyBindingData::removeBinding_helper()
{ {
QPropertyBindingDataPointer d{this}; QPropertyBindingDataPointer d{this};
auto *existingBinding = d.bindingPtr(); auto *existingBinding = d.binding();
Q_ASSERT(existingBinding); Q_ASSERT(existingBinding);
auto observer = existingBinding->takeObservers(); auto observer = existingBinding->takeObservers();
d_ptr = 0; d_ref() = 0;
if (observer) if (observer)
d.setObservers(observer.ptr); d.setObservers(observer.ptr);
existingBinding->unlinkAndDeref(); existingBinding->unlinkAndDeref();
@ -370,22 +530,25 @@ void QPropertyBindingData::registerWithCurrentlyEvaluatingBinding_helper(Binding
QPropertyBindingDataPointer d{this}; QPropertyBindingDataPointer d{this};
QPropertyObserverPointer dependencyObserver = currentState->binding->allocateDependencyObserver(); QPropertyObserverPointer dependencyObserver = currentState->binding->allocateDependencyObserver();
dependencyObserver.setBindingToMarkDirty(currentState->binding); dependencyObserver.setBindingToNotify(currentState->binding);
dependencyObserver.observeProperty(d); dependencyObserver.observeProperty(d);
} }
void QPropertyBindingData::notifyObservers(QUntypedPropertyData *propertyDataPtr) const void QPropertyBindingData::notifyObservers(QUntypedPropertyData *propertyDataPtr) const
{ {
if (isNotificationDelayed())
return;
QPropertyBindingDataPointer d{this}; QPropertyBindingDataPointer d{this};
if (QPropertyObserverPointer observer = d.firstObserver()) QPropertyObserverPointer observer = d.firstObserver();
observer.notify(d.bindingPtr(), propertyDataPtr); if (!observer)
} return;
auto *delay = groupUpdateData;
void QPropertyBindingData::markDirty() if (delay) {
{ delay->addProperty(this, propertyDataPtr);
QPropertyBindingDataPointer d{this}; return;
if (auto *binding = d.bindingPtr()) }
binding->setDirty(true); observer.evaluateBindings();
observer.notify(propertyDataPtr);
} }
int QPropertyBindingDataPointer::observerCount() const int QPropertyBindingDataPointer::observerCount() const
@ -425,7 +588,7 @@ QPropertyObserver::~QPropertyObserver()
QPropertyObserver::QPropertyObserver(QPropertyObserver &&other) noexcept QPropertyObserver::QPropertyObserver(QPropertyObserver &&other) noexcept
{ {
bindingToMarkDirty = std::exchange(other.bindingToMarkDirty, {}); binding = std::exchange(other.binding, {});
next = std::exchange(other.next, {}); next = std::exchange(other.next, {});
prev = std::exchange(other.prev, {}); prev = std::exchange(other.prev, {});
if (next) if (next)
@ -441,9 +604,9 @@ QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) noexc
QPropertyObserverPointer d{this}; QPropertyObserverPointer d{this};
d.unlink(); d.unlink();
bindingToMarkDirty = nullptr; binding = nullptr;
bindingToMarkDirty = std::exchange(other.bindingToMarkDirty, {}); binding = std::exchange(other.binding, {});
next = std::exchange(other.next, {}); next = std::exchange(other.next, {});
prev = std::exchange(other.prev, {}); prev = std::exchange(other.prev, {});
if (next) if (next)
@ -480,10 +643,10 @@ void QPropertyObserverPointer::setAliasedProperty(QUntypedPropertyData *property
ptr->next.setTag(QPropertyObserver::ObserverNotifiesAlias); ptr->next.setTag(QPropertyObserver::ObserverNotifiesAlias);
} }
void QPropertyObserverPointer::setBindingToMarkDirty(QPropertyBindingPrivate *binding) void QPropertyObserverPointer::setBindingToNotify(QPropertyBindingPrivate *binding)
{ {
Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder); Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder);
ptr->bindingToMarkDirty = binding; ptr->binding = binding;
ptr->next.setTag(QPropertyObserver::ObserverNotifiesBinding); ptr->next.setTag(QPropertyObserver::ObserverNotifiesBinding);
} }
@ -516,14 +679,8 @@ struct [[nodiscard]] QPropertyObserverNodeProtector {
/*! \internal /*! \internal
\a propertyDataPtr is a pointer to the observed property's property data \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); auto observer = const_cast<QPropertyObserver*>(ptr);
/* /*
@ -557,22 +714,17 @@ void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding
observer = next->next.data(); observer = next->next.data();
continue; continue;
} }
// both evaluateIfDirtyAndReturnTrueIfValueChanged and handlerToCall might modify the list // handlerToCall might modify the list
QPropertyObserverNodeProtector protector(observer); QPropertyObserverNodeProtector protector(observer);
if (!knownToHaveChanged && triggeringBinding) {
if (!triggeringBinding->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr))
return;
knownToHaveChanged = true;
}
handlerToCall(observer, propertyDataPtr); handlerToCall(observer, propertyDataPtr);
next = protector.next(); next = protector.next();
break; break;
} }
case QPropertyObserver::ObserverNotifiesBinding: case QPropertyObserver::ObserverNotifiesBinding:
{ {
auto bindingToMarkDirty = observer->bindingToMarkDirty; auto bindingToNotify = observer->binding;
QPropertyObserverNodeProtector protector(observer); QPropertyObserverNodeProtector protector(observer);
bindingToMarkDirty->markDirtyAndNotifyObservers(); bindingToNotify->notifyRecursive();
next = protector.next(); next = protector.next();
break; 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) void QPropertyObserverPointer::observeProperty(QPropertyBindingDataPointer property)
{ {
if (ptr->prev) if (ptr->prev)
@ -1873,18 +2058,26 @@ QBindingStorage::~QBindingStorage()
QBindingStoragePrivate(d).destroy(); QBindingStoragePrivate(d).destroy();
} }
// ### Unused, retained for BC with 6.0
void QBindingStorage::maybeUpdateBindingAndRegister_helper(const QUntypedPropertyData *data) const void QBindingStorage::maybeUpdateBindingAndRegister_helper(const QUntypedPropertyData *data) const
{
registerDependency_helper(data);
}
void QBindingStorage::registerDependency_helper(const QUntypedPropertyData *data) const
{ {
Q_ASSERT(bindingStatus); 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); 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) if (!storage)
return; return;
if (auto *binding = storage->binding()) storage->registerWithCurrentlyEvaluatingBinding(currentBinding);
binding->evaluateIfDirtyAndReturnTrueIfValueChanged(const_cast<QUntypedPropertyData *>(data), bindingStatus);
storage->registerWithCurrentlyEvaluatingBinding(bindingStatus->currentlyEvaluatingBinding);
} }
QPropertyBindingData *QBindingStorage::bindingData_helper(const QUntypedPropertyData *data) const QPropertyBindingData *QBindingStorage::bindingData_helper(const QUntypedPropertyData *data) const
{ {
return QBindingStoragePrivate(d).get(data); return QBindingStoragePrivate(d).get(data);
@ -1921,6 +2114,13 @@ bool isAnyBindingEvaluating()
{ {
return bindingStatus.currentlyEvaluatingBinding != nullptr; return bindingStatus.currentlyEvaluatingBinding != nullptr;
} }
bool isPropertyInBindingWrapper(const QUntypedPropertyData *property)
{
return bindingStatus.currentCompatProperty &&
bindingStatus.currentCompatProperty->property == property;
}
} // namespace QtPrivate end } // namespace QtPrivate end
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -63,6 +63,11 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
namespace Qt {
Q_CORE_EXPORT void beginPropertyUpdateGroup();
Q_CORE_EXPORT void endPropertyUpdateGroup();
}
template <typename T> template <typename T>
class QPropertyData : public QUntypedPropertyData class QPropertyData : public QUntypedPropertyData
{ {
@ -217,6 +222,7 @@ protected:
using ChangeHandler = void (*)(QPropertyObserver*, QUntypedPropertyData *); using ChangeHandler = void (*)(QPropertyObserver*, QUntypedPropertyData *);
private: private:
friend struct QPropertyDelayedNotifications;
friend struct QPropertyObserverNodeProtector; friend struct QPropertyObserverNodeProtector;
friend class QPropertyObserver; friend class QPropertyObserver;
friend struct QPropertyObserverPointer; friend struct QPropertyObserverPointer;
@ -229,7 +235,7 @@ private:
QtPrivate::QTagPreservingPointerToPointer<QPropertyObserver, ObserverTag> prev; QtPrivate::QTagPreservingPointerToPointer<QPropertyObserver, ObserverTag> prev;
union { union {
QPropertyBindingPrivate *bindingToMarkDirty = nullptr; QPropertyBindingPrivate *binding = nullptr;
ChangeHandler changeHandler; ChangeHandler changeHandler;
QUntypedPropertyData *aliasedPropertyData; QUntypedPropertyData *aliasedPropertyData;
}; };
@ -329,8 +335,6 @@ public:
parameter_type value() const parameter_type value() const
{ {
if (d.hasBinding())
d.evaluateIfDirty(this);
d.registerWithCurrentlyEvaluatingBinding(); d.registerWithCurrentlyEvaluatingBinding();
return this->val; return this->val;
} }
@ -389,9 +393,7 @@ public:
QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding) QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding)
{ {
QPropertyBinding<T> oldBinding(d.setBinding(newBinding, this)); return QPropertyBinding<T>(d.setBinding(newBinding, this));
notify();
return oldBinding;
} }
bool setBinding(const QUntypedPropertyBinding &newBinding) bool setBinding(const QUntypedPropertyBinding &newBinding)
@ -402,11 +404,6 @@ public:
return true; return true;
} }
void markDirty() {
d.markDirty();
notify();
}
#ifndef Q_CLANG_QDOC #ifndef Q_CLANG_QDOC
template <typename Functor> template <typename Functor>
QPropertyBinding<T> setBinding(Functor &&f, QPropertyBinding<T> setBinding(Functor &&f,
@ -852,11 +849,11 @@ public:
bool isEmpty() { return !d; } bool isEmpty() { return !d; }
void maybeUpdateBindingAndRegister(const QUntypedPropertyData *data) const void registerDependency(const QUntypedPropertyData *data) const
{ {
if (!d && !bindingStatus->currentlyEvaluatingBinding) if (!bindingStatus->currentlyEvaluatingBinding)
return; return;
maybeUpdateBindingAndRegister_helper(data); registerDependency_helper(data);
} }
QtPrivate::QPropertyBindingData *bindingData(const QUntypedPropertyData *data) const QtPrivate::QPropertyBindingData *bindingData(const QUntypedPropertyData *data) const
{ {
@ -864,6 +861,9 @@ public:
return nullptr; return nullptr;
return bindingData_helper(data); 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) QtPrivate::QPropertyBindingData *bindingData(QUntypedPropertyData *data, bool create)
{ {
if (!d && !create) if (!d && !create)
@ -871,6 +871,8 @@ public:
return bindingData_helper(data, create); return bindingData_helper(data, create);
} }
private: private:
void registerDependency_helper(const QUntypedPropertyData *data) const;
// ### Unused, but keep for BC
void maybeUpdateBindingAndRegister_helper(const QUntypedPropertyData *data) const; void maybeUpdateBindingAndRegister_helper(const QUntypedPropertyData *data) const;
QtPrivate::QPropertyBindingData *bindingData_helper(const QUntypedPropertyData *data) const; QtPrivate::QPropertyBindingData *bindingData_helper(const QUntypedPropertyData *data) const;
QtPrivate::QPropertyBindingData *bindingData_helper(QUntypedPropertyData *data, bool create); QtPrivate::QPropertyBindingData *bindingData_helper(QUntypedPropertyData *data, bool create);
@ -923,7 +925,7 @@ public:
parameter_type value() const parameter_type value() const
{ {
qGetBindingStorage(owner())->maybeUpdateBindingAndRegister(this); qGetBindingStorage(owner())->registerDependency(this);
return this->val; return this->val;
} }
@ -987,7 +989,6 @@ public:
{ {
QtPrivate::QPropertyBindingData *bd = qGetBindingStorage(owner())->bindingData(this, true); QtPrivate::QPropertyBindingData *bd = qGetBindingStorage(owner())->bindingData(this, true);
QUntypedPropertyBinding oldBinding(bd->setBinding(newBinding, this, HasSignal ? &signalCallBack : nullptr)); QUntypedPropertyBinding oldBinding(bd->setBinding(newBinding, this, HasSignal ? &signalCallBack : nullptr));
notify(bd);
return static_cast<QPropertyBinding<T> &>(oldBinding); return static_cast<QPropertyBinding<T> &>(oldBinding);
} }
@ -1018,15 +1019,6 @@ public:
return bd && bd->binding() != nullptr; 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 QPropertyBinding<T> binding() const
{ {
auto *bd = qGetBindingStorage(owner())->bindingData(this); auto *bd = qGetBindingStorage(owner())->bindingData(this);
@ -1130,7 +1122,7 @@ public:
parameter_type value() const parameter_type value() const
{ {
qGetBindingStorage(owner())->maybeUpdateBindingAndRegister(this); qGetBindingStorage(owner())->registerDependency(this);
return (owner()->*Getter)(); return (owner()->*Getter)();
} }
@ -1176,15 +1168,13 @@ public:
return *storage->bindingData(const_cast<QObjectComputedProperty *>(this), true); 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 // computed property can't store a binding, so there's nothing to mark
auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner())); auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner()));
auto bd = storage->bindingData(const_cast<QObjectComputedProperty *>(this), false); auto bd = storage->bindingData(const_cast<QObjectComputedProperty *>(this), false);
if (bd) if (bd)
bindingData().notifyObservers(this); bd->notifyObservers(this);
} }
private:
}; };
#define Q_OBJECT_COMPUTED_PROPERTY(Class, Type, name, ...) \ #define Q_OBJECT_COMPUTED_PROPERTY(Class, Type, name, ...) \

View File

@ -71,19 +71,18 @@ struct Q_AUTOTEST_EXPORT QPropertyBindingDataPointer
{ {
const QtPrivate::QPropertyBindingData *ptr = nullptr; const QtPrivate::QPropertyBindingData *ptr = nullptr;
QPropertyBindingPrivate *bindingPtr() const QPropertyBindingPrivate *binding() const
{ {
if (ptr->d_ptr & QtPrivate::QPropertyBindingData::BindingBit) return ptr->binding();
return reinterpret_cast<QPropertyBindingPrivate*>(ptr->d_ptr - QtPrivate::QPropertyBindingData::BindingBit);
return nullptr;
} }
void setObservers(QPropertyObserver *observer) void setObservers(QPropertyObserver *observer)
{ {
observer->prev = reinterpret_cast<QPropertyObserver**>(&(ptr->d_ptr)); auto &d = ptr->d_ref();
ptr->d_ptr = reinterpret_cast<quintptr>(observer); 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 addObserver(QPropertyObserver *observer);
void setFirstObserver(QPropertyObserver *observer); void setFirstObserver(QPropertyObserver *observer);
QPropertyObserverPointer firstObserver() const; QPropertyObserverPointer firstObserver() const;
@ -104,11 +103,17 @@ struct QPropertyObserverPointer
void unlink(); void unlink();
void setBindingToMarkDirty(QPropertyBindingPrivate *binding); void setBindingToNotify(QPropertyBindingPrivate *binding);
void setChangeHandler(QPropertyObserver::ChangeHandler changeHandler); void setChangeHandler(QPropertyObserver::ChangeHandler changeHandler);
void setAliasedProperty(QUntypedPropertyData *propertyPtr); 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); void observeProperty(QPropertyBindingDataPointer property);
explicit operator bool() const { return ptr != nullptr; } explicit operator bool() const { return ptr != nullptr; }
@ -172,14 +177,12 @@ private:
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 // used to detect binding loops for lazy evaluated properties
bool updating = false; bool updating = false;
bool hasStaticObserver = false; bool hasStaticObserver = false;
bool pendingNotify = false;
bool hasBindingWrapper:1; bool hasBindingWrapper:1;
// used to detect binding loops for eagerly evaluated properties // used to detect binding loops for eagerly evaluated properties
bool eagerlyUpdating:1;
bool isQQmlPropertyBinding:1; bool isQQmlPropertyBinding:1;
const QtPrivate::BindingFunctionVTable *vtable; const QtPrivate::BindingFunctionVTable *vtable;
@ -230,13 +233,11 @@ public:
// public because the auto-tests access it, too. // public because the auto-tests access it, too.
size_t dependencyObserverCount = 0; size_t dependencyObserverCount = 0;
bool isEagerlyUpdating() {return eagerlyUpdating;} bool isUpdating() {return updating;}
void setEagerlyUpdating(bool b) {eagerlyUpdating = b;}
QPropertyBindingPrivate(QMetaType metaType, const QtPrivate::BindingFunctionVTable *vtable, QPropertyBindingPrivate(QMetaType metaType, const QtPrivate::BindingFunctionVTable *vtable,
const QPropertyBindingSourceLocation &location, bool isQQmlPropertyBinding=false) const QPropertyBindingSourceLocation &location, bool isQQmlPropertyBinding=false)
: hasBindingWrapper(false) : hasBindingWrapper(false)
, eagerlyUpdating(false)
, isQQmlPropertyBinding(isQQmlPropertyBinding) , isQQmlPropertyBinding(isQQmlPropertyBinding)
, vtable(vtable) , vtable(vtable)
, location(location) , location(location)
@ -245,7 +246,6 @@ public:
~QPropertyBindingPrivate(); ~QPropertyBindingPrivate();
void setDirty(bool d) { dirty = d; }
void setProperty(QUntypedPropertyData *propertyPtr) { propertyDataPtr = propertyPtr; } void setProperty(QUntypedPropertyData *propertyPtr) { propertyDataPtr = propertyPtr; }
void setStaticObserver(QtPrivate::QPropertyObserverCallback callback, QtPrivate::QPropertyBindingWrapper bindingWrapper) void setStaticObserver(QtPrivate::QPropertyObserverCallback callback, QtPrivate::QPropertyBindingWrapper bindingWrapper)
{ {
@ -312,13 +312,8 @@ public:
void unlinkAndDeref(); void unlinkAndDeref();
void markDirtyAndNotifyObservers(); void evaluateRecursive();
bool evaluateIfDirtyAndReturnTrueIfValueChanged(const QUntypedPropertyData *data, QBindingStatus *status = nullptr) void notifyRecursive();
{
if (!dirty)
return false;
return evaluateIfDirtyAndReturnTrueIfValueChanged_helper(data, status);
}
static QPropertyBindingPrivate *get(const QUntypedPropertyBinding &binding) static QPropertyBindingPrivate *get(const QUntypedPropertyBinding &binding)
{ return static_cast<QPropertyBindingPrivate *>(binding.d.data()); } { return static_cast<QPropertyBindingPrivate *>(binding.d.data()); }
@ -334,8 +329,6 @@ public:
clearDependencyObservers(); clearDependencyObservers();
} }
bool requiresEagerEvaluation() const { return hasBindingWrapper; }
static QPropertyBindingPrivate *currentlyEvaluatingBinding(); static QPropertyBindingPrivate *currentlyEvaluatingBinding();
bool hasCustomVTable() const bool hasCustomVTable() const
@ -353,36 +346,44 @@ public:
delete[] reinterpret_cast<std::byte *>(priv); delete[] reinterpret_cast<std::byte *>(priv);
} }
} }
private:
bool evaluateIfDirtyAndReturnTrueIfValueChanged_helper(const QUntypedPropertyData *data, QBindingStatus *status = nullptr);
}; };
inline void QPropertyBindingDataPointer::setFirstObserver(QPropertyObserver *observer) inline void QPropertyBindingDataPointer::setFirstObserver(QPropertyObserver *observer)
{ {
if (auto *binding = bindingPtr()) { if (auto *b = binding()) {
binding->firstObserver.ptr = observer; b->firstObserver.ptr = observer;
return; 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 // 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 // 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 return; // nothing to do if the observer is stored in the binding
if (auto observer = firstObserver()) if (auto observer = reinterpret_cast<QPropertyObserver *>(d))
observer.ptr->prev = reinterpret_cast<QPropertyObserver **>(&(ptr->d_ptr)); observer->prev = reinterpret_cast<QPropertyObserver **>(&d);
} }
inline QPropertyObserverPointer QPropertyBindingDataPointer::firstObserver() const inline QPropertyObserverPointer QPropertyBindingDataPointer::firstObserver() const
{ {
if (auto *binding = bindingPtr()) if (auto *b = binding())
return binding->firstObserver; return b->firstObserver;
return { reinterpret_cast<QPropertyObserver *>(ptr->d_ptr) }; 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> 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()); (thisData->owner()->*Setter)(copy.valueBypassingBindings());
return true; return true;
} }
inline bool inBindingWrapper(const QBindingStorage *storage) const bool inBindingWrapper(const QBindingStorage *storage) const
{ {
return storage->bindingStatus->currentCompatProperty && return storage->bindingStatus->currentCompatProperty
storage->bindingStatus->currentCompatProperty->property == this; && QtPrivate::isPropertyInBindingWrapper(this);
} }
public: public:
@ -434,7 +435,7 @@ public:
const QBindingStorage *storage = qGetBindingStorage(owner()); const QBindingStorage *storage = qGetBindingStorage(owner());
// make sure we don't register this binding as a dependency to itself // make sure we don't register this binding as a dependency to itself
if (!inBindingWrapper(storage)) if (!inBindingWrapper(storage))
storage->maybeUpdateBindingAndRegister(this); storage->registerDependency(this);
return this->val; return this->val;
} }
@ -511,15 +512,6 @@ public:
return bd && bd->binding() != nullptr; 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() void removeBindingUnlessInWrapper()
{ {
QBindingStorage *storage = qGetBindingStorage(owner()); QBindingStorage *storage = qGetBindingStorage(owner());
@ -576,6 +568,7 @@ public:
auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner())); auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner()));
return *storage->bindingData(const_cast<QObjectCompatProperty *>(this), true); return *storage->bindingData(const_cast<QObjectCompatProperty *>(this), true);
} }
private: private:
void notify(const QtPrivate::QPropertyBindingData *binding) void notify(const QtPrivate::QPropertyBindingData *binding)
{ {

View File

@ -143,7 +143,6 @@ private:
QtPrivate::RefCounted *d; QtPrivate::RefCounted *d;
}; };
class QUntypedPropertyBinding; class QUntypedPropertyBinding;
class QPropertyBindingPrivate; class QPropertyBindingPrivate;
struct QPropertyBindingDataPointer; struct QPropertyBindingDataPointer;
@ -158,6 +157,24 @@ public:
template <typename T> template <typename T>
class QPropertyData; 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 { namespace QtPrivate {
struct BindingEvaluationState; struct BindingEvaluationState;
@ -218,6 +235,16 @@ struct QPropertyBindingFunction {
using QPropertyObserverCallback = void (*)(QUntypedPropertyData *); using QPropertyObserverCallback = void (*)(QUntypedPropertyData *);
using QPropertyBindingWrapper = bool(*)(QMetaType, QUntypedPropertyData *dataPtr, QPropertyBindingFunction); 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 class Q_CORE_EXPORT QPropertyBindingData
{ {
// Mutable because the address of the observer of the currently evaluating binding is stored here, for // 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; mutable quintptr d_ptr = 0;
friend struct QT_PREPEND_NAMESPACE(QPropertyBindingDataPointer); friend struct QT_PREPEND_NAMESPACE(QPropertyBindingDataPointer);
friend class QT_PREPEND_NAMESPACE(QQmlPropertyBinding); friend class QT_PREPEND_NAMESPACE(QQmlPropertyBinding);
friend struct QT_PREPEND_NAMESPACE(QPropertyDelayedNotifications);
Q_DISABLE_COPY(QPropertyBindingData) Q_DISABLE_COPY(QPropertyBindingData)
public: public:
QPropertyBindingData() = default; QPropertyBindingData() = default;
@ -232,9 +260,13 @@ public:
QPropertyBindingData &operator=(QPropertyBindingData &&other) = delete; QPropertyBindingData &operator=(QPropertyBindingData &&other) = delete;
~QPropertyBindingData(); ~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 hasBinding() const { return d_ptr & BindingBit; }
bool isNotificationDelayed() const { return d_ptr & DelayedNotificationBit; }
QUntypedPropertyBinding setBinding(const QUntypedPropertyBinding &newBinding, QUntypedPropertyBinding setBinding(const QUntypedPropertyBinding &newBinding,
QUntypedPropertyData *propertyDataPtr, QUntypedPropertyData *propertyDataPtr,
@ -243,14 +275,14 @@ public:
QPropertyBindingPrivate *binding() const QPropertyBindingPrivate *binding() const
{ {
if (d_ptr & BindingBit) quintptr dd = d();
return reinterpret_cast<QPropertyBindingPrivate*>(d_ptr - BindingBit); if (dd & BindingBit)
return reinterpret_cast<QPropertyBindingPrivate*>(dd - BindingBit);
return nullptr; return nullptr;
} }
void evaluateIfDirty(const QUntypedPropertyData *property) const; void evaluateIfDirty(const QUntypedPropertyData *) const; // ### Kept for BC reasons, unused
void markDirty();
void removeBinding() void removeBinding()
{ {
@ -267,6 +299,25 @@ public:
void registerWithCurrentlyEvaluatingBinding() const; void registerWithCurrentlyEvaluatingBinding() const;
void notifyObservers(QUntypedPropertyData *propertyDataPtr) const; void notifyObservers(QUntypedPropertyData *propertyDataPtr) const;
private: 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 registerWithCurrentlyEvaluatingBinding_helper(BindingEvaluationState *currentBinding) const;
void removeBinding_helper(); void removeBinding_helper();
}; };

View File

@ -241,7 +241,7 @@ void QTimer::start()
stop(); stop();
d->nulltimer = (!d->inter && d->single); d->nulltimer = (!d->inter && d->single);
d->id = QObject::startTimer(d->inter, d->type); 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) { if (d->id != INV_TIMER) {
QObject::killTimer(d->id); QObject::killTimer(d->id);
d->id = INV_TIMER; 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, // No need to call markDirty() for d->isActiveData here,
// as timer state actually does not change // as timer state actually does not change
} }
if (intervalChanged) if (intervalChanged)
d->inter.markDirty(); d->inter.notify();
} }
int QTimer::interval() const int QTimer::interval() const

View File

@ -333,7 +333,7 @@ class Q_CORE_EXPORT QVariant
explicit QVariant(Type type) explicit QVariant(Type type)
: QVariant(QMetaType(int(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 Type type() const
{ {
int type = d.typeId(); int type = d.typeId();

View File

@ -2190,6 +2190,16 @@ inline char qToLower(char ch)
\sa fromLatin1(), fromLocal8Bit(), fromUtf8() \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) /*! \fn QString QString::fromStdString(const std::string &str)
Returns a copy of the \a str string. The given string is converted Returns a copy of the \a str string. The given string is converted
@ -5324,6 +5334,14 @@ QString QString::fromLocal8Bit(QByteArrayView ba)
\sa toUtf8(), fromLatin1(), fromLocal8Bit() \sa toUtf8(), fromLatin1(), fromLocal8Bit()
*/ */
/*!
\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) \fn QString QString::fromUtf8(const char8_t *str, qsizetype size)
\overload \overload

View File

@ -386,6 +386,12 @@ public:
QString(QChar c); QString(QChar c);
QString(qsizetype size, QChar c); QString(qsizetype size, QChar c);
inline QString(QLatin1String latin1); 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(const QString &) noexcept;
inline ~QString(); inline ~QString();
QString &operator=(QChar c); QString &operator=(QChar c);
@ -746,6 +752,9 @@ public:
return fromUtf8(QByteArrayView(utf8, !utf8 || size < 0 ? qstrlen(utf8) : size)); return fromUtf8(QByteArrayView(utf8, !utf8 || size < 0 ? qstrlen(utf8) : size));
} }
#if defined(__cpp_char8_t) || defined(Q_CLANG_QDOC) #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 Q_WEAK_OVERLOAD
static inline QString fromUtf8(const char8_t *str, qsizetype size) static inline QString fromUtf8(const char8_t *str, qsizetype size)
{ return fromUtf8(reinterpret_cast<const char *>(str), size); } { return fromUtf8(reinterpret_cast<const char *>(str), size); }

View File

@ -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 hashes. A multi-valued hash is a hash that allows multiple values
with the same key. with the same key.
Because QMultiHash inherits QHash, all of QHash's functionality also QMultiHash mostly mirrors QHash's API. For example, you can use isEmpty() to test
applies to QMultiHash. For example, you can use isEmpty() to test
whether the hash is empty, and you can traverse a QMultiHash using whether the hash is empty, and you can traverse a QMultiHash using
QHash's iterator classes (for example, QHashIterator). But opposed to 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 multiple items with the same key. The replace() function corresponds to
QHash::insert(). It also provides convenient operator+() and QHash::insert(). It also provides convenient operator+() and
operator+=(). operator+=().

View File

@ -137,10 +137,10 @@ public:
// libstdc++ shipped with gcc < 11 does not have a fix for defect LWG 3346 // libstdc++ shipped with gcc < 11 does not have a fix for defect LWG 3346
#if __cplusplus >= 202002L && (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 11) #if __cplusplus >= 202002L && (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 11)
using iterator_category = std::contiguous_iterator_tag; using iterator_category = std::contiguous_iterator_tag;
using element_type = value_type;
#else #else
using iterator_category = std::random_access_iterator_tag; using iterator_category = std::random_access_iterator_tag;
#endif #endif
using element_type = value_type;
using pointer = T *; using pointer = T *;
using reference = T &; using reference = T &;
@ -179,10 +179,10 @@ public:
// libstdc++ shipped with gcc < 11 does not have a fix for defect LWG 3346 // libstdc++ shipped with gcc < 11 does not have a fix for defect LWG 3346
#if __cplusplus >= 202002L && (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 11) #if __cplusplus >= 202002L && (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 11)
using iterator_category = std::contiguous_iterator_tag; using iterator_category = std::contiguous_iterator_tag;
using element_type = const value_type;
#else #else
using iterator_category = std::random_access_iterator_tag; using iterator_category = std::random_access_iterator_tag;
#endif #endif
using element_type = const value_type;
using pointer = const T *; using pointer = const T *;
using reference = const T &; using reference = const T &;

View File

@ -1,6 +1,6 @@
/**************************************************************************** /****************************************************************************
** **
** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of the QtGui module of the Qt Toolkit. ** 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) { for (int i = 0; i < length; ++i) {
int pos = i; int pos = i;
char32_t uc = text[i].unicode(); 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; ++i;
analysis[i].bidiDirection = QChar::DirNSM; analysis[i].bidiDirection = QChar::DirNSM;
uc = QChar::surrogateToUcs4(ushort(uc), text[i].unicode()); uc = QChar::surrogateToUcs4(ushort(uc), text[i].unicode());

View File

@ -61,13 +61,6 @@
// including jpeglib.h seems to be a little messy // including jpeglib.h seems to be a little messy
extern "C" { 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 #define XMD_H // shut JPEGlib up
#include <jpeglib.h> #include <jpeglib.h>
#ifdef const #ifdef const

View File

@ -95,7 +95,9 @@ private slots:
void noFakeDependencies(); void noFakeDependencies();
void bindablePropertyWithInitialization(); void bindablePropertyWithInitialization();
void markDirty(); void noDoubleNotification();
void groupedNotifications();
void groupedNotificationConsistency();
}; };
void tst_QProperty::functorBinding() void tst_QProperty::functorBinding()
@ -129,8 +131,8 @@ void tst_QProperty::multipleDependencies()
QProperty<int> sum; QProperty<int> sum;
sum.setBinding([&]() { return firstDependency + secondDependency; }); sum.setBinding([&]() { return firstDependency + secondDependency; });
QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 0); QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 1);
QCOMPARE(QPropertyBindingDataPointer::get(secondDependency).observerCount(), 0); QCOMPARE(QPropertyBindingDataPointer::get(secondDependency).observerCount(), 1);
QCOMPARE(sum.value(), int(3)); QCOMPARE(sum.value(), int(3));
QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 1); QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 1);
@ -260,12 +262,12 @@ void tst_QProperty::avoidDependencyAllocationAfterFirstEval()
QCOMPARE(propWithBinding.value(), int(11)); QCOMPARE(propWithBinding.value(), int(11));
QVERIFY(QPropertyBindingDataPointer::get(propWithBinding).bindingPtr()); QVERIFY(QPropertyBindingDataPointer::get(propWithBinding).binding());
QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).bindingPtr()->dependencyObserverCount, 2u); QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).binding()->dependencyObserverCount, 2u);
firstDependency = 100; firstDependency = 100;
QCOMPARE(propWithBinding.value(), int(110)); QCOMPARE(propWithBinding.value(), int(110));
QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).bindingPtr()->dependencyObserverCount, 2u); QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).binding()->dependencyObserverCount, 2u);
} }
void tst_QProperty::boolProperty() void tst_QProperty::boolProperty()
@ -484,23 +486,22 @@ class BindingLoopTester : public QObject
void tst_QProperty::bindingLoop() void tst_QProperty::bindingLoop()
{ {
QScopedPointer<QProperty<int>> firstProp; QProperty<int> firstProp;
QProperty<int> secondProp([&]() -> int { QProperty<int> secondProp([&]() -> int {
return firstProp ? firstProp->value() : 0; return firstProp.value();
}); });
QProperty<int> thirdProp([&]() -> int { QProperty<int> thirdProp([&]() -> int {
return secondProp.value(); return secondProp.value();
}); });
firstProp.reset(new QProperty<int>()); firstProp.setBinding([&]() -> int {
firstProp->setBinding([&]() -> int { return secondProp.value() + thirdProp.value();
return secondProp.value();
}); });
QCOMPARE(thirdProp.value(), 0); thirdProp.setValue(10);
QCOMPARE(secondProp.binding().error().type(), QPropertyBindingError::BindingLoop); QCOMPARE(firstProp.binding().error().type(), QPropertyBindingError::BindingLoop);
{ {
@ -1200,7 +1201,9 @@ void tst_QProperty::qobjectObservers()
MyQObject object; MyQObject object;
int onValueChangedCalled = 0; int onValueChangedCalled = 0;
{ {
auto handler = object.bindableFoo().onValueChanged([&onValueChangedCalled]() { ++onValueChangedCalled;}); auto handler = object.bindableFoo().onValueChanged([&onValueChangedCalled]() {
++onValueChangedCalled;
});
QCOMPARE(onValueChangedCalled, 0); QCOMPARE(onValueChangedCalled, 0);
object.setFoo(10); object.setFoo(10);
@ -1606,80 +1609,103 @@ void tst_QProperty::bindablePropertyWithInitialization()
QCOMPARE(tester.prop3().anotherValue, 20); QCOMPARE(tester.prop3().anotherValue, 20);
} }
class MarkDirtyTester : public QObject void tst_QProperty::noDoubleNotification()
{ {
Q_OBJECT /* dependency graph for this test
public: x --> y means y depends on x
Q_PROPERTY(int value1 READ value1 WRITE setValue1 BINDABLE bindableValue1) a-->b-->d
Q_PROPERTY(int value2 READ value2 WRITE setValue1 BINDABLE bindableValue2) \ ^
Q_PROPERTY(int computed READ computed BINDABLE bindableComputed) \->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);
}
inline static int staticValue = 0; void tst_QProperty::groupedNotifications()
int value1() const {return m_value1;}
void setValue1(int val) {m_value1 = val;}
QBindable<int> bindableValue1() {return { &m_value1 };}
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 markValue1Dirty() {
m_value1.markDirty();
}
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)
};
void tst_QProperty::markDirty()
{ {
{ QProperty<int> a(0);
QProperty<int> testProperty; QProperty<int> b;
int changeCounter = 0; b.setBinding([&](){ return a.value(); });
auto handler = testProperty.onValueChanged([&](){++changeCounter;}); QProperty<int> c;
testProperty.markDirty(); c.setBinding([&](){ return a.value(); });
QCOMPARE(changeCounter, 1); QProperty<int> d;
} QProperty<int> e;
{ e.setBinding([&](){ return b.value() + c.value() + d.value(); });
MarkDirtyTester dirtyTester; int nNotifications = 0;
int computedChangeCounter = 0; int expected = 0;
int value1ChangeCounter = 0; auto connection = e.subscribe([&](){
auto handler = dirtyTester.bindableComputed().onValueChanged([&](){ ++nNotifications;
computedChangeCounter++; QCOMPARE(e.value(), expected);
}); });
auto handler2 = dirtyTester.bindableValue1().onValueChanged([&](){ QCOMPARE(nNotifications, 1);
value1ChangeCounter++;
}); expected = 2;
dirtyTester.incrementStaticValue(); Qt::beginPropertyUpdateGroup();
QCOMPARE(computedChangeCounter, 1); a = 1;
QCOMPARE(dirtyTester.computed(), 1); QCOMPARE(b.value(), 0);
dirtyTester.markValue1Dirty(); QCOMPARE(c.value(), 0);
QCOMPARE(value1ChangeCounter, 1); QCOMPARE(d.value(), 0);
QCOMPARE(computedChangeCounter, 1); QCOMPARE(nNotifications, 1);
} Qt::endPropertyUpdateGroup();
{ QCOMPARE(b.value(), 1);
MarkDirtyTester dirtyTester; QCOMPARE(c.value(), 1);
int changeCounter = 0; QCOMPARE(e.value(), 2);
auto handler = dirtyTester.bindableValue2().onValueChanged([&](){ QCOMPARE(nNotifications, 2);
changeCounter++;
}); expected = 7;
dirtyTester.markValue2Dirty(); Qt::beginPropertyUpdateGroup();
QCOMPARE(changeCounter, 1); 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);
}
void tst_QProperty::groupedNotificationConsistency()
{
QProperty<int> i(0);
QProperty<int> j(0);
bool areEqual = true;
auto observer = i.onValueChanged([&](){
areEqual = i == j;
});
i = 1;
j = 1;
QVERIFY(!areEqual); // value changed runs before j = 1
Qt::beginPropertyUpdateGroup();
i = 2;
j = 2;
Qt::endPropertyUpdateGroup();
QVERIFY(areEqual); // value changed runs after everything has been evaluated
} }
QTEST_MAIN(tst_QProperty); QTEST_MAIN(tst_QProperty);

View File

@ -1,3 +1,4 @@
SOURCES += main.cpp SOURCES += main.cpp
QT = core QT = core
CONFIG += console CONFIG += console
DEFINES += QT_FORCE_ASSERTS