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