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 }