File indexing completed on 2024-04-28 15:27:48

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