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