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 {Narrow window with stacked layout and wide window with
68 side-by-side layout}
69 \enddiv
70
71 The LayoutItemProxy can also be used without layouts, e.g. by anchoring it
72 to different items. A mix of real \l {Item}{Items} and proxy items is
73 equally possible, as well as nested structures of layouts and items.
74
75 \warning The LayoutItemProxy will set the parent of its target to itself.
76 Keep this in mind when referring to the parent of the target item.
77
78 \sa Item, GridLayout, RowLayout, ColumnLayout
79*/
80
81Q_STATIC_LOGGING_CATEGORY(lcLayouts, "qt.quick.layouts")
82
83
84QQuickLayoutItemProxy::QQuickLayoutItemProxy(QQuickItem *parent)
85 : QQuickItem(*new QQuickLayoutItemProxyPrivate, parent)
86{
87
88}
89
91{
92 Q_D(QQuickLayoutItemProxy);
93
94 if (!d->target)
95 return;
96
97 QQuickLayoutItemProxyAttachedData * attachedData = d->target->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>();
98 // De-register this proxy from the proxies controlling the target
99 if (attachedData) {
100 if (attachedData->getControllingProxy() == this) {
101 attachedData->releaseControl(this);
102 d->target->setParentItem(nullptr);
103 }
104 attachedData->releaseProxy(this);
105 }
106 // The target item still has a QObject parent that takes care of its destrctuion.
107 // No need to invoke destruction of the target tiem from here.
108}
109
110/*! \internal
111 \brief QQuickLayoutItemProxy::geometryChange Reimplementation of
112 QQuickItem::geometryChange to update the target geometry too.
113*/
114void QQuickLayoutItemProxy::geometryChange(const QRectF &newGeom, const QRectF &oldGeom)
115{
116 QQuickItem::geometryChange(newGeom, oldGeom);
117 if (!isVisible())
118 return;
119
120 const QSizeF sz = newGeom.size();
121 QPointF pos(0., 0.);
122
123 if (QQuickItem *t = effectiveTarget()) {
124 if (QQuickLayoutItemProxyAttachedData * attachedData = target()->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>()) {
125 if (attachedData->getControllingProxy() != this)
126 return;
127 }
128
129 // Should normally not be the case, except the user resets the parent
130 // This is a failsave for this case and positions the item correctly
131 if (t->parentItem() != this)
132 pos = t->parentItem()->mapFromGlobal(mapToGlobal(0, 0));
133
134 if (t->size() == sz && t->position() == pos && newGeom == oldGeom)
135 return;
136
137 t->setSize(sz);
138 t->setPosition(pos);
139 }
140}
141
142/*! \internal
143 \brief QQuickLayoutItemProxy::itemChange is a reimplementation of
144 QQuickItem::itemChange to react to changes in visibility.
145*/
146void QQuickLayoutItemProxy::itemChange(ItemChange c, const ItemChangeData &d)
147{
148 if (c == QQuickItem::ItemVisibleHasChanged)
149 {
151 }
152 QQuickItem::itemChange(c, d);
153}
154
155// Implementation of the slots to react to changes of the Layout attached properties.
156// If the target Layout propertie change, we change the proxy Layout properties accordingly
157// If the proxy Layout properties have been changed externally, we want to remove this binding.
158// The member variables m_expectProxy##Property##Change help us keep track about who invokes
159// the change of the parameter. If it is invoked by the target we expect a proxy property
160// change and will not remove the connection.
161#define propertyForwarding(property, Property)
162 void QQuickLayoutItemProxy::target##Property##Changed() {
163 Q_D(QQuickLayoutItemProxy);
164 QQuickLayoutAttached *attTarget = attachedLayoutObject(target(), false);
165 QQuickLayoutAttached *attProxy = attachedLayoutObject(this, false);
166 if (!attTarget) return;
167 if (attProxy->property() == attTarget->property())
168 return;
169 d->m_expectProxy##Property##Change = true;
170 attProxy->set##Property(attTarget->property());
171 }
172 void QQuickLayoutItemProxy::proxy##Property##Changed() {
173 Q_D(QQuickLayoutItemProxy);
174 if (d->m_expectProxy##Property##Change) {
175 d->m_expectProxy##Property##Change = false;
176 return;
177 }
178 QQuickLayoutAttached *attTarget = attachedLayoutObject(target(), false);
179 if (!attTarget) return;
180 disconnect(attTarget, &QQuickLayoutAttached::property##Changed, this, &QQuickLayoutItemProxy::target##Property##Changed);
181 }
182
183propertyForwarding(minimumWidth, MinimumWidth)
184propertyForwarding(minimumHeight, MinimumHeight)
185propertyForwarding(preferredWidth, PreferredWidth)
186propertyForwarding(preferredHeight, PreferredHeight)
187propertyForwarding(maximumWidth, MaximumWidth)
188propertyForwarding(maximumHeight, MaximumHeight)
189propertyForwarding(fillWidth, FillWidth)
190propertyForwarding(fillHeight, FillHeight)
191propertyForwarding(alignment, Alignment)
192propertyForwarding(horizontalStretchFactor, HorizontalStretchFactor)
193propertyForwarding(verticalStretchFactor, VerticalStretchFactor)
194propertyForwarding(margins, Margins)
195propertyForwarding(leftMargin, LeftMargin)
196propertyForwarding(topMargin, TopMargin)
197propertyForwarding(rightMargin, RightMargin)
198propertyForwarding(bottomMargin, BottomMargin)
199
200#undef propertyForwarding
201
202/*!
203 \qmlproperty Item LayoutItemProxy::target
204
205 This property holds the \l Item that the proxy should represent in a
206 \l {Layout} hierarchy.
207*/
208
209/*! \internal
210 \brief QQuickLayoutItemProxy::target
211 \return The target item of the proxy
212*/
214{
215 Q_D(const QQuickLayoutItemProxy);
216 return d->target;
217}
218
219/*! \internal
220 \brief QQuickLayoutItemProxy::setTarget sets the target
221 \param newTarget The item that the proxy stands in place for.
222
223 All layout properties of the target are connected to the layout properties
224 of the LayoutItemProxy. It the LayoutItemProxy is visible, it will try to
225 take control of the target.
226*/
227void QQuickLayoutItemProxy::setTarget(QQuickItem *newTarget)
228{
229 Q_D(QQuickLayoutItemProxy);
230
231 if (newTarget == d->target)
232 return;
233
234 if (d->target && d->target->property("QQuickLayoutItemProxyAttachedData").isValid()) {
235 QQuickLayoutItemProxyAttachedData *attachedData = d->target->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>();
236 attachedData->releaseProxy(this);
237 }
238 d->target = newTarget;
239
240 if (newTarget) {
241
242 QQuickLayoutItemProxyAttachedData *attachedData;
243 if (newTarget->property("QQuickLayoutItemProxyAttachedData").isValid()) {
244 attachedData = newTarget->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>();
245 } else {
246 attachedData = new QQuickLayoutItemProxyAttachedData(newTarget);
247 QVariant v;
248 v.setValue(attachedData);
249 newTarget->setProperty("QQuickLayoutItemProxyAttachedData", v);
250 }
251 attachedData->registerProxy(this);
252
253 // If there is no other controlling proxy, we will hide the target
254 if (!attachedData->proxyHasControl())
255 newTarget->setVisible(false);
256 // We are calling maybeTakeControl at the end to eventually take
257 // responsibility of showing the target.
258
259 if (QQuickLayoutAttached *attTarget = attachedLayoutObject(newTarget)) {
260 QQuickLayoutAttached *attProxy = attachedLayoutObject(this, true);
261
262 disconnect(attTarget, nullptr, attProxy, nullptr);
263
264 // bind item-specific layout properties:
265
266#define connectPropertyForwarding(property, Property)
267 if (!attProxy->is##Property##Set()) {
268 connect(attTarget, &QQuickLayoutAttached::property##Changed, this, &QQuickLayoutItemProxy::target##Property##Changed);
269 connect(attProxy, &QQuickLayoutAttached::property##Changed, this, &QQuickLayoutItemProxy::proxy##Property##Changed);
270 target##Property##Changed();
271 }
272 connectPropertyForwarding(minimumWidth, MinimumWidth)
273 connectPropertyForwarding(minimumHeight, MinimumHeight)
274 connectPropertyForwarding(preferredWidth, PreferredWidth)
275 connectPropertyForwarding(preferredHeight, PreferredHeight)
276 connectPropertyForwarding(maximumWidth, MaximumWidth)
277 connectPropertyForwarding(maximumHeight, MaximumHeight)
278 connectPropertyForwarding(fillWidth, FillWidth)
279 connectPropertyForwarding(fillHeight, FillHeight)
280 connectPropertyForwarding(alignment, Alignment)
281 connectPropertyForwarding(horizontalStretchFactor, HorizontalStretchFactor)
282 connectPropertyForwarding(verticalStretchFactor, VerticalStretchFactor)
283 connectPropertyForwarding(margins, Margins)
284 connectPropertyForwarding(leftMargin, LeftMargin)
285 connectPropertyForwarding(topMargin, TopMargin)
286 connectPropertyForwarding(rightMargin, RightMargin)
287 connectPropertyForwarding(bottomMargin, BottomMargin)
288#undef connectPropertyForwarding
289
290 // proxy.implicitWidth: target.implicitWidth
291 auto fnBindImplW = [newTarget, this](){ this->setImplicitWidth(newTarget->implicitWidth()); };
292 fnBindImplW();
293 connect(newTarget, &QQuickItem::implicitWidthChanged, fnBindImplW);
294
295 // proxy.implicitHeight: target.implicitHeight
296 auto fnBindImplH = [newTarget, this](){ this->setImplicitHeight(newTarget->implicitHeight()); };
297 fnBindImplH();
298 connect(newTarget, &QQuickItem::implicitHeightChanged, fnBindImplH);
299 }
300 }
301
302 if (isVisible())
303 maybeTakeControl();
304
305 emit targetChanged();
306}
307
308/*! \internal
309 \brief QQuickLayoutItemProxy::effectiveTarget
310 \return The target item of the proxy if it is in control, \c null otherwise.
311*/
312QQuickItem *QQuickLayoutItemProxy::effectiveTarget() const
313{
314 if (target() == nullptr)
315 return nullptr;
316
317 QQuickLayoutItemProxyAttachedData * attachedData = target()->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>();
318 return (attachedData->getControllingProxy() == this) ? target() : nullptr;
319}
320
321/*! \internal
322 \brief QQuickLayoutItemProxy::clearTarget sets the target to null.
323
324 This function is called if the target is destroyed to make sure we do not
325 try to access a non-existing object.
326*/
328{
329 setTarget(nullptr);
330}
331
332/*! \internal
333 \brief QQuickLayoutItemProxy::maybeTakeControl checks and takes over control
334 of the item.
335
336 If the proxy is visible it will try to take control over the target and set
337 its visibility to true. If the proxy is hidden it will also hide the target
338 and another LayoutItemProxy has to set the visibility to \c true or the
339 target will stay invisible.
340*/
342{
343 Q_D(QQuickLayoutItemProxy);
344 if (!d->target)
345 return;
346
347 QQuickLayoutItemProxyAttachedData * attachedData = d->target->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>();
348 if (isVisible() && attachedData->getControllingProxy() != this) {
349 if (attachedData->takeControl(this)) {
350 d->target->setVisible(true);
351 d->target->setParentItem(this);
352 updatePos();
353 }
354 }
355 if (!isVisible() && attachedData->getControllingProxy() == this){
356 if (d->target->parentItem() == this) {
357 d->target->setParentItem(nullptr);
358 } else
359 qCDebug(lcLayouts) << "Parent was changed to" << d->target->parentItem() << "while an ItemProxy had control";
360 d->target->setVisible(false);
361 attachedData->releaseControl(this);
362 }
363}
364
365/*! \internal
366 \brief QQuickLayoutItemProxy::updatePos sets the geometry of the target to
367 the geometry of the proxy
368*/
369void QQuickLayoutItemProxy::updatePos()
370{
371 if (!isVisible())
372 return;
373 if (target()) {
374 if (QQuickLayoutItemProxyAttachedData * attachedData = target()->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>()) {
375 if (attachedData->getControllingProxy() == this)
376 geometryChange(boundingRect(), boundingRect());
377 }
378 }
379}
380
402
403/*! \internal
404 \class QQuickLayoutItemProxyAttachedData
405 \brief Provides attached properties for items that are managed by one or
406 more LayoutItemProxy.
407
408 It stores all proxies that target the item, and will emit signals when the
409 proxies or the controlling proxy changes. Proxies can listen to the signal
410 and pick up control if they wish to.
411*/
412QQuickLayoutItemProxyAttachedData::QQuickLayoutItemProxyAttachedData(QObject *parent)
413 : QObject(parent), controllingProxy(nullptr)
414{
415
416}
417
419{
420 if (QObject *par = parent())
421 par->setProperty("QQuickLayoutItemProxyAttachedData", QVariant());
422
423 // If this is destroyed, so is the target. Clear the target from the
424 // proxies so they do not try to access a destroyed object
425 for (auto &proxy: std::as_const(proxies))
426 proxy->clearTarget();
427}
428
429/*! \internal
430 \brief QQuickLayoutItemProxyAttachedData::registerProxy registers a proxy
431 that manages the item this data is attached to.
432
433 This is required to easily notify proxies when the target is destroyed or
434 when it is free to take over control.
435*/
437{
438 if (proxies.contains(proxy))
439 return;
440
441 proxies.append(proxy);
442 emit proxiesChanged();
443}
444
445/*! \internal
446 \brief QQuickLayoutItemProxyAttachedData::releaseProxy removes a proxy from
447 a list of known proxies that manage the item this data is attached to.
448*/
450{
451 if (proxy == controllingProxy)
452 releaseControl(proxy);
453
454 proxies.removeAll(proxy);
455
456 if (proxies.isEmpty())
457 deleteLater();
458
459 emit proxiesChanged();
460}
461
462/*! \internal
463 \brief QQuickLayoutItemProxyAttachedData::takeControl is called by
464 LayoutItemProxies when they try to take control over the item this data is
465 attached to.
466 \return \c true if no other proxy controls the item and if control is
467 granted to the proxy, \c false otherwise.
468
469 \param proxy The proxy that tries to take control.
470*/
472{
473 if (controllingProxy || !proxies.contains(proxy))
474 return false;
475
476 qCDebug(lcLayouts) << proxy
477 << "takes control of"
478 << parent();
479
480 controllingProxy = proxy;
481 emit controlTaken();
482 emit controllingProxyChanged();
483 return true;
484}
485
486/*! \internal
487 \brief QQuickLayoutItemProxyAttachedData::releaseControl is called by
488 LayoutItemProxies when they try no longer control the item
489
490 \param proxy The proxy that gives up control.
491*/
493{
494 if (controllingProxy != proxy)
495 return;
496
497 qCDebug(lcLayouts) << proxy
498 << "no longer controls"
499 << parent();
500
501 controllingProxy = nullptr;
502 emit controlReleased();
503 emit controllingProxyChanged();
504
505 for (auto &otherProxy: std::as_const(proxies)) {
506 if (proxy != otherProxy)
507 otherProxy->maybeTakeControl();
508 }
509}
510
511/*! \internal
512 \brief QQuickLayoutItemProxyAttachedData::getControllingProxy
513 \return the proxy that currently controls the item this data is attached to.
514 Returns \c null if no proxy controls the item.
515*/
517{
518 return controllingProxy;
519}
520
521/*! \internal
522 \brief QQuickLayoutItemProxyAttachedData::getProxies
523 \return a list of all proxies that target the item this data is attached to.
524*/
526{
527 using Type = QQuickLayoutItemProxy;
528 using Property = QQmlListProperty<Type>;
529
530 return Property(
531 this, &proxies,
532 [](Property *p) { return static_cast<QList<Type *> *>(p->data)->size(); },
533 [](Property *p, qsizetype i) { return static_cast<QList<Type *> *>(p->data)->at(i); }
534 );
535}
536
537/*! \internal
538 \brief QQuickLayoutItemProxyAttachedData::proxyHasControl
539 \return \c true if a proxy is controlling the item, \c false otherwise.
540*/
542{
543 return controllingProxy != nullptr;
544}
545
546QT_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.
Combined button and popup list for selecting options.
#define connectPropertyForwarding(property, Property)
#define propertyForwarding(property, Property)