diff --git a/src/widgets/kernel/qformlayout.cpp b/src/widgets/kernel/qformlayout.cpp index 8c3d3448cae..e043ca243f0 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; @@ -2299,6 +2332,158 @@ void QFormLayout::setItem(int row, ItemRole role, QLayoutItem *item) d->setItem(row, role, 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 c716a963e58..cc846fd0872 100644 --- a/src/widgets/kernel/qformlayout.h +++ b/src/widgets/kernel/qformlayout.h @@ -138,6 +138,14 @@ class Q_WIDGETS_EXPORT QFormLayout : public QLayout 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 4aa9f8ac2d1..a7ac5fa4798 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 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;