summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/widgets/kernel/qformlayout.cpp199
-rw-r--r--src/widgets/kernel/qformlayout.h8
-rw-r--r--tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp130
3 files changed, 330 insertions, 7 deletions
diff --git a/src/widgets/kernel/qformlayout.cpp b/src/widgets/kernel/qformlayout.cpp
index 8c3d3448ca..e043ca243f 100644
--- a/src/widgets/kernel/qformlayout.cpp
+++ b/src/widgets/kernel/qformlayout.cpp
@@ -127,11 +127,15 @@ struct QFormLayoutItem
void setGeometry(const QRect& r) { item->setGeometry(r); }
QRect geometry() const { return item->geometry(); }
+ void setVisible(bool on);
+ bool isHidden() const { return !isVisible || (widget() && widget()->isHidden()); }
+
// For use with FixedColumnMatrix
bool operator==(const QFormLayoutItem& other) { return item == other.item; }
QLayoutItem *item;
bool fullRow;
+ bool isVisible = true;
// set by updateSizes
bool isHfw;
@@ -152,6 +156,33 @@ struct QFormLayoutItem
int layoutWidth;
};
+static void hideOrShowWidgetsInLayout(QLayout *layout, bool on)
+{
+ for (int i = 0; i < layout->count(); ++i) {
+ QLayoutItem *item = layout->itemAt(i);
+ if (QWidget *widget = item->widget())
+ widget->setVisible(on);
+ else if (item->layout())
+ hideOrShowWidgetsInLayout(item->layout(), on);
+ }
+}
+
+void QFormLayoutItem::setVisible(bool on)
+{
+ isVisible = on;
+ // Explicitly hide the widget so that it loses focus and
+ // doesn't automatically get shown again when this layout
+ // hides and shows.
+ if (widget()) {
+ widget()->setVisible(on);
+ return;
+ }
+ // Layouts can't be hidden, so we have to traverse the widgets
+ // inside and hide all of them so that they also lose focus.
+ if (layout())
+ hideOrShowWidgetsInLayout(layout(), on);
+}
+
class QFormLayoutPrivate : public QLayoutPrivate
{
Q_DECLARE_PUBLIC(QFormLayout)
@@ -378,23 +409,25 @@ void QFormLayoutPrivate::updateSizes()
QSizePolicy::ControlTypes(fldtop ? fldtop->controlTypes() : QSizePolicy::DefaultType);
// To be compatible to QGridLayout, we have to compare solitary labels & fields with both predecessors
- if (label) {
+ if (label && !label->isHidden()) {
if (!field) {
int lblspacing = style->combinedLayoutSpacing(lbltoptypes, lbltypes, Qt::Vertical, nullptr, parent);
int fldspacing = style->combinedLayoutSpacing(fldtoptypes, lbltypes, Qt::Vertical, nullptr, parent);
label->vSpace = qMax(lblspacing, fldspacing);
- } else
+ } else {
label->vSpace = style->combinedLayoutSpacing(lbltoptypes, lbltypes, Qt::Vertical, nullptr, parent);
+ }
}
- if (field) {
+ if (field && !field->isHidden()) {
// check spacing against both the previous label and field
if (!label) {
int lblspacing = style->combinedLayoutSpacing(lbltoptypes, fldtypes, Qt::Vertical, nullptr, parent);
int fldspacing = style->combinedLayoutSpacing(fldtoptypes, fldtypes, Qt::Vertical, nullptr, parent);
field->vSpace = qMax(lblspacing, fldspacing);
- } else
+ } else {
field->vSpace = style->combinedLayoutSpacing(fldtoptypes, fldtypes, Qt::Vertical, nullptr, parent);
+ }
}
}
}
@@ -681,11 +714,11 @@ void QFormLayoutPrivate::setupVerticalLayoutData(int width)
bool prevRowSplit = false;
for (int i = 0; i < rr; ++i) {
- QFormLayoutItem *label = m_matrix(i, 0);
+ QFormLayoutItem *label = m_matrix(i, 0);
QFormLayoutItem *field = m_matrix(i, 1);
- // Totally ignore empty rows...
- if (!label && !field)
+ // Totally ignore empty rows or rows with only hidden items
+ if (!q->isRowVisible(i))
continue;
QSize min1;
@@ -2300,6 +2333,158 @@ void QFormLayout::setItem(int row, ItemRole role, QLayoutItem *item)
}
/*!
+ \since 6.4
+
+ Shows the row \a row if \a on is true, otherwise hides the row.
+
+ \a row must be non-negative and less than rowCount().
+
+ \sa removeRow(), takeRow()
+*/
+void QFormLayout::setRowVisible(int row, bool on)
+{
+ Q_D(QFormLayout);
+ QFormLayoutItem *label = d->m_matrix(row, 0);
+ QFormLayoutItem *field = d->m_matrix(row, 1);
+ bool change = false;
+ if (label) {
+ change = label->isVisible != on;
+ label->setVisible(on);
+ }
+ if (field) {
+ change |= field->isVisible != on;
+ field->setVisible(on);
+ }
+ if (change)
+ invalidate();
+}
+
+/*!
+ \since 6.4
+
+ \overload
+
+ Shows the row corresponding to \a widget if \a on is true,
+ otherwise hides the row.
+
+ \sa removeRow(), takeRow()
+*/
+void QFormLayout::setRowVisible(QWidget *widget, bool on)
+{
+ Q_D(QFormLayout);
+ if (Q_UNLIKELY(!d->checkWidget(widget)))
+ return;
+
+ int row;
+ ItemRole role;
+ getWidgetPosition(widget, &row, &role);
+
+ if (Q_UNLIKELY(row < 0)) {
+ qWarning("QFormLayout::setRowVisible: Invalid widget");
+ return;
+ }
+
+ setRowVisible(row, on);
+}
+
+/*!
+ \since 6.4
+
+ \overload
+
+ Shows the row corresponding to \a layout if \a on is true,
+ otherwise hides the row.
+
+ \sa removeRow(), takeRow()
+*/
+void QFormLayout::setRowVisible(QLayout *layout, bool on)
+{
+ Q_D(QFormLayout);
+ if (Q_UNLIKELY(!d->checkLayout(layout)))
+ return;
+
+ int row;
+ ItemRole role;
+ getLayoutPosition(layout, &row, &role);
+
+ if (Q_UNLIKELY(row < 0)) {
+ qWarning("QFormLayout::setRowVisible: Invalid layout");
+ return;
+ }
+
+ setRowVisible(row, on);
+}
+
+/*!
+ \since 6.4
+
+ Returns true if some items in the row \a row are visible,
+ otherwise returns false.
+*/
+bool QFormLayout::isRowVisible(int row) const
+{
+ Q_D(const QFormLayout);
+ QFormLayoutItem *label = d->m_matrix(row, 0);
+ QFormLayoutItem *field = d->m_matrix(row, 1);
+
+ int visibleItemCount = 2;
+ if (!label || label->isHidden() || (label->widget() && label->widget()->isHidden()))
+ --visibleItemCount;
+ if (!field || field->isHidden() || (field->widget() && field->widget()->isHidden()))
+ --visibleItemCount;
+
+ return visibleItemCount > 0;
+}
+
+/*!
+ \since 6.4
+ \overload
+
+ Returns true if some items in the row corresponding to \a widget
+ are visible, otherwise returns false.
+*/
+bool QFormLayout::isRowVisible(QWidget *widget) const
+{
+ Q_D(const QFormLayout);
+ if (Q_UNLIKELY(!d->checkWidget(widget)))
+ return false;
+ int row;
+ ItemRole role;
+ getWidgetPosition(widget, &row, &role);
+
+ if (Q_UNLIKELY(row < 0)) {
+ qWarning("QFormLayout::takeRow: Invalid widget");
+ return false;
+ }
+
+ return isRowVisible(row);
+}
+
+/*!
+ \since 6.4
+ \overload
+
+ Returns true if some items in the row corresponding to \a layout
+ are visible, otherwise returns false.
+*/
+bool QFormLayout::isRowVisible(QLayout *layout) const
+{
+ Q_D(const QFormLayout);
+ if (Q_UNLIKELY(!d->checkLayout(layout)))
+ return false;
+ int row;
+ ItemRole role;
+ getLayoutPosition(layout, &row, &role);
+
+ if (Q_UNLIKELY(row < 0)) {
+ qWarning("QFormLayout::takeRow: Invalid layout");
+ return false;
+ }
+
+ return isRowVisible(row);
+}
+
+/*!
\internal
*/
diff --git a/src/widgets/kernel/qformlayout.h b/src/widgets/kernel/qformlayout.h
index c716a963e5..cc846fd087 100644
--- a/src/widgets/kernel/qformlayout.h
+++ b/src/widgets/kernel/qformlayout.h
@@ -138,6 +138,14 @@ public:
void setWidget(int row, ItemRole role, QWidget *widget);
void setLayout(int row, ItemRole role, QLayout *layout);
+ void setRowVisible(int row, bool on);
+ void setRowVisible(QWidget *widget, bool on);
+ void setRowVisible(QLayout *layout, bool on);
+
+ bool isRowVisible(int row) const;
+ bool isRowVisible(QWidget *widget) const;
+ bool isRowVisible(QLayout *layout) const;
+
QLayoutItem *itemAt(int row, ItemRole role) const;
void getItemPosition(int index, int *rowPtr, ItemRole *rolePtr) const;
void getWidgetPosition(QWidget *widget, int *rowPtr, ItemRole *rolePtr) const;
diff --git a/tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp b/tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp
index 4aa9f8ac2d..a7ac5fa479 100644
--- a/tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp
+++ b/tests/auto/widgets/kernel/qformlayout/tst_qformlayout.cpp
@@ -135,6 +135,7 @@ private slots:
void takeRow_QLayout();
void setWidget();
void setLayout();
+ void hideShowRow();
/*
QLayoutItem *itemAt(int row, ItemRole role) const;
@@ -1123,6 +1124,135 @@ 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::itemAt()
{
QWidget topLevel;