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 *window = popup->popupItem()->window();
182 auto *object = attachedObject(ourAttachedType, window);
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 *>(window)) {
186 auto *transientParentWnd = window->transientParent();
187 do {
188 object = attachedObject(ourAttachedType, transientParentWnd);
189 if (object)
190 break;
191 transientParentWnd = transientParentWnd->transientParent();
192 } while (transientParentWnd);
193 qCDebug(lcAttached).noquote() << "- attached object of the transient parent window: " << object;
194 }
195 return object;
196 }
197 }
198
199 // lookup parent window
200 QQuickWindow *window = qobject_cast<QQuickWindow *>(objectWeAreAttachedTo);
201 if (window) {
202 // It doesn't seem like a parent window can be anything but transient in Qt Quick.
203 QQuickWindow *parentWindow = qobject_cast<QQuickWindow *>(window->transientParent());
204 qCDebug(lcAttached).noquote() << "- attachee is a window; checking its parent window" << parentWindow;
205 if (parentWindow) {
206 QQuickAttachedPropertyPropagator *attached = attachedObject(ourAttachedType, parentWindow);
207 if (attached) {
208 qCDebug(lcAttached).noquote() << "- parent window has attached object" << attached << "- returning";
209 return attached;
210 }
211 }
212 }
213
214 // fallback to engine (global)
215 if (objectWeAreAttachedTo) {
216 QQmlEngine *engine = qmlEngine(objectWeAreAttachedTo);
217 qCDebug(lcAttached).noquote() << "- falling back to engine" << engine;
218 if (engine) {
219 QByteArray name = QByteArray("_q_") + ourAttachedType->className();
220 QQuickAttachedPropertyPropagator *attached = engine->property(name).value<QQuickAttachedPropertyPropagator *>();
221 if (!attached) {
222 attached = attachedObject(ourAttachedType, engine, true);
223 engine->setProperty(name, QVariant::fromValue(attached));
224 }
225 return attached;
226 }
227 }
228
229 return nullptr;
230}
231
232static QList<QQuickAttachedPropertyPropagator *> findAttachedChildren(const QMetaObject *type, QObject *object)
233{
234 QList<QQuickAttachedPropertyPropagator *> children;
235
236 QQuickItem *item = qobject_cast<QQuickItem *>(object);
237 if (!item) {
238 QQuickWindow *window = qobject_cast<QQuickWindow *>(object);
239 if (window)
240 item = window->contentItem();
241 }
242
243 if (!item)
244 return children;
245
246 // At this point, "item" could either be an item that the attached object is
247 // attached to directly, or the contentItem of a window that the attached object
248 // is attached to.
249
250 // Look for attached properties on items.
251 const auto childItems = item->childItems();
252 for (QQuickItem *child : childItems) {
253 QQuickAttachedPropertyPropagator *attached = attachedObject(type, child);
254 if (attached)
255 children += attached;
256 else
257 children += findAttachedChildren(type, child);
258 }
259
260 // Look for attached properties on windows. Windows declared in QML
261 // as children of a Window are QObject-parented to the contentItem (see
262 // QQuickWindowPrivate::data_append()). Windows declared as children
263 // of items are QObject-parented to those items.
264 const auto &windowChildren = item->children();
265 for (QObject *child : windowChildren) {
266 QQuickWindow *childWindow = qobject_cast<QQuickWindow *>(child);
267 if (childWindow) {
268 QQuickAttachedPropertyPropagator *attached = attachedObject(type, childWindow);
269 if (attached)
270 children += attached;
271 }
272 }
273
274 return children;
275}
276
277static QQuickItem *findAttachedItem(QObject *parent)
278{
279 QQuickItem *item = qobject_cast<QQuickItem *>(parent);
280 if (!item) {
281 QQuickPopup *popup = qobject_cast<QQuickPopup *>(parent);
282 if (popup)
283 item = popup->popupItem();
284 }
285 return item;
286}
287
290{
291public:
292 Q_DECLARE_PUBLIC(QQuickAttachedPropertyPropagator)
293
298
300 void detachFrom(QObject *object);
301 void setAttachedParent(QQuickAttachedPropertyPropagator *parent);
302
303 void itemWindowChanged(QQuickWindow *window);
304 void transientParentWindowChanged(QWindow *newTransientParent);
305 void itemParentChanged(QQuickItem *item, QQuickItem *parent) override;
306
309};
310
311void QQuickAttachedPropertyPropagatorPrivate::attachTo(QObject *object)
312{
313 if (QQuickItem *item = findAttachedItem(object)) {
314 connect(item, &QQuickItem::windowChanged, this, &QQuickAttachedPropertyPropagatorPrivate::itemWindowChanged);
315 QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Parent);
316 } else if (auto *window = qobject_cast<QQuickWindow *>(object)) {
317 QObjectPrivate::connect(window, &QWindow::transientParentChanged, this,
318 &QQuickAttachedPropertyPropagatorPrivate::transientParentWindowChanged);
319 }
320}
321
323{
324 if (QQuickItem *item = findAttachedItem(object)) {
325 disconnect(item, &QQuickItem::windowChanged, this, &QQuickAttachedPropertyPropagatorPrivate::itemWindowChanged);
326 QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Parent);
327 } else if (auto *window = qobject_cast<QQuickWindow *>(object)) {
328 QObjectPrivate::disconnect(window, &QWindow::transientParentChanged,
329 this, &QQuickAttachedPropertyPropagatorPrivate::transientParentWindowChanged);
330 }
331}
332
333/*!
334 \internal
335
336 This function sets the attached parent of this attached object.
337
338 Currently it is called when:
339 \list
340 \li The target item's parent changes.
341 \li The target item's window changes.
342 \li The attached object is constructed, to set the attached parent
343 and the attached parent of the attached object children.
344 \li The attached object is destructed.
345 \endlist
346
347 \quotefromfile ../../examples/quickcontrols/attachedstyleproperties/MyStyle/mystyle.cpp
348 \skipto MyStyle::resetTheme
349 \printuntil }
350*/
351void QQuickAttachedPropertyPropagatorPrivate::setAttachedParent(QQuickAttachedPropertyPropagator *parent)
352{
353 Q_Q(QQuickAttachedPropertyPropagator);
354 if (attachedParent == parent)
355 return;
356
357 QQuickAttachedPropertyPropagator *oldParent = attachedParent;
358 qCDebug(lcAttached).noquote() << "setAttachedParent called on" << q << "with parent" << parent;
359 if (attachedParent) {
360 qCDebug(lcAttached).noquote() << "- removing ourselves as an attached child of" << attachedParent;
361 QQuickAttachedPropertyPropagatorPrivate::get(attachedParent)->attachedChildren.removeOne(q);
362 }
363 attachedParent = parent;
364 if (parent) {
365 qCDebug(lcAttached).noquote() << "- adding ourselves as an attached child of" << parent;
366 QQuickAttachedPropertyPropagatorPrivate::get(parent)->attachedChildren.append(q);
367 }
368 q->attachedParentChange(parent, oldParent);
369}
370
371/*
372 If there's e.g. code like this:
373
374 Behavior on Material.elevation {}
375
376 The meta type will be something like QQuickMaterialStyle_QML_125,
377 whereas QQmlMetaType::attachedPropertiesFunc only has attached
378 property data for QQuickMaterialStyle (i.e. attached property types
379 created from C++). We work around this by finding the first C++
380 meta object, which works even for attached types created in QML.
381*/
382const QMetaObject *firstCppMetaObject(QQuickAttachedPropertyPropagator *propagator)
383{
384 return QQmlData::ensurePropertyCache(propagator)->firstCppMetaObject();
385}
386
388{
389 Q_Q(QQuickAttachedPropertyPropagator);
390 QQuickAttachedPropertyPropagator *attachedParent = nullptr;
391 qCDebug(lcAttached).noquote() << "window of" << q << "changed to" << window;
392
393 attachedParent = findAttachedParent(firstCppMetaObject(q), q->parent());
394 if (!attachedParent)
395 attachedParent = attachedObject(firstCppMetaObject(q), window);
396 setAttachedParent(attachedParent);
397}
398
400{
401 Q_Q(QQuickAttachedPropertyPropagator);
402 QQuickAttachedPropertyPropagator *attachedParent = nullptr;
403 qCDebug(lcAttached).noquote() << "transient parent window of" << q << "changed to" << newTransientParent;
404 attachedParent = findAttachedParent(firstCppMetaObject(q), q->parent());
405 if (!attachedParent)
406 attachedParent = attachedObject(firstCppMetaObject(q), newTransientParent);
407 setAttachedParent(attachedParent);
408}
409
410void QQuickAttachedPropertyPropagatorPrivate::itemParentChanged(QQuickItem *item, QQuickItem *parent)
411{
412 Q_Q(QQuickAttachedPropertyPropagator);
413 Q_UNUSED(item);
414 Q_UNUSED(parent);
415 setAttachedParent(findAttachedParent(firstCppMetaObject(q), q->parent()));
416}
417
418/*!
419 Constructs a QQuickAttachedPropertyPropagator with the given \a parent.
420
421 The \c parent will be used to find this object's
422 \l {attachedParent()}{attached parent}.
423
424 Derived classes should call \l initialize() in their constructor.
425*/
426QQuickAttachedPropertyPropagator::QQuickAttachedPropertyPropagator(QObject *parent)
427 : QObject(*(new QQuickAttachedPropertyPropagatorPrivate), parent)
428{
429 Q_D(QQuickAttachedPropertyPropagator);
430 d->attachTo(parent);
431}
432
433/*!
434 Destroys the QQuickAttachedPropertyPropagator.
435*/
436QQuickAttachedPropertyPropagator::~QQuickAttachedPropertyPropagator()
437{
438 Q_D(QQuickAttachedPropertyPropagator);
439 d->detachFrom(parent());
440 d->setAttachedParent(nullptr);
441}
442
443/*!
444 This function returns the attached children of this attached object.
445
446 The attached children are used when propagating property values:
447
448 \quotefromfile ../../examples/quickcontrols/attachedstyleproperties/MyStyle/mystyle.cpp
449 \skipto MyStyle::propagateTheme
450 \printuntil }
451 \printuntil }
452*/
453QList<QQuickAttachedPropertyPropagator *> QQuickAttachedPropertyPropagator::attachedChildren() const
454{
455 Q_D(const QQuickAttachedPropertyPropagator);
456 return d->attachedChildren;
457}
458
459/*!
460 This function returns the attached parent of this attached object.
461
462 The attached parent is used when inheriting property values:
463
464 \quotefromfile ../../examples/quickcontrols/attachedstyleproperties/MyStyle/mystyle.cpp
465 \skipto MyStyle::resetTheme
466 \printuntil }
467*/
468QQuickAttachedPropertyPropagator *QQuickAttachedPropertyPropagator::attachedParent() const
469{
470 Q_D(const QQuickAttachedPropertyPropagator);
471 return d->attachedParent;
472}
473
474/*!
475 Finds and sets the attached parent for this attached object, and then does
476 the same for its children. This must be called upon construction of the
477 attached object in order for propagation to work.
478
479 It can be useful to read global/default values before calling this
480 function. For example, before calling \c initialize(), the
481 \l {Imagine Style}{Imagine} style checks a static "globalsInitialized" flag
482 to see if it should read default values from \l QSettings. The values from
483 that file form the basis for any attached property values that have not
484 been explicitly set.
485
486 \quotefromfile ../../examples/quickcontrols/attachedstyleproperties/MyStyle/mystyle.cpp
487 \skipto MyStyle::MyStyle
488 \printuntil }
489*/
490void QQuickAttachedPropertyPropagator::initialize()
491{
492 Q_D(QQuickAttachedPropertyPropagator);
493 qCDebug(lcAttached) << "initialize called for" << parent() << "- looking for attached parent...";
494 QQuickAttachedPropertyPropagator *attachedParent = findAttachedParent(metaObject(), parent());
495 if (attachedParent)
496 d->setAttachedParent(attachedParent);
497
498 const QList<QQuickAttachedPropertyPropagator *> attachedChildren = findAttachedChildren(metaObject(), parent());
499 qCDebug(lcAttached) << "- found" << attachedChildren.size() << "attached children:";
500 for (QQuickAttachedPropertyPropagator *child : attachedChildren) {
501 qCDebug(lcAttached) << " -" << child->parent();
502 QQuickAttachedPropertyPropagatorPrivate::get(child)->setAttachedParent(this);
503 }
504
505 qCDebug(lcAttached) << "... finished initializing";
506}
507
508/*!
509 This function is called whenever the attached parent of this
510 QQuickAttachedPropertyPropagator changes from \a oldParent to \a newParent.
511
512 Subclasses should reimplement this function to inherit attached properties
513 from \c newParent.
514
515 \quotefromfile ../../examples/quickcontrols/attachedstyleproperties/MyStyle/mystyle.cpp
516 \skipto attachedParentChange
517 \printuntil }
518 \printuntil }
519*/
520void QQuickAttachedPropertyPropagator::attachedParentChange(QQuickAttachedPropertyPropagator *newParent, QQuickAttachedPropertyPropagator *oldParent)
521{
522 Q_UNUSED(newParent);
523 Q_UNUSED(oldParent);
524}
525
526#ifndef QT_NO_DEBUG_STREAM
527QDebug operator<<(QDebug debug, const QQuickAttachedPropertyPropagator *propagator)
528{
529 QDebugStateSaver saver(debug);
530 debug.nospace().noquote();
531 if (!propagator) {
532 debug << "QQuickAttachedPropertyPropagator(nullptr)";
533 return debug;
534 }
535
536 // Cast to QObject to avoid recursion.
537 debug << static_cast<const QObject *>(propagator) << " (which is attached to " << propagator->parent() << ')';
538 return debug;
539}
540#endif
541
542QT_END_NAMESPACE
543
544#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)
Combined button and popup list for selecting options.
QDebug operator<<(QDebug dbg, const NSObject *nsObject)
Definition qcore_mac.mm:203
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)