File indexing completed on 2024-05-12 17:07:45

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     return d->m_firstItem / d->m_pageSize;
0174 }
0175 
0176 int PaginateModel::pageCount() const
0177 {
0178     if (!d->m_sourceModel)
0179         return 0;
0180     const int rc = d->m_sourceModel->rowCount();
0181     const int r = (rc % d->m_pageSize == 0) ? 1 : 0;
0182     return qMax(qCeil(float(rc) / d->m_pageSize) - r, 1);
0183 }
0184 
0185 bool PaginateModel::hasStaticRowCount() const
0186 {
0187     return d->m_hasStaticRowCount;
0188 }
0189 
0190 void PaginateModel::setStaticRowCount(bool src)
0191 {
0192     if (src == d->m_hasStaticRowCount) {
0193         return;
0194     }
0195 
0196     beginResetModel();
0197     d->m_hasStaticRowCount = src;
0198     endResetModel();
0199 
0200     Q_EMIT staticRowCountChanged();
0201 }
0202 
0203 //////////////////////////////
0204 
0205 void PaginateModel::_k_sourceColumnsAboutToBeInserted(const QModelIndex &parent, int start, int end)
0206 {
0207     Q_UNUSED(end)
0208     if (parent.isValid() || start != 0) {
0209         return;
0210     }
0211     beginResetModel();
0212 }
0213 
0214 void PaginateModel::_k_sourceColumnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest)
0215 {
0216     Q_UNUSED(sourceParent)
0217     Q_UNUSED(sourceStart)
0218     Q_UNUSED(sourceEnd)
0219     Q_UNUSED(destParent)
0220     Q_UNUSED(dest)
0221     beginResetModel();
0222 }
0223 
0224 void PaginateModel::_k_sourceColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
0225 {
0226     Q_UNUSED(end)
0227     if (parent.isValid() || start != 0) {
0228         return;
0229     }
0230     beginResetModel();
0231 }
0232 
0233 void PaginateModel::_k_sourceColumnsInserted(const QModelIndex &parent, int start, int end)
0234 {
0235     Q_UNUSED(end)
0236     if (parent.isValid() || start != 0) {
0237         return;
0238     }
0239     endResetModel();
0240 }
0241 
0242 void PaginateModel::_k_sourceColumnsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest)
0243 {
0244     Q_UNUSED(sourceParent)
0245     Q_UNUSED(sourceStart)
0246     Q_UNUSED(sourceEnd)
0247     Q_UNUSED(destParent)
0248     Q_UNUSED(dest)
0249     endResetModel();
0250 }
0251 
0252 void PaginateModel::_k_sourceColumnsRemoved(const QModelIndex &parent, int start, int end)
0253 {
0254     Q_UNUSED(end)
0255     if (parent.isValid() || start != 0) {
0256         return;
0257     }
0258     endResetModel();
0259 }
0260 
0261 void PaginateModel::_k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
0262 {
0263     if (topLeft.parent().isValid() || bottomRight.row() < d->m_firstItem || topLeft.row() > lastItem()) {
0264         return;
0265     }
0266 
0267     QModelIndex idxTop = mapFromSource(topLeft);
0268     QModelIndex idxBottom = mapFromSource(bottomRight);
0269     if (!idxTop.isValid())
0270         idxTop = index(0);
0271     if (!idxBottom.isValid())
0272         idxBottom = index(rowCount() - 1);
0273 
0274     Q_EMIT dataChanged(idxTop, idxBottom, roles);
0275 }
0276 
0277 void PaginateModel::_k_sourceHeaderDataChanged(Qt::Orientation orientation, int first, int last)
0278 {
0279     Q_UNUSED(last)
0280     if (first == 0)
0281         Q_EMIT headerDataChanged(orientation, 0, 0);
0282 }
0283 
0284 void PaginateModel::_k_sourceModelAboutToBeReset()
0285 {
0286     beginResetModel();
0287 }
0288 
0289 void PaginateModel::_k_sourceModelReset()
0290 {
0291     endResetModel();
0292 }
0293 
0294 bool PaginateModel::isIntervalValid(const QModelIndex &parent, int start, int /*end*/) const
0295 {
0296     return !parent.isValid() && start <= lastItem();
0297 }
0298 
0299 bool PaginateModel::canSizeChange() const
0300 {
0301     return !d->m_hasStaticRowCount && currentPage() == pageCount() - 1;
0302 }
0303 
0304 void PaginateModel::_k_sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
0305 {
0306     if (!isIntervalValid(parent, start, end)) {
0307         return;
0308     }
0309 
0310     if (canSizeChange()) {
0311         const int newStart = qMax(start - d->m_firstItem, 0);
0312         const int insertedCount = qMin(end - start, pageSize() - newStart - 1);
0313         beginInsertRows(QModelIndex(), newStart, newStart + insertedCount);
0314     } else {
0315         beginResetModel();
0316     }
0317 }
0318 
0319 void PaginateModel::_k_sourceRowsInserted(const QModelIndex &parent, int start, int end)
0320 {
0321     if (!isIntervalValid(parent, start, end)) {
0322         return;
0323     }
0324 
0325     if (canSizeChange()) {
0326         endInsertRows();
0327     } else {
0328         endResetModel();
0329     }
0330 }
0331 
0332 void PaginateModel::_k_sourceRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest)
0333 {
0334     Q_UNUSED(sourceParent)
0335     Q_UNUSED(sourceStart)
0336     Q_UNUSED(sourceEnd)
0337     Q_UNUSED(destParent)
0338     Q_UNUSED(dest)
0339     // NOTE could optimize, unsure if it makes sense
0340     beginResetModel();
0341 }
0342 
0343 void PaginateModel::_k_sourceRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest)
0344 {
0345     Q_UNUSED(sourceParent)
0346     Q_UNUSED(sourceStart)
0347     Q_UNUSED(sourceEnd)
0348     Q_UNUSED(destParent)
0349     Q_UNUSED(dest)
0350     endResetModel();
0351 }
0352 
0353 void PaginateModel::_k_sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
0354 {
0355     if (!isIntervalValid(parent, start, end)) {
0356         return;
0357     }
0358 
0359     if (canSizeChange()) {
0360         const int removedCount = end - start;
0361         const int newStart = qMax(start - d->m_firstItem, 0);
0362         beginRemoveRows(QModelIndex(), newStart, newStart + removedCount);
0363     } else {
0364         beginResetModel();
0365     }
0366 }
0367 
0368 void PaginateModel::_k_sourceRowsRemoved(const QModelIndex &parent, int start, int end)
0369 {
0370     if (!isIntervalValid(parent, start, end)) {
0371         return;
0372     }
0373 
0374     if (canSizeChange()) {
0375         endRemoveRows();
0376     } else {
0377         beginResetModel();
0378     }
0379 }
0380 
0381 int PaginateModel::lastItem() const
0382 {
0383     return d->m_firstItem + rowCount();
0384 }