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
4#include "qstackedlayout.h"
5#include "qlayout_p.h"
6
7#include <qlist.h>
8#include "private/qwidget_p.h"
9#include "private/qlayoutengine_p.h"
10
11#include <memory>
12
13QT_BEGIN_NAMESPACE
14
15class QStackedLayoutPrivate : public QLayoutPrivate
16{
17 Q_DECLARE_PUBLIC(QStackedLayout)
18public:
19 QStackedLayoutPrivate() : index(-1), stackingMode(QStackedLayout::StackOne) {}
20 QLayoutItem* replaceAt(int index, QLayoutItem *newitem) override;
21 QList<QLayoutItem *> list;
22 int index;
23 QStackedLayout::StackingMode stackingMode;
24};
25
26QLayoutItem* QStackedLayoutPrivate::replaceAt(int idx, QLayoutItem *newitem)
27{
28 Q_Q(QStackedLayout);
29 if (idx < 0 || idx >= list.size() || !newitem)
30 return nullptr;
31 QWidget *wdg = newitem->widget();
32 if (Q_UNLIKELY(!wdg)) {
33 qWarning(msg: "QStackedLayout::replaceAt: Only widgets can be added");
34 return nullptr;
35 }
36 QLayoutItem *orgitem = list.at(i: idx);
37 list.replace(i: idx, t: newitem);
38 if (idx == index)
39 q->setCurrentIndex(index);
40 return orgitem;
41}
42
43/*!
44 \class QStackedLayout
45
46 \brief The QStackedLayout class provides a stack of widgets where
47 only one widget is visible at a time.
48
49 \ingroup geomanagement
50 \inmodule QtWidgets
51
52 QStackedLayout can be used to create a user interface similar to
53 the one provided by QTabWidget. There is also a convenience
54 QStackedWidget class built on top of QStackedLayout.
55
56 A QStackedLayout can be populated with a number of child widgets
57 ("pages"). For example:
58
59 \snippet qstackedlayout/main.cpp 0
60 \codeline
61 \snippet qstackedlayout/main.cpp 2
62 \snippet qstackedlayout/main.cpp 3
63
64 QStackedLayout provides no intrinsic means for the user to switch
65 page. This is typically done through a QComboBox or a QListWidget
66 that stores the titles of the QStackedLayout's pages. For
67 example:
68
69 \snippet qstackedlayout/main.cpp 1
70
71 When populating a layout, the widgets are added to an internal
72 list. The indexOf() function returns the index of a widget in that
73 list. The widgets can either be added to the end of the list using
74 the addWidget() function, or inserted at a given index using the
75 insertWidget() function. The removeWidget() function removes the
76 widget at the given index from the layout. The number of widgets
77 contained in the layout, can be obtained using the count()
78 function.
79
80 The widget() function returns the widget at a given index
81 position. The index of the widget that is shown on screen is given
82 by currentIndex() and can be changed using setCurrentIndex(). In a
83 similar manner, the currently shown widget can be retrieved using
84 the currentWidget() function, and altered using the
85 setCurrentWidget() function.
86
87 Whenever the current widget in the layout changes or a widget is
88 removed from the layout, the currentChanged() and widgetRemoved()
89 signals are emitted respectively.
90
91 \sa QStackedWidget, QTabWidget
92*/
93
94/*!
95 \fn void QStackedLayout::currentChanged(int index)
96
97 This signal is emitted whenever the current widget in the layout
98 changes. The \a index specifies the index of the new current
99 widget, or -1 if there isn't a new one (for example, if there
100 are no widgets in the QStackedLayout)
101
102 \sa currentWidget(), setCurrentWidget()
103*/
104
105/*!
106 \fn void QStackedLayout::widgetRemoved(int index)
107
108 This signal is emitted whenever a widget is removed from the
109 layout. The widget's \a index is passed as parameter.
110
111 \sa removeWidget()
112*/
113
114/*!
115 Constructs a QStackedLayout with no parent.
116
117 This QStackedLayout must be installed on a widget later on to
118 become effective.
119
120 \sa addWidget(), insertWidget()
121*/
122QStackedLayout::QStackedLayout()
123 : QLayout(*new QStackedLayoutPrivate, nullptr, nullptr)
124{
125}
126
127/*!
128 Constructs a new QStackedLayout with the given \a parent.
129
130 This layout will install itself on the \a parent widget and
131 manage the geometry of its children.
132*/
133QStackedLayout::QStackedLayout(QWidget *parent)
134 : QLayout(*new QStackedLayoutPrivate, nullptr, parent)
135{
136}
137
138/*!
139 Constructs a new QStackedLayout and inserts it into
140 the given \a parentLayout.
141*/
142QStackedLayout::QStackedLayout(QLayout *parentLayout)
143 : QLayout(*new QStackedLayoutPrivate, parentLayout, nullptr)
144{
145}
146
147/*!
148 Destroys this QStackedLayout. Note that the layout's widgets are
149 \e not destroyed.
150*/
151QStackedLayout::~QStackedLayout()
152{
153 Q_D(QStackedLayout);
154 qDeleteAll(c: d->list);
155}
156
157/*!
158 Adds the given \a widget to the end of this layout and returns the
159 index position of the \a widget.
160
161 If the QStackedLayout is empty before this function is called,
162 the given \a widget becomes the current widget.
163
164 \sa insertWidget(), removeWidget(), setCurrentWidget()
165*/
166int QStackedLayout::addWidget(QWidget *widget)
167{
168 Q_D(QStackedLayout);
169 return insertWidget(index: d->list.size(), w: widget);
170}
171
172/*!
173 Inserts the given \a widget at the given \a index in this
174 QStackedLayout. If \a index is out of range, the widget is
175 appended (in which case it is the actual index of the \a widget
176 that is returned).
177
178 If the QStackedLayout is empty before this function is called, the
179 given \a widget becomes the current widget.
180
181 Inserting a new widget at an index less than or equal to the current index
182 will increment the current index, but keep the current widget.
183
184 \sa addWidget(), removeWidget(), setCurrentWidget()
185*/
186int QStackedLayout::insertWidget(int index, QWidget *widget)
187{
188 Q_D(QStackedLayout);
189 addChildWidget(w: widget);
190 index = qMin(a: index, b: d->list.size());
191 if (index < 0)
192 index = d->list.size();
193 QWidgetItem *wi = QLayoutPrivate::createWidgetItem(layout: this, widget);
194 d->list.insert(i: index, t: wi);
195 invalidate();
196 if (d->index < 0) {
197 setCurrentIndex(index);
198 } else {
199 if (index <= d->index)
200 ++d->index;
201 if (d->stackingMode == StackOne)
202 widget->hide();
203 widget->lower();
204 }
205 return index;
206}
207
208/*!
209 \reimp
210*/
211QLayoutItem *QStackedLayout::itemAt(int index) const
212{
213 Q_D(const QStackedLayout);
214 return d->list.value(i: index);
215}
216
217// Code that enables proper handling of the case that takeAt() is
218// called somewhere inside QObject destructor (can't call hide()
219// on the object then)
220static bool qt_wasDeleted(const QWidget *w)
221{
222 return QObjectPrivate::get(o: w)->wasDeleted;
223}
224
225
226/*!
227 \reimp
228*/
229QLayoutItem *QStackedLayout::takeAt(int index)
230{
231 Q_D(QStackedLayout);
232 if (index <0 || index >= d->list.size())
233 return nullptr;
234 QLayoutItem *item = d->list.takeAt(i: index);
235 if (index == d->index) {
236 d->index = -1;
237 if ( d->list.size() > 0 ) {
238 int newIndex = (index == d->list.size()) ? index-1 : index;
239 setCurrentIndex(newIndex);
240 } else {
241 emit currentChanged(index: -1);
242 }
243 } else if (index < d->index) {
244 --d->index;
245 }
246 emit widgetRemoved(index);
247 if (item->widget() && !qt_wasDeleted(w: item->widget()))
248 item->widget()->hide();
249 return item;
250}
251
252/*!
253 \property QStackedLayout::currentIndex
254 \brief the index position of the widget that is visible
255
256 The current index is -1 if there is no current widget.
257
258 \sa currentWidget(), indexOf()
259*/
260void QStackedLayout::setCurrentIndex(int index)
261{
262 Q_D(QStackedLayout);
263 QWidget *prev = currentWidget();
264 QWidget *next = widget(index);
265 if (!next || next == prev)
266 return;
267
268 bool reenableUpdates = false;
269 QWidget *parent = parentWidget();
270
271 if (parent && parent->updatesEnabled()) {
272 reenableUpdates = true;
273 parent->setUpdatesEnabled(false);
274 }
275
276 QPointer<QWidget> fw = parent ? parent->window()->focusWidget() : nullptr;
277 const bool focusWasOnOldPage = fw && (prev && prev->isAncestorOf(child: fw));
278
279 if (prev) {
280 prev->clearFocus();
281 if (d->stackingMode == StackOne)
282 prev->hide();
283 }
284
285 d->index = index;
286 next->raise();
287 next->show();
288
289 // try to move focus onto the incoming widget if focus
290 // was somewhere on the outgoing widget.
291
292 if (parent) {
293 if (focusWasOnOldPage) {
294 // look for the best focus widget we can find
295 if (QWidget *nfw = next->focusWidget())
296 nfw->setFocus();
297 else {
298 // second best: first child widget in the focus chain
299 if (QWidget *i = fw) {
300 while ((i = i->nextInFocusChain()) != fw) {
301 if (((i->focusPolicy() & Qt::TabFocus) == Qt::TabFocus)
302 && !i->focusProxy() && i->isVisibleTo(next) && i->isEnabled()
303 && next->isAncestorOf(child: i)) {
304 i->setFocus();
305 break;
306 }
307 }
308 // third best: incoming widget
309 if (i == fw )
310 next->setFocus();
311 }
312 }
313 }
314 }
315 if (reenableUpdates)
316 parent->setUpdatesEnabled(true);
317 emit currentChanged(index);
318}
319
320int QStackedLayout::currentIndex() const
321{
322 Q_D(const QStackedLayout);
323 return d->index;
324}
325
326
327/*!
328 \fn void QStackedLayout::setCurrentWidget(QWidget *widget)
329
330 Sets the current widget to be the specified \a widget. The new
331 current widget must already be contained in this stacked layout.
332
333 \sa setCurrentIndex(), currentWidget()
334 */
335void QStackedLayout::setCurrentWidget(QWidget *widget)
336{
337 int index = indexOf(widget);
338 if (Q_UNLIKELY(index == -1)) {
339 qWarning(msg: "QStackedLayout::setCurrentWidget: Widget %p not contained in stack", widget);
340 return;
341 }
342 setCurrentIndex(index);
343}
344
345
346/*!
347 Returns the current widget, or \nullptr if there are no widgets
348 in this layout.
349
350 \sa currentIndex(), setCurrentWidget()
351*/
352QWidget *QStackedLayout::currentWidget() const
353{
354 Q_D(const QStackedLayout);
355 return d->index >= 0 ? d->list.at(i: d->index)->widget() : nullptr;
356}
357
358/*!
359 Returns the widget at the given \a index, or \nullptr if there is
360 no widget at the given position.
361
362 \sa currentWidget(), indexOf()
363*/
364QWidget *QStackedLayout::widget(int index) const
365{
366 Q_D(const QStackedLayout);
367 if (index < 0 || index >= d->list.size())
368 return nullptr;
369 return d->list.at(i: index)->widget();
370}
371
372/*!
373 \property QStackedLayout::count
374 \brief the number of widgets contained in the layout
375
376 \sa currentIndex(), widget()
377*/
378int QStackedLayout::count() const
379{
380 Q_D(const QStackedLayout);
381 return d->list.size();
382}
383
384
385/*!
386 \reimp
387*/
388void QStackedLayout::addItem(QLayoutItem *item)
389{
390 std::unique_ptr<QLayoutItem> guard(item);
391 QWidget *widget = item->widget();
392 if (Q_UNLIKELY(!widget)) {
393 qWarning(msg: "QStackedLayout::addItem: Only widgets can be added");
394 return;
395 }
396 addWidget(widget);
397}
398
399/*!
400 \reimp
401*/
402QSize QStackedLayout::sizeHint() const
403{
404 Q_D(const QStackedLayout);
405 QSize s(0, 0);
406 int n = d->list.size();
407
408 for (int i = 0; i < n; ++i)
409 if (QWidget *widget = d->list.at(i)->widget()) {
410 QSize ws(widget->sizeHint());
411 if (widget->sizePolicy().horizontalPolicy() == QSizePolicy::Ignored)
412 ws.setWidth(0);
413 if (widget->sizePolicy().verticalPolicy() == QSizePolicy::Ignored)
414 ws.setHeight(0);
415 s = s.expandedTo(otherSize: ws);
416 }
417 return s;
418}
419
420/*!
421 \reimp
422*/
423QSize QStackedLayout::minimumSize() const
424{
425 Q_D(const QStackedLayout);
426 QSize s(0, 0);
427 int n = d->list.size();
428
429 for (int i = 0; i < n; ++i)
430 if (QWidget *widget = d->list.at(i)->widget())
431 s = s.expandedTo(otherSize: qSmartMinSize(w: widget));
432 return s;
433}
434
435/*!
436 \reimp
437*/
438void QStackedLayout::setGeometry(const QRect &rect)
439{
440 Q_D(QStackedLayout);
441 switch (d->stackingMode) {
442 case StackOne:
443 if (QWidget *widget = currentWidget())
444 widget->setGeometry(rect);
445 break;
446 case StackAll:
447 if (const int n = d->list.size())
448 for (int i = 0; i < n; ++i)
449 if (QWidget *widget = d->list.at(i)->widget())
450 widget->setGeometry(rect);
451 break;
452 }
453}
454
455/*!
456 \reimp
457*/
458bool QStackedLayout::hasHeightForWidth() const
459{
460 const int n = count();
461
462 for (int i = 0; i < n; ++i) {
463 if (QLayoutItem *item = itemAt(index: i)) {
464 if (item->hasHeightForWidth())
465 return true;
466 }
467 }
468 return false;
469}
470
471/*!
472 \reimp
473*/
474int QStackedLayout::heightForWidth(int width) const
475{
476 const int n = count();
477
478 int hfw = 0;
479 for (int i = 0; i < n; ++i) {
480 if (QLayoutItem *item = itemAt(index: i)) {
481 if (QWidget *w = item->widget())
482 /*
483 Note: Does not query the layout item, but bypasses it and asks the widget
484 directly. This is consistent with how QStackedLayout::sizeHint() is
485 implemented. This also avoids an issue where QWidgetItem::heightForWidth()
486 returns -1 if the widget is hidden.
487 */
488 hfw = qMax(a: hfw, b: w->heightForWidth(width));
489 }
490 }
491 hfw = qMax(a: hfw, b: minimumSize().height());
492 return hfw;
493}
494
495/*!
496 \enum QStackedLayout::StackingMode
497 \since 4.4
498
499 This enum specifies how the layout handles its child widgets
500 regarding their visibility.
501
502 \value StackOne
503 Only the current widget is visible. This is the default.
504
505 \value StackAll
506 All widgets are visible. The current widget is merely raised.
507*/
508
509
510/*!
511 \property QStackedLayout::stackingMode
512 \brief determines the way visibility of child widgets are handled.
513 \since 4.4
514
515 The default value is StackOne. Setting the property to StackAll
516 allows you to make use of the layout for overlay widgets
517 that do additional drawing on top of other widgets, for example,
518 graphical editors.
519*/
520
521QStackedLayout::StackingMode QStackedLayout::stackingMode() const
522{
523 Q_D(const QStackedLayout);
524 return d->stackingMode;
525}
526
527void QStackedLayout::setStackingMode(StackingMode stackingMode)
528{
529 Q_D(QStackedLayout);
530 if (d->stackingMode == stackingMode)
531 return;
532 d->stackingMode = stackingMode;
533
534 const int n = d->list.size();
535 if (n == 0)
536 return;
537
538 switch (d->stackingMode) {
539 case StackOne:
540 if (const int idx = currentIndex())
541 for (int i = 0; i < n; ++i)
542 if (QWidget *widget = d->list.at(i)->widget())
543 widget->setVisible(i == idx);
544 break;
545 case StackAll: { // Turn overlay on: Make sure all widgets are the same size
546 QRect geometry;
547 if (const QWidget *widget = currentWidget())
548 geometry = widget->geometry();
549 for (int i = 0; i < n; ++i)
550 if (QWidget *widget = d->list.at(i)->widget()) {
551 if (!geometry.isNull())
552 widget->setGeometry(geometry);
553 widget->setVisible(true);
554 }
555 }
556 break;
557 }
558}
559
560QT_END_NAMESPACE
561
562#include "moc_qstackedlayout.cpp"
563

source code of qtbase/src/widgets/kernel/qstackedlayout.cpp