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 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | class QStackedLayoutPrivate : public QLayoutPrivate |
18 | { |
19 | Q_DECLARE_PUBLIC(QStackedLayout) |
20 | public: |
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 | |
28 | QLayoutItem* 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 | */ |
124 | QStackedLayout::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 | */ |
135 | QStackedLayout::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 | */ |
144 | QStackedLayout::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 | */ |
153 | QStackedLayout::~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 | */ |
168 | int 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 | */ |
188 | int 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 | */ |
213 | QLayoutItem *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) |
222 | static bool qt_wasDeleted(const QWidget *w) |
223 | { |
224 | return QObjectPrivate::get(o: w)->wasDeleted; |
225 | } |
226 | |
227 | |
228 | /*! |
229 | \reimp |
230 | */ |
231 | QLayoutItem *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 | */ |
262 | void 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 | |
322 | int 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 | */ |
337 | void 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 | */ |
354 | QWidget *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 | */ |
366 | QWidget *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 | */ |
380 | int QStackedLayout::count() const |
381 | { |
382 | Q_D(const QStackedLayout); |
383 | return d->list.size(); |
384 | } |
385 | |
386 | |
387 | /*! |
388 | \reimp |
389 | */ |
390 | void 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 | */ |
404 | QSize 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 | */ |
425 | QSize 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 | */ |
440 | void 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 | */ |
460 | bool 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 | */ |
476 | int 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 | |
523 | QStackedLayout::StackingMode QStackedLayout::stackingMode() const |
524 | { |
525 | Q_D(const QStackedLayout); |
526 | return d->stackingMode; |
527 | } |
528 | |
529 | void 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 | |
562 | QT_END_NAMESPACE |
563 | |
564 | #include "moc_qstackedlayout.cpp" |
565 |
Definitions
- QStackedLayoutPrivate
- QStackedLayoutPrivate
- replaceAt
- QStackedLayout
- QStackedLayout
- QStackedLayout
- ~QStackedLayout
- addWidget
- insertWidget
- itemAt
- qt_wasDeleted
- takeAt
- setCurrentIndex
- currentIndex
- setCurrentWidget
- currentWidget
- widget
- count
- addItem
- sizeHint
- minimumSize
- setGeometry
- hasHeightForWidth
- heightForWidth
- stackingMode
Learn Advanced QML with KDAB
Find out more