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
qquickstacklayout.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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 <limits>
8
9#include <QtQml/qqmlinfo.h>
10
11/*!
12 \qmltype StackLayout
13 //! \nativetype QQuickStackLayout
14 \inherits Item
15 \inqmlmodule QtQuick.Layouts
16 \ingroup layouts
17 \brief The StackLayout class provides a stack of items where
18 only one item is visible at a time.
19
20 To be able to use this type more efficiently, it is recommended that you
21 understand the general mechanism of the Qt Quick Layouts module. Refer to
22 \l{Qt Quick Layouts Overview} for more information.
23
24 The current visible item can be modified by setting the \l currentIndex property.
25 The index corresponds to the order of the StackLayout's children.
26
27 In contrast to most other layouts, child Items' \l{Layout::fillWidth}{Layout.fillWidth} and \l{Layout::fillHeight}{Layout.fillHeight} properties
28 default to \c true. As a consequence, child items are by default filled to match the size of the StackLayout as long as their
29 \l{Layout::maximumWidth}{Layout.maximumWidth} or \l{Layout::maximumHeight}{Layout.maximumHeight} does not prevent it.
30
31 Items are added to the layout by reparenting the item to the layout. Similarly, removal is done by reparenting the item from the layout.
32 Both of these operations will affect the layout's \l count property.
33
34 The following code will create a StackLayout where only the 'plum' rectangle is visible.
35 \code
36 StackLayout {
37 id: layout
38 anchors.fill: parent
39 currentIndex: 1
40 Rectangle {
41 color: 'teal'
42 implicitWidth: 200
43 implicitHeight: 200
44 }
45 Rectangle {
46 color: 'plum'
47 implicitWidth: 300
48 implicitHeight: 200
49 }
50 }
51 \endcode
52
53 Items in a StackLayout support these attached properties:
54 \list
55 \li \l{Layout::minimumWidth}{Layout.minimumWidth}
56 \li \l{Layout::minimumHeight}{Layout.minimumHeight}
57 \li \l{Layout::preferredWidth}{Layout.preferredWidth}
58 \li \l{Layout::preferredHeight}{Layout.preferredHeight}
59 \li \l{Layout::maximumWidth}{Layout.maximumWidth}
60 \li \l{Layout::maximumHeight}{Layout.maximumHeight}
61 \li \l{Layout::fillWidth}{Layout.fillWidth}
62 \li \l{Layout::fillHeight}{Layout.fillHeight}
63 \endlist
64
65 Read more about attached properties \l{QML Object Attributes}{here}.
66 \sa ColumnLayout
67 \sa GridLayout
68 \sa RowLayout
69 \sa {QtQuick.Controls::StackView}{StackView}
70 \sa {Qt Quick Layouts Overview}
71*/
72
73QT_BEGIN_NAMESPACE
74
75static QQuickStackLayoutAttached *attachedStackLayoutObject(QQuickItem *item, bool create = false)
76{
77 return static_cast<QQuickStackLayoutAttached*>(
78 qmlAttachedPropertiesObject<QQuickStackLayout>(item, create));
79}
80
81QQuickStackLayout::QQuickStackLayout(QQuickItem *parent) :
82 QQuickLayout(*new QQuickStackLayoutPrivate, parent)
83{
84}
85
86/*!
87 \qmlproperty int StackLayout::count
88 \readonly
89
90 This property holds the number of items that belong to the layout.
91
92 Only items that are children of the StackLayout will be candidates for layouting.
93*/
94int QQuickStackLayout::count() const
95{
96 Q_D(const QQuickStackLayout);
97 return d->count;
98}
99
100/*!
101 \qmlproperty int StackLayout::currentIndex
102
103 This property holds the index of the child item that is currently visible in the StackLayout.
104 By default it will be \c -1 for an empty layout, otherwise the default is \c 0 (referring to the first item).
105
106 Since 6.5, inserting/removing a new Item at an index less than or equal to the current index
107 will increment/decrement the current index, but keep the current Item.
108*/
109int QQuickStackLayout::currentIndex() const
110{
111 Q_D(const QQuickStackLayout);
112 return d->currentIndex;
113}
114
115void QQuickStackLayout::setCurrentIndex(int index)
116{
117 Q_D(QQuickStackLayout);
118 if (index == d->currentIndex)
119 return;
120
121 QQuickItem *prev = itemAt(d->currentIndex);
122 QQuickItem *next = itemAt(index);
123 d->currentIndex = index;
124 d->explicitCurrentIndex = true;
125 if (prev)
126 prev->setVisible(false);
127 if (next)
128 next->setVisible(true);
129
130 if (isComponentComplete()) {
131 rearrange(QSizeF(width(), height()));
132 emit currentIndexChanged();
133 }
134
135 // Update attached properties after emitting currentIndexChanged()
136 // to maintain a more sensible emission order.
137 if (prev) {
138 auto stackLayoutAttached = attachedStackLayoutObject(prev);
139 if (stackLayoutAttached)
140 stackLayoutAttached->setIsCurrentItem(false);
141 }
142 if (next) {
143 auto stackLayoutAttached = attachedStackLayoutObject(next);
144 if (stackLayoutAttached)
145 stackLayoutAttached->setIsCurrentItem(true);
146 }
147}
148
149void QQuickStackLayout::componentComplete()
150{
151 QQuickLayout::componentComplete(); // will call our geometryChange(), (where isComponentComplete() == true)
152
153 childItemsChanged();
154 invalidate();
155 ensureLayoutItemsUpdated(ApplySizeHints);
156
157 QQuickItem *par = parentItem();
158 if (qobject_cast<QQuickLayout*>(par))
159 return;
160
161 rearrange(QSizeF(width(), height()));
162}
163
164void QQuickStackLayout::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
165{
166 QQuickLayout::itemChange(change, value);
167 if (!isReady())
168 return;
169
170 if (change == ItemChildRemovedChange) {
171 QQuickItem *item = value.item;
172 auto stackLayoutAttached = attachedStackLayoutObject(item);
173 if (stackLayoutAttached) {
174 stackLayoutAttached->setLayout(nullptr);
175 stackLayoutAttached->setIndex(-1);
176 stackLayoutAttached->setIsCurrentItem(false);
177 }
178 m_cachedItemSizeHints.remove(item);
179 childItemsChanged(AdjustCurrentIndex); // removal; might have to adjust currentIndex
180 invalidate();
181 } else if (change == ItemChildAddedChange) {
182 childItemsChanged();
183 invalidate();
184 }
185}
186
187QSizeF QQuickStackLayout::sizeHint(Qt::SizeHint whichSizeHint) const
188{
189 Q_D(const QQuickStackLayout);
190 QSizeF &askingFor = m_cachedSizeHints[whichSizeHint];
191 if (!askingFor.isValid()) {
192 QSizeF &minS = m_cachedSizeHints[Qt::MinimumSize];
193 QSizeF &prefS = m_cachedSizeHints[Qt::PreferredSize];
194 QSizeF &maxS = m_cachedSizeHints[Qt::MaximumSize];
195
196 minS = QSizeF(0,0);
197 prefS = QSizeF(0,0);
198 maxS = QSizeF(std::numeric_limits<qreal>::infinity(), std::numeric_limits<qreal>::infinity());
199
200 const int count = itemCount();
201 for (int i = 0; i < count; ++i) {
202 SizeHints &hints = cachedItemSizeHints(i);
203 minS = minS.expandedTo(hints.min());
204 prefS = prefS.expandedTo(hints.pref());
205 //maxS = maxS.boundedTo(hints.max()); // Can be resized to be larger than any of its items.
206 // This is the same as QStackLayout does it.
207 // Not sure how descent makes sense here...
208 }
209 }
210 d->m_dirty = false;
211 return askingFor;
212}
213
214int QQuickStackLayout::indexOf(QQuickItem *childItem) const
215{
216 if (childItem) {
217 int indexOfItem = 0;
218 const auto items = childItems();
219 for (QQuickItem *item : items) {
220 if (shouldIgnoreItem(item))
221 continue;
222 if (childItem == item)
223 return indexOfItem;
224 ++indexOfItem;
225 }
226 }
227 return -1;
228}
229
230QQuickStackLayoutAttached *QQuickStackLayout::qmlAttachedProperties(QObject *object)
231{
232 return new QQuickStackLayoutAttached(object);
233}
234
235QQuickItem *QQuickStackLayout::itemAt(int index) const
236{
237 const auto items = childItems();
238 for (QQuickItem *item : items) {
239 if (shouldIgnoreItem(item))
240 continue;
241 if (index == 0)
242 return item;
243 --index;
244 }
245 return nullptr;
246}
247
248int QQuickStackLayout::itemCount() const
249{
250 int count = 0;
251 const auto items = childItems();
252 for (QQuickItem *item : items) {
253 if (shouldIgnoreItem(item))
254 continue;
255 ++count;
256 }
257 return count;
258}
259
260void QQuickStackLayout::setAlignment(QQuickItem * /*item*/, Qt::Alignment /*align*/)
261{
262 // ### Do we have to respect alignment?
263}
264
265void QQuickStackLayout::invalidate(QQuickItem *childItem)
266{
267 if (childItem) {
268 SizeHints &hints = m_cachedItemSizeHints[childItem];
269 hints.min() = QSizeF();
270 hints.pref() = QSizeF();
271 hints.max() = QSizeF();
272 }
273
274 for (int i = 0; i < Qt::NSizeHints; ++i)
275 m_cachedSizeHints[i] = QSizeF();
276 QQuickLayout::invalidate(this);
277
278 if (QQuickLayout *parentLayout = qobject_cast<QQuickLayout *>(parentItem()))
279 parentLayout->invalidate(this);
280}
281
282void QQuickStackLayout::childItemsChanged(AdjustCurrentIndexPolicy adjustCurrentIndexPolicy)
283{
284 Q_D(QQuickStackLayout);
285 const int count = itemCount();
286 const int oldIndex = d->currentIndex;
287 if (!d->explicitCurrentIndex)
288 d->currentIndex = (count > 0 ? 0 : -1);
289
290 if (adjustCurrentIndexPolicy == AdjustCurrentIndex) {
291 /*
292 * If an item is inserted or deleted at an index less than or equal to the current index it
293 * will affect the current index, but keep the current item. This is consistent with
294 * QStackedLayout, QStackedWidget and TabBar
295 *
296 * Unless the caller is componentComplete(), we can assume that only one of the children
297 * are visible, and we should keep that visible even if the stacking order has changed.
298 * This means that if the sibling order has changed (or an item stacked before the current
299 * item is added/removed), we must update the currentIndex so that it corresponds with the
300 * current visible item.
301 */
302 if (d->currentIndex < d->count) {
303 for (int i = 0; i < count; ++i) {
304 QQuickItem *child = itemAt(i);
305 if (child->isVisible()) {
306 d->currentIndex = i;
307 break;
308 }
309 }
310 }
311 }
312 if (d->currentIndex != oldIndex)
313 emit currentIndexChanged();
314
315 if (count != d->count) {
316 d->count = count;
317 emit countChanged();
318 }
319 for (int i = 0; i < count; ++i) {
320 QQuickItem *child = itemAt(i);
321 checkAnchors(child);
322 child->setVisible(d->currentIndex == i);
323
324 auto stackLayoutAttached = attachedStackLayoutObject(child);
325 if (stackLayoutAttached) {
326 stackLayoutAttached->setLayout(this);
327 stackLayoutAttached->setIndex(i);
328 stackLayoutAttached->setIsCurrentItem(d->currentIndex == i);
329 }
330 }
331}
332
333QQuickStackLayout::SizeHints &QQuickStackLayout::cachedItemSizeHints(int index) const
334{
335 QQuickItem *item = itemAt(index);
336 Q_ASSERT(item);
337 SizeHints &hints = m_cachedItemSizeHints[item]; // will create an entry if it doesn't exist
338 if (!hints.min().isValid())
339 QQuickStackLayout::collectItemSizeHints(item, hints.array);
340 return hints;
341}
342
343
344void QQuickStackLayout::rearrange(const QSizeF &newSize)
345{
346 Q_D(QQuickStackLayout);
347 if (newSize.isNull() || !newSize.isValid())
348 return;
349
350 qCDebug(lcQuickLayouts) << "QQuickStackLayout::rearrange";
351
352 if (d->currentIndex == -1 || d->currentIndex >= m_cachedItemSizeHints.size())
353 return;
354 QQuickStackLayout::SizeHints &hints = cachedItemSizeHints(d->currentIndex);
355 QQuickItem *item = itemAt(d->currentIndex);
356 Q_ASSERT(item);
357 item->setPosition(QPointF(0,0)); // ### respect alignment?
358 const QSizeF oldSize(item->width(), item->height());
359 const QSizeF effectiveNewSize = newSize.expandedTo(hints.min()).boundedTo(hints.max());
360 item->setSize(effectiveNewSize);
361 if (effectiveNewSize == oldSize)
362 item->polish();
363 QQuickLayout::rearrange(newSize);
364}
365
366void QQuickStackLayout::setStretchFactor(QQuickItem * /*item*/, int /*stretchFactor*/, Qt::Orientation /*orient*/)
367{
368}
369
370void QQuickStackLayout::collectItemSizeHints(QQuickItem *item, QSizeF *sizeHints)
371{
372 QQuickLayoutAttached *info = nullptr;
373 QQuickLayout::effectiveSizeHints_helper(item, sizeHints, &info, true);
374 if (!info)
375 return;
376 if (info->isFillWidthSet() && !info->fillWidth()) {
377 const qreal pref = sizeHints[Qt::PreferredSize].width();
378 sizeHints[Qt::MinimumSize].setWidth(pref);
379 sizeHints[Qt::MaximumSize].setWidth(pref);
380 }
381
382 if (info->isFillHeightSet() && !info->fillHeight()) {
383 const qreal pref = sizeHints[Qt::PreferredSize].height();
384 sizeHints[Qt::MinimumSize].setHeight(pref);
385 sizeHints[Qt::MaximumSize].setHeight(pref);
386 }
387}
388
389bool QQuickStackLayout::shouldIgnoreItem(QQuickItem *item) const
390{
391 return QQuickItemPrivate::get(item)->isTransparentForPositioner();
392}
393
394void QQuickStackLayout::itemSiblingOrderChanged(QQuickItem *)
395{
396 if (!isReady())
397 return;
398 childItemsChanged(AdjustCurrentIndex);
399 invalidate();
400}
401
402QQuickStackLayoutAttached::QQuickStackLayoutAttached(QObject *object)
403{
404 auto item = qobject_cast<QQuickItem*>(object);
405 if (!item) {
406 qmlWarning(object) << "StackLayout attached property must be attached to an object deriving from Item";
407 return;
408 }
409
410 auto stackLayout = qobject_cast<QQuickStackLayout*>(item->parentItem());
411 if (!stackLayout) {
412 // It might not be a child of a StackLayout yet, and that's OK.
413 // The index will get set by updateLayoutItems() when it's reparented.
414 return;
415 }
416
417 if (!stackLayout->isComponentComplete()) {
418 // Don't try to get the index if the StackLayout itself hasn't loaded yet.
419 return;
420 }
421
422 // If we got this far, the item was added as a child to the StackLayout after it loaded.
423 const int index = stackLayout->indexOf(item);
424 setLayout(stackLayout);
425 setIndex(index);
426 setIsCurrentItem(stackLayout->currentIndex() == index);
427
428 // In case of lazy loading in loader, attachedProperties are created and updated for the
429 // object after adding the child object to the stack layout, which leads to entries with
430 // same index. Triggering childItemsChanged() resets to right index in the stack layout.
431 stackLayout->childItemsChanged();
432}
433
434/*!
435 \qmlattachedproperty int StackLayout::index
436 \readonly
437
438 This attached property holds the index of each child item in the
439 \l StackLayout.
440
441 \sa isCurrentItem, layout
442
443 \since QtQuick.Layouts 1.15
444*/
445int QQuickStackLayoutAttached::index() const
446{
447 return m_index;
448}
449
450void QQuickStackLayoutAttached::setIndex(int index)
451{
452 if (index == m_index)
453 return;
454
455 m_index = index;
456 emit indexChanged();
457}
458
459/*!
460 \qmlattachedproperty bool StackLayout::isCurrentItem
461 \readonly
462
463 This attached property is \c true if this child is the current item
464 in the \l StackLayout.
465
466 \sa index, layout
467
468 \since QtQuick.Layouts 1.15
469*/
470bool QQuickStackLayoutAttached::isCurrentItem() const
471{
472 return m_isCurrentItem;
473}
474
475void QQuickStackLayoutAttached::setIsCurrentItem(bool isCurrentItem)
476{
477 if (isCurrentItem == m_isCurrentItem)
478 return;
479
480 m_isCurrentItem = isCurrentItem;
481 emit isCurrentItemChanged();
482}
483
484/*!
485 \qmlattachedproperty StackLayout StackLayout::layout
486 \readonly
487
488 This attached property holds the \l StackLayout that manages this child
489 item.
490
491 \sa index, isCurrentItem
492
493 \since QtQuick.Layouts 1.15
494*/
495QQuickStackLayout *QQuickStackLayoutAttached::layout() const
496{
497 return m_layout;
498}
499
500void QQuickStackLayoutAttached::setLayout(QQuickStackLayout *layout)
501{
502 if (layout == m_layout)
503 return;
504
505 m_layout = layout;
506 emit layoutChanged();
507}
508
509QT_END_NAMESPACE
510
511#include "moc_qquickstacklayout_p.cpp"