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

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