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