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

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