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