QGIM: implement moveColumns

We can move columns when the row-type is a range (which guarantees
that all columns are of the same type), and if the columns are moved
within the same parent. Otherwise we would end up with different
branches of a tree having different column counts, which we don't allow.
Since we require trees to use statically sized row types, the former
implies the latter anyway.

Change-Id: Iaf9513d3642d6143cd4880937f2c022a0621c70d
Reviewed-by: Artem Dyomin <artem.dyomin@qt.io>
This commit is contained in:
Volker Hilsheimer 2025-03-01 12:12:07 +01:00
parent 1c44eb8e16
commit ec200659da
4 changed files with 135 additions and 0 deletions

View File

@ -688,6 +688,23 @@ bool QGenericItemModel::removeColumns(int column, int count, const QModelIndex &
return impl->call<bool>(QGenericItemModelImplBase::RemoveColumns, column, count, parent); return impl->call<bool>(QGenericItemModelImplBase::RemoveColumns, column, count, parent);
} }
/*!
\reimp
Moves \a count columns starting with the given \a sourceColumn under parent
\a sourceParent to column \a destinationColumn under parent \a destinationParent.
Returns \c{true} if the columns were successfully moved; otherwise returns
\c{false}.
*/
bool QGenericItemModel::moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count,
const QModelIndex &destinationParent, int destinationColumn)
{
return impl->call<bool>(QGenericItemModelImplBase::MoveColumns,
sourceParent, sourceColumn, count,
destinationParent, destinationColumn);
}
/* /*
//! [row-change-requirement] //! [row-change-requirement]
\note The range needs to be dynamically sized and provide a \c{\1} \note The range needs to be dynamically sized and provide a \c{\1}

View File

@ -72,6 +72,8 @@ public:
bool clearItemData(const QModelIndex &index) override; bool clearItemData(const QModelIndex &index) override;
bool insertColumns(int column, int count, const QModelIndex &parent = {}) override; bool insertColumns(int column, int count, const QModelIndex &parent = {}) override;
bool removeColumns(int column, int count, const QModelIndex &parent = {}) override; bool removeColumns(int column, int count, const QModelIndex &parent = {}) override;
bool moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count,
const QModelIndex &destParent, int destColumn) override;
bool insertRows(int row, int count, const QModelIndex &parent = {}) override; bool insertRows(int row, int count, const QModelIndex &parent = {}) override;
bool removeRows(int row, int count, const QModelIndex &parent = {}) override; bool removeRows(int row, int count, const QModelIndex &parent = {}) override;
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count,
@ -120,6 +122,18 @@ void QGenericItemModelImplBase::endRemoveColumns()
{ {
m_itemModel->endRemoveColumns(); m_itemModel->endRemoveColumns();
} }
bool QGenericItemModelImplBase::beginMoveColumns(const QModelIndex &sourceParent, int sourceFirst,
int sourceLast, const QModelIndex &destParent,
int destColumn)
{
return m_itemModel->beginMoveColumns(sourceParent, sourceFirst, sourceLast,
destParent, destColumn);
}
void QGenericItemModelImplBase::endMoveColumns()
{
m_itemModel->endMoveColumns();
}
void QGenericItemModelImplBase::beginInsertRows(const QModelIndex &parent, int start, int count) void QGenericItemModelImplBase::beginInsertRows(const QModelIndex &parent, int start, int count)
{ {
m_itemModel->beginInsertRows(parent, start, count); m_itemModel->beginInsertRows(parent, start, count);
@ -290,6 +304,8 @@ public:
break; break;
case RemoveColumns: makeCall(that, &Self::removeColumns, r, args); case RemoveColumns: makeCall(that, &Self::removeColumns, r, args);
break; break;
case MoveColumns: makeCall(that, &Self::moveColumns, r, args);
break;
case InsertRows: makeCall(that, &Self::insertRows, r, args); case InsertRows: makeCall(that, &Self::insertRows, r, args);
break; break;
case RemoveRows: makeCall(that, &Self::removeRows, r, args); case RemoveRows: makeCall(that, &Self::removeRows, r, args);
@ -779,6 +795,47 @@ public:
return false; return false;
} }
bool moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count,
const QModelIndex &destParent, int destColumn)
{
// we only support moving columns within the same parent
if (sourceParent != destParent)
return false;
if constexpr (isMutable()) {
if (!Structure::canMoveColumns(sourceParent, destParent))
return false;
if constexpr (dynamicColumns()) {
// we only support ranges as columns, as other types might
// not have the same data type across all columns
range_type * const children = childRange(sourceParent);
if (!children)
return false;
if (!beginMoveColumns(sourceParent, sourceColumn, sourceColumn + count - 1,
destParent, destColumn)) {
return false;
}
for (auto &child : *children) {
const auto begin = std::begin(child);
const auto first = std::next(begin, sourceColumn);
const auto middle = std::next(begin, sourceColumn + count);
const auto last = std::next(begin, destColumn);
if (sourceColumn < destColumn) // moving right
std::rotate(first, middle, last);
else // moving left
std::rotate(last, first, middle);
}
endMoveColumns();
return true;
}
}
return false;
}
bool insertRows(int row, int count, const QModelIndex &parent) bool insertRows(int row, int count, const QModelIndex &parent)
{ {
if constexpr (Structure::canInsertRows()) { if constexpr (Structure::canInsertRows()) {
@ -1222,6 +1279,11 @@ protected:
&& Base::dynamicRows() && range_features::has_erase; && Base::dynamicRows() && range_features::has_erase;
} }
static constexpr bool canMoveColumns(const QModelIndex &, const QModelIndex &)
{
return true;
}
static constexpr bool canMoveRows(const QModelIndex &, const QModelIndex &) static constexpr bool canMoveRows(const QModelIndex &, const QModelIndex &)
{ {
return true; return true;
@ -1507,6 +1569,11 @@ protected:
return Base::dynamicRows() && range_features::has_erase; return Base::dynamicRows() && range_features::has_erase;
} }
static constexpr bool canMoveColumns(const QModelIndex &source, const QModelIndex &destination)
{
return !source.isValid() && !destination.isValid();
}
static constexpr bool canMoveRows(const QModelIndex &source, const QModelIndex &destination) static constexpr bool canMoveRows(const QModelIndex &source, const QModelIndex &destination)
{ {
return !source.isValid() && !destination.isValid(); return !source.isValid() && !destination.isValid();

View File

@ -493,6 +493,7 @@ public:
ClearItemData, ClearItemData,
InsertColumns, InsertColumns,
RemoveColumns, RemoveColumns,
MoveColumns,
InsertRows, InsertRows,
RemoveRows, RemoveRows,
MoveRows, MoveRows,
@ -532,6 +533,9 @@ protected:
inline void endInsertColumns(); inline void endInsertColumns();
inline void beginRemoveColumns(const QModelIndex &parent, int start, int count); inline void beginRemoveColumns(const QModelIndex &parent, int start, int count);
inline void endRemoveColumns(); inline void endRemoveColumns();
inline bool beginMoveColumns(const QModelIndex &sourceParent, int sourceFirst, int sourceLast,
const QModelIndex &destParent, int destRow);
inline void endMoveColumns();
inline void beginInsertRows(const QModelIndex &parent, int start, int count); inline void beginInsertRows(const QModelIndex &parent, int start, int count);
inline void endInsertRows(); inline void endInsertRows();
inline void beginRemoveRows(const QModelIndex &parent, int start, int count); inline void beginRemoveRows(const QModelIndex &parent, int start, int count);

View File

@ -300,6 +300,8 @@ private slots:
void insertColumns(); void insertColumns();
void removeColumns_data() { createTestData(); } void removeColumns_data() { createTestData(); }
void removeColumns(); void removeColumns();
void moveColumns_data() { createTestData(); }
void moveColumns();
void inconsistentColumnCount(); void inconsistentColumnCount();
@ -1207,6 +1209,51 @@ void tst_QGenericItemModel::removeColumns()
changeActions.testFlag(ChangeAction::RemoveColumns)); changeActions.testFlag(ChangeAction::RemoveColumns));
} }
void tst_QGenericItemModel::moveColumns()
{
QFETCH(Factory, factory);
auto model = factory();
QFETCH(const int, expectedColumnCount);
QFETCH(const ChangeActions, changeActions);
QCOMPARE(model->columnCount(), expectedColumnCount);
if (expectedColumnCount < 2)
QSKIP("Cannot test moveColumns with a single-column model");
const QVariant first = model->index(0, 0).data();
const QVariant second = model->index(0, 1).data();
const QVariant last = model->index(0, expectedColumnCount - 1).data();
QCOMPARE(model->moveColumns({}, 0, 1, {}, expectedColumnCount),
bool(changeActions & ChangeAction::ChangeColumns));
if (!(changeActions & ChangeAction::ChangeColumns))
return;
QCOMPARE(model->index(0, 0).data(), second);
QCOMPARE(model->index(0, expectedColumnCount - 2).data(), last);
QCOMPARE(model->index(0, expectedColumnCount - 1).data(), first);
// the rest only makes sense for models with at least 3 columns
if (expectedColumnCount >= 3) {
// move all but one column to the end - this restores the order
QVERIFY(model->moveColumns({}, 0, expectedColumnCount - 1,
{}, expectedColumnCount));
QCOMPARE(model->index(0, 0).data(), first);
QCOMPARE(model->index(0, 1).data(), second);
QCOMPARE(model->index(0, expectedColumnCount - 1).data(), last);
// move the last row step by step up to the top
for (int column = model->columnCount() - 1; column > 0; --column)
QVERIFY(model->moveColumn({}, column, {}, column - 1));
QCOMPARE(model->index(0, 0).data(), last);
// move all except the first row up - this restores the order again
QVERIFY(model->moveColumns({}, 1, expectedColumnCount - 1, {}, 0));
QCOMPARE(model->index(0, 0).data(), first);
QCOMPARE(model->index(0, 1).data(), second);
QCOMPARE(model->index(0, expectedColumnCount - 1).data(), last);
}
}
void tst_QGenericItemModel::inconsistentColumnCount() void tst_QGenericItemModel::inconsistentColumnCount()
{ {
QTest::ignoreMessage(QtCriticalMsg, "QGenericItemModel: " QTest::ignoreMessage(QtCriticalMsg, "QGenericItemModel: "