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
qquicklayoutitemproxy.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 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
6
8
9/*!
10 \qmltype LayoutItemProxy
11 \nativetype QQuickLayoutItemProxy
12 \inherits Item
13 \inqmlmodule QtQuick.Layouts
14 \ingroup layouts
15 \since QtQuick.Layouts 6.6
16 \brief The LayoutItemProxy class provides a placeholder for \l{QQuickItem}s
17 in layouts.
18
19 Some responsive layouts require different layout hierarchies for different
20 screen sizes, but the layout hierarchy is the same as the QML structure and
21 can therefore not be changed at runtime. LayoutItemProxy overcomes this
22 limitation by representing a \l{target} item within the layout. The
23 \l{target} item itself can be defined anywhere in the QML hierarchy. This
24 allows declaration of multiple layouts with the same content items. The
25 layouts can be shown and hidden to switch between them.
26
27 The LayoutItemProxy will try to take control of the \l{target} item if it
28 is \l [QML] {Item::}{visible}. Taking control will position and resize the
29 \l{target} item to match the position and size of the LayoutItemProxy.
30 Further, the LayoutItemProxy will set itself as the parent of the
31 \l{target} (to ensure event delivery and useful drawing order) and set the
32 visibility to \c true. Multiple LayoutItemProxies can \l{target} the same
33 item, but only one LayoutItemProxy can control an item at a time. Therefore
34 only one of the proxies targeting the same item should be visible at a
35 time. If multiple proxies target the same item but \e visible is set to
36 false for each proxy, the item will also be invisible.
37
38 All \l{Layout} attached properties of the \l {target}, as well as the
39 \l{QQuickItem::implicitWidth} and \l{QQuickItem::implicitHeight} of the
40 \l{target} are forwarded by the LayoutItemProxy. The LayoutItemProxy will
41 mimic the \l{target} as closely as possible in terms of \l{Layout}
42 properties and size. \l{Layout} attached properties can also be set
43 explicitly on the LayoutItemProxy which will stop the forwarding of the
44 \l {target} properties.
45
46 \section1 Example Usage
47
48 This is a minimalistic example, changing between two layouts using proxies
49 to use the same items in both layouts. The items that populate the layouts
50 can be defined at an arbitrary point in the QML structure.
51
52 \snippet layouts/simpleProxy.qml item definition
53
54 Then we can define the Layouts with LayoutItemProxys
55
56 \snippet layouts/simpleProxy.qml layout definition
57
58 We can switch now between the layouts, depending on a criterion of our
59 choice by toggling the visibility of the layouts on and off.
60
61 \snippet layouts/simpleProxy.qml layout choice
62
63 The two resulting layouts look like this:
64
65 \div {class="float-right"}
66 \inlineimage simpleProxy.png
67 \enddiv
68
69 The LayoutItemProxy can also be used without layouts, e.g. by anchoring it
70 to different items. A mix of real \l {Item}{Items} and proxy items is
71 equally possible, as well as nested structures of layouts and items.
72
73 \warning The LayoutItemProxy will set the parent of its target to itself.
74 Keep this in mind when referring to the parent of the target item.
75
76 \sa Item, GridLayout, RowLayout, ColumnLayout
77*/
78
79Q_STATIC_LOGGING_CATEGORY(lcLayouts, "qt.quick.layouts")
80
81
82QQuickLayoutItemProxy::QQuickLayoutItemProxy(QQuickItem *parent)
83 : QQuickItem(*new QQuickLayoutItemProxyPrivate, parent)
84{
85
86}
87
89{
90 Q_D(QQuickLayoutItemProxy);
91
92 if (!d->target)
93 return;
94
95 QQuickLayoutItemProxyAttachedData * attachedData = d->target->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>();
96 // De-register this proxy from the proxies controlling the target
97 if (attachedData) {
98 if (attachedData->getControllingProxy() == this) {
99 attachedData->releaseControl(this);
100 d->target->setParentItem(nullptr);
101 }
102 attachedData->releaseProxy(this);
103 }
104 // The target item still has a QObject parent that takes care of its destrctuion.
105 // No need to invoke destruction of the target tiem from here.
106}
107
108/*! \internal
109 \brief QQuickLayoutItemProxy::geometryChange Reimplementation of
110 QQuickItem::geometryChange to update the target geometry too.
111*/
112void QQuickLayoutItemProxy::geometryChange(const QRectF &newGeom, const QRectF &oldGeom)
113{
114 QQuickItem::geometryChange(newGeom, oldGeom);
115 if (!isVisible())
116 return;
117
118 const QSizeF sz = newGeom.size();
119 QPointF pos(0., 0.);
120
121 if (QQuickItem *t = effectiveTarget()) {
122 if (QQuickLayoutItemProxyAttachedData * attachedData = target()->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>()) {
123 if (attachedData->getControllingProxy() != this)
124 return;
125 }
126
127 // Should normally not be the case, except the user resets the parent
128 // This is a failsave for this case and positions the item correctly
129 if (t->parentItem() != this)
130 pos = t->parentItem()->mapFromGlobal(mapToGlobal(0, 0));
131
132 if (t->size() == sz && t->position() == pos && newGeom == oldGeom)
133 return;
134
135 t->setSize(sz);
136 t->setPosition(pos);
137 }
138}
139
140/*! \internal
141 \brief QQuickLayoutItemProxy::itemChange is a reimplementation of
142 QQuickItem::itemChange to react to changes in visibility.
143*/
144void QQuickLayoutItemProxy::itemChange(ItemChange c, const ItemChangeData &d)
145{
146 if (c == QQuickItem::ItemVisibleHasChanged)
147 {
149 }
150 QQuickItem::itemChange(c, d);
151}
152
153// Implementation of the slots to react to changes of the Layout attached properties.
154// If the target Layout propertie change, we change the proxy Layout properties accordingly
155// If the proxy Layout properties have been changed externally, we want to remove this binding.
156// The member variables m_expectProxy##Property##Change help us keep track about who invokes
157// the change of the parameter. If it is invoked by the target we expect a proxy property
158// change and will not remove the connection.
159#define propertyForwarding(property, Property)
160 void QQuickLayoutItemProxy::target##Property##Changed() {
161 Q_D(QQuickLayoutItemProxy);
162 QQuickLayoutAttached *attTarget = attachedLayoutObject(target(), false);
163 QQuickLayoutAttached *attProxy = attachedLayoutObject(this, false);
164 if (!attTarget) return;
165 if (attProxy->property() == attTarget->property())
166 return;
167 d->m_expectProxy##Property##Change = true;
168 attProxy->set##Property(attTarget->property());
169 }
170 void QQuickLayoutItemProxy::proxy##Property##Changed() {
171 Q_D(QQuickLayoutItemProxy);
172 if (d->m_expectProxy##Property##Change) {
173 d->m_expectProxy##Property##Change = false;
174 return;
175 }
176 QQuickLayoutAttached *attTarget = attachedLayoutObject(target(), false);
177 if (!attTarget) return;
178 disconnect(attTarget, &QQuickLayoutAttached::property##Changed, this, &QQuickLayoutItemProxy::target##Property##Changed);
179 }
180
181propertyForwarding(minimumWidth, MinimumWidth)
182propertyForwarding(minimumHeight, MinimumHeight)
183propertyForwarding(preferredWidth, PreferredWidth)
184propertyForwarding(preferredHeight, PreferredHeight)
185propertyForwarding(maximumWidth, MaximumWidth)
186propertyForwarding(maximumHeight, MaximumHeight)
187propertyForwarding(fillWidth, FillWidth)
188propertyForwarding(fillHeight, FillHeight)
189propertyForwarding(alignment, Alignment)
190propertyForwarding(horizontalStretchFactor, HorizontalStretchFactor)
191propertyForwarding(verticalStretchFactor, VerticalStretchFactor)
192propertyForwarding(margins, Margins)
193propertyForwarding(leftMargin, LeftMargin)
194propertyForwarding(topMargin, TopMargin)
195propertyForwarding(rightMargin, RightMargin)
196propertyForwarding(bottomMargin, BottomMargin)
197
198#undef propertyForwarding
199
200/*!
201 \qmlproperty Item LayoutItemProxy::target
202
203 This property holds the \l Item that the proxy should represent in a
204 \l {Layout} hierarchy.
205*/
206
207/*! \internal
208 \brief QQuickLayoutItemProxy::target
209 \return The target item of the proxy
210*/
212{
213 Q_D(const QQuickLayoutItemProxy);
214 return d->target;
215}
216
217/*! \internal
218 \brief QQuickLayoutItemProxy::setTarget sets the target
219 \param newTarget The item that the proxy stands in place for.
220
221 All layout properties of the target are connected to the layout properties
222 of the LayoutItemProxy. It the LayoutItemProxy is visible, it will try to
223 take control of the target.
224*/
225void QQuickLayoutItemProxy::setTarget(QQuickItem *newTarget)
226{
227 Q_D(QQuickLayoutItemProxy);
228
229 if (newTarget == d->target)
230 return;
231
232 if (d->target && d->target->property("QQuickLayoutItemProxyAttachedData").isValid()) {
233 QQuickLayoutItemProxyAttachedData *attachedData = d->target->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>();
234 attachedData->releaseProxy(this);
235 }
236 d->target = newTarget;
237
238 if (newTarget) {
239
240 QQuickLayoutItemProxyAttachedData *attachedData;
241 if (newTarget->property("QQuickLayoutItemProxyAttachedData").isValid()) {
242 attachedData = newTarget->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>();
243 } else {
244 attachedData = new QQuickLayoutItemProxyAttachedData(newTarget);
245 QVariant v;
246 v.setValue(attachedData);
247 newTarget->setProperty("QQuickLayoutItemProxyAttachedData", v);
248 }
249 attachedData->registerProxy(this);
250
251 // If there is no other controlling proxy, we will hide the target
252 if (!attachedData->proxyHasControl())
253 newTarget->setVisible(false);
254 // We are calling maybeTakeControl at the end to eventually take
255 // responsibility of showing the target.
256
257 if (QQuickLayoutAttached *attTarget = attachedLayoutObject(newTarget)) {
258 QQuickLayoutAttached *attProxy = attachedLayoutObject(this, true);
259
260 disconnect(attTarget, nullptr, attProxy, nullptr);
261
262 // bind item-specific layout properties:
263
264#define connectPropertyForwarding(property, Property)
265 if (!attProxy->is##Property##Set()) {
266 connect(attTarget, &QQuickLayoutAttached::property##Changed, this, &QQuickLayoutItemProxy::target##Property##Changed);
267 connect(attProxy, &QQuickLayoutAttached::property##Changed, this, &QQuickLayoutItemProxy::proxy##Property##Changed);
268 target##Property##Changed();
269 }
270 connectPropertyForwarding(minimumWidth, MinimumWidth)
271 connectPropertyForwarding(minimumHeight, MinimumHeight)
272 connectPropertyForwarding(preferredWidth, PreferredWidth)
273 connectPropertyForwarding(preferredHeight, PreferredHeight)
274 connectPropertyForwarding(maximumWidth, MaximumWidth)
275 connectPropertyForwarding(maximumHeight, MaximumHeight)
276 connectPropertyForwarding(fillWidth, FillWidth)
277 connectPropertyForwarding(fillHeight, FillHeight)
278 connectPropertyForwarding(alignment, Alignment)
279 connectPropertyForwarding(horizontalStretchFactor, HorizontalStretchFactor)
280 connectPropertyForwarding(verticalStretchFactor, VerticalStretchFactor)
281 connectPropertyForwarding(margins, Margins)
282 connectPropertyForwarding(leftMargin, LeftMargin)
283 connectPropertyForwarding(topMargin, TopMargin)
284 connectPropertyForwarding(rightMargin, RightMargin)
285 connectPropertyForwarding(bottomMargin, BottomMargin)
286#undef connectPropertyForwarding
287
288 // proxy.implicitWidth: target.implicitWidth
289 auto fnBindImplW = [newTarget, this](){ this->setImplicitWidth(newTarget->implicitWidth()); };
290 fnBindImplW();
291 connect(newTarget, &QQuickItem::implicitWidthChanged, fnBindImplW);
292
293 // proxy.implicitHeight: target.implicitHeight
294 auto fnBindImplH = [newTarget, this](){ this->setImplicitHeight(newTarget->implicitHeight()); };
295 fnBindImplH();
296 connect(newTarget, &QQuickItem::implicitHeightChanged, fnBindImplH);
297 }
298 }
299
300 if (isVisible())
301 maybeTakeControl();
302
303 emit targetChanged();
304}
305
306/*! \internal
307 \brief QQuickLayoutItemProxy::effectiveTarget
308 \return The target item of the proxy if it is in control, \c null otherwise.
309*/
310QQuickItem *QQuickLayoutItemProxy::effectiveTarget() const
311{
312 if (target() == nullptr)
313 return nullptr;
314
315 QQuickLayoutItemProxyAttachedData * attachedData = target()->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>();
316 return (attachedData->getControllingProxy() == this) ? target() : nullptr;
317}
318
319/*! \internal
320 \brief QQuickLayoutItemProxy::clearTarget sets the target to null.
321
322 This function is called if the target is destroyed to make sure we do not
323 try to access a non-existing object.
324*/
326{
327 setTarget(nullptr);
328}
329
330/*! \internal
331 \brief QQuickLayoutItemProxy::maybeTakeControl checks and takes over control
332 of the item.
333
334 If the proxy is visible it will try to take control over the target and set
335 its visibility to true. If the proxy is hidden it will also hide the target
336 and another LayoutItemProxy has to set the visibility to \c true or the
337 target will stay invisible.
338*/
340{
341 Q_D(QQuickLayoutItemProxy);
342 if (!d->target)
343 return;
344
345 QQuickLayoutItemProxyAttachedData * attachedData = d->target->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>();
346 if (isVisible() && attachedData->getControllingProxy() != this) {
347 if (attachedData->takeControl(this)) {
348 d->target->setVisible(true);
349 d->target->setParentItem(this);
350 updatePos();
351 }
352 }
353 if (!isVisible() && attachedData->getControllingProxy() == this){
354 if (d->target->parentItem() == this) {
355 d->target->setParentItem(nullptr);
356 } else
357 qCDebug(lcLayouts) << "Parent was changed to" << d->target->parentItem() << "while an ItemProxy had control";
358 d->target->setVisible(false);
359 attachedData->releaseControl(this);
360 }
361}
362
363/*! \internal
364 \brief QQuickLayoutItemProxy::updatePos sets the geometry of the target to
365 the geometry of the proxy
366*/
367void QQuickLayoutItemProxy::updatePos()
368{
369 if (!isVisible())
370 return;
371 if (target()) {
372 if (QQuickLayoutItemProxyAttachedData * attachedData = target()->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>()) {
373 if (attachedData->getControllingProxy() == this)
374 geometryChange(boundingRect(), boundingRect());
375 }
376 }
377}
378
400
401/*! \internal
402 \class QQuickLayoutItemProxyAttachedData
403 \brief Provides attached properties for items that are managed by one or
404 more LayoutItemProxy.
405
406 It stores all proxies that target the item, and will emit signals when the
407 proxies or the controlling proxy changes. Proxies can listen to the signal
408 and pick up control if they wish to.
409*/
410QQuickLayoutItemProxyAttachedData::QQuickLayoutItemProxyAttachedData(QObject *parent)
411 : QObject(parent), controllingProxy(nullptr)
412{
413
414}
415
417{
418 if (QObject *par = parent())
419 par->setProperty("QQuickLayoutItemProxyAttachedData", QVariant());
420
421 // If this is destroyed, so is the target. Clear the target from the
422 // proxies so they do not try to access a destroyed object
423 for (auto &proxy: std::as_const(proxies))
424 proxy->clearTarget();
425}
426
427/*! \internal
428 \brief QQuickLayoutItemProxyAttachedData::registerProxy registers a proxy
429 that manages the item this data is attached to.
430
431 This is required to easily notify proxies when the target is destroyed or
432 when it is free to take over control.
433*/
435{
436 if (proxies.contains(proxy))
437 return;
438
439 proxies.append(proxy);
440 emit proxiesChanged();
441}
442
443/*! \internal
444 \brief QQuickLayoutItemProxyAttachedData::releaseProxy removes a proxy from
445 a list of known proxies that manage the item this data is attached to.
446*/
448{
449 if (proxy == controllingProxy)
450 releaseControl(proxy);
451
452 proxies.removeAll(proxy);
453
454 if (proxies.isEmpty())
455 deleteLater();
456
457 emit proxiesChanged();
458}
459
460/*! \internal
461 \brief QQuickLayoutItemProxyAttachedData::takeControl is called by
462 LayoutItemProxies when they try to take control over the item this data is
463 attached to.
464 \return \c true if no other proxy controls the item and if control is
465 granted to the proxy, \c false otherwise.
466
467 \param proxy The proxy that tries to take control.
468*/
470{
471 if (controllingProxy || !proxies.contains(proxy))
472 return false;
473
474 qCDebug(lcLayouts) << proxy
475 << "takes control of"
476 << parent();
477
478 controllingProxy = proxy;
479 emit controlTaken();
480 emit controllingProxyChanged();
481 return true;
482}
483
484/*! \internal
485 \brief QQuickLayoutItemProxyAttachedData::releaseControl is called by
486 LayoutItemProxies when they try no longer control the item
487
488 \param proxy The proxy that gives up control.
489*/
491{
492 if (controllingProxy != proxy)
493 return;
494
495 qCDebug(lcLayouts) << proxy
496 << "no longer controls"
497 << parent();
498
499 controllingProxy = nullptr;
500 emit controlReleased();
501 emit controllingProxyChanged();
502
503 for (auto &otherProxy: std::as_const(proxies)) {
504 if (proxy != otherProxy)
505 otherProxy->maybeTakeControl();
506 }
507}
508
509/*! \internal
510 \brief QQuickLayoutItemProxyAttachedData::getControllingProxy
511 \return the proxy that currently controls the item this data is attached to.
512 Returns \c null if no proxy controls the item.
513*/
515{
516 return controllingProxy;
517}
518
519/*! \internal
520 \brief QQuickLayoutItemProxyAttachedData::getProxies
521 \return a list of all proxies that target the item this data is attached to.
522*/
524{
525 using Type = QQuickLayoutItemProxy;
526 using Property = QQmlListProperty<Type>;
527
528 return Property(
529 this, &proxies,
530 [](Property *p) { return static_cast<QList<Type *> *>(p->data)->size(); },
531 [](Property *p, qsizetype i) { return static_cast<QList<Type *> *>(p->data)->at(i); }
532 );
533}
534
535/*! \internal
536 \brief QQuickLayoutItemProxyAttachedData::proxyHasControl
537 \return \c true if a proxy is controlling the item, \c false otherwise.
538*/
540{
541 return controllingProxy != nullptr;
542}
543
544QT_END_NAMESPACE
Provides attached properties for items that are managed by one or more LayoutItemProxy.
QQmlListProperty< QQuickLayoutItemProxy > getProxies()
QQuickLayoutItemProxyAttachedData::getProxies.
void registerProxy(QQuickLayoutItemProxy *proxy)
QQuickLayoutItemProxyAttachedData::registerProxy registers a proxy that manages the item this data is...
void releaseControl(QQuickLayoutItemProxy *proxy)
QQuickLayoutItemProxyAttachedData::releaseControl is called by LayoutItemProxies when they try no lon...
bool proxyHasControl() const
QQuickLayoutItemProxyAttachedData::proxyHasControl.
bool takeControl(QQuickLayoutItemProxy *proxy)
QQuickLayoutItemProxyAttachedData::takeControl is called by LayoutItemProxies when they try to take c...
void releaseProxy(QQuickLayoutItemProxy *proxy)
QQuickLayoutItemProxyAttachedData::releaseProxy removes a proxy from a list of known proxies that man...
QQuickLayoutItemProxy * getControllingProxy() const
QQuickLayoutItemProxyAttachedData::getControllingProxy.
void clearTarget()
QQuickLayoutItemProxy::clearTarget sets the target to null.
void geometryChange(const QRectF &newGeom, const QRectF &oldGeom) override
QQuickLayoutItemProxy::geometryChange Reimplementation of QQuickItem::geometryChange to update the ta...
QQuickItem * target() const
\qmlproperty Item LayoutItemProxy::target
void itemChange(ItemChange c, const ItemChangeData &d) override
QQuickLayoutItemProxy::itemChange is a reimplementation of QQuickItem::itemChange to react to changes...
void maybeTakeControl()
QQuickLayoutItemProxy::maybeTakeControl checks and takes over control of the item.
#define connectPropertyForwarding(property, Property)
#define propertyForwarding(property, Property)