File indexing completed on 2024-04-14 03:53:51

0001 /*
0002     SPDX-FileCopyrightText: 2009 Stephen Kelly <steveire@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "modelspy.h"
0008 
0009 #include <QDebug>
0010 
0011 ModelSpy::ModelSpy(QObject *parent)
0012     : QObject(parent)
0013     , QList<QVariantList>()
0014     , m_model(nullptr)
0015     , m_isSpying(false)
0016     , m_lazyPersist(false)
0017 {
0018 }
0019 
0020 void ModelSpy::setModel(QAbstractItemModel *model)
0021 {
0022     stopSpying();
0023     m_model = model;
0024 }
0025 
0026 void ModelSpy::clearTestData()
0027 {
0028     m_changeList.clear();
0029     m_unchangedIndexes.clear();
0030     m_unchangedPersistentIndexes.clear();
0031 }
0032 
0033 void ModelSpy::startSpying()
0034 {
0035     // If a signal is connected to a slot multiple times, the slot gets called multiple times.
0036     // As we're doing start and stop spying all the time, we disconnect here first to make sure.
0037     stopSpying();
0038 
0039     m_isSpying = true;
0040 
0041     connect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &ModelSpy::rowsAboutToBeInserted);
0042     connect(m_model, &QAbstractItemModel::rowsInserted, this, &ModelSpy::rowsInserted);
0043     connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ModelSpy::rowsAboutToBeRemoved);
0044     connect(m_model, &QAbstractItemModel::rowsRemoved, this, &ModelSpy::rowsRemoved);
0045     connect(m_model, &QAbstractItemModel::layoutAboutToBeChanged, this, &ModelSpy::layoutAboutToBeChanged);
0046     connect(m_model, &QAbstractItemModel::layoutChanged, this, &ModelSpy::layoutChanged);
0047     connect(m_model, &QAbstractItemModel::modelAboutToBeReset, this, &ModelSpy::modelAboutToBeReset);
0048     connect(m_model, &QAbstractItemModel::modelReset, this, &ModelSpy::modelReset);
0049     connect(m_model, &QAbstractItemModel::rowsAboutToBeMoved, this, &ModelSpy::rowsAboutToBeMoved);
0050     connect(m_model, &QAbstractItemModel::rowsMoved, this, &ModelSpy::rowsMoved);
0051     connect(m_model, &QAbstractItemModel::dataChanged, this, &ModelSpy::dataChanged);
0052     connect(m_model, &QObject::destroyed, this, &ModelSpy::modelDestroyed);
0053 }
0054 
0055 void ModelSpy::stopSpying()
0056 {
0057     m_isSpying = false;
0058     if (!m_model) {
0059         return;
0060     }
0061 
0062     disconnect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &ModelSpy::rowsAboutToBeInserted);
0063     disconnect(m_model, &QAbstractItemModel::rowsInserted, this, &ModelSpy::rowsInserted);
0064     disconnect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ModelSpy::rowsAboutToBeRemoved);
0065     disconnect(m_model, &QAbstractItemModel::rowsRemoved, this, &ModelSpy::rowsRemoved);
0066     disconnect(m_model, &QAbstractItemModel::layoutAboutToBeChanged, this, &ModelSpy::layoutAboutToBeChanged);
0067     disconnect(m_model, &QAbstractItemModel::layoutChanged, this, &ModelSpy::layoutChanged);
0068     disconnect(m_model, &QAbstractItemModel::modelAboutToBeReset, this, &ModelSpy::modelAboutToBeReset);
0069     disconnect(m_model, &QAbstractItemModel::modelReset, this, &ModelSpy::modelReset);
0070     disconnect(m_model, &QAbstractItemModel::rowsAboutToBeMoved, this, &ModelSpy::rowsAboutToBeMoved);
0071     disconnect(m_model, &QAbstractItemModel::rowsMoved, this, &ModelSpy::rowsMoved);
0072     disconnect(m_model, &QAbstractItemModel::dataChanged, this, &ModelSpy::dataChanged);
0073     disconnect(m_model, &QObject::destroyed, this, &ModelSpy::modelDestroyed);
0074 }
0075 
0076 void ModelSpy::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
0077 {
0078     append(QVariantList() << RowsAboutToBeInserted << QVariant::fromValue(parent) << start << end);
0079 
0080     if (m_lazyPersist) {
0081         doPersist();
0082     }
0083 }
0084 
0085 void ModelSpy::rowsInserted(const QModelIndex &parent, int start, int end)
0086 {
0087     append(QVariantList() << RowsInserted << QVariant::fromValue(parent) << start << end);
0088 }
0089 
0090 void ModelSpy::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
0091 {
0092     append(QVariantList() << RowsAboutToBeRemoved << QVariant::fromValue(parent) << start << end);
0093 
0094     if (m_lazyPersist) {
0095         doPersist();
0096     }
0097 }
0098 
0099 void ModelSpy::rowsRemoved(const QModelIndex &parent, int start, int end)
0100 {
0101     append(QVariantList() << RowsRemoved << QVariant::fromValue(parent) << start << end);
0102 }
0103 
0104 void ModelSpy::layoutAboutToBeChanged()
0105 {
0106     append(QVariantList() << LayoutAboutToBeChanged);
0107 
0108     if (m_lazyPersist) {
0109         doPersist();
0110     }
0111 }
0112 
0113 void ModelSpy::layoutChanged()
0114 {
0115     append(QVariantList() << LayoutChanged);
0116 }
0117 
0118 void ModelSpy::modelAboutToBeReset()
0119 {
0120     append(QVariantList() << ModelAboutToBeReset);
0121 
0122     // This is called in setSourceModel for example, which is not when we want to persist.
0123     if (m_lazyPersist && m_model->hasChildren()) {
0124         doPersist();
0125     }
0126 }
0127 
0128 void ModelSpy::modelReset()
0129 {
0130     append(QVariantList() << ModelReset);
0131 }
0132 
0133 void ModelSpy::modelDestroyed()
0134 {
0135     stopSpying();
0136     m_model = nullptr;
0137 }
0138 
0139 void ModelSpy::rowsAboutToBeMoved(const QModelIndex &srcParent, int start, int end, const QModelIndex &destParent, int destStart)
0140 {
0141     append(QVariantList() << RowsAboutToBeMoved << QVariant::fromValue(srcParent) << start << end << QVariant::fromValue(destParent) << destStart);
0142 
0143     // Don't do a lazy persist here. That will be done on the layoutAboutToBeChanged signal.
0144     //   if (m_lazyPersist)
0145     //     doPersist();
0146 }
0147 
0148 void ModelSpy::rowsMoved(const QModelIndex &srcParent, int start, int end, const QModelIndex &destParent, int destStart)
0149 {
0150     append(QVariantList() << RowsMoved << QVariant::fromValue(srcParent) << start << end << QVariant::fromValue(destParent) << destStart);
0151 }
0152 
0153 void ModelSpy::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0154 {
0155     append(QVariantList() << DataChanged << QVariant::fromValue(topLeft) << QVariant::fromValue(bottomRight));
0156 }
0157 
0158 QModelIndexList ModelSpy::getDescendantIndexes(const QModelIndex &parent)
0159 {
0160     QModelIndexList list;
0161     const int column = 0;
0162     for (int row = 0; row < m_model->rowCount(parent); ++row) {
0163         QModelIndex idx = m_model->index(row, column, parent);
0164         list << idx;
0165         list << getDescendantIndexes(idx);
0166     }
0167     return list;
0168 }
0169 
0170 QList<QPersistentModelIndex> ModelSpy::toPersistent(const QModelIndexList &list)
0171 {
0172     QList<QPersistentModelIndex> persistentList;
0173     for (const QModelIndex &idx : list) {
0174         persistentList << QPersistentModelIndex(idx);
0175     }
0176     return persistentList;
0177 }
0178 
0179 QModelIndexList ModelSpy::getUnchangedIndexes(const QModelIndex &parent, const QList<QItemSelectionRange> &ignoredRanges)
0180 {
0181     QModelIndexList list;
0182     int rowCount = m_model->rowCount(parent);
0183     for (int row = 0; row < rowCount;) {
0184         int column = 0;
0185         QModelIndex idx = m_model->index(row, column, parent);
0186         Q_ASSERT(idx.isValid());
0187         bool found = false;
0188         for (const QItemSelectionRange &range : ignoredRanges) {
0189             if (range.topLeft().parent() == parent && range.topLeft().row() == idx.row()) {
0190                 row = range.bottomRight().row() + 1;
0191                 found = true;
0192                 break;
0193             }
0194         }
0195         if (!found) {
0196             for (column = 0; column < m_model->columnCount(); ++column) {
0197                 list << m_model->index(row, column, parent);
0198             }
0199             list << getUnchangedIndexes(idx, ignoredRanges);
0200             ++row;
0201         }
0202     }
0203     return list;
0204 }
0205 
0206 void ModelSpy::preTestPersistIndexes(const PersistentChangeList &changeList)
0207 {
0208     m_changeList = changeList;
0209     if (!m_lazyPersist) {
0210         doPersist();
0211     }
0212 }
0213 
0214 void ModelSpy::doPersist()
0215 {
0216     Q_ASSERT(m_unchangedIndexes.isEmpty());
0217     Q_ASSERT(m_unchangedPersistentIndexes.isEmpty());
0218 
0219     const int columnCount = m_model->columnCount();
0220     QMutableListIterator<PersistentIndexChange> it(m_changeList);
0221 
0222     // The indexes are defined by the test are described with IndexFinder before anything in the model exists.
0223     // Now that the indexes should exist, resolve them in the change objects.
0224     QList<QItemSelectionRange> changedRanges;
0225 
0226     while (it.hasNext()) {
0227         PersistentIndexChange change = it.next();
0228         change.parentFinder.setModel(m_model);
0229         QModelIndex parent = change.parentFinder.getIndex();
0230 
0231         Q_ASSERT(change.startRow >= 0);
0232         Q_ASSERT(change.startRow <= change.endRow);
0233 
0234         if (change.endRow >= m_model->rowCount(parent)) {
0235             qDebug() << m_model << parent << change.startRow << change.endRow << parent.data() << m_model->rowCount(parent);
0236         }
0237 
0238         Q_ASSERT(change.endRow < m_model->rowCount(parent));
0239 
0240         QModelIndex topLeft = m_model->index(change.startRow, 0, parent);
0241         QModelIndex bottomRight = m_model->index(change.endRow, columnCount - 1, parent);
0242 
0243         // We store the changed ranges so that we know which ranges should not be changed
0244         changedRanges << QItemSelectionRange(topLeft, bottomRight);
0245 
0246         // Store the initial state of the indexes in the model which we expect to change.
0247         for (int row = change.startRow; row <= change.endRow; ++row) {
0248             for (int column = 0; column < columnCount; ++column) {
0249                 QModelIndex idx = m_model->index(row, column, parent);
0250                 Q_ASSERT(idx.isValid());
0251                 change.indexes << idx;
0252                 change.persistentIndexes << QPersistentModelIndex(idx);
0253             }
0254 
0255             // Also store the descendants of changed indexes so that we can verify the effect on them
0256             QModelIndex idx = m_model->index(row, 0, parent);
0257             QModelIndexList descs = getDescendantIndexes(idx);
0258             change.descendantIndexes << descs;
0259             change.persistentDescendantIndexes << toPersistent(descs);
0260         }
0261         it.setValue(change);
0262     }
0263     // Any indexes outside of the ranges we expect to be changed are stored
0264     // so that we can later verify that they remain unchanged.
0265     m_unchangedIndexes = getUnchangedIndexes(QModelIndex(), changedRanges);
0266     m_unchangedPersistentIndexes = toPersistent(m_unchangedIndexes);
0267 }
0268 
0269 static const char *const signaltypes[] = {"NoSignal",
0270                                           "RowsAboutToBeInserted",
0271                                           "RowsInserted",
0272                                           "RowsAboutToBeRemoved",
0273                                           "RowsRemoved",
0274                                           "RowsAboutToBeMoved",
0275                                           "RowsMoved",
0276                                           "DataChanged",
0277                                           "LayoutAboutToBeChanged",
0278                                           "LayoutChanged",
0279                                           "ModelAboutToBeReset",
0280                                           "ModelReset"};
0281 
0282 QDebug operator<<(QDebug d, ModelSpy *modelSpy)
0283 {
0284     d << "ModelSpy(";
0285     for (const QVariantList &list : std::as_const(*modelSpy)) {
0286         d << "SIGNAL(";
0287         int sigType = list.first().toInt();
0288         d << signaltypes[sigType];
0289         if (list.size() > 1) {
0290             d << ", " << list.mid(1);
0291         }
0292         d << ")";
0293     }
0294     d << ")";
0295     return d;
0296 }
0297 
0298 #include "moc_modelspy.cpp"