1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qqmlsynchronizer_p.h"
5
6#include <private/qobject_p.h>
7#include <private/qqmlproperty_p.h>
8#include <private/qqmlvaluetype_p.h>
9#include <private/qv4scopedvalue_p.h>
10
11#include <QtQml/qqmlinfo.h>
12#include <QtCore/qcompare.h>
13
14QT_BEGIN_NAMESPACE
15
16struct QQmlSynchronizerProperty
17{
18 QQmlSynchronizerProperty() = default;
19 QQmlSynchronizerProperty(
20 QObject *object, const QQmlPropertyData *core,
21 const QQmlPropertyData *valueTypeData = nullptr)
22 : m_object(object)
23 , m_core(core)
24 , m_valueTypeData(valueTypeData)
25 {}
26
27 bool isValid() const { return m_object != nullptr && m_core != nullptr; }
28
29 QVariant read() const;
30 void write(QVariant &&value) const;
31
32 QObject *object() const { return m_object; }
33 const QQmlPropertyData *core() const { return m_core; }
34 const QQmlPropertyData *valueTypeData() const { return m_valueTypeData; }
35
36 QString name() const
37 {
38 if (!m_core)
39 return QString();
40
41 const QString coreName = m_core->name(object: m_object);
42
43 const QQmlPropertyData *vt = valueTypeData();
44 if (!vt)
45 return coreName;
46
47 const QMetaObject *vtMetaObject = QQmlMetaType::metaObjectForValueType(type: m_core->propType());
48 Q_ASSERT(vtMetaObject);
49 const char *vtName = vtMetaObject->property(index: vt->coreIndex()).name();
50 return coreName + QLatin1Char('.') + QString::fromUtf8(utf8: vtName);
51
52 }
53
54 int notifyIndex() const { return m_core->notifyIndex(); }
55
56 void clear()
57 {
58 m_object = nullptr;
59 m_core = nullptr;
60 m_valueTypeData = nullptr;
61 }
62
63 QVariant coerce(const QVariant &source, QQmlSynchronizer *q) const;
64
65private:
66 QPointer<QObject> m_object;
67 const QQmlPropertyData *m_core = nullptr;
68 const QQmlPropertyData *m_valueTypeData = nullptr;
69
70 friend bool comparesEqual(
71 const QQmlSynchronizerProperty &a, const QQmlSynchronizerProperty &b) noexcept
72 {
73 return a.m_core == b.m_core && a.m_valueTypeData == b.m_valueTypeData
74 && a.m_object == b.m_object;
75 }
76 Q_DECLARE_EQUALITY_COMPARABLE(QQmlSynchronizerProperty)
77
78 friend size_t qHash(const QQmlSynchronizerProperty &v, size_t seed = 0)
79 {
80 // We'd better not use m_object here since it may turn to nullptr spontaneously.
81 // This results in a weaker hash, but we can live with it.
82 return qHashMulti(seed, args: v.m_core, args: v.m_valueTypeData);
83 }
84
85
86 QMetaType metaType() const
87 {
88 return m_valueTypeData
89 ? m_valueTypeData->propType()
90 : m_core->propType();
91 }
92};
93
94class QQmlSynchronizerSlotObject : public QtPrivate::QSlotObjectBase
95{
96public:
97 QQmlSynchronizerSlotObject(
98 QQmlSynchronizer *receiver, const QQmlSynchronizerProperty &property)
99 : QtPrivate::QSlotObjectBase(&impl)
100 , m_property(property)
101 , m_connection(createConnection(receiver))
102 {
103 }
104
105 ~QQmlSynchronizerSlotObject()
106 {
107 QObject::disconnect(m_connection);
108 }
109
110 bool contains(const QQmlSynchronizerProperty &p) const { return m_property == p; }
111 void write(QVariant value) const { m_property.write(value: std::move(value)); }
112 QVariant coerce(const QVariant &source, QQmlSynchronizer *q) const
113 {
114 return m_property.coerce(source, q);
115 }
116 QQmlSynchronizerProperty property() const { return m_property; }
117
118 static void impl(int which, QtPrivate::QSlotObjectBase *self, QObject *r, void **a, bool *ret);
119 void operator()(QQmlSynchronizer *receiver)
120 {
121 impl(which: QtPrivate::QSlotObjectBase::Call, self: this, r: receiver, a: nullptr, ret: nullptr);
122 }
123
124 void addref() { ref(); }
125 void release() { destroyIfLastRef(); }
126
127private:
128 QMetaObject::Connection createConnection(QQmlSynchronizer *receiver)
129 {
130 Q_ASSERT(receiver);
131 QObject *object = m_property.object();
132 Q_ASSERT(object);
133 const int notifyIndex = m_property.notifyIndex();
134 Q_ASSERT(notifyIndex != -1);
135
136 return QObjectPrivate::connectImpl(
137 sender: object, signal_index: notifyIndex, receiver, slot: nullptr, slotObj: this, type: Qt::AutoConnection, types: nullptr,
138 senderMetaObject: object->metaObject());
139 }
140
141
142 QQmlSynchronizerProperty m_property;
143 QMetaObject::Connection m_connection;
144};
145
146class QQmlSynchronizerHandler
147{
148public:
149 QQmlSynchronizerHandler(
150 QQmlSynchronizerPrivate *receiver, const QQmlSynchronizerProperty &property)
151 : m_property(property)
152 , m_synchronizer(receiver)
153 {}
154
155 bool contains(const QQmlSynchronizerProperty &p) const { return m_property == p; }
156 void write(QVariant value) const { m_property.write(value: std::move(value)); }
157 QVariant coerce(const QVariant &source, QQmlSynchronizer *q) const
158 {
159 return m_property.coerce(source, q);
160 }
161 QQmlSynchronizerProperty property() const { return m_property; }
162
163 void operator()() const;
164
165protected:
166 QPropertyChangeHandler<QQmlSynchronizerHandler> createChangeHandler()
167 {
168 const QQmlPropertyData *core = m_property.core();
169 Q_ASSERT(core && core->isValid());
170 QObject *object = m_property.object();
171 Q_ASSERT(object);
172
173 QUntypedBindable bindable;
174 void *argv[1] { &bindable };
175 core->doMetacall<QMetaObject::BindableProperty>(object, idx: core->coreIndex(), argv);
176 Q_ASSERT(bindable.isValid());
177
178 return bindable.onValueChanged(f: *this);
179 }
180
181private:
182 QQmlSynchronizerProperty m_property;
183 QQmlSynchronizerPrivate *m_synchronizer = nullptr;
184};
185
186class QQmlSynchronizerChangeHandler : public QQmlSynchronizerHandler
187{
188public:
189 QQmlSynchronizerChangeHandler(
190 QQmlSynchronizerPrivate *receiver, const QQmlSynchronizerProperty &property)
191 : QQmlSynchronizerHandler(receiver, property)
192 , changeHandler(createChangeHandler())
193 {}
194
195private:
196 QPropertyChangeHandler<QQmlSynchronizerHandler> changeHandler;
197};
198
199class QQmlSynchronizerPrivate : public QObjectPrivate
200{
201 Q_DECLARE_PUBLIC(QQmlSynchronizer)
202public:
203 enum Result
204 {
205 Origin,
206 Accepted,
207 Bounced,
208 Ignored
209 };
210
211 struct State
212 {
213 QVariant value;
214 QHash<QQmlSynchronizerProperty, Result> results;
215 };
216
217 struct OwnedTarget
218 {
219 QPointer<QObject> object;
220 std::unique_ptr<const QQmlPropertyData> core;
221 std::unique_ptr<const QQmlPropertyData> auxiliary;
222 };
223
224 static QQmlSynchronizerPrivate *get(QQmlSynchronizer *q) { return q->d_func(); }
225
226 std::vector<QQmlRefPointer<QQmlSynchronizerSlotObject>> slotObjects;
227 std::vector<QQmlSynchronizerChangeHandler> changeHandlers;
228
229 // Target set by QQmlPropertyValueSource
230 // This can't change, but we have to hold on to the QQmlPropertyData.
231 OwnedTarget target;
232
233 // Target set using sourceObject/sourceProperty properties
234 // The string is the primary source of truth for sourceProperty.
235 // The QQmlPropertyData are only used if the object does not have a property cache.
236 QString sourceProperty;
237 OwnedTarget sourceObjectProperty;
238
239 // Target set using targetObject/targetProperty properties
240 // The string is the primary source of truth for targetProperty.
241 // The QQmlPropertyData are only used if the object does not have a property cache.
242 QString targetProperty;
243 OwnedTarget targetObjectProperty;
244
245 State *currentState = nullptr;
246
247 bool isComponentFinalized = false;
248
249 void createConnection(QQmlSynchronizer *q, const QQmlSynchronizerProperty &property);
250
251 void disconnectObjectProperty(const QString &property, OwnedTarget *objectProperty);
252 QQmlSynchronizerProperty connectObjectProperty(
253 const QString &property, OwnedTarget *objectProperty, QQmlSynchronizer *q);
254
255 QQmlSynchronizerProperty connectTarget(QQmlSynchronizer *q);
256
257 void initialize(QQmlSynchronizer *q);
258
259 void synchronize(const QQmlSynchronizerProperty &property);
260};
261
262/*!
263 \qmlmodule Qt.labs.synchronizer
264 \title Qt Labs Synchronizer QML Types
265 \ingroup qmlmodules
266 \brief Synchronizer synchronizes values between two or more properties.
267
268 To use this module, import the module with the following line:
269
270 \qml
271 import Qt.labs.synchronizer
272 \endqml
273*/
274
275/*!
276 \qmltype Synchronizer
277 \inqmlmodule Qt.labs.synchronizer
278 \since 6.10
279 \brief Synchronizes values between two or more properties.
280
281 A Synchronizer object binds two or more properties together so that a change
282 to any one of them automatically updates all others. While doing so, none of
283 the bindings on any of the properties are broken. You can use Synchronizer if
284 the direction of data flow between two properties is not pre-determined. For
285 example, a TextInput may be initialized with a model value but should also
286 update the model value when editing is finished.
287
288 \note The input elements provided by Qt Quick and Qt Quick Controls solve this
289 problem by providing user interaction signals separate from value change
290 signals and hiding the value assignments in C++ code. You \e{don't} need
291 Synchronizer for their internals. However, it may still be useful when
292 connecting a control to a model.
293
294 Consider the following example.
295
296 \section1 Without Synchronizer
297
298 \qml
299 // MyCustomTextInput.qml
300 Item {
301 property string text
302 function append(characters: string) { text += characters }
303 [...]
304 }
305 \endqml
306
307 You may be inclined to populate the \c text property from a model and update
308 the model when the \c textChanged signal is received.
309
310 \qml
311 // Does not work!
312 Item {
313 id: root
314 property string model: "lorem ipsum"
315 MyCustomTextInput {
316 text: root.model
317 onTextChanged: root.model = text
318 }
319 }
320 \endqml
321
322 This does \e not work. When the \c append function is called, the
323 \c text property is modified, and the binding that updates it from the
324 \c model property is broken. The next time the \c model is updated
325 independently \c text is not updated anymore.
326
327 To solve this, you can omit the binding altogether and use only signals
328 for updating both properties. This way you would need to give up the
329 convenience of bindings.
330
331 Or, you can use Synchronizer.
332
333 \section1 With Synchronizer
334
335 \qml
336 Item {
337 id: root
338 property string model: "lorem ipsum"
339 MyCustomTextInput {
340 Synchronizer on text {
341 property alias source: root.model
342 }
343 }
344 }
345 \endqml
346
347 Synchronizer makes sure that whenever either the model or
348 the text change, the other one is updated.
349
350 You can specify properties to be synchronized in several ways:
351 \list
352 \li Using the \c on syntax
353 \li Populating the \c sourceObject and \c sourceProperty properties
354 \li Populating the \c targetObject and \c targetProperty properties
355 \li Creating aliases in the scope of the synchronizer
356 \endlist
357
358 The following example synchronizes four different properties,
359 exercising all the different options:
360
361 \qml
362 Item {
363 id: root
364 property string model: "lorem ipsum"
365
366 MyCustomTextInput {
367 Synchronizer on text {
368 sourceObject: other
369 sourceProperty: "text"
370
371 targetObject: root.children[0]
372 targetProperty: "objectName"
373
374 property alias source: root.model
375 property alias another: root.objectName
376 }
377 }
378
379 MyCustomTextInput {
380 id: other
381 }
382 }
383 \endqml
384
385 Optionally, Synchronizer will perform an initial synchronization:
386 \list
387 \li If one of the aliases is called \c{source}, then it will be used to
388 initialize the other properties.
389 \li Otherwise, if the values assigned to \l{sourceObject} and
390 \l{sourceProperty} denote a property, that property will be used
391 as source for initial synchronization.
392 \li Otherwise, if the \c on syntax is used, the property on which the
393 Synchronizer is created that way is used as source for initial
394 synchronization.
395 \li Otherwise no initial synchronization is performed. Only when one of
396 the properties changes the others will be updated.
397 \endlist
398
399 Synchronizer automatically \e{de-bounces}. While it is synchronizing
400 using a given value as the source, it does not accept further updates from
401 one of the properties expected to be the target of the update. Such
402 behavior would otherwise easily lead to infinite update loops.
403 Synchronizer uses the \l{valueBounced} signal to notify about this
404 condition. Furthermore, it detects properties that silently
405 refuse an update and emits the \l{valueIgnored} signal for them.
406 Silence, in this context, is determined by the lack of a change signal
407 after calling the setter for the given property.
408
409 If the properties to be synchronized are of different types, the usual
410 QML type coercions are applied.
411 */
412
413QQmlSynchronizer::QQmlSynchronizer(QObject *parent)
414 : QObject(*(new QQmlSynchronizerPrivate), parent)
415{
416}
417
418void QQmlSynchronizer::setTarget(const QQmlProperty &target)
419{
420 Q_D(QQmlSynchronizer);
421
422 // Should be set before component completion
423 Q_ASSERT(!d->isComponentFinalized);
424
425 QQmlPropertyPrivate *p = QQmlPropertyPrivate::get(p: target);
426 d->target.object = p->object;
427 d->target.core = std::make_unique<QQmlPropertyData>(args&: p->core);
428 d->target.auxiliary = p->valueTypeData.isValid()
429 ? std::make_unique<QQmlPropertyData>(args&: p->valueTypeData)
430 : nullptr;
431}
432
433void QQmlSynchronizer::componentFinalized()
434{
435 Q_D(QQmlSynchronizer);
436
437 d->isComponentFinalized = true;
438 d->initialize(q: this);
439}
440
441/*!
442 \qmlproperty QtObject Qt.labs.synchronizer::Synchronizer::sourceObject
443
444 This property holds the \l{sourceObject} part of the
445 \l{sourceObject}/\l{sourceProperty} pair that together can specify one of
446 the properties Synchronizer will synchronize.
447*/
448QObject *QQmlSynchronizer::sourceObject() const
449{
450 Q_D(const QQmlSynchronizer);
451 return d->sourceObjectProperty.object;
452}
453
454void QQmlSynchronizer::setSourceObject(QObject *object)
455{
456 Q_D(QQmlSynchronizer);
457 if (object == d->sourceObjectProperty.object)
458 return;
459
460 if (d->isComponentFinalized)
461 d->disconnectObjectProperty(property: d->sourceProperty, objectProperty: &d->sourceObjectProperty);
462
463 d->sourceObjectProperty.object = object;
464 emit sourceObjectChanged();
465
466 if (d->isComponentFinalized)
467 d->connectObjectProperty(property: d->sourceProperty, objectProperty: &d->sourceObjectProperty, q: this);
468}
469
470/*!
471 \qmlproperty string Qt.labs.synchronizer::Synchronizer::sourceProperty
472
473 This sourceProperty holds the \l{sourceProperty} part of the
474 \l{sourceObject}/\l{sourceProperty} pair that together can specify one of
475 the properties Synchronizer will synchronize.
476*/
477QString QQmlSynchronizer::sourceProperty() const
478{
479 Q_D(const QQmlSynchronizer);
480 return d->sourceProperty;
481}
482
483void QQmlSynchronizer::setSourceProperty(const QString &property)
484{
485 Q_D(QQmlSynchronizer);
486 if (property == d->sourceProperty)
487 return;
488
489 if (d->isComponentFinalized)
490 d->disconnectObjectProperty(property: d->sourceProperty, objectProperty: &d->sourceObjectProperty);
491
492 d->sourceProperty = property;
493 emit sourcePropertyChanged();
494
495 if (d->isComponentFinalized)
496 d->connectObjectProperty(property: d->sourceProperty, objectProperty: &d->sourceObjectProperty, q: this);
497}
498
499/*!
500 \qmlproperty QtObject Qt.labs.synchronizer::Synchronizer::targetObject
501
502 This property holds the \l{targetObject} part of the
503 \l{targetObject}/\l{targetProperty} pair that together can specify one of
504 the properties Synchronizer will synchronize.
505 */
506QObject *QQmlSynchronizer::targetObject() const
507{
508 Q_D(const QQmlSynchronizer);
509 return d->targetObjectProperty.object;
510}
511
512void QQmlSynchronizer::setTargetObject(QObject *object)
513{
514 Q_D(QQmlSynchronizer);
515 if (object == d->targetObjectProperty.object)
516 return;
517
518 if (d->isComponentFinalized)
519 d->disconnectObjectProperty(property: d->targetProperty, objectProperty: &d->targetObjectProperty);
520
521 d->targetObjectProperty.object = object;
522 emit targetObjectChanged();
523
524 if (d->isComponentFinalized)
525 d->connectObjectProperty(property: d->targetProperty, objectProperty: &d->targetObjectProperty, q: this);
526}
527
528/*!
529 \qmlproperty string Qt.labs.synchronizer::Synchronizer::targetProperty
530
531 This targetProperty holds the \l{targetProperty} part of the
532 \l{targetObject}/\l{targetProperty} pair that together can specify one of
533 the properties Synchronizer will synchronize.
534*/
535QString QQmlSynchronizer::targetProperty() const
536{
537 Q_D(const QQmlSynchronizer);
538 return d->targetProperty;
539}
540
541void QQmlSynchronizer::setTargetProperty(const QString &property)
542{
543 Q_D(QQmlSynchronizer);
544 if (property == d->targetProperty)
545 return;
546
547 if (d->isComponentFinalized)
548 d->disconnectObjectProperty(property: d->targetProperty, objectProperty: &d->targetObjectProperty);
549
550 d->targetProperty = property;
551 emit targetPropertyChanged();
552
553 if (d->isComponentFinalized)
554 d->connectObjectProperty(property: d->targetProperty, objectProperty: &d->targetObjectProperty, q: this);
555}
556
557/*!
558 \qmlsignal Qt.labs.synchronizer::Synchronizer::valueBounced(QtObject object, string property)
559
560 This signal is emitted if the \a{property} of the \a{object} refused
561 an attempt to set its value as part of the synchronization and produced a
562 different value in response. Such \e{bounced} values are ignored and
563 \e{don't} trigger another round of synchronization.
564 */
565
566/*!
567 \qmlsignal Qt.labs.synchronizer::Synchronizer::valueIgnored(QtObject object, string property)
568
569 This signal is emitted if \a{property} of the \a{object} did not
570 respond to an attempt to set its value as part of the synchronization.
571 */
572
573
574void QQmlSynchronizerPrivate::createConnection(
575 QQmlSynchronizer *q, const QQmlSynchronizerProperty &property)
576{
577 if (property.core()->notifiesViaBindable()) {
578 changeHandlers.push_back(x: QQmlSynchronizerChangeHandler(this, property));
579 } else if (const int notifyIndex = property.notifyIndex(); notifyIndex != -1) {
580 slotObjects.push_back(x: QQmlRefPointer(
581 new QQmlSynchronizerSlotObject(q, property),
582 QQmlRefPointer<QQmlSynchronizerSlotObject>::AddRef));
583 }
584}
585
586void QQmlSynchronizerPrivate::disconnectObjectProperty(
587 const QString &property, OwnedTarget *objectProperty)
588{
589 QObject *object = objectProperty->object;
590 if (!object || property.isEmpty())
591 return;
592
593 QQmlPropertyData localCore;
594 const QQmlPropertyData *coreData = objectProperty->core
595 ? objectProperty->core.get()
596 : QQmlPropertyCache::property(object, property, {}, &localCore);
597 if (!coreData || coreData->isFunction())
598 return;
599
600 const QQmlSynchronizerProperty synchronizerProperty = QQmlSynchronizerProperty(object, coreData);
601 const auto slot = std::find_if(first: slotObjects.begin(), last: slotObjects.end(), pred: [&](const auto &slot) {
602 return slot->contains(synchronizerProperty);
603 });
604
605 if (slot == slotObjects.end()) {
606 const auto handler
607 = std::find_if(first: changeHandlers.begin(), last: changeHandlers.end(), pred: [&](const auto &handler) {
608 return handler.contains(synchronizerProperty);
609 });
610
611 Q_ASSERT(handler != changeHandlers.end());
612 changeHandlers.erase(position: handler);
613 } else {
614 slotObjects.erase(position: slot);
615 }
616
617 objectProperty->core.reset();
618 objectProperty->auxiliary.reset();
619}
620
621QQmlSynchronizerProperty QQmlSynchronizerPrivate::connectObjectProperty(
622 const QString &property, OwnedTarget *objectProperty, QQmlSynchronizer *q)
623{
624 QObject *object = objectProperty->object;
625 if (!object || property.isEmpty())
626 return QQmlSynchronizerProperty();
627
628 QQmlPropertyData localCore;
629 const QQmlPropertyData *coreData = QQmlPropertyCache::property(object, property, {}, &localCore);
630 if (!coreData) {
631 qmlWarning(me: q) << "Target object has no property called " << property;
632 return QQmlSynchronizerProperty();
633 }
634
635 if (coreData->isFunction()) {
636 qmlWarning(me: q) << "Member " << property << " of target object is a function";
637 return QQmlSynchronizerProperty();
638 }
639
640 if (coreData == &localCore) {
641 objectProperty->core = std::make_unique<QQmlPropertyData>(args: std::move(localCore));
642 coreData = objectProperty->core.get();
643 }
644
645 const QQmlSynchronizerProperty synchronizerProperty(object, coreData);
646 createConnection(q, property: synchronizerProperty);
647 return synchronizerProperty;
648}
649
650QQmlSynchronizerProperty QQmlSynchronizerPrivate::connectTarget(QQmlSynchronizer *q)
651{
652 QObject *object = target.object;
653 if (!object)
654 return QQmlSynchronizerProperty();
655
656 const QQmlPropertyData *core = target.core.get();
657 Q_ASSERT(core->isValid());
658
659 if (const QQmlPropertyData *valueTypeData = target.auxiliary.get()) {
660 const QQmlSynchronizerProperty property(object, core, valueTypeData);
661 createConnection(q, property);
662 return property;
663 }
664
665 const QQmlSynchronizerProperty property(object, core);
666 createConnection(q, property);
667 return property;
668}
669
670void QQmlSynchronizerPrivate::initialize(QQmlSynchronizer *q)
671{
672 changeHandlers.clear();
673 slotObjects.clear();
674
675 QQmlSynchronizerProperty initializationSource = connectTarget(q);
676 if (QQmlSynchronizerProperty source = connectObjectProperty(
677 property: sourceProperty, objectProperty: &sourceObjectProperty, q); source.isValid()) {
678 initializationSource = source;
679 }
680 connectObjectProperty(property: targetProperty, objectProperty: &targetObjectProperty, q);
681
682 const QQmlPropertyCache::ConstPtr propertyCache = QQmlData::ensurePropertyCache(object: q);
683 Q_ASSERT(propertyCache);
684
685 const int propertyCount = propertyCache->propertyCount();
686 const int propertyOffset = QQmlSynchronizer::staticMetaObject.propertyCount();
687
688 bool foundSource = false;
689 for (int i = propertyOffset; i < propertyCount; ++i) {
690 const QQmlPropertyData *property = propertyCache->property(index: i);
691 if (!property->isAlias())
692 continue;
693
694 const QQmlSynchronizerProperty synchronizerProperty(q, property);
695 createConnection(q, property: synchronizerProperty);
696
697 if (!foundSource && property->name(object: q) == QLatin1String("source")) {
698 initializationSource = synchronizerProperty;
699 foundSource = true;
700 continue;
701 }
702 }
703
704 if (initializationSource.isValid())
705 synchronize(property: initializationSource);
706}
707
708void QQmlSynchronizerPrivate::synchronize(const QQmlSynchronizerProperty &property)
709{
710 const QVariant value = property.read();
711
712 if (currentState) {
713 // There can be interesting logic attached to the target property that causes it
714 // to change multiple times in a row. We only count the last change.
715 currentState->results[property] = (value == currentState->value) ? Accepted : Bounced;
716 return;
717 }
718
719 Q_Q(QQmlSynchronizer);
720
721 State state;
722 state.results[property] = Origin;
723 const auto guard = QScopedValueRollback(currentState, &state);
724
725 for (const auto &slotObject : slotObjects) {
726 if (slotObject->contains(p: property))
727 continue;
728 state.results[slotObject->property()] = Ignored;
729 state.value = slotObject->coerce(source: value, q);
730 slotObject->write(value: state.value);
731 }
732
733 for (const QQmlSynchronizerChangeHandler &changeHandler : changeHandlers) {
734 if (changeHandler.contains(p: property))
735 continue;
736 state.results[changeHandler.property()] = Ignored;
737 state.value = changeHandler.coerce(source: value, q);
738 changeHandler.write(value: state.value);
739 }
740
741
742 for (auto it = state.results.constBegin(), end = state.results.constEnd(); it != end; ++it) {
743 switch (*it) {
744 case Origin:
745 case Accepted:
746 break;
747 case Bounced: {
748 const QQmlSynchronizerProperty &key = it.key();
749 emit q->valueBounced(object: key.object(), property: key.name());
750 break;
751 }
752 case Ignored: {
753 const QQmlSynchronizerProperty &key = it.key();
754 emit q->valueIgnored(object: key.object(), property: key.name());
755 break;
756 }
757 }
758 }
759}
760
761void QQmlSynchronizerSlotObject::impl(
762 int which, QSlotObjectBase *self, QObject *r, void **a, bool *ret)
763{
764 Q_UNUSED(a);
765 QQmlSynchronizerSlotObject *synchronizerSlotObject
766 = static_cast<QQmlSynchronizerSlotObject *>(self);
767 switch (which) {
768 case Destroy:
769 delete synchronizerSlotObject;
770 break;
771 case Call: {
772 QQmlSynchronizer *q = static_cast<QQmlSynchronizer *>(r);
773 QQmlSynchronizerPrivate::get(q)->synchronize(property: synchronizerSlotObject->m_property);
774 break;
775 }
776 case Compare:
777 if (ret)
778 *ret = false;
779 break;
780 case NumOperations:
781 break;
782 }
783}
784
785static QVariant doReadProperty(QObject *object, const QQmlPropertyData *property)
786{
787 const QMetaType metaType = property->propType();
788 if (metaType == QMetaType::fromType<QVariant>()) {
789 QVariant content;
790 property->readProperty(target: object, property: &content);
791 return content;
792 }
793
794 QVariant content(metaType);
795 property->readProperty(target: object, property: content.data());
796 return content;
797}
798
799QVariant QQmlSynchronizerProperty::read() const
800{
801 Q_ASSERT(m_object);
802 Q_ASSERT(m_core);
803
804 QVariant coreContent = doReadProperty(object: m_object.data(), property: m_core);
805
806 if (!m_valueTypeData)
807 return coreContent;
808
809 if (QQmlGadgetPtrWrapper *wrapper
810 = QQmlGadgetPtrWrapper::instance(engine: qmlEngine(m_object), type: coreContent.metaType())) {
811 return doReadProperty(object: wrapper, property: m_valueTypeData);
812 }
813
814 QQmlGadgetPtrWrapper wrapper(QQmlMetaType::valueType(metaType: coreContent.metaType()));
815 return doReadProperty(object: &wrapper, property: m_valueTypeData);
816}
817
818void QQmlSynchronizerProperty::write(QVariant &&value) const
819{
820 Q_ASSERT(value.metaType() == metaType());
821
822 if (!m_object) // Was disconnected in some way or other
823 return;
824
825 if (!m_valueTypeData) {
826 m_core->writeProperty(target: m_object, value: value.data(), flags: QQmlPropertyData::DontRemoveBinding);
827 return;
828 }
829
830 QVariant coreContent = doReadProperty(object: m_object, property: m_core);
831
832 if (QQmlGadgetPtrWrapper *wrapper
833 = QQmlGadgetPtrWrapper::instance(engine: qmlEngine(m_object), type: coreContent.metaType())) {
834 m_valueTypeData->writeProperty(target: wrapper, value: value.data(), flags: QQmlPropertyData::DontRemoveBinding);
835 m_core->writeProperty(target: m_object, value: coreContent.data(), flags: QQmlPropertyData::DontRemoveBinding);
836 return;
837 }
838
839 QQmlGadgetPtrWrapper wrapper(QQmlMetaType::valueType(metaType: coreContent.metaType()));
840 m_valueTypeData->writeProperty(target: &wrapper, value: value.data(), flags: QQmlPropertyData::DontRemoveBinding);
841 m_core->writeProperty(target: m_object, value: coreContent.data(), flags: QQmlPropertyData::DontRemoveBinding);
842}
843
844QVariant QQmlSynchronizerProperty::coerce(const QVariant &source, QQmlSynchronizer *q) const
845{
846 Q_ASSERT(m_core);
847
848 const QMetaType targetMetaType = m_valueTypeData
849 ? m_valueTypeData->propType()
850 : m_core->propType();
851 const QMetaType sourceMetaType = source.metaType();
852 if (targetMetaType == sourceMetaType)
853 return source;
854
855 QVariant target(targetMetaType);
856
857 QQmlData *ddata = QQmlData::get(object: q);
858 if (ddata && !ddata->jsWrapper.isNullOrUndefined()) {
859 QV4::Scope scope(ddata->jsWrapper.engine());
860 QV4::ScopedValue scoped(scope, scope.engine->fromData(type: sourceMetaType, ptr: source.constData()));
861 if (QV4::ExecutionEngine::metaTypeFromJS(value: scoped, type: targetMetaType, data: target.data()))
862 return target;
863 }
864
865 if (QMetaType::convert(fromType: sourceMetaType, from: source.constData(), toType: targetMetaType, to: target.data()))
866 return target;
867
868 qmlWarning(me: q) << "Cannot convert from " << sourceMetaType.name() << " to " << targetMetaType.name();
869 return target;
870}
871
872void QQmlSynchronizerHandler::operator()() const
873{
874 m_synchronizer->synchronize(property: m_property);
875}
876
877QT_END_NAMESPACE
878

source code of qtdeclarative/src/labs/synchronizer/qqmlsynchronizer.cpp