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"