File indexing completed on 2024-09-29 07:52:36

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