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