Implement QFormLayout::set/isRowVisible
Hiding a row in a form layout is inconvenient to do as access to the widgets in each row is cumbersome. In addition, a row might include a layout for the label or the field column, and we can't hide layouts and instead need to navigate to the widgets inside the layout. And even if an application developer does all that, the spacing calculation doesn't ignore hidden rows. Add setRowVisible and isRowVisible APIs with the usual overloads. Implement the logic to traverse a layout item to its contained widgets, so that they are explicitly hidden when a row is hidden, and skip hidden rows in the spacing calculation. [ChangeLog][Widgets][QFormLayout] New APIs setRowVisible and isRowVisible to hide and show rows in a form layout. Fixes: QTBUG-6864 Change-Id: I6af98409802f331c4523e91d7dac8a97762c579d Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Axel Spoerl <axel.spoerl@qt.io> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>bb10
parent
599c648b77
commit
a74cdf778c
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue