File indexing completed on 2025-04-20 06:44:16
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"