Changeset View
Changeset View
Standalone View
Standalone View
applets/kicker/plugin/kastatsfavoritesmodel.cpp
- This file was added.
1 | /*************************************************************************** | ||||
---|---|---|---|---|---|
2 | * Copyright (C) 2014-2015 by Eike Hein <hein@kde.org> * | ||||
3 | * Copyright (C) 2016-2017 by Ivan Cukic <ivan.cukic@kde.org> * | ||||
4 | * * | ||||
5 | * This program is free software; you can redistribute it and/or modify * | ||||
6 | * it under the terms of the GNU General Public License as published by * | ||||
7 | * the Free Software Foundation; either version 2 of the License, or * | ||||
8 | * (at your option) any later version. * | ||||
9 | * * | ||||
10 | * This program is distributed in the hope that it will be useful, * | ||||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | ||||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | ||||
13 | * GNU General Public License for more details. * | ||||
14 | * * | ||||
15 | * You should have received a copy of the GNU General Public License * | ||||
16 | * along with this program; if not, write to the * | ||||
17 | * Free Software Foundation, Inc., * | ||||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * | ||||
19 | ***************************************************************************/ | ||||
20 | | ||||
21 | #include "kastatsfavoritesmodel.h" | ||||
22 | #include "appentry.h" | ||||
23 | #include "contactentry.h" | ||||
24 | #include "fileentry.h" | ||||
25 | #include "actionlist.h" | ||||
26 | | ||||
27 | #include "modeltest.h" | ||||
28 | | ||||
29 | #include <QDebug> | ||||
30 | #include <QFileInfo> | ||||
31 | #include <QTimer> | ||||
32 | #include <QSortFilterProxyModel> | ||||
33 | | ||||
34 | #include <KLocalizedString> | ||||
35 | #include <KSharedConfig> | ||||
36 | #include <KConfigGroup> | ||||
37 | | ||||
38 | #include <KActivities/Consumer> | ||||
39 | #include <KActivities/Stats/Terms> | ||||
40 | #include <KActivities/Stats/Query> | ||||
41 | #include <KActivities/Stats/ResultSet> | ||||
42 | #include <KActivities/Stats/ResultWatcher> | ||||
43 | | ||||
44 | namespace KAStats = KActivities::Stats; | ||||
45 | | ||||
46 | using namespace KAStats; | ||||
47 | using namespace KAStats::Terms; | ||||
48 | | ||||
49 | #define AGENT_APPLICATIONS "org.kde.plasma.favorites.applications" | ||||
50 | #define AGENT_CONTACTS "org.kde.plasma.favorites.contacts" | ||||
51 | #define AGENT_DOCUMENTS "org.kde.plasma.favorites.documents" | ||||
52 | | ||||
53 | #define DEBUG_PREFIX "\033[0;31m[KASTATSFAVS]\033[0;34m " | ||||
54 | #define DEBUG qDebug() << DEBUG_PREFIX << (void*)q << ((void*)this) << m_clientId << "\033[0;0m " | ||||
55 | | ||||
56 | QString agentForUrl(const QString &url) | ||||
57 | { | ||||
58 | return url.startsWith("ktp:") | ||||
59 | ? AGENT_CONTACTS | ||||
60 | : url.startsWith("preferred:") | ||||
61 | ? AGENT_APPLICATIONS | ||||
62 | : url.startsWith("applications:") | ||||
63 | ? AGENT_APPLICATIONS | ||||
64 | : (url.startsWith("/") && !url.endsWith(".desktop")) | ||||
65 | ? AGENT_DOCUMENTS | ||||
66 | : (url.startsWith("file:/") && !url.endsWith(".desktop")) | ||||
67 | ? AGENT_DOCUMENTS | ||||
68 | // use applications as the default | ||||
69 | : AGENT_APPLICATIONS; | ||||
70 | } | ||||
71 | | ||||
72 | class KAStatsFavoritesModel::Private: public QAbstractListModel { | ||||
73 | public: | ||||
74 | class NormalizedId { | ||||
75 | public: | ||||
76 | NormalizedId() | ||||
77 | { | ||||
78 | } | ||||
79 | | ||||
80 | NormalizedId(const Private *parent, const QString &id) | ||||
81 | { | ||||
82 | if (id.isEmpty()) return; | ||||
83 | | ||||
84 | AbstractEntry *entry = nullptr; | ||||
85 | QScopedPointer<AbstractEntry> deleter; | ||||
86 | | ||||
87 | if (parent->m_itemEntries.contains(id)) { | ||||
88 | entry = parent->m_itemEntries[id]; | ||||
89 | } else { | ||||
90 | // This entry is not cached - it is temporary, | ||||
91 | // so let's clean up when we exit this function | ||||
92 | entry = parent->entryForResource(id); | ||||
93 | deleter.reset(entry); | ||||
94 | } | ||||
95 | | ||||
96 | if (!entry || !entry->isValid()) { | ||||
97 | qWarning() << "Entry is not valid" << id << entry; | ||||
98 | m_id = id; | ||||
99 | return; | ||||
100 | } | ||||
101 | | ||||
102 | const auto url = entry->url(); | ||||
103 | | ||||
104 | qDebug() << "Original id is: " << id << ", and the url is" << url; | ||||
105 | | ||||
106 | // Preferred applications need special handling | ||||
107 | if (entry->id().startsWith("preferred:")) { | ||||
108 | m_id = entry->id(); | ||||
109 | return; | ||||
110 | } | ||||
111 | | ||||
112 | // If this is an application, use the applications:-format url | ||||
113 | auto appEntry = dynamic_cast<AppEntry*>(entry); | ||||
114 | if (appEntry && !appEntry->menuId().isEmpty()) { | ||||
115 | m_id = "applications:" + appEntry->menuId(); | ||||
116 | return; | ||||
117 | } | ||||
118 | | ||||
119 | // We want to resolve symbolic links not to have two paths | ||||
120 | // refer to the same .desktop file | ||||
121 | if (url.isLocalFile()) { | ||||
122 | QFileInfo file(url.toLocalFile()); | ||||
123 | | ||||
124 | if (file.exists()) { | ||||
125 | m_id = QUrl::fromLocalFile(file.canonicalFilePath()).toString(); | ||||
126 | return; | ||||
127 | } | ||||
128 | } | ||||
129 | | ||||
130 | // If this is a file, we should have already covered it | ||||
131 | if (url.scheme() == "file") { | ||||
132 | return; | ||||
133 | } | ||||
134 | | ||||
135 | m_id = url.toString(); | ||||
136 | } | ||||
137 | | ||||
138 | const QString& value() const | ||||
139 | { | ||||
140 | return m_id; | ||||
141 | } | ||||
142 | | ||||
143 | bool operator==(const NormalizedId &other) const | ||||
144 | { | ||||
145 | return m_id == other.m_id; | ||||
146 | } | ||||
147 | | ||||
148 | private: | ||||
149 | QString m_id; | ||||
150 | }; | ||||
151 | | ||||
152 | NormalizedId normalizedId(const QString &id) const | ||||
153 | { | ||||
154 | return NormalizedId(this, id); | ||||
155 | } | ||||
156 | | ||||
157 | AbstractEntry *entryForResource(const QString &resource) const | ||||
158 | { | ||||
159 | const auto agent = | ||||
160 | agentForUrl(resource); | ||||
161 | | ||||
162 | if (agent == AGENT_CONTACTS) { | ||||
163 | return new ContactEntry(q, resource); | ||||
164 | | ||||
165 | } else if (agent == AGENT_DOCUMENTS) { | ||||
166 | if (resource.startsWith("/")) { | ||||
167 | return new FileEntry(q, QUrl::fromLocalFile(resource)); | ||||
168 | } else { | ||||
169 | return new FileEntry(q, QUrl(resource)); | ||||
170 | } | ||||
171 | | ||||
172 | } else if (agent == AGENT_APPLICATIONS) { | ||||
173 | if (resource.startsWith("applications:")) { | ||||
174 | return new AppEntry(q, resource.mid(13)); | ||||
175 | } else { | ||||
176 | return new AppEntry(q, resource); | ||||
177 | } | ||||
178 | | ||||
179 | } else { | ||||
180 | return nullptr; | ||||
181 | } | ||||
182 | } | ||||
183 | | ||||
184 | Private(KAStatsFavoritesModel *parent, QString clientId) | ||||
185 | : q(parent) | ||||
186 | , m_query( | ||||
187 | LinkedResources | ||||
188 | | Agent { | ||||
189 | AGENT_APPLICATIONS, | ||||
190 | AGENT_CONTACTS, | ||||
191 | AGENT_DOCUMENTS | ||||
192 | } | ||||
193 | | Type::any() | ||||
194 | | Activity::current() | ||||
195 | | Activity::global() | ||||
196 | ) | ||||
197 | , m_watcher(m_query) | ||||
198 | , m_clientId(clientId) | ||||
199 | { | ||||
200 | // Connecting the watcher | ||||
201 | connect(&m_watcher, &ResultWatcher::resultLinked, | ||||
202 | [this] (const QString &resource) { | ||||
203 | addResult(resource, -1); | ||||
204 | }); | ||||
205 | | ||||
206 | connect(&m_watcher, &ResultWatcher::resultUnlinked, | ||||
207 | [this] (const QString &resource) { | ||||
208 | removeResult(resource); | ||||
209 | }); | ||||
210 | | ||||
211 | // Loading the items order | ||||
212 | const auto cfg = KSharedConfig::openConfig("kactivitymanagerd-statsrc"); | ||||
213 | | ||||
214 | // We want first to check whether we have an ordering for this activity. | ||||
215 | // If not, we will try to get a global one for this applet | ||||
216 | | ||||
217 | const QString thisGroupName = | ||||
218 | "Favorites-" + clientId + "-" + m_activities.currentActivity(); | ||||
219 | const QString globalGroupName = | ||||
220 | "Favorites-" + clientId + "-global"; | ||||
221 | | ||||
222 | KConfigGroup thisCfgGroup(cfg, thisGroupName); | ||||
223 | KConfigGroup globalCfgGroup(cfg, globalGroupName); | ||||
224 | | ||||
225 | QStringList ordering = | ||||
226 | thisCfgGroup.readEntry("ordering", QStringList()) + | ||||
227 | globalCfgGroup.readEntry("ordering", QStringList()); | ||||
228 | | ||||
229 | DEBUG << "Loading the ordering " << ordering; | ||||
230 | | ||||
231 | // Loading the results without emitting any model signals | ||||
232 | ResultSet results(m_query); | ||||
233 | | ||||
234 | for (const auto& result: results) { | ||||
235 | addResult(result.resource(), -1, false); | ||||
236 | } | ||||
237 | | ||||
238 | // Normalizing all the ids | ||||
239 | std::transform(ordering.begin(), ordering.end(), ordering.begin(), | ||||
240 | [&] (const QString &item) { | ||||
241 | return normalizedId(item).value(); | ||||
242 | }); | ||||
243 | | ||||
244 | // Sorting the items in the cache | ||||
245 | std::sort(m_items.begin(), m_items.end(), | ||||
246 | [&] (const NormalizedId &left, const NormalizedId &right) { | ||||
247 | auto leftIndex = ordering.indexOf(left.value()); | ||||
248 | auto rightIndex = ordering.indexOf(right.value()); | ||||
249 | | ||||
250 | return (leftIndex == -1 && rightIndex == -1) ? | ||||
251 | left.value() < right.value() : | ||||
252 | | ||||
253 | (leftIndex == -1) ? | ||||
254 | false : | ||||
255 | | ||||
256 | (rightIndex == -1) ? | ||||
257 | true : | ||||
258 | | ||||
259 | // otherwise | ||||
260 | leftIndex < rightIndex; | ||||
261 | }); | ||||
262 | | ||||
263 | // Debugging: | ||||
264 | QVector<QString> itemStrings(m_items.size()); | ||||
265 | std::transform(m_items.cbegin(), m_items.cend(), itemStrings.begin(), | ||||
266 | [] (const NormalizedId &item) { | ||||
267 | return item.value(); | ||||
268 | }); | ||||
269 | DEBUG << "After ordering: " << itemStrings; | ||||
270 | } | ||||
271 | | ||||
272 | void addResult(const QString &_resource, int index, bool notifyModel = true) | ||||
273 | { | ||||
274 | // We want even files to have a proper URL | ||||
275 | const auto resource = | ||||
276 | _resource.startsWith("/") ? QUrl::fromLocalFile(_resource).toString() : _resource; | ||||
277 | | ||||
278 | DEBUG << "Adding result" << resource << "already present?" << m_itemEntries.contains(resource); | ||||
279 | | ||||
280 | if (m_itemEntries.contains(resource)) return; | ||||
281 | | ||||
282 | auto entry = entryForResource(resource); | ||||
283 | | ||||
284 | if (!entry || !entry->isValid()) { | ||||
285 | DEBUG << "Entry is not valid!"; | ||||
286 | return; | ||||
287 | } | ||||
288 | | ||||
289 | if (index == -1) { | ||||
290 | index = m_items.count(); | ||||
291 | } | ||||
292 | | ||||
293 | if (notifyModel) { | ||||
294 | beginInsertRows(QModelIndex(), index, index); | ||||
295 | } | ||||
296 | | ||||
297 | auto url = entry->url(); | ||||
298 | | ||||
299 | m_itemEntries[resource] | ||||
300 | = m_itemEntries[entry->id()] | ||||
301 | = m_itemEntries[url.toString()] | ||||
302 | = m_itemEntries[url.toLocalFile()] | ||||
303 | = entry; | ||||
304 | | ||||
305 | auto normalized = normalizedId(resource); | ||||
306 | m_items.insert(index, normalized); | ||||
307 | m_itemEntries[normalized.value()] = entry; | ||||
308 | | ||||
309 | if (notifyModel) { | ||||
310 | endInsertRows(); | ||||
311 | saveOrdering(); | ||||
312 | } | ||||
313 | } | ||||
314 | | ||||
315 | void removeResult(const QString &resource) | ||||
316 | { | ||||
317 | auto normalized = normalizedId(resource); | ||||
318 | | ||||
319 | // If we know this item will not really be removed, | ||||
320 | // but only that activities it is on have changed, | ||||
321 | // lets leave it | ||||
322 | if (m_ignoredItems.contains(normalized.value())) { | ||||
323 | m_ignoredItems.removeAll(normalized.value()); | ||||
324 | return; | ||||
325 | } | ||||
326 | | ||||
327 | DEBUG << "Removing result" << resource; | ||||
328 | | ||||
329 | auto index = m_items.indexOf(normalizedId(resource)); | ||||
330 | | ||||
331 | if (index == -1) return; | ||||
332 | | ||||
333 | beginRemoveRows(QModelIndex(), index, index); | ||||
334 | auto entry = m_itemEntries[resource]; | ||||
335 | m_items.removeAt(index); | ||||
336 | | ||||
337 | // Removing the entry from the cache | ||||
338 | QMutableHashIterator<QString, AbstractEntry*> i(m_itemEntries); | ||||
339 | while (i.hasNext()) { | ||||
340 | if (i.value() == entry) { | ||||
341 | i.remove(); | ||||
342 | } | ||||
343 | i.next(); | ||||
344 | } | ||||
345 | delete entry; | ||||
346 | | ||||
347 | endRemoveRows(); | ||||
348 | } | ||||
349 | | ||||
350 | | ||||
351 | int rowCount(const QModelIndex &parent = QModelIndex()) const override | ||||
352 | { | ||||
353 | if (parent.isValid()) return 0; | ||||
354 | | ||||
355 | return m_items.count(); | ||||
356 | } | ||||
357 | | ||||
358 | QVariant data(const QModelIndex &item, | ||||
359 | int role = Qt::DisplayRole) const override | ||||
360 | { | ||||
361 | if (item.parent().isValid()) return QVariant(); | ||||
362 | | ||||
363 | const auto index = item.row(); | ||||
364 | | ||||
365 | const auto entry = m_itemEntries[m_items[index].value()]; | ||||
366 | | ||||
367 | return entry == nullptr ? QVariant() | ||||
368 | : role == Qt::DisplayRole ? entry->name() | ||||
369 | : role == Qt::DecorationRole ? entry->icon() | ||||
370 | : role == Kicker::DescriptionRole ? entry->description() | ||||
371 | : role == Kicker::FavoriteIdRole ? entry->id() | ||||
372 | : role == Kicker::UrlRole ? entry->url() | ||||
373 | : role == Kicker::HasActionListRole ? entry->hasActions() | ||||
374 | : role == Kicker::ActionListRole ? entry->actions() | ||||
375 | : QVariant(); | ||||
376 | } | ||||
377 | | ||||
378 | bool trigger(int row, const QString &actionId, const QVariant &argument) | ||||
379 | { | ||||
380 | if (row < 0 || row >= rowCount()) { | ||||
381 | return false; | ||||
382 | } | ||||
383 | | ||||
384 | const QString id = data(index(row, 0), Kicker::UrlRole).toString(); | ||||
385 | | ||||
386 | return m_itemEntries.contains(id) | ||||
387 | ? m_itemEntries[id]->run(actionId, argument) | ||||
388 | : false; | ||||
389 | } | ||||
390 | | ||||
391 | void move(int from, int to) | ||||
392 | { | ||||
393 | if (from < 0) return; | ||||
394 | if (from >= m_items.count()) return; | ||||
395 | if (to < 0) return; | ||||
396 | if (to >= m_items.count()) return; | ||||
397 | | ||||
398 | if (from == to) return; | ||||
399 | | ||||
400 | const int modelTo = to + (to > from ? 1 : 0); | ||||
401 | | ||||
402 | if (q->beginMoveRows(QModelIndex(), from, from, | ||||
403 | QModelIndex(), modelTo)) { | ||||
404 | m_items.move(from, to); | ||||
405 | q->endMoveRows(); | ||||
406 | | ||||
407 | DEBUG << "Save ordering (from Private::move) -->"; | ||||
408 | saveOrdering(); | ||||
409 | } | ||||
410 | } | ||||
411 | | ||||
412 | void saveOrdering() | ||||
413 | { | ||||
414 | QStringList ids; | ||||
415 | | ||||
416 | for (const auto& item: m_items) { | ||||
417 | ids << item.value(); | ||||
418 | } | ||||
419 | | ||||
420 | DEBUG << "Save ordering (from Private::saveOrdering) -->"; | ||||
421 | saveOrdering(ids, m_clientId, m_activities.currentActivity()); | ||||
422 | } | ||||
423 | | ||||
424 | static void saveOrdering(const QStringList &ids, const QString &clientId, const QString ¤tActivity) | ||||
425 | { | ||||
426 | const auto cfg = KSharedConfig::openConfig("kactivitymanagerd-statsrc"); | ||||
427 | | ||||
428 | QStringList activities { | ||||
429 | currentActivity, | ||||
430 | "global" | ||||
431 | }; | ||||
432 | | ||||
433 | qDebug() << "Saving ordering for" << currentActivity << "and global" << ids; | ||||
434 | | ||||
435 | for (const auto& activity: activities) { | ||||
436 | const QString groupName = | ||||
437 | "Favorites-" + clientId + "-" + activity; | ||||
438 | | ||||
439 | KConfigGroup cfgGroup(cfg, groupName); | ||||
440 | | ||||
441 | cfgGroup.writeEntry("ordering", ids); | ||||
442 | } | ||||
443 | | ||||
444 | cfg->sync(); | ||||
445 | } | ||||
446 | | ||||
447 | KAStatsFavoritesModel *const q; | ||||
448 | KActivities::Consumer m_activities; | ||||
449 | Query m_query; | ||||
450 | ResultWatcher m_watcher; | ||||
451 | QString m_clientId; | ||||
452 | | ||||
453 | QVector<NormalizedId> m_items; | ||||
454 | QHash<QString, AbstractEntry*> m_itemEntries; | ||||
455 | QStringList m_ignoredItems; | ||||
456 | }; | ||||
457 | | ||||
458 | #undef DEBUG | ||||
459 | #define DEBUG qDebug() << DEBUG_PREFIX << ((void*)this) << ((void*)d) << (d ? d->m_clientId : QString("no client ID yet")) << "\033[0;0m " | ||||
460 | | ||||
461 | KAStatsFavoritesModel::KAStatsFavoritesModel(QObject *parent) | ||||
462 | : PlaceholderModel(parent) | ||||
463 | , d(nullptr) // we have no client id yet | ||||
464 | , m_enabled(true) | ||||
465 | , m_maxFavorites(-1) | ||||
466 | , m_activities(new KActivities::Consumer(this)) | ||||
467 | { | ||||
468 | connect(m_activities, &KActivities::Consumer::currentActivityChanged, | ||||
469 | this, [&] (const QString ¤tActivity) { | ||||
470 | DEBUG << "Activity just got changed"; | ||||
471 | Q_UNUSED(currentActivity); | ||||
472 | auto clientId = d->m_clientId; | ||||
473 | initForClient(clientId); | ||||
474 | }); | ||||
475 | } | ||||
476 | | ||||
477 | KAStatsFavoritesModel::~KAStatsFavoritesModel() | ||||
478 | { | ||||
479 | delete d; | ||||
480 | } | ||||
481 | | ||||
482 | void KAStatsFavoritesModel::initForClient(const QString &clientId) | ||||
483 | { | ||||
484 | DEBUG << "initForClient" << clientId; | ||||
485 | | ||||
486 | setSourceModel(nullptr); | ||||
487 | delete d; | ||||
488 | d = new Private( | ||||
489 | this, | ||||
490 | clientId | ||||
491 | ); | ||||
492 | | ||||
493 | if (!qobject_cast<KAStatsFavoritesModel*>(QObject::parent())) { | ||||
494 | // creating a model test with a copy of this | ||||
495 | DEBUG << "Creating the test model"; | ||||
496 | new ModelTest(new KAStatsFavoritesModel(d), d); | ||||
497 | } | ||||
498 | | ||||
499 | setSourceModel(d); | ||||
500 | } | ||||
501 | | ||||
502 | QString KAStatsFavoritesModel::description() const | ||||
503 | { | ||||
504 | return i18n("Favorites"); | ||||
505 | } | ||||
506 | | ||||
507 | bool KAStatsFavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) | ||||
508 | { | ||||
509 | return d->trigger(row, actionId, argument); | ||||
510 | } | ||||
511 | | ||||
512 | bool KAStatsFavoritesModel::enabled() const | ||||
513 | { | ||||
514 | return m_enabled; | ||||
515 | } | ||||
516 | | ||||
517 | int KAStatsFavoritesModel::maxFavorites() const | ||||
518 | { | ||||
519 | return m_maxFavorites; | ||||
520 | } | ||||
521 | | ||||
522 | void KAStatsFavoritesModel::setMaxFavorites(int max) | ||||
523 | { | ||||
524 | Q_UNUSED(max); | ||||
525 | } | ||||
526 | | ||||
527 | void KAStatsFavoritesModel::setEnabled(bool enable) | ||||
528 | { | ||||
529 | if (m_enabled != enable) { | ||||
530 | m_enabled = enable; | ||||
531 | | ||||
532 | emit enabledChanged(); | ||||
533 | } | ||||
534 | } | ||||
535 | | ||||
536 | QStringList KAStatsFavoritesModel::favorites() const | ||||
537 | { | ||||
538 | qWarning() << "KAStatsFavoritesModel::favorites returns nothing, it is here just to keep the API backwards-compatible"; | ||||
539 | return QStringList(); | ||||
540 | } | ||||
541 | | ||||
542 | void KAStatsFavoritesModel::setFavorites(const QStringList& favorites) | ||||
543 | { | ||||
544 | Q_UNUSED(favorites); | ||||
545 | qWarning() << "KAStatsFavoritesModel::setFavorites is ignored"; | ||||
546 | } | ||||
547 | | ||||
548 | bool KAStatsFavoritesModel::isFavorite(const QString &id) const | ||||
549 | { | ||||
550 | return d && d->m_itemEntries.contains(id); | ||||
551 | } | ||||
552 | | ||||
553 | void KAStatsFavoritesModel::portOldFavorites(const QStringList &ids) | ||||
554 | { | ||||
555 | DEBUG << "portOldFavorites" << ids; | ||||
556 | | ||||
557 | const auto activityId = ":global"; | ||||
558 | std::for_each(ids.begin(), ids.end(), [&] (const QString &id) { | ||||
559 | addFavoriteTo(id, activityId); | ||||
560 | }); | ||||
561 | | ||||
562 | // Resetting the model | ||||
563 | auto clientId = d->m_clientId; | ||||
564 | setSourceModel(nullptr); | ||||
565 | delete d; | ||||
566 | d = nullptr; | ||||
567 | | ||||
568 | DEBUG << "Save ordering (from portOldFavorites) -->"; | ||||
569 | Private::saveOrdering(ids, clientId, m_activities->currentActivity()); | ||||
570 | | ||||
571 | QTimer::singleShot(500, | ||||
572 | std::bind(&KAStatsFavoritesModel::initForClient, this, clientId)); | ||||
573 | } | ||||
574 | | ||||
575 | void KAStatsFavoritesModel::addFavorite(const QString &id, int index) | ||||
576 | { | ||||
577 | DEBUG << "addFavorite" << id << index << " -->"; | ||||
578 | addFavoriteTo(id, Activity::current(), index); | ||||
579 | } | ||||
580 | | ||||
581 | void KAStatsFavoritesModel::removeFavorite(const QString &id) | ||||
582 | { | ||||
583 | DEBUG << "removeFavorite" << id << " -->"; | ||||
584 | removeFavoriteFrom(id, Activity::current()); | ||||
585 | } | ||||
586 | | ||||
587 | void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const QString &activityId, int index) | ||||
588 | { | ||||
589 | DEBUG << "addFavoriteTo" << id << activityId << index << " -->"; | ||||
590 | addFavoriteTo(id, Activity(activityId), index); | ||||
591 | } | ||||
592 | | ||||
593 | void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const QString &activityId) | ||||
594 | { | ||||
595 | DEBUG << "removeFavoriteFrom" << id << activityId << " -->"; | ||||
596 | removeFavoriteFrom(id, Activity(activityId)); | ||||
597 | } | ||||
598 | | ||||
599 | void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const Activity &activity, int index) | ||||
600 | { | ||||
601 | if (!d || id.isEmpty()) return; | ||||
602 | | ||||
603 | Q_ASSERT(!activity.values.isEmpty()); | ||||
604 | | ||||
605 | setDropPlaceholderIndex(-1); | ||||
606 | | ||||
607 | QStringList matchers { d->m_activities.currentActivity(), ":global", ":current" }; | ||||
608 | if (std::find_first_of(activity.values.cbegin(), activity.values.cend(), | ||||
609 | matchers.cbegin(), matchers.cend()) != activity.values.cend()) { | ||||
610 | d->addResult(id, index); | ||||
611 | } | ||||
612 | | ||||
613 | const auto url = d->normalizedId(id).value(); | ||||
614 | | ||||
615 | DEBUG << "addFavoriteTo" << id << activity << index << url << " (actual)"; | ||||
616 | | ||||
617 | if (url.isEmpty()) return; | ||||
618 | | ||||
619 | d->m_watcher.linkToActivity(QUrl(url), activity, | ||||
620 | Agent(agentForUrl(url))); | ||||
621 | } | ||||
622 | | ||||
623 | void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const Activity &activity) | ||||
624 | { | ||||
625 | const auto url = d->normalizedId(id).value(); | ||||
626 | | ||||
627 | Q_ASSERT(!activity.values.isEmpty()); | ||||
628 | | ||||
629 | DEBUG << "addFavoriteTo" << id << activity << url << " (actual)"; | ||||
630 | | ||||
631 | if (url.isEmpty()) return; | ||||
632 | | ||||
633 | d->m_watcher.unlinkFromActivity(QUrl(url), activity, | ||||
634 | Agent(agentForUrl(url))); | ||||
635 | } | ||||
636 | | ||||
637 | void KAStatsFavoritesModel::setFavoriteOn(const QString &id, const QString &activityId) | ||||
638 | { | ||||
639 | const auto url = d->normalizedId(id).value(); | ||||
640 | | ||||
641 | DEBUG << "setFavoriteOn" << id << activityId << url << " (actual)"; | ||||
642 | | ||||
643 | DEBUG << "%%%%%%%%%%% Activity is" << activityId; | ||||
644 | if (activityId.isEmpty() || activityId == ":any" || | ||||
645 | activityId == ":global" || | ||||
646 | activityId == m_activities->currentActivity()) { | ||||
647 | d->m_ignoredItems << url; | ||||
648 | } | ||||
649 | | ||||
650 | d->m_watcher.unlinkFromActivity(QUrl(url), Activity::any(), | ||||
651 | Agent(agentForUrl(url))); | ||||
652 | d->m_watcher.linkToActivity(QUrl(url), activityId, | ||||
653 | Agent(agentForUrl(url))); | ||||
654 | } | ||||
655 | | ||||
656 | void KAStatsFavoritesModel::moveRow(int from, int to) | ||||
657 | { | ||||
658 | d->move(from, to); | ||||
659 | } | ||||
660 | | ||||
661 | AbstractModel *KAStatsFavoritesModel::favoritesModel() | ||||
662 | { | ||||
663 | return this; | ||||
664 | } | ||||
665 | | ||||
666 | void KAStatsFavoritesModel::refresh() | ||||
667 | { | ||||
668 | } | ||||
669 | | ||||
670 | QObject *KAStatsFavoritesModel::activities() const | ||||
671 | { | ||||
672 | return m_activities; | ||||
673 | } | ||||
674 | | ||||
675 | QString KAStatsFavoritesModel::activityNameForId(const QString &activityId) const | ||||
676 | { | ||||
677 | // It is safe to use a short-lived object here, | ||||
678 | // we are always synced with KAMD in plasma | ||||
679 | KActivities::Info info(activityId); | ||||
680 | return info.name(); | ||||
681 | } | ||||
682 | | ||||
683 | QStringList KAStatsFavoritesModel::linkedActivitiesFor(const QString &id) const | ||||
684 | { | ||||
685 | if (!d) { | ||||
686 | DEBUG << "Linked for" << id << "is empty, no Private instance"; | ||||
687 | return {}; | ||||
688 | } | ||||
689 | | ||||
690 | auto url = d->normalizedId(id).value(); | ||||
691 | | ||||
692 | if (url.startsWith("file:")) { | ||||
693 | url = QUrl(url).toLocalFile(); | ||||
694 | } | ||||
695 | | ||||
696 | if (url.isEmpty()) { | ||||
697 | DEBUG << "The url for" << id << "is empty"; | ||||
698 | return {}; | ||||
699 | } | ||||
700 | | ||||
701 | auto query = LinkedResources | ||||
702 | | Agent { | ||||
703 | AGENT_APPLICATIONS, | ||||
704 | AGENT_CONTACTS, | ||||
705 | AGENT_DOCUMENTS | ||||
706 | } | ||||
707 | | Type::any() | ||||
708 | | Activity::any() | ||||
709 | | Url(url); | ||||
710 | | ||||
711 | ResultSet results(query); | ||||
712 | | ||||
713 | for (const auto &result: results) { | ||||
714 | DEBUG << "Returning" << result.linkedActivities() << "for" << id << url; | ||||
715 | return result.linkedActivities(); | ||||
716 | } | ||||
717 | | ||||
718 | DEBUG << "Returning empty list of activities for" << id << url; | ||||
719 | return {}; | ||||
720 | } | ||||
721 | |