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,
294 this, std::move(fnBindImplW));
295
296 // proxy.implicitHeight: target.implicitHeight
297 auto fnBindImplH = [newTarget, this](){ this->setImplicitHeight(newTarget->implicitHeight()); };
298 fnBindImplH();
299 connect(newTarget, &QQuickItem::implicitHeightChanged,
300 this, std::move(fnBindImplH));
301 }
302 }
303
304 if (isVisible())
305 maybeTakeControl();
306
307 emit targetChanged();
308}
309
310/*! \internal
311 \brief QQuickLayoutItemProxy::effectiveTarget
312 \return The target item of the proxy if it is in control, \c null otherwise.
313*/
314QQuickItem *QQuickLayoutItemProxy::effectiveTarget() const
315{
316 if (target() == nullptr)
317 return nullptr;
318
319 QQuickLayoutItemProxyAttachedData * attachedData = target()->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>();
320 return (attachedData->getControllingProxy() == this) ? target() : nullptr;
321}
322
323/*! \internal
324 \brief QQuickLayoutItemProxy::clearTarget sets the target to null.
325
326 This function is called if the target is destroyed to make sure we do not
327 try to access a non-existing object.
328*/
330{
331 setTarget(nullptr);
332}
333
334/*! \internal
335 \brief QQuickLayoutItemProxy::maybeTakeControl checks and takes over control
336 of the item.
337
338 If the proxy is visible it will try to take control over the target and set
339 its visibility to true. If the proxy is hidden it will also hide the target
340 and another LayoutItemProxy has to set the visibility to \c true or the
341 target will stay invisible.
342*/
344{
345 Q_D(QQuickLayoutItemProxy);
346 if (!d->target)
347 return;
348
349 QQuickLayoutItemProxyAttachedData * attachedData = d->target->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>();
350 if (isVisible() && attachedData->getControllingProxy() != this) {
351 if (attachedData->takeControl(this)) {
352 d->target->setVisible(true);
353 d->target->setParentItem(this);
354 updatePos();
355 }
356 }
357 if (!isVisible() && attachedData->getControllingProxy() == this){
358 if (d->target->parentItem() == this) {
359 d->target->setParentItem(nullptr);
360 } else
361 qCDebug(lcLayouts) << "Parent was changed to" << d->target->parentItem() << "while an ItemProxy had control";
362 d->target->setVisible(false);
363 attachedData->releaseControl(this);
364 }
365}
366
367/*! \internal
368 \brief QQuickLayoutItemProxy::updatePos sets the geometry of the target to
369 the geometry of the proxy
370*/
371void QQuickLayoutItemProxy::updatePos()
372{
373 if (!isVisible())
374 return;
375 if (target()) {
376 if (QQuickLayoutItemProxyAttachedData * attachedData = target()->property("QQuickLayoutItemProxyAttachedData").value<QQuickLayoutItemProxyAttachedData*>()) {
377 if (attachedData->getControllingProxy() == this)
378 geometryChange(boundingRect(), boundingRect());
379 }
380 }
381}
382
404
405/*! \internal
406 \class QQuickLayoutItemProxyAttachedData
407 \brief Provides attached properties for items that are managed by one or
408 more LayoutItemProxy.
409
410 It stores all proxies that target the item, and will emit signals when the
411 proxies or the controlling proxy changes. Proxies can listen to the signal
412 and pick up control if they wish to.
413*/
414QQuickLayoutItemProxyAttachedData::QQuickLayoutItemProxyAttachedData(QObject *parent)
415 : QObject(parent), controllingProxy(nullptr)
416{
417
418}
419
421{
422 if (QObject *par = parent())
423 par->setProperty("QQuickLayoutItemProxyAttachedData", QVariant());
424
425 // If this is destroyed, so is the target. Clear the target from the
426 // proxies so they do not try to access a destroyed object
427 for (auto &proxy: std::as_const(proxies))
428 proxy->clearTarget();
429}
430
431/*! \internal
432 \brief QQuickLayoutItemProxyAttachedData::registerProxy registers a proxy
433 that manages the item this data is attached to.
434
435 This is required to easily notify proxies when the target is destroyed or
436 when it is free to take over control.
437*/
439{
440 if (proxies.contains(proxy))
441 return;
442
443 proxies.append(proxy);
444 emit proxiesChanged();
445}
446
447/*! \internal
448 \brief QQuickLayoutItemProxyAttachedData::releaseProxy removes a proxy from
449 a list of known proxies that manage the item this data is attached to.
450*/
452{
453 if (proxy == controllingProxy)
454 releaseControl(proxy);
455
456 proxies.removeAll(proxy);
457
458 if (proxies.isEmpty())
459 deleteLater();
460
461 emit proxiesChanged();
462}
463
464/*! \internal
465 \brief QQuickLayoutItemProxyAttachedData::takeControl is called by
466 LayoutItemProxies when they try to take control over the item this data is
467 attached to.
468 \return \c true if no other proxy controls the item and if control is
469 granted to the proxy, \c false otherwise.
470
471 \param proxy The proxy that tries to take control.
472*/
474{
475 if (controllingProxy || !proxies.contains(proxy))
476 return false;
477
478 qCDebug(lcLayouts) << proxy
479 << "takes control of"
480 << parent();
481
482 controllingProxy = proxy;
483 emit controlTaken();
484 emit controllingProxyChanged();
485 return true;
486}
487
488/*! \internal
489 \brief QQuickLayoutItemProxyAttachedData::releaseControl is called by
490 LayoutItemProxies when they try no longer control the item
491
492 \param proxy The proxy that gives up control.
493*/
495{
496 if (controllingProxy != proxy)
497 return;
498
499 qCDebug(lcLayouts) << proxy
500 << "no longer controls"
501 << parent();
502
503 controllingProxy = nullptr;
504 emit controlReleased();
505 emit controllingProxyChanged();
506
507 for (auto &otherProxy: std::as_const(proxies)) {
508 if (proxy != otherProxy)
509 otherProxy->maybeTakeControl();
510 }
511}
512
513/*! \internal
514 \brief QQuickLayoutItemProxyAttachedData::getControllingProxy
515 \return the proxy that currently controls the item this data is attached to.
516 Returns \c null if no proxy controls the item.
517*/
519{
520 return controllingProxy;
521}
522
523/*! \internal
524 \brief QQuickLayoutItemProxyAttachedData::getProxies
525 \return a list of all proxies that target the item this data is attached to.
526*/
528{
529 using Type = QQuickLayoutItemProxy;
530 using Property = QQmlListProperty<Type>;
531
532 return Property(
533 this, &proxies,
534 [](Property *p) { return static_cast<QList<Type *> *>(p->data)->size(); },
535 [](Property *p, qsizetype i) { return static_cast<QList<Type *> *>(p->data)->at(i); }
536 );
537}
538
539/*! \internal
540 \brief QQuickLayoutItemProxyAttachedData::proxyHasControl
541 \return \c true if a proxy is controlling the item, \c false otherwise.
542*/
544{
545 return controllingProxy != nullptr;
546}
547
548QT_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)