diff --git a/src/corelib/itemmodels/qgenericitemmodel.cpp b/src/corelib/itemmodels/qgenericitemmodel.cpp index 830d0361d93..6759604040e 100644 --- a/src/corelib/itemmodels/qgenericitemmodel.cpp +++ b/src/corelib/itemmodels/qgenericitemmodel.cpp @@ -688,6 +688,23 @@ bool QGenericItemModel::removeColumns(int column, int count, const QModelIndex & return impl->call(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(QGenericItemModelImplBase::MoveColumns, + sourceParent, sourceColumn, count, + destinationParent, destinationColumn); +} + /* //! [row-change-requirement] \note The range needs to be dynamically sized and provide a \c{\1} diff --git a/src/corelib/itemmodels/qgenericitemmodel.h b/src/corelib/itemmodels/qgenericitemmodel.h index a25d7fc6a4d..22d8b22095b 100644 --- a/src/corelib/itemmodels/qgenericitemmodel.h +++ b/src/corelib/itemmodels/qgenericitemmodel.h @@ -72,6 +72,8 @@ public: bool clearItemData(const QModelIndex &index) override; bool insertColumns(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 removeRows(int row, int count, const QModelIndex &parent = {}) override; bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, @@ -120,6 +122,18 @@ void QGenericItemModelImplBase::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) { m_itemModel->beginInsertRows(parent, start, count); @@ -290,6 +304,8 @@ public: break; case RemoveColumns: makeCall(that, &Self::removeColumns, r, args); break; + case MoveColumns: makeCall(that, &Self::moveColumns, r, args); + break; case InsertRows: makeCall(that, &Self::insertRows, r, args); break; case RemoveRows: makeCall(that, &Self::removeRows, r, args); @@ -779,6 +795,47 @@ public: 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) { if constexpr (Structure::canInsertRows()) { @@ -1222,6 +1279,11 @@ protected: && Base::dynamicRows() && range_features::has_erase; } + static constexpr bool canMoveColumns(const QModelIndex &, const QModelIndex &) + { + return true; + } + static constexpr bool canMoveRows(const QModelIndex &, const QModelIndex &) { return true; @@ -1507,6 +1569,11 @@ protected: 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) { return !source.isValid() && !destination.isValid(); diff --git a/src/corelib/itemmodels/qgenericitemmodel_impl.h b/src/corelib/itemmodels/qgenericitemmodel_impl.h index fefcad5ef75..0045181d87b 100644 --- a/src/corelib/itemmodels/qgenericitemmodel_impl.h +++ b/src/corelib/itemmodels/qgenericitemmodel_impl.h @@ -493,6 +493,7 @@ public: ClearItemData, InsertColumns, RemoveColumns, + MoveColumns, InsertRows, RemoveRows, MoveRows, @@ -532,6 +533,9 @@ protected: inline void endInsertColumns(); inline void beginRemoveColumns(const QModelIndex &parent, int start, int count); 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 endInsertRows(); inline void beginRemoveRows(const QModelIndex &parent, int start, int count); diff --git a/tests/auto/corelib/itemmodels/qgenericitemmodel/tst_qgenericitemmodel.cpp b/tests/auto/corelib/itemmodels/qgenericitemmodel/tst_qgenericitemmodel.cpp index cd53b43dd8d..d476a01d55e 100644 --- a/tests/auto/corelib/itemmodels/qgenericitemmodel/tst_qgenericitemmodel.cpp +++ b/tests/auto/corelib/itemmodels/qgenericitemmodel/tst_qgenericitemmodel.cpp @@ -300,6 +300,8 @@ private slots: void insertColumns(); void removeColumns_data() { createTestData(); } void removeColumns(); + void moveColumns_data() { createTestData(); } + void moveColumns(); void inconsistentColumnCount(); @@ -1207,6 +1209,51 @@ void tst_QGenericItemModel::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() { QTest::ignoreMessage(QtCriticalMsg, "QGenericItemModel: "