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
5#include <private/qabstractitemmodel_p.h>
6#include "qsize.h"
7#include "qmap.h"
8#include "qdebug.h"
9
11
13{
14 Q_DECLARE_PUBLIC(QConcatenateTablesProxyModel);
15
16public:
18
19 int computeRowsPrior(const QAbstractItemModel *sourceModel) const;
20
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);
50 int columnCountAfterChange(const QAbstractItemModel *model, int newCount) const;
53 bool mapDropCoordinatesToSource(int row, int column, const QModelIndex &parent,
54 int *sourceRow, int *sourceColumn, QModelIndex *sourceParent, QAbstractItemModel **sourceModel) const;
55
56 struct ModelInfo {
58 ModelInfo(QAbstractItemModel *m, ConnArray &&con)
59 : model(m), connections(std::move(con)) {}
62 };
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(m_models.cbegin(), m_models.cend(), 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
76
77 // for columns{AboutToBe,}{Inserted,Removed}
79
80 // for layoutAboutToBeChanged/layoutChanged
83};
84
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(sourceModel)) {
145 qWarning("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(rowsPrior + sourceIndex.row(), sourceIndex.column(), 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("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(result.sourceRow, proxyIndex.column());
174}
175
176/*!
177 \reimp
178*/
179QVariant QConcatenateTablesProxyModel::data(const QModelIndex &index, int role) const
180{
181 const QModelIndex sourceIndex = mapToSource(index);
182 Q_ASSERT(checkIndex(index, CheckIndexOption::IndexIsValid));
183 if (!sourceIndex.isValid())
184 return QVariant();
185 return sourceIndex.data(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(index);
195 Q_ASSERT(sourceIndex.isValid());
196 const auto sourceModel = const_cast<QAbstractItemModel *>(sourceIndex.model());
197 return sourceModel->setData(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(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(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(0).model->flags(index);
237 const QModelIndex sourceIndex = mapToSource(index);
238 Q_ASSERT(sourceIndex.isValid());
239 return sourceIndex.model()->flags(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(0).model->headerData(section, orientation, role);
255 case Qt::Vertical: {
256 const auto result = d->sourceModelForRow(section);
257 Q_ASSERT(result.sourceModel);
258 return result.sourceModel->headerData(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(result.sourceModel->index(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(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(firstIndex.row());
342 QModelIndexList sourceIndexes;
343 sourceIndexes.reserve(indexes.size());
344 for (const QModelIndex &index : indexes) {
345 const QModelIndex sourceIndex = mapToSource(index);
346 Q_ASSERT(sourceIndex.model() == result.sourceModel); // see documentation above
347 sourceIndexes.append(sourceIndex);
348 }
349 return result.sourceModel->mimeData(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(targetRow);
377 Q_ASSERT(result.sourceModel);
378 const QModelIndex sourceIndex = q->mapToSource(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, &sourceColumn, &sourceParent, &sourceModel))
399 return false;
400 return sourceModel->canDropMimeData(data, action, sourceRow, sourceColumn, 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, &sourceColumn, &sourceParent, &sourceModel))
421 return false;
422
423 return sourceModel->dropMimeData(data, action, sourceRow, sourceColumn, 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(index);
436 Q_ASSERT(sourceIndex.isValid());
437 return sourceIndex.model()->span(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(d->m_models.size());
450 for (const auto &info : d->m_models)
451 ret.push_back(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(QModelIndex(), d->m_rowCount, d->m_rowCount + newRows - 1);
471 d->m_rowCount += newRows;
472 d->m_models.emplace_back(sourceModel, std::array{
473 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::dataChanged,
474 d, &QConcatenateTablesProxyModelPrivate::slotDataChanged),
475 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::rowsInserted,
476 d, &QConcatenateTablesProxyModelPrivate::slotRowsInserted),
477 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::rowsRemoved,
478 d, &QConcatenateTablesProxyModelPrivate::slotRowsRemoved),
479 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::rowsAboutToBeInserted,
480 d, &QConcatenateTablesProxyModelPrivate::slotRowsAboutToBeInserted),
481 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved,
482 d, &QConcatenateTablesProxyModelPrivate::slotRowsAboutToBeRemoved),
483 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::rowsMoved,
484 d, &QConcatenateTablesProxyModelPrivate::slotRowsMoved),
485 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::rowsAboutToBeMoved,
486 d, &QConcatenateTablesProxyModelPrivate::slotRowsAboutToBeMoved),
487
488 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::columnsInserted,
489 d, &QConcatenateTablesProxyModelPrivate::slotColumnsInserted),
490 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::columnsRemoved,
491 d, &QConcatenateTablesProxyModelPrivate::slotColumnsRemoved),
492 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::columnsAboutToBeInserted,
493 d, &QConcatenateTablesProxyModelPrivate::slotColumnsAboutToBeInserted),
494 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::columnsAboutToBeRemoved,
495 d, &QConcatenateTablesProxyModelPrivate::slotColumnsAboutToBeRemoved),
496 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::columnsMoved,
497 d, &QConcatenateTablesProxyModelPrivate::slotColumnsMoved),
498 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::columnsAboutToBeMoved,
499 d, &QConcatenateTablesProxyModelPrivate::slotColumnsAboutToBeMoved),
500
501 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::layoutAboutToBeChanged,
502 d, &QConcatenateTablesProxyModelPrivate::slotSourceLayoutAboutToBeChanged),
503 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::layoutChanged,
504 d, &QConcatenateTablesProxyModelPrivate::slotSourceLayoutChanged),
505 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::modelAboutToBeReset,
506 d, &QConcatenateTablesProxyModelPrivate::slotModelAboutToBeReset),
507 QObjectPrivate::connect(sourceModel, &QAbstractItemModel::modelReset,
508 d, &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(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(QModelIndex(), rowsPrior, rowsPrior + rowsRemoved - 1);
535 d->m_models.erase(it);
536 d->m_rowCount -= rowsRemoved;
537 if (rowsRemoved > 0)
538 endRemoveRows();
539
540 d->updateColumnCount();
541}
542
543/*!
544 \since 6.9.0
545 \reimp
546 Returns the union of the roleNames() of the underlying models.
547
548 In case source models associate different names to the same role,
549 the name used in last source model overrides the name used in earlier models.
550*/
551QHash<int, QByteArray> QConcatenateTablesProxyModel::roleNames() const
552{
553 Q_D(const QConcatenateTablesProxyModel);
554 QHash<int, QByteArray> ret = QAbstractItemModel::roleNames();
555 for (const auto &[model, _] : d->m_models)
556 ret.insert(model->roleNames());
557 return ret;
558}
559
561 int start, int end)
562{
563 Q_Q(QConcatenateTablesProxyModel);
564 if (parent.isValid()) // not supported, the proxy is a flat model
565 return;
566 const QAbstractItemModel * const model = static_cast<QAbstractItemModel *>(q->sender());
567 const int rowsPrior = computeRowsPrior(model);
568 q->beginInsertRows(QModelIndex(), rowsPrior + start, rowsPrior + end);
569}
570
571void QConcatenateTablesProxyModelPrivate::slotRowsInserted(const QModelIndex &parent, int start,
572 int end)
573{
574 Q_Q(QConcatenateTablesProxyModel);
575 if (parent.isValid()) // flat model
576 return;
577 m_rowCount += end - start + 1;
578 q->endInsertRows();
579}
580
582 int start, int end)
583{
584 Q_Q(QConcatenateTablesProxyModel);
585 if (parent.isValid()) // flat model
586 return;
587 const QAbstractItemModel * const model = static_cast<QAbstractItemModel *>(q->sender());
588 const int rowsPrior = computeRowsPrior(model);
589 q->beginRemoveRows(QModelIndex(), rowsPrior + start, rowsPrior + end);
590}
591
592void QConcatenateTablesProxyModelPrivate::slotRowsRemoved(const QModelIndex &parent, int start, int end)
593{
594 Q_Q(QConcatenateTablesProxyModel);
595 if (parent.isValid()) // flat model
596 return;
597 m_rowCount -= end - start + 1;
598 q->endRemoveRows();
599}
600
602 const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
603 const QModelIndex &destinationParent, int destinationRow)
604{
605 Q_Q(QConcatenateTablesProxyModel);
606 if (sourceParent.isValid() || destinationParent.isValid())
607 return;
608 const QAbstractItemModel *const model = static_cast<QAbstractItemModel *>(q->sender());
609 const int rowsPrior = computeRowsPrior(model);
610 q->beginMoveRows(sourceParent, rowsPrior + sourceStart, rowsPrior + sourceEnd,
611 destinationParent, rowsPrior + destinationRow);
612}
613
614void QConcatenateTablesProxyModelPrivate::slotRowsMoved(const QModelIndex &sourceParent,
615 int sourceStart, int sourceEnd,
616 const QModelIndex &destinationParent,
617 int destinationRow)
618{
619 Q_Q(QConcatenateTablesProxyModel);
620 Q_UNUSED(sourceStart)
621 Q_UNUSED(sourceEnd)
622 Q_UNUSED(destinationRow)
623 if (sourceParent.isValid() || destinationParent.isValid())
624 return;
625 q->endMoveRows();
626}
627
629 int start, int end)
630{
631 Q_Q(QConcatenateTablesProxyModel);
632 if (parent.isValid()) // flat model
633 return;
634 const QAbstractItemModel * const model = static_cast<QAbstractItemModel *>(q->sender());
635 const int oldColCount = model->columnCount();
636 const int newColCount = columnCountAfterChange(model, oldColCount + end - start + 1);
637 Q_ASSERT(newColCount >= oldColCount);
638 if (newColCount > oldColCount)
639 // If the underlying models have a different number of columns (example: 2 and 3), inserting 2 columns in
640 // the first model leads to inserting only one column in the proxy, since qMin(2+2,3) == 3.
641 q->beginInsertColumns(QModelIndex(), start, qMin(end, start + newColCount - oldColCount - 1));
642 m_newColumnCount = newColCount;
643}
644
645void QConcatenateTablesProxyModelPrivate::slotColumnsInserted(const QModelIndex &parent, int start,
646 int end)
647{
648 Q_UNUSED(start);
649 Q_UNUSED(end);
650 Q_Q(QConcatenateTablesProxyModel);
651 if (parent.isValid()) // flat model
652 return;
655 q->endInsertColumns();
656 }
657}
658
660 int start, int end)
661{
662 Q_Q(QConcatenateTablesProxyModel);
663 if (parent.isValid()) // flat model
664 return;
665 const QAbstractItemModel * const model = static_cast<QAbstractItemModel *>(q->sender());
666 const int oldColCount = model->columnCount();
667 const int newColCount = columnCountAfterChange(model, oldColCount - (end - start + 1));
668 Q_ASSERT(newColCount <= oldColCount);
669 if (newColCount < oldColCount)
670 q->beginRemoveColumns(QModelIndex(), start, qMax(end, start + oldColCount - newColCount - 1));
671 m_newColumnCount = newColCount;
672}
673
674void QConcatenateTablesProxyModelPrivate::slotColumnsRemoved(const QModelIndex &parent, int start,
675 int end)
676{
677 Q_Q(QConcatenateTablesProxyModel);
678 Q_UNUSED(start);
679 Q_UNUSED(end);
680 if (parent.isValid()) // flat model
681 return;
684 q->endRemoveColumns();
685 }
686}
687
689 const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
690 const QModelIndex &destinationParent, int destination)
691{
692 Q_UNUSED(sourceStart)
693 Q_UNUSED(sourceEnd)
694 Q_UNUSED(destination)
695 if (sourceParent.isValid() || destinationParent.isValid())
696 return;
697 slotSourceLayoutAboutToBeChanged({}, QAbstractItemModel::HorizontalSortHint);
698}
699
700void QConcatenateTablesProxyModelPrivate::slotColumnsMoved(const QModelIndex &sourceParent,
701 int sourceStart, int sourceEnd,
702 const QModelIndex &destinationParent,
703 int destination)
704{
705 Q_UNUSED(sourceStart)
706 Q_UNUSED(sourceEnd)
707 Q_UNUSED(destination)
708 if (sourceParent.isValid() || destinationParent.isValid())
709 return;
710 slotSourceLayoutChanged({}, QAbstractItemModel::HorizontalSortHint);
711}
712
714 const QModelIndex &to,
715 const QList<int> &roles)
716{
717 Q_Q(QConcatenateTablesProxyModel);
718 Q_ASSERT(from.isValid());
719 Q_ASSERT(to.isValid());
720 if (from.column() >= m_columnCount)
721 return;
722 QModelIndex adjustedTo = to;
723 if (to.column() >= m_columnCount)
724 adjustedTo = to.siblingAtColumn(m_columnCount - 1);
725 const QModelIndex myFrom = q->mapFromSource(from);
726 Q_ASSERT(q->checkIndex(myFrom, QAbstractItemModel::CheckIndexOption::IndexIsValid));
727 const QModelIndex myTo = q->mapFromSource(adjustedTo);
728 Q_ASSERT(q->checkIndex(myTo, QAbstractItemModel::CheckIndexOption::IndexIsValid));
729 emit q->dataChanged(myFrom, myTo, roles);
730}
731
733 const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint)
734{
735 Q_Q(QConcatenateTablesProxyModel);
736
737 if (!sourceParents.isEmpty() && !sourceParents.contains(QModelIndex()))
738 return;
739
740 emit q->layoutAboutToBeChanged({}, hint);
741
742 const QModelIndexList persistentIndexList = q->persistentIndexList();
743 layoutChangePersistentIndexes.reserve(persistentIndexList.size());
744 layoutChangeProxyIndexes.reserve(persistentIndexList.size());
745
746 for (const QModelIndex &proxyPersistentIndex : persistentIndexList) {
747 layoutChangeProxyIndexes.append(proxyPersistentIndex);
748 Q_ASSERT(proxyPersistentIndex.isValid());
749 const QPersistentModelIndex srcPersistentIndex = q->mapToSource(proxyPersistentIndex);
750 Q_ASSERT(srcPersistentIndex.isValid());
751 layoutChangePersistentIndexes << srcPersistentIndex;
752 }
753}
754
756 const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint)
757{
758 Q_Q(QConcatenateTablesProxyModel);
759 if (!sourceParents.isEmpty() && !sourceParents.contains(QModelIndex()))
760 return;
761 for (int i = 0; i < layoutChangeProxyIndexes.size(); ++i) {
762 const QModelIndex proxyIdx = layoutChangeProxyIndexes.at(i);
763 const QModelIndex newProxyIdx = q->mapFromSource(layoutChangePersistentIndexes.at(i));
764 q->changePersistentIndex(proxyIdx, newProxyIdx);
765 }
766
767 layoutChangePersistentIndexes.clear();
768 layoutChangeProxyIndexes.clear();
769
770 emit q->layoutChanged({}, hint);
771}
772
774{
775 Q_Q(QConcatenateTablesProxyModel);
776 Q_ASSERT(containsSourceModel(static_cast<QAbstractItemModel *>(q->sender())));
777 q->beginResetModel();
778 // A reset might reduce both rowCount and columnCount, and we can't notify of both at the same time,
779 // and notifying of one after the other leaves an intermediary invalid situation.
780 // So the only safe choice is to forward it as a full reset.
781}
782
784{
785 Q_Q(QConcatenateTablesProxyModel);
786 Q_ASSERT(containsSourceModel(static_cast<QAbstractItemModel *>(q->sender())));
788 m_rowCount = computeRowsPrior(nullptr);
789 q->endResetModel();
790}
791
793{
794 if (m_models.isEmpty())
795 return 0;
796
797 auto byColumnCount = [](const auto &a, const auto &b) {
798 return a.model->columnCount() < b.model->columnCount();
799 };
800 const auto it = std::min_element(m_models.begin(), m_models.end(), byColumnCount);
801 return it->model->columnCount();
802}
803
805{
806 Q_Q(QConcatenateTablesProxyModel);
807 const int newColumnCount = calculatedColumnCount();
808 const int columnDiff = newColumnCount - m_columnCount;
809 if (columnDiff > 0) {
810 q->beginInsertColumns(QModelIndex(), m_columnCount, m_columnCount + columnDiff - 1);
811 m_columnCount = newColumnCount;
812 q->endInsertColumns();
813 } else if (columnDiff < 0) {
814 const int lastColumn = m_columnCount - 1;
815 q->beginRemoveColumns(QModelIndex(), lastColumn + columnDiff + 1, lastColumn);
816 m_columnCount = newColumnCount;
817 q->endRemoveColumns();
818 }
819}
820
821int QConcatenateTablesProxyModelPrivate::columnCountAfterChange(const QAbstractItemModel *model, int newCount) const
822{
823 int newColumnCount = 0;
824 for (qsizetype i = 0; i < m_models.size(); ++i) {
825 const QAbstractItemModel *mod = m_models.at(i).model;
826 const int colCount = mod == model ? newCount : mod->columnCount();
827 if (i == 0)
828 newColumnCount = colCount;
829 else
830 newColumnCount = qMin(colCount, newColumnCount);
831 }
832 return newColumnCount;
833}
834
835int QConcatenateTablesProxyModelPrivate::computeRowsPrior(const QAbstractItemModel *sourceModel) const
836{
837 int rowsPrior = 0;
838 for (const auto &[model, _] : m_models) {
839 if (model == sourceModel)
840 break;
841 rowsPrior += model->rowCount();
842 }
843 return rowsPrior;
844}
845
847{
849 int rowCount = 0;
850 for (const auto &[model, _] : m_models) {
851 const int subRowCount = model->rowCount();
852 if (rowCount + subRowCount > row) {
853 result.sourceModel = model;
854 break;
855 }
856 rowCount += subRowCount;
857 }
858 result.sourceRow = row - rowCount;
859 return result;
860}
861
862QT_END_NAMESPACE
863
864#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)
Combined button and popup list for selecting options.