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