File indexing completed on 2024-06-09 05:30:57
0001 /* 0002 SPDX-FileCopyrightText: 2014-2015 Eike Hein <hein@kde.org> 0003 SPDX-FileCopyrightText: 2016-2017 Ivan Cukic <ivan.cukic@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kastatsfavoritesmodel.h" 0009 #include "actionlist.h" 0010 #include "appentry.h" 0011 #include "debug.h" 0012 #include "fileentry.h" 0013 0014 #include <QFileInfo> 0015 #include <QSortFilterProxyModel> 0016 #include <QTimer> 0017 0018 #include <KConfigGroup> 0019 #include <KLocalizedString> 0020 #include <KProtocolInfo> 0021 #include <KSharedConfig> 0022 #include <KStringHandler> 0023 #include <KSycoca> 0024 0025 #include <PlasmaActivities/Consumer> 0026 #include <PlasmaActivities/Stats/Query> 0027 #include <PlasmaActivities/Stats/ResultSet> 0028 #include <PlasmaActivities/Stats/ResultWatcher> 0029 #include <PlasmaActivities/Stats/Terms> 0030 0031 #include "config-KDECI_BUILD.h" 0032 0033 namespace KAStats = KActivities::Stats; 0034 0035 using namespace KAStats; 0036 using namespace KAStats::Terms; 0037 0038 #define AGENT_APPLICATIONS QStringLiteral("org.kde.plasma.favorites.applications") 0039 #define AGENT_DOCUMENTS QStringLiteral("org.kde.plasma.favorites.documents") 0040 0041 QString agentForUrl(const QString &url) 0042 { 0043 QUrl u(url); 0044 // clang-format off 0045 return url.startsWith(QLatin1String("preferred:")) 0046 ? AGENT_APPLICATIONS 0047 : url.startsWith(QLatin1String("applications:")) 0048 ? AGENT_APPLICATIONS 0049 : (url.startsWith(QLatin1Char('/')) && !url.endsWith(QLatin1String(".desktop"))) 0050 ? AGENT_DOCUMENTS 0051 : (url.startsWith(QLatin1String("file:/")) && !url.endsWith(QLatin1String(".desktop"))) 0052 ? AGENT_DOCUMENTS 0053 : (u.scheme() != QLatin1String("file") && !u.scheme().isEmpty() && KProtocolInfo::isKnownProtocol(u.scheme())) 0054 ? AGENT_DOCUMENTS 0055 // use applications as the default 0056 : AGENT_APPLICATIONS; 0057 // clang-format on 0058 } 0059 0060 class KAStatsFavoritesModel::Private : public QAbstractListModel 0061 { 0062 public: 0063 class NormalizedId 0064 { 0065 public: 0066 NormalizedId() 0067 { 0068 } 0069 0070 NormalizedId(const Private *parent, const QString &id) 0071 { 0072 if (id.isEmpty()) 0073 return; 0074 0075 std::shared_ptr<AbstractEntry> entry = nullptr; 0076 0077 if (parent->m_itemEntries.contains(id)) { 0078 entry = parent->m_itemEntries[id]; 0079 } else { 0080 // This entry is not cached - it is temporary, 0081 // so let's clean up when we exit this function 0082 entry = parent->entryForResource(id); 0083 } 0084 0085 if (!entry || !entry->isValid()) { 0086 qCWarning(KICKER_DEBUG) << "Entry is not valid" << id << entry.get(); 0087 m_id = id; 0088 return; 0089 } 0090 0091 const auto url = entry->url(); 0092 0093 qCDebug(KICKER_DEBUG) << "Original id is: " << id << ", and the url is" << url; 0094 0095 // Preferred applications need special handling 0096 if (entry->id().startsWith(QLatin1String("preferred:"))) { 0097 m_id = entry->id(); 0098 return; 0099 } 0100 0101 // If this is an application, use the applications:-format url 0102 auto appEntry = dynamic_cast<AppEntry *>(entry.get()); 0103 if (appEntry && !appEntry->menuId().isEmpty()) { 0104 m_id = QLatin1String("applications:") + appEntry->menuId(); 0105 return; 0106 } 0107 0108 // We want to resolve symbolic links not to have two paths 0109 // refer to the same .desktop file 0110 if (url.isLocalFile()) { 0111 QFileInfo file(url.toLocalFile()); 0112 0113 if (file.exists()) { 0114 m_id = QUrl::fromLocalFile(file.canonicalFilePath()).toString(); 0115 return; 0116 } 0117 } 0118 0119 // If this is a file, we should have already covered it 0120 if (url.scheme() == QLatin1String("file")) { 0121 return; 0122 } 0123 0124 m_id = url.toString(); 0125 } 0126 0127 const QString &value() const 0128 { 0129 return m_id; 0130 } 0131 0132 bool operator==(const NormalizedId &other) const 0133 { 0134 return m_id == other.m_id; 0135 } 0136 0137 private: 0138 QString m_id; 0139 }; 0140 0141 NormalizedId normalizedId(const QString &id) const 0142 { 0143 return NormalizedId(this, id); 0144 } 0145 0146 std::shared_ptr<AbstractEntry> entryForResource(const QString &resource, const QString &mimeType = QString()) const 0147 { 0148 using SP = std::shared_ptr<AbstractEntry>; 0149 0150 const auto agent = agentForUrl(resource); 0151 0152 if (agent == AGENT_DOCUMENTS) { 0153 if (resource.startsWith(QLatin1String("/"))) { 0154 return SP(new FileEntry(q, QUrl::fromLocalFile(resource), mimeType)); 0155 } else { 0156 return SP(new FileEntry(q, QUrl(resource), mimeType)); 0157 } 0158 0159 } else if (agent == AGENT_APPLICATIONS) { 0160 if (resource.startsWith(QLatin1String("applications:"))) { 0161 return SP(new AppEntry(q, resource.mid(13))); 0162 } else { 0163 return SP(new AppEntry(q, resource)); 0164 } 0165 0166 } else { 0167 return {}; 0168 } 0169 } 0170 0171 Private(KAStatsFavoritesModel *parent, const QString &clientId) 0172 : q(parent) 0173 , m_query(LinkedResources | Agent{AGENT_APPLICATIONS, AGENT_DOCUMENTS} | Type::any() | Activity::current() | Activity::global() | Limit(100)) 0174 , m_watcher(m_query) 0175 , m_clientId(clientId) 0176 { 0177 // Connecting the watcher 0178 connect(&m_watcher, &ResultWatcher::resultLinked, [this](const QString &resource) { 0179 addResult(resource, -1); 0180 }); 0181 0182 connect(&m_watcher, &ResultWatcher::resultUnlinked, [this](const QString &resource) { 0183 removeResult(resource); 0184 }); 0185 connect(KSycoca::self(), &KSycoca::databaseChanged, this, [this]() { 0186 QStringList keys; 0187 for (auto it = m_itemEntries.cbegin(); it != m_itemEntries.cend(); it++) { 0188 if (it.value() && !it.value()->isValid()) { 0189 keys << it.key(); 0190 } 0191 } 0192 if (!keys.isEmpty()) { 0193 for (const QString &key : keys) { 0194 removeResult(key); 0195 } 0196 } 0197 }); 0198 0199 // Loading the items order 0200 const auto cfg = KSharedConfig::openConfig(QStringLiteral("kactivitymanagerd-statsrc")); 0201 0202 // We want first to check whether we have an ordering for this activity. 0203 // If not, we will try to get a global one for this applet 0204 0205 const QString thisGroupName = QStringLiteral("Favorites-") + clientId + QStringLiteral("-") + m_activities.currentActivity(); 0206 const QString globalGroupName = QStringLiteral("Favorites-") + clientId + QStringLiteral("-global"); 0207 0208 KConfigGroup thisCfgGroup(cfg, thisGroupName); 0209 KConfigGroup globalCfgGroup(cfg, globalGroupName); 0210 0211 QStringList ordering = thisCfgGroup.readEntry("ordering", QStringList()) + globalCfgGroup.readEntry("ordering", QStringList()); 0212 // Normalizing all the ids 0213 std::transform(ordering.begin(), ordering.end(), ordering.begin(), [&](const QString &item) { 0214 return normalizedId(item).value(); 0215 }); 0216 0217 qCDebug(KICKER_DEBUG) << "Loading the ordering " << ordering; 0218 0219 // Loading the results without emitting any model signals 0220 qCDebug(KICKER_DEBUG) << "Query is" << m_query; 0221 ResultSet results(m_query); 0222 0223 for (const auto &result : results) { 0224 qCDebug(KICKER_DEBUG) << "Got " << result.resource() << " -->"; 0225 addResult(result.resource(), -1, false, result.mimetype()); 0226 } 0227 0228 if (ordering.length() == 0) { 0229 // try again with other applets with highest instance number which has the matching apps 0230 qCDebug(KICKER_DEBUG) << "No ordering for this applet found, trying others"; 0231 const auto allGroups = cfg->groupList(); 0232 int instanceHighest = -1; 0233 for (const auto &groupName : allGroups) { 0234 if (groupName.contains(QStringLiteral(".favorites.instance-")) 0235 && (groupName.endsWith(QStringLiteral("-global")) || groupName.endsWith(m_activities.currentActivity()))) { 0236 // the group names look like "Favorites-org.kde.plasma.kicker.favorites.instance-58-1bd5bb42-187c-4c77-a746-c9644c5da866" 0237 const QStringList split = groupName.split(QStringLiteral("-")); 0238 if (split.length() >= 3) { 0239 bool ok; 0240 int instanceN = split[2].toInt(&ok); 0241 if (!ok) { 0242 continue; 0243 } 0244 auto groupOrdering = KConfigGroup(cfg, groupName).readEntry("ordering", QStringList()); 0245 if (groupOrdering.length() != m_items.length()) { 0246 continue; 0247 } 0248 std::transform(groupOrdering.begin(), groupOrdering.end(), groupOrdering.begin(), [&](const QString &item) { 0249 return normalizedId(item).value(); 0250 }); 0251 for (auto item : m_items) { 0252 if (!groupOrdering.contains(item.value())) { 0253 continue; 0254 } 0255 } 0256 if (instanceHighest == instanceN) { 0257 // we got a -global as well as -{activity uuid} 0258 // we add them 0259 if (groupName.endsWith(QStringLiteral("-global"))) { 0260 ordering += groupOrdering; 0261 } else { 0262 ordering = groupOrdering + ordering; 0263 } 0264 qCDebug(KICKER_DEBUG) << "adding ordering from: " << groupName; 0265 } else if (instanceN > instanceHighest) { 0266 instanceHighest = instanceN; 0267 ordering = (groupOrdering.length() != 0) ? groupOrdering : ordering; 0268 qCDebug(KICKER_DEBUG) << "taking ordering from: " << groupName; 0269 } 0270 } 0271 } 0272 } 0273 } 0274 0275 // Sorting the items in the cache 0276 std::sort(m_items.begin(), m_items.end(), [&](const NormalizedId &left, const NormalizedId &right) { 0277 auto leftIndex = ordering.indexOf(left.value()); 0278 auto rightIndex = ordering.indexOf(right.value()); 0279 // clang-format off 0280 return (leftIndex == -1 && rightIndex == -1) ? 0281 left.value() < right.value() : 0282 0283 (leftIndex == -1) ? 0284 false : 0285 0286 (rightIndex == -1) ? 0287 true : 0288 0289 // otherwise 0290 leftIndex < rightIndex; 0291 // clang-format on 0292 }); 0293 0294 // Debugging: 0295 QList<QString> itemStrings(m_items.size()); 0296 std::transform(m_items.cbegin(), m_items.cend(), itemStrings.begin(), [](const NormalizedId &item) { 0297 return item.value(); 0298 }); 0299 qCDebug(KICKER_DEBUG) << "After ordering: " << itemStrings; 0300 } 0301 0302 void addResult(const QString &_resource, int index, bool notifyModel = true, const QString &mimeType = QString()) 0303 { 0304 // We want even files to have a proper URL 0305 const auto resource = _resource.startsWith(QLatin1Char('/')) ? QUrl::fromLocalFile(_resource).toString() : _resource; 0306 0307 qCDebug(KICKER_DEBUG) << "Adding result" << resource << "already present?" << m_itemEntries.contains(resource); 0308 0309 if (m_itemEntries.contains(resource)) 0310 return; 0311 0312 auto entry = entryForResource(resource, mimeType); 0313 0314 if (!entry || !entry->isValid()) { 0315 qCDebug(KICKER_DEBUG) << "Entry is not valid!" << resource; 0316 return; 0317 } 0318 0319 // TODO Remove a few releases after Plasma 5.25 as this should become dead code, once users have run this code at least once 0320 // Converts old-style applications favorites with path (/usrshare/applications/org.kde.dolphin.desktop) or storageId (org.kde.dolphin.desktop) 0321 if ((_resource.startsWith(QLatin1String("/")) || QUrl(_resource).scheme().isEmpty()) && _resource.endsWith(QLatin1String(".desktop"))) { 0322 m_watcher.unlinkFromActivity(QUrl(_resource), QStringLiteral(":any"), agentForUrl(resource)); 0323 0324 const auto normalized = normalizedId(resource).value(); 0325 qCDebug(KICKER_DEBUG) << "Converting old-style application favorite entry" << _resource << "to" << normalized; 0326 0327 m_watcher.linkToActivity(QUrl(normalized), QStringLiteral(":any"), agentForUrl(resource)); 0328 return; 0329 } 0330 0331 if (index == -1) { 0332 index = m_items.count(); 0333 } 0334 0335 if (notifyModel) { 0336 beginInsertRows(QModelIndex(), index, index); 0337 } 0338 0339 auto url = entry->url(); 0340 0341 m_itemEntries[resource] = m_itemEntries[entry->id()] = m_itemEntries[url.toString()] = entry; 0342 if (!url.toLocalFile().isEmpty()) { 0343 m_itemEntries[url.toLocalFile()] = entry; 0344 } 0345 0346 auto normalized = normalizedId(resource); 0347 m_items.insert(index, normalized); 0348 m_itemEntries[normalized.value()] = entry; 0349 0350 if (notifyModel) { 0351 endInsertRows(); 0352 saveOrdering(); 0353 } 0354 } 0355 0356 void removeResult(const QString &resource) 0357 { 0358 const auto normalized = normalizedId(resource); 0359 0360 // If we know this item will not really be removed, 0361 // but only that activities it is on have changed, 0362 // lets leave it 0363 if (m_ignoredItems.contains(normalized.value())) { 0364 m_ignoredItems.removeAll(normalized.value()); 0365 return; 0366 } 0367 0368 qCDebug(KICKER_DEBUG) << "Removing result" << resource; 0369 0370 auto index = m_items.indexOf(normalized); 0371 0372 if (index == -1) 0373 return; 0374 0375 beginRemoveRows(QModelIndex(), index, index); 0376 const auto entry = m_itemEntries[resource]; 0377 m_items.removeAt(index); 0378 0379 // Removing the entry from the cache 0380 QMutableHashIterator<QString, std::shared_ptr<AbstractEntry>> i(m_itemEntries); 0381 while (i.hasNext()) { 0382 i.next(); 0383 if (i.value() == entry) { 0384 i.remove(); 0385 } 0386 } 0387 0388 endRemoveRows(); 0389 0390 saveOrdering(); 0391 } 0392 0393 int rowCount(const QModelIndex &parent = QModelIndex()) const override 0394 { 0395 if (parent.isValid()) 0396 return 0; 0397 0398 return m_items.count(); 0399 } 0400 0401 QVariant data(const QModelIndex &item, int role = Qt::DisplayRole) const override 0402 { 0403 if (item.parent().isValid()) 0404 return QVariant(); 0405 0406 const auto index = item.row(); 0407 0408 const auto entry = m_itemEntries[m_items[index].value()]; 0409 // clang-format off 0410 return entry == nullptr ? QVariant() 0411 : role == Qt::DisplayRole ? entry->name() 0412 : role == Kicker::DisplayWrappedRole ? KStringHandler::preProcessWrap(entry->name()) 0413 : role == Qt::DecorationRole ? entry->icon() 0414 : role == Kicker::DescriptionRole ? entry->description() 0415 : role == Kicker::FavoriteIdRole ? entry->id() 0416 : role == Kicker::UrlRole ? entry->url() 0417 : role == Kicker::HasActionListRole ? entry->hasActions() 0418 : role == Kicker::ActionListRole ? entry->actions() 0419 : QVariant(); 0420 // clang-format on 0421 } 0422 0423 bool trigger(int row, const QString &actionId, const QVariant &argument) 0424 { 0425 if (row < 0 || row >= rowCount()) { 0426 return false; 0427 } 0428 0429 const QString id = data(index(row, 0), Kicker::UrlRole).toString(); 0430 if (m_itemEntries.contains(id)) { 0431 return m_itemEntries[id]->run(actionId, argument); 0432 } 0433 // Entries with preferred:// can be changed by the user, BUG: 416161 0434 // then the list of entries could be out of sync 0435 const auto entry = m_itemEntries[m_items[row].value()]; 0436 if (QUrl(entry->id()).scheme() == QLatin1String("preferred")) { 0437 return entry->run(actionId, argument); 0438 } 0439 return false; 0440 } 0441 0442 void move(int from, int to) 0443 { 0444 if (from < 0) 0445 return; 0446 if (from >= m_items.count()) 0447 return; 0448 if (to < 0) 0449 return; 0450 if (to >= m_items.count()) 0451 return; 0452 0453 if (from == to) 0454 return; 0455 0456 const int modelTo = to + (to > from ? 1 : 0); 0457 0458 if (q->beginMoveRows(QModelIndex(), from, from, QModelIndex(), modelTo)) { 0459 m_items.move(from, to); 0460 q->endMoveRows(); 0461 0462 qCDebug(KICKER_DEBUG) << "Save ordering (from Private::move) -->"; 0463 saveOrdering(); 0464 } 0465 } 0466 0467 void saveOrdering() 0468 { 0469 QStringList ids; 0470 0471 for (const auto &item : std::as_const(m_items)) { 0472 ids << item.value(); 0473 } 0474 0475 qCDebug(KICKER_DEBUG) << "Save ordering (from Private::saveOrdering) -->"; 0476 saveOrdering(ids, m_clientId, m_activities.currentActivity()); 0477 } 0478 0479 static void saveOrdering(const QStringList &ids, const QString &clientId, const QString ¤tActivity) 0480 { 0481 const auto cfg = KSharedConfig::openConfig(QStringLiteral("kactivitymanagerd-statsrc")); 0482 0483 QStringList activities{currentActivity, QStringLiteral("global")}; 0484 0485 qCDebug(KICKER_DEBUG) << "Saving ordering for" << currentActivity << "and global" << ids; 0486 0487 for (const auto &activity : activities) { 0488 const QString groupName = QStringLiteral("Favorites-") + clientId + QStringLiteral("-") + activity; 0489 0490 KConfigGroup cfgGroup(cfg, groupName); 0491 0492 cfgGroup.writeEntry("ordering", ids); 0493 } 0494 0495 cfg->sync(); 0496 } 0497 0498 KAStatsFavoritesModel *const q; 0499 KActivities::Consumer m_activities; 0500 Query m_query; 0501 ResultWatcher m_watcher; 0502 QString m_clientId; 0503 0504 QList<NormalizedId> m_items; 0505 QHash<QString, std::shared_ptr<AbstractEntry>> m_itemEntries; 0506 QStringList m_ignoredItems; 0507 }; 0508 0509 KAStatsFavoritesModel::KAStatsFavoritesModel(QObject *parent) 0510 : PlaceholderModel(parent) 0511 , d(nullptr) // we have no client id yet 0512 , m_enabled(true) 0513 , m_maxFavorites(-1) 0514 , m_activities(new KActivities::Consumer(this)) 0515 { 0516 connect(m_activities, &KActivities::Consumer::currentActivityChanged, this, [&](const QString ¤tActivity) { 0517 qCDebug(KICKER_DEBUG) << "Activity just got changed to" << currentActivity; 0518 Q_UNUSED(currentActivity); 0519 if (d) { 0520 auto clientId = d->m_clientId; 0521 initForClient(clientId); 0522 } 0523 }); 0524 } 0525 0526 KAStatsFavoritesModel::~KAStatsFavoritesModel() 0527 { 0528 delete d; 0529 } 0530 0531 void KAStatsFavoritesModel::initForClient(const QString &clientId) 0532 { 0533 qCDebug(KICKER_DEBUG) << "initForClient" << clientId; 0534 0535 setSourceModel(nullptr); 0536 delete d; 0537 d = new Private(this, clientId); 0538 0539 setSourceModel(d); 0540 } 0541 0542 QString KAStatsFavoritesModel::description() const 0543 { 0544 return i18n("Favorites"); 0545 } 0546 0547 bool KAStatsFavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) 0548 { 0549 return d && d->trigger(row, actionId, argument); 0550 } 0551 0552 bool KAStatsFavoritesModel::enabled() const 0553 { 0554 return m_enabled; 0555 } 0556 0557 int KAStatsFavoritesModel::maxFavorites() const 0558 { 0559 return m_maxFavorites; 0560 } 0561 0562 void KAStatsFavoritesModel::setMaxFavorites(int max) 0563 { 0564 Q_UNUSED(max); 0565 } 0566 0567 void KAStatsFavoritesModel::setEnabled(bool enable) 0568 { 0569 if (m_enabled != enable) { 0570 m_enabled = enable; 0571 0572 Q_EMIT enabledChanged(); 0573 } 0574 } 0575 0576 QStringList KAStatsFavoritesModel::favorites() const 0577 { 0578 qCWarning(KICKER_DEBUG) << "KAStatsFavoritesModel::favorites returns nothing, it is here just to keep the API backwards-compatible"; 0579 return QStringList(); 0580 } 0581 0582 void KAStatsFavoritesModel::setFavorites(const QStringList &favorites) 0583 { 0584 Q_UNUSED(favorites); 0585 qCWarning(KICKER_DEBUG) << "KAStatsFavoritesModel::setFavorites is ignored"; 0586 } 0587 0588 bool KAStatsFavoritesModel::isFavorite(const QString &id) const 0589 { 0590 return d && d->m_itemEntries.contains(id); 0591 } 0592 0593 #if BUILD_TESTING 0594 void KAStatsFavoritesModel::portOldFavorites(const QStringList &_ids) 0595 #else 0596 void KAStatsFavoritesModel::portOldFavorites(const QStringList &ids) 0597 #endif 0598 { 0599 if (!d) 0600 return; 0601 0602 #if BUILD_TESTING 0603 const QStringList ids = qEnvironmentVariableIsSet("KDECI_BUILD") ? QStringList{ 0604 QLatin1String("org.kde.plasma.emojier.desktop"), 0605 QLatin1String("linguist5.desktop"), 0606 QLatin1String("org.qt.linguist6.desktop"), 0607 } : _ids; 0608 #endif 0609 0610 qCDebug(KICKER_DEBUG) << "portOldFavorites" << ids; 0611 0612 const QString activityId = QStringLiteral(":global"); 0613 std::for_each(ids.begin(), ids.end(), [&](const QString &id) { 0614 addFavoriteTo(id, activityId); 0615 }); 0616 0617 // Resetting the model 0618 auto clientId = d->m_clientId; 0619 setSourceModel(nullptr); 0620 delete d; 0621 d = nullptr; 0622 0623 qCDebug(KICKER_DEBUG) << "Save ordering (from portOldFavorites) -->"; 0624 Private::saveOrdering(ids, clientId, m_activities->currentActivity()); 0625 0626 QTimer::singleShot(500, this, std::bind(&KAStatsFavoritesModel::initForClient, this, clientId)); 0627 } 0628 0629 void KAStatsFavoritesModel::addFavorite(const QString &id, int index) 0630 { 0631 qCDebug(KICKER_DEBUG) << "addFavorite" << id << index << " -->"; 0632 addFavoriteTo(id, QStringLiteral(":global"), index); 0633 } 0634 0635 void KAStatsFavoritesModel::removeFavorite(const QString &id) 0636 { 0637 qCDebug(KICKER_DEBUG) << "removeFavorite" << id << " -->"; 0638 removeFavoriteFrom(id, QStringLiteral(":any")); 0639 } 0640 0641 void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const QString &activityId, int index) 0642 { 0643 qCDebug(KICKER_DEBUG) << "addFavoriteTo" << id << activityId << index << " -->"; 0644 addFavoriteTo(id, Activity(activityId), index); 0645 } 0646 0647 void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const QString &activityId) 0648 { 0649 qCDebug(KICKER_DEBUG) << "removeFavoriteFrom" << id << activityId << " -->"; 0650 removeFavoriteFrom(id, Activity(activityId)); 0651 } 0652 0653 void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const Activity &activity, int index) 0654 { 0655 if (!d || id.isEmpty()) 0656 return; 0657 0658 Q_ASSERT(!activity.values.isEmpty()); 0659 0660 setDropPlaceholderIndex(-1); 0661 0662 QStringList matchers{d->m_activities.currentActivity(), QStringLiteral(":global"), QStringLiteral(":current")}; 0663 if (std::find_first_of(activity.values.cbegin(), activity.values.cend(), matchers.cbegin(), matchers.cend()) != activity.values.cend()) { 0664 d->addResult(id, index); 0665 } 0666 0667 const auto url = d->normalizedId(id).value(); 0668 0669 qCDebug(KICKER_DEBUG) << "addFavoriteTo" << id << activity << index << url << " (actual)"; 0670 0671 if (url.isEmpty()) 0672 return; 0673 0674 d->m_watcher.linkToActivity(QUrl(url), activity, Agent(agentForUrl(url))); 0675 } 0676 0677 void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const Activity &activity) 0678 { 0679 if (!d || id.isEmpty()) 0680 return; 0681 0682 Q_ASSERT(!activity.values.isEmpty()); 0683 0684 qCDebug(KICKER_DEBUG) << "removeFavoriteFrom" << id << activity; 0685 0686 if (!isFavorite(id)) { 0687 return; 0688 } 0689 0690 QUrl url = QUrl(id); 0691 0692 d->m_watcher.unlinkFromActivity(url, activity, Agent(agentForUrl(id))); 0693 } 0694 0695 void KAStatsFavoritesModel::setFavoriteOn(const QString &id, const QString &activityId) 0696 { 0697 if (!d || id.isEmpty()) 0698 return; 0699 0700 const auto url = d->normalizedId(id).value(); 0701 0702 qCDebug(KICKER_DEBUG) << "setFavoriteOn" << id << activityId << url << " (actual)"; 0703 0704 qCDebug(KICKER_DEBUG) << "%%%%%%%%%%% Activity is" << activityId; 0705 if (activityId.isEmpty() || activityId == QLatin1String(":any") || activityId == QLatin1String(":global") 0706 || activityId == m_activities->currentActivity()) { 0707 d->m_ignoredItems << url; 0708 } 0709 0710 d->m_watcher.unlinkFromActivity(QUrl(url), Activity::any(), Agent(agentForUrl(url))); 0711 d->m_watcher.linkToActivity(QUrl(url), activityId, Agent(agentForUrl(url))); 0712 } 0713 0714 void KAStatsFavoritesModel::moveRow(int from, int to) 0715 { 0716 if (!d) 0717 return; 0718 0719 d->move(from, to); 0720 } 0721 0722 AbstractModel *KAStatsFavoritesModel::favoritesModel() 0723 { 0724 return this; 0725 } 0726 0727 void KAStatsFavoritesModel::refresh() 0728 { 0729 } 0730 0731 QObject *KAStatsFavoritesModel::activities() const 0732 { 0733 return m_activities; 0734 } 0735 0736 QString KAStatsFavoritesModel::activityNameForId(const QString &activityId) const 0737 { 0738 // It is safe to use a short-lived object here, 0739 // we are always synced with KAMD in plasma 0740 KActivities::Info info(activityId); 0741 return info.name(); 0742 } 0743 0744 QStringList KAStatsFavoritesModel::linkedActivitiesFor(const QString &id) const 0745 { 0746 if (!d) { 0747 qCDebug(KICKER_DEBUG) << "Linked for" << id << "is empty, no Private instance"; 0748 return {}; 0749 } 0750 0751 auto url = d->normalizedId(id).value(); 0752 0753 if (url.startsWith(QLatin1String("file:"))) { 0754 url = QUrl(url).toLocalFile(); 0755 } 0756 0757 if (url.isEmpty()) { 0758 qCDebug(KICKER_DEBUG) << "The url for" << id << "is empty"; 0759 return {}; 0760 } 0761 0762 auto query = LinkedResources | Agent{AGENT_APPLICATIONS, AGENT_DOCUMENTS} | Type::any() | Activity::any() | Url(url) | Limit(100); 0763 0764 ResultSet results(query); 0765 0766 for (const auto &result : results) { 0767 qCDebug(KICKER_DEBUG) << "Returning" << result.linkedActivities() << "for" << id << url; 0768 return result.linkedActivities(); 0769 } 0770 0771 qCDebug(KICKER_DEBUG) << "Returning empty list of activities for" << id << url; 0772 return {}; 0773 }