1/*
2 SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
3 SPDX-FileContributor: David Faure <david.faure@kdab.com>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kextracolumnsproxymodel.h"
9#include "kitemmodels_debug.h"
10
11#include <QItemSelection>
12
13class KExtraColumnsProxyModelPrivate
14{
15 Q_DECLARE_PUBLIC(KExtraColumnsProxyModel)
16 KExtraColumnsProxyModel *const q_ptr;
17
18public:
19 KExtraColumnsProxyModelPrivate(KExtraColumnsProxyModel *model)
20 : q_ptr(model)
21 {
22 }
23
24 void _ec_sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint);
25 void _ec_sourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint);
26
27 // Configuration (doesn't change once source model is plugged in)
28 QList<QString> m_extraHeaders;
29
30 // for layoutAboutToBeChanged/layoutChanged
31 QList<QPersistentModelIndex> layoutChangePersistentIndexes;
32 QList<int> layoutChangeProxyColumns;
33 QModelIndexList proxyIndexes;
34};
35
36KExtraColumnsProxyModel::KExtraColumnsProxyModel(QObject *parent)
37 : QIdentityProxyModel(parent)
38 , d_ptr(new KExtraColumnsProxyModelPrivate(this))
39{
40}
41
42KExtraColumnsProxyModel::~KExtraColumnsProxyModel()
43{
44}
45
46void KExtraColumnsProxyModel::appendColumn(const QString &header)
47{
48 Q_D(KExtraColumnsProxyModel);
49 d->m_extraHeaders.append(header);
50}
51
52void KExtraColumnsProxyModel::removeExtraColumn(int idx)
53{
54 Q_D(KExtraColumnsProxyModel);
55 d->m_extraHeaders.remove(idx);
56}
57
58bool KExtraColumnsProxyModel::setExtraColumnData(const QModelIndex &parent, int row, int extraColumn, const QVariant &data, int role)
59{
60 Q_UNUSED(parent);
61 Q_UNUSED(row);
62 Q_UNUSED(extraColumn);
63 Q_UNUSED(data);
64 Q_UNUSED(role);
65 return false;
66}
67
68void KExtraColumnsProxyModel::extraColumnDataChanged(const QModelIndex &parent, int row, int extraColumn, const QList<int> &roles)
69{
70 const QModelIndex idx = index(row, proxyColumnForExtraColumn(extraColumn), parent);
71 Q_EMIT dataChanged(idx, idx, roles);
72}
73
74void KExtraColumnsProxyModel::setSourceModel(QAbstractItemModel *model)
75{
76 if (sourceModel()) {
77 disconnect(sourceModel(),
78 SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
79 this,
80 SLOT(_ec_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
81 disconnect(sourceModel(),
82 SIGNAL(layoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
83 this,
84 SLOT(_ec_sourceLayoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
85 }
86
87 QIdentityProxyModel::setSourceModel(model);
88
89 if (model) {
90 // The handling of persistent model indexes assumes mapToSource can be called for any index
91 // This breaks for the extra column, so we'll have to do it ourselves
92 disconnect(model,
93 SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
94 this,
95 SLOT(_q_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
96 disconnect(model,
97 SIGNAL(layoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
98 this,
99 SLOT(_q_sourceLayoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
100 connect(model,
101 SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
102 this,
103 SLOT(_ec_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
104 connect(model,
105 SIGNAL(layoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
106 this,
107 SLOT(_ec_sourceLayoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
108 }
109}
110
111QModelIndex KExtraColumnsProxyModel::mapToSource(const QModelIndex &proxyIndex) const
112{
113 if (!proxyIndex.isValid()) { // happens in e.g. rowCount(mapToSource(parent))
114 return QModelIndex();
115 }
116 const int column = proxyIndex.column();
117 if (column >= sourceModel()->columnCount()) {
118 qCDebug(KITEMMODELS_LOG) << "Returning invalid index in mapToSource";
119 return QModelIndex();
120 }
121 return QIdentityProxyModel::mapToSource(proxyIndex);
122}
123
124QModelIndex KExtraColumnsProxyModel::buddy(const QModelIndex &proxyIndex) const
125{
126 const int column = proxyIndex.column();
127 if (column >= sourceModel()->columnCount()) {
128 return proxyIndex;
129 }
130 return QIdentityProxyModel::buddy(proxyIndex);
131}
132
133QModelIndex KExtraColumnsProxyModel::sibling(int row, int column, const QModelIndex &idx) const
134{
135 if (row == idx.row() && column == idx.column()) {
136 return idx;
137 }
138 return index(row, column, parent(idx));
139}
140
141QItemSelection KExtraColumnsProxyModel::mapSelectionToSource(const QItemSelection &selection) const
142{
143 QItemSelection sourceSelection;
144
145 if (!sourceModel()) {
146 return sourceSelection;
147 }
148
149 // mapToSource will give invalid index for our additional columns, so truncate the selection
150 // to the columns known by the source model
151 const int sourceColumnCount = sourceModel()->columnCount();
152 QItemSelection::const_iterator it = selection.constBegin();
153 const QItemSelection::const_iterator end = selection.constEnd();
154 for (; it != end; ++it) {
155 Q_ASSERT(it->model() == this);
156 QModelIndex topLeft = it->topLeft();
157 Q_ASSERT(topLeft.isValid());
158 Q_ASSERT(topLeft.model() == this);
159 topLeft = topLeft.sibling(topLeft.row(), 0);
160 QModelIndex bottomRight = it->bottomRight();
161 Q_ASSERT(bottomRight.isValid());
162 Q_ASSERT(bottomRight.model() == this);
163 if (bottomRight.column() >= sourceColumnCount) {
164 bottomRight = bottomRight.sibling(bottomRight.row(), sourceColumnCount - 1);
165 }
166 // This can lead to duplicate source indexes, so use merge().
167 const QItemSelectionRange range(mapToSource(topLeft), mapToSource(bottomRight));
168 QItemSelection newSelection;
169 newSelection << range;
170 sourceSelection.merge(newSelection, QItemSelectionModel::Select);
171 }
172
173 return sourceSelection;
174}
175
176int KExtraColumnsProxyModel::columnCount(const QModelIndex &parent) const
177{
178 Q_D(const KExtraColumnsProxyModel);
179 return QIdentityProxyModel::columnCount(parent) + d->m_extraHeaders.count();
180}
181
182QVariant KExtraColumnsProxyModel::data(const QModelIndex &index, int role) const
183{
184 Q_D(const KExtraColumnsProxyModel);
185 const int extraCol = extraColumnForProxyColumn(proxyColumn: index.column());
186 if (extraCol >= 0 && !d->m_extraHeaders.isEmpty()) {
187 return extraColumnData(index.parent(), index.row(), extraCol, role);
188 }
189 return sourceModel()->data(mapToSource(index), role);
190}
191
192bool KExtraColumnsProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
193{
194 Q_D(const KExtraColumnsProxyModel);
195 const int extraCol = extraColumnForProxyColumn(proxyColumn: index.column());
196 if (extraCol >= 0 && !d->m_extraHeaders.isEmpty()) {
197 return setExtraColumnData(index.parent(), index.row(), extraCol, value, role);
198 }
199 return sourceModel()->setData(mapToSource(index), value, role);
200}
201
202Qt::ItemFlags KExtraColumnsProxyModel::flags(const QModelIndex &index) const
203{
204 const int extraCol = extraColumnForProxyColumn(proxyColumn: index.column());
205 if (extraCol >= 0) {
206 // extra columns are readonly
207 return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
208 }
209 return sourceModel() != nullptr ? sourceModel()->flags(mapToSource(index)) : Qt::NoItemFlags;
210}
211
212bool KExtraColumnsProxyModel::hasChildren(const QModelIndex &index) const
213{
214 if (index.column() > 0) {
215 return false;
216 }
217 return QIdentityProxyModel::hasChildren(index);
218}
219
220QVariant KExtraColumnsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
221{
222 Q_D(const KExtraColumnsProxyModel);
223 if (orientation == Qt::Horizontal) {
224 const int extraCol = extraColumnForProxyColumn(proxyColumn: section);
225 if (extraCol >= 0) {
226 // Only text is supported, in headers for extra columns
227 if (role == Qt::DisplayRole) {
228 return d->m_extraHeaders.at(extraCol);
229 }
230 return QVariant();
231 }
232 }
233 return QIdentityProxyModel::headerData(section, orientation, role);
234}
235
236QModelIndex KExtraColumnsProxyModel::index(int row, int column, const QModelIndex &parent) const
237{
238 const int extraCol = extraColumnForProxyColumn(proxyColumn: column);
239 if (extraCol >= 0) {
240 // We store the internal pointer of the index for column 0 in the proxy index for extra columns.
241 // This will be useful in the parent method.
242 return createIndex(row, column, QIdentityProxyModel::index(row, 0, parent).internalPointer());
243 }
244 return QIdentityProxyModel::index(row, column, parent);
245}
246
247QModelIndex KExtraColumnsProxyModel::parent(const QModelIndex &child) const
248{
249 const int extraCol = extraColumnForProxyColumn(proxyColumn: child.column());
250 if (extraCol >= 0) {
251 // Create an index for column 0 and use that to get the parent.
252 const QModelIndex proxySibling = createIndex(child.row(), 0, child.internalPointer());
253 return QIdentityProxyModel::parent(proxySibling);
254 }
255 return QIdentityProxyModel::parent(child);
256}
257
258int KExtraColumnsProxyModel::extraColumnForProxyColumn(int proxyColumn) const
259{
260 if (sourceModel() != nullptr) {
261 const int sourceColumnCount = sourceModel()->columnCount();
262 if (proxyColumn >= sourceColumnCount) {
263 return proxyColumn - sourceColumnCount;
264 }
265 }
266 return -1;
267}
268
269int KExtraColumnsProxyModel::proxyColumnForExtraColumn(int extraColumn) const
270{
271 return sourceModel()->columnCount() + extraColumn;
272}
273
274void KExtraColumnsProxyModelPrivate::_ec_sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents,
275 QAbstractItemModel::LayoutChangeHint hint)
276{
277 Q_Q(KExtraColumnsProxyModel);
278
279 QList<QPersistentModelIndex> parents;
280 parents.reserve(sourceParents.size());
281 for (const QPersistentModelIndex &parent : sourceParents) {
282 if (!parent.isValid()) {
283 parents << QPersistentModelIndex();
284 continue;
285 }
286 const QModelIndex mappedParent = q->mapFromSource(parent);
287 Q_ASSERT(mappedParent.isValid());
288 parents << mappedParent;
289 }
290
291 Q_EMIT q->layoutAboutToBeChanged(parents, hint);
292
293 const QModelIndexList persistentIndexList = q->persistentIndexList();
294 layoutChangePersistentIndexes.reserve(persistentIndexList.size());
295 layoutChangeProxyColumns.reserve(persistentIndexList.size());
296
297 for (QModelIndex proxyPersistentIndex : persistentIndexList) {
298 proxyIndexes << proxyPersistentIndex;
299 Q_ASSERT(proxyPersistentIndex.isValid());
300 const int column = proxyPersistentIndex.column();
301 layoutChangeProxyColumns << column;
302 if (column >= q->sourceModel()->columnCount()) {
303 proxyPersistentIndex = proxyPersistentIndex.sibling(proxyPersistentIndex.row(), 0);
304 }
305 const QPersistentModelIndex srcPersistentIndex = q->mapToSource(proxyPersistentIndex);
306 Q_ASSERT(srcPersistentIndex.isValid());
307 layoutChangePersistentIndexes << srcPersistentIndex;
308 }
309}
310
311void KExtraColumnsProxyModelPrivate::_ec_sourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint)
312{
313 Q_Q(KExtraColumnsProxyModel);
314 for (int i = 0; i < proxyIndexes.size(); ++i) {
315 const QModelIndex proxyIdx = proxyIndexes.at(i);
316 QModelIndex newProxyIdx = q->mapFromSource(layoutChangePersistentIndexes.at(i));
317 if (proxyIdx.column() >= q->sourceModel()->columnCount()) {
318 newProxyIdx = newProxyIdx.sibling(newProxyIdx.row(), layoutChangeProxyColumns.at(i));
319 }
320 q->changePersistentIndex(proxyIdx, newProxyIdx);
321 }
322
323 layoutChangePersistentIndexes.clear();
324 layoutChangeProxyColumns.clear();
325 proxyIndexes.clear();
326
327 QList<QPersistentModelIndex> parents;
328 parents.reserve(sourceParents.size());
329 for (const QPersistentModelIndex &parent : sourceParents) {
330 if (!parent.isValid()) {
331 parents << QPersistentModelIndex();
332 continue;
333 }
334 const QModelIndex mappedParent = q->mapFromSource(parent);
335 Q_ASSERT(mappedParent.isValid());
336 parents << mappedParent;
337 }
338
339 Q_EMIT q->layoutChanged(parents, hint);
340}
341
342#include "moc_kextracolumnsproxymodel.cpp"
343

source code of kitemmodels/src/core/kextracolumnsproxymodel.cpp