File indexing completed on 2024-07-21 06:38:22

0001 /*
0002     SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
0003     SPDX-FileContributor: David Faure <david.faure@kdab.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kextracolumnsproxymodel.h"
0009 #include "kitemmodels_debug.h"
0010 
0011 #include <QItemSelection>
0012 
0013 class KExtraColumnsProxyModelPrivate
0014 {
0015     Q_DECLARE_PUBLIC(KExtraColumnsProxyModel)
0016     KExtraColumnsProxyModel *const q_ptr;
0017 
0018 public:
0019     KExtraColumnsProxyModelPrivate(KExtraColumnsProxyModel *model)
0020         : q_ptr(model)
0021     {
0022     }
0023 
0024     void _ec_sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint);
0025     void _ec_sourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint);
0026 
0027     // Configuration (doesn't change once source model is plugged in)
0028     QList<QString> m_extraHeaders;
0029 
0030     // for layoutAboutToBeChanged/layoutChanged
0031     QList<QPersistentModelIndex> layoutChangePersistentIndexes;
0032     QList<int> layoutChangeProxyColumns;
0033     QModelIndexList proxyIndexes;
0034 };
0035 
0036 KExtraColumnsProxyModel::KExtraColumnsProxyModel(QObject *parent)
0037     : QIdentityProxyModel(parent)
0038     , d_ptr(new KExtraColumnsProxyModelPrivate(this))
0039 {
0040 }
0041 
0042 KExtraColumnsProxyModel::~KExtraColumnsProxyModel()
0043 {
0044 }
0045 
0046 void KExtraColumnsProxyModel::appendColumn(const QString &header)
0047 {
0048     Q_D(KExtraColumnsProxyModel);
0049     d->m_extraHeaders.append(header);
0050 }
0051 
0052 void KExtraColumnsProxyModel::removeExtraColumn(int idx)
0053 {
0054     Q_D(KExtraColumnsProxyModel);
0055     d->m_extraHeaders.remove(idx);
0056 }
0057 
0058 bool KExtraColumnsProxyModel::setExtraColumnData(const QModelIndex &parent, int row, int extraColumn, const QVariant &data, int role)
0059 {
0060     Q_UNUSED(parent);
0061     Q_UNUSED(row);
0062     Q_UNUSED(extraColumn);
0063     Q_UNUSED(data);
0064     Q_UNUSED(role);
0065     return false;
0066 }
0067 
0068 void KExtraColumnsProxyModel::extraColumnDataChanged(const QModelIndex &parent, int row, int extraColumn, const QList<int> &roles)
0069 {
0070     const QModelIndex idx = index(row, proxyColumnForExtraColumn(extraColumn), parent);
0071     Q_EMIT dataChanged(idx, idx, roles);
0072 }
0073 
0074 void KExtraColumnsProxyModel::setSourceModel(QAbstractItemModel *model)
0075 {
0076     if (sourceModel()) {
0077         disconnect(sourceModel(),
0078                    SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
0079                    this,
0080                    SLOT(_ec_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
0081         disconnect(sourceModel(),
0082                    SIGNAL(layoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
0083                    this,
0084                    SLOT(_ec_sourceLayoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
0085     }
0086 
0087     QIdentityProxyModel::setSourceModel(model);
0088 
0089     if (model) {
0090         // The handling of persistent model indexes assumes mapToSource can be called for any index
0091         // This breaks for the extra column, so we'll have to do it ourselves
0092         disconnect(model,
0093                    SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
0094                    this,
0095                    SLOT(_q_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
0096         disconnect(model,
0097                    SIGNAL(layoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
0098                    this,
0099                    SLOT(_q_sourceLayoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
0100         connect(model,
0101                 SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
0102                 this,
0103                 SLOT(_ec_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
0104         connect(model,
0105                 SIGNAL(layoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
0106                 this,
0107                 SLOT(_ec_sourceLayoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
0108     }
0109 }
0110 
0111 QModelIndex KExtraColumnsProxyModel::mapToSource(const QModelIndex &proxyIndex) const
0112 {
0113     if (!proxyIndex.isValid()) { // happens in e.g. rowCount(mapToSource(parent))
0114         return QModelIndex();
0115     }
0116     const int column = proxyIndex.column();
0117     if (column >= sourceModel()->columnCount()) {
0118         qCDebug(KITEMMODELS_LOG) << "Returning invalid index in mapToSource";
0119         return QModelIndex();
0120     }
0121     return QIdentityProxyModel::mapToSource(proxyIndex);
0122 }
0123 
0124 QModelIndex KExtraColumnsProxyModel::buddy(const QModelIndex &proxyIndex) const
0125 {
0126     const int column = proxyIndex.column();
0127     if (column >= sourceModel()->columnCount()) {
0128         return proxyIndex;
0129     }
0130     return QIdentityProxyModel::buddy(proxyIndex);
0131 }
0132 
0133 QModelIndex KExtraColumnsProxyModel::sibling(int row, int column, const QModelIndex &idx) const
0134 {
0135     if (row == idx.row() && column == idx.column()) {
0136         return idx;
0137     }
0138     return index(row, column, parent(idx));
0139 }
0140 
0141 QItemSelection KExtraColumnsProxyModel::mapSelectionToSource(const QItemSelection &selection) const
0142 {
0143     QItemSelection sourceSelection;
0144 
0145     if (!sourceModel()) {
0146         return sourceSelection;
0147     }
0148 
0149     // mapToSource will give invalid index for our additional columns, so truncate the selection
0150     // to the columns known by the source model
0151     const int sourceColumnCount = sourceModel()->columnCount();
0152     QItemSelection::const_iterator it = selection.constBegin();
0153     const QItemSelection::const_iterator end = selection.constEnd();
0154     for (; it != end; ++it) {
0155         Q_ASSERT(it->model() == this);
0156         QModelIndex topLeft = it->topLeft();
0157         Q_ASSERT(topLeft.isValid());
0158         Q_ASSERT(topLeft.model() == this);
0159         topLeft = topLeft.sibling(topLeft.row(), 0);
0160         QModelIndex bottomRight = it->bottomRight();
0161         Q_ASSERT(bottomRight.isValid());
0162         Q_ASSERT(bottomRight.model() == this);
0163         if (bottomRight.column() >= sourceColumnCount) {
0164             bottomRight = bottomRight.sibling(bottomRight.row(), sourceColumnCount - 1);
0165         }
0166         // This can lead to duplicate source indexes, so use merge().
0167         const QItemSelectionRange range(mapToSource(topLeft), mapToSource(bottomRight));
0168         QItemSelection newSelection;
0169         newSelection << range;
0170         sourceSelection.merge(newSelection, QItemSelectionModel::Select);
0171     }
0172 
0173     return sourceSelection;
0174 }
0175 
0176 int KExtraColumnsProxyModel::columnCount(const QModelIndex &parent) const
0177 {
0178     Q_D(const KExtraColumnsProxyModel);
0179     return QIdentityProxyModel::columnCount(parent) + d->m_extraHeaders.count();
0180 }
0181 
0182 QVariant KExtraColumnsProxyModel::data(const QModelIndex &index, int role) const
0183 {
0184     Q_D(const KExtraColumnsProxyModel);
0185     const int extraCol = extraColumnForProxyColumn(index.column());
0186     if (extraCol >= 0 && !d->m_extraHeaders.isEmpty()) {
0187         return extraColumnData(index.parent(), index.row(), extraCol, role);
0188     }
0189     return sourceModel()->data(mapToSource(index), role);
0190 }
0191 
0192 bool KExtraColumnsProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
0193 {
0194     Q_D(const KExtraColumnsProxyModel);
0195     const int extraCol = extraColumnForProxyColumn(index.column());
0196     if (extraCol >= 0 && !d->m_extraHeaders.isEmpty()) {
0197         return setExtraColumnData(index.parent(), index.row(), extraCol, value, role);
0198     }
0199     return sourceModel()->setData(mapToSource(index), value, role);
0200 }
0201 
0202 Qt::ItemFlags KExtraColumnsProxyModel::flags(const QModelIndex &index) const
0203 {
0204     const int extraCol = extraColumnForProxyColumn(index.column());
0205     if (extraCol >= 0) {
0206         // extra columns are readonly
0207         return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
0208     }
0209     return sourceModel() != nullptr ? sourceModel()->flags(mapToSource(index)) : Qt::NoItemFlags;
0210 }
0211 
0212 bool KExtraColumnsProxyModel::hasChildren(const QModelIndex &index) const
0213 {
0214     if (index.column() > 0) {
0215         return false;
0216     }
0217     return QIdentityProxyModel::hasChildren(index);
0218 }
0219 
0220 QVariant KExtraColumnsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
0221 {
0222     Q_D(const KExtraColumnsProxyModel);
0223     if (orientation == Qt::Horizontal) {
0224         const int extraCol = extraColumnForProxyColumn(section);
0225         if (extraCol >= 0) {
0226             // Only text is supported, in headers for extra columns
0227             if (role == Qt::DisplayRole) {
0228                 return d->m_extraHeaders.at(extraCol);
0229             }
0230             return QVariant();
0231         }
0232     }
0233     return QIdentityProxyModel::headerData(section, orientation, role);
0234 }
0235 
0236 QModelIndex KExtraColumnsProxyModel::index(int row, int column, const QModelIndex &parent) const
0237 {
0238     const int extraCol = extraColumnForProxyColumn(column);
0239     if (extraCol >= 0) {
0240         // We store the internal pointer of the index for column 0 in the proxy index for extra columns.
0241         // This will be useful in the parent method.
0242         return createIndex(row, column, QIdentityProxyModel::index(row, 0, parent).internalPointer());
0243     }
0244     return QIdentityProxyModel::index(row, column, parent);
0245 }
0246 
0247 QModelIndex KExtraColumnsProxyModel::parent(const QModelIndex &child) const
0248 {
0249     const int extraCol = extraColumnForProxyColumn(child.column());
0250     if (extraCol >= 0) {
0251         // Create an index for column 0 and use that to get the parent.
0252         const QModelIndex proxySibling = createIndex(child.row(), 0, child.internalPointer());
0253         return QIdentityProxyModel::parent(proxySibling);
0254     }
0255     return QIdentityProxyModel::parent(child);
0256 }
0257 
0258 int KExtraColumnsProxyModel::extraColumnForProxyColumn(int proxyColumn) const
0259 {
0260     if (sourceModel() != nullptr) {
0261         const int sourceColumnCount = sourceModel()->columnCount();
0262         if (proxyColumn >= sourceColumnCount) {
0263             return proxyColumn - sourceColumnCount;
0264         }
0265     }
0266     return -1;
0267 }
0268 
0269 int KExtraColumnsProxyModel::proxyColumnForExtraColumn(int extraColumn) const
0270 {
0271     return sourceModel()->columnCount() + extraColumn;
0272 }
0273 
0274 void KExtraColumnsProxyModelPrivate::_ec_sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents,
0275                                                                       QAbstractItemModel::LayoutChangeHint hint)
0276 {
0277     Q_Q(KExtraColumnsProxyModel);
0278 
0279     QList<QPersistentModelIndex> parents;
0280     parents.reserve(sourceParents.size());
0281     for (const QPersistentModelIndex &parent : sourceParents) {
0282         if (!parent.isValid()) {
0283             parents << QPersistentModelIndex();
0284             continue;
0285         }
0286         const QModelIndex mappedParent = q->mapFromSource(parent);
0287         Q_ASSERT(mappedParent.isValid());
0288         parents << mappedParent;
0289     }
0290 
0291     Q_EMIT q->layoutAboutToBeChanged(parents, hint);
0292 
0293     const QModelIndexList persistentIndexList = q->persistentIndexList();
0294     layoutChangePersistentIndexes.reserve(persistentIndexList.size());
0295     layoutChangeProxyColumns.reserve(persistentIndexList.size());
0296 
0297     for (QModelIndex proxyPersistentIndex : persistentIndexList) {
0298         proxyIndexes << proxyPersistentIndex;
0299         Q_ASSERT(proxyPersistentIndex.isValid());
0300         const int column = proxyPersistentIndex.column();
0301         layoutChangeProxyColumns << column;
0302         if (column >= q->sourceModel()->columnCount()) {
0303             proxyPersistentIndex = proxyPersistentIndex.sibling(proxyPersistentIndex.row(), 0);
0304         }
0305         const QPersistentModelIndex srcPersistentIndex = q->mapToSource(proxyPersistentIndex);
0306         Q_ASSERT(srcPersistentIndex.isValid());
0307         layoutChangePersistentIndexes << srcPersistentIndex;
0308     }
0309 }
0310 
0311 void KExtraColumnsProxyModelPrivate::_ec_sourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint)
0312 {
0313     Q_Q(KExtraColumnsProxyModel);
0314     for (int i = 0; i < proxyIndexes.size(); ++i) {
0315         const QModelIndex proxyIdx = proxyIndexes.at(i);
0316         QModelIndex newProxyIdx = q->mapFromSource(layoutChangePersistentIndexes.at(i));
0317         if (proxyIdx.column() >= q->sourceModel()->columnCount()) {
0318             newProxyIdx = newProxyIdx.sibling(newProxyIdx.row(), layoutChangeProxyColumns.at(i));
0319         }
0320         q->changePersistentIndex(proxyIdx, newProxyIdx);
0321     }
0322 
0323     layoutChangePersistentIndexes.clear();
0324     layoutChangeProxyColumns.clear();
0325     proxyIndexes.clear();
0326 
0327     QList<QPersistentModelIndex> parents;
0328     parents.reserve(sourceParents.size());
0329     for (const QPersistentModelIndex &parent : sourceParents) {
0330         if (!parent.isValid()) {
0331             parents << QPersistentModelIndex();
0332             continue;
0333         }
0334         const QModelIndex mappedParent = q->mapFromSource(parent);
0335         Q_ASSERT(mappedParent.isValid());
0336         parents << mappedParent;
0337     }
0338 
0339     Q_EMIT q->layoutChanged(parents, hint);
0340 }
0341 
0342 #include "moc_kextracolumnsproxymodel.cpp"