diff options
Diffstat (limited to 'tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp')
-rw-r--r-- | tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp | 230 |
1 files changed, 202 insertions, 28 deletions
diff --git a/tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp b/tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp index 4aa9f8ac2d..9638823538 100644 --- a/tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp +++ b/tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> @@ -135,6 +110,9 @@ private slots: void takeRow_QLayout(); void setWidget(); void setLayout(); + void hideShowRow(); + void showWithHiddenRow(); + void hiddenRowAndStretch(); /* QLayoutItem *itemAt(int row, ItemRole role) const; @@ -274,7 +252,9 @@ void tst_QFormLayout::wrapping() fl->setRowWrapPolicy(QFormLayout::WrapLongRows); QLineEdit *le = new QLineEdit; - QLabel *lbl = new QLabel("A long label"); + QLabel *lbl = new QLabel("A long label which is actually long enough to trigger wrapping," + " even on Android and even if it is executed on a tiling window" + " manager which forces the window into fullscreen mode."); le->setMinimumWidth(200); fl->addRow(lbl, le); @@ -814,6 +794,7 @@ void tst_QFormLayout::removeRow_QWidget() QCOMPARE(layout->rowCount(), 0); QWidget *w3 = new QWidget; + QTest::ignoreMessage(QtWarningMsg, "QFormLayout::takeRow: Invalid widget"); layout->removeRow(w3); delete w3; } @@ -854,6 +835,7 @@ void tst_QFormLayout::removeRow_QLayout() QCOMPARE(layout->rowCount(), 0); QHBoxLayout *l3 = new QHBoxLayout; + QTest::ignoreMessage(QtWarningMsg, "QFormLayout::takeRow: Invalid layout"); layout->removeRow(l3); delete l3; } @@ -893,6 +875,7 @@ void tst_QFormLayout::takeRow() QCOMPARE(layout->rowCount(), 0); QCOMPARE(result.fieldItem->widget(), w1.data()); + QTest::ignoreMessage(QtWarningMsg, "QFormLayout::takeRow: Invalid row 0"); result = layout->takeRow(0); QVERIFY(!result.fieldItem); @@ -933,6 +916,7 @@ void tst_QFormLayout::takeRow_QWidget() QCOMPARE(layout->rowCount(), 0); QWidget *w3 = new QWidget; + QTest::ignoreMessage(QtWarningMsg, "QFormLayout::takeRow: Invalid widget"); result = layout->takeRow(w3); delete w3; @@ -980,6 +964,7 @@ void tst_QFormLayout::takeRow_QLayout() QCOMPARE(layout->rowCount(), 0); QHBoxLayout *l3 = new QHBoxLayout; + QTest::ignoreMessage(QtWarningMsg, "QFormLayout::takeRow: Invalid layout"); result = layout->takeRow(l3); delete l3; @@ -1009,7 +994,9 @@ void tst_QFormLayout::setWidget() QCOMPARE(layout.rowCount(), 6); // should be ignored and generate warnings + QTest::ignoreMessage(QtWarningMsg, "QFormLayoutPrivate::setItem: Cell (3, 1) already occupied"); layout.setWidget(3, QFormLayout::FieldRole, &w4); + QTest::ignoreMessage(QtWarningMsg, "QFormLayoutPrivate::setItem: Invalid cell (-1, 1)"); layout.setWidget(-1, QFormLayout::FieldRole, &w4); { @@ -1077,7 +1064,9 @@ void tst_QFormLayout::setLayout() QCOMPARE(layout.rowCount(), 6); // should be ignored and generate warnings + QTest::ignoreMessage(QtWarningMsg, "QFormLayoutPrivate::setItem: Cell (3, 1) already occupied"); layout.setLayout(3, QFormLayout::FieldRole, &l4); + QTest::ignoreMessage(QtWarningMsg, "QLayout::addChildLayout: layout QHBoxLayout \"\" already has a parent"); layout.setLayout(-1, QFormLayout::FieldRole, &l4); QCOMPARE(layout.count(), 3); QCOMPARE(layout.rowCount(), 6); @@ -1123,6 +1112,191 @@ void tst_QFormLayout::setLayout() } } +void tst_QFormLayout::hideShowRow() +{ + QWidget topLevel; + QFormLayout layout; + + const auto makeComplex = []{ + QHBoxLayout *hboxField = new QHBoxLayout; + hboxField->addWidget(new QLineEdit("Left")); + hboxField->addWidget(new QLineEdit("Right")); + return hboxField; + }; + + layout.addRow("Label", new QLineEdit("one")); + layout.addRow("Label", new QLineEdit("two")); + layout.addRow("Label", new QLineEdit("three")); + layout.addRow("Label", makeComplex()); + layout.addRow(new QLineEdit("five")); // spanning widget + layout.addRow(makeComplex()); // spanning layout + + topLevel.setLayout(&layout); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + + // returns the top-left position of the items in a row + const auto rowPosition = [&layout](int row) { + QRect rect; + if (QLayoutItem *spanningItem = layout.itemAt(row, QFormLayout::SpanningRole)) { + rect = spanningItem->geometry(); + } else { + if (QLayoutItem *labelItem = layout.itemAt(row, QFormLayout::LabelRole)) { + rect = labelItem->geometry(); + } + if (QLayoutItem *fieldItem = layout.itemAt(row, QFormLayout::FieldRole)) { + rect |= fieldItem->geometry(); + } + } + return rect.topLeft(); + }; + + // returns the first widget in a row, even if that row is taken by a layout + const auto rowInputWidget = [&layout](int row) -> QWidget* { + auto fieldItem = layout.itemAt(row, QFormLayout::FieldRole); + if (!fieldItem) + return nullptr; + QWidget *fieldWidget = fieldItem->widget(); + // we happen to know our layout structure + if (!fieldWidget) + fieldWidget = fieldItem->layout()->itemAt(0)->widget(); + return fieldWidget; + }; + + // record the reference positions for all rows + QList<QPoint> rowPositions(layout.rowCount()); + for (int row = 0; row < layout.rowCount(); ++row) + rowPositions[row] = rowPosition(row); + + // hide each row in turn, the next row should take the space of the hidden row + for (int row = 0; row < layout.rowCount(); ++ row) { + layout.setRowVisible(row, false); + QVERIFY(!layout.isRowVisible(row)); + if (row < layout.rowCount() - 1) + QTRY_COMPARE(rowPosition(row + 1), rowPositions[row]); + layout.setRowVisible(row, true); + QVERIFY(layout.isRowVisible(row)); + } + + // Hiding only the label or only the field doesn't hide the row. + for (int row = 0; row < layout.rowCount() - 1; ++row) { + const auto labelItem = layout.itemAt(0, QFormLayout::LabelRole); + if (labelItem) { + labelItem->widget()->hide(); + QVERIFY(layout.isRowVisible(row)); + QCOMPARE(rowPosition(row), rowPositions[row]); + layout.itemAt(0, QFormLayout::LabelRole)->widget()->show(); + } + const auto fieldItem = layout.itemAt(0, QFormLayout::FieldRole); + if (fieldItem) { + fieldItem->widget()->hide(); + QVERIFY(layout.isRowVisible(row)); + QCOMPARE(rowPosition(row), rowPositions[row]); + layout.itemAt(0, QFormLayout::FieldRole)->widget()->show(); + } + } + + // If we hide both label and field, then the row should be considered hidden and the + // following row should move up into the space of the hidden row. We can only test + // this if both label and field are widgets, or if there is a spanning widget. + for (int row = 0; row < layout.rowCount() - 1; ++row) { + QWidget *labelWidget = nullptr; + if (auto labelItem = layout.itemAt(row, QFormLayout::LabelRole)) + labelWidget = labelItem->widget(); + QWidget *fieldWidget = nullptr; + if (auto fieldItem = layout.itemAt(row, QFormLayout::FieldRole)) + fieldWidget = fieldItem->widget(); + + if (!fieldWidget) + continue; + if (labelWidget) + labelWidget->hide(); + fieldWidget->hide(); + QVERIFY(!layout.isRowVisible(row)); + QVERIFY(!layout.isRowVisible(fieldWidget)); + if (labelWidget) + QVERIFY(!layout.isRowVisible(labelWidget)); + QTRY_COMPARE(rowPosition(row + 1), rowPositions[row]); + if (labelWidget) + labelWidget->show(); + fieldWidget->show(); + } + + // hiding a row where a widget has focus must move focus to a widget in the next row + for (int row = 0; row < layout.rowCount(); ++row) { + QWidget *inputWidget = rowInputWidget(row); + QVERIFY(inputWidget); + inputWidget->setFocus(); + layout.setRowVisible(row, false); + QVERIFY(!inputWidget->hasFocus()); + } + + // Now hide all rows, hide the toplevel widget, and show the toplevel widget again. + // None of the widgets inside must be visible. + for (int row = 0; row < layout.rowCount(); ++row) + layout.setRowVisible(row, false); + topLevel.hide(); + topLevel.show(); + for (int row = 0; row < layout.rowCount(); ++row) + QVERIFY(rowInputWidget(row)->isHidden()); +} + +void tst_QFormLayout::showWithHiddenRow() +{ + QWidget topLevel; + QFormLayout layout; + + for (int row = 0; row < 3; ++row) + layout.addRow(QString("Label %1").arg(row), new QLineEdit); + layout.setRowVisible(1, false); + + topLevel.setLayout(&layout); + topLevel.show(); +} + +/* + Test that hiding rows does not leave outdated layout data behind + in hidden items that results in out-of-bounds array access. See + QTBUG-109237. +*/ +void tst_QFormLayout::hiddenRowAndStretch() +{ + QWidget topLevel; + QFormLayout layout; + layout.setRowWrapPolicy(QFormLayout::WrapAllRows); + + // We need our own stretcher item so that QFormLayout doesn't insert + // it's own, as that would grow the size of the layout data array again. + QSpacerItem *stretch = new QSpacerItem(100, 100, QSizePolicy::Expanding, QSizePolicy::Expanding); + layout.setItem(0, QFormLayout::FieldRole, stretch); + + QLabel *lastLabel = nullptr; + QLineEdit *lastField = nullptr; + for (int row = 1; row < 4; ++row) { + QLabel *label = new QLabel(QString("Label %1").arg(row)); + label->setWordWrap(true); + QLineEdit *field = new QLineEdit; + layout.setWidget(row, QFormLayout::LabelRole, label); + layout.setWidget(row, QFormLayout::FieldRole, field); + if (row == 3) { + lastLabel = label; + lastField = field; + } + } + + Q_ASSERT(lastLabel); + Q_ASSERT(lastField); + + topLevel.setLayout(&layout); + topLevel.sizeHint(); + + lastLabel->setVisible(false); + lastField->setVisible(false); + + // should not assert here + topLevel.show(); +} + void tst_QFormLayout::itemAt() { QWidget topLevel; |