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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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