1// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com>
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qconcatenatetablesproxymodel.h"
5#include <private/qabstractitemmodel_p.h>
6#include "qsize.h"
7#include "qmap.h"
8#include "qdebug.h"
9
10QT_BEGIN_NAMESPACE
11
12class QConcatenateTablesProxyModelPrivate : public QAbstractItemModelPrivate
13{
14 Q_DECLARE_PUBLIC(QConcatenateTablesProxyModel);
15
16public:
17 QConcatenateTablesProxyModelPrivate();
18
19 int computeRowsPrior(const QAbstractItemModel *sourceModel) const;
20
21 struct SourceModelForRowResult
22 {
23 SourceModelForRowResult() : sourceModel(nullptr), sourceRow(-1) {}
24 QAbstractItemModel *sourceModel;
25 int sourceRow;
26 };
27 SourceModelForRowResult sourceModelForRow(int row) const;
28
29 void slotRowsAboutToBeInserted(const QModelIndex &, int start, int end);
30 void slotRowsInserted(const QModelIndex &, int start, int end);
31 void slotRowsAboutToBeRemoved(const QModelIndex &, int start, int end);
32 void slotRowsRemoved(const QModelIndex &, int start, int end);
33 void slotRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
34 const QModelIndex &destinationParent, int destinationRow);
35 void slotRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
36 const QModelIndex &destinationParent, int destinationRow);
37 void slotColumnsAboutToBeInserted(const QModelIndex &parent, int start, int end);
38 void slotColumnsInserted(const QModelIndex &parent, int, int);
39 void slotColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
40 void slotColumnsRemoved(const QModelIndex &parent, int, int);
41 void slotColumnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
42 const QModelIndex &destinationParent, int destination);
43 void slotColumnsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
44 const QModelIndex &destinationParent, int destination);
45 void slotDataChanged(const QModelIndex &from, const QModelIndex &to, const QList<int> &roles);
46 void slotSourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint);
47 void slotSourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint);
48 void slotModelAboutToBeReset();
49 void slotModelReset();
50 int columnCountAfterChange(const QAbstractItemModel *model, int newCount) const;
51 int calculatedColumnCount() const;
52 void updateColumnCount();
53 bool mapDropCoordinatesToSource(int row, int column, const QModelIndex &parent,
54 int *sourceRow, int *sourceColumn, QModelIndex *sourceParent, QAbstractItemModel **sourceModel) const;
55
56 struct ModelInfo {
57 using ConnArray = std::array<QMetaObject::Connection, 17>;
58 ModelInfo(QAbstractItemModel *m, ConnArray &&con)
59 : model(m), connections(std::move(con)) {}
60 QAbstractItemModel *model = nullptr;
61 ConnArray connections;
62 };
63 QList<ModelInfo> m_models;
64
65 QList<ModelInfo>::const_iterator findSourceModel(const QAbstractItemModel *m) const
66 {
67 auto byModelPtr = [m](const auto &modInfo) { return modInfo.model == m; };
68 return std::find_if(first: m_models.cbegin(), last: m_models.cend(), pred: byModelPtr);
69 }
70
71 bool containsSourceModel(const QAbstractItemModel *m) const
72 { return findSourceModel(m) != m_models.cend(); }
73
74 int m_rowCount; // have to maintain it here since we can't compute during model destruction
75 int m_columnCount;
76
77 // for columns{AboutToBe,}{Inserted,Removed}
78 int m_newColumnCount;
79
80 // for layoutAboutToBeChanged/layoutChanged
81 QList<QPersistentModelIndex> layoutChangePersistentIndexes;
82 QList<QModelIndex> layoutChangeProxyIndexes;
83};
84
85QConcatenateTablesProxyModelPrivate::QConcatenateTablesProxyModelPrivate()
86 : m_rowCount(0),
87 m_columnCount(0),
88 m_newColumnCount(0)
89{
90}
91
92/*!
93 \since 5.13
94 \class QConcatenateTablesProxyModel
95 \inmodule QtCore
96 \brief The QConcatenateTablesProxyModel class proxies multiple source models, concatenating their rows.
97
98 \ingroup model-view
99
100 QConcatenateTablesProxyModel takes multiple source models and concatenates their rows.
101
102 In other words, the proxy will have all rows of the first source model,
103 followed by all rows of the second source model, and so on.
104
105 If the source models don't have the same number of columns, the proxy will only
106 have as many columns as the source model with the smallest number of columns.
107 Additional columns in other source models will simply be ignored.
108
109 Source models can be added and removed at runtime, and the column count is adjusted accordingly.
110
111 This proxy does not inherit from QAbstractProxyModel because it uses multiple source
112 models, rather than a single one.
113
114 Only flat models (lists and tables) are supported, tree models are not.
115
116 \sa QAbstractProxyModel, {Model/View Programming}, QIdentityProxyModel, QAbstractItemModel
117 */
118
119
120/*!
121 Constructs a concatenate-rows proxy model with the given \a parent.
122*/
123QConcatenateTablesProxyModel::QConcatenateTablesProxyModel(QObject *parent)
124 : QAbstractItemModel(*new QConcatenateTablesProxyModelPrivate, parent)
125{
126}
127
128/*!
129 Destroys this proxy model.
130*/
131QConcatenateTablesProxyModel::~QConcatenateTablesProxyModel()
132{
133}
134
135/*!
136 Returns the proxy index for a given \a sourceIndex, which can be from any of the source models.
137*/
138QModelIndex QConcatenateTablesProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
139{
140 Q_D(const QConcatenateTablesProxyModel);
141 if (!sourceIndex.isValid())
142 return QModelIndex();
143 const QAbstractItemModel *sourceModel = sourceIndex.model();
144 if (!d->containsSourceModel(m: sourceModel)) {
145 qWarning(msg: "QConcatenateTablesProxyModel: index from wrong model passed to mapFromSource");
146 Q_ASSERT(!"QConcatenateTablesProxyModel: index from wrong model passed to mapFromSource");
147 return QModelIndex();
148 }
149 if (sourceIndex.column() >= d->m_columnCount)
150 return QModelIndex();
151 int rowsPrior = d_func()->computeRowsPrior(sourceModel);
152 return createIndex(arow: rowsPrior + sourceIndex.row(), acolumn: sourceIndex.column(), adata: sourceIndex.internalPointer());
153}
154
155/*!
156 Returns the source index for a given \a proxyIndex.
157*/
158QModelIndex QConcatenateTablesProxyModel::mapToSource(const QModelIndex &proxyIndex) const
159{
160 Q_D(const QConcatenateTablesProxyModel);
161 Q_ASSERT(checkIndex(proxyIndex));
162 if (!proxyIndex.isValid())
163 return QModelIndex();
164 if (proxyIndex.model() != this) {
165 qWarning(msg: "QConcatenateTablesProxyModel: index from wrong model passed to mapToSource");
166 Q_ASSERT(!"QConcatenateTablesProxyModel: index from wrong model passed to mapToSource");
167 return QModelIndex();
168 }
169 const int row = proxyIndex.row();
170 const auto result = d->sourceModelForRow(row);
171 if (!result.sourceModel)
172 return QModelIndex();
173 return result.sourceModel->index(row: result.sourceRow, column: proxyIndex.column());
174}
175
176/*!
177 \reimp
178*/
179QVariant QConcatenateTablesProxyModel::data(const QModelIndex &index, int role) const
180{
181 const QModelIndex sourceIndex = mapToSource(proxyIndex: index);
182 Q_ASSERT(checkIndex(index, CheckIndexOption::IndexIsValid));
183 if (!sourceIndex.isValid())
184 return QVariant();
185 return sourceIndex.data(arole: role);
186}
187
188/*!
189 \reimp
190*/
191bool QConcatenateTablesProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
192{
193 Q_ASSERT(checkIndex(index, CheckIndexOption::IndexIsValid));
194 const QModelIndex sourceIndex = mapToSource(proxyIndex: index);
195 Q_ASSERT(sourceIndex.isValid());
196 const auto sourceModel = const_cast<QAbstractItemModel *>(sourceIndex.model());
197 return sourceModel->setData(index: sourceIndex, value, role);
198}
199
200/*!
201 \reimp
202*/
203QMap<int, QVariant> QConcatenateTablesProxyModel::itemData(const QModelIndex &proxyIndex) const
204{
205 Q_ASSERT(checkIndex(proxyIndex));
206 const QModelIndex sourceIndex = mapToSource(proxyIndex);
207 Q_ASSERT(sourceIndex.isValid());
208 return sourceIndex.model()->itemData(index: sourceIndex);
209}
210
211/*!
212 \reimp
213*/
214bool QConcatenateTablesProxyModel::setItemData(const QModelIndex &proxyIndex, const QMap<int, QVariant> &roles)
215{
216 Q_ASSERT(checkIndex(proxyIndex));
217 const QModelIndex sourceIndex = mapToSource(proxyIndex);
218 Q_ASSERT(sourceIndex.isValid());
219 const auto sourceModel = const_cast<QAbstractItemModel *>(sourceIndex.model());
220 return sourceModel->setItemData(index: sourceIndex, roles);
221}
222
223/*!
224 Returns the flags for the given index.
225 If the \a index is valid, the flags come from the source model for this \a index.
226 If the \a index is invalid (as used to determine if dropping onto an empty area
227 in the view is allowed, for instance), the flags from the first model are returned.
228*/
229Qt::ItemFlags QConcatenateTablesProxyModel::flags(const QModelIndex &index) const
230{
231 Q_D(const QConcatenateTablesProxyModel);
232 if (d->m_models.isEmpty())
233 return Qt::NoItemFlags;
234 Q_ASSERT(checkIndex(index));
235 if (!index.isValid())
236 return d->m_models.at(i: 0).model->flags(index);
237 const QModelIndex sourceIndex = mapToSource(proxyIndex: index);
238 Q_ASSERT(sourceIndex.isValid());
239 return sourceIndex.model()->flags(index: sourceIndex);
240}
241
242/*!
243 This method returns the horizontal header data for the first source model,
244 and the vertical header data for the source model corresponding to each row.
245 \reimp
246*/
247QVariant QConcatenateTablesProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
248{
249 Q_D(const QConcatenateTablesProxyModel);
250 if (d->m_models.isEmpty())
251 return QVariant();
252 switch (orientation) {
253 case Qt::Horizontal:
254 return d->m_models.at(i: 0).model->headerData(section, orientation, role);
255 case Qt::Vertical: {
256 const auto result = d->sourceModelForRow(row: section);
257 Q_ASSERT(result.sourceModel);
258 return result.sourceModel->headerData(section: result.sourceRow, orientation, role);
259 }
260 }
261 return QVariant();
262}
263
264/*!
265 This method returns the column count of the source model with the smallest number of columns.
266 \reimp
267*/
268int QConcatenateTablesProxyModel::columnCount(const QModelIndex &parent) const
269{
270 Q_D(const QConcatenateTablesProxyModel);
271 if (parent.isValid())
272 return 0; // flat model
273 return d->m_columnCount;
274}
275
276/*!
277 \reimp
278*/
279QModelIndex QConcatenateTablesProxyModel::index(int row, int column, const QModelIndex &parent) const
280{
281 Q_D(const QConcatenateTablesProxyModel);
282 Q_ASSERT(hasIndex(row, column, parent));
283 if (!hasIndex(row, column, parent))
284 return QModelIndex();
285 Q_ASSERT(checkIndex(parent, QAbstractItemModel::CheckIndexOption::ParentIsInvalid)); // flat model
286 const auto result = d->sourceModelForRow(row);
287 Q_ASSERT(result.sourceModel);
288 return mapFromSource(sourceIndex: result.sourceModel->index(row: result.sourceRow, column));
289}
290
291/*!
292 \reimp
293*/
294QModelIndex QConcatenateTablesProxyModel::parent(const QModelIndex &index) const
295{
296 Q_UNUSED(index);
297 return QModelIndex(); // flat model, no hierarchy
298}
299
300/*!
301 \reimp
302*/
303int QConcatenateTablesProxyModel::rowCount(const QModelIndex &parent) const
304{
305 Q_D(const QConcatenateTablesProxyModel);
306 if (parent.isValid())
307 return 0; // flat model
308 return d->m_rowCount;
309}
310
311/*!
312 This method returns the mime types for the first source model.
313 \reimp
314*/
315QStringList QConcatenateTablesProxyModel::mimeTypes() const
316{
317 Q_D(const QConcatenateTablesProxyModel);
318 if (d->m_models.isEmpty())
319 return QStringList();
320 return d->m_models.at(i: 0).model->mimeTypes();
321}
322
323/*!
324 The call is forwarded to the source model of the first index in the list of \a indexes.
325
326 Important: please note that this proxy only supports dragging a single row.
327 It will assert if called with indexes from multiple rows, because dragging rows that
328 might come from different source models cannot be implemented generically by this proxy model.
329 Each piece of data in the QMimeData needs to be merged, which is data-type-specific.
330 Reimplement this method in a subclass if you want to support dragging multiple rows.
331
332 \reimp
333*/
334QMimeData *QConcatenateTablesProxyModel::mimeData(const QModelIndexList &indexes) const
335{
336 Q_D(const QConcatenateTablesProxyModel);
337 if (indexes.isEmpty())
338 return nullptr;
339 const QModelIndex firstIndex = indexes.first();
340 Q_ASSERT(checkIndex(firstIndex, CheckIndexOption::IndexIsValid));
341 const auto result = d->sourceModelForRow(row: firstIndex.row());
342 QModelIndexList sourceIndexes;
343 sourceIndexes.reserve(size: indexes.size());
344 for (const QModelIndex &index : indexes) {
345 const QModelIndex sourceIndex = mapToSource(proxyIndex: index);
346 Q_ASSERT(sourceIndex.model() == result.sourceModel); // see documentation above
347 sourceIndexes.append(t: sourceIndex);
348 }
349 return result.sourceModel->mimeData(indexes: sourceIndexes);
350}
351
352
353bool QConcatenateTablesProxyModelPrivate::mapDropCoordinatesToSource(int row, int column, const QModelIndex &parent,
354 int *sourceRow, int *sourceColumn, QModelIndex *sourceParent, QAbstractItemModel **sourceModel) const
355{
356 Q_Q(const QConcatenateTablesProxyModel);
357 *sourceColumn = column;
358 if (!parent.isValid()) {
359 // Drop after the last item
360 if (row == -1 || row == m_rowCount) {
361 *sourceRow = -1;
362 *sourceModel = m_models.constLast().model;
363 return true;
364 }
365 // Drop between toplevel items
366 const auto result = sourceModelForRow(row);
367 Q_ASSERT(result.sourceModel);
368 *sourceRow = result.sourceRow;
369 *sourceModel = result.sourceModel;
370 return true;
371 } else {
372 if (row > -1)
373 return false; // flat model, no dropping as new children of items
374 // Drop onto item
375 const int targetRow = parent.row();
376 const auto result = sourceModelForRow(row: targetRow);
377 Q_ASSERT(result.sourceModel);
378 const QModelIndex sourceIndex = q->mapToSource(proxyIndex: parent);
379 *sourceRow = -1;
380 *sourceParent = sourceIndex;
381 *sourceModel = result.sourceModel;
382 return true;
383 }
384}
385
386/*!
387 \reimp
388*/
389bool QConcatenateTablesProxyModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
390{
391 Q_D(const QConcatenateTablesProxyModel);
392 if (d->m_models.isEmpty())
393 return false;
394
395 int sourceRow, sourceColumn;
396 QModelIndex sourceParent;
397 QAbstractItemModel *sourceModel;
398 if (!d->mapDropCoordinatesToSource(row, column, parent, sourceRow: &sourceRow, sourceColumn: &sourceColumn, sourceParent: &sourceParent, sourceModel: &sourceModel))
399 return false;
400 return sourceModel->canDropMimeData(data, action, row: sourceRow, column: sourceColumn, parent: sourceParent);
401}
402
403/*!
404 QConcatenateTablesProxyModel handles dropping onto an item, between items, and after the last item.
405 In all cases the call is forwarded to the underlying source model.
406 When dropping onto an item, the source model for this item is called.
407 When dropping between items, the source model immediately below the drop position is called.
408 When dropping after the last item, the last source model is called.
409
410 \reimp
411*/
412bool QConcatenateTablesProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
413{
414 Q_D(const QConcatenateTablesProxyModel);
415 if (d->m_models.isEmpty())
416 return false;
417 int sourceRow, sourceColumn;
418 QModelIndex sourceParent;
419 QAbstractItemModel *sourceModel;
420 if (!d->mapDropCoordinatesToSource(row, column, parent, sourceRow: &sourceRow, sourceColumn: &sourceColumn, sourceParent: &sourceParent, sourceModel: &sourceModel))
421 return false;
422
423 return sourceModel->dropMimeData(data, action, row: sourceRow, column: sourceColumn, parent: sourceParent);
424}
425
426/*!
427 \reimp
428*/
429QSize QConcatenateTablesProxyModel::span(const QModelIndex &index) const
430{
431 Q_D(const QConcatenateTablesProxyModel);
432 Q_ASSERT(checkIndex(index));
433 if (d->m_models.isEmpty() || !index.isValid())
434 return QSize();
435 const QModelIndex sourceIndex = mapToSource(proxyIndex: index);
436 Q_ASSERT(sourceIndex.isValid());
437 return sourceIndex.model()->span(index: sourceIndex);
438}
439
440/*!
441 Returns a list of models that were added as source models for this proxy model.
442
443 \since 5.15
444*/
445QList<QAbstractItemModel *> QConcatenateTablesProxyModel::sourceModels() const
446{
447 Q_D(const QConcatenateTablesProxyModel);
448 QList<QAbstractItemModel *> ret;
449 ret.reserve(size: d->m_models.size());
450 for (const auto &info : d->m_models)
451 ret.push_back(t: info.model);
452 return ret;
453}
454
455/*!
456 Adds a source model \a sourceModel, below all previously added source models.
457
458 The ownership of \a sourceModel is not affected by this.
459
460 The same source model cannot be added more than once.
461 */
462void QConcatenateTablesProxyModel::addSourceModel(QAbstractItemModel *sourceModel)
463{
464 Q_D(QConcatenateTablesProxyModel);
465 Q_ASSERT(sourceModel);
466 Q_ASSERT(!d->containsSourceModel(sourceModel));
467
468 const int newRows = sourceModel->rowCount();
469 if (newRows > 0)
470 beginInsertRows(parent: QModelIndex(), first: d->m_rowCount, last: d->m_rowCount + newRows - 1);
471 d->m_rowCount += newRows;
472 d->m_models.emplace_back(args&: sourceModel, args: std::array{
473 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::dataChanged,
474 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotDataChanged),
475 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::rowsInserted,
476 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotRowsInserted),
477 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::rowsRemoved,
478 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotRowsRemoved),
479 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::rowsAboutToBeInserted,
480 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotRowsAboutToBeInserted),
481 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::rowsAboutToBeRemoved,
482 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotRowsAboutToBeRemoved),
483 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::rowsMoved,
484 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotRowsMoved),
485 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::rowsAboutToBeMoved,
486 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotRowsAboutToBeMoved),
487
488 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::columnsInserted,
489 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotColumnsInserted),
490 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::columnsRemoved,
491 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotColumnsRemoved),
492 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::columnsAboutToBeInserted,
493 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotColumnsAboutToBeInserted),
494 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::columnsAboutToBeRemoved,
495 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotColumnsAboutToBeRemoved),
496 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::columnsMoved,
497 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotColumnsMoved),
498 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::columnsAboutToBeMoved,
499 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotColumnsAboutToBeMoved),
500
501 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::layoutAboutToBeChanged,
502 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotSourceLayoutAboutToBeChanged),
503 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::layoutChanged,
504 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotSourceLayoutChanged),
505 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::modelAboutToBeReset,
506 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotModelAboutToBeReset),
507 QObjectPrivate::connect(sender: sourceModel, signal: &QAbstractItemModel::modelReset,
508 receiverPrivate: d, slot: &QConcatenateTablesProxyModelPrivate::slotModelReset),
509 });
510 if (newRows > 0)
511 endInsertRows();
512
513 d->updateColumnCount();
514}
515
516/*!
517 Removes the source model \a sourceModel, which was previously added to this proxy.
518
519 The ownership of \a sourceModel is not affected by this.
520*/
521void QConcatenateTablesProxyModel::removeSourceModel(QAbstractItemModel *sourceModel)
522{
523 Q_D(QConcatenateTablesProxyModel);
524
525 auto it = d->findSourceModel(m: sourceModel);
526 Q_ASSERT(it != d->m_models.cend());
527 for (auto &c : it->connections)
528 disconnect(c);
529
530 const int rowsRemoved = sourceModel->rowCount();
531 const int rowsPrior = d->computeRowsPrior(sourceModel); // location of removed section
532
533 if (rowsRemoved > 0)
534 beginRemoveRows(parent: QModelIndex(), first: rowsPrior, last: rowsPrior + rowsRemoved - 1);
535 d->m_models.erase(pos: it);
536 d->m_rowCount -= rowsRemoved;
537 if (rowsRemoved > 0)
538 endRemoveRows();
539
540 d->updateColumnCount();
541}
542
543void QConcatenateTablesProxyModelPrivate::slotRowsAboutToBeInserted(const QModelIndex &parent,
544 int start, int end)
545{
546 Q_Q(QConcatenateTablesProxyModel);
547 if (parent.isValid()) // not supported, the proxy is a flat model
548 return;
549 const QAbstractItemModel * const model = static_cast<QAbstractItemModel *>(q->sender());
550 const int rowsPrior = computeRowsPrior(sourceModel: model);
551 q->beginInsertRows(parent: QModelIndex(), first: rowsPrior + start, last: rowsPrior + end);
552}
553
554void QConcatenateTablesProxyModelPrivate::slotRowsInserted(const QModelIndex &parent, int start,
555 int end)
556{
557 Q_Q(QConcatenateTablesProxyModel);
558 if (parent.isValid()) // flat model
559 return;
560 m_rowCount += end - start + 1;
561 q->endInsertRows();
562}
563
564void QConcatenateTablesProxyModelPrivate::slotRowsAboutToBeRemoved(const QModelIndex &parent,
565 int start, int end)
566{
567 Q_Q(QConcatenateTablesProxyModel);
568 if (parent.isValid()) // flat model
569 return;
570 const QAbstractItemModel * const model = static_cast<QAbstractItemModel *>(q->sender());
571 const int rowsPrior = computeRowsPrior(sourceModel: model);
572 q->beginRemoveRows(parent: QModelIndex(), first: rowsPrior + start, last: rowsPrior + end);
573}
574
575void QConcatenateTablesProxyModelPrivate::slotRowsRemoved(const QModelIndex &parent, int start, int end)
576{
577 Q_Q(QConcatenateTablesProxyModel);
578 if (parent.isValid()) // flat model
579 return;
580 m_rowCount -= end - start + 1;
581 q->endRemoveRows();
582}
583
584void QConcatenateTablesProxyModelPrivate::slotRowsAboutToBeMoved(
585 const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
586 const QModelIndex &destinationParent, int destinationRow)
587{
588 Q_Q(QConcatenateTablesProxyModel);
589 if (sourceParent.isValid() || destinationParent.isValid())
590 return;
591 const QAbstractItemModel *const model = static_cast<QAbstractItemModel *>(q->sender());
592 const int rowsPrior = computeRowsPrior(sourceModel: model);
593 q->beginMoveRows(sourceParent, sourceFirst: rowsPrior + sourceStart, sourceLast: rowsPrior + sourceEnd,
594 destinationParent, destinationRow: rowsPrior + destinationRow);
595}
596
597void QConcatenateTablesProxyModelPrivate::slotRowsMoved(const QModelIndex &sourceParent,
598 int sourceStart, int sourceEnd,
599 const QModelIndex &destinationParent,
600 int destinationRow)
601{
602 Q_Q(QConcatenateTablesProxyModel);
603 Q_UNUSED(sourceStart)
604 Q_UNUSED(sourceEnd)
605 Q_UNUSED(destinationRow)
606 if (sourceParent.isValid() || destinationParent.isValid())
607 return;
608 q->endMoveRows();
609}
610
611void QConcatenateTablesProxyModelPrivate::slotColumnsAboutToBeInserted(const QModelIndex &parent,
612 int start, int end)
613{
614 Q_Q(QConcatenateTablesProxyModel);
615 if (parent.isValid()) // flat model
616 return;
617 const QAbstractItemModel * const model = static_cast<QAbstractItemModel *>(q->sender());
618 const int oldColCount = model->columnCount();
619 const int newColCount = columnCountAfterChange(model, newCount: oldColCount + end - start + 1);
620 Q_ASSERT(newColCount >= oldColCount);
621 if (newColCount > oldColCount)
622 // If the underlying models have a different number of columns (example: 2 and 3), inserting 2 columns in
623 // the first model leads to inserting only one column in the proxy, since qMin(2+2,3) == 3.
624 q->beginInsertColumns(parent: QModelIndex(), first: start, last: qMin(a: end, b: start + newColCount - oldColCount - 1));
625 m_newColumnCount = newColCount;
626}
627
628void QConcatenateTablesProxyModelPrivate::slotColumnsInserted(const QModelIndex &parent, int start,
629 int end)
630{
631 Q_UNUSED(start);
632 Q_UNUSED(end);
633 Q_Q(QConcatenateTablesProxyModel);
634 if (parent.isValid()) // flat model
635 return;
636 if (m_newColumnCount != m_columnCount) {
637 m_columnCount = m_newColumnCount;
638 q->endInsertColumns();
639 }
640}
641
642void QConcatenateTablesProxyModelPrivate::slotColumnsAboutToBeRemoved(const QModelIndex &parent,
643 int start, int end)
644{
645 Q_Q(QConcatenateTablesProxyModel);
646 if (parent.isValid()) // flat model
647 return;
648 const QAbstractItemModel * const model = static_cast<QAbstractItemModel *>(q->sender());
649 const int oldColCount = model->columnCount();
650 const int newColCount = columnCountAfterChange(model, newCount: oldColCount - (end - start + 1));
651 Q_ASSERT(newColCount <= oldColCount);
652 if (newColCount < oldColCount)
653 q->beginRemoveColumns(parent: QModelIndex(), first: start, last: qMax(a: end, b: start + oldColCount - newColCount - 1));
654 m_newColumnCount = newColCount;
655}
656
657void QConcatenateTablesProxyModelPrivate::slotColumnsRemoved(const QModelIndex &parent, int start,
658 int end)
659{
660 Q_Q(QConcatenateTablesProxyModel);
661 Q_UNUSED(start);
662 Q_UNUSED(end);
663 if (parent.isValid()) // flat model
664 return;
665 if (m_newColumnCount != m_columnCount) {
666 m_columnCount = m_newColumnCount;
667 q->endRemoveColumns();
668 }
669}
670
671void QConcatenateTablesProxyModelPrivate::slotColumnsAboutToBeMoved(
672 const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
673 const QModelIndex &destinationParent, int destination)
674{
675 Q_UNUSED(sourceStart)
676 Q_UNUSED(sourceEnd)
677 Q_UNUSED(destination)
678 if (sourceParent.isValid() || destinationParent.isValid())
679 return;
680 slotSourceLayoutAboutToBeChanged(sourceParents: {}, hint: QAbstractItemModel::HorizontalSortHint);
681}
682
683void QConcatenateTablesProxyModelPrivate::slotColumnsMoved(const QModelIndex &sourceParent,
684 int sourceStart, int sourceEnd,
685 const QModelIndex &destinationParent,
686 int destination)
687{
688 Q_UNUSED(sourceStart)
689 Q_UNUSED(sourceEnd)
690 Q_UNUSED(destination)
691 if (sourceParent.isValid() || destinationParent.isValid())
692 return;
693 slotSourceLayoutChanged(sourceParents: {}, hint: QAbstractItemModel::HorizontalSortHint);
694}
695
696void QConcatenateTablesProxyModelPrivate::slotDataChanged(const QModelIndex &from,
697 const QModelIndex &to,
698 const QList<int> &roles)
699{
700 Q_Q(QConcatenateTablesProxyModel);
701 Q_ASSERT(from.isValid());
702 Q_ASSERT(to.isValid());
703 if (from.column() >= m_columnCount)
704 return;
705 QModelIndex adjustedTo = to;
706 if (to.column() >= m_columnCount)
707 adjustedTo = to.siblingAtColumn(acolumn: m_columnCount - 1);
708 const QModelIndex myFrom = q->mapFromSource(sourceIndex: from);
709 Q_ASSERT(q->checkIndex(myFrom, QAbstractItemModel::CheckIndexOption::IndexIsValid));
710 const QModelIndex myTo = q->mapFromSource(sourceIndex: adjustedTo);
711 Q_ASSERT(q->checkIndex(myTo, QAbstractItemModel::CheckIndexOption::IndexIsValid));
712 emit q->dataChanged(topLeft: myFrom, bottomRight: myTo, roles);
713}
714
715void QConcatenateTablesProxyModelPrivate::slotSourceLayoutAboutToBeChanged(
716 const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint)
717{
718 Q_Q(QConcatenateTablesProxyModel);
719
720 if (!sourceParents.isEmpty() && !sourceParents.contains(t: QModelIndex()))
721 return;
722
723 emit q->layoutAboutToBeChanged(parents: {}, hint);
724
725 const QModelIndexList persistentIndexList = q->persistentIndexList();
726 layoutChangePersistentIndexes.reserve(size: persistentIndexList.size());
727 layoutChangeProxyIndexes.reserve(size: persistentIndexList.size());
728
729 for (const QModelIndex &proxyPersistentIndex : persistentIndexList) {
730 layoutChangeProxyIndexes.append(t: proxyPersistentIndex);
731 Q_ASSERT(proxyPersistentIndex.isValid());
732 const QPersistentModelIndex srcPersistentIndex = q->mapToSource(proxyIndex: proxyPersistentIndex);
733 Q_ASSERT(srcPersistentIndex.isValid());
734 layoutChangePersistentIndexes << srcPersistentIndex;
735 }
736}
737
738void QConcatenateTablesProxyModelPrivate::slotSourceLayoutChanged(
739 const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint)
740{
741 Q_Q(QConcatenateTablesProxyModel);
742 if (!sourceParents.isEmpty() && !sourceParents.contains(t: QModelIndex()))
743 return;
744 for (int i = 0; i < layoutChangeProxyIndexes.size(); ++i) {
745 const QModelIndex proxyIdx = layoutChangeProxyIndexes.at(i);
746 const QModelIndex newProxyIdx = q->mapFromSource(sourceIndex: layoutChangePersistentIndexes.at(i));
747 q->changePersistentIndex(from: proxyIdx, to: newProxyIdx);
748 }
749
750 layoutChangePersistentIndexes.clear();
751 layoutChangeProxyIndexes.clear();
752
753 emit q->layoutChanged(parents: {}, hint);
754}
755
756void QConcatenateTablesProxyModelPrivate::slotModelAboutToBeReset()
757{
758 Q_Q(QConcatenateTablesProxyModel);
759 Q_ASSERT(containsSourceModel(static_cast<QAbstractItemModel *>(q->sender())));
760 q->beginResetModel();
761 // A reset might reduce both rowCount and columnCount, and we can't notify of both at the same time,
762 // and notifying of one after the other leaves an intermediary invalid situation.
763 // So the only safe choice is to forward it as a full reset.
764}
765
766void QConcatenateTablesProxyModelPrivate::slotModelReset()
767{
768 Q_Q(QConcatenateTablesProxyModel);
769 Q_ASSERT(containsSourceModel(static_cast<QAbstractItemModel *>(q->sender())));
770 m_columnCount = calculatedColumnCount();
771 m_rowCount = computeRowsPrior(sourceModel: nullptr);
772 q->endResetModel();
773}
774
775int QConcatenateTablesProxyModelPrivate::calculatedColumnCount() const
776{
777 if (m_models.isEmpty())
778 return 0;
779
780 auto byColumnCount = [](const auto &a, const auto &b) {
781 return a.model->columnCount() < b.model->columnCount();
782 };
783 const auto it = std::min_element(first: m_models.begin(), last: m_models.end(), comp: byColumnCount);
784 return it->model->columnCount();
785}
786
787void QConcatenateTablesProxyModelPrivate::updateColumnCount()
788{
789 Q_Q(QConcatenateTablesProxyModel);
790 const int newColumnCount = calculatedColumnCount();
791 const int columnDiff = newColumnCount - m_columnCount;
792 if (columnDiff > 0) {
793 q->beginInsertColumns(parent: QModelIndex(), first: m_columnCount, last: m_columnCount + columnDiff - 1);
794 m_columnCount = newColumnCount;
795 q->endInsertColumns();
796 } else if (columnDiff < 0) {
797 const int lastColumn = m_columnCount - 1;
798 q->beginRemoveColumns(parent: QModelIndex(), first: lastColumn + columnDiff + 1, last: lastColumn);
799 m_columnCount = newColumnCount;
800 q->endRemoveColumns();
801 }
802}
803
804int QConcatenateTablesProxyModelPrivate::columnCountAfterChange(const QAbstractItemModel *model, int newCount) const
805{
806 int newColumnCount = 0;
807 for (qsizetype i = 0; i < m_models.size(); ++i) {
808 const QAbstractItemModel *mod = m_models.at(i).model;
809 const int colCount = mod == model ? newCount : mod->columnCount();
810 if (i == 0)
811 newColumnCount = colCount;
812 else
813 newColumnCount = qMin(a: colCount, b: newColumnCount);
814 }
815 return newColumnCount;
816}
817
818int QConcatenateTablesProxyModelPrivate::computeRowsPrior(const QAbstractItemModel *sourceModel) const
819{
820 int rowsPrior = 0;
821 for (const auto &[model, _] : m_models) {
822 if (model == sourceModel)
823 break;
824 rowsPrior += model->rowCount();
825 }
826 return rowsPrior;
827}
828
829QConcatenateTablesProxyModelPrivate::SourceModelForRowResult QConcatenateTablesProxyModelPrivate::sourceModelForRow(int row) const
830{
831 QConcatenateTablesProxyModelPrivate::SourceModelForRowResult result;
832 int rowCount = 0;
833 for (const auto &[model, _] : m_models) {
834 const int subRowCount = model->rowCount();
835 if (rowCount + subRowCount > row) {
836 result.sourceModel = model;
837 break;
838 }
839 rowCount += subRowCount;
840 }
841 result.sourceRow = row - rowCount;
842 return result;
843}
844
845QT_END_NAMESPACE
846
847#include "moc_qconcatenatetablesproxymodel.cpp"
848

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/corelib/itemmodels/qconcatenatetablesproxymodel.cpp