/* * Copyright (C) 2012, 2013 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // self #include "qlimitproxymodelqml.h" // Qt #include QLimitProxyModelQML::QLimitProxyModelQML(QObject *parent) : QIdentityProxyModel(parent) , m_limit(-1) , m_sourceInserting(false) , m_sourceRemoving(false) , m_dataChangedBegin(-1) , m_dataChangedEnd(-1) { connect(this, &QLimitProxyModelQML::modelReset, this, &QLimitProxyModelQML::countChanged); connect(this, &QLimitProxyModelQML::rowsInserted, this, &QLimitProxyModelQML::countChanged); connect(this, &QLimitProxyModelQML::rowsRemoved, this, &QLimitProxyModelQML::countChanged); } QHash QLimitProxyModelQML::roleNames() const { return sourceModel() ? sourceModel()->roleNames() : QHash(); } void QLimitProxyModelQML::setModel(QAbstractItemModel *itemModel) { if (itemModel != sourceModel()) { if (sourceModel() != nullptr) { sourceModel()->disconnect(this); } setSourceModel(itemModel); if (sourceModel() != nullptr) { // Disconnect the QIdentityProxyModel handling for rows removed/added... disconnect(sourceModel(), &QAbstractItemModel::rowsAboutToBeInserted, this, nullptr); disconnect(sourceModel(), &QAbstractItemModel::rowsInserted, this, nullptr); disconnect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved, this, nullptr); disconnect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, nullptr); // ... and use our own connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeInserted, this, &QLimitProxyModelQML::sourceRowsAboutToBeInserted); connect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &QLimitProxyModelQML::sourceRowsInserted); connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved, this, &QLimitProxyModelQML::sourceRowsAboutToBeRemoved); connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, &QLimitProxyModelQML::sourceRowsRemoved); } Q_EMIT modelChanged(); } } int QLimitProxyModelQML::rowCount(const QModelIndex &parent) const { if (parent.isValid()) // We are not a tree return 0; const int unlimitedCount = QIdentityProxyModel::rowCount(parent); return m_limit < 0 ? unlimitedCount : qMin(m_limit, unlimitedCount); } int QLimitProxyModelQML::limit() const { return m_limit; } void QLimitProxyModelQML::setLimit(int limit) { if (limit != m_limit) { bool inserting = false; bool removing = false; const int oldCount = rowCount(); const int unlimitedCount = QIdentityProxyModel::rowCount(); if (m_limit < 0) { if (limit < oldCount) { removing = true; beginRemoveRows(QModelIndex(), limit, oldCount - 1); } } else if (limit < 0) { if (m_limit < unlimitedCount) { inserting = true; beginInsertRows(QModelIndex(), m_limit, unlimitedCount - 1); } } else { if (limit > m_limit && unlimitedCount > m_limit) { inserting = true; beginInsertRows(QModelIndex(), m_limit, qMin(limit, unlimitedCount) - 1); } else if (limit < m_limit && limit < oldCount) { removing = true; beginRemoveRows(QModelIndex(), limit, oldCount - 1); } } m_limit = limit; if (inserting) { endInsertRows(); } else if (removing) { endRemoveRows(); } Q_EMIT limitChanged(); } } void QLimitProxyModelQML::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end) { if (m_limit < 0) { beginInsertRows(mapFromSource(parent), start, end); m_sourceInserting = true; } else if (start < m_limit) { const int nSourceAddedItems = end - start + 1; const int currentCount = QIdentityProxyModel::rowCount(); if (currentCount + nSourceAddedItems <= m_limit) { // After Inserting items we will be under the limit // so just proceed with the insertion normally beginInsertRows(mapFromSource(parent), start, end); m_sourceInserting = true; } else if (currentCount >= m_limit) { // We are already over the limit so to our users we are not adding items, just // changing it's data, i.e we had something like // A B C D E // with a limit of 5 // after inserting (let's say three 'F' at position 1) we will have // A F F F B // so we just need to signal a dataChanged from 1 to 4 m_dataChangedBegin = start; m_dataChangedEnd = m_limit - 1; } else { // currentCount < m_limit && currentCount + nSourceAddedItems > m_limit // We have less items than the limit but after adding them we will be over // To our users this means we need to insert some items and change the // data of some others, i.e we had something like // A B C // with a limit of 5 // after inserting (let's say three 'F' at position 1) we will have // A F F F B // so we need to signal an insetion from position 1 to 2, instead of from // position 1 to 3 and a after that a data changed from 3 to 4 const int nItemsToInsert = m_limit - currentCount; beginInsertRows(mapFromSource(parent), start, start + nItemsToInsert - 1); m_sourceInserting = true; m_dataChangedBegin = start + nItemsToInsert; m_dataChangedEnd = m_limit - 1; if (m_dataChangedBegin > m_dataChangedEnd) { // Just in case we were empty and insert 6 items with a limit of 5 // We don't want to signal a dataChanged from 5 to 4 m_dataChangedBegin = -1; m_dataChangedEnd = -1; } } } } void QLimitProxyModelQML::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { if (m_limit < 0) { beginRemoveRows(mapFromSource(parent), start, end); m_sourceRemoving = true; } else if (start < m_limit) { const int nSourceRemovedItems = end - start + 1; const int currentCount = QIdentityProxyModel::rowCount(); if (currentCount <= m_limit) { // We are already under the limit so // so just proceed with the removal normally beginRemoveRows(mapFromSource(parent), start, end); m_sourceRemoving = true; } else if (currentCount - nSourceRemovedItems >= m_limit) { // Even after removing items we will be at or over the limit // So to our users we are not removing anything, just changing the data // i.e. we had a internal model with // A B C D E F G H // and a limit of 5, our users just see // A B C D E // so if we remove 3 items starting at 1 we have to expose // A E F G H // that is, a dataChanged from 1 to 4 m_dataChangedBegin = start; m_dataChangedEnd = m_limit - 1; } else { // currentCount > m_limit && currentCount - nSourceRemovedItems < m_limit // We have more items than the limit but after removing we will be below it // So to our users we both removing and changing the data // i.e. we had a internal model with // A B C D E F G // and a limit of 5, our users just see // A B C D E // so if we remove items from 1 to 3 we have to expose // A E F G // that is, a remove from 4 to 4 and a dataChanged from 1 to 3 const int nItemsToRemove = m_limit - (currentCount - nSourceRemovedItems); beginRemoveRows(mapFromSource(parent), m_limit - nItemsToRemove, m_limit - 1); m_sourceRemoving = true; m_dataChangedBegin = start; m_dataChangedEnd = m_limit - nItemsToRemove - 1; if (m_dataChangedBegin > m_dataChangedEnd) { m_dataChangedBegin = -1; m_dataChangedEnd = -1; } } } } void QLimitProxyModelQML::sourceRowsInserted(const QModelIndex & /*parent*/, int /*start*/, int /*end*/) { if (m_sourceInserting) { endInsertRows(); m_sourceInserting = false; } if (m_dataChangedBegin != -1 && m_dataChangedEnd != -1) { dataChanged(index(m_dataChangedBegin, 0), index(m_dataChangedEnd, 0)); m_dataChangedBegin = -1; m_dataChangedEnd = -1; } } void QLimitProxyModelQML::sourceRowsRemoved(const QModelIndex & /*parent*/, int /*start*/, int /*end*/) { if (m_sourceRemoving) { endRemoveRows(); m_sourceRemoving = false; } if (m_dataChangedBegin != -1 && m_dataChangedEnd != -1) { dataChanged(index(m_dataChangedBegin, 0), index(m_dataChangedEnd, 0)); m_dataChangedBegin = -1; m_dataChangedEnd = -1; } }