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"