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