File indexing completed on 2024-05-05 17:33:21

0001 /*
0002  *   SPDX-FileCopyrightText: 2010 Jonathan Thomas <echidnaman@kubuntu.org>
0003  *   SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0004  *
0005  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 #include "ResourcesProxyModel.h"
0009 
0010 #include "libdiscover_debug.h"
0011 #include <QMetaProperty>
0012 #include <cmath>
0013 #include <utils.h>
0014 
0015 #include "ResourcesModel.h"
0016 #include <Category/CategoryModel.h>
0017 #include <KLocalizedString>
0018 #include <ReviewsBackend/Rating.h>
0019 #include <Transaction/TransactionModel.h>
0020 
0021 const QHash<int, QByteArray> ResourcesProxyModel::s_roles = {{NameRole, "name"},
0022                                                              {IconRole, "icon"},
0023                                                              {CommentRole, "comment"},
0024                                                              {StateRole, "state"},
0025                                                              {RatingRole, "rating"},
0026                                                              {RatingPointsRole, "ratingPoints"},
0027                                                              {RatingCountRole, "ratingCount"},
0028                                                              {SortableRatingRole, "sortableRating"},
0029                                                              {InstalledRole, "isInstalled"},
0030                                                              {ApplicationRole, "application"},
0031                                                              {OriginRole, "origin"},
0032                                                              {DisplayOriginRole, "displayOrigin"},
0033                                                              {CanUpgrade, "canUpgrade"},
0034                                                              {PackageNameRole, "packageName"},
0035                                                              {CategoryRole, "category"},
0036                                                              {SectionRole, "section"},
0037                                                              {MimeTypes, "mimetypes"},
0038                                                              {LongDescriptionRole, "longDescription"},
0039                                                              {SourceIconRole, "sourceIcon"},
0040                                                              {SizeRole, "size"},
0041                                                              {ReleaseDateRole, "releaseDate"}};
0042 
0043 ResourcesProxyModel::ResourcesProxyModel(QObject *parent)
0044     : QAbstractListModel(parent)
0045     , m_sortRole(NameRole)
0046     , m_sortOrder(Qt::AscendingOrder)
0047     , m_sortByRelevancy(false)
0048     , m_currentStream(nullptr)
0049 {
0050     // new QAbstractItemModelTester(this, this);
0051 
0052     connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, this, &ResourcesProxyModel::invalidateFilter);
0053     connect(ResourcesModel::global(), &ResourcesModel::backendDataChanged, this, &ResourcesProxyModel::refreshBackend);
0054     connect(ResourcesModel::global(), &ResourcesModel::resourceDataChanged, this, &ResourcesProxyModel::refreshResource);
0055     connect(ResourcesModel::global(), &ResourcesModel::resourceRemoved, this, &ResourcesProxyModel::removeResource);
0056 
0057     m_countTimer.setInterval(10);
0058     m_countTimer.setSingleShot(true);
0059     connect(&m_countTimer, &QTimer::timeout, this, &ResourcesProxyModel::countChanged);
0060 
0061     connect(this, &QAbstractItemModel::modelReset, &m_countTimer, qOverload<>(&QTimer::start));
0062     connect(this, &QAbstractItemModel::rowsInserted, &m_countTimer, qOverload<>(&QTimer::start));
0063     connect(this, &QAbstractItemModel::rowsRemoved, &m_countTimer, qOverload<>(&QTimer::start));
0064 
0065     connect(this, &ResourcesProxyModel::busyChanged, &m_countTimer, qOverload<>(&QTimer::start));
0066 }
0067 
0068 void ResourcesProxyModel::componentComplete()
0069 {
0070     m_setup = true;
0071     invalidateFilter();
0072 }
0073 
0074 QHash<int, QByteArray> ResourcesProxyModel::roleNames() const
0075 {
0076     return s_roles;
0077 }
0078 
0079 void ResourcesProxyModel::setSortRole(Roles sortRole)
0080 {
0081     if (sortRole != m_sortRole) {
0082         Q_ASSERT(roleNames().contains(sortRole));
0083 
0084         m_sortRole = sortRole;
0085         Q_EMIT sortRoleChanged(sortRole);
0086         invalidateSorting();
0087     }
0088 }
0089 
0090 void ResourcesProxyModel::setSortOrder(Qt::SortOrder sortOrder)
0091 {
0092     if (sortOrder != m_sortOrder) {
0093         m_sortOrder = sortOrder;
0094         Q_EMIT sortOrderChanged(sortOrder);
0095         invalidateSorting();
0096     }
0097 }
0098 
0099 void ResourcesProxyModel::setSearch(const QString &_searchText)
0100 {
0101     // 1-character searches are painfully slow. >= 2 chars are fine, though
0102     const QString searchText = _searchText.count() <= 1 ? QString() : _searchText;
0103 
0104     const bool diff = searchText != m_filters.search;
0105 
0106     if (diff) {
0107         m_filters.search = searchText;
0108         if (m_sortByRelevancy == searchText.isEmpty()) {
0109             m_sortByRelevancy = !searchText.isEmpty();
0110             Q_EMIT sortByRelevancyChanged(m_sortByRelevancy);
0111         }
0112         invalidateFilter();
0113         Q_EMIT searchChanged(m_filters.search);
0114     }
0115 }
0116 
0117 void ResourcesProxyModel::removeDuplicates(QVector<AbstractResource *> &resources)
0118 {
0119     const auto cab = ResourcesModel::global()->currentApplicationBackend();
0120     QHash<QString, QString> aliases;
0121     QHash<QString, QVector<AbstractResource *>::iterator> storedIds;
0122     for (auto it = m_displayedResources.begin(); it != m_displayedResources.end(); ++it) {
0123         const auto appstreamid = (*it)->appstreamId();
0124         if (appstreamid.isEmpty()) {
0125             continue;
0126         }
0127         auto at = storedIds.find(appstreamid);
0128         if (at == storedIds.end()) {
0129             storedIds[appstreamid] = it;
0130         } else {
0131             qCWarning(LIBDISCOVER_LOG) << "We should have sanitized the displayed resources. There is a bug";
0132             Q_UNREACHABLE();
0133         }
0134 
0135         const auto alts = (*it)->alternativeAppstreamIds();
0136         for (const auto &alias : alts) {
0137             aliases[alias] = appstreamid;
0138         }
0139     }
0140 
0141     QHash<QString, QVector<AbstractResource *>::iterator> ids;
0142     for (auto it = resources.begin(); it != resources.end();) {
0143         const auto appstreamid = (*it)->appstreamId();
0144         if (appstreamid.isEmpty()) {
0145             ++it;
0146             continue;
0147         }
0148         auto at = storedIds.find(appstreamid);
0149         if (at == storedIds.end()) {
0150             auto aliased = aliases.constFind(appstreamid);
0151             if (aliased != aliases.constEnd()) {
0152                 at = storedIds.find(aliased.value());
0153             }
0154         }
0155 
0156         if (at == storedIds.end()) {
0157             const auto alts = (*it)->alternativeAppstreamIds();
0158             for (const auto &alt : alts) {
0159                 at = storedIds.find(alt);
0160                 if (at == storedIds.end())
0161                     break;
0162 
0163                 auto aliased = aliases.constFind(alt);
0164                 if (aliased != aliases.constEnd()) {
0165                     at = storedIds.find(aliased.value());
0166                     if (at != storedIds.end())
0167                         break;
0168                 }
0169             }
0170         }
0171         if (at == storedIds.end()) {
0172             auto at = ids.find(appstreamid);
0173             if (at == ids.end()) {
0174                 auto aliased = aliases.constFind(appstreamid);
0175                 if (aliased != aliases.constEnd()) {
0176                     at = ids.find(aliased.value());
0177                 }
0178             }
0179             if (at == ids.end()) {
0180                 const auto alts = (*it)->alternativeAppstreamIds();
0181                 for (const auto &alt : alts) {
0182                     at = ids.find(alt);
0183                     if (at != ids.end())
0184                         break;
0185 
0186                     auto aliased = aliases.constFind(appstreamid);
0187                     if (aliased != aliases.constEnd()) {
0188                         at = ids.find(aliased.value());
0189                         if (at != ids.end())
0190                             break;
0191                     }
0192                 }
0193             }
0194             if (at == ids.end()) {
0195                 ids[appstreamid] = it;
0196                 const auto alts = (*it)->alternativeAppstreamIds();
0197                 for (const auto &alias : alts) {
0198                     aliases[alias] = appstreamid;
0199                 }
0200                 ++it;
0201             } else {
0202                 if ((*it)->backend() == cab && (*it)->backend() != (**at)->backend()) {
0203                     qSwap(*it, **at);
0204                 }
0205                 it = resources.erase(it);
0206             }
0207         } else {
0208             if ((*it)->backend() == cab) {
0209                 **at = *it;
0210                 auto pos = index(*at - m_displayedResources.begin(), 0);
0211                 Q_EMIT dataChanged(pos, pos);
0212             }
0213             it = resources.erase(it);
0214         }
0215     }
0216 }
0217 
0218 void ResourcesProxyModel::addResources(const QVector<AbstractResource *> &_res)
0219 {
0220     auto res = _res;
0221     m_filters.filterJustInCase(res);
0222 
0223     if (res.isEmpty())
0224         return;
0225 
0226     if (!m_sortByRelevancy)
0227         std::sort(res.begin(), res.end(), [this](AbstractResource *res, AbstractResource *res2) {
0228             return lessThan(res, res2);
0229         });
0230 
0231     sortedInsertion(res);
0232     fetchSubcategories();
0233 }
0234 
0235 void ResourcesProxyModel::invalidateSorting()
0236 {
0237     if (m_displayedResources.isEmpty())
0238         return;
0239 
0240     if (!m_sortByRelevancy) {
0241         beginResetModel();
0242         std::sort(m_displayedResources.begin(), m_displayedResources.end(), [this](AbstractResource *res, AbstractResource *res2) {
0243             return lessThan(res, res2);
0244         });
0245         endResetModel();
0246     }
0247 }
0248 
0249 QString ResourcesProxyModel::lastSearch() const
0250 {
0251     return m_filters.search;
0252 }
0253 
0254 void ResourcesProxyModel::setOriginFilter(const QString &origin)
0255 {
0256     if (origin == m_filters.origin)
0257         return;
0258 
0259     m_filters.origin = origin;
0260 
0261     invalidateFilter();
0262 }
0263 
0264 QString ResourcesProxyModel::originFilter() const
0265 {
0266     return m_filters.origin;
0267 }
0268 
0269 QString ResourcesProxyModel::filteredCategoryName() const
0270 {
0271     return m_categoryName;
0272 }
0273 
0274 void ResourcesProxyModel::setFilteredCategoryName(const QString &cat)
0275 {
0276     if (cat == m_categoryName)
0277         return;
0278 
0279     m_categoryName = cat;
0280 
0281     auto category = CategoryModel::global()->findCategoryByName(cat);
0282     if (category) {
0283         setFiltersFromCategory(category);
0284     } else {
0285         qDebug() << "looking up wrong category or too early" << m_categoryName;
0286         auto f = [this, cat] {
0287             auto category = CategoryModel::global()->findCategoryByName(cat);
0288             setFiltersFromCategory(category);
0289         };
0290         auto one = new OneTimeAction(f, this);
0291         connect(CategoryModel::global(), &CategoryModel::rootCategoriesChanged, one, &OneTimeAction::trigger);
0292     }
0293 }
0294 
0295 void ResourcesProxyModel::setFiltersFromCategory(Category *category)
0296 {
0297     if (category == m_filters.category)
0298         return;
0299 
0300     m_filters.category = category;
0301     invalidateFilter();
0302     Q_EMIT categoryChanged();
0303 }
0304 
0305 void ResourcesProxyModel::fetchSubcategories()
0306 {
0307     auto cats = kToSet(m_filters.category ? m_filters.category->subCategories() : CategoryModel::global()->rootCategories());
0308 
0309     const int count = rowCount();
0310     QSet<Category *> done;
0311     for (int i = 0; i < count && !cats.isEmpty(); ++i) {
0312         AbstractResource *res = m_displayedResources[i];
0313         const auto found = res->categoryObjects(kSetToVector(cats));
0314         done.unite(found);
0315         cats.subtract(found);
0316     }
0317 
0318     const QVariantList ret = kTransform<QVariantList>(done, [](Category *cat) {
0319         return QVariant::fromValue<QObject *>(cat);
0320     });
0321     if (ret != m_subcategories) {
0322         m_subcategories = ret;
0323         Q_EMIT subcategoriesChanged(m_subcategories);
0324     }
0325 }
0326 
0327 QVariantList ResourcesProxyModel::subcategories() const
0328 {
0329     return m_subcategories;
0330 }
0331 
0332 void ResourcesProxyModel::invalidateFilter()
0333 {
0334     if (!m_setup || ResourcesModel::global()->backends().isEmpty()) {
0335         return;
0336     }
0337 
0338     if (!m_categoryName.isEmpty() && m_filters.category == nullptr) {
0339         return;
0340     }
0341 
0342     if (m_currentStream) {
0343         qCWarning(LIBDISCOVER_LOG) << "last stream isn't over yet" << m_filters << this;
0344         delete m_currentStream;
0345     }
0346 
0347     m_currentStream = m_filters.backend ? m_filters.backend->search(m_filters) : ResourcesModel::global()->search(m_filters);
0348     Q_EMIT busyChanged(true);
0349 
0350     if (!m_displayedResources.isEmpty()) {
0351         beginResetModel();
0352         m_displayedResources.clear();
0353         endResetModel();
0354     }
0355 
0356     connect(m_currentStream, &ResultsStream::resourcesFound, this, &ResourcesProxyModel::addResources);
0357     connect(m_currentStream, &ResultsStream::destroyed, this, [this]() {
0358         m_currentStream = nullptr;
0359         Q_EMIT busyChanged(false);
0360     });
0361 }
0362 
0363 int ResourcesProxyModel::rowCount(const QModelIndex &parent) const
0364 {
0365     return parent.isValid() ? 0 : m_displayedResources.count();
0366 }
0367 
0368 bool ResourcesProxyModel::lessThan(AbstractResource *leftPackage, AbstractResource *rightPackage) const
0369 {
0370     auto role = m_sortRole;
0371     Qt::SortOrder order = m_sortOrder;
0372     QVariant leftValue;
0373     QVariant rightValue;
0374     // if we're comparing two equal values, we want the model sorted by application name
0375     if (role != NameRole) {
0376         leftValue = roleToValue(leftPackage, role);
0377         rightValue = roleToValue(rightPackage, role);
0378 
0379         if (leftValue == rightValue) {
0380             role = NameRole;
0381             order = Qt::AscendingOrder;
0382         }
0383     }
0384 
0385     bool ret;
0386     if (role == NameRole) {
0387         ret = leftPackage->nameSortKey().compare(rightPackage->nameSortKey()) < 0;
0388     } else if (role == CanUpgrade) {
0389         ret = leftValue.toBool();
0390     } else {
0391 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0392         ret = leftValue < rightValue;
0393 #else
0394         const auto order = QVariant::compare(leftValue, rightValue);
0395         Q_ASSERT(order != QPartialOrdering::Unordered);
0396         return order == QPartialOrdering::Less;
0397 #endif
0398     }
0399     return ret != (order != Qt::AscendingOrder);
0400 }
0401 
0402 Category *ResourcesProxyModel::filteredCategory() const
0403 {
0404     return m_filters.category;
0405 }
0406 
0407 void ResourcesProxyModel::setStateFilter(AbstractResource::State s)
0408 {
0409     if (s != m_filters.state) {
0410         m_filters.state = s;
0411         invalidateFilter();
0412         Q_EMIT stateFilterChanged();
0413     }
0414 }
0415 
0416 AbstractResource::State ResourcesProxyModel::stateFilter() const
0417 {
0418     return m_filters.state;
0419 }
0420 
0421 QString ResourcesProxyModel::mimeTypeFilter() const
0422 {
0423     return m_filters.mimetype;
0424 }
0425 
0426 void ResourcesProxyModel::setMimeTypeFilter(const QString &mime)
0427 {
0428     if (m_filters.mimetype != mime) {
0429         m_filters.mimetype = mime;
0430         invalidateFilter();
0431     }
0432 }
0433 
0434 QString ResourcesProxyModel::extends() const
0435 {
0436     return m_filters.extends;
0437 }
0438 
0439 void ResourcesProxyModel::setExtends(const QString &extends)
0440 {
0441     if (m_filters.extends != extends) {
0442         m_filters.extends = extends;
0443         invalidateFilter();
0444     }
0445 }
0446 
0447 void ResourcesProxyModel::setFilterMinimumState(bool filterMinimumState)
0448 {
0449     if (filterMinimumState != m_filters.filterMinimumState) {
0450         m_filters.filterMinimumState = filterMinimumState;
0451         invalidateFilter();
0452         Q_EMIT filterMinimumStateChanged(m_filters.filterMinimumState);
0453     }
0454 }
0455 
0456 bool ResourcesProxyModel::filterMinimumState() const
0457 {
0458     return m_filters.filterMinimumState;
0459 }
0460 
0461 QUrl ResourcesProxyModel::resourcesUrl() const
0462 {
0463     return m_filters.resourceUrl;
0464 }
0465 
0466 void ResourcesProxyModel::setResourcesUrl(const QUrl &resourcesUrl)
0467 {
0468     if (m_filters.resourceUrl != resourcesUrl) {
0469         m_filters.resourceUrl = resourcesUrl;
0470         invalidateFilter();
0471     }
0472 }
0473 
0474 bool ResourcesProxyModel::allBackends() const
0475 {
0476     return m_filters.allBackends;
0477 }
0478 
0479 void ResourcesProxyModel::setAllBackends(bool allBackends)
0480 {
0481     m_filters.allBackends = allBackends;
0482 }
0483 
0484 AbstractResourcesBackend *ResourcesProxyModel::backendFilter() const
0485 {
0486     return m_filters.backend;
0487 }
0488 
0489 void ResourcesProxyModel::setBackendFilter(AbstractResourcesBackend *filtered)
0490 {
0491     m_filters.backend = filtered;
0492 }
0493 
0494 QVariant ResourcesProxyModel::data(const QModelIndex &index, int role) const
0495 {
0496     if (!index.isValid()) {
0497         return QVariant();
0498     }
0499 
0500     AbstractResource *const resource = m_displayedResources[index.row()];
0501     return roleToValue(resource, role);
0502 }
0503 
0504 QVariant ResourcesProxyModel::roleToValue(AbstractResource *resource, int role) const
0505 {
0506     switch (role) {
0507     case ApplicationRole:
0508         return QVariant::fromValue<QObject *>(resource);
0509     case RatingPointsRole:
0510     case RatingRole:
0511     case RatingCountRole:
0512     case SortableRatingRole: {
0513         Rating *const rating = resource->rating();
0514         const int idx = Rating::staticMetaObject.indexOfProperty(roleNames().value(role).constData());
0515         Q_ASSERT(idx >= 0);
0516         auto prop = Rating::staticMetaObject.property(idx);
0517         if (rating) {
0518             return prop.readOnGadget(rating);
0519         } else {
0520             QVariant val(0);
0521             val.convert(prop.type());
0522             return val;
0523         }
0524     }
0525     case Qt::DecorationRole:
0526     case Qt::DisplayRole:
0527     case Qt::StatusTipRole:
0528     case Qt::ToolTipRole:
0529         return QVariant();
0530     default: {
0531         QByteArray roleText = roleNames().value(role);
0532         if (Q_UNLIKELY(roleText.isEmpty())) {
0533             qCDebug(LIBDISCOVER_LOG) << "unsupported role" << role;
0534             return {};
0535         }
0536         static const QMetaObject *m = &AbstractResource::staticMetaObject;
0537         int propidx = roleText.isEmpty() ? -1 : m->indexOfProperty(roleText.constData());
0538 
0539         if (Q_UNLIKELY(propidx < 0)) {
0540             qCWarning(LIBDISCOVER_LOG) << "unknown role:" << role << roleText;
0541             return QVariant();
0542         } else
0543             return m->property(propidx).read(resource);
0544     }
0545     }
0546 }
0547 
0548 bool ResourcesProxyModel::isSorted(const QVector<AbstractResource *> &resources)
0549 {
0550     auto last = resources.constFirst();
0551     for (auto it = resources.constBegin() + 1, itEnd = resources.constEnd(); it != itEnd; ++it) {
0552         auto v1 = roleToValue(last, m_sortRole), v2 = roleToValue(*it, m_sortRole);
0553         if (!lessThan(last, *it) && v1 != v2) {
0554             qDebug() << "faulty sort" << last->name() << (*it)->name() << last << (*it);
0555             return false;
0556         }
0557         last = *it;
0558     }
0559     return true;
0560 }
0561 
0562 void ResourcesProxyModel::sortedInsertion(const QVector<AbstractResource *> &_res)
0563 {
0564     Q_ASSERT(_res.size() == QSet(_res.constBegin(), _res.constEnd()).size());
0565 
0566     auto resources = _res;
0567     Q_ASSERT(!resources.isEmpty());
0568 
0569     if (!m_filters.allBackends) {
0570         removeDuplicates(resources);
0571         if (resources.isEmpty())
0572             return;
0573     }
0574 
0575     if (m_sortByRelevancy || m_displayedResources.isEmpty()) {
0576         // Q_ASSERT(m_sortByRelevancy || isSorted(resources));
0577         int rows = rowCount();
0578         beginInsertRows({}, rows, rows + resources.count() - 1);
0579         m_displayedResources += resources;
0580         endInsertRows();
0581         return;
0582     }
0583 
0584     for (auto resource : qAsConst(resources)) {
0585         const auto finder = [this](AbstractResource *resource, AbstractResource *res) {
0586             return lessThan(resource, res);
0587         };
0588         const auto it = std::upper_bound(m_displayedResources.constBegin(), m_displayedResources.constEnd(), resource, finder);
0589         const auto newIdx = it == m_displayedResources.constEnd() ? m_displayedResources.count() : (it - m_displayedResources.constBegin());
0590 
0591         if ((it - 1) != m_displayedResources.constEnd() && *(it - 1) == resource)
0592             continue;
0593 
0594         beginInsertRows({}, newIdx, newIdx);
0595         m_displayedResources.insert(newIdx, resource);
0596         endInsertRows();
0597         // Q_ASSERT(isSorted(resources));
0598     }
0599 }
0600 
0601 void ResourcesProxyModel::refreshResource(AbstractResource *resource, const QVector<QByteArray> &properties)
0602 {
0603     const auto residx = m_displayedResources.indexOf(resource);
0604     if (residx < 0) {
0605         return;
0606     }
0607 
0608     if (!m_filters.shouldFilter(resource)) {
0609         beginRemoveRows({}, residx, residx);
0610         m_displayedResources.removeAt(residx);
0611         endRemoveRows();
0612         return;
0613     }
0614 
0615     const QModelIndex idx = index(residx, 0);
0616     Q_ASSERT(idx.isValid());
0617     const auto roles = propertiesToRoles(properties);
0618     if (!m_sortByRelevancy && roles.contains(m_sortRole)) {
0619         beginRemoveRows({}, residx, residx);
0620         m_displayedResources.removeAt(residx);
0621         endRemoveRows();
0622 
0623         sortedInsertion({resource});
0624     } else
0625         Q_EMIT dataChanged(idx, idx, roles);
0626 }
0627 
0628 void ResourcesProxyModel::removeResource(AbstractResource *resource)
0629 {
0630     const auto residx = m_displayedResources.indexOf(resource);
0631     if (residx < 0)
0632         return;
0633     beginRemoveRows({}, residx, residx);
0634     m_displayedResources.removeAt(residx);
0635     endRemoveRows();
0636 }
0637 
0638 void ResourcesProxyModel::refreshBackend(AbstractResourcesBackend *backend, const QVector<QByteArray> &properties)
0639 {
0640     auto roles = propertiesToRoles(properties);
0641     const int count = m_displayedResources.count();
0642 
0643     bool found = false;
0644 
0645     for (int i = 0; i < count; ++i) {
0646         if (backend != m_displayedResources[i]->backend())
0647             continue;
0648 
0649         int j = i + 1;
0650         for (; j < count && backend == m_displayedResources[j]->backend(); ++j) { }
0651 
0652         Q_EMIT dataChanged(index(i, 0), index(j - 1, 0), roles);
0653         i = j;
0654         found = true;
0655     }
0656 
0657     if (found && properties.contains(s_roles.value(m_sortRole))) {
0658         invalidateSorting();
0659     }
0660 }
0661 
0662 QVector<int> ResourcesProxyModel::propertiesToRoles(const QVector<QByteArray> &properties) const
0663 {
0664     QVector<int> roles = kTransform<QVector<int>>(properties, [this](const QByteArray &arr) {
0665         return roleNames().key(arr, -1);
0666     });
0667     roles.removeAll(-1);
0668     return roles;
0669 }
0670 
0671 int ResourcesProxyModel::indexOf(AbstractResource *res)
0672 {
0673     return m_displayedResources.indexOf(res);
0674 }
0675 
0676 AbstractResource *ResourcesProxyModel::resourceAt(int row) const
0677 {
0678     return m_displayedResources[row];
0679 }
0680 
0681 bool ResourcesProxyModel::canFetchMore(const QModelIndex &parent) const
0682 {
0683     Q_ASSERT(!parent.isValid());
0684     return m_currentStream;
0685 }
0686 
0687 void ResourcesProxyModel::fetchMore(const QModelIndex &parent)
0688 {
0689     Q_ASSERT(!parent.isValid());
0690     if (!m_currentStream)
0691         return;
0692     Q_EMIT m_currentStream->fetchMore();
0693 }
0694 
0695 bool ResourcesProxyModel::sortByRelevancy() const
0696 {
0697     return m_sortByRelevancy;
0698 }
0699 
0700 ResourcesCount ResourcesProxyModel::count() const
0701 {
0702     const int rows = rowCount();
0703     if (isBusy()) {
0704         // We return an empty string because it's evidently confusing
0705         if (rows == 0) {
0706             return ResourcesCount();
0707         }
0708 
0709         // We convert rows=1234 into round=1000
0710         const int round = std::pow(10, std::floor(std::log10(rows)));
0711         if (round >= 1) {
0712             const int roughCount = (rows / round) * round;
0713             const auto string = i18nc("an approximation number, like 3000+", "%1+", roughCount);
0714             return ResourcesCount(roughCount, string);
0715         }
0716     }
0717     return ResourcesCount(rows);
0718 }
0719 
0720 ResourcesCount::ResourcesCount()
0721     : m_valid(false)
0722     , m_number(0)
0723     , m_string()
0724 {
0725 }
0726 
0727 ResourcesCount::ResourcesCount(int number)
0728     : m_valid(true)
0729     , m_number(number)
0730     , m_string(QString::number(number))
0731 {
0732 }
0733 
0734 ResourcesCount::ResourcesCount(int number, const QString &string)
0735     : m_valid(true)
0736     , m_number(number)
0737     , m_string(string)
0738 {
0739 }