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