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