File indexing completed on 2024-05-12 17:07:19
0001 /* 0002 SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com> 0003 SPDX-FileCopyrightText: 2015 David Faure <david.faure@kdab.com> 0004 SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de> 0005 0006 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0007 */ 0008 0009 #include "shortcutsmodel.h" 0010 0011 class ShortcutsModelPrivate 0012 { 0013 public: 0014 ShortcutsModelPrivate(ShortcutsModel *model) 0015 : q(model) 0016 { 0017 } 0018 0019 int computeRowsPrior(const QAbstractItemModel *sourceModel) const; 0020 QAbstractItemModel *sourceModelForRow(int row, int *sourceRow) const; 0021 0022 void slotRowsAboutToBeInserted(const QModelIndex &, int start, int end); 0023 void slotRowsInserted(const QModelIndex &, int start, int end); 0024 void slotRowsAboutToBeRemoved(const QModelIndex &, int start, int end); 0025 void slotRowsRemoved(const QModelIndex &, int start, int end); 0026 void slotColumnsAboutToBeInserted(const QModelIndex &parent, int start, int end); 0027 void slotColumnsInserted(const QModelIndex &parent, int, int); 0028 void slotColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int end); 0029 void slotColumnsRemoved(const QModelIndex &parent, int, int); 0030 void slotDataChanged(const QModelIndex &from, const QModelIndex &to, const QVector<int> &roles); 0031 void slotSourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint); 0032 void slotSourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint); 0033 void slotModelAboutToBeReset(); 0034 void slotModelReset(); 0035 0036 ShortcutsModel *q; 0037 QList<QAbstractItemModel *> m_models; 0038 int m_rowCount = 0; // have to maintain it here since we can't compute during model destruction 0039 0040 // for layoutAboutToBeChanged/layoutChanged 0041 QVector<QPersistentModelIndex> layoutChangePersistentIndexes; 0042 QModelIndexList proxyIndexes; 0043 }; 0044 0045 ShortcutsModel::ShortcutsModel(QObject *parent) 0046 : QAbstractItemModel(parent) 0047 , d(new ShortcutsModelPrivate(this)) 0048 { 0049 } 0050 0051 ShortcutsModel::~ShortcutsModel() 0052 { 0053 } 0054 0055 QModelIndex ShortcutsModel::mapFromSource(const QModelIndex &sourceIndex) const 0056 { 0057 const QAbstractItemModel *sourceModel = sourceIndex.model(); 0058 if (!sourceModel) { 0059 return {}; 0060 } 0061 int rowsPrior = d->computeRowsPrior(sourceModel); 0062 0063 if (sourceIndex.parent().isValid()) { 0064 return createIndex(sourceIndex.row(), sourceIndex.column(), rowsPrior + sourceIndex.parent().row() + 1); 0065 } 0066 return createIndex(rowsPrior + sourceIndex.row(), sourceIndex.column()); 0067 } 0068 0069 QModelIndex ShortcutsModel::mapToSource(const QModelIndex &proxyIndex) const 0070 { 0071 if (!proxyIndex.isValid()) { 0072 return QModelIndex(); 0073 } 0074 0075 int sourceRow; 0076 int topLevelRow = proxyIndex.internalId() ? proxyIndex.internalId() - 1 : proxyIndex.row(); 0077 QAbstractItemModel *sourceModel = d->sourceModelForRow(topLevelRow, &sourceRow); 0078 if (!sourceModel) { 0079 return QModelIndex(); 0080 } 0081 0082 if (proxyIndex.internalId()) { 0083 return sourceModel->index(proxyIndex.row(), proxyIndex.column(), sourceModel->index(sourceRow, proxyIndex.column())); 0084 } 0085 return sourceModel->index(sourceRow, proxyIndex.column()); 0086 } 0087 0088 QVariant ShortcutsModel::data(const QModelIndex &index, int role) const 0089 { 0090 const QModelIndex sourceIndex = mapToSource(index); 0091 if (!sourceIndex.isValid()) { 0092 return QVariant(); 0093 } 0094 return sourceIndex.model()->data(sourceIndex, role); 0095 } 0096 0097 bool ShortcutsModel::setData(const QModelIndex &index, const QVariant &value, int role) 0098 { 0099 const QModelIndex sourceIndex = mapToSource(index); 0100 if (!sourceIndex.isValid()) { 0101 return false; 0102 } 0103 QAbstractItemModel *sourceModel = const_cast<QAbstractItemModel *>(sourceIndex.model()); 0104 return sourceModel->setData(sourceIndex, value, role); 0105 } 0106 0107 QMap<int, QVariant> ShortcutsModel::itemData(const QModelIndex &proxyIndex) const 0108 { 0109 const QModelIndex sourceIndex = mapToSource(proxyIndex); 0110 if (!sourceIndex.isValid()) { 0111 return {}; 0112 } 0113 return sourceIndex.model()->itemData(sourceIndex); 0114 } 0115 0116 Qt::ItemFlags ShortcutsModel::flags(const QModelIndex &index) const 0117 { 0118 const QModelIndex sourceIndex = mapToSource(index); 0119 return sourceIndex.isValid() ? sourceIndex.model()->flags(sourceIndex) : Qt::ItemFlags(); 0120 } 0121 0122 QVariant ShortcutsModel::headerData(int section, Qt::Orientation orientation, int role) const 0123 { 0124 if (d->m_models.isEmpty()) { 0125 return QVariant(); 0126 } 0127 if (orientation == Qt::Horizontal) { 0128 return d->m_models.at(0)->headerData(section, orientation, role); 0129 } else { 0130 int sourceRow; 0131 QAbstractItemModel *sourceModel = d->sourceModelForRow(section, &sourceRow); 0132 if (!sourceModel) { 0133 return QVariant(); 0134 } 0135 return sourceModel->headerData(sourceRow, orientation, role); 0136 } 0137 } 0138 0139 int ShortcutsModel::columnCount(const QModelIndex &parent) const 0140 { 0141 if (d->m_models.isEmpty()) { 0142 return 0; 0143 } 0144 if (parent.isValid()) { 0145 const QModelIndex sourceParent = mapToSource(parent); 0146 return sourceParent.model()->columnCount(sourceParent); 0147 } 0148 return d->m_models.at(0)->columnCount(QModelIndex()); 0149 } 0150 0151 QHash<int, QByteArray> ShortcutsModel::roleNames() const 0152 { 0153 if (d->m_models.isEmpty()) { 0154 return {}; 0155 } 0156 return d->m_models.at(0)->roleNames(); 0157 } 0158 0159 QModelIndex ShortcutsModel::index(int row, int column, const QModelIndex &parent) const 0160 { 0161 if (row < 0) { 0162 return {}; 0163 } 0164 if (column < 0) { 0165 return {}; 0166 } 0167 0168 if (parent.isValid()) { 0169 const QModelIndex sourceParent = mapToSource(parent); 0170 return mapFromSource(sourceParent.model()->index(row, column, sourceParent)); 0171 } 0172 0173 int sourceRow; 0174 QAbstractItemModel *sourceModel = d->sourceModelForRow(row, &sourceRow); 0175 if (!sourceModel) { 0176 return QModelIndex(); 0177 } 0178 return mapFromSource(sourceModel->index(sourceRow, column, parent)); 0179 } 0180 0181 QModelIndex ShortcutsModel::parent(const QModelIndex &child) const 0182 { 0183 return mapFromSource(mapToSource(child).parent()); 0184 } 0185 0186 int ShortcutsModel::rowCount(const QModelIndex &parent) const 0187 { 0188 if (parent.isValid()) { 0189 const QModelIndex sourceParent = mapToSource(parent); 0190 return sourceParent.model()->rowCount(sourceParent); 0191 } 0192 0193 return d->m_rowCount; 0194 } 0195 0196 void ShortcutsModel::addSourceModel(QAbstractItemModel *sourceModel) 0197 { 0198 Q_ASSERT(sourceModel); 0199 Q_ASSERT(!d->m_models.contains(sourceModel)); 0200 // clang-format off 0201 connect(sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)), this, SLOT(slotDataChanged(QModelIndex,QModelIndex,QVector<int>))); 0202 connect(sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotRowsInserted(QModelIndex,int,int))); 0203 connect(sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(slotRowsRemoved(QModelIndex,int,int))); 0204 connect(sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(slotRowsAboutToBeInserted(QModelIndex,int,int))); 0205 connect(sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); 0206 0207 connect(sourceModel, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(slotColumnsInserted(QModelIndex,int,int))); 0208 connect(sourceModel, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(slotColumnsRemoved(QModelIndex,int,int))); 0209 connect(sourceModel, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(slotColumnsAboutToBeInserted(QModelIndex,int,int))); 0210 connect(sourceModel, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(slotColumnsAboutToBeRemoved(QModelIndex,int,int))); 0211 0212 connect(sourceModel, SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)), 0213 this, SLOT(slotSourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint))); 0214 connect(sourceModel, SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)), 0215 this, SLOT(slotSourceLayoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint))); 0216 connect(sourceModel, SIGNAL(modelAboutToBeReset()), this, SLOT(slotModelAboutToBeReset())); 0217 connect(sourceModel, SIGNAL(modelReset()), this, SLOT(slotModelReset())); 0218 // clang-format on 0219 0220 const int newRows = sourceModel->rowCount(); 0221 if (newRows > 0) { 0222 beginInsertRows(QModelIndex(), d->m_rowCount, d->m_rowCount + newRows - 1); 0223 } 0224 d->m_rowCount += newRows; 0225 d->m_models.append(sourceModel); 0226 if (newRows > 0) { 0227 endInsertRows(); 0228 } 0229 } 0230 0231 QList<QAbstractItemModel *> ShortcutsModel::sources() const 0232 { 0233 return d->m_models; 0234 } 0235 0236 void ShortcutsModel::removeSourceModel(QAbstractItemModel *sourceModel) 0237 { 0238 Q_ASSERT(d->m_models.contains(sourceModel)); 0239 disconnect(sourceModel, nullptr, this, nullptr); 0240 0241 const int rowsRemoved = sourceModel->rowCount(); 0242 const int rowsPrior = d->computeRowsPrior(sourceModel); // location of removed section 0243 0244 if (rowsRemoved > 0) { 0245 beginRemoveRows(QModelIndex(), rowsPrior, rowsPrior + rowsRemoved - 1); 0246 } 0247 d->m_models.removeOne(sourceModel); 0248 d->m_rowCount -= rowsRemoved; 0249 if (rowsRemoved > 0) { 0250 endRemoveRows(); 0251 } 0252 } 0253 0254 void ShortcutsModelPrivate::slotRowsAboutToBeInserted(const QModelIndex &sourceParent, int start, int end) 0255 { 0256 const QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(q->sender()); 0257 if (sourceParent.isValid()) { 0258 q->beginInsertRows(q->mapFromSource(sourceParent), start, end); 0259 } else { 0260 const int rowsPrior = computeRowsPrior(model); 0261 q->beginInsertRows(QModelIndex(), rowsPrior + start, rowsPrior + end); 0262 } 0263 } 0264 0265 void ShortcutsModelPrivate::slotRowsInserted(const QModelIndex &sourceParent, int start, int end) 0266 { 0267 if (!sourceParent.isValid()) { 0268 m_rowCount += end - start + 1; 0269 } 0270 q->endInsertRows(); 0271 } 0272 0273 void ShortcutsModelPrivate::slotRowsAboutToBeRemoved(const QModelIndex &sourceParent, int start, int end) 0274 { 0275 if (sourceParent.isValid()) { 0276 q->beginRemoveRows(q->mapFromSource(sourceParent), start, end); 0277 } else { 0278 const QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(q->sender()); 0279 const int rowsPrior = computeRowsPrior(model); 0280 q->beginRemoveRows(QModelIndex(), rowsPrior + start, rowsPrior + end); 0281 } 0282 } 0283 0284 void ShortcutsModelPrivate::slotRowsRemoved(const QModelIndex &sourceParent, int start, int end) 0285 { 0286 if (!sourceParent.isValid()) { 0287 m_rowCount -= end - start + 1; 0288 } 0289 q->endRemoveRows(); 0290 } 0291 0292 void ShortcutsModelPrivate::slotColumnsAboutToBeInserted(const QModelIndex &parent, int start, int end) 0293 { 0294 if (parent.isValid()) { // we are flat 0295 q->beginInsertColumns(q->mapFromSource(parent), start, end); 0296 } 0297 const QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(q->sender()); 0298 if (m_models.at(0) == model) { 0299 q->beginInsertColumns(QModelIndex(), start, end); 0300 } 0301 } 0302 0303 void ShortcutsModelPrivate::slotColumnsInserted(const QModelIndex &parent, int, int) 0304 { 0305 const QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(q->sender()); 0306 if (m_models.at(0) == model || parent.isValid()) { 0307 q->endInsertColumns(); 0308 } 0309 } 0310 0311 void ShortcutsModelPrivate::slotColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) 0312 { 0313 if (parent.isValid()) { 0314 q->beginRemoveColumns(q->mapFromSource(parent), start, end); 0315 } 0316 const QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(q->sender()); 0317 if (m_models.at(0) == model) { 0318 q->beginRemoveColumns(QModelIndex(), start, end); 0319 } 0320 } 0321 0322 void ShortcutsModelPrivate::slotColumnsRemoved(const QModelIndex &parent, int, int) 0323 { 0324 const QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(q->sender()); 0325 if (m_models.at(0) == model || parent.isValid()) { 0326 q->endRemoveColumns(); 0327 } 0328 } 0329 0330 void ShortcutsModelPrivate::slotDataChanged(const QModelIndex &from, const QModelIndex &to, const QVector<int> &roles) 0331 { 0332 if (!from.isValid()) { // QSFPM bug, it emits dataChanged(invalid, invalid) if a cell in a hidden column changes 0333 return; 0334 } 0335 const QModelIndex myFrom = q->mapFromSource(from); 0336 const QModelIndex myTo = q->mapFromSource(to); 0337 Q_EMIT q->dataChanged(myFrom, myTo, roles); 0338 } 0339 0340 void ShortcutsModelPrivate::slotSourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint) 0341 { 0342 QList<QPersistentModelIndex> parents; 0343 parents.reserve(sourceParents.size()); 0344 for (const QPersistentModelIndex &parent : sourceParents) { 0345 if (!parent.isValid()) { 0346 parents << QPersistentModelIndex(); 0347 continue; 0348 } 0349 const QModelIndex mappedParent = q->mapFromSource(parent); 0350 Q_ASSERT(mappedParent.isValid()); 0351 parents << mappedParent; 0352 } 0353 0354 Q_EMIT q->layoutAboutToBeChanged(parents, hint); 0355 0356 const QModelIndexList persistentIndexList = q->persistentIndexList(); 0357 layoutChangePersistentIndexes.reserve(persistentIndexList.size()); 0358 0359 for (const QPersistentModelIndex &proxyPersistentIndex : persistentIndexList) { 0360 proxyIndexes << proxyPersistentIndex; 0361 Q_ASSERT(proxyPersistentIndex.isValid()); 0362 const QPersistentModelIndex srcPersistentIndex = q->mapToSource(proxyPersistentIndex); 0363 Q_ASSERT(srcPersistentIndex.isValid()); 0364 layoutChangePersistentIndexes << srcPersistentIndex; 0365 } 0366 } 0367 0368 void ShortcutsModelPrivate::slotSourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint) 0369 { 0370 for (int i = 0; i < proxyIndexes.size(); ++i) { 0371 const QModelIndex proxyIdx = proxyIndexes.at(i); 0372 QModelIndex newProxyIdx = q->mapFromSource(layoutChangePersistentIndexes.at(i)); 0373 q->changePersistentIndex(proxyIdx, newProxyIdx); 0374 } 0375 0376 layoutChangePersistentIndexes.clear(); 0377 proxyIndexes.clear(); 0378 0379 QList<QPersistentModelIndex> parents; 0380 parents.reserve(sourceParents.size()); 0381 for (const QPersistentModelIndex &parent : sourceParents) { 0382 if (!parent.isValid()) { 0383 parents << QPersistentModelIndex(); 0384 continue; 0385 } 0386 const QModelIndex mappedParent = q->mapFromSource(parent); 0387 Q_ASSERT(mappedParent.isValid()); 0388 parents << mappedParent; 0389 } 0390 Q_EMIT q->layoutChanged(parents, hint); 0391 } 0392 0393 void ShortcutsModelPrivate::slotModelAboutToBeReset() 0394 { 0395 const QAbstractItemModel *sourceModel = qobject_cast<const QAbstractItemModel *>(q->sender()); 0396 Q_ASSERT(m_models.contains(const_cast<QAbstractItemModel *>(sourceModel))); 0397 q->beginResetModel(); 0398 } 0399 0400 void ShortcutsModelPrivate::slotModelReset() 0401 { 0402 m_rowCount = computeRowsPrior(nullptr); 0403 q->endResetModel(); 0404 } 0405 0406 int ShortcutsModelPrivate::computeRowsPrior(const QAbstractItemModel *sourceModel) const 0407 { 0408 int rowsPrior = 0; 0409 for (const QAbstractItemModel *model : qAsConst(m_models)) { 0410 if (model == sourceModel) { 0411 break; 0412 } 0413 rowsPrior += model->rowCount(); 0414 } 0415 return rowsPrior; 0416 } 0417 0418 QAbstractItemModel *ShortcutsModelPrivate::sourceModelForRow(int row, int *sourceRow) const 0419 { 0420 int rowCount = 0; 0421 QAbstractItemModel *selection = nullptr; 0422 for (QAbstractItemModel *model : qAsConst(m_models)) { 0423 const int subRowCount = model->rowCount(); 0424 if (rowCount + subRowCount > row) { 0425 selection = model; 0426 break; 0427 } 0428 rowCount += subRowCount; 0429 } 0430 *sourceRow = row - rowCount; 0431 return selection; 0432 } 0433 0434 #include "moc_shortcutsmodel.cpp"