diff --git a/discover/qml/ApplicationDelegate.qml b/discover/qml/ApplicationDelegate.qml index de6a3275..812fab2e 100644 --- a/discover/qml/ApplicationDelegate.qml +++ b/discover/qml/ApplicationDelegate.qml @@ -1,133 +1,156 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * 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 Library/Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.1 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.1 import "navigation.js" as Navigation import org.kde.kirigami 2.6 as Kirigami Kirigami.AbstractCard { id: delegateArea property alias application: installButton.application property bool compact: false property bool showRating: true showClickFeedback: true function trigger() { if (delegateRecycler.ListView.view) delegateRecycler.ListView.view.currentIndex = index Navigation.openApplication(application) } highlighted: delegateRecycler && delegateRecycler.ListView.isCurrentItem Keys.onReturnPressed: trigger() onClicked: trigger() + ToolTip { + visible: parent.hovered + width: sources.width + leftMargin + rightMargin + height: sources.height + bottomMargin + topMargin + Column { + id: sources + Repeater { + model: duplicates + delegate: RowLayout { + Kirigami.Icon { + width: Kirigami.Units.gridUnit + height: Kirigami.Units.gridUnit + source: modelData.sourceIcon + } + + Kirigami.Label { + text: modelData.availableVersion + } + } + } + } + } + contentItem: Item { implicitHeight: delegateArea.compact ? Kirigami.Units.gridUnit * 2 : Kirigami.Units.gridUnit * 4 Kirigami.Icon { id: resourceIcon source: application.icon readonly property real contHeight: delegateArea.compact ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 5 height: contHeight width: contHeight anchors { verticalCenter: parent.verticalCenter left: parent.left } } GridLayout { columnSpacing: delegateArea.compact ? 0 : 5 rowSpacing: delegateArea.compact ? 0 : 5 anchors { verticalCenter: parent.verticalCenter right: parent.right left: resourceIcon.right leftMargin: Kirigami.Units.largeSpacing } columns: 2 rows: delegateArea.compact ? 4 : 3 RowLayout { Layout.fillWidth: true readonly property bool bigTitle: (head.implicitWidth + installButton.width) > parent.width Kirigami.Heading { id: head level: delegateArea.compact ? 3 : 2 Layout.fillWidth: !category.visible || parent.bigTitle elide: Text.ElideRight text: delegateArea.application.name maximumLineCount: 1 } Kirigami.Heading { id: category level: 5 Layout.fillWidth: true elide: Text.ElideRight text: i18nc("Part of a string like this: ' - '", "- %1", delegateArea.application.categoryDisplay) maximumLineCount: 1 opacity: 0.6 visible: delegateArea.application.categoryDisplay && delegateArea.application.categoryDisplay !== page.title && !parent.bigTitle } } InstallApplicationButton { id: installButton Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.rowSpan: delegateArea.compact ? 3 : 1 compact: delegateArea.compact } RowLayout { visible: showRating spacing: Kirigami.Units.largeSpacing Layout.fillWidth: true Rating { rating: delegateArea.application.rating ? delegateArea.application.rating.sortableRating : 0 starSize: delegateArea.compact ? summary.font.pointSize : head.font.pointSize } Label { Layout.fillWidth: true text: delegateArea.application.rating ? i18np("%1 rating", "%1 ratings", delegateArea.application.rating.ratingCount) : i18n("No ratings yet") visible: delegateArea.application.rating || delegateArea.application.backend.reviewsBackend.isResourceSupported(delegateArea.application) opacity: 0.5 elide: Text.ElideRight } } Label { Layout.columnSpan: delegateArea.compact ? 1 : 2 id: summary Layout.fillWidth: true bottomPadding: Kirigami.Units.smallSpacing elide: Text.ElideRight text: delegateArea.application.comment maximumLineCount: 1 textFormat: Text.PlainText } } } } diff --git a/libdiscover/resources/ResourcesProxyModel.cpp b/libdiscover/resources/ResourcesProxyModel.cpp index 42315777..83f0768d 100644 --- a/libdiscover/resources/ResourcesProxyModel.cpp +++ b/libdiscover/resources/ResourcesProxyModel.cpp @@ -1,601 +1,628 @@ /*************************************************************************** * 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 "libdiscover_debug.h" #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" }, { CategoryRole, "category" }, { CategoryDisplayRole, "categoryDisplay" }, { SectionRole, "section" }, { MimeTypes, "mimetypes" }, { LongDescriptionRole, "longDescription" }, { SourceIconRole, "sourceIcon" }, { SizeRole, "size" }, + { DuplicatesRole, "duplicates" }, { ReleaseDateRole, "releaseDate" } }) , m_currentStream(nullptr) { // new QAbstractItemModelTester(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); connect(this, &QAbstractItemModel::modelReset, this, &ResourcesProxyModel::countChanged); connect(this, &QAbstractItemModel::rowsInserted, this, &ResourcesProxyModel::countChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &ResourcesProxyModel::countChanged); } 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 sortOrderChanged(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; if (m_sortByRelevancy == searchText.isEmpty()) { m_sortByRelevancy = !searchText.isEmpty(); Q_EMIT sortByRelevancyChanged(m_sortByRelevancy); } invalidateFilter(); Q_EMIT searchChanged(m_filters.search); } } -void ResourcesProxyModel::removeDuplicates(QVector& resources) +void ResourcesProxyModel::removeDuplicates(QVector& resources) { - const auto cab = ResourcesModel::global()->currentApplicationBackend(); - QHash::iterator> storedIds; + QHash::iterator> storedIds; for(auto it = m_displayedResources.begin(); it != m_displayedResources.end(); ++it) { - const auto appstreamid = (*it)->appstreamId(); + const auto appstreamid = it->resource->appstreamId(); if (appstreamid.isEmpty()) { continue; } auto at = storedIds.find(appstreamid); if (at == storedIds.end()) { storedIds[appstreamid] = it; } else { qCWarning(LIBDISCOVER_LOG) << "We should have sanitized the displayed resources. There is a bug"; Q_UNREACHABLE(); } } - QHash::iterator> ids; + const auto cab = ResourcesModel::global()->currentApplicationBackend(); + QHash::iterator> ids; for(auto it = resources.begin(); it != resources.end(); ) { - const auto appstreamid = (*it)->appstreamId(); + const auto appstreamid = it->resource->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); + if (it->resource->backend() == cab) { + (*at)->dupes += (*at)->resource; + (*at)->resource = it->resource; + std::swap(*it, **at); + } else { + (*at)->dupes += it->resource; } it = resources.erase(it); } } else { - if ((*it)->backend() == cab) { - **at = *it; - auto pos = index(*at - m_displayedResources.begin(), 0); + const auto pos = index(*at - m_displayedResources.begin(), 0); + if (it->resource->backend() == cab) { + (*at)->dupes += (*at)->resource; + (*at)->resource = it->resource; Q_EMIT dataChanged(pos, pos); + } else { + (*at)->dupes += it->resource; + Q_EMIT dataChanged(pos, pos, {DuplicatesRole}); } it = resources.erase(it); } } } void ResourcesProxyModel::addResources(const QVector& _res) { auto res = _res; m_filters.filterJustInCase(res); if (res.isEmpty()) return; + QVector entries = kTransform>(_res, [] (AbstractResource* res) { return ResourcesProxyModelEntry{res, {}}; }); if (!m_sortByRelevancy) - std::sort(res.begin(), res.end(), [this](AbstractResource* res, AbstractResource* res2){ return lessThan(res, res2); }); + std::sort(entries.begin(), entries.end(), lessThanFunction()); - sortedInsertion(res); + sortedInsertion(entries); fetchSubcategories(); } void ResourcesProxyModel::invalidateSorting() { if (m_displayedResources.isEmpty()) return; if (!m_sortByRelevancy) { beginResetModel(); - std::sort(m_displayedResources.begin(), m_displayedResources.end(), [this](AbstractResource* res, AbstractResource* res2){ return lessThan(res, res2); }); + std::sort(m_displayedResources.begin(), m_displayedResources.end(), lessThanFunction()); endResetModel(); } } QString ResourcesProxyModel::lastSearch() const { return m_filters.search; } void ResourcesProxyModel::setOriginFilter(const QString &origin) { if (origin == m_filters.origin) return; m_filters.origin = origin; invalidateFilter(); } QString ResourcesProxyModel::originFilter() const { 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 = kToSet(m_filters.category ? m_filters.category->subCategories() : CategoryModel::global()->rootCategories()); const int count = rowCount(); QSet done; for (int i=0; icategoryObjects(kSetToVector(cats)); 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) { qCWarning(LIBDISCOVER_LOG) << "last stream isn't over yet" << m_filters << this; delete m_currentStream; } m_currentStream = ResourcesModel::global()->search(m_filters); Q_EMIT busyChanged(true); 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); }); } int ResourcesProxyModel::rowCount(const QModelIndex& parent) const { return parent.isValid() ? 0 : m_displayedResources.count(); } -bool ResourcesProxyModel::lessThan(AbstractResource* leftPackage, AbstractResource* rightPackage) const +bool ResourcesProxyModel::lessThan(const ResourcesProxyModelEntry &leftPackage, const ResourcesProxyModelEntry &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::AscendingOrder; } } bool ret; if(role == NameRole) { - ret = leftPackage->nameSortKey().compare(rightPackage->nameSortKey()) < 0; + ret = leftPackage.resource->nameSortKey().compare(rightPackage.resource->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(); } } void ResourcesProxyModel::setFilterMinimumState(bool filterMinimumState) { if (filterMinimumState != m_filters.filterMinimumState) { m_filters.filterMinimumState = filterMinimumState; invalidateFilter(); Q_EMIT filterMinimumStateChanged(m_filters.filterMinimumState); } } bool ResourcesProxyModel::filterMinimumState() const { return m_filters.filterMinimumState; } 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); + return roleToValue(m_displayedResources[index.row()], role); } -QVariant ResourcesProxyModel::roleToValue(AbstractResource* resource, int role) const +QVariant ResourcesProxyModel::roleToValue(const ResourcesProxyModelEntry &entry, int role) const { + const auto &resource = entry.resource; switch(role) { + case DuplicatesRole: { + QVariantList sources = { QVariant::fromValue(entry.resource) }; + for (const auto &resource : entry.dupes) { + sources += QVariant::fromValue(resource); + } + return sources; + } case ApplicationRole: return QVariant::fromValue(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())) { qCDebug(LIBDISCOVER_LOG) << "unsupported role" << role; return {}; } static const QMetaObject* m = &AbstractResource::staticMetaObject; int propidx = roleText.isEmpty() ? -1 : m->indexOfProperty(roleText.constData()); if(Q_UNLIKELY(propidx < 0)) { qCWarning(LIBDISCOVER_LOG) << "unknown role:" << role << roleText; return QVariant(); } else return m->property(propidx).read(resource); } } } -bool ResourcesProxyModel::isSorted(const QVector & resources) +bool ResourcesProxyModel::isSorted(const QVector & resources) { auto last = resources.constFirst(); for(auto it = resources.constBegin()+1, itEnd = resources.constEnd(); it != itEnd; ++it) { if(!lessThan(last, *it)) { return false; } last = *it; } return true; } -void ResourcesProxyModel::sortedInsertion(const QVector & _res) +void ResourcesProxyModel::sortedInsertion(const QVector & _res) { auto resources = _res; Q_ASSERT(!resources.isEmpty()); if (!m_filters.allBackends) { removeDuplicates(resources); if (resources.isEmpty()) return; } if (m_sortByRelevancy || m_displayedResources.isEmpty()) { // Q_ASSERT(m_sortByRelevancy || isSorted(resources)); int rows = rowCount(); beginInsertRows({}, rows, rows+resources.count()-1); - m_displayedResources += resources; + for (auto r : qAsConst(resources)) + m_displayedResources += r; endInsertRows(); return; } - for(auto resource: qAsConst(resources)) { - const auto finder = [this](AbstractResource* resource, AbstractResource* res){ return lessThan(resource, res); }; - const auto it = std::upper_bound(m_displayedResources.constBegin(), m_displayedResources.constEnd(), resource, finder); + for(const auto &entry : qAsConst(resources)) { + const auto it = std::upper_bound(m_displayedResources.constBegin(), m_displayedResources.constEnd(), entry, lessThanFunction()); const auto newIdx = it == m_displayedResources.constEnd() ? m_displayedResources.count() : (it - m_displayedResources.constBegin()); - if ((it-1) != m_displayedResources.constEnd() && *(it-1) == resource) + if ((it-1) != m_displayedResources.constEnd() && (*(it-1)).resource == entry.resource) continue; beginInsertRows({}, newIdx, newIdx); - m_displayedResources.insert(newIdx, resource); + m_displayedResources.insert(newIdx, entry); endInsertRows(); // Q_ASSERT(isSorted(resources)); } } void ResourcesProxyModel::refreshResource(AbstractResource* resource, const QVector& properties) { - const auto residx = m_displayedResources.indexOf(resource); + const auto residx = indexOf(resource); if (residx<0) { if (!m_sortByRelevancy && m_filters.shouldFilter(resource)) { - sortedInsertion({resource}); + sortedInsertion({{resource, {}}}); } return; } if (!m_filters.shouldFilter(resource)) { - beginRemoveRows({}, residx, residx); - m_displayedResources.removeAt(residx); - endRemoveRows(); + removeResource(resource); return; } - const QModelIndex idx = index(residx, 0); - Q_ASSERT(idx.isValid()); const auto roles = propertiesToRoles(properties); if (!m_sortByRelevancy && roles.contains(m_sortRole)) { - beginRemoveRows({}, residx, residx); - m_displayedResources.removeAt(residx); - endRemoveRows(); - - sortedInsertion({resource}); - } else + removeResource(resource); + sortedInsertion({{resource, {}}}); + } else { + const QModelIndex idx = index(residx, 0); + Q_ASSERT(idx.isValid()); emit dataChanged(idx, idx, roles); + } } void ResourcesProxyModel::removeResource(AbstractResource* resource) { - const auto residx = m_displayedResources.indexOf(resource); + const auto residx = indexOf(resource); if (residx < 0) return; - beginRemoveRows({}, residx, residx); - m_displayedResources.removeAt(residx); - endRemoveRows(); + + auto &entry = m_displayedResources[residx]; + if (entry.resource == resource) { + if (entry.dupes.isEmpty()) { + beginRemoveRows({}, residx, residx); + m_displayedResources.removeAt(residx); + endRemoveRows(); + } else { + entry.resource = entry.dupes.takeFirst(); + const QModelIndex pos = index(residx, residx); + Q_EMIT dataChanged(pos, pos); + } + } else { + Q_ASSERT(entry.dupes.contains(resource)); + const QModelIndex pos = index(residx, residx); + Q_EMIT dataChanged(pos, pos, {DuplicatesRole}); + } } 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()) + if (backend != m_displayedResources[i].resource->backend()) continue; int j = i+1; - for(; jbackend(); ++j) + 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) +int ResourcesProxyModel::indexOf(AbstractResource* resource) { - return m_displayedResources.indexOf(res); + return kIndexOf(m_displayedResources, [resource] (const ResourcesProxyModelEntry &entry) { return entry.resource == resource || entry.dupes.contains(resource); }); } AbstractResource * ResourcesProxyModel::resourceAt(int row) const { - return m_displayedResources[row]; + return m_displayedResources[row].resource; } bool ResourcesProxyModel::canFetchMore(const QModelIndex& parent) const { Q_ASSERT(!parent.isValid()); return m_currentStream; } void ResourcesProxyModel::fetchMore(const QModelIndex& parent) { Q_ASSERT(!parent.isValid()); if (!m_currentStream) return; Q_EMIT m_currentStream->fetchMore(); } bool ResourcesProxyModel::sortByRelevancy() const { return m_sortByRelevancy; } diff --git a/libdiscover/resources/ResourcesProxyModel.h b/libdiscover/resources/ResourcesProxyModel.h index 52945acf..e8be47ec 100644 --- a/libdiscover/resources/ResourcesProxyModel.h +++ b/libdiscover/resources/ResourcesProxyModel.h @@ -1,177 +1,190 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef RESOURCESPROXYMODEL_H #define RESOURCESPROXYMODEL_H #include #include #include +#include #include #include #include "discovercommon_export.h" #include "AbstractResource.h" #include "AbstractResourcesBackend.h" class AggregatedResultsStream; +class ResourcesProxyModelEntry +{ +public: + AbstractResource* resource; + QVector dupes; +}; + class DISCOVERCOMMON_EXPORT ResourcesProxyModel : public QAbstractListModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(Roles sortRole READ sortRole WRITE setSortRole NOTIFY sortRoleChanged) Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged) Q_PROPERTY(Category* filteredCategory READ filteredCategory WRITE setFiltersFromCategory NOTIFY categoryChanged) Q_PROPERTY(QString originFilter READ originFilter WRITE setOriginFilter) Q_PROPERTY(AbstractResource::State stateFilter READ stateFilter WRITE setStateFilter NOTIFY stateFilterChanged) Q_PROPERTY(bool filterMinimumState READ filterMinimumState WRITE setFilterMinimumState NOTIFY filterMinimumStateChanged) Q_PROPERTY(QString mimeTypeFilter READ mimeTypeFilter WRITE setMimeTypeFilter) Q_PROPERTY(QString search READ lastSearch WRITE setSearch NOTIFY searchChanged) Q_PROPERTY(QUrl resourcesUrl READ resourcesUrl WRITE setResourcesUrl NOTIFY resourcesUrlChanged) Q_PROPERTY(QString extending READ extends WRITE setExtends) Q_PROPERTY(bool allBackends READ allBackends WRITE setAllBackends) Q_PROPERTY(QVariantList subcategories READ subcategories NOTIFY subcategoriesChanged) Q_PROPERTY(bool isBusy READ isBusy NOTIFY busyChanged) Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(bool sortByRelevancy READ sortByRelevancy NOTIFY sortByRelevancyChanged) public: explicit ResourcesProxyModel(QObject* parent = nullptr); enum Roles { NameRole = Qt::UserRole, IconRole, CommentRole, StateRole, RatingRole, RatingPointsRole, RatingCountRole, SortableRatingRole, InstalledRole, ApplicationRole, OriginRole, DisplayOriginRole, CanUpgrade, PackageNameRole, CategoryRole, CategoryDisplayRole, SectionRole, MimeTypes, SizeRole, LongDescriptionRole, SourceIconRole, + DuplicatesRole, ReleaseDateRole }; Q_ENUM(Roles) QHash roleNames() const override; void setSearch(const QString &text); QString lastSearch() const; void setOriginFilter(const QString &origin); QString originFilter() const; void setFiltersFromCategory(Category *category); void setStateFilter(AbstractResource::State s); AbstractResource::State stateFilter() const; void setSortRole(Roles sortRole); Roles sortRole() const { return m_sortRole; } void setSortOrder(Qt::SortOrder sortOrder); Qt::SortOrder sortOrder() const { return m_sortOrder; } void setFilterMinimumState(bool filterMinimumState); bool filterMinimumState() const; Category* filteredCategory() const; QString mimeTypeFilter() const; void setMimeTypeFilter(const QString& mime); QString extends() const; void setExtends(const QString &extends); QUrl resourcesUrl() const; void setResourcesUrl(const QUrl& resourcesUrl); bool allBackends() const; void setAllBackends(bool allBackends); QVariantList subcategories() const; QVariant data(const QModelIndex & index, int role) const override; int rowCount(const QModelIndex & parent = {}) const override; Q_SCRIPTABLE int indexOf(AbstractResource* res); Q_SCRIPTABLE AbstractResource* resourceAt(int row) const; bool isBusy() const { return m_currentStream != nullptr; } - bool lessThan(AbstractResource* rl, AbstractResource* rr) const; + bool lessThan(const ResourcesProxyModelEntry &rl, const ResourcesProxyModelEntry &rr) const; Q_SCRIPTABLE void invalidateFilter(); void invalidateSorting(); bool canFetchMore(const QModelIndex & parent) const override; void fetchMore(const QModelIndex & parent) override; bool sortByRelevancy() const; void classBegin() override {} void componentComplete() override; private Q_SLOTS: void refreshBackend(AbstractResourcesBackend* backend, const QVector& properties); void refreshResource(AbstractResource* resource, const QVector& properties); void removeResource(AbstractResource* resource); private: - void sortedInsertion(const QVector &res); - QVariant roleToValue(AbstractResource* res, int role) const; + void sortedInsertion(const QVector &res); + QVariant roleToValue(const ResourcesProxyModelEntry &res, int role) const; QVector propertiesToRoles(const QVector& properties) const; void addResources(const QVector &res); void fetchSubcategories(); - void removeDuplicates(QVector& newResources); - bool isSorted(const QVector & resources); + void removeDuplicates(QVector& newResources); + bool isSorted(const QVector & resources); + + std::function lessThanFunction() const { + return [this](const ResourcesProxyModelEntry &rl, const ResourcesProxyModelEntry &rr){ return lessThan(rl, rr); }; + } Roles m_sortRole; Qt::SortOrder m_sortOrder; bool m_sortByRelevancy; bool m_setup = false; AbstractResourcesBackend::Filters m_filters; QVariantList m_subcategories; - QVector m_displayedResources; + QVector m_displayedResources; const QHash m_roles; AggregatedResultsStream* m_currentStream; Q_SIGNALS: void busyChanged(bool isBusy); void sortRoleChanged(int sortRole); void sortOrderChanged(Qt::SortOrder order); void categoryChanged(); void stateFilterChanged(); void searchChanged(const QString &search); void subcategoriesChanged(const QVariantList &subcategories); void resourcesUrlChanged(const QUrl &url); void countChanged(); void filterMinimumStateChanged(bool filterMinimumState); void sortByRelevancyChanged(bool sortByRelevancy); }; #endif