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 test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
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 General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | |
30 | #include <QtTest/QtTest> |
31 | #include <QLineEdit> |
32 | #include <QLabel> |
33 | #include <QStackedLayout> |
34 | #include <qapplication.h> |
35 | #include <qwidget.h> |
36 | #include <QPushButton> |
37 | |
38 | class tst_QStackedLayout : public QObject |
39 | { |
40 | Q_OBJECT |
41 | |
42 | public: |
43 | tst_QStackedLayout(); |
44 | |
45 | private slots: |
46 | void init(); |
47 | void cleanup(); |
48 | |
49 | void getSetCheck(); |
50 | void testCase(); |
51 | void deleteCurrent(); |
52 | void removeWidget(); |
53 | void keepFocusAfterSetCurrent(); |
54 | void heigthForWidth(); |
55 | void replaceWidget(); |
56 | |
57 | private: |
58 | QWidget *testWidget; |
59 | }; |
60 | |
61 | // Testing get/set functions |
62 | void tst_QStackedLayout::getSetCheck() |
63 | { |
64 | QStackedLayout obj1; |
65 | // int QStackedLayout::currentIndex() |
66 | // void QStackedLayout::setCurrentIndex(int) |
67 | obj1.setCurrentIndex(0); |
68 | QCOMPARE(-1, obj1.currentIndex()); |
69 | obj1.setCurrentIndex(INT_MIN); |
70 | QCOMPARE(-1, obj1.currentIndex()); |
71 | obj1.setCurrentIndex(INT_MAX); |
72 | QCOMPARE(-1, obj1.currentIndex()); |
73 | |
74 | // QWidget * QStackedLayout::currentWidget() |
75 | // void QStackedLayout::setCurrentWidget(QWidget *) |
76 | QWidget *var2 = new QWidget(); |
77 | obj1.addWidget(w: var2); |
78 | obj1.setCurrentWidget(var2); |
79 | QCOMPARE(var2, obj1.currentWidget()); |
80 | |
81 | obj1.setCurrentWidget((QWidget *)0); |
82 | QCOMPARE(obj1.currentWidget(), var2); |
83 | |
84 | delete var2; |
85 | } |
86 | |
87 | |
88 | tst_QStackedLayout::tst_QStackedLayout() |
89 | : testWidget(0) |
90 | { |
91 | } |
92 | |
93 | void tst_QStackedLayout::init() |
94 | { |
95 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
96 | QSKIP("Wayland: This fails. Figure out why." ); |
97 | |
98 | if (testWidget) { |
99 | delete testWidget; |
100 | testWidget = 0; |
101 | } |
102 | testWidget = new QWidget(0); |
103 | testWidget->resize( w: 200, h: 200 ); |
104 | testWidget->show(); |
105 | |
106 | // make sure the tests work with focus follows mouse |
107 | QCursor::setPos(testWidget->geometry().center()); |
108 | testWidget->activateWindow(); |
109 | QVERIFY(QTest::qWaitForWindowActive(testWidget)); |
110 | } |
111 | |
112 | void tst_QStackedLayout::cleanup() |
113 | { |
114 | delete testWidget; |
115 | testWidget = 0; |
116 | } |
117 | |
118 | void tst_QStackedLayout::testCase() |
119 | { |
120 | QStackedLayout onStack(testWidget); |
121 | QStackedLayout *testLayout = &onStack; |
122 | testWidget->setLayout(testLayout); |
123 | |
124 | QSignalSpy spy(testLayout,SIGNAL(currentChanged(int))); |
125 | |
126 | // Nothing in layout |
127 | QCOMPARE(testLayout->currentIndex(), -1); |
128 | QCOMPARE(testLayout->currentWidget(), nullptr); |
129 | QCOMPARE(testLayout->count(), 0); |
130 | |
131 | // One widget added to layout |
132 | QWidget *w1 = new QWidget(testWidget); |
133 | testLayout->addWidget(w: w1); |
134 | QCOMPARE(spy.count(), 1); |
135 | QCOMPARE(spy.at(0).at(0).toInt(), 0); |
136 | spy.clear(); |
137 | QCOMPARE(testLayout->currentIndex(), 0); |
138 | QCOMPARE(testLayout->currentWidget(), w1); |
139 | QCOMPARE(testLayout->count(), 1); |
140 | |
141 | // Another widget added to layout |
142 | QWidget *w2 = new QWidget(testWidget); |
143 | testLayout->addWidget(w: w2); |
144 | QCOMPARE(testLayout->currentIndex(), 0); |
145 | QCOMPARE(testLayout->currentWidget(), w1); |
146 | QCOMPARE(testLayout->indexOf(w2), 1); |
147 | QCOMPARE(testLayout->count(), 2); |
148 | |
149 | // Change the current index |
150 | testLayout->setCurrentIndex(1); |
151 | QCOMPARE(spy.count(), 1); |
152 | QCOMPARE(spy.at(0).at(0).toInt(), 1); |
153 | spy.clear(); |
154 | QCOMPARE(testLayout->currentIndex(), 1); |
155 | QCOMPARE(testLayout->currentWidget(), w2); |
156 | |
157 | // First widget removed from layout |
158 | testLayout->removeWidget(w: w1); |
159 | QCOMPARE(testLayout->currentIndex(), 0); |
160 | QCOMPARE(testLayout->currentWidget(), w2); |
161 | QCOMPARE(testLayout->count(), 1); |
162 | |
163 | // Second widget removed from layout; back to nothing |
164 | testLayout->removeWidget(w: w2); |
165 | QCOMPARE(spy.count(), 1); |
166 | QCOMPARE(spy.at(0).at(0).toInt(), -1); |
167 | spy.clear(); |
168 | QCOMPARE(testLayout->currentIndex(), -1); |
169 | QCOMPARE(testLayout->currentWidget(), nullptr); |
170 | QCOMPARE(testLayout->count(), 0); |
171 | |
172 | // Another widget inserted at current index. |
173 | // Current index should become current index + 1, but the |
174 | // current widget should stay the same |
175 | testLayout->addWidget(w: w1); |
176 | QCOMPARE(testLayout->currentIndex(), 0); |
177 | QCOMPARE(testLayout->currentWidget(), w1); |
178 | testLayout->insertWidget(index: 0, w: w2); |
179 | QCOMPARE(testLayout->currentIndex(), 1); |
180 | QCOMPARE(testLayout->currentWidget(), w1); |
181 | QVERIFY(w1->isVisible()); |
182 | QVERIFY(!w2->isVisible()); |
183 | |
184 | testLayout->setCurrentWidget(w2); |
185 | // Another widget added, so we have: w2, w1, w3 with w2 current |
186 | QWidget *w3 = new QWidget(testWidget); |
187 | testLayout->addWidget(w: w3); |
188 | QCOMPARE(testLayout->indexOf(w2), 0); |
189 | QCOMPARE(testLayout->indexOf(w1), 1); |
190 | QCOMPARE(testLayout->indexOf(w3), 2); |
191 | |
192 | // Set index to 1 and remove that widget (w1). |
193 | // Then, current index should still be 1, but w3 |
194 | // should be the new current widget. |
195 | testLayout->setCurrentIndex(1); |
196 | testLayout->removeWidget(w: w1); |
197 | QCOMPARE(testLayout->currentIndex(), 1); |
198 | QCOMPARE(testLayout->currentWidget(), w3); |
199 | QVERIFY(w3->isVisible()); |
200 | |
201 | // Remove the current widget (w3). |
202 | // Since it's the last one in the list, current index should now |
203 | // become 0 and w2 becomes the current widget. |
204 | testLayout->removeWidget(w: w3); |
205 | QCOMPARE(testLayout->currentIndex(), 0); |
206 | QCOMPARE(testLayout->currentWidget(), w2); |
207 | QVERIFY(w2->isVisible()); |
208 | |
209 | // Make sure index is decremented when we remove a widget at index < current index |
210 | testLayout->addWidget(w: w1); |
211 | testLayout->addWidget(w: w3); |
212 | testLayout->setCurrentIndex(2); |
213 | testLayout->removeWidget(w: w2); // At index 0 |
214 | QCOMPARE(testLayout->currentIndex(), 1); |
215 | QCOMPARE(testLayout->currentWidget(), w3); |
216 | QVERIFY(w3->isVisible()); |
217 | testLayout->removeWidget(w: w1); // At index 0 |
218 | QCOMPARE(testLayout->currentIndex(), 0); |
219 | QCOMPARE(testLayout->currentWidget(), w3); |
220 | QVERIFY(w3->isVisible()); |
221 | testLayout->removeWidget(w: w3); |
222 | QCOMPARE(testLayout->currentIndex(), -1); |
223 | QCOMPARE(testLayout->currentWidget(), nullptr); |
224 | } |
225 | |
226 | void tst_QStackedLayout::deleteCurrent() |
227 | { |
228 | QStackedLayout *testLayout = new QStackedLayout(testWidget); |
229 | |
230 | QWidget *w1 = new QWidget; |
231 | testLayout->addWidget(w: w1); |
232 | QWidget *w2 = new QWidget; |
233 | testLayout->addWidget(w: w2); |
234 | QCOMPARE(testLayout->currentWidget(), w1); |
235 | delete testLayout->currentWidget(); |
236 | QCOMPARE(testLayout->currentWidget(), w2); |
237 | } |
238 | |
239 | void tst_QStackedLayout::removeWidget() |
240 | { |
241 | if (testWidget->layout()) delete testWidget->layout(); |
242 | QVBoxLayout *vbox = new QVBoxLayout(testWidget); |
243 | |
244 | QPushButton *top = new QPushButton("top" , testWidget); //add another widget that can receive focus |
245 | top->setObjectName("top" ); |
246 | vbox->addWidget(top); |
247 | |
248 | QStackedLayout *testLayout = new QStackedLayout(); |
249 | QPushButton *w1 = new QPushButton("1st" , testWidget); |
250 | w1->setObjectName("1st" ); |
251 | testLayout->addWidget(w: w1); |
252 | QPushButton *w2 = new QPushButton("2nd" , testWidget); |
253 | w2->setObjectName("2nd" ); |
254 | testLayout->addWidget(w: w2); |
255 | vbox->addLayout(layout: testLayout); |
256 | top->setFocus(); |
257 | top->activateWindow(); |
258 | QTRY_COMPARE(QApplication::focusWidget(), top); |
259 | |
260 | // focus should stay at the 'top' widget |
261 | testLayout->removeWidget(w: w1); |
262 | |
263 | QCOMPARE(QApplication::focusWidget(), top); |
264 | } |
265 | |
266 | class LineEdit : public QLineEdit |
267 | { |
268 | public: |
269 | LineEdit() : hasFakeEditFocus(false) |
270 | { } |
271 | |
272 | bool hasFakeEditFocus; |
273 | |
274 | protected: |
275 | bool isSingleFocusWidget() const |
276 | { |
277 | const QWidget *w = this; |
278 | while ((w = w->nextInFocusChain()) != this) { |
279 | if (w->isVisible() && static_cast<const QWidget*>(w->focusProxy()) != this |
280 | && w->focusPolicy() & Qt::TabFocus) { |
281 | return false; |
282 | } |
283 | } |
284 | return true; |
285 | } |
286 | |
287 | void focusInEvent(QFocusEvent *event) |
288 | { |
289 | QLineEdit::focusInEvent(event); |
290 | hasFakeEditFocus = isSingleFocusWidget(); |
291 | } |
292 | |
293 | void focusOutEvent(QFocusEvent *event) |
294 | { |
295 | hasFakeEditFocus = false; |
296 | QLineEdit::focusOutEvent(event); |
297 | } |
298 | }; |
299 | |
300 | void tst_QStackedLayout::keepFocusAfterSetCurrent() |
301 | { |
302 | if (testWidget->layout()) delete testWidget->layout(); |
303 | QStackedLayout *stackLayout = new QStackedLayout(testWidget); |
304 | testWidget->setFocusPolicy(Qt::NoFocus); |
305 | |
306 | LineEdit *edit1 = new LineEdit; |
307 | LineEdit *edit2 = new LineEdit; |
308 | stackLayout->addWidget(w: edit1); |
309 | stackLayout->addWidget(w: edit2); |
310 | |
311 | stackLayout->setCurrentIndex(0); |
312 | |
313 | testWidget->show(); |
314 | QApplication::setActiveWindow(testWidget); |
315 | QVERIFY(QTest::qWaitForWindowActive(testWidget)); |
316 | |
317 | edit1->setFocus(); |
318 | edit1->activateWindow(); |
319 | |
320 | QTRY_VERIFY(edit1->hasFocus()); |
321 | |
322 | stackLayout->setCurrentIndex(1); |
323 | QVERIFY(!edit1->hasFocus()); |
324 | QVERIFY(edit2->hasFocus()); |
325 | QVERIFY(edit2->hasFakeEditFocus); |
326 | } |
327 | |
328 | void tst_QStackedLayout::heigthForWidth() |
329 | { |
330 | if (testWidget->layout()) delete testWidget->layout(); |
331 | QStackedLayout *stackLayout = new QStackedLayout(testWidget); |
332 | |
333 | QLabel *shortLabel = new QLabel("This is a short text." ); |
334 | shortLabel->setWordWrap(true); |
335 | stackLayout->addWidget(w: shortLabel); |
336 | |
337 | QLabel *longLabel = new QLabel("Write code once to target multiple platforms\n" |
338 | "Qt allows you to write advanced applications and UIs once, " |
339 | "and deploy them across desktop and embedded operating systems " |
340 | "without rewriting the source code saving time and development cost.\n\n" |
341 | "Create amazing user experiences\n" |
342 | "Whether you prefer C++ or JavaScript, Qt provides the building blocks - " |
343 | "a broad set of customizable widgets, graphics canvas, style engine " |
344 | "and more that you need to build modern user interfaces. " |
345 | "Incorporate 3D graphics, multimedia audio or video, visual effects, " |
346 | "and animations to set your application apart from the competition." ); |
347 | |
348 | longLabel->setWordWrap(true); |
349 | stackLayout->addWidget(w: longLabel); |
350 | stackLayout->setCurrentIndex(0); |
351 | int hfw_index0 = stackLayout->heightForWidth(width: 200); |
352 | |
353 | stackLayout->setCurrentIndex(1); |
354 | QCOMPARE(stackLayout->heightForWidth(200), hfw_index0); |
355 | |
356 | } |
357 | |
358 | void tst_QStackedLayout::replaceWidget() |
359 | { |
360 | QWidget w; |
361 | QStackedLayout *stackLayout = new QStackedLayout(&w); |
362 | |
363 | QLineEdit *replaceFrom = new QLineEdit; |
364 | QLineEdit *replaceTo = new QLineEdit; |
365 | stackLayout->addWidget(w: new QLineEdit()); |
366 | stackLayout->addWidget(w: replaceFrom); |
367 | stackLayout->addWidget(w: new QLineEdit()); |
368 | stackLayout->setCurrentWidget(replaceFrom); |
369 | |
370 | QCOMPARE(stackLayout->indexOf(replaceFrom), 1); |
371 | QCOMPARE(stackLayout->indexOf(replaceTo), -1); |
372 | delete stackLayout->replaceWidget(from: replaceFrom, to: replaceTo); |
373 | |
374 | QCOMPARE(stackLayout->indexOf(replaceFrom), -1); |
375 | QCOMPARE(stackLayout->indexOf(replaceTo), 1); |
376 | QCOMPARE(stackLayout->currentWidget(), replaceTo); |
377 | } |
378 | |
379 | QTEST_MAIN(tst_QStackedLayout) |
380 | #include "tst_qstackedlayout.moc" |
381 | |
382 | |