File indexing completed on 2024-05-05 17:33:04

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