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"