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 <QtGui>
32#include <QtWidgets>
33
34#include <QtTest/private/qtesthelpers_p.h>
35
36using namespace QTestPrivate;
37
38class tst_QBoxLayout : public QObject
39{
40 Q_OBJECT
41
42private slots:
43 void cleanup();
44 void insertSpacerItem();
45 void insertLayout();
46 void sizeHint();
47 void sizeConstraints();
48 void setGeometry();
49 void setStyleShouldChangeSpacing();
50 void widgetSurplus();
51
52 void testLayoutEngine_data();
53 void testLayoutEngine();
54
55 void taskQTBUG_7103_minMaxWidthNotRespected();
56 void taskQTBUG_27420_takeAtShouldUnparentLayout();
57 void taskQTBUG_40609_addingWidgetToItsOwnLayout();
58 void taskQTBUG_40609_addingLayoutToItself();
59 void replaceWidget();
60 void indexOf();
61};
62
63class CustomLayoutStyle : public QProxyStyle
64{
65 Q_OBJECT
66public:
67 CustomLayoutStyle() : QProxyStyle(QStyleFactory::create("windows"))
68 {
69 hspacing = 5;
70 vspacing = 10;
71 }
72
73 virtual int pixelMetric(PixelMetric metric, const QStyleOption * option = 0,
74 const QWidget * widget = 0 ) const;
75
76 int hspacing;
77 int vspacing;
78};
79
80int CustomLayoutStyle::pixelMetric(PixelMetric metric, const QStyleOption * option /*= 0*/,
81 const QWidget * widget /*= 0*/ ) const
82{
83 switch (metric) {
84 case PM_LayoutLeftMargin:
85 return 0;
86 break;
87 case PM_LayoutTopMargin:
88 return 3;
89 break;
90 case PM_LayoutRightMargin:
91 return 6;
92 break;
93 case PM_LayoutBottomMargin:
94 return 9;
95 break;
96 case PM_LayoutHorizontalSpacing:
97 return hspacing;
98 case PM_LayoutVerticalSpacing:
99 return vspacing;
100 break;
101 default:
102 break;
103 }
104 return QProxyStyle::pixelMetric(metric, option, widget);
105}
106
107void tst_QBoxLayout::cleanup()
108{
109 QVERIFY(QApplication::topLevelWidgets().isEmpty());
110}
111
112void tst_QBoxLayout::insertSpacerItem()
113{
114 QWidget window;
115 window.setWindowTitle(QTest::currentTestFunction());
116
117 QSpacerItem *spacer1 = new QSpacerItem(20, 10, QSizePolicy::Expanding, QSizePolicy::Expanding);
118 QSpacerItem *spacer2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Expanding);
119
120 QBoxLayout *layout = new QHBoxLayout;
121 layout->addWidget(new QLineEdit("Foooooooooooooooooooooooooo"));
122 layout->addSpacerItem(spacerItem: spacer1);
123 layout->addWidget(new QLineEdit("Baaaaaaaaaaaaaaaaaaaaaaaaar"));
124 layout->insertSpacerItem(index: 0, spacerItem: spacer2);
125 window.setLayout(layout);
126
127 QCOMPARE(layout->itemAt(0), spacer2);
128 QCOMPARE(layout->itemAt(2), spacer1);
129
130 window.show();
131}
132
133void tst_QBoxLayout::insertLayout()
134{
135 QWidget window;
136 QVBoxLayout *vbox = new QVBoxLayout(&window);
137 QScopedPointer<QVBoxLayout> dummyParentLayout(new QVBoxLayout);
138 QHBoxLayout *subLayout = new QHBoxLayout;
139 dummyParentLayout->addLayout(layout: subLayout);
140 QCOMPARE(subLayout->parent(), dummyParentLayout.data());
141 QCOMPARE(dummyParentLayout->count(), 1);
142
143 // add subLayout to another layout
144 QTest::ignoreMessage(type: QtWarningMsg, message: "QLayout::addChildLayout: layout \"\" already has a parent");
145 vbox->addLayout(layout: subLayout);
146 QCOMPARE((subLayout->parent() == vbox), (vbox->count() == 1));
147}
148
149
150void tst_QBoxLayout::sizeHint()
151{
152 QWidget window;
153 window.setWindowTitle(QTest::currentTestFunction());
154 QHBoxLayout *lay1 = new QHBoxLayout;
155 QHBoxLayout *lay2 = new QHBoxLayout;
156 QLabel *label = new QLabel("widget twooooooooooooooooooooooooooooooooooooooooooooooooooooooo");
157 lay2->addWidget(label);
158 lay1->addLayout(layout: lay2);
159 window.setLayout(lay1);
160 window.show();
161 QVERIFY(QTest::qWaitForWindowExposed(&window));
162 label->setText("foooooooo baaaaaaar");
163 QSize sh = lay1->sizeHint();
164 QApplication::processEvents();
165 // Note that this is not strictly required behaviour - actually
166 // the preferred behaviour would be that sizeHint returns
167 // the same value regardless of what's lying in the event queue.
168 // (i.e. we would check for equality here instead)
169 QVERIFY(lay1->sizeHint() != sh);
170}
171
172void tst_QBoxLayout::sizeConstraints()
173{
174 QWidget window;
175 window.setWindowTitle(QTest::currentTestFunction());
176 QHBoxLayout *lay = new QHBoxLayout;
177 lay->addWidget(new QLabel("foooooooooooooooooooooooooooooooooooo"));
178 lay->addWidget(new QLabel("baaaaaaaaaaaaaaaaaaaaaaaaaaaaaar"));
179 lay->setSizeConstraint(QLayout::SetFixedSize);
180 window.setLayout(lay);
181 window.show();
182 QVERIFY(QTest::qWaitForWindowExposed(&window));
183 QSize sh = window.sizeHint();
184 delete lay->takeAt(1);
185 QVERIFY(sh.width() >= window.sizeHint().width() &&
186 sh.height() >= window.sizeHint().height());
187
188}
189
190void tst_QBoxLayout::setGeometry()
191{
192 QWidget toplevel;
193 toplevel.setWindowTitle(QTest::currentTestFunction());
194 setFrameless(&toplevel);
195 QWidget w(&toplevel);
196 QVBoxLayout *lay = new QVBoxLayout;
197 lay->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
198 lay->setSpacing(0);
199 QHBoxLayout *lay2 = new QHBoxLayout;
200 QDial *dial = new QDial;
201 lay2->addWidget(dial);
202 lay2->setAlignment(Qt::AlignTop);
203 lay2->setAlignment(Qt::AlignRight);
204 lay->addLayout(layout: lay2);
205 w.setLayout(lay);
206 toplevel.show();
207
208 QRect newGeom(0, 0, 70, 70);
209 lay2->setGeometry(newGeom);
210 QVERIFY2(newGeom.contains(dial->geometry()), "dial->geometry() should be smaller and within newGeom");
211}
212
213void tst_QBoxLayout::setStyleShouldChangeSpacing()
214{
215
216 QWidget window;
217 window.setWindowTitle(QTest::currentTestFunction());
218 QHBoxLayout *hbox = new QHBoxLayout(&window);
219 QPushButton *pb1 = new QPushButton(tr(s: "The spacing between this"));
220 QPushButton *pb2 = new QPushButton(tr(s: "and this button should depend on the style of the parent widget"));;
221 pb1->setAttribute(Qt::WA_LayoutUsesWidgetRect);
222 pb2->setAttribute(Qt::WA_LayoutUsesWidgetRect);
223 hbox->addWidget(pb1);
224 hbox->addWidget(pb2);
225 QScopedPointer<CustomLayoutStyle> style1(new CustomLayoutStyle);
226 style1->hspacing = 6;
227 window.setStyle(style1.data());
228 window.show();
229 QVERIFY(QTest::qWaitForWindowExposed(&window));
230
231 auto spacing = [&]() { return pb2->geometry().left() - pb1->geometry().right() - 1; };
232 QCOMPARE(spacing(), 6);
233
234 QScopedPointer<CustomLayoutStyle> style2(new CustomLayoutStyle());
235 style2->hspacing = 10;
236 window.setStyle(style2.data());
237 QTRY_COMPARE(spacing(), 10);
238}
239
240class MarginEatingStyle : public QProxyStyle
241{
242public:
243 MarginEatingStyle() : QProxyStyle(QStyleFactory::create("windows"))
244 {
245 }
246
247 virtual QRect subElementRect(SubElement sr, const QStyleOption *opt,
248 const QWidget *widget) const
249 {
250 QRect rect = opt->rect;
251 switch (sr) {
252 case SE_GroupBoxLayoutItem:
253 // this is a simplifed version of what the macOS style does
254 rect.setTop(rect.top() + 20);
255 rect.setLeft(rect.left() + 20);
256 rect.setRight(rect.right() - 20);
257 rect.setBottom(rect.bottom() - 20);
258 break;
259 default:
260 return QProxyStyle::subElementRect(element: sr, option: opt, widget);
261 }
262
263 return rect;
264 }
265};
266
267void tst_QBoxLayout::widgetSurplus()
268{
269 // Test case for QTBUG-67608 - a style requests space in the margin
270
271 QDialog window;
272 QScopedPointer<MarginEatingStyle> marginEater(new MarginEatingStyle);
273 QVBoxLayout *vbox = new QVBoxLayout(&window);
274 vbox->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
275 vbox->setSpacing(0);
276
277 QLabel *hiddenLabel = new QLabel(tr(s: "Invisible label"));
278 hiddenLabel->setVisible(false);
279
280 QGroupBox *groupBox = new QGroupBox(tr(s: "Groupbox Title"));
281 groupBox->setStyle(marginEater.data());
282 groupBox->setObjectName("Test group box");
283 QPushButton *button1 = new QPushButton(tr(s: "Button 1"));
284 QPushButton *button2 = new QPushButton(tr(s: "Button 2"));
285 QVBoxLayout *groupLayout = new QVBoxLayout;
286 groupLayout->addWidget(button1);
287 groupLayout->addWidget(button2);
288 groupBox->setLayout(groupLayout);
289
290 QLabel *label = new QLabel(tr(s: "Visible label"));
291
292 vbox->addWidget(hiddenLabel);
293 vbox->addWidget(groupBox);
294 vbox->addWidget(label);
295 window.setLayout(vbox);
296
297 window.show();
298 QVERIFY(QTest::qWaitForWindowExposed(&window));
299 QCOMPARE(groupBox->y(), 0);
300 QCOMPARE(groupBox->x(), 0);
301}
302
303void tst_QBoxLayout::taskQTBUG_7103_minMaxWidthNotRespected()
304{
305 QLabel *label = new QLabel("Qt uses standard C++, but makes extensive use of the C pre-processor to enrich the language. Qt can also be used in several other programming languages via language bindings. It runs on all major platforms, and has extensive internationalization support. Non-GUI features include SQL database access, XML parsing, thread management, network support and a unified cross-platform API for file handling.");
306 label->setWordWrap(true);
307 label->setFixedWidth(200);
308
309 QVBoxLayout *layout = new QVBoxLayout;
310 layout->addWidget(label);
311 layout->addSpacerItem(spacerItem: new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding));
312
313 QWidget widget;
314 widget.setWindowTitle(QTest::currentTestFunction());
315 widget.setLayout(layout);
316 widget.show();
317 QVERIFY(QTest::qWaitForWindowExposed(&widget));
318
319 int height = label->height();
320
321 QRect g = widget.geometry();
322 g.setWidth(600);
323 widget.setGeometry(g);
324
325 QTest::qWait(ms: 50);
326
327 QCOMPARE(label->height(), height);
328}
329
330void tst_QBoxLayout::taskQTBUG_27420_takeAtShouldUnparentLayout()
331{
332 QSharedPointer<QHBoxLayout> outer(new QHBoxLayout);
333 QPointer<QVBoxLayout> inner = new QVBoxLayout;
334
335 outer->addLayout(layout: inner);
336 QCOMPARE(outer->count(), 1);
337 QCOMPARE(inner->parent(), outer.data());
338
339 QLayoutItem *item = outer->takeAt(0);
340 QCOMPARE(item->layout(), inner.data());
341 QVERIFY(!item->layout()->parent());
342
343 outer.reset();
344
345 if (inner)
346 delete item; // success: a taken item/layout should not be deleted when the old parent is deleted
347 else
348 QVERIFY(!inner.isNull());
349}
350
351void tst_QBoxLayout::taskQTBUG_40609_addingWidgetToItsOwnLayout(){
352 QWidget widget;
353 widget.setWindowTitle(QTest::currentTestFunction());
354 widget.setObjectName("347b469225a24a0ef05150a");
355 QVBoxLayout layout(&widget);
356 layout.setObjectName("ef9e2b42298e0e6420105bb");
357
358 QTest::ignoreMessage(type: QtWarningMsg, message: "QLayout: Cannot add a null widget to QVBoxLayout/ef9e2b42298e0e6420105bb");
359 layout.addWidget(nullptr);
360 QCOMPARE(layout.count(), 0);
361
362 QTest::ignoreMessage(type: QtWarningMsg, message: "QLayout: Cannot add parent widget QWidget/347b469225a24a0ef05150a to its child layout QVBoxLayout/ef9e2b42298e0e6420105bb");
363 layout.addWidget(&widget);
364 QCOMPARE(layout.count(), 0);
365}
366
367void tst_QBoxLayout::taskQTBUG_40609_addingLayoutToItself(){
368 QWidget widget;
369 widget.setWindowTitle(QTest::currentTestFunction());
370 widget.setObjectName("fe44e5cb6c08006597126a");
371 QVBoxLayout layout(&widget);
372 layout.setObjectName("cc751dd0f50f62b05a62da");
373
374 QTest::ignoreMessage(type: QtWarningMsg, message: "QLayout: Cannot add a null layout to QVBoxLayout/cc751dd0f50f62b05a62da");
375 layout.addLayout(layout: nullptr);
376 QCOMPARE(layout.count(), 0);
377
378 QTest::ignoreMessage(type: QtWarningMsg, message: "QLayout: Cannot add layout QVBoxLayout/cc751dd0f50f62b05a62da to itself");
379 layout.addLayout(layout: &layout);
380 QCOMPARE(layout.count(), 0);
381}
382
383struct Descr
384{
385 Descr(int min, int sh, int max = -1, bool exp= false, int _stretch = 0, bool _empty = false)
386 :minimumSize(min), sizeHint(sh), maximumSize(max < 0 ? QLAYOUTSIZE_MAX : max),
387 expanding(exp), stretch(_stretch), empty(_empty)
388 {}
389
390 int minimumSize;
391 int sizeHint;
392 int maximumSize;
393 bool expanding;
394
395 int stretch;
396
397 bool empty;
398};
399
400
401typedef QList<Descr> DescrList;
402Q_DECLARE_METATYPE(DescrList);
403
404typedef QList<int> SizeList;
405typedef QList<int> PosList;
406
407
408
409class LayoutItem : public QLayoutItem
410{
411public:
412 LayoutItem(const Descr &descr) :m_descr(descr) {}
413
414 QSize sizeHint() const { return QSize(m_descr.sizeHint, 100); }
415 QSize minimumSize() const { return QSize(m_descr.minimumSize, 0); }
416 QSize maximumSize() const { return QSize(m_descr.maximumSize, QLAYOUTSIZE_MAX); }
417 Qt::Orientations expandingDirections() const
418 { return m_descr.expanding ? Qt::Horizontal : Qt::Orientations{}; }
419 void setGeometry(const QRect &r) { m_pos = r.x(); m_size = r.width();}
420 QRect geometry() const { return QRect(m_pos, 0, m_size, 100); }
421 bool isEmpty() const { return m_descr.empty; }
422
423private:
424 Descr m_descr;
425 int m_pos;
426 int m_size;
427};
428
429void tst_QBoxLayout::testLayoutEngine_data()
430{
431 // (int min, int sh, int max = -1, bool exp= false, int _stretch = 0, bool _empty = false)
432 QTest::addColumn<DescrList>(name: "itemDescriptions");
433 QTest::addColumn<int>(name: "size");
434 QTest::addColumn<int>(name: "spacing");
435 QTest::addColumn<PosList>(name: "expectedPositions");
436 QTest::addColumn<SizeList>(name: "expectedSizes");
437
438 QTest::newRow(dataTag: "Just one")
439 << (DescrList() << Descr(0, 100))
440 << 200
441 << 0
442 << (PosList() << 0)
443 << (SizeList() << 200);
444
445 QTest::newRow(dataTag: "Two non-exp")
446 << (DescrList() << Descr(0, 100) << Descr(0,100))
447 << 400
448 << 0
449 << (PosList() << 0 << 200)
450 << (SizeList() << 200 << 200);
451
452 QTest::newRow(dataTag: "Exp + non-exp")
453 << (DescrList() << Descr(0, 100, -1, true) << Descr(0,100))
454 << 400
455 << 0
456 << (PosList() << 0 << 300)
457 << (SizeList() << 300 << 100);
458
459
460 QTest::newRow(dataTag: "Stretch")
461 << (DescrList() << Descr(0, 100, -1, false, 1) << Descr(0,100, -1, false, 2))
462 << 300
463 << 0
464 << (PosList() << 0 << 100)
465 << (SizeList() << 100 << 200);
466
467
468 QTest::newRow(dataTag: "Spacing")
469 << (DescrList() << Descr(0, 100) << Descr(0,100))
470 << 400
471 << 10
472 << (PosList() << 0 << 205)
473 << (SizeList() << 195 << 195);
474
475
476 QTest::newRow(dataTag: "Less than minimum")
477 << (DescrList() << Descr(100, 100, 100, false) << Descr(50, 100, 100, false))
478 << 100
479 << 0
480 << (PosList() << 0 << 50)
481 << (SizeList() << 50 << 50);
482
483
484 QTest::newRow(dataTag: "Less than sizehint")
485 << (DescrList() << Descr(100, 200, 100, false) << Descr(50, 200, 100, false))
486 << 200
487 << 0
488 << (PosList() << 0 << 100)
489 << (SizeList() << 100 << 100);
490
491 QTest::newRow(dataTag: "Too much space")
492 << (DescrList() << Descr(0, 100, 100, false) << Descr(0, 100, 100, false))
493 << 500
494 << 0
495 << (PosList() << 100 << 300)
496 << (SizeList() << 100 << 100);
497
498 QTest::newRow(dataTag: "Empty")
499 << (DescrList() << Descr(0, 100, 100) << Descr(0,0,-1, false, 0, true) << Descr(0, 100, 100) )
500 << 500
501 << 0
502 << (PosList() << 100 << 300 << 300)
503 << (SizeList() << 100 << 0 << 100);
504
505 QTest::newRow(dataTag: "QTBUG-33104")
506 << (DescrList() << Descr(11, 75, 75, true) << Descr(75, 75))
507 << 200
508 << 0
509 << (PosList() << 0 << 75)
510 << (SizeList() << 75 << 125);
511
512 QTest::newRow(dataTag: "Expanding with maximumSize")
513 << (DescrList() << Descr(11, 75, 100, true) << Descr(75, 75))
514 << 200
515 << 0
516 << (PosList() << 0 << 100)
517 << (SizeList() << 100 << 100);
518
519 QTest::newRow(dataTag: "Stretch with maximumSize")
520 << (DescrList() << Descr(11, 75, 100, false, 1) << Descr(75, 75))
521 << 200
522 << 0
523 << (PosList() << 0 << 100)
524 << (SizeList() << 100 << 100);
525
526 QTest::newRow(dataTag: "Stretch with maximumSize last")
527 << (DescrList() << Descr(75, 75) << Descr(11, 75, 100, false, 1))
528 << 200
529 << 0
530 << (PosList() << 0 << 100)
531 << (SizeList() << 100 << 100);
532}
533
534void tst_QBoxLayout::testLayoutEngine()
535{
536 QFETCH(DescrList, itemDescriptions);
537 QFETCH(int, size);
538 QFETCH(int, spacing);
539 QFETCH(PosList, expectedPositions);
540 QFETCH(SizeList, expectedSizes);
541
542 QHBoxLayout box;
543 box.setSpacing(spacing);
544 int i;
545 for (i = 0; i < itemDescriptions.count(); ++i) {
546 Descr descr = itemDescriptions.at(i);
547 LayoutItem *li = new LayoutItem(descr);
548 box.addItem(li);
549 box.setStretch(index: i, stretch: descr.stretch);
550 }
551 box.setGeometry(QRect(0,0,size,100));
552 for (i = 0; i < expectedSizes.count(); ++i) {
553 int xSize = expectedSizes.at(i);
554 int xPos = expectedPositions.at(i);
555 QLayoutItem *item = box.itemAt(i);
556 QCOMPARE(item->geometry().width(), xSize);
557 QCOMPARE(item->geometry().x(), xPos);
558 }
559}
560
561void tst_QBoxLayout::replaceWidget()
562{
563 QWidget w;
564 QBoxLayout *boxLayout = new QVBoxLayout(&w);
565
566 QLineEdit *replaceFrom = new QLineEdit;
567 QLineEdit *replaceTo = new QLineEdit;
568 boxLayout->addWidget(new QLineEdit());
569 boxLayout->addWidget(replaceFrom);
570 boxLayout->addWidget(new QLineEdit());
571
572 QCOMPARE(boxLayout->indexOf(replaceFrom), 1);
573 QCOMPARE(boxLayout->indexOf(replaceTo), -1);
574 QCOMPARE(boxLayout->count(), 3);
575 boxLayout->replaceWidget(from: replaceFrom, to: replaceFrom);
576 QCOMPARE(boxLayout->count(), 3);
577
578 delete boxLayout->replaceWidget(from: replaceFrom, to: replaceTo);
579
580 QCOMPARE(boxLayout->indexOf(replaceFrom), -1);
581 QCOMPARE(boxLayout->indexOf(replaceTo), 1);
582}
583
584void tst_QBoxLayout::indexOf()
585{
586 QWidget w;
587 auto outer = new QVBoxLayout(&w);
588 auto inner = new QHBoxLayout();
589 outer->addLayout(layout: inner);
590 auto widget1 = new QWidget();
591 QWidget widget2;
592 inner->addWidget(widget1);
593
594 QCOMPARE(inner->indexOf(widget1), 0);
595 QCOMPARE(inner->indexOf(&widget2), -1);
596 QCOMPARE(outer->indexOf(widget1), -1);
597 QCOMPARE(outer->indexOf(&widget2), -1);
598 QCOMPARE(outer->indexOf(outer), -1);
599 QCOMPARE(outer->indexOf(inner), 0);
600 QCOMPARE(inner->indexOf(inner->itemAt(0)), 0);
601}
602
603QTEST_MAIN(tst_QBoxLayout)
604#include "tst_qboxlayout.moc"
605

source code of qtbase/tests/auto/widgets/kernel/qboxlayout/tst_qboxlayout.cpp