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