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 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | Q_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 | |
55 | static 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 | */ |
74 | static 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 = qobject_cast<QQuickPopupItem *>(object: objectWeAreAttachedTo); |
96 | if (popupItem) { |
97 | auto = QQuickPopupItemPrivate::get(popupItem); |
98 | QQuickAttachedPropertyPropagator * = 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 * = 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 * = 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 | |
159 | static 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 | |
204 | static QQuickItem *findAttachedItem(QObject *parent) |
205 | { |
206 | QQuickItem *item = qobject_cast<QQuickItem *>(o: parent); |
207 | if (!item) { |
208 | QQuickPopup * = qobject_cast<QQuickPopup *>(object: parent); |
209 | if (popup) |
210 | item = popup->popupItem(); |
211 | } |
212 | return item; |
213 | } |
214 | |
215 | class QQuickAttachedPropertyPropagatorPrivate : public QObjectPrivate, public QQuickItemChangeListener |
216 | { |
217 | public: |
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 | |
236 | void 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 | |
245 | void 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 | */ |
272 | void 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 | |
292 | void 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 | |
303 | void 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 | */ |
319 | QQuickAttachedPropertyPropagator::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 | */ |
329 | QQuickAttachedPropertyPropagator::~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 | */ |
346 | QList<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 | */ |
361 | QQuickAttachedPropertyPropagator *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 | */ |
383 | void 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 | */ |
410 | void QQuickAttachedPropertyPropagator::attachedParentChange(QQuickAttachedPropertyPropagator *newParent, QQuickAttachedPropertyPropagator *oldParent) |
411 | { |
412 | Q_UNUSED(newParent); |
413 | Q_UNUSED(oldParent); |
414 | } |
415 | |
416 | QT_END_NAMESPACE |
417 | |
418 | #include "moc_qquickattachedpropertypropagator.cpp" |
419 | |