1// Copyright (C) 2022 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 "qquickattachedpropertypropagator.h"
5
6#include <QtCore/qpointer.h>
7#include <QtQuick/qquickwindow.h>
8#include <QtQuick/private/qquickitem_p.h>
9#include <QtQuick/private/qquickitemchangelistener_p.h>
10#include <QtQuickTemplates2/private/qquickpopup_p.h>
11#include <QtQuickTemplates2/private/qquickpopupitem_p_p.h>
12
13QT_BEGIN_NAMESPACE
14
15Q_LOGGING_CATEGORY(lcAttached, "qt.quick.controls.attachedpropertypropagator")
16
17/*!
18 \class QQuickAttachedPropertyPropagator
19 \brief The QQuickAttachedPropertyPropagator class provides a way to
20 propagate attached properties.
21 \inmodule QtQuickControls2
22 \since 6.5
23
24 In QML, it is possible to
25 \l {Attached Properties and Attached Signal Handlers}{attach properties and
26 signal handlers} to objects. \l {Providing Attached Properties} goes into more
27 detail about how to expose your own C++ attached types.
28
29 QQuickAttachedPropertyPropagator provides an API to propagate attached
30 properties from a parent object to its children, similar to
31 \l {Control::}{font} and \l {Item::}{palette} propagation. It supports
32 propagation through \l {Item}{items}, \l {Popup}{popups}, and
33 \l {Window}{windows}.
34
35 If propagation of properties is not important, consider using a
36 \l {QML_SINGLETON}{C++} or
37 \l {Contents of a Module Definition qmldir File}{QML} singleton instead,
38 as it is better suited for that use case, and is more efficient in that
39 it only requires one QObject.
40
41 To use QQuickAttachedPropertyPropagator:
42 \list
43 \li Derive from it
44 \li Call \l initialize() in the constructor
45 \li Define set/inherit/propagate/reset functions for each property as needed
46 \li Reimplement \l attachedParentChange() to handle property inheritance
47 \endlist
48
49 For an example that demonstrates this in depth, see
50 \l {Qt Quick Controls - Attached Style Properties Example}.
51
52 \sa {Styling Qt Quick Controls}
53*/
54
55static QQuickAttachedPropertyPropagator *attachedObject(const QMetaObject *type, QObject *object, bool create = false)
56{
57 if (!object)
58 return nullptr;
59 auto func = qmlAttachedPropertiesFunction(object, type);
60 return qobject_cast<QQuickAttachedPropertyPropagator *>(object: qmlAttachedPropertiesObject(object, func, create));
61}
62
63/*!
64 \internal
65
66 Tries to find a QQuickAttachedPropertyPropagator whose type is \a ourAttachedType
67 and is attached to an ancestor of \a objectWeAreAttachedTo.
68
69 QQuickAttachedPropertyPropagator needs to know who its parent attached object is in
70 order to inherit attached property values from it. This is called when an
71 instance of QQuickAttachedPropertyPropagator is created, and whenever
72 \c {objectWeAreAttachedTo}'s parent changes, for example.
73*/
74static QQuickAttachedPropertyPropagator *findAttachedParent(const QMetaObject *ourAttachedType, QObject *objectWeAreAttachedTo)
75{
76 /*
77 In the Material ComboBox.qml, we have code like this:
78
79 popup: T.Popup {
80 // ...
81 Material.theme: control.Material.theme
82 // ...
83
84 background: Rectangle {
85 //...
86 color: parent.Material.dialogColor
87
88 The Material attached object has to be accessed this way due to
89 deferred execution limitations (see 3e87695fb4b1a5d503c744046e6d9f43a2ae18a6).
90 However, since parent here refers to QQuickPopupItem and not the popup,
91 the color will actually come from the window. If a dark theme was set on
92 the ComboBox, it will not be respected in the background if we don't have
93 the check below.
94 */
95 auto popupItem = qobject_cast<QQuickPopupItem *>(object: objectWeAreAttachedTo);
96 if (popupItem) {
97 auto popupItemPrivate = QQuickPopupItemPrivate::get(popupItem);
98 QQuickAttachedPropertyPropagator *popupAttached = attachedObject(type: ourAttachedType, object: popupItemPrivate->popup);
99 if (popupAttached)
100 return popupAttached;
101 }
102
103 QQuickItem *item = qobject_cast<QQuickItem *>(o: objectWeAreAttachedTo);
104 if (item) {
105 // lookup parent items and popups
106 QQuickItem *parent = item->parentItem();
107 while (parent) {
108 QQuickAttachedPropertyPropagator *attached = attachedObject(type: ourAttachedType, object: parent);
109 if (attached)
110 return attached;
111
112 QQuickPopup *popup = qobject_cast<QQuickPopup *>(object: parent->parent());
113 if (popup)
114 return attachedObject(type: ourAttachedType, object: popup);
115
116 parent = parent->parentItem();
117 }
118
119 // fallback to item's window
120 QQuickAttachedPropertyPropagator *attached = attachedObject(type: ourAttachedType, object: item->window());
121 if (attached)
122 return attached;
123 } else {
124 // lookup popup's window
125 QQuickPopup *popup = qobject_cast<QQuickPopup *>(object: objectWeAreAttachedTo);
126 if (popup)
127 return attachedObject(type: ourAttachedType, object: popup->popupItem()->window());
128 }
129
130 // lookup parent window
131 QQuickWindow *window = qobject_cast<QQuickWindow *>(object: objectWeAreAttachedTo);
132 if (window) {
133 // It doesn't seem like a parent window can be anything but transient in Qt Quick.
134 QQuickWindow *parentWindow = qobject_cast<QQuickWindow *>(object: window->transientParent());
135 if (parentWindow) {
136 QQuickAttachedPropertyPropagator *attached = attachedObject(type: ourAttachedType, object: parentWindow);
137 if (attached)
138 return attached;
139 }
140 }
141
142 // fallback to engine (global)
143 if (objectWeAreAttachedTo) {
144 QQmlEngine *engine = qmlEngine(objectWeAreAttachedTo);
145 if (engine) {
146 QByteArray name = QByteArray("_q_") + ourAttachedType->className();
147 QQuickAttachedPropertyPropagator *attached = engine->property(name).value<QQuickAttachedPropertyPropagator *>();
148 if (!attached) {
149 attached = attachedObject(type: ourAttachedType, object: engine, create: true);
150 engine->setProperty(name, value: QVariant::fromValue(value: attached));
151 }
152 return attached;
153 }
154 }
155
156 return nullptr;
157}
158
159static QList<QQuickAttachedPropertyPropagator *> findAttachedChildren(const QMetaObject *type, QObject *object)
160{
161 QList<QQuickAttachedPropertyPropagator *> children;
162
163 QQuickItem *item = qobject_cast<QQuickItem *>(o: object);
164 if (!item) {
165 QQuickWindow *window = qobject_cast<QQuickWindow *>(object);
166 if (window)
167 item = window->contentItem();
168 }
169
170 if (!item)
171 return children;
172
173 // At this point, "item" could either be an item that the attached object is
174 // attached to directly, or the contentItem of a window that the attached object
175 // is attached to.
176
177 // Look for attached properties on items.
178 const auto childItems = item->childItems();
179 for (QQuickItem *child : childItems) {
180 QQuickAttachedPropertyPropagator *attached = attachedObject(type, object: child);
181 if (attached)
182 children += attached;
183 else
184 children += findAttachedChildren(type, object: child);
185 }
186
187 // Look for attached properties on windows. Windows declared in QML
188 // as children of a Window are QObject-parented to the contentItem (see
189 // QQuickWindowPrivate::data_append()). Windows declared as children
190 // of items are QObject-parented to those items.
191 const auto &windowChildren = item->children();
192 for (QObject *child : windowChildren) {
193 QQuickWindow *childWindow = qobject_cast<QQuickWindow *>(object: child);
194 if (childWindow) {
195 QQuickAttachedPropertyPropagator *attached = attachedObject(type, object: childWindow);
196 if (attached)
197 children += attached;
198 }
199 }
200
201 return children;
202}
203
204static QQuickItem *findAttachedItem(QObject *parent)
205{
206 QQuickItem *item = qobject_cast<QQuickItem *>(o: parent);
207 if (!item) {
208 QQuickPopup *popup = qobject_cast<QQuickPopup *>(object: parent);
209 if (popup)
210 item = popup->popupItem();
211 }
212 return item;
213}
214
215class QQuickAttachedPropertyPropagatorPrivate : public QObjectPrivate, public QQuickItemChangeListener
216{
217public:
218 Q_DECLARE_PUBLIC(QQuickAttachedPropertyPropagator)
219
220 static QQuickAttachedPropertyPropagatorPrivate *get(QQuickAttachedPropertyPropagator *attachedObject)
221 {
222 return attachedObject->d_func();
223 }
224
225 void attachTo(QObject *object);
226 void detachFrom(QObject *object);
227 void setAttachedParent(QQuickAttachedPropertyPropagator *parent);
228
229 void itemWindowChanged(QQuickWindow *window);
230 void itemParentChanged(QQuickItem *item, QQuickItem *parent) override;
231
232 QList<QQuickAttachedPropertyPropagator *> attachedChildren;
233 QPointer<QQuickAttachedPropertyPropagator> attachedParent;
234};
235
236void QQuickAttachedPropertyPropagatorPrivate::attachTo(QObject *object)
237{
238 QQuickItem *item = findAttachedItem(parent: object);
239 if (item) {
240 connect(sender: item, signal: &QQuickItem::windowChanged, receiverPrivate: this, slot: &QQuickAttachedPropertyPropagatorPrivate::itemWindowChanged);
241 QQuickItemPrivate::get(item)->addItemChangeListener(listener: this, types: QQuickItemPrivate::Parent);
242 }
243}
244
245void QQuickAttachedPropertyPropagatorPrivate::detachFrom(QObject *object)
246{
247 QQuickItem *item = findAttachedItem(parent: object);
248 if (item) {
249 disconnect(sender: item, signal: &QQuickItem::windowChanged, receiverPrivate: this, slot: &QQuickAttachedPropertyPropagatorPrivate::itemWindowChanged);
250 QQuickItemPrivate::get(item)->removeItemChangeListener(this, types: QQuickItemPrivate::Parent);
251 }
252}
253
254/*!
255 \internal
256
257 This function sets the attached parent of this attached object.
258
259 Currently it is called when:
260 \list
261 \li The target item's parent changes.
262 \li The target item's window changes.
263 \li The attached object is constructed, to set the attached parent
264 and the attached parent of the attached object children.
265 \li The attached object is destructed.
266 \endlist
267
268 \quotefromfile ../../examples/quickcontrols/attachedstyleproperties/MyStyle/mystyle.cpp
269 \skipto MyStyle::resetTheme
270 \printuntil }
271*/
272void QQuickAttachedPropertyPropagatorPrivate::setAttachedParent(QQuickAttachedPropertyPropagator *parent)
273{
274 Q_Q(QQuickAttachedPropertyPropagator);
275 if (attachedParent == parent)
276 return;
277
278 QQuickAttachedPropertyPropagator *oldParent = attachedParent;
279 qCDebug(lcAttached) << "setAttachedParent called on" << q->parent();
280 if (attachedParent) {
281 qCDebug(lcAttached) << "- removing ourselves as an attached child of" << attachedParent->parent() << attachedParent;
282 QQuickAttachedPropertyPropagatorPrivate::get(attachedObject: attachedParent)->attachedChildren.removeOne(t: q);
283 }
284 attachedParent = parent;
285 if (parent) {
286 qCDebug(lcAttached) << "- adding ourselves as an attached child of" << parent->parent() << parent;
287 QQuickAttachedPropertyPropagatorPrivate::get(attachedObject: parent)->attachedChildren.append(t: q);
288 }
289 q->attachedParentChange(newParent: parent, oldParent);
290}
291
292void QQuickAttachedPropertyPropagatorPrivate::itemWindowChanged(QQuickWindow *window)
293{
294 Q_Q(QQuickAttachedPropertyPropagator);
295 QQuickAttachedPropertyPropagator *attachedParent = nullptr;
296 qCDebug(lcAttached) << "window of" << q->parent() << "changed to" << window;
297 attachedParent = findAttachedParent(ourAttachedType: q->metaObject(), objectWeAreAttachedTo: q->parent());
298 if (!attachedParent)
299 attachedParent = attachedObject(type: q->metaObject(), object: window);
300 setAttachedParent(attachedParent);
301}
302
303void QQuickAttachedPropertyPropagatorPrivate::itemParentChanged(QQuickItem *item, QQuickItem *parent)
304{
305 Q_Q(QQuickAttachedPropertyPropagator);
306 Q_UNUSED(item);
307 Q_UNUSED(parent);
308 setAttachedParent(findAttachedParent(ourAttachedType: q->metaObject(), objectWeAreAttachedTo: q->parent()));
309}
310
311/*!
312 Constructs a QQuickAttachedPropertyPropagator with the given \a parent.
313
314 The \c parent will be used to find this object's
315 \l {attachedParent()}{attached parent}.
316
317 Derived classes should call \l initialize() in their constructor.
318*/
319QQuickAttachedPropertyPropagator::QQuickAttachedPropertyPropagator(QObject *parent)
320 : QObject(*(new QQuickAttachedPropertyPropagatorPrivate), parent)
321{
322 Q_D(QQuickAttachedPropertyPropagator);
323 d->attachTo(object: parent);
324}
325
326/*!
327 Destroys the QQuickAttachedPropertyPropagator.
328*/
329QQuickAttachedPropertyPropagator::~QQuickAttachedPropertyPropagator()
330{
331 Q_D(QQuickAttachedPropertyPropagator);
332 d->detachFrom(object: parent());
333 d->setAttachedParent(nullptr);
334}
335
336/*!
337 This function returns the attached children of this attached object.
338
339 The attached children are used when propagating property values:
340
341 \quotefromfile ../../examples/quickcontrols/attachedstyleproperties/MyStyle/mystyle.cpp
342 \skipto MyStyle::propagateTheme
343 \printuntil }
344 \printuntil }
345*/
346QList<QQuickAttachedPropertyPropagator *> QQuickAttachedPropertyPropagator::attachedChildren() const
347{
348 Q_D(const QQuickAttachedPropertyPropagator);
349 return d->attachedChildren;
350}
351
352/*!
353 This function returns the attached parent of this attached object.
354
355 The attached parent is used when inheriting property values:
356
357 \quotefromfile ../../examples/quickcontrols/attachedstyleproperties/MyStyle/mystyle.cpp
358 \skipto MyStyle::resetTheme
359 \printuntil }
360*/
361QQuickAttachedPropertyPropagator *QQuickAttachedPropertyPropagator::attachedParent() const
362{
363 Q_D(const QQuickAttachedPropertyPropagator);
364 return d->attachedParent;
365}
366
367/*!
368 Finds and sets the attached parent for this attached object, and then does
369 the same for its children. This must be called upon construction of the
370 attached object in order for propagation to work.
371
372 It can be useful to read global/default values before calling this
373 function. For example, before calling \c initialize(), the
374 \l {Imagine Style}{Imagine} style checks a static "globalsInitialized" flag
375 to see if it should read default values from \l QSettings. The values from
376 that file form the basis for any attached property values that have not
377 been explicitly set.
378
379 \quotefromfile ../../examples/quickcontrols/attachedstyleproperties/MyStyle/mystyle.cpp
380 \skipto MyStyle::MyStyle
381 \printuntil }
382*/
383void QQuickAttachedPropertyPropagator::initialize()
384{
385 Q_D(QQuickAttachedPropertyPropagator);
386 QQuickAttachedPropertyPropagator *attachedParent = findAttachedParent(ourAttachedType: metaObject(), objectWeAreAttachedTo: parent());
387 if (attachedParent)
388 d->setAttachedParent(attachedParent);
389
390 const QList<QQuickAttachedPropertyPropagator *> attachedChildren = findAttachedChildren(type: metaObject(), object: parent());
391 qCDebug(lcAttached) << "initialize called for" << parent() << "- found" << attachedChildren.size() << "attached children";
392 for (QQuickAttachedPropertyPropagator *child : attachedChildren) {
393 qCDebug(lcAttached) << "-" << child->parent();
394 QQuickAttachedPropertyPropagatorPrivate::get(attachedObject: child)->setAttachedParent(this);
395 }
396}
397
398/*!
399 This function is called whenever the attached parent of this
400 QQuickAttachedPropertyPropagator changes from \a oldParent to \a newParent.
401
402 Subclasses should reimplement this function to inherit attached properties
403 from \c newParent.
404
405 \quotefromfile ../../examples/quickcontrols/attachedstyleproperties/MyStyle/mystyle.cpp
406 \skipto attachedParentChange
407 \printuntil }
408 \printuntil }
409*/
410void QQuickAttachedPropertyPropagator::attachedParentChange(QQuickAttachedPropertyPropagator *newParent, QQuickAttachedPropertyPropagator *oldParent)
411{
412 Q_UNUSED(newParent);
413 Q_UNUSED(oldParent);
414}
415
416QT_END_NAMESPACE
417
418#include "moc_qquickattachedpropertypropagator.cpp"
419

source code of qtdeclarative/src/quickcontrols/qquickattachedpropertypropagator.cpp