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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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