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