KItemModels

kextracolumnsproxymodel.cpp
1 /*
2  SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
3  SPDX-FileContributor: David Faure <[email protected]>
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 
13 class KExtraColumnsProxyModelPrivate
14 {
15  Q_DECLARE_PUBLIC(KExtraColumnsProxyModel)
16  KExtraColumnsProxyModel *const q_ptr;
17 
18 public:
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  QVector<QString> m_extraHeaders;
29 
30  // for layoutAboutToBeChanged/layoutChanged
31  QVector<QPersistentModelIndex> layoutChangePersistentIndexes;
32  QVector<int> layoutChangeProxyColumns;
33  QModelIndexList proxyIndexes;
34 };
35 
37  : QIdentityProxyModel(parent)
38  , d_ptr(new KExtraColumnsProxyModelPrivate(this))
39 {
40 }
41 
43 {
44 }
45 
47 {
49  d->m_extraHeaders.append(header);
50 }
51 
53 {
55  d->m_extraHeaders.remove(idx);
56 }
57 
58 bool 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 
68 void KExtraColumnsProxyModel::extraColumnDataChanged(const QModelIndex &parent, int row, int extraColumn, const QVector<int> &roles)
69 {
70  const QModelIndex idx = index(row, proxyColumnForExtraColumn(extraColumn), parent);
71  Q_EMIT dataChanged(idx, idx, roles);
72 }
73 
75 {
76  if (sourceModel()) {
79  this,
80  SLOT(_ec_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
83  this,
85  }
86 
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,
94  this,
95  SLOT(_q_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
96  disconnect(model,
98  this,
100  connect(model,
102  this,
103  SLOT(_ec_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
104  connect(model,
106  this,
108  }
109 }
110 
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 
125 {
126  const int column = proxyIndex.column();
127  if (column >= sourceModel()->columnCount()) {
128  return proxyIndex;
129  }
130  return QIdentityProxyModel::buddy(proxyIndex);
131 }
132 
133 QModelIndex 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 
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();
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 
177 {
179  return QIdentityProxyModel::columnCount(parent) + d->m_extraHeaders.count();
180 }
181 
183 {
185  const int extraCol = extraColumnForProxyColumn(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 
192 bool KExtraColumnsProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
193 {
195  const int extraCol = extraColumnForProxyColumn(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 
203 {
204  const int extraCol = extraColumnForProxyColumn(index.column());
205  if (extraCol >= 0) {
206  // extra columns are readonly
208  }
209  return sourceModel() != nullptr ? sourceModel()->flags(mapToSource(index)) : Qt::NoItemFlags;
210 }
211 
213 {
214  if (index.column() > 0) {
215  return false;
216  }
217  return QIdentityProxyModel::hasChildren(index);
218 }
219 
220 QVariant KExtraColumnsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
221 {
223  if (orientation == Qt::Horizontal) {
224  const int extraCol = extraColumnForProxyColumn(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 
237 {
238  const int extraCol = extraColumnForProxyColumn(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 
248 {
249  const int extraCol = extraColumnForProxyColumn(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 
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 
270 {
271  return sourceModel()->columnCount() + extraColumn;
272 }
273 
274 void KExtraColumnsProxyModelPrivate::_ec_sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents,
276 {
278 
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 
311 void KExtraColumnsProxyModelPrivate::_ec_sourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint)
312 {
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 
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"
void layoutChanged(const QList< QPersistentModelIndex > &parents, QAbstractItemModel::LayoutChangeHint hint)
virtual void setSourceModel(QAbstractItemModel *newSourceModel) override
bool hasChildren(const QModelIndex &index) const override
int proxyColumnForExtraColumn(int extraColumn) const
Returns the proxy column number for a given extra column number (starting at 0).
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const const override
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const const override
void reserve(int alloc)
void setSourceModel(QAbstractItemModel *model) override
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
QItemSelection mapSelectionToSource(const QItemSelection &selection) const override
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QModelIndexList persistentIndexList() const const
void appendColumn(const QString &header=QString())
Appends an extra column.
int size() const const
void layoutAboutToBeChanged(const QList< QPersistentModelIndex > &parents, QAbstractItemModel::LayoutChangeHint hint)
KExtraColumnsProxyModel(QObject *parent=nullptr)
Base class constructor.
bool isValid() const const
virtual bool hasChildren(const QModelIndex &parent) const const override
bool isValid() const const
virtual QModelIndex buddy(const QModelIndex &index) const const override
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
QModelIndex sibling(int row, int column, const QModelIndex &idx) const override
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
DisplayRole
int row() const const
Qt::ItemFlags flags(const QModelIndex &index) const override
void * internalPointer() const const
virtual QVariant data(const QModelIndex &index, int role) const const =0
QModelIndex parent() const const
QModelIndex createIndex(int row, int column, void *ptr) const const
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
This proxy appends extra columns (after all existing columns).
~KExtraColumnsProxyModel() override
Destructor.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
int columnCount(const QModelIndex &parent=QModelIndex()) const override
QAbstractItemModel * sourceModel() const const
int extraColumnForProxyColumn(int proxyColumn) const
Returns the extra column number (0, 1, ...) for a given column number of the proxymodel.
const QAbstractItemModel * model() const const
void merge(const QItemSelection &other, QItemSelectionModel::SelectionFlags command)
virtual QVariant extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role=Qt::DisplayRole) const =0
This method is called by data() for extra columns.
QModelIndex sibling(int row, int column) const const
virtual int columnCount(const QModelIndex &parent) const const =0
Q_D(Todo)
int column() const const
virtual int columnCount(const QModelIndex &parent) const const override
virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
virtual Qt::ItemFlags flags(const QModelIndex &index) const const
Orientation
void extraColumnDataChanged(const QModelIndex &parent, int row, int extraColumn, const QVector< int > &roles)
This method can be called by your derived class when the data in an extra column has changed...
QList::const_iterator constEnd() const const
virtual bool setExtraColumnData(const QModelIndex &parent, int row, int extraColumn, const QVariant &data, int role=Qt::EditRole)
This method is called by setData() for extra columns.
QList::const_iterator constBegin() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
QModelIndex buddy(const QModelIndex &index) const override
void removeExtraColumn(int idx)
Removes an extra column.
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
Q_EMITQ_EMIT
typedef ItemFlags
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Jan 17 2022 22:45:14 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.