1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Quick Layouts module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickstacklayout_p.h"
41#include <limits>
42
43/*!
44 \qmltype StackLayout
45 \instantiates QQuickStackLayout
46 \inherits Item
47 \inqmlmodule QtQuick.Layouts
48 \ingroup layouts
49 \brief The StackLayout class provides a stack of items where
50 only one item is visible at a time.
51
52 The current visible item can be modified by setting the \l currentIndex property.
53 The index corresponds to the order of the StackLayout's children.
54
55 In contrast to most other layouts, child Items' \l{Layout::fillWidth}{Layout.fillWidth} and \l{Layout::fillHeight}{Layout.fillHeight} properties
56 default to \c true. As a consequence, child items are by default filled to match the size of the StackLayout as long as their
57 \l{Layout::maximumWidth}{Layout.maximumWidth} or \l{Layout::maximumHeight}{Layout.maximumHeight} does not prevent it.
58
59 Items are added to the layout by reparenting the item to the layout. Similarly, removal is done by reparenting the item from the layout.
60 Both of these operations will affect the layout's \l count property.
61
62 The following code will create a StackLayout where only the 'plum' rectangle is visible.
63 \code
64 StackLayout {
65 id: layout
66 anchors.fill: parent
67 currentIndex: 1
68 Rectangle {
69 color: 'teal'
70 implicitWidth: 200
71 implicitHeight: 200
72 }
73 Rectangle {
74 color: 'plum'
75 implicitWidth: 300
76 implicitHeight: 200
77 }
78 }
79 \endcode
80
81 Items in a StackLayout support these attached properties:
82 \list
83 \li \l{Layout::minimumWidth}{Layout.minimumWidth}
84 \li \l{Layout::minimumHeight}{Layout.minimumHeight}
85 \li \l{Layout::preferredWidth}{Layout.preferredWidth}
86 \li \l{Layout::preferredHeight}{Layout.preferredHeight}
87 \li \l{Layout::maximumWidth}{Layout.maximumWidth}
88 \li \l{Layout::maximumHeight}{Layout.maximumHeight}
89 \li \l{Layout::fillWidth}{Layout.fillWidth}
90 \li \l{Layout::fillHeight}{Layout.fillHeight}
91 \endlist
92
93 Read more about attached properties \l{QML Object Attributes}{here}.
94 \sa ColumnLayout
95 \sa GridLayout
96 \sa RowLayout
97 \sa {QtQuick.Controls::StackView}{StackView}
98*/
99
100QT_BEGIN_NAMESPACE
101
102QQuickStackLayout::QQuickStackLayout(QQuickItem *parent) :
103 QQuickLayout(*new QQuickStackLayoutPrivate, parent)
104{
105}
106
107/*!
108 \qmlproperty int StackLayout::count
109
110 This property holds the number of items that belong to the layout.
111
112 Only items that are children of the StackLayout will be candidates for layouting.
113*/
114int QQuickStackLayout::count() const
115{
116 Q_D(const QQuickStackLayout);
117 ensureLayoutItemsUpdated();
118 return d->count;
119}
120
121/*!
122 \qmlproperty int StackLayout::currentIndex
123
124 This property holds the index of the child item that is currently visible in the StackLayout.
125 By default it will be \c -1 for an empty layout, otherwise the default is \c 0 (referring to the first item).
126*/
127int QQuickStackLayout::currentIndex() const
128{
129 Q_D(const QQuickStackLayout);
130 ensureLayoutItemsUpdated();
131 return d->currentIndex;
132}
133
134void QQuickStackLayout::setCurrentIndex(int index)
135{
136 Q_D(QQuickStackLayout);
137 if (index != d->currentIndex) {
138 ensureLayoutItemsUpdated();
139 QQuickItem *prev = itemAt(index: d->currentIndex);
140 QQuickItem *next = itemAt(index);
141 d->currentIndex = index;
142 d->explicitCurrentIndex = true;
143 if (prev)
144 prev->setVisible(false);
145 if (next)
146 next->setVisible(true);
147
148 if (isComponentComplete()) {
149 rearrange(QSizeF(width(), height()));
150 emit currentIndexChanged();
151 }
152 }
153}
154
155
156void QQuickStackLayout::componentComplete()
157{
158 QQuickLayout::componentComplete(); // will call our geometryChange(), (where isComponentComplete() == true)
159
160 ensureLayoutItemsUpdated();
161
162 QQuickItem *par = parentItem();
163 if (qobject_cast<QQuickLayout*>(object: par))
164 return;
165
166 rearrange(QSizeF(width(), height()));
167}
168
169void QQuickStackLayout::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
170{
171 QQuickLayout::itemChange(change, value);
172
173 if (change == ItemChildRemovedChange) {
174 invalidate();
175 } else if (change == ItemChildAddedChange) {
176 invalidate();
177 }
178}
179
180QSizeF QQuickStackLayout::sizeHint(Qt::SizeHint whichSizeHint) const
181{
182 Q_D(const QQuickStackLayout);
183 ensureLayoutItemsUpdated();
184 QSizeF &askingFor = m_cachedSizeHints[whichSizeHint];
185 if (!askingFor.isValid()) {
186 QSizeF &minS = m_cachedSizeHints[Qt::MinimumSize];
187 QSizeF &prefS = m_cachedSizeHints[Qt::PreferredSize];
188 QSizeF &maxS = m_cachedSizeHints[Qt::MaximumSize];
189
190 minS = QSizeF(0,0);
191 prefS = QSizeF(0,0);
192 maxS = QSizeF(std::numeric_limits<qreal>::infinity(), std::numeric_limits<qreal>::infinity());
193
194 const int count = itemCount();
195 m_cachedItemSizeHints.resize(size: count);
196 for (int i = 0; i < count; ++i) {
197 SizeHints &hints = m_cachedItemSizeHints[i];
198 QQuickStackLayout::collectItemSizeHints(item: itemAt(index: i), sizeHints: hints.array);
199 minS = minS.expandedTo(otherSize: hints.min());
200 prefS = prefS.expandedTo(otherSize: hints.pref());
201 //maxS = maxS.boundedTo(hints.max()); // Can be resized to be larger than any of its items.
202 // This is the same as QStackLayout does it.
203 // Not sure how descent makes sense here...
204 }
205 }
206 d->m_dirty = false;
207 return askingFor;
208}
209
210int QQuickStackLayout::indexOf(QQuickItem *childItem) const
211{
212 ensureLayoutItemsUpdated();
213 if (childItem) {
214 int indexOfItem = 0;
215 const auto items = childItems();
216 for (QQuickItem *item : items) {
217 if (shouldIgnoreItem(item))
218 continue;
219 if (childItem == item)
220 return indexOfItem;
221 ++indexOfItem;
222 }
223 }
224 return -1;
225}
226
227QQuickItem *QQuickStackLayout::itemAt(int index) const
228{
229 const auto items = childItems();
230 for (QQuickItem *item : items) {
231 if (shouldIgnoreItem(item))
232 continue;
233 if (index == 0)
234 return item;
235 --index;
236 }
237 return nullptr;
238}
239
240int QQuickStackLayout::itemCount() const
241{
242 int count = 0;
243 const auto items = childItems();
244 for (QQuickItem *item : items) {
245 if (shouldIgnoreItem(item))
246 continue;
247 ++count;
248 }
249 return count;
250}
251
252void QQuickStackLayout::setAlignment(QQuickItem * /*item*/, Qt::Alignment /*align*/)
253{
254 // ### Do we have to respect alignment?
255}
256
257void QQuickStackLayout::invalidate(QQuickItem *childItem)
258{
259 const int indexOfChild = indexOf(childItem);
260 if (indexOfChild >= 0 && indexOfChild < m_cachedItemSizeHints.count()) {
261 m_cachedItemSizeHints[indexOfChild].min() = QSizeF();
262 m_cachedItemSizeHints[indexOfChild].pref() = QSizeF();
263 m_cachedItemSizeHints[indexOfChild].max() = QSizeF();
264 }
265
266 for (int i = 0; i < Qt::NSizeHints; ++i)
267 m_cachedSizeHints[i] = QSizeF();
268 QQuickLayout::invalidate(childItem: this);
269
270 if (QQuickLayout *parentLayout = qobject_cast<QQuickLayout *>(object: parentItem()))
271 parentLayout->invalidate(childItem: this);
272}
273
274void QQuickStackLayout::updateLayoutItems()
275{
276 Q_D(QQuickStackLayout);
277 d->m_ignoredItems.clear();
278 const int count = itemCount();
279 int oldIndex = d->currentIndex;
280 if (!d->explicitCurrentIndex)
281 d->currentIndex = (count > 0 ? 0 : -1);
282
283 if (d->currentIndex != oldIndex)
284 emit currentIndexChanged();
285
286 if (count != d->count) {
287 d->count = count;
288 emit countChanged();
289 }
290 for (int i = 0; i < count; ++i) {
291 QQuickItem *child = itemAt(index: i);
292 checkAnchors(item: child);
293 child->setVisible(d->currentIndex == i);
294 }
295}
296
297void QQuickStackLayout::rearrange(const QSizeF &newSize)
298{
299 Q_D(QQuickStackLayout);
300 if (newSize.isNull() || !newSize.isValid())
301 return;
302
303 qCDebug(lcQuickLayouts) << "QQuickStackLayout::rearrange";
304 ensureLayoutItemsUpdated();
305
306 if (d->currentIndex == -1 || d->currentIndex >= m_cachedItemSizeHints.count())
307 return;
308 QQuickStackLayout::SizeHints &hints = m_cachedItemSizeHints[d->currentIndex];
309 QQuickItem *item = itemAt(index: d->currentIndex);
310 Q_ASSERT(item);
311 item->setPosition(QPointF(0,0)); // ### respect alignment?
312 const QSizeF oldSize(item->width(), item->height());
313 const QSizeF effectiveNewSize = newSize.expandedTo(otherSize: hints.min()).boundedTo(otherSize: hints.max());
314 item->setSize(effectiveNewSize);
315 if (effectiveNewSize == oldSize)
316 item->polish();
317 QQuickLayout::rearrange(newSize);
318}
319
320void QQuickStackLayout::collectItemSizeHints(QQuickItem *item, QSizeF *sizeHints)
321{
322 QQuickLayoutAttached *info = nullptr;
323 QQuickLayout::effectiveSizeHints_helper(item, cachedSizeHints: sizeHints, info: &info, useFallbackToWidthOrHeight: true);
324 if (!info)
325 return;
326 if (info->isFillWidthSet() && !info->fillWidth()) {
327 const qreal pref = sizeHints[Qt::PreferredSize].width();
328 sizeHints[Qt::MinimumSize].setWidth(pref);
329 sizeHints[Qt::MaximumSize].setWidth(pref);
330 }
331
332 if (info->isFillHeightSet() && !info->fillHeight()) {
333 const qreal pref = sizeHints[Qt::PreferredSize].height();
334 sizeHints[Qt::MinimumSize].setHeight(pref);
335 sizeHints[Qt::MaximumSize].setHeight(pref);
336 }
337}
338
339bool QQuickStackLayout::shouldIgnoreItem(QQuickItem *item) const
340{
341 const bool ignored = QQuickItemPrivate::get(item)->isTransparentForPositioner();
342 if (ignored)
343 d_func()->m_ignoredItems << item;
344 return ignored;
345}
346
347QT_END_NAMESPACE
348
349#include "moc_qquickstacklayout_p.cpp"
350

source code of qtdeclarative/src/imports/layouts/qquickstacklayout.cpp