Implement QFormLayout row removal

QFormLayout currently allows to remove a row only through takeAt, which
doesn't resize the internal structure used to store the layout-related
data. The implementation of rowCount uses that structure so it returns
a row count that doesn't match what the user sees.

The removeRow methods complement that be doing a "real" removal which
will also keep the row count in sync.

[ChangeLog][QtWidgets][QFormLayout] Added removeRow(), takeRow().

Task-number: QTBUG-15990
Change-Id: I204b219a837887e4bf95d1bc77407f1c53ae9819
Reviewed-by: Marc Mutz <marc.mutz@kdab.com>
bb10
Samuel Gaist 2016-04-11 10:17:23 +02:00
parent ffde99d5a0
commit 8fbae648db
3 changed files with 563 additions and 12 deletions

View File

@ -185,6 +185,7 @@ public:
int insertRow(int row);
void insertRows(int row, int count);
void removeRow(int row);
void setItem(int row, QFormLayout::ItemRole role, QLayoutItem *item);
void setLayout(int row, QFormLayout::ItemRole role, QLayout *layout);
void setWidget(int row, QFormLayout::ItemRole role, QWidget *widget);
@ -947,6 +948,12 @@ void QFormLayoutPrivate::insertRows(int row, int count)
}
}
void QFormLayoutPrivate::removeRow(int row)
{
if (uint(row) < uint(m_matrix.rowCount()))
m_matrix.removeRow(row);
}
void QFormLayoutPrivate::setItem(int row, QFormLayout::ItemRole role, QLayoutItem *item)
{
const bool fullRow = role == QFormLayout::SpanningRole;
@ -1177,6 +1184,28 @@ QLayoutItem* QFormLayoutPrivate::replaceAt(int index, QLayoutItem *newitem)
\sa itemAt(), getItemPosition()
*/
/*!
\since 5.8
\struct QFormLayout::TakeRowResult
\brief Contains the result of a QFormLayout::takeRow() call.
\sa QFormLayout::takeRow()
*/
/*!
\variable QFormLayout::TakeRowResult::labelItem
Contains the layout item corresponding to the label of the row.
*/
/*!
\variable QFormLayout::TakeRowResult::fieldItem
Contains the layout item corresponding to the field of the row.
*/
/*!
Constructs a new form layout with the given \a parent widget.
@ -1380,6 +1409,287 @@ void QFormLayout::insertRow(int row, QLayout *layout)
invalidate();
}
static QLayoutItem *ownershipCleanedItem(QFormLayoutItem *item, QFormLayout *layout)
{
if (!item)
return nullptr;
// grab ownership back from the QFormLayoutItem
QLayoutItem *i = item->item;
item->item = nullptr;
delete item;
if (QLayout *l = i->layout()) {
// sanity check in case the user passed something weird to QObject::setParent()
if (l->parent() == layout)
l->setParent(nullptr);
}
return i;
}
static void clearQLayoutItem(QLayoutItem *item)
{
if (Q_LIKELY(item)) {
if (QLayout *layout = item->layout()) {
while (QLayoutItem *child = layout->takeAt(0)) {
clearQLayoutItem(child);
delete child;
}
delete layout;
}
delete item->widget();
delete item->spacerItem();
}
}
/*!
\since 5.8
Deletes row \a row from this form layout.
\a row must be non-negative and less than rowCount().
After this call, rowCount() is decremented by one. All widgets and
nested layouts that occupied this row are deleted. That includes both
the field widget(s) and the label, if any. All following rows are shifted
up one row and the freed vertical space is redistributed amongst the remaining rows.
You can use this function to undo a previous addRow() or insertRow():
\code
QFormLayout *flay = ...;
QPointer<QLineEdit> le = new QLineEdit;
flay->insertRow(2, "User:", le);
// later:
flay->removeRow(2); // le == nullptr at this point
\endcode
If you want to remove the row from the layout without deleting the widgets, use takeRow() instead.
\sa takeRow()
*/
void QFormLayout::removeRow(int row)
{
TakeRowResult result = takeRow(row);
clearQLayoutItem(result.labelItem);
clearQLayoutItem(result.fieldItem);
}
/*!
\since 5.8
\overload
Deletes the row corresponding to \a widget from this form layout.
After this call, rowCount() is decremented by one. All widgets and
nested layouts that occupied this row are deleted. That includes both
the field widget(s) and the label, if any. All following rows are shifted
up one row and the freed vertical space is redistributed amongst the remaining rows.
You can use this function to undo a previous addRow() or insertRow():
\code
QFormLayout *flay = ...;
QPointer<QLineEdit> le = new QLineEdit;
flay->insertRow(2, "User:", le);
// later:
flay->removeRow(le); // le == nullptr at this point
\endcode
If you want to remove the row from the layout without deleting the widgets, use takeRow() instead.
\sa takeRow()
*/
void QFormLayout::removeRow(QWidget *widget)
{
TakeRowResult result = takeRow(widget);
clearQLayoutItem(result.labelItem);
clearQLayoutItem(result.fieldItem);
}
/*!
\since 5.8
\overload
Deletes the row corresponding to \a layout from this form layout.
After this call, rowCount() is decremented by one. All widgets and
nested layouts that occupied this row are deleted. That includes both
the field widget(s) and the label, if any. All following rows are shifted
up one row and the freed vertical space is redistributed amongst the remaining rows.
You can use this function to undo a previous addRow() or insertRow():
\code
QFormLayout *flay = ...;
QPointer<QVBoxLayout> vbl = new QVBoxLayout;
flay->insertRow(2, "User:", vbl);
// later:
flay->removeRow(layout); // vbl == nullptr at this point
\endcode
If you want to remove the row from the form layout without deleting the inserted layout,
use takeRow() instead.
\sa takeRow()
*/
void QFormLayout::removeRow(QLayout *layout)
{
TakeRowResult result = takeRow(layout);
clearQLayoutItem(result.labelItem);
clearQLayoutItem(result.fieldItem);
}
/*!
\since 5.8
Removes the specified \a row from this form layout.
\a row must be non-negative and less than rowCount().
\note This function doesn't delete anything.
After this call, rowCount() is decremented by one. All following rows are shifted
up one row and the freed vertical space is redistributed amongst the remaining rows.
You can use this function to undo a previous addRow() or insertRow():
\code
QFormLayout *flay = ...;
QPointer<QLineEdit> le = new QLineEdit;
flay->insertRow(2, "User:", le);
// later:
QFormLayout::TakeRowResult result = flay->takeRow(2);
\endcode
If you want to remove the row from the layout and delete the widgets, use removeRow() instead.
\return A structure containing both the widget and
corresponding label layout items
\sa removeRow()
*/
QFormLayout::TakeRowResult QFormLayout::takeRow(int row)
{
Q_D(QFormLayout);
const int storageIndex = storageIndexFromLayoutItem(d->m_matrix, d->m_things.value(row));
if (Q_UNLIKELY(storageIndex == -1)) {
qWarning("QFormLayout::takeRow: Invalid row %d", row);
return TakeRowResult();
}
int storageRow, dummy;
QFormLayoutPrivate::ItemMatrix::storageIndexToPosition(storageIndex, &storageRow, &dummy);
Q_ASSERT(d->m_matrix(storageRow, dummy));
QFormLayoutItem *label = d->m_matrix(storageRow, 0);
QFormLayoutItem *field = d->m_matrix(storageRow, 1);
Q_ASSERT(field);
d->m_things.removeOne(label);
d->m_things.removeOne(field);
d->m_matrix.removeRow(storageRow);
invalidate();
TakeRowResult result;
result.labelItem = ownershipCleanedItem(label, this);
result.fieldItem = ownershipCleanedItem(field, this);
return result;
}
/*!
\since 5.8
\overload
Removes the specified \a widget from this form layout.
\note This function doesn't delete anything.
After this call, rowCount() is decremented by one. All following rows are shifted
up one row and the freed vertical space is redistributed amongst the remaining rows.
\code
QFormLayout *flay = ...;
QPointer<QLineEdit> le = new QLineEdit;
flay->insertRow(2, "User:", le);
// later:
QFormLayout::TakeRowResult result = flay->takeRow(widget);
\endcode
If you want to remove the row from the layout and delete the widgets, use removeRow() instead.
\return A structure containing both the widget and
corresponding label layout items
\sa removeRow()
*/
QFormLayout::TakeRowResult QFormLayout::takeRow(QWidget *widget)
{
Q_D(QFormLayout);
if (Q_UNLIKELY(!d->checkWidget(widget)))
return TakeRowResult();
int row;
ItemRole role;
getWidgetPosition(widget, &row, &role);
if (Q_UNLIKELY(row < 0)) {
qWarning("QFormLayout::takeRow: Invalid widget");
return TakeRowResult();
}
return takeRow(row);
}
/*!
\since 5.8
\overload
Removes the specified \a layout from this form layout.
\note This function doesn't delete anything.
After this call, rowCount() is decremented by one. All following rows are shifted
up one row and the freed vertical space is redistributed amongst the remaining rows.
\code
QFormLayout *flay = ...;
QPointer<QVBoxLayout> vbl = new QVBoxLayout;
flay->insertRow(2, "User:", vbl);
// later:
QFormLayout::TakeRowResult result = flay->takeRow(widget);
\endcode
If you want to remove the row from the form layout and delete the inserted layout,
use removeRow() instead.
\return A structure containing both the widget and
corresponding label layout items
\sa removeRow()
*/
QFormLayout::TakeRowResult QFormLayout::takeRow(QLayout *layout)
{
Q_D(QFormLayout);
if (Q_UNLIKELY(!d->checkLayout(layout)))
return TakeRowResult();
int row;
ItemRole role;
getLayoutPosition(layout, &row, &role);
if (Q_UNLIKELY(row < 0)) {
qWarning("QFormLayout::takeRow: Invalid layout");
return TakeRowResult();
}
return takeRow(row);
}
/*!
\reimp
*/
@ -1436,18 +1746,7 @@ QLayoutItem *QFormLayout::takeAt(int index)
invalidate();
// grab ownership back from the QFormLayoutItem
QLayoutItem *i = item->item;
item->item = 0;
delete item;
if (QLayout *l = i->layout()) {
// sanity check in case the user passed something weird to QObject::setParent()
if (l->parent() == this)
l->setParent(0);
}
return i;
return ownershipCleanedItem(item, this);
}
/*!

View File

@ -80,6 +80,11 @@ public:
};
Q_ENUM(ItemRole)
struct TakeRowResult {
QLayoutItem *labelItem;
QLayoutItem *fieldItem;
};
explicit QFormLayout(QWidget *parent = Q_NULLPTR);
~QFormLayout();
@ -114,6 +119,14 @@ public:
void insertRow(int row, QWidget *widget);
void insertRow(int row, QLayout *layout);
void removeRow(int row);
void removeRow(QWidget *widget);
void removeRow(QLayout *layout);
TakeRowResult takeRow(int row);
TakeRowResult takeRow(QWidget *widget);
TakeRowResult takeRow(QLayout *layout);
void setItem(int row, ItemRole role, QLayoutItem *item);
void setWidget(int row, ItemRole role, QWidget *widget);
void setLayout(int row, ItemRole role, QLayout *layout);
@ -153,6 +166,8 @@ private:
void resetFormAlignment();
};
Q_DECLARE_TYPEINFO(QFormLayout::TakeRowResult, Q_PRIMITIVE_TYPE);
QT_END_NAMESPACE
#endif

View File

@ -84,6 +84,12 @@ private slots:
void insertRow_QString_QLayout();
void insertRow_QWidget();
void insertRow_QLayout();
void removeRow();
void removeRow_QWidget();
void removeRow_QLayout();
void takeRow();
void takeRow_QWidget();
void takeRow_QLayout();
void setWidget();
void setLayout();
@ -687,6 +693,237 @@ void tst_QFormLayout::insertRow_QLayout()
// ### come back to this later
}
void tst_QFormLayout::removeRow()
{
QWidget topLevel;
QFormLayout *layout = new QFormLayout(&topLevel);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->rowCount(), 0);
QPointer<QWidget> w1 = new QWidget;
QPointer<QWidget> w2 = new QWidget;
layout->addRow("test1", w1);
layout->addRow(w2);
QCOMPARE(layout->count(), 3);
QCOMPARE(layout->rowCount(), 2);
layout->removeRow(1);
QVERIFY(!w1);
QCOMPARE(layout->count(), 1);
QCOMPARE(layout->rowCount(), 1);
layout->removeRow(0);
QVERIFY(!w2);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->rowCount(), 0);
}
void tst_QFormLayout::removeRow_QWidget()
{
QWidget topLevel;
QFormLayout *layout = new QFormLayout(&topLevel);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->rowCount(), 0);
QPointer<QWidget> w1 = new QWidget;
QPointer<QWidget> w2 = new QWidget;
layout->addRow("test1", w1);
layout->addRow(w2);
QCOMPARE(layout->count(), 3);
QCOMPARE(layout->rowCount(), 2);
layout->removeRow(w1);
QVERIFY(!w1);
QCOMPARE(layout->count(), 1);
QCOMPARE(layout->rowCount(), 1);
layout->removeRow(w2);
QVERIFY(!w2);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->rowCount(), 0);
QWidget *w3 = new QWidget;
layout->removeRow(w3);
delete w3;
}
void tst_QFormLayout::removeRow_QLayout()
{
QWidget topLevel;
QFormLayout *layout = new QFormLayout(&topLevel);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->rowCount(), 0);
QPointer<QHBoxLayout> l1 = new QHBoxLayout;
QPointer<QWidget> w1 = new QWidget;
l1->addWidget(w1);
QPointer<QHBoxLayout> l2 = new QHBoxLayout;
QPointer<QWidget> w2 = new QWidget;
l2->addWidget(w2);
layout->addRow("test1", l1);
layout->addRow(l2);
QCOMPARE(layout->count(), 3);
QCOMPARE(layout->rowCount(), 2);
layout->removeRow(l1);
QVERIFY(!l1);
QVERIFY(!w1);
QCOMPARE(layout->count(), 1);
QCOMPARE(layout->rowCount(), 1);
layout->removeRow(l2);
QVERIFY(!l2);
QVERIFY(!w2);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->rowCount(), 0);
QHBoxLayout *l3 = new QHBoxLayout;
layout->removeRow(l3);
delete l3;
}
void tst_QFormLayout::takeRow()
{
QWidget topLevel;
QFormLayout *layout = new QFormLayout(&topLevel);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->rowCount(), 0);
QPointer<QWidget> w1 = new QWidget;
QPointer<QWidget> w2 = new QWidget;
layout->addRow("test1", w1);
layout->addRow(w2);
QCOMPARE(layout->count(), 3);
QCOMPARE(layout->rowCount(), 2);
QFormLayout::TakeRowResult result = layout->takeRow(1);
QVERIFY(w2);
QVERIFY(result.fieldItem);
QVERIFY(result.labelItem);
QCOMPARE(layout->count(), 1);
QCOMPARE(layout->rowCount(), 1);
result = layout->takeRow(0);
QVERIFY(w1);
QVERIFY(result.fieldItem);
QVERIFY(!result.labelItem);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->rowCount(), 0);
result = layout->takeRow(0);
QVERIFY(!result.fieldItem);
QVERIFY(!result.labelItem);
}
void tst_QFormLayout::takeRow_QWidget()
{
QWidget topLevel;
QFormLayout *layout = new QFormLayout(&topLevel);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->rowCount(), 0);
QPointer<QWidget> w1 = new QWidget;
QPointer<QWidget> w2 = new QWidget;
layout->addRow("test1", w1);
layout->addRow(w2);
QCOMPARE(layout->count(), 3);
QCOMPARE(layout->rowCount(), 2);
QFormLayout::TakeRowResult result = layout->takeRow(w1);
QVERIFY(w1);
QVERIFY(result.fieldItem);
QVERIFY(result.labelItem);
QCOMPARE(layout->count(), 1);
QCOMPARE(layout->rowCount(), 1);
result = layout->takeRow(w2);
QVERIFY(w2);
QVERIFY(result.fieldItem);
QVERIFY(!result.labelItem);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->rowCount(), 0);
QWidget *w3 = new QWidget;
result = layout->takeRow(w3);
delete w3;
QVERIFY(!result.fieldItem);
QVERIFY(!result.labelItem);
}
void tst_QFormLayout::takeRow_QLayout()
{
QWidget topLevel;
QFormLayout *layout = new QFormLayout(&topLevel);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->rowCount(), 0);
QPointer<QHBoxLayout> l1 = new QHBoxLayout;
QPointer<QWidget> w1 = new QWidget;
l1->addWidget(w1);
QPointer<QHBoxLayout> l2 = new QHBoxLayout;
QPointer<QWidget> w2 = new QWidget;
l2->addWidget(w2);
layout->addRow("test1", l1);
layout->addRow(l2);
QCOMPARE(layout->count(), 3);
QCOMPARE(layout->rowCount(), 2);
QFormLayout::TakeRowResult result = layout->takeRow(l1);
QVERIFY(l1);
QVERIFY(w1);
QVERIFY(result.fieldItem);
QVERIFY(result.labelItem);
QCOMPARE(layout->count(), 1);
QCOMPARE(layout->rowCount(), 1);
result = layout->takeRow(l2);
QVERIFY(l2);
QVERIFY(w2);
QVERIFY(result.fieldItem);
QVERIFY(!result.labelItem);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->rowCount(), 0);
QHBoxLayout *l3 = new QHBoxLayout;
result = layout->takeRow(l3);
delete l3;
QVERIFY(!result.fieldItem);
QVERIFY(!result.labelItem);
}
void tst_QFormLayout::setWidget()
{
QFormLayout layout;