diff --git a/libdiscover/backends/DummyBackend/DummyNotifier.cpp b/libdiscover/backends/DummyBackend/DummyNotifier.cpp index 1ac6a73f..89470fd0 100644 --- a/libdiscover/backends/DummyBackend/DummyNotifier.cpp +++ b/libdiscover/backends/DummyBackend/DummyNotifier.cpp @@ -1,53 +1,51 @@ /*************************************************************************** * Copyright © 2013 Lukas Appelhans * * * * 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 "DummyNotifier.h" #include DummyNotifier::DummyNotifier(QObject* parent) : BackendNotifierModule(parent) { } DummyNotifier::~DummyNotifier() { } void DummyNotifier::recheckSystemUpdateNeeded() { emit foundUpdates(); } bool DummyNotifier::isSystemUpToDate() const { return true; } uint DummyNotifier::securityUpdatesCount() { return 0; } uint DummyNotifier::updatesCount() { return 0; } - -#include "DummyNotifier.moc" diff --git a/libdiscover/backends/DummyBackend/DummyReviewsBackend.cpp b/libdiscover/backends/DummyBackend/DummyReviewsBackend.cpp index 2e7640c9..84d4f0f6 100644 --- a/libdiscover/backends/DummyBackend/DummyReviewsBackend.cpp +++ b/libdiscover/backends/DummyBackend/DummyReviewsBackend.cpp @@ -1,82 +1,82 @@ /*************************************************************************** * Copyright © 2013 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 "DummyReviewsBackend.h" #include "DummyBackend.h" #include "DummyResource.h" #include #include #include #include #include DummyReviewsBackend::DummyReviewsBackend(DummyBackend* parent) : AbstractReviewsBackend(parent) {} void DummyReviewsBackend::fetchReviews(AbstractResource* app, int page) { if (page>=5) return; QVector review; for(int i=0; i<33; i++) { review += ReviewPtr(new Review(app->name(), app->packageName(), QStringLiteral("en_US"), QStringLiteral("good morning"), QStringLiteral("the morning is very good"), QStringLiteral("dummy"), QDateTime(), true, page+i, i%5, 1, 1, app->packageName())); } emit reviewsReady(app, review, false); } Rating* DummyReviewsBackend::ratingForApplication(AbstractResource* app) const { return m_ratings[app]; } void DummyReviewsBackend::initialize() { int i = 11; DummyBackend* b = qobject_cast(parent()); foreach(DummyResource* app, b->resources()) { if (m_ratings.contains(app)) continue; auto randomRating = qrand()%10; Rating* rating = new Rating(app->packageName(), ++i, randomRating, QStringLiteral("\"0, 0, 0, 4, %1\"").arg(randomRating)); rating->setParent(this); m_ratings.insert(app, rating); app->ratingFetched(); } emit ratingsReady(); } void DummyReviewsBackend::submitUsefulness(Review* r, bool useful) { qDebug() << "usefulness..." << r->applicationName() << r->reviewer() << useful; r->setUsefulChoice(useful ? ReviewsModel::Yes : ReviewsModel::No); } void DummyReviewsBackend::submitReview(AbstractResource* res, const QString& a, const QString& b, const QString& c) { qDebug() << "dummy submit review" << res->name() << a << b << c; } -bool DummyReviewsBackend::isResourceSupported(AbstractResource* res) const +bool DummyReviewsBackend::isResourceSupported(AbstractResource* /*res*/) const { return true; } diff --git a/libdiscover/backends/FlatpakBackend/FlatpakNotifier.cpp b/libdiscover/backends/FlatpakBackend/FlatpakNotifier.cpp index d20dc11c..98ad2c27 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakNotifier.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakNotifier.cpp @@ -1,191 +1,189 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2017 Jan Grulich * * * * 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 "FlatpakNotifier.h" #include #include #include #include #include static void installationChanged(GFileMonitor *monitor, GFile *child, GFile *other_file, GFileMonitorEvent event_type, gpointer self) { Q_UNUSED(monitor); Q_UNUSED(child); Q_UNUSED(other_file); Q_UNUSED(event_type); FlatpakNotifier *notifier = (FlatpakNotifier*)self; if (!notifier) { return; } notifier->checkUpdates(); } FlatpakNotifier::FlatpakNotifier(QObject* parent) : BackendNotifierModule(parent) , m_userInstallationUpdates(0) , m_systemInstallationUpdates(0) { m_cancellable = g_cancellable_new(); checkUpdates(); QTimer *dailyCheck = new QTimer(this); dailyCheck->setInterval(24 * 60 * 60 * 1000); //refresh at least once every day connect(dailyCheck, &QTimer::timeout, this, &FlatpakNotifier::checkUpdates); } FlatpakNotifier::~FlatpakNotifier() { g_object_unref(m_userInstallationMonitor); g_object_unref(m_systemInstallationMonitor); g_object_unref(m_flatpakInstallationSystem); g_object_unref(m_flatpakInstallationUser); g_object_unref(m_cancellable); } void FlatpakNotifier::recheckSystemUpdateNeeded() { checkUpdates(); } bool FlatpakNotifier::isSystemUpToDate() const { return !m_systemInstallationUpdates && !m_userInstallationUpdates; } uint FlatpakNotifier::securityUpdatesCount() { return 0; } uint FlatpakNotifier::updatesCount() { return m_systemInstallationUpdates + m_userInstallationUpdates; } void FlatpakNotifier::checkUpdates() { g_autoptr(GError) error = nullptr; // Load flatpak installation if (!setupFlatpakInstallations(&error)) { qWarning() << "Failed to setup flatpak installations: " << error->message; } else { // Load updates from remote repositories loadRemoteUpdates(m_flatpakInstallationSystem); loadRemoteUpdates(m_flatpakInstallationUser); } } void FlatpakNotifier::onFetchUpdatesFinished(FlatpakInstallation *flatpakInstallation, GPtrArray *updates) { bool changed = false; uint validUpdates = 0; g_autoptr(GPtrArray) fetchedUpdates = updates; for (uint i = 0; i < fetchedUpdates->len; i++) { FlatpakInstalledRef *ref = FLATPAK_INSTALLED_REF(g_ptr_array_index(fetchedUpdates, i)); const QString refName = QString::fromUtf8(flatpak_ref_get_name(FLATPAK_REF(ref))); // FIXME right now I can't think of any other filter than this, in FlatpakBackend updates are matched // with apps so .Locale/.Debug subrefs are not shown and updated automatically. Also this will show // updates for refs we don't show in Discover if appstream metadata or desktop file for them is not found if (refName.endsWith(QStringLiteral(".Locale")) || refName.endsWith(QStringLiteral(".Debug"))) { continue; } validUpdates++; } if (flatpak_installation_get_is_user(flatpakInstallation)) { changed = m_userInstallationUpdates != validUpdates; m_userInstallationUpdates = validUpdates; } else { changed = m_systemInstallationUpdates != validUpdates; m_systemInstallationUpdates = validUpdates; } if (changed) { Q_EMIT foundUpdates(); } } void FlatpakNotifier::loadRemoteUpdates(FlatpakInstallation *installation) { auto fw = new QFutureWatcher(this); fw->setFuture(QtConcurrent::run( [installation]() -> GPtrArray * { g_autoptr(GCancellable) cancellable = g_cancellable_new(); g_autoptr(GError) localError = nullptr; GPtrArray *refs = flatpak_installation_list_installed_refs_for_update(installation, cancellable, &localError); if (!refs) { qWarning() << "Failed to get list of installed refs for listing updates: " << localError->message; } return refs; })); connect(fw, &QFutureWatcher::finished, this, [this, installation, fw](){ auto refs = fw->result(); onFetchUpdatesFinished(installation, refs); fw->deleteLater(); }); } bool FlatpakNotifier::setupFlatpakInstallations(GError **error) { if (!m_flatpakInstallationSystem) { m_flatpakInstallationSystem = flatpak_installation_new_system(m_cancellable, error); if (!m_flatpakInstallationSystem) { return false; } } if (!m_flatpakInstallationUser) { m_flatpakInstallationUser = flatpak_installation_new_user(m_cancellable, error); if (!m_flatpakInstallationUser) { return false; } } if (!m_systemInstallationMonitor) { m_systemInstallationMonitor = flatpak_installation_create_monitor(m_flatpakInstallationSystem, m_cancellable, error); if (!m_systemInstallationMonitor) { return false; } g_signal_connect(m_systemInstallationMonitor, "changed", G_CALLBACK(installationChanged), this); } if (!m_userInstallationMonitor) { m_userInstallationMonitor = flatpak_installation_create_monitor(m_flatpakInstallationUser, m_cancellable, error); if (!m_userInstallationMonitor) { return false; } g_signal_connect(m_userInstallationMonitor, "changed", G_CALLBACK(installationChanged), this); } return true; } - -#include "FlatpakNotifier.moc" diff --git a/libdiscover/resources/ResourcesProxyModel.cpp b/libdiscover/resources/ResourcesProxyModel.cpp index 581634f9..62372e68 100644 --- a/libdiscover/resources/ResourcesProxyModel.cpp +++ b/libdiscover/resources/ResourcesProxyModel.cpp @@ -1,547 +1,546 @@ /*************************************************************************** * 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(); ) { const auto appstreamid = (*it)->appstreamId(); if (appstreamid.isEmpty()) { ++it; continue; } auto at = storedIds.find(appstreamid); if (at == storedIds.end()) { storedIds[appstreamid] = it; ++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()) return; if(origin.isEmpty()) m_filters.roles.remove("origin"); else m_filters.roles.insert("origin", origin); invalidateFilter(); } QString ResourcesProxyModel::originFilter() const { return m_filters.roles.value("origin").toString(); } 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, [this](const QVector& resources) { addResources(resources); }); 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]; }