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 <qapplication.h>
32#include <qsplitter.h>
33#include <qstyle.h>
34#include <qfile.h>
35#include <qtextstream.h>
36#include <qlayout.h>
37#include <qabstractscrollarea.h>
38#include <qgraphicsview.h>
39#include <qmdiarea.h>
40#include <qscrollarea.h>
41#include <qtextedit.h>
42#include <qtreeview.h>
43#include <qlabel.h>
44#include <qdialog.h>
45#include <qscreen.h>
46#include <qproxystyle.h>
47#include <qdebug.h> // for file error messages
48
49QT_FORWARD_DECLARE_CLASS(QSplitter)
50QT_FORWARD_DECLARE_CLASS(QWidget)
51class tst_QSplitter : public QObject
52{
53 Q_OBJECT
54
55public:
56 tst_QSplitter();
57 virtual ~tst_QSplitter();
58
59public slots:
60 void initTestCase();
61 void cleanupTestCase();
62 void init();
63private slots:
64 void getSetCheck();
65 void setSizes();
66 void setSizes_data();
67 void saveAndRestoreState();
68 void saveAndRestoreState_data();
69 void saveState_data();
70 void addWidget();
71 void insertWidget();
72 void setStretchFactor_data();
73 void setStretchFactor();
74 void testShowHide_data();
75 void testShowHide();
76 void testRemoval();
77 void rubberBandNotInSplitter();
78 void saveAndRestoreStateOfNotYetShownSplitter();
79 void saveAndRestoreHandleWidth();
80 void replaceWidget_data();
81 void replaceWidget();
82 void replaceWidgetWithSplitterChild_data();
83 void replaceWidgetWithSplitterChild();
84 void handleMinimumWidth();
85
86 // task-specific tests below me:
87 void task187373_addAbstractScrollAreas();
88 void task187373_addAbstractScrollAreas_data();
89 void task169702_sizes();
90 void taskQTBUG_4101_ensureOneNonCollapsedWidget_data();
91 void taskQTBUG_4101_ensureOneNonCollapsedWidget();
92 void setLayout();
93 void autoAdd();
94
95private:
96 void removeThirdWidget();
97 void addThirdWidget();
98 QSplitter *splitter;
99 QWidget *w1;
100 QWidget *w2;
101 QWidget *w3;
102};
103
104// Testing get/set functions
105void tst_QSplitter::getSetCheck()
106{
107 QSplitter obj1;
108 // bool QSplitter::opaqueResize()
109 // void QSplitter::setOpaqueResize(bool)
110 bool styleHint = obj1.style()->styleHint(stylehint: QStyle::SH_Splitter_OpaqueResize);
111 QCOMPARE(styleHint, obj1.opaqueResize());
112 obj1.setOpaqueResize(false);
113 QCOMPARE(false, obj1.opaqueResize());
114 obj1.setOpaqueResize(true);
115 QCOMPARE(true, obj1.opaqueResize());
116}
117
118tst_QSplitter::tst_QSplitter()
119 : w1(0), w2(0), w3(0)
120{
121}
122
123tst_QSplitter::~tst_QSplitter()
124{
125}
126
127void tst_QSplitter::initTestCase()
128{
129 splitter = new QSplitter(Qt::Horizontal);
130 w1 = new QWidget;
131 w2 = new QWidget;
132 splitter->addWidget(widget: w1);
133 splitter->addWidget(widget: w2);
134}
135
136void tst_QSplitter::init()
137{
138 removeThirdWidget();
139 w1->show();
140 w2->show();
141 w1->setMinimumSize(minw: 0, minh: 0);
142 w2->setMinimumSize(minw: 0, minh: 0);
143 splitter->setSizes(QList<int>() << 200 << 200);
144 qApp->sendPostedEvents();
145}
146
147void tst_QSplitter::removeThirdWidget()
148{
149 delete w3;
150 w3 = 0;
151 int handleWidth = splitter->style()->pixelMetric(metric: QStyle::PM_SplitterWidth);
152 splitter->setFixedSize(w: 400 + handleWidth, h: 400);
153}
154
155void tst_QSplitter::addThirdWidget()
156{
157 if (!w3) {
158 w3 = new QWidget;
159 splitter->addWidget(widget: w3);
160 int handleWidth = splitter->style()->pixelMetric(metric: QStyle::PM_SplitterWidth);
161 splitter->setFixedSize(w: 600 + 2 * handleWidth, h: 400);
162 }
163}
164
165void tst_QSplitter::cleanupTestCase()
166{
167}
168
169
170typedef QList<int> IntList;
171
172void tst_QSplitter::setSizes()
173{
174 QFETCH(IntList, minimumSizes);
175 QFETCH(IntList, splitterSizes);
176 QFETCH(IntList, collapsibleStates);
177 QFETCH(bool, childrenCollapse);
178
179 QCOMPARE(minimumSizes.size(), splitterSizes.size());
180 if (minimumSizes.size() > 2)
181 addThirdWidget();
182 for (int i = 0; i < minimumSizes.size(); ++i) {
183 QWidget *w = splitter->widget(index: i);
184 w->setMinimumWidth(minimumSizes.at(i));
185 splitter->setCollapsible(index: splitter->indexOf(w), collapsibleStates.at(i));
186 }
187 splitter->setChildrenCollapsible(childrenCollapse);
188 splitter->setSizes(splitterSizes);
189 QTEST(splitter->sizes(), "expectedSizes");
190}
191
192void tst_QSplitter::setSizes_data()
193{
194 QTest::addColumn<IntList>(name: "minimumSizes");
195 QTest::addColumn<IntList>(name: "splitterSizes");
196 QTest::addColumn<IntList>(name: "expectedSizes");
197 QTest::addColumn<IntList>(name: "collapsibleStates");
198 QTest::addColumn<bool>(name: "childrenCollapse");
199
200 QFile file(QFINDTESTDATA("setSizes3.dat"));
201 if (!file.open(flags: QIODevice::ReadOnly)) {
202 qDebug() << "Can't open file, reason:" << file.errorString();
203 return;
204 }
205 QTextStream ts(&file);
206 ts.setIntegerBase(10);
207
208 QString dataName;
209 IntList minimumSizes;
210 IntList splitterSizes;
211 IntList expectedSizes;
212 IntList collapsibleStates;
213 int childrenCollapse;
214 while (!ts.atEnd()) {
215 int i1, i2, i3;
216 minimumSizes.clear();
217 splitterSizes.clear();
218 expectedSizes.clear();
219 collapsibleStates.clear();
220 ts >> dataName;
221 ts >> i1 >> i2 >> i3;
222 minimumSizes << i1 << i2 << i3;
223 ts >> i1 >> i2 >> i3;
224 splitterSizes << i1 << i2 << i3;
225 ts >> i1 >> i2 >> i3;
226 expectedSizes << i1 << i2 << i3;
227 ts >> i1 >> i2 >> i3;
228 collapsibleStates << i1 << i2 << i3;
229 ts >> childrenCollapse;
230 QTest::newRow(dataTag: dataName.toLocal8Bit()) << minimumSizes << splitterSizes << expectedSizes << collapsibleStates << bool(childrenCollapse);
231 ts.skipWhiteSpace();
232 }
233}
234
235void tst_QSplitter::saveAndRestoreState_data()
236{
237 saveState_data();
238}
239
240void tst_QSplitter::saveAndRestoreState()
241{
242 QFETCH(IntList, initialSizes);
243 splitter->setSizes(initialSizes);
244 QApplication::instance()->sendPostedEvents();
245
246 QSplitter *splitter2 = new QSplitter(splitter->orientation() == Qt::Horizontal ?
247 Qt::Vertical : Qt::Horizontal);
248 for (int i = 0; i < splitter->count(); ++i) {
249 splitter2->addWidget(widget: new QWidget());
250 }
251 splitter2->resize(splitter->size());
252 splitter2->setChildrenCollapsible(!splitter->childrenCollapsible());
253 splitter2->setOpaqueResize(!splitter->opaqueResize());
254 splitter2->setHandleWidth(splitter->handleWidth()+3);
255
256 QByteArray ba = splitter->saveState();
257 QVERIFY(splitter2->restoreState(ba));
258
259 QCOMPARE(splitter2->orientation(), splitter->orientation());
260 QCOMPARE(splitter2->handleWidth(), splitter->handleWidth());
261 QCOMPARE(splitter2->opaqueResize(), splitter->opaqueResize());
262 QCOMPARE(splitter2->childrenCollapsible(), splitter->childrenCollapsible());
263
264 QList<int> l1 = splitter->sizes();
265 QList<int> l2 = splitter2->sizes();
266 QCOMPARE(l1.size(), l2.size());
267 for (int i = 0; i < splitter->sizes().size(); ++i) {
268 QCOMPARE(l2.at(i), l1.at(i));
269 }
270
271 // destroy version and magic number
272 for (int i = 0; i < ba.size(); ++i)
273 ba[i] = ~ba.at(i);
274 QVERIFY(!splitter2->restoreState(ba));
275
276 delete splitter2;
277}
278
279void tst_QSplitter::saveAndRestoreStateOfNotYetShownSplitter()
280{
281 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
282 QSKIP("Wayland: This fails. Figure out why.");
283
284 QSplitter *spl = new QSplitter;
285 QLabel *l1 = new QLabel;
286 QLabel *l2 = new QLabel;
287 spl->addWidget(widget: l1);
288 spl->addWidget(widget: l2);
289
290 QByteArray ba = spl->saveState();
291 spl->restoreState(state: ba);
292 spl->show();
293 QVERIFY(QTest::qWaitForWindowActive(spl));
294
295 QCOMPARE(l1->geometry().isValid(), true);
296 QCOMPARE(l2->geometry().isValid(), true);
297
298 delete spl;
299}
300
301class TestSplitterStyle : public QProxyStyle
302{
303public:
304 TestSplitterStyle() : handleWidth(5) {}
305 int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0) const override
306 {
307 if (metric == QStyle::PM_SplitterWidth)
308 return handleWidth;
309 else
310 return QProxyStyle::pixelMetric(metric, option, widget);
311 }
312 int handleWidth;
313};
314
315void tst_QSplitter::saveAndRestoreHandleWidth()
316{
317 TestSplitterStyle style;
318 style.handleWidth = 5;
319 QSplitter spl;
320 spl.setStyle(&style);
321
322 QCOMPARE(spl.handleWidth(), style.handleWidth);
323 style.handleWidth = 10;
324 QCOMPARE(spl.handleWidth(), style.handleWidth);
325 QByteArray ba = spl.saveState();
326 spl.setHandleWidth(20);
327 QCOMPARE(spl.handleWidth(), 20);
328 spl.setHandleWidth(-1);
329 QCOMPARE(spl.handleWidth(), style.handleWidth);
330 spl.setHandleWidth(15);
331 QCOMPARE(spl.handleWidth(), 15);
332 spl.restoreState(state: ba);
333 QCOMPARE(spl.handleWidth(), style.handleWidth);
334}
335
336void tst_QSplitter::saveState_data()
337{
338 QTest::addColumn<IntList>(name: "initialSizes");
339 QTest::addColumn<bool>(name: "hideWidget1");
340 QTest::addColumn<bool>(name: "hideWidget2");
341 QTest::addColumn<QByteArray>(name: "finalBa");
342
343 QTest::newRow(dataTag: "ok0") << (IntList() << 200 << 200) << bool(false) << bool(false) << QByteArray("[200,200]");
344 QTest::newRow(dataTag: "ok1") << (IntList() << 300 << 100) << bool(false) << bool(false) << QByteArray("[300,100]");
345 QTest::newRow(dataTag: "ok2") << (IntList() << 100 << 300) << bool(false) << bool(false) << QByteArray("[100,300]");
346 QTest::newRow(dataTag: "ok3") << (IntList() << 200 << 200) << bool(false) << bool(true) << QByteArray("[200,H]");
347 QTest::newRow(dataTag: "ok4") << (IntList() << 200 << 200) << bool(true) << bool(false) << QByteArray("[H,200]");
348 QTest::newRow(dataTag: "ok5") << (IntList() << 200 << 200) << bool(false) << bool(false) << QByteArray("[200,200]");
349 QTest::newRow(dataTag: "ok6") << (IntList() << 200 << 200) << bool(false) << bool(false) << QByteArray("[200,200]");
350 QTest::newRow(dataTag: "ok7") << (IntList() << 200 << 200) << bool(false) << bool(false) << QByteArray("[200,200]");
351 QTest::newRow(dataTag: "ok8") << (IntList() << 200 << 200) << bool(true) << bool(true) << QByteArray("[H,H]");
352}
353
354void tst_QSplitter::addWidget()
355{
356 QSplitter split;
357
358 // Simple case
359 QWidget *widget1 = new QWidget;
360 QWidget *widget2 = new QWidget;
361 split.addWidget(widget: widget1);
362 split.addWidget(widget: widget2);
363 QCOMPARE(split.count(), 2);
364 QCOMPARE(split.indexOf(widget1), 0);
365 QCOMPARE(split.indexOf(widget2), 1);
366 QCOMPARE(split.widget(0), widget1);
367 QCOMPARE(split.widget(1), widget2);
368
369
370 // Implicit Add
371 QWidget *widget3 = new QWidget(&split);
372 QCOMPARE(split.count(), 3);
373 QCOMPARE(split.indexOf(widget3), 2);
374 QCOMPARE(split.widget(2), widget3);
375
376 // Try and add it again
377 split.addWidget(widget: widget3);
378 QCOMPARE(split.count(), 3);
379 QCOMPARE(split.indexOf(widget3), 2);
380 QCOMPARE(split.widget(2), widget3);
381
382 // Add a widget that is already in the splitter
383 split.addWidget(widget: widget1);
384 QCOMPARE(split.count(), 3);
385 QCOMPARE(split.indexOf(widget1), 2);
386 QCOMPARE(split.widget(0), widget2);
387 QCOMPARE(split.widget(1), widget3);
388 QCOMPARE(split.widget(2), widget1);
389
390 // Change a widget's parent
391 widget2->setParent(0);
392 QCOMPARE(split.count(), 2);
393 QCOMPARE(split.indexOf(widget2), -1);
394
395
396 // Add the widget in again.
397 split.addWidget(widget: widget2);
398 QCOMPARE(split.count(), 3);
399 QCOMPARE(split.indexOf(widget2), 2);
400 QCOMPARE(split.widget(0), widget3);
401 QCOMPARE(split.widget(1), widget1);
402 QCOMPARE(split.widget(2), widget2);
403
404 // Delete a widget
405 delete widget1;
406 QCOMPARE(split.count(), 2);
407 QCOMPARE(split.indexOf(widget1), -1); // Nasty
408 QCOMPARE(split.widget(0), widget3);
409 QCOMPARE(split.widget(1), widget2);
410
411 delete widget2;
412}
413
414void tst_QSplitter::insertWidget()
415{
416 QSplitter split;
417 QWidget *widget1 = new QWidget;
418 QWidget *widget2 = new QWidget;
419 QWidget *widget3 = new QWidget;
420
421 split.insertWidget(index: 0, widget: widget1);
422 QCOMPARE(split.count(), 1);
423 QCOMPARE(split.indexOf(widget1), 0);
424 QCOMPARE(split.widget(0), widget1);
425
426 split.insertWidget(index: 0, widget: widget2);
427 QCOMPARE(split.count(), 2);
428 QCOMPARE(split.indexOf(widget1), 1);
429 QCOMPARE(split.indexOf(widget2), 0);
430 QCOMPARE(split.widget(0), widget2);
431 QCOMPARE(split.widget(1), widget1);
432
433 split.insertWidget(index: 1, widget: widget3);
434 QCOMPARE(split.count(), 3);
435 QCOMPARE(split.indexOf(widget1), 2);
436 QCOMPARE(split.indexOf(widget2), 0);
437 QCOMPARE(split.indexOf(widget3), 1);
438 QCOMPARE(split.widget(0), widget2);
439 QCOMPARE(split.widget(1), widget3);
440 QCOMPARE(split.widget(2), widget1);
441
442 delete widget3;
443 QCOMPARE(split.count(), 2);
444 QCOMPARE(split.indexOf(widget1), 1);
445 QCOMPARE(split.indexOf(widget2), 0);
446 QCOMPARE(split.widget(0), widget2);
447 QCOMPARE(split.widget(1), widget1);
448
449 widget3 = new QWidget;
450 split.insertWidget(index: split.count() + 1, widget: widget3);
451 QCOMPARE(split.count(), 3);
452 QCOMPARE(split.indexOf(widget1), 1);
453 QCOMPARE(split.indexOf(widget2), 0);
454 QCOMPARE(split.indexOf(widget3), 2);
455 QCOMPARE(split.widget(0), widget2);
456 QCOMPARE(split.widget(1), widget1);
457 QCOMPARE(split.widget(2), widget3);
458
459
460 // Try it again,
461 split.insertWidget(index: split.count() + 1, widget: widget3);
462 QCOMPARE(split.count(), 3);
463 QCOMPARE(split.indexOf(widget1), 1);
464 QCOMPARE(split.indexOf(widget2), 0);
465 QCOMPARE(split.indexOf(widget3), 2);
466 QCOMPARE(split.widget(0), widget2);
467 QCOMPARE(split.widget(1), widget1);
468 QCOMPARE(split.widget(2), widget3);
469
470 // Try to move widget2 to a bad place
471 split.insertWidget(index: -1, widget: widget2);
472 QCOMPARE(split.count(), 3);
473 QCOMPARE(split.indexOf(widget1), 0);
474 QCOMPARE(split.indexOf(widget2), 2);
475 QCOMPARE(split.indexOf(widget3), 1);
476 QCOMPARE(split.widget(0), widget1);
477 QCOMPARE(split.widget(1), widget3);
478 QCOMPARE(split.widget(2), widget2);
479
480 QWidget *widget4 = new QWidget(&split);
481 QCOMPARE(split.count(), 4);
482 QCOMPARE(split.indexOf(widget1), 0);
483 QCOMPARE(split.indexOf(widget2), 2);
484 QCOMPARE(split.indexOf(widget3), 1);
485 QCOMPARE(split.indexOf(widget4), 3);
486 QCOMPARE(split.widget(0), widget1);
487 QCOMPARE(split.widget(1), widget3);
488 QCOMPARE(split.widget(2), widget2);
489 QCOMPARE(split.widget(3), widget4);
490
491 QWidget *widget5 = new QWidget(&split);
492 QCOMPARE(split.count(), 5);
493 QCOMPARE(split.indexOf(widget1), 0);
494 QCOMPARE(split.indexOf(widget2), 2);
495 QCOMPARE(split.indexOf(widget3), 1);
496 QCOMPARE(split.indexOf(widget4), 3);
497 QCOMPARE(split.indexOf(widget5), 4);
498 QCOMPARE(split.widget(0), widget1);
499 QCOMPARE(split.widget(1), widget3);
500 QCOMPARE(split.widget(2), widget2);
501 QCOMPARE(split.widget(3), widget4);
502 QCOMPARE(split.widget(4), widget5);
503
504 split.insertWidget(index: 2, widget: widget4);
505 QCOMPARE(split.count(), 5);
506 QCOMPARE(split.indexOf(widget1), 0);
507 QCOMPARE(split.indexOf(widget2), 3);
508 QCOMPARE(split.indexOf(widget3), 1);
509 QCOMPARE(split.indexOf(widget4), 2);
510 QCOMPARE(split.indexOf(widget5), 4);
511 QCOMPARE(split.widget(0), widget1);
512 QCOMPARE(split.widget(1), widget3);
513 QCOMPARE(split.widget(2), widget4);
514 QCOMPARE(split.widget(3), widget2);
515 QCOMPARE(split.widget(4), widget5);
516
517 split.insertWidget(index: 1, widget: widget5);
518 QCOMPARE(split.count(), 5);
519 QCOMPARE(split.indexOf(widget1), 0);
520 QCOMPARE(split.indexOf(widget2), 4);
521 QCOMPARE(split.indexOf(widget3), 2);
522 QCOMPARE(split.indexOf(widget4), 3);
523 QCOMPARE(split.indexOf(widget5), 1);
524 QCOMPARE(split.widget(0), widget1);
525 QCOMPARE(split.widget(1), widget5);
526 QCOMPARE(split.widget(2), widget3);
527 QCOMPARE(split.widget(3), widget4);
528 QCOMPARE(split.widget(4), widget2);
529}
530
531void tst_QSplitter::setStretchFactor_data()
532{
533 QTest::addColumn<int>(name: "orientation");
534 QTest::addColumn<int>(name: "widgetIndex");
535 QTest::addColumn<int>(name: "stretchFactor");
536 QTest::addColumn<int>(name: "expectedHStretch");
537 QTest::addColumn<int>(name: "expectedVStretch");
538
539 QTest::newRow(dataTag: "ok01") << int(Qt::Horizontal) << 1 << 2 << 2 << 2;
540 QTest::newRow(dataTag: "ok02") << int(Qt::Horizontal) << 2 << 0 << 0 << 0;
541 QTest::newRow(dataTag: "ok03") << int(Qt::Horizontal) << 3 << 1 << 1 << 1;
542 QTest::newRow(dataTag: "ok04") << int(Qt::Horizontal) << 0 << 7 << 7 << 7;
543 QTest::newRow(dataTag: "ok05") << int(Qt::Vertical) << 0 << 0 << 0 << 0;
544 QTest::newRow(dataTag: "ok06") << int(Qt::Vertical) << 1 << 1 << 1 << 1;
545 QTest::newRow(dataTag: "ok07") << int(Qt::Vertical) << 2 << 2 << 2 << 2;
546 QTest::newRow(dataTag: "ok08") << int(Qt::Vertical) << 3 << 5 << 5 << 5;
547 QTest::newRow(dataTag: "ok09") << int(Qt::Vertical) << -1 << 5 << 0 << 0;
548}
549
550void tst_QSplitter::setStretchFactor()
551{
552 QFETCH(int, orientation);
553 Qt::Orientation orient = Qt::Orientation(orientation);
554 QSplitter split(orient);
555 QWidget *w = new QWidget;
556 split.addWidget(widget: w);
557 w = new QWidget;
558 split.addWidget(widget: w);
559 w = new QWidget;
560 split.addWidget(widget: w);
561 w = new QWidget;
562 split.addWidget(widget: w);
563
564 QFETCH(int, widgetIndex);
565 QFETCH(int, stretchFactor);
566 w = split.widget(index: widgetIndex);
567 QSizePolicy sp;
568 if (w) {
569 QCOMPARE(sp.horizontalStretch(), 0);
570 QCOMPARE(sp.verticalStretch(), 0);
571 }
572 split.setStretchFactor(index: widgetIndex, stretch: stretchFactor);
573 if (w)
574 sp = w->sizePolicy();
575 QTEST(sp.horizontalStretch(), "expectedHStretch");
576 QTEST(sp.verticalStretch(), "expectedVStretch");
577}
578
579void tst_QSplitter::testShowHide_data()
580{
581 QTest::addColumn<bool>(name: "hideWidget1");
582 QTest::addColumn<bool>(name: "hideWidget2");
583 QTest::addColumn<QList<int> >(name: "finalValues");
584 QTest::addColumn<bool>(name: "handleVisible");
585
586 QSplitter *split = new QSplitter(Qt::Horizontal);
587 QTest::newRow(dataTag: "hideNone") << false << false << (QList<int>() << 200 << 200) << true;
588 QTest::newRow(dataTag: "hide2") << false << true << (QList<int>() << 400 + split->handleWidth() << 0) << false;
589 QTest::newRow(dataTag: "hide1") << true << false << (QList<int>() << 0 << 400 + split->handleWidth()) << false;
590 QTest::newRow(dataTag: "hideall") << true << true << (QList<int>() << 0 << 0) << false;
591 delete split;
592}
593
594void tst_QSplitter::testShowHide()
595{
596 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
597 QSKIP("Wayland: This fails. Figure out why.");
598
599 QFETCH(bool, hideWidget1);
600 QFETCH(bool, hideWidget2);
601
602 QSplitter *split = new QSplitter(Qt::Horizontal);
603
604 QWidget topLevel;
605 QWidget widget(&topLevel);
606 widget.resize(w: 400 + split->handleWidth(), h: 200);
607 QVBoxLayout *lay=new QVBoxLayout(&widget);
608 lay->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
609 lay->setSpacing(0);
610 split->addWidget(widget: new QWidget);
611 split->addWidget(widget: new QWidget);
612 split->setSizes(QList<int>() << 200 << 200);
613 lay->addWidget(split);
614 widget.setLayout(lay);
615 topLevel.show();
616 QVERIFY(QTest::qWaitForWindowActive(&topLevel));
617
618 widget.hide();
619 split->widget(index: 0)->setHidden(hideWidget1);
620 split->widget(index: 1)->setHidden(hideWidget2);
621 widget.show();
622 QTest::qWait(ms: 100);
623
624 QTEST(split->sizes(), "finalValues");
625 QTEST(split->handle(1)->isVisible(), "handleVisible");
626}
627
628void tst_QSplitter::testRemoval()
629{
630
631 // This test relies on the internal structure of QSplitter That is, that
632 // there is a handle before every splitter, but sometimes that handle is
633 // hidden. But definiately when something is removed the front handle
634 // should not be visible.
635
636 QSplitter split;
637 split.addWidget(widget: new QWidget);
638 split.addWidget(widget: new QWidget);
639 split.show();
640 QTest::qWait(ms: 100);
641
642 QCOMPARE(split.handle(0)->isVisible(), false);
643 QSplitterHandle *handle = split.handle(index: 1);
644 QCOMPARE(handle->isVisible(), true);
645
646 delete split.widget(index: 0);
647 QSplitterHandle *sameHandle = split.handle(index: 0);
648 QCOMPARE(handle, sameHandle);
649 QCOMPARE(sameHandle->isVisible(), false);
650}
651
652class MyFriendlySplitter : public QSplitter
653{
654public:
655 MyFriendlySplitter(QWidget *parent = 0) : QSplitter(parent) {}
656 void setRubberBand(int pos) { QSplitter::setRubberBand(pos); }
657
658 void moveSplitter(int pos, int index) { QSplitter::moveSplitter(pos, index); }
659
660 friend class tst_QSplitter;
661};
662
663class EventCounterSpy : public QObject
664{
665public:
666 EventCounterSpy(QWidget *obj) : objectToWatch(obj)
667 { }
668
669 ~EventCounterSpy()
670 {
671 removeEventFilter();
672 }
673
674 void installEventFilter()
675 {
676 if (needRemoveEventFilter)
677 return;
678 needRemoveEventFilter = true;
679 qApp->installEventFilter(filterObj: this);
680 }
681
682 void removeEventFilter()
683 {
684 if (!needRemoveEventFilter)
685 return;
686 needRemoveEventFilter = false;
687 qApp->removeEventFilter(obj: this);
688 }
689
690 bool eventFilter(QObject *watched, QEvent *event) override
691 {
692 // Watch for events in the parent widget and all its children
693 if (watched == objectToWatch || watched->parent() == objectToWatch) {
694 if (event->type() == QEvent::Resize)
695 resizeCount++;
696 else if (event->type() == QEvent::Paint)
697 paintCount++;
698 }
699
700 return QObject::eventFilter(watched, event);
701 }
702
703 int resizeCount = 0;
704 int paintCount = 0;
705 bool needRemoveEventFilter = false;
706 QObject *objectToWatch;
707};
708
709void tst_QSplitter::replaceWidget_data()
710{
711 QTest::addColumn<int>(name: "index");
712 QTest::addColumn<bool>(name: "visible");
713 QTest::addColumn<bool>(name: "collapsed");
714
715 QTest::newRow(dataTag: "negative index") << -1 << true << false;
716 QTest::newRow(dataTag: "index too large") << 80 << true << false;
717 QTest::newRow(dataTag: "visible, not collapsed") << 3 << true << false;
718 QTest::newRow(dataTag: "visible, collapsed") << 3 << true << true;
719 QTest::newRow(dataTag: "not visible, not collapsed") << 3 << false << false;
720 QTest::newRow(dataTag: "not visible, collapsed") << 3 << false << true;
721}
722
723void tst_QSplitter::replaceWidget()
724{
725 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
726 QSKIP("Wayland: This fails. Figure out why.");
727
728 QFETCH(int, index);
729 QFETCH(bool, visible);
730 QFETCH(bool, collapsed);
731
732 // Setup
733 MyFriendlySplitter sp;
734 const int count = 7;
735 for (int i = 0; i < count; i++) {
736 // We use labels instead of plain widgets to
737 // make it easier to fix eventual regressions.
738 QLabel *w = new QLabel(QString::asprintf(format: "WIDGET #%d", i));
739 sp.addWidget(widget: w);
740 }
741 sp.setWindowTitle(QString::asprintf(format: "index %d, visible %d, collapsed %d", index, visible, collapsed));
742 sp.show();
743 QVERIFY(QTest::qWaitForWindowExposed(&sp));
744
745 // Configure splitter
746 QWidget *oldWidget = sp.widget(index);
747 QTest::qWait(ms: 100); // Make sure we record the right original size (after the window manager adds the frame)
748 const QRect oldGeom = oldWidget ? oldWidget->geometry() : QRect();
749 if (oldWidget) {
750 // Collapse first, then hide, if necessary
751 if (collapsed) {
752 sp.setCollapsible(index, true);
753 sp.moveSplitter(pos: oldWidget->x() + 1, index: index + 1);
754 }
755 if (!visible)
756 oldWidget->hide();
757 }
758
759 // Replace widget
760 QTest::qWait(ms: 100); // Flush event queue
761 const QList<int> sizes = sp.sizes();
762 // Shorter label: The important thing is to ensure we can set
763 // the same size on the new widget. Because of QLabel's sizing
764 // constraints (they can expand but not shrink) the easiest is
765 // to set a shorter label.
766 QLabel *newWidget = new QLabel(QLatin1String("<b>NEW</b>"));
767
768 EventCounterSpy ef(&sp);
769 ef.installEventFilter();
770 const QWidget *res = sp.replaceWidget(index, widget: newWidget);
771 QTest::qWait(ms: 100); // Give visibility and resizing some time
772
773 // Check
774 if (index < 0 || index >= count) {
775 QVERIFY(!res);
776 QVERIFY(!newWidget->parentWidget());
777 QCOMPARE(ef.resizeCount, 0);
778 QCOMPARE(ef.paintCount, 0);
779 } else {
780 QCOMPARE(res, oldWidget);
781 QVERIFY(!res->parentWidget());
782 QVERIFY(!res->isVisible());
783 const int expectedResizeCount = visible ? 1 : 0; // new widget only
784 const int expectedPaintCount = visible && !collapsed ? 2 : 0; // splitter and new widget
785 QTRY_COMPARE(ef.resizeCount, expectedResizeCount);
786#ifndef Q_OS_WINRT // QTBUG-68297
787 QTRY_COMPARE(ef.paintCount, expectedPaintCount);
788#endif
789 QCOMPARE(newWidget->parentWidget(), &sp);
790 QCOMPARE(newWidget->isVisible(), visible);
791 if (visible && !collapsed)
792 QCOMPARE(newWidget->geometry(), oldGeom);
793#ifndef Q_OS_WINRT // QTBUG-68297
794 QCOMPARE(newWidget->size().isEmpty(), !visible || collapsed);
795#endif
796 delete res;
797 }
798 QCOMPARE(sp.count(), count);
799 QCOMPARE(sp.sizes(), sizes);
800}
801
802void tst_QSplitter::replaceWidgetWithSplitterChild_data()
803{
804 QTest::addColumn<int>(name: "srcIndex");
805 QTest::addColumn<int>(name: "dstIndex");
806
807 QTest::newRow(dataTag: "replace with null widget") << -2 << 3;
808 QTest::newRow(dataTag: "replace with itself") << 3 << 3;
809 QTest::newRow(dataTag: "replace with sibling, after recalc") << 1 << 4;
810 QTest::newRow(dataTag: "replace with sibling, before recalc") << -1 << 4;
811}
812
813void tst_QSplitter::replaceWidgetWithSplitterChild()
814{
815 QFETCH(int, srcIndex);
816 QFETCH(int, dstIndex);
817
818 // Setup
819 MyFriendlySplitter sp;
820 const int count = 7;
821 for (int i = 0; i < count; i++) {
822 // We use labels instead of plain widgets to
823 // make it easier to fix eventual regressions.
824 QLabel *w = new QLabel(QString::asprintf(format: "WIDGET #%d", i));
825 sp.addWidget(widget: w);
826 }
827 sp.setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1Char(' ') + QLatin1String(QTest::currentDataTag()));
828 sp.show();
829 QVERIFY(QTest::qWaitForWindowExposed(&sp));
830
831 QTest::qWait(ms: 100); // Flush event queue before new widget creation
832 const QList<int> sizes = sp.sizes();
833 QWidget *sibling = srcIndex == -1 ? (new QLabel("<b>NEW</b>", &sp)) : sp.widget(index: srcIndex);
834
835 EventCounterSpy ef(&sp);
836 ef.installEventFilter();
837 const QWidget *res = sp.replaceWidget(index: dstIndex, widget: sibling);
838 QTest::qWait(ms: 100); // Give visibility and resizing some time
839
840 QVERIFY(!res);
841 if (srcIndex == -1) {
842 // Create and replace before recalc. The sibling is scheduled to be
843 // added after replaceWidget(), when QSplitter receives a child event.
844 QTRY_VERIFY(ef.resizeCount > 0);
845 QTRY_VERIFY(ef.paintCount > 0);
846 QCOMPARE(sp.count(), count + 1);
847#ifndef Q_OS_WINRT // QTBUG-68297
848 QCOMPARE(sp.sizes().mid(0, count), sizes);
849#endif
850 QCOMPARE(sp.sizes().last(), sibling->width());
851 } else {
852 // No-op for the rest
853 QCOMPARE(ef.resizeCount, 0);
854 QCOMPARE(ef.paintCount, 0);
855 QCOMPARE(sp.count(), count);
856 QCOMPARE(sp.sizes(), sizes);
857 }
858}
859
860void tst_QSplitter::handleMinimumWidth()
861{
862 MyFriendlySplitter split;
863 split.addWidget(widget: new QLabel("Number Wan"));
864 split.addWidget(widget: new QLabel("Number Too"));
865
866 split.show();
867 QVERIFY(QTest::qWaitForWindowExposed(&split));
868 for (int i = 0; i < 10; i++) {
869 split.setHandleWidth(i);
870 QTest::qWait(ms: 100); // resizing
871 QCOMPARE(split.handle(1)->width(), qMax(4 + (i & 1), i));
872 }
873
874 split.setOrientation(Qt::Vertical);
875 QTest::qWait(ms: 100);
876 for (int i = 0; i < 10; i++) {
877 split.setHandleWidth(i);
878 QTest::qWait(ms: 100); // resizing
879 QCOMPARE(split.handle(1)->height(), qMax(4 + (i & 1), i));
880 }
881}
882
883void tst_QSplitter::rubberBandNotInSplitter()
884{
885 MyFriendlySplitter split;
886 split.addWidget(widget: new QWidget);
887 split.addWidget(widget: new QWidget);
888 split.setOpaqueResize(false);
889 QCOMPARE(split.count(), 2);
890 split.setRubberBand(2);
891 QCOMPARE(split.count(), 2);
892}
893
894void tst_QSplitter::task187373_addAbstractScrollAreas_data()
895{
896 QTest::addColumn<QString>(name: "className");
897 QTest::addColumn<bool>(name: "addInConstructor");
898 QTest::addColumn<bool>(name: "addOutsideConstructor");
899
900 QStringList classNames;
901 classNames << QLatin1String("QGraphicsView");
902 classNames << QLatin1String("QMdiArea");
903 classNames << QLatin1String("QScrollArea");
904 classNames << QLatin1String("QTextEdit");
905 classNames << QLatin1String("QTreeView");
906
907 foreach (QString className, classNames) {
908 QTest::newRow(qPrintable(className + QLatin1String(" 1"))) << className << false << true;
909 QTest::newRow(qPrintable(className + QLatin1String(" 2"))) << className << true << false;
910 QTest::newRow(qPrintable(className + QLatin1String(" 3"))) << className << true << true;
911 }
912}
913
914static QAbstractScrollArea *task187373_createScrollArea(
915 QSplitter *splitter, const QString &className, bool addInConstructor)
916{
917 if (className == QLatin1String("QGraphicsView"))
918 return new QGraphicsView(addInConstructor ? splitter : 0);
919 if (className == QLatin1String("QMdiArea"))
920 return new QMdiArea(addInConstructor ? splitter : 0);
921 if (className == QLatin1String("QScrollArea"))
922 return new QScrollArea(addInConstructor ? splitter : 0);
923 if (className == QLatin1String("QTextEdit"))
924 return new QTextEdit(addInConstructor ? splitter : 0);
925 if (className == QLatin1String("QTreeView"))
926 return new QTreeView(addInConstructor ? splitter : 0);
927 return 0;
928}
929
930void tst_QSplitter::task187373_addAbstractScrollAreas()
931{
932 QFETCH(QString, className);
933 QFETCH(bool, addInConstructor);
934 QFETCH(bool, addOutsideConstructor);
935 QVERIFY(addInConstructor || addOutsideConstructor);
936
937 QSplitter *splitter = new QSplitter;
938 splitter->show();
939 QVERIFY(splitter->isVisible());
940
941 QAbstractScrollArea *w = task187373_createScrollArea(splitter, className, addInConstructor);
942 QVERIFY(w);
943 if (addOutsideConstructor)
944 splitter->addWidget(widget: w);
945
946 QTRY_VERIFY(w->isVisible());
947 QVERIFY(!w->isHidden());
948 QVERIFY(w->viewport()->isVisible());
949 QVERIFY(!w->viewport()->isHidden());
950}
951
952//! A simple QTextEdit which can switch between two different size states
953class MyTextEdit : public QTextEdit
954{
955 public:
956 MyTextEdit(const QString & text, QWidget* parent = NULL)
957 : QTextEdit(text, parent) , m_iFactor(1)
958 {
959 setSizePolicy(hor: QSizePolicy::Maximum, ver: QSizePolicy::Maximum);
960 }
961 virtual QSize minimumSizeHint () const
962 {
963 return QSize(200, 200) * m_iFactor;
964 }
965 virtual QSize sizeHint() const
966 {
967 return QSize(390, 390) * m_iFactor;
968 }
969 int m_iFactor;
970};
971
972void tst_QSplitter::task169702_sizes()
973{
974 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
975 QSKIP("Wayland: This fails. Figure out why.");
976
977 QWidget topLevel;
978 // Create two nested (non-collapsible) splitters
979 QSplitter* outerSplitter = new QSplitter(Qt::Vertical, &topLevel);
980 outerSplitter->setChildrenCollapsible(false);
981 QSplitter* splitter = new QSplitter(Qt::Horizontal, outerSplitter);
982 splitter->setChildrenCollapsible(false);
983
984 // populate the outer splitter
985 outerSplitter->addWidget(widget: new QTextEdit("Foo"));
986 outerSplitter->addWidget(widget: splitter);
987 outerSplitter->setStretchFactor(index: 0, stretch: 1);
988 outerSplitter->setStretchFactor(index: 1, stretch: 0);
989
990 // populate the inner splitter
991 MyTextEdit* testW = new MyTextEdit("TextEdit with size restriction");
992 splitter->addWidget(widget: testW);
993 splitter->addWidget(widget: new QTextEdit("Bar"));
994
995 outerSplitter->setGeometry(ax: 100, ay: 100, aw: 500, ah: 500);
996 topLevel.show();
997 QVERIFY(QTest::qWaitForWindowActive(&topLevel));
998
999 testW->m_iFactor++;
1000 testW->updateGeometry();
1001
1002 //Make sure the minimimSizeHint is respected
1003 QTRY_COMPARE(testW->size().height(), testW->minimumSizeHint().height());
1004}
1005
1006void tst_QSplitter::taskQTBUG_4101_ensureOneNonCollapsedWidget_data()
1007{
1008 QTest::addColumn<bool>(name: "testingHide");
1009
1010 QTest::newRow(dataTag: "last non collapsed hidden") << true;
1011 QTest::newRow(dataTag: "last non collapsed deleted") << false;
1012}
1013
1014void tst_QSplitter::taskQTBUG_4101_ensureOneNonCollapsedWidget()
1015{
1016 QFETCH(bool, testingHide);
1017
1018 MyFriendlySplitter s;
1019 QLabel *l;
1020 for (int i = 0; i < 5; ++i) {
1021 l = new QLabel(QString("Label ") + QChar('A' + i));
1022 l->setAlignment(Qt::AlignCenter);
1023 s.addWidget(widget: l);
1024 s.moveSplitter(pos: 0, index: i); // Collapse all the labels except the last one.
1025 }
1026
1027 s.show();
1028 if (testingHide)
1029 l->hide();
1030 else
1031 delete l;
1032 QTRY_VERIFY(s.sizes().at(0) > 0);
1033}
1034
1035void tst_QSplitter::setLayout()
1036{
1037 QSplitter splitter;
1038 QVBoxLayout layout;
1039 QTest::ignoreMessage(type: QtWarningMsg, message: "Adding a QLayout to a QSplitter is not supported.");
1040 splitter.setLayout(&layout);
1041 // It will work, but we don't recommend it...
1042 QCOMPARE(splitter.layout(), &layout);
1043}
1044
1045void tst_QSplitter::autoAdd()
1046{
1047 QSplitter splitter;
1048 splitter.setWindowTitle("autoAdd");
1049 splitter.setMinimumSize(QSize(200, 200));
1050 splitter.move(QGuiApplication::primaryScreen()->availableGeometry().center() - QPoint(100, 100));
1051 splitter.show();
1052 QVERIFY(QTest::qWaitForWindowExposed(&splitter));
1053 // Constructing a child widget on the splitter should
1054 // automatically add and show it.
1055 QWidget *childWidget = new QWidget(&splitter);
1056 QCOMPARE(splitter.count(), 1);
1057 QTRY_VERIFY(childWidget->isVisible());
1058 // Deleting should automatically remove it
1059 delete childWidget;
1060 QCOMPARE(splitter.count(), 0);
1061 // QTBUG-40132, top level windows should not be affected by this.
1062 QDialog *dialog = new QDialog(&splitter);
1063 QCOMPARE(splitter.count(), 0);
1064 QCoreApplication::processEvents();
1065 QVERIFY(!dialog->isVisible());
1066}
1067
1068QTEST_MAIN(tst_QSplitter)
1069#include "tst_qsplitter.moc"
1070

source code of qtbase/tests/auto/widgets/widgets/qsplitter/tst_qsplitter.cpp