File indexing completed on 2024-05-05 05:29:12

0001 /*
0002  *   SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0003  *
0004  *   SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "PaginateModel.h"
0008 #include "discover_debug.h"
0009 #include <QtMath>
0010 
0011 class PaginateModel::PaginateModelPrivate
0012 {
0013 public:
0014     int m_firstItem = 0;
0015     int m_pageSize = 0;
0016     QAbstractItemModel *m_sourceModel = nullptr;
0017     bool m_hasStaticRowCount = false;
0018     bool m_modelResetInProgress = false;
0019 };
0020 
0021 PaginateModel::PaginateModel(QObject *object)
0022     : QAbstractListModel(object)
0023     , d(new PaginateModelPrivate)
0024 {
0025 }
0026 
0027 PaginateModel::~PaginateModel() = default;
0028 
0029 int PaginateModel::firstItem() const
0030 {
0031     return d->m_firstItem;
0032 }
0033 
0034 void PaginateModel::setFirstItem(int row)
0035 {
0036     Q_ASSERT(row >= 0 && row < d->m_sourceModel->rowCount());
0037     if (row != d->m_firstItem) {
0038         d->m_modelResetInProgress = true;
0039         beginResetModel();
0040         d->m_firstItem = row;
0041         endResetModel();
0042         d->m_modelResetInProgress = false;
0043         Q_EMIT firstItemChanged();
0044     }
0045 }
0046 
0047 int PaginateModel::pageSize() const
0048 {
0049     return d->m_pageSize;
0050 }
0051 
0052 void PaginateModel::setPageSize(int count)
0053 {
0054     if (count != d->m_pageSize) {
0055         const int oldSize = rowsByPageSize(d->m_pageSize);
0056         const int newSize = rowsByPageSize(count);
0057         const int difference = newSize - oldSize;
0058         if (difference == 0) {
0059             d->m_pageSize = count;
0060         } else if (difference > 0) {
0061             beginInsertRows(QModelIndex(), d->m_pageSize, d->m_pageSize + difference - 1);
0062             d->m_pageSize = count;
0063             endInsertRows();
0064         } else {
0065             beginRemoveRows(QModelIndex(), d->m_pageSize + difference, d->m_pageSize - 1);
0066             d->m_pageSize = count;
0067             endRemoveRows();
0068         }
0069         Q_EMIT pageSizeChanged();
0070     }
0071 }
0072 
0073 QAbstractItemModel *PaginateModel::sourceModel() const
0074 {
0075     return d->m_sourceModel;
0076 }
0077 
0078 void PaginateModel::setSourceModel(QAbstractItemModel *model)
0079 {
0080     if (d->m_sourceModel) {
0081         disconnect(d->m_sourceModel, nullptr, this, nullptr);
0082     }
0083 
0084     if (model != d->m_sourceModel) {
0085         d->m_modelResetInProgress = true;
0086         beginResetModel();
0087         d->m_sourceModel = model;
0088         if (model) {
0089             connect(d->m_sourceModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &PaginateModel::_k_sourceRowsAboutToBeInserted);
0090             connect(d->m_sourceModel, &QAbstractItemModel::rowsInserted, this, &PaginateModel::_k_sourceRowsInserted);
0091             connect(d->m_sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PaginateModel::_k_sourceRowsAboutToBeRemoved);
0092             connect(d->m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &PaginateModel::_k_sourceRowsRemoved);
0093             connect(d->m_sourceModel, &QAbstractItemModel::rowsAboutToBeMoved, this, &PaginateModel::_k_sourceRowsAboutToBeMoved);
0094             connect(d->m_sourceModel, &QAbstractItemModel::rowsMoved, this, &PaginateModel::_k_sourceRowsMoved);
0095 
0096             connect(d->m_sourceModel, &QAbstractItemModel::columnsAboutToBeInserted, this, &PaginateModel::_k_sourceColumnsAboutToBeInserted);
0097             connect(d->m_sourceModel, &QAbstractItemModel::columnsInserted, this, &PaginateModel::_k_sourceColumnsInserted);
0098             connect(d->m_sourceModel, &QAbstractItemModel::columnsAboutToBeRemoved, this, &PaginateModel::_k_sourceColumnsAboutToBeRemoved);
0099             connect(d->m_sourceModel, &QAbstractItemModel::columnsRemoved, this, &PaginateModel::_k_sourceColumnsRemoved);
0100             connect(d->m_sourceModel, &QAbstractItemModel::columnsAboutToBeMoved, this, &PaginateModel::_k_sourceColumnsAboutToBeMoved);
0101             connect(d->m_sourceModel, &QAbstractItemModel::columnsMoved, this, &PaginateModel::_k_sourceColumnsMoved);
0102 
0103             connect(d->m_sourceModel, &QAbstractItemModel::dataChanged, this, &PaginateModel::_k_sourceDataChanged);
0104             connect(d->m_sourceModel, &QAbstractItemModel::headerDataChanged, this, &PaginateModel::_k_sourceHeaderDataChanged);
0105 
0106             connect(d->m_sourceModel, &QAbstractItemModel::modelAboutToBeReset, this, &PaginateModel::_k_sourceModelAboutToBeReset);
0107             connect(d->m_sourceModel, &QAbstractItemModel::modelReset, this, &PaginateModel::_k_sourceModelReset);
0108 
0109             connect(d->m_sourceModel, &QAbstractItemModel::rowsInserted, this, &PaginateModel::pageCountChanged);
0110             connect(d->m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &PaginateModel::pageCountChanged);
0111             connect(d->m_sourceModel, &QAbstractItemModel::modelReset, this, &PaginateModel::pageCountChanged);
0112         }
0113         endResetModel();
0114         d->m_modelResetInProgress = false;
0115         Q_EMIT sourceModelChanged();
0116     }
0117 }
0118 
0119 QHash<int, QByteArray> PaginateModel::roleNames() const
0120 {
0121     return d->m_sourceModel ? d->m_sourceModel->roleNames() : QAbstractItemModel::roleNames();
0122 }
0123 
0124 int PaginateModel::rowsByPageSize(int size) const
0125 {
0126     return d->m_hasStaticRowCount ? size : !d->m_sourceModel ? 0 : qMin(d->m_sourceModel->rowCount() - d->m_firstItem, size);
0127 }
0128 
0129 int PaginateModel::rowCount(const QModelIndex &parent) const
0130 {
0131     return parent.isValid() ? 0 : rowsByPageSize(d->m_pageSize);
0132 }
0133 
0134 QModelIndex PaginateModel::mapToSource(const QModelIndex &idx) const
0135 {
0136     if (!d->m_sourceModel)
0137         return QModelIndex();
0138     return d->m_sourceModel->index(idx.row() + d->m_firstItem, idx.column());
0139 }
0140 
0141 QModelIndex PaginateModel::mapFromSource(const QModelIndex &idx) const
0142 {
0143     Q_ASSERT(idx.model() == d->m_sourceModel);
0144     if (!d->m_sourceModel)
0145         return QModelIndex();
0146     return index(idx.row() - d->m_firstItem, idx.column());
0147 }
0148 
0149 QVariant PaginateModel::data(const QModelIndex &index, int role) const
0150 {
0151     if (!d->m_sourceModel)
0152         return QVariant();
0153     QModelIndex idx = mapToSource(index);
0154     return idx.data(role);
0155 }
0156 
0157 void PaginateModel::firstPage()
0158 {
0159     setFirstItem(0);
0160 }
0161 
0162 void PaginateModel::lastPage()
0163 {
0164     setFirstItem((pageCount() - 1) * d->m_pageSize);
0165 }
0166 
0167 void PaginateModel::nextPage()
0168 {
0169     setFirstItem(d->m_firstItem + d->m_pageSize);
0170 }
0171 
0172 void PaginateModel::previousPage()
0173 {
0174     setFirstItem(d->m_firstItem - d->m_pageSize);
0175 }
0176 
0177 int PaginateModel::currentPage() const
0178 {
0179     if (d->m_pageSize == 0)
0180         return 0;
0181 
0182     return d->m_firstItem / d->m_pageSize;
0183 }
0184 
0185 int PaginateModel::pageCount() const
0186 {
0187     if (!d->m_sourceModel || d->m_pageSize == 0)
0188         return 0;
0189     const int rc = d->m_sourceModel->rowCount();
0190     const int r = (rc % d->m_pageSize == 0) ? 1 : 0;
0191     return qMax(qCeil(float(rc) / d->m_pageSize) - r, 1);
0192 }
0193 
0194 bool PaginateModel::hasStaticRowCount() const
0195 {
0196     return d->m_hasStaticRowCount;
0197 }
0198 
0199 void PaginateModel::setStaticRowCount(bool src)
0200 {
0201     if (src == d->m_hasStaticRowCount) {
0202         return;
0203     }
0204 
0205     d->m_modelResetInProgress = true;
0206     beginResetModel();
0207     d->m_hasStaticRowCount = src;
0208     endResetModel();
0209     d->m_modelResetInProgress = false;
0210 
0211     Q_EMIT staticRowCountChanged();
0212 }
0213 
0214 //////////////////////////////
0215 
0216 void PaginateModel::_k_sourceColumnsAboutToBeInserted(const QModelIndex &parent, int start, int end)
0217 {
0218     Q_UNUSED(end)
0219     if (parent.isValid() || start != 0) {
0220         return;
0221     }
0222     d->m_modelResetInProgress = true;
0223     beginResetModel();
0224 }
0225 
0226 void PaginateModel::_k_sourceColumnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest)
0227 {
0228     Q_UNUSED(sourceParent)
0229     Q_UNUSED(sourceStart)
0230     Q_UNUSED(sourceEnd)
0231     Q_UNUSED(destParent)
0232     Q_UNUSED(dest)
0233     d->m_modelResetInProgress = true;
0234     beginResetModel();
0235 }
0236 
0237 void PaginateModel::_k_sourceColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
0238 {
0239     Q_UNUSED(end)
0240     if (parent.isValid() || start != 0) {
0241         return;
0242     }
0243     d->m_modelResetInProgress = true;
0244     beginResetModel();
0245 }
0246 
0247 void PaginateModel::_k_sourceColumnsInserted(const QModelIndex &parent, int start, int end)
0248 {
0249     Q_UNUSED(end)
0250     if (parent.isValid() || start != 0) {
0251         return;
0252     }
0253     endResetModel();
0254     d->m_modelResetInProgress = false;
0255 }
0256 
0257 void PaginateModel::_k_sourceColumnsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest)
0258 {
0259     Q_UNUSED(sourceParent)
0260     Q_UNUSED(sourceStart)
0261     Q_UNUSED(sourceEnd)
0262     Q_UNUSED(destParent)
0263     Q_UNUSED(dest)
0264     endResetModel();
0265     d->m_modelResetInProgress = false;
0266 }
0267 
0268 void PaginateModel::_k_sourceColumnsRemoved(const QModelIndex &parent, int start, int end)
0269 {
0270     Q_UNUSED(end)
0271     if (parent.isValid() || start != 0) {
0272         return;
0273     }
0274     endResetModel();
0275     d->m_modelResetInProgress = false;
0276 }
0277 
0278 void PaginateModel::_k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
0279 {
0280     if (topLeft.parent().isValid() || bottomRight.row() < d->m_firstItem || topLeft.row() > lastItem()) {
0281         return;
0282     }
0283 
0284     QModelIndex idxTop = mapFromSource(topLeft);
0285     QModelIndex idxBottom = mapFromSource(bottomRight);
0286     if (!idxTop.isValid())
0287         idxTop = index(0);
0288     if (!idxBottom.isValid())
0289         idxBottom = index(rowCount() - 1);
0290 
0291     Q_EMIT dataChanged(idxTop, idxBottom, roles);
0292 }
0293 
0294 void PaginateModel::_k_sourceHeaderDataChanged(Qt::Orientation orientation, int first, int last)
0295 {
0296     Q_UNUSED(last)
0297     if (first == 0)
0298         Q_EMIT headerDataChanged(orientation, 0, 0);
0299 }
0300 
0301 void PaginateModel::_k_sourceModelAboutToBeReset()
0302 {
0303     d->m_modelResetInProgress = true;
0304     beginResetModel();
0305 }
0306 
0307 void PaginateModel::_k_sourceModelReset()
0308 {
0309     endResetModel();
0310     d->m_modelResetInProgress = false;
0311 }
0312 
0313 bool PaginateModel::isIntervalValid(const QModelIndex &parent, int start, int /*end*/) const
0314 {
0315     return !parent.isValid() && start <= lastItem();
0316 }
0317 
0318 bool PaginateModel::canSizeChange() const
0319 {
0320     return !d->m_hasStaticRowCount && currentPage() == pageCount() - 1;
0321 }
0322 
0323 void PaginateModel::_k_sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
0324 {
0325     if (!isIntervalValid(parent, start, end)) {
0326         return;
0327     }
0328 
0329     if (canSizeChange()) {
0330         const int newStart = qMax(start - d->m_firstItem, 0);
0331         const int insertedCount = qMin(end - start, pageSize() - newStart - 1);
0332         beginInsertRows(QModelIndex(), newStart, newStart + insertedCount);
0333     } else {
0334         d->m_modelResetInProgress = true;
0335         beginResetModel();
0336     }
0337 }
0338 
0339 void PaginateModel::_k_sourceRowsInserted(const QModelIndex &parent, int start, int end)
0340 {
0341     if (!isIntervalValid(parent, start, end)) {
0342         return;
0343     }
0344 
0345     if (d->m_modelResetInProgress) {
0346         endResetModel();
0347         d->m_modelResetInProgress = false;
0348     } else {
0349         endInsertRows();
0350     }
0351 }
0352 
0353 void PaginateModel::_k_sourceRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest)
0354 {
0355     Q_UNUSED(sourceParent)
0356     Q_UNUSED(sourceStart)
0357     Q_UNUSED(sourceEnd)
0358     Q_UNUSED(destParent)
0359     Q_UNUSED(dest)
0360     // NOTE could optimize, unsure if it makes sense
0361     d->m_modelResetInProgress = true;
0362     beginResetModel();
0363 }
0364 
0365 void PaginateModel::_k_sourceRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest)
0366 {
0367     Q_UNUSED(sourceParent)
0368     Q_UNUSED(sourceStart)
0369     Q_UNUSED(sourceEnd)
0370     Q_UNUSED(destParent)
0371     Q_UNUSED(dest)
0372     endResetModel();
0373     d->m_modelResetInProgress = false;
0374 }
0375 
0376 void PaginateModel::_k_sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
0377 {
0378     if (!isIntervalValid(parent, start, end)) {
0379         return;
0380     }
0381 
0382     if (canSizeChange()) {
0383         const int removedCount = end - start;
0384         const int newStart = qMax(start - d->m_firstItem, 0);
0385         beginRemoveRows(QModelIndex(), newStart, newStart + removedCount);
0386     } else {
0387         d->m_modelResetInProgress = true;
0388         beginResetModel();
0389     }
0390 }
0391 
0392 void PaginateModel::_k_sourceRowsRemoved(const QModelIndex &parent, int start, int end)
0393 {
0394     if (!isIntervalValid(parent, start, end)) {
0395         return;
0396     }
0397 
0398     if (canSizeChange()) {
0399         endRemoveRows();
0400     } else {
0401         d->m_modelResetInProgress = true;
0402         beginResetModel();
0403     }
0404 }
0405 
0406 int PaginateModel::lastItem() const
0407 {
0408     return d->m_firstItem + rowCount();
0409 }