/* * SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "paginatemodel.h" #include class PaginateModel::PaginateModelPrivate { public: int m_firstItem = 0; int m_pageSize = 0; QAbstractItemModel *m_sourceModel = nullptr; bool m_hasStaticRowCount = false; }; PaginateModel::PaginateModel(QObject *object) : QAbstractListModel(object) , d(new PaginateModelPrivate) { } PaginateModel::~PaginateModel() = default; int PaginateModel::firstItem() const { return d->m_firstItem; } void PaginateModel::setFirstItem(int row) { Q_ASSERT(row >= 0 && row < d->m_sourceModel->rowCount()); if (row != d->m_firstItem) { beginResetModel(); d->m_firstItem = row; endResetModel(); Q_EMIT firstItemChanged(); } } int PaginateModel::pageSize() const { return d->m_pageSize; } void PaginateModel::setPageSize(int count) { if (count != d->m_pageSize) { const int oldSize = rowsByPageSize(d->m_pageSize); const int newSize = rowsByPageSize(count); const int difference = newSize - oldSize; if (difference == 0) { d->m_pageSize = count; } else if (difference > 0) { beginInsertRows(QModelIndex(), d->m_pageSize, d->m_pageSize + difference - 1); d->m_pageSize = count; endInsertRows(); } else { beginRemoveRows(QModelIndex(), d->m_pageSize + difference, d->m_pageSize - 1); d->m_pageSize = count; endRemoveRows(); } Q_EMIT pageSizeChanged(); } } QAbstractItemModel *PaginateModel::sourceModel() const { return d->m_sourceModel; } void PaginateModel::setSourceModel(QAbstractItemModel *model) { if (d->m_sourceModel) { disconnect(d->m_sourceModel, nullptr, this, nullptr); } if (model != d->m_sourceModel) { beginResetModel(); d->m_sourceModel = model; if (model) { connect(d->m_sourceModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &PaginateModel::_k_sourceRowsAboutToBeInserted); connect(d->m_sourceModel, &QAbstractItemModel::rowsInserted, this, &PaginateModel::_k_sourceRowsInserted); connect(d->m_sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PaginateModel::_k_sourceRowsAboutToBeRemoved); connect(d->m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &PaginateModel::_k_sourceRowsRemoved); connect(d->m_sourceModel, &QAbstractItemModel::rowsAboutToBeMoved, this, &PaginateModel::_k_sourceRowsAboutToBeMoved); connect(d->m_sourceModel, &QAbstractItemModel::rowsMoved, this, &PaginateModel::_k_sourceRowsMoved); connect(d->m_sourceModel, &QAbstractItemModel::columnsAboutToBeInserted, this, &PaginateModel::_k_sourceColumnsAboutToBeInserted); connect(d->m_sourceModel, &QAbstractItemModel::columnsInserted, this, &PaginateModel::_k_sourceColumnsInserted); connect(d->m_sourceModel, &QAbstractItemModel::columnsAboutToBeRemoved, this, &PaginateModel::_k_sourceColumnsAboutToBeRemoved); connect(d->m_sourceModel, &QAbstractItemModel::columnsRemoved, this, &PaginateModel::_k_sourceColumnsRemoved); connect(d->m_sourceModel, &QAbstractItemModel::columnsAboutToBeMoved, this, &PaginateModel::_k_sourceColumnsAboutToBeMoved); connect(d->m_sourceModel, &QAbstractItemModel::columnsMoved, this, &PaginateModel::_k_sourceColumnsMoved); connect(d->m_sourceModel, &QAbstractItemModel::dataChanged, this, &PaginateModel::_k_sourceDataChanged); connect(d->m_sourceModel, &QAbstractItemModel::headerDataChanged, this, &PaginateModel::_k_sourceHeaderDataChanged); connect(d->m_sourceModel, &QAbstractItemModel::modelAboutToBeReset, this, &PaginateModel::_k_sourceModelAboutToBeReset); connect(d->m_sourceModel, &QAbstractItemModel::modelReset, this, &PaginateModel::_k_sourceModelReset); connect(d->m_sourceModel, &QAbstractItemModel::rowsInserted, this, &PaginateModel::pageCountChanged); connect(d->m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &PaginateModel::pageCountChanged); connect(d->m_sourceModel, &QAbstractItemModel::modelReset, this, &PaginateModel::pageCountChanged); } endResetModel(); Q_EMIT sourceModelChanged(); } } QHash PaginateModel::roleNames() const { return d->m_sourceModel ? d->m_sourceModel->roleNames() : QAbstractItemModel::roleNames(); } int PaginateModel::rowsByPageSize(int size) const { return d->m_hasStaticRowCount ? size : !d->m_sourceModel ? 0 : qMin(d->m_sourceModel->rowCount() - d->m_firstItem, size); } int PaginateModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : rowsByPageSize(d->m_pageSize); } QModelIndex PaginateModel::mapToSource(const QModelIndex &idx) const { if (!d->m_sourceModel) return QModelIndex(); return d->m_sourceModel->index(idx.row() + d->m_firstItem, idx.column()); } QModelIndex PaginateModel::mapFromSource(const QModelIndex &idx) const { Q_ASSERT(idx.model() == d->m_sourceModel); if (!d->m_sourceModel) return QModelIndex(); return index(idx.row() - d->m_firstItem, idx.column()); } QVariant PaginateModel::data(const QModelIndex &index, int role) const { if (!d->m_sourceModel) return QVariant(); QModelIndex idx = mapToSource(index); return idx.data(role); } void PaginateModel::firstPage() { setFirstItem(0); } void PaginateModel::lastPage() { setFirstItem((pageCount() - 1) * d->m_pageSize); } void PaginateModel::nextPage() { setFirstItem(d->m_firstItem + d->m_pageSize); } void PaginateModel::previousPage() { setFirstItem(d->m_firstItem - d->m_pageSize); } int PaginateModel::currentPage() const { if (d->m_pageSize == 0) return 0; return d->m_firstItem / d->m_pageSize; } int PaginateModel::pageCount() const { if (!d->m_sourceModel || d->m_pageSize == 0) return 0; const int rc = d->m_sourceModel->rowCount(); const int r = (rc % d->m_pageSize == 0) ? 1 : 0; return qMax(qCeil(float(rc) / d->m_pageSize) - r, 1); } bool PaginateModel::hasStaticRowCount() const { return d->m_hasStaticRowCount; } void PaginateModel::setStaticRowCount(bool src) { if (src == d->m_hasStaticRowCount) { return; } beginResetModel(); d->m_hasStaticRowCount = src; endResetModel(); Q_EMIT staticRowCountChanged(); } ////////////////////////////// void PaginateModel::_k_sourceColumnsAboutToBeInserted(const QModelIndex &parent, int start, int end) { Q_UNUSED(end) if (parent.isValid() || start != 0) { return; } beginResetModel(); } void PaginateModel::_k_sourceColumnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest) { Q_UNUSED(sourceParent) Q_UNUSED(sourceStart) Q_UNUSED(sourceEnd) Q_UNUSED(destParent) Q_UNUSED(dest) beginResetModel(); } void PaginateModel::_k_sourceColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { Q_UNUSED(end) if (parent.isValid() || start != 0) { return; } beginResetModel(); } void PaginateModel::_k_sourceColumnsInserted(const QModelIndex &parent, int start, int end) { Q_UNUSED(end) if (parent.isValid() || start != 0) { return; } endResetModel(); } void PaginateModel::_k_sourceColumnsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest) { Q_UNUSED(sourceParent) Q_UNUSED(sourceStart) Q_UNUSED(sourceEnd) Q_UNUSED(destParent) Q_UNUSED(dest) endResetModel(); } void PaginateModel::_k_sourceColumnsRemoved(const QModelIndex &parent, int start, int end) { Q_UNUSED(end) if (parent.isValid() || start != 0) { return; } endResetModel(); } void PaginateModel::_k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { if (topLeft.parent().isValid() || bottomRight.row() < d->m_firstItem || topLeft.row() > lastItem()) { return; } QModelIndex idxTop = mapFromSource(topLeft); QModelIndex idxBottom = mapFromSource(bottomRight); if (!idxTop.isValid()) idxTop = index(0); if (!idxBottom.isValid()) idxBottom = index(rowCount() - 1); Q_EMIT dataChanged(idxTop, idxBottom, roles); } void PaginateModel::_k_sourceHeaderDataChanged(Qt::Orientation orientation, int first, int last) { Q_UNUSED(last) if (first == 0) Q_EMIT headerDataChanged(orientation, 0, 0); } void PaginateModel::_k_sourceModelAboutToBeReset() { beginResetModel(); } void PaginateModel::_k_sourceModelReset() { endResetModel(); } bool PaginateModel::isIntervalValid(const QModelIndex &parent, int start, int /*end*/) const { return !parent.isValid() && start <= lastItem(); } bool PaginateModel::canSizeChange() const { return !d->m_hasStaticRowCount && currentPage() == pageCount() - 1; } void PaginateModel::_k_sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end) { if (!isIntervalValid(parent, start, end)) { return; } if (canSizeChange()) { const int newStart = qMax(start - d->m_firstItem, 0); const int insertedCount = qMin(end - start, pageSize() - newStart - 1); beginInsertRows(QModelIndex(), newStart, newStart + insertedCount); } else { beginResetModel(); } } void PaginateModel::_k_sourceRowsInserted(const QModelIndex &parent, int start, int end) { if (!isIntervalValid(parent, start, end)) { return; } if (canSizeChange()) { endInsertRows(); } else { endResetModel(); } } void PaginateModel::_k_sourceRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest) { Q_UNUSED(sourceParent) Q_UNUSED(sourceStart) Q_UNUSED(sourceEnd) Q_UNUSED(destParent) Q_UNUSED(dest) // NOTE could optimize, unsure if it makes sense beginResetModel(); } void PaginateModel::_k_sourceRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest) { Q_UNUSED(sourceParent) Q_UNUSED(sourceStart) Q_UNUSED(sourceEnd) Q_UNUSED(destParent) Q_UNUSED(dest) endResetModel(); } void PaginateModel::_k_sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { if (!isIntervalValid(parent, start, end)) { return; } if (canSizeChange()) { const int removedCount = end - start; const int newStart = qMax(start - d->m_firstItem, 0); beginRemoveRows(QModelIndex(), newStart, newStart + removedCount); } else { beginResetModel(); } } void PaginateModel::_k_sourceRowsRemoved(const QModelIndex &parent, int start, int end) { if (!isIntervalValid(parent, start, end)) { return; } if (canSizeChange()) { endRemoveRows(); } else { beginResetModel(); } } int PaginateModel::lastItem() const { return d->m_firstItem + rowCount(); }