diff --git a/libdiscover/resources/AbstractResourcesBackend.cpp b/libdiscover/resources/AbstractResourcesBackend.cpp index 4d0b92b6..75ff3c58 100644 --- a/libdiscover/resources/AbstractResourcesBackend.cpp +++ b/libdiscover/resources/AbstractResourcesBackend.cpp @@ -1,137 +1,134 @@ /*************************************************************************** * Copyright © 2012 Aleix Pol Gonzalez * * * * 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; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 . * ***************************************************************************/ #include "AbstractResourcesBackend.h" #include "AbstractResource.h" #include "Category/Category.h" #include #include #include #include #include QDebug operator<<(QDebug debug, const AbstractResourcesBackend::Filters& filters) { QDebugStateSaver saver(debug); debug.nospace() << "Filters("; if (filters.category) debug.nospace() << "category: " << filters.category << ','; if (filters.state) debug.nospace() << "state: " << filters.state << ','; if (!filters.mimetype.isEmpty()) debug.nospace() << "mimetype: " << filters.mimetype << ','; if (!filters.search.isEmpty()) debug.nospace() << "search: " << filters.search << ','; if (!filters.extends.isEmpty()) debug.nospace() << "extends:" << filters.extends << ','; - if (!filters.roles.isEmpty()) debug.nospace() << "roles:" << filters.roles << ','; + if (!filters.origin.isEmpty()) debug.nospace() << "origin:" << filters.origin << ','; debug.nospace() << ')'; return debug; } ResultsStream::ResultsStream(const QString &objectName, const QVector& resources) : ResultsStream(objectName) { Q_ASSERT(!resources.contains(nullptr)); QTimer::singleShot(0, this, [resources, this] () { if (!resources.isEmpty()) Q_EMIT resourcesFound(resources); finish(); }); } ResultsStream::ResultsStream(const QString &objectName) { setObjectName(objectName); QTimer::singleShot(5000, this, [objectName]() { qDebug() << "stream took really long" << objectName; }); } ResultsStream::~ResultsStream() { } void ResultsStream::finish() { deleteLater(); } AbstractResourcesBackend::AbstractResourcesBackend(QObject* parent) : QObject(parent) { } Transaction* AbstractResourcesBackend::installApplication(AbstractResource* app) { return installApplication(app, AddonList()); } void AbstractResourcesBackend::setName(const QString& name) { m_name = name; } QString AbstractResourcesBackend::name() const { return m_name; } void AbstractResourcesBackend::emitRatingsReady() { emit allDataChanged({ "rating", "ratingPoints", "ratingCount", "sortableRating" }); } bool AbstractResourcesBackend::Filters::shouldFilter(AbstractResource* res) const { Q_ASSERT(res); if(!extends.isEmpty() && !res->extends().contains(extends)) { return false; } if(!resourceUrl.isEmpty() && res->url() != resourceUrl) { return false; } - for(QHash::const_iterator it=roles.constBegin(), itEnd=roles.constEnd(); it!=itEnd; ++it) { - Q_ASSERT(AbstractResource::staticMetaObject.indexOfProperty(it.key().constData())>=0); - if(res->property(it.key().constData()) != it.value()) { - return false; - } + if(!origin.isEmpty() && res->origin() != origin) { + return false; } if(res->state() < state) return false; if(!mimetype.isEmpty() && !res->mimetypes().contains(mimetype)) { return false; } return !category || res->categoryMatches(category); } void AbstractResourcesBackend::Filters::filterJustInCase(QVector& input) const { for(auto it = input.begin(); it != input.end();) { if (shouldFilter(*it)) ++it; else it = input.erase(it); } } QStringList AbstractResourcesBackend::extends() const { return {}; } diff --git a/libdiscover/resources/AbstractResourcesBackend.h b/libdiscover/resources/AbstractResourcesBackend.h index 13ec3263..002688c9 100644 --- a/libdiscover/resources/AbstractResourcesBackend.h +++ b/libdiscover/resources/AbstractResourcesBackend.h @@ -1,271 +1,271 @@ /*************************************************************************** * Copyright ?? 2012 Aleix Pol Gonzalez * * * * 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; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 . * ***************************************************************************/ #ifndef ABSTRACTRESOURCESBACKEND_H #define ABSTRACTRESOURCESBACKEND_H #include #include #include #include "AbstractResource.h" #include "Transaction/AddonList.h" #include "discovercommon_export.h" class QAction; class Transaction; class Category; class AbstractReviewsBackend; class AbstractBackendUpdater; class DISCOVERCOMMON_EXPORT ResultsStream : public QObject { Q_OBJECT public: ResultsStream(const QString &objectName); /// assumes all the information is in @p resources ResultsStream(const QString &objectName, const QVector& resources); ~ResultsStream() override; void finish(); Q_SIGNALS: void resourcesFound(const QVector& resources); }; /** * \class AbstractResourcesBackend AbstractResourcesBackend.h "AbstractResourcesBackend.h" * * \brief This is the base class of all resource backends. * * For writing basic new resource backends, we need to implement two classes: this and the * AbstractResource one. Basic questions on how to build your plugin with those classes * can be answered by looking at the dummy plugin. * * As this is the base class of a backend, we save all the created resources here and also * accept calls to install and remove applications or to cancel transactions. * * To show resources in Muon, we need to initialize all resources we want to show beforehand, * we should not create resources in the search function. When we reload the resources * (e.g. when initializing), the backend needs change the fetching property throughout the * processs. */ class DISCOVERCOMMON_EXPORT AbstractResourcesBackend : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString displayName READ displayName CONSTANT) Q_PROPERTY(AbstractReviewsBackend* reviewsBackend READ reviewsBackend CONSTANT) Q_PROPERTY(int updatesCount READ updatesCount NOTIFY updatesCountChanged) Q_PROPERTY(bool isFetching READ isFetching NOTIFY fetchingChanged) Q_PROPERTY(QList messageActions READ messageActions CONSTANT) public: /** * Constructs an AbstractResourcesBackend * @param parent the parent of the class (the object will be deleted when the parent gets deleted) */ explicit AbstractResourcesBackend(QObject* parent = nullptr); /** * @returns true when the backend is in a valid state, which means it is able to work * You must return true here if you want the backend to be loaded. */ virtual bool isValid() const = 0; struct Filters { Category* category = nullptr; AbstractResource::State state = AbstractResource::Broken; QString mimetype; QString search; QString extends; QUrl resourceUrl; - QHash roles; + QString origin; bool allBackends = false; bool shouldFilter(AbstractResource* res) const; void filterJustInCase(QVector& input) const; }; /** * @returns a stream that will provide elements that match the search */ virtual ResultsStream* search(const Filters &search) = 0;//FIXME: Probably provide a standard implementation?! virtual ResultsStream* findResourceByPackageName(const QUrl &search) = 0;//FIXME: Probably provide a standard implementation?! /** * @returns the reviews backend of this AbstractResourcesBackend (which handles all ratings and reviews of resources) */ virtual AbstractReviewsBackend* reviewsBackend() const = 0;//FIXME: Have a standard impl which returns 0? /** * @returns the class which is used by muon to update the users system, if you are unsure what to do * just return the StandardBackendUpdater */ virtual AbstractBackendUpdater* backendUpdater() const = 0;//FIXME: Standard impl returning the standard updater? /** * @returns the number of resources for which an update is available, it should only count technical packages */ virtual int updatesCount() const = 0;//FIXME: Probably provide a standard implementation?! /** * Tells whether the backend is fetching resources */ virtual bool isFetching() const = 0; /** * This method is used to integrate advanced functions into the Muon GUI. * * In plasma-discover-updater, actions with HighPriority will be shown in a KMessageWidget, * normal priority will go right on top of the more menu, low priority will go * to the advanced menu. */ virtual QList messageActions() const = 0; /** * @returns the appstream ids that this backend extends */ virtual QStringList extends() const; /** @returns the plugin's name */ QString name() const; /** @internal only to be used by the factory */ void setName(const QString& name); virtual QString displayName() const = 0; /** * emits a change for all rating properties */ void emitRatingsReady(); virtual AbstractResource* resourceForFile(const QUrl &/*url*/) { return nullptr; } /** * @returns the root category tree */ virtual QVector category() const { return {}; } virtual bool hasApplications() const { return false; } public Q_SLOTS: /** * This gets called when the backend should install an application. * The AbstractResourcesBackend should create a Transaction object, is returned and * will be included in the TransactionModel * @param app the application to be installed * @param addons the addons which should be installed with the application * @returns the Transaction that keeps track of the installation process */ virtual Transaction* installApplication(AbstractResource *app, const AddonList& addons) = 0; /** * Overloaded function, which simply does the same, except not installing any addons. */ virtual Transaction* installApplication(AbstractResource *app); /** * This gets called when the backend should remove an application. * Like in the installApplication() method, we'll return the Transaction * responsible for the removal. * * @see installApplication * @param app the application to be removed * @returns the Transaction that keeps track of the removal process */ virtual Transaction* removeApplication(AbstractResource *app) = 0; /** * Notifies the backend that the user wants the information to be up to date */ virtual void checkForUpdates() = 0; Q_SIGNALS: /** * Notify of a change in the backend */ void fetchingChanged(); /** * This should be emitted when the number of upgradeable packages changed. */ void updatesCountChanged(); /** * This should be emitted when all data of the backends resources changed. Internally it will emit * a signal in the model to show the view that all data of a certain backend changed. */ void allDataChanged(const QVector &propertyNames); /** * Allows to notify some @p properties in @p resource have changed */ void resourcesChanged(AbstractResource* resource, const QVector &properties); void resourceRemoved(AbstractResource* resource); void passiveMessage(const QString &message); private: QString m_name; }; template static T containerValues(const W& container) { T ret; ret.reserve(container.size()); for(auto a : container) { ret.push_back(a); } return ret; } DISCOVERCOMMON_EXPORT QDebug operator<<(QDebug dbg, const AbstractResourcesBackend::Filters& filters); /** * @internal Workaround because QPluginLoader enforces 1 instance per plugin */ class DISCOVERCOMMON_EXPORT AbstractResourcesBackendFactory : public QObject { Q_OBJECT public: virtual QVector newInstance(QObject* parent, const QString &name) const = 0; }; #define MUON_BACKEND_PLUGIN(ClassName)\ class ClassName##Factory : public AbstractResourcesBackendFactory {\ Q_OBJECT\ Q_PLUGIN_METADATA(IID "org.kde.muon.AbstractResourcesBackendFactory")\ Q_INTERFACES(AbstractResourcesBackendFactory)\ public:\ QVector newInstance(QObject* parent, const QString &name) const override {\ auto c = new ClassName(parent);\ c->setName(name);\ return {c};\ }\ }; Q_DECLARE_INTERFACE( AbstractResourcesBackendFactory, "org.kde.muon.AbstractResourcesBackendFactory" ) #endif // ABSTRACTRESOURCESBACKEND_H diff --git a/libdiscover/resources/ResourcesProxyModel.cpp b/libdiscover/resources/ResourcesProxyModel.cpp index 6e762b1c..ec35777b 100644 --- a/libdiscover/resources/ResourcesProxyModel.cpp +++ b/libdiscover/resources/ResourcesProxyModel.cpp @@ -1,542 +1,539 @@ /*************************************************************************** * Copyright © 2010 Jonathan Thomas * * Copyright © 2012 Aleix Pol Gonzalez * * * * 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; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 . * ***************************************************************************/ #include "ResourcesProxyModel.h" #include #include #include #include "ResourcesModel.h" #include "AbstractResource.h" #include "AbstractResourcesBackend.h" #include #include #include ResourcesProxyModel::ResourcesProxyModel(QObject *parent) : QAbstractListModel(parent) , m_sortRole(NameRole) , m_sortOrder(Qt::AscendingOrder) , m_sortByRelevancy(false) , m_roles({ { NameRole, "name" }, { IconRole, "icon" }, { CommentRole, "comment" }, { StateRole, "state" }, { RatingRole, "rating" }, { RatingPointsRole, "ratingPoints" }, { RatingCountRole, "ratingCount" }, { SortableRatingRole, "sortableRating" }, { InstalledRole, "isInstalled" }, { ApplicationRole, "application" }, { OriginRole, "origin" }, { DisplayOriginRole, "displayOrigin" }, { CanUpgrade, "canUpgrade" }, { PackageNameRole, "packageName" }, { IsTechnicalRole, "isTechnical" }, { CategoryRole, "category" }, { CategoryDisplayRole, "categoryDisplay" }, { SectionRole, "section" }, { MimeTypes, "mimetypes" }, { LongDescriptionRole, "longDescription" }, { SizeRole, "size" } }) , m_currentStream(nullptr) { // new ModelTest(this, this); connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, this, &ResourcesProxyModel::invalidateFilter); connect(ResourcesModel::global(), &ResourcesModel::backendDataChanged, this, &ResourcesProxyModel::refreshBackend); connect(ResourcesModel::global(), &ResourcesModel::resourceDataChanged, this, &ResourcesProxyModel::refreshResource); connect(ResourcesModel::global(), &ResourcesModel::resourceRemoved, this, &ResourcesProxyModel::removeResource); } void ResourcesProxyModel::componentComplete() { m_setup = true; invalidateFilter(); } QHash ResourcesProxyModel::roleNames() const { return m_roles; } void ResourcesProxyModel::setSortRole(Roles sortRole) { if (sortRole != m_sortRole) { Q_ASSERT(roleNames().contains(sortRole)); m_sortRole = sortRole; Q_EMIT sortRoleChanged(sortRole); invalidateSorting(); } } void ResourcesProxyModel::setSortOrder(Qt::SortOrder sortOrder) { if (sortOrder != m_sortOrder) { m_sortOrder = sortOrder; Q_EMIT sortRoleChanged(sortOrder); invalidateSorting(); } } void ResourcesProxyModel::setSearch(const QString &_searchText) { // 1-character searches are painfully slow. >= 2 chars are fine, though const QString searchText = _searchText.count() <= 1 ? QString() : _searchText; const bool diff = searchText != m_filters.search; if (diff) { m_filters.search = searchText; m_sortByRelevancy = !searchText.isEmpty(); invalidateFilter(); Q_EMIT searchChanged(m_filters.search); } } void ResourcesProxyModel::removeDuplicates(QVector& resources) { const auto cab = ResourcesModel::global()->currentApplicationBackend(); QHash::iterator> storedIds; for(auto it = m_displayedResources.begin(); it != m_displayedResources.end(); ++it) { const auto appstreamid = (*it)->appstreamId(); if (appstreamid.isEmpty()) { continue; } auto at = storedIds.find(appstreamid); if (at == storedIds.end()) { storedIds[appstreamid] = it; } else { qWarning() << "We should have sanitized the displayed resources. There is a bug"; Q_UNREACHABLE(); } } QHash::iterator> ids; for(auto it = resources.begin(); it != resources.end(); ) { const auto appstreamid = (*it)->appstreamId(); if (appstreamid.isEmpty()) { ++it; continue; } auto at = storedIds.find(appstreamid); if (at == storedIds.end()) { auto at = ids.find(appstreamid); if (at == ids.end()) { ids[appstreamid] = it; ++it; } else { if ((*it)->backend() == cab) { qSwap(*it, **at); } it = resources.erase(it); } } else { if ((*it)->backend() == cab) { **at = *it; auto pos = index(*at - m_displayedResources.begin(), 0); dataChanged(pos, pos); } it = resources.erase(it); } } } void ResourcesProxyModel::addResources(const QVector& _res) { auto res = _res; m_filters.filterJustInCase(res); if (res.isEmpty()) return; if (!m_filters.allBackends) { removeDuplicates(res); } if (!m_sortByRelevancy) qSort(res.begin(), res.end(), [this](AbstractResource* res, AbstractResource* res2){ return lessThan(res, res2); }); sortedInsertion(res); fetchSubcategories(); } void ResourcesProxyModel::invalidateSorting() { if (m_displayedResources.isEmpty()) return; if (!m_sortByRelevancy) { beginResetModel(); qSort(m_displayedResources.begin(), m_displayedResources.end(), [this](AbstractResource* res, AbstractResource* res2){ return lessThan(res, res2); }); endResetModel(); } } QString ResourcesProxyModel::lastSearch() const { return m_filters.search; } void ResourcesProxyModel::setOriginFilter(const QString &origin) { - if (origin == originFilter()) + if (origin == m_filters.origin) return; - if(origin.isEmpty()) - m_filters.roles.remove("origin"); - else - m_filters.roles.insert("origin", origin); + m_filters.origin = origin; invalidateFilter(); } QString ResourcesProxyModel::originFilter() const { - return m_filters.roles.value("origin").toString(); + return m_filters.origin; } void ResourcesProxyModel::setFiltersFromCategory(Category *category) { if(category==m_filters.category) return; m_filters.category = category; invalidateFilter(); emit categoryChanged(); } void ResourcesProxyModel::fetchSubcategories() { auto cats = (m_filters.category ? m_filters.category->subCategories() : CategoryModel::global()->rootCategories()).toList().toSet(); const int count = rowCount(); QSet done; for (int i=0; icategoryObjects(cats.toList().toVector()); done.unite(found); cats.subtract(found); } const QVariantList ret = kTransform(done, [](Category* cat) { return QVariant::fromValue(cat); }); if (ret != m_subcategories) { m_subcategories = ret; Q_EMIT subcategoriesChanged(m_subcategories); } } QVariantList ResourcesProxyModel::subcategories() const { return m_subcategories; } void ResourcesProxyModel::invalidateFilter() { if (!m_setup || ResourcesModel::global()->backends().isEmpty()) { return; } if (m_currentStream) { qWarning() << "last stream isn't over yet" << m_filters << this; delete m_currentStream; } m_currentStream = ResourcesModel::global()->search(m_filters); if (!m_displayedResources.isEmpty()) { beginResetModel(); m_displayedResources.clear(); endResetModel(); } connect(m_currentStream, &AggregatedResultsStream::resourcesFound, this, &ResourcesProxyModel::addResources); connect(m_currentStream, &AggregatedResultsStream::finished, this, [this]() { m_currentStream = nullptr; Q_EMIT busyChanged(false); }); Q_EMIT busyChanged(true); } int ResourcesProxyModel::rowCount(const QModelIndex& parent) const { return parent.isValid() ? 0 : m_displayedResources.count(); } bool ResourcesProxyModel::lessThan(AbstractResource* leftPackage, AbstractResource* rightPackage) const { auto role = m_sortRole; Qt::SortOrder order = m_sortOrder; QVariant leftValue; QVariant rightValue; //if we're comparing two equal values, we want the model sorted by application name if(role != NameRole) { leftValue = roleToValue(leftPackage, role); rightValue = roleToValue(rightPackage, role); if (leftValue == rightValue) { role = NameRole; order = Qt::DescendingOrder; } } bool ret; if(role == NameRole) { ret = leftPackage->nameSortKey().compare(rightPackage->nameSortKey()) < 0; } else if(role == CanUpgrade) { ret = leftValue.toBool(); } else { ret = leftValue < rightValue; } return ret != (order != Qt::AscendingOrder); } Category* ResourcesProxyModel::filteredCategory() const { return m_filters.category; } void ResourcesProxyModel::setStateFilter(AbstractResource::State s) { if (s != m_filters.state) { m_filters.state = s; invalidateFilter(); emit stateFilterChanged(); } } AbstractResource::State ResourcesProxyModel::stateFilter() const { return m_filters.state; } QString ResourcesProxyModel::mimeTypeFilter() const { return m_filters.mimetype; } void ResourcesProxyModel::setMimeTypeFilter(const QString& mime) { if (m_filters.mimetype != mime) { m_filters.mimetype = mime; invalidateFilter(); } } QString ResourcesProxyModel::extends() const { return m_filters.extends; } void ResourcesProxyModel::setExtends(const QString& extends) { if (m_filters.extends != extends) { m_filters.extends = extends; invalidateFilter(); } } QUrl ResourcesProxyModel::resourcesUrl() const { return m_filters.resourceUrl; } void ResourcesProxyModel::setResourcesUrl(const QUrl& resourcesUrl) { if (m_filters.resourceUrl != resourcesUrl) { m_filters.resourceUrl = resourcesUrl; invalidateFilter(); } } bool ResourcesProxyModel::allBackends() const { return m_filters.allBackends; } void ResourcesProxyModel::setAllBackends(bool allBackends) { m_filters.allBackends = allBackends; } QVariant ResourcesProxyModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } AbstractResource* const resource = m_displayedResources[index.row()]; return roleToValue(resource, role); } QVariant ResourcesProxyModel::roleToValue(AbstractResource* resource, int role) const { switch(role) { case ApplicationRole: return qVariantFromValue(resource); case RatingPointsRole: case RatingRole: case RatingCountRole: case SortableRatingRole: { Rating* const rating = resource->rating(); const int idx = Rating::staticMetaObject.indexOfProperty(roleNames().value(role).constData()); Q_ASSERT(idx >= 0); auto prop = Rating::staticMetaObject.property(idx); if (rating) return prop.read(rating); else { QVariant val(0); val.convert(prop.type()); return val; } } case Qt::DecorationRole: case Qt::DisplayRole: case Qt::StatusTipRole: case Qt::ToolTipRole: return QVariant(); default: { QByteArray roleText = roleNames().value(role); if(Q_UNLIKELY(roleText.isEmpty())) { qDebug() << "unsupported role" << role; return {}; } static const QMetaObject* m = &AbstractResource::staticMetaObject; int propidx = roleText.isEmpty() ? -1 : m->indexOfProperty(roleText.constData()); if(Q_UNLIKELY(propidx < 0)) { qWarning() << "unknown role:" << role << roleText; return QVariant(); } else return m->property(propidx).read(resource); } } } void ResourcesProxyModel::sortedInsertion(const QVector & resources) { Q_ASSERT(!resources.isEmpty()); if (m_sortByRelevancy || m_displayedResources.isEmpty()) { int rows = rowCount(); beginInsertRows({}, rows, rows+resources.count()-1); m_displayedResources += resources; endInsertRows(); return; } for(auto resource: resources) { int newIdx = 0; const auto finder = [this, resource](AbstractResource* res){ return lessThan(resource, res); }; const auto it = std::find_if(m_displayedResources.constBegin() + newIdx, m_displayedResources.constEnd(), finder); newIdx = it == m_displayedResources.constEnd() ? m_displayedResources.count() : (it - m_displayedResources.constBegin()); beginInsertRows({}, newIdx, newIdx); m_displayedResources.insert(newIdx, resource); endInsertRows(); } } void ResourcesProxyModel::refreshResource(AbstractResource* resource, const QVector& properties) { const auto residx = m_displayedResources.indexOf(resource); if (residx<0) { if (!m_sortByRelevancy && m_filters.shouldFilter(resource)) { sortedInsertion({resource}); } return; } if (!m_filters.shouldFilter(resource)) { beginRemoveRows({}, residx, residx); m_displayedResources.removeAt(residx); endRemoveRows(); return; } const QModelIndex idx = index(residx, 0); Q_ASSERT(idx.isValid()); const auto roles = propertiesToRoles(properties); if (roles.contains(m_sortRole)) { beginRemoveRows({}, residx, residx); m_displayedResources.removeAt(residx); endRemoveRows(); sortedInsertion({resource}); } else emit dataChanged(idx, idx, roles); } void ResourcesProxyModel::removeResource(AbstractResource* resource) { const auto residx = m_displayedResources.indexOf(resource); if (residx < 0) return; beginRemoveRows({}, residx, residx); m_displayedResources.removeAt(residx); endRemoveRows(); } void ResourcesProxyModel::refreshBackend(AbstractResourcesBackend* backend, const QVector& properties) { auto roles = propertiesToRoles(properties); const int count = m_displayedResources.count(); bool found = false; for(int i = 0; ibackend()) continue; int j = i+1; for(; jbackend(); ++j) {} Q_EMIT dataChanged(index(i, 0), index(j-1, 0), roles); i = j; found = true; } if (found && properties.contains(m_roles.value(m_sortRole))) { invalidateSorting(); } } QVector ResourcesProxyModel::propertiesToRoles(const QVector& properties) const { QVector roles = kTransform>(properties, [this](const QByteArray& arr) { return roleNames().key(arr, -1); }); roles.removeAll(-1); return roles; } int ResourcesProxyModel::indexOf(AbstractResource* res) { return m_displayedResources.indexOf(res); } AbstractResource * ResourcesProxyModel::resourceAt(int row) const { return m_displayedResources[row]; }