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) 2015 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 <QDebug> | ||||
28 | #include <QFileInfo> | ||||
29 | #include <QTimer> | ||||
30 | | ||||
31 | #include <KLocalizedString> | ||||
32 | #include <KConfigGroup> | ||||
33 | | ||||
34 | #include <KActivities/Consumer> | ||||
35 | #include <KActivities/Stats/Terms> | ||||
36 | #include <KActivities/Stats/Query> | ||||
37 | #include <KActivities/Stats/ResultSet> | ||||
38 | #include <KActivities/Stats/ResultModel> | ||||
39 | | ||||
40 | #include <QListView> | ||||
41 | | ||||
42 | #include "modeltest.h" | ||||
43 | | ||||
44 | namespace KAStats = KActivities::Stats; | ||||
45 | | ||||
46 | using namespace KAStats; | ||||
47 | using namespace KAStats::Terms; | ||||
48 | | ||||
49 | QListView *mainList; | ||||
50 | QListView *sourceList; | ||||
51 | | ||||
52 | KAStatsFavoritesModel::KAStatsFavoritesModel(QObject *parent) : PlaceholderModel(parent) | ||||
53 | , m_enabled(true) | ||||
54 | , m_maxFavorites(-1) | ||||
55 | , m_whereTheItemIsBeingDropped(-1) | ||||
56 | , m_sourceModel(nullptr) | ||||
57 | , m_activities(new KActivities::Consumer(this)) | ||||
58 | , m_config("TESTTEST") | ||||
59 | { | ||||
60 | // new ModelTest(this); | ||||
61 | | ||||
62 | mainList = new QListView(); | ||||
63 | mainList->setModel(this); | ||||
64 | mainList->setWindowTitle("Main"); | ||||
65 | mainList->show(); | ||||
66 | | ||||
67 | sourceList = new QListView(); | ||||
68 | sourceList->setWindowTitle("Source"); | ||||
69 | sourceList->show(); | ||||
70 | | ||||
71 | auto query = LinkedResources | ||||
72 | | Agent { | ||||
73 | "org.kde.plasma.favorites.applications", | ||||
74 | "org.kde.plasma.favorites.contacts" | ||||
75 | } | ||||
76 | | Type::any() | ||||
77 | | Activity::current() | ||||
78 | | Activity::global() | ||||
79 | | Limit(15); | ||||
80 | | ||||
81 | m_sourceModel = new ResultModel(query, "org.kde.plasma.favorites", this); | ||||
82 | | ||||
83 | sourceList->setModel(m_sourceModel); | ||||
84 | | ||||
85 | connect(m_sourceModel, &ResultModel::rowsInserted, | ||||
86 | this, &KAStatsFavoritesModel::rowsInserted); | ||||
87 | | ||||
88 | QModelIndex index; | ||||
89 | | ||||
90 | if (m_sourceModel->canFetchMore(index)) { | ||||
91 | m_sourceModel->fetchMore(index); | ||||
92 | } | ||||
93 | | ||||
94 | setSourceModel(m_sourceModel); | ||||
95 | } | ||||
96 | | ||||
97 | KAStatsFavoritesModel::~KAStatsFavoritesModel() | ||||
98 | { | ||||
99 | } | ||||
100 | | ||||
101 | QString KAStatsFavoritesModel::description() const | ||||
102 | { | ||||
103 | return i18n("Favorites"); | ||||
104 | } | ||||
105 | | ||||
106 | QVariant KAStatsFavoritesModel::internalData(const QModelIndex& index, int role) const | ||||
107 | { | ||||
108 | if (!index.isValid() || index.row() >= rowCount()) { | ||||
109 | return QVariant(); | ||||
110 | } | ||||
111 | | ||||
112 | const QString id = | ||||
113 | sourceModel()->data(index, ResultModel::ResourceRole).toString(); | ||||
114 | | ||||
115 | // const casts are bad, but we can not achieve this | ||||
116 | // with the standard 'mutable' members for lazy evaluation, | ||||
117 | // at least, not with the current design of the library | ||||
118 | const auto _this = const_cast<KAStatsFavoritesModel*>(this); | ||||
119 | | ||||
120 | if (m_whereTheItemIsBeingDropped != -1) { | ||||
121 | // If we are in the process of dropping an item, we need to wait | ||||
122 | // for it to get here. When it does, request it to be moved | ||||
123 | // to its desired location. We can not do it in a better way | ||||
124 | // because the model might be reset in the mean time | ||||
125 | qDebug() << "Waiting for " << m_whichIdIsBeingDropped | ||||
126 | << "and the id is " << id << "<-------------"; | ||||
127 | if (m_whichIdIsBeingDropped == id) { | ||||
128 | qDebug() << "Requesting the item to be moved... async... <------"; | ||||
129 | | ||||
130 | // This needs to happen only once, we need to make the captured | ||||
131 | // variable independent of the member variable and we are not | ||||
132 | // in C++14 | ||||
133 | const auto where = m_whereTheItemIsBeingDropped; | ||||
134 | m_whereTheItemIsBeingDropped = -1; | ||||
135 | _this->m_sourceModel->setResultPosition(id, where); | ||||
136 | | ||||
137 | } | ||||
138 | } | ||||
139 | | ||||
140 | const auto *entry = _this->favoriteFromId(id); | ||||
141 | | ||||
142 | if (!entry || !entry->isValid()) { | ||||
143 | // If the result is not valid, we need to unlink it -- to | ||||
144 | // remove it from the model | ||||
145 | const auto url = urlForId(id); | ||||
146 | | ||||
147 | if (!m_invalidUrls.contains(url)) { | ||||
148 | m_sourceModel->unlinkFromActivity( | ||||
149 | url, Activity::any(), | ||||
150 | Agent(agentForScheme(url.scheme())) | ||||
151 | ); | ||||
152 | m_invalidUrls << url; | ||||
153 | } | ||||
154 | | ||||
155 | // return QVariant("NULL for '" + url + "'! - " + agentForScheme(scheme)); | ||||
156 | | ||||
157 | return role == Qt::DecorationRole ? "unknown" | ||||
158 | : QVariant(); | ||||
159 | } | ||||
160 | | ||||
161 | // qDebug() << "Entry: " << entry->name() << entry->icon(); | ||||
162 | | ||||
163 | return role == Qt::DisplayRole ? entry->name() | ||||
164 | : role == Qt::DecorationRole ? entry->icon() | ||||
165 | : role == Kicker::DescriptionRole ? entry->description() | ||||
166 | : role == Kicker::FavoriteIdRole ? entry->id() | ||||
167 | : role == Kicker::UrlRole ? entry->url() | ||||
168 | : role == Kicker::HasActionListRole ? entry->hasActions() | ||||
169 | : role == Kicker::ActionListRole ? entry->actions() | ||||
170 | : QVariant(); | ||||
171 | } | ||||
172 | | ||||
173 | int KAStatsFavoritesModel::rowCount(const QModelIndex& parent) const | ||||
174 | { | ||||
175 | return parent.isValid() | ||||
176 | ? 0 | ||||
177 | : PlaceholderModel::rowCount(parent); | ||||
178 | } | ||||
179 | | ||||
180 | bool KAStatsFavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) | ||||
181 | { | ||||
182 | if (row < 0 || row >= rowCount()) { | ||||
183 | return false; | ||||
184 | } | ||||
185 | | ||||
186 | const QString id = | ||||
187 | PlaceholderModel::data(index(row, 0), ResultModel::ResourceRole).toString(); | ||||
188 | | ||||
189 | return m_entries.contains(id) | ||||
190 | ? m_entries[id]->run(actionId, argument) | ||||
191 | : false; | ||||
192 | } | ||||
193 | | ||||
194 | bool KAStatsFavoritesModel::enabled() const | ||||
195 | { | ||||
196 | return m_enabled; | ||||
197 | } | ||||
198 | | ||||
199 | int KAStatsFavoritesModel::maxFavorites() const | ||||
200 | { | ||||
201 | return m_maxFavorites; | ||||
202 | } | ||||
203 | | ||||
204 | void KAStatsFavoritesModel::setMaxFavorites(int max) | ||||
205 | { | ||||
206 | } | ||||
207 | | ||||
208 | void KAStatsFavoritesModel::setEnabled(bool enable) | ||||
209 | { | ||||
210 | if (m_enabled != enable) { | ||||
211 | m_enabled = enable; | ||||
212 | | ||||
213 | emit enabledChanged(); | ||||
214 | } | ||||
215 | } | ||||
216 | | ||||
217 | QStringList KAStatsFavoritesModel::favorites() const | ||||
218 | { | ||||
219 | return QStringList(); | ||||
220 | } | ||||
221 | | ||||
222 | void KAStatsFavoritesModel::setFavorites(const QStringList& favorites) | ||||
223 | { | ||||
224 | } | ||||
225 | | ||||
226 | void KAStatsFavoritesModel::removeOldCachedEntries() const | ||||
227 | { | ||||
228 | QList<QUrl> knownUrls; | ||||
229 | for (int row = 0; row < rowCount(); ++row) { | ||||
230 | // qDebug() << "URL we got is" << sourceModel()->data(index(row, 0), ResultModel::ResourceRole); | ||||
231 | knownUrls << | ||||
232 | urlForId(sourceModel()->data(index(row, 0), ResultModel::ResourceRole).toString()); | ||||
233 | } | ||||
234 | | ||||
235 | // qDebug() << "Known urls are: " << knownUrls; | ||||
236 | | ||||
237 | QMutableHashIterator<QString, AbstractEntry*> i(m_entries); | ||||
238 | while (i.hasNext()) { | ||||
239 | i.next(); | ||||
240 | | ||||
241 | const auto url = urlForId(i.key()); | ||||
242 | | ||||
243 | if (!knownUrls.contains(url)) { | ||||
244 | delete i.value(); | ||||
245 | i.remove(); | ||||
246 | } | ||||
247 | } | ||||
248 | } | ||||
249 | | ||||
250 | bool KAStatsFavoritesModel::isFavorite(const QString &id) const | ||||
251 | { | ||||
252 | removeOldCachedEntries(); | ||||
253 | return m_entries.contains(id); | ||||
254 | } | ||||
255 | | ||||
256 | void KAStatsFavoritesModel::addFavorite(const QString &id, int index) | ||||
257 | { | ||||
258 | addFavoriteTo(id, Activity::current(), index); | ||||
259 | } | ||||
260 | | ||||
261 | void KAStatsFavoritesModel::removeFavorite(const QString &id) | ||||
262 | { | ||||
263 | removeFavoriteFrom(id, Activity::current()); | ||||
264 | } | ||||
265 | | ||||
266 | void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const QString &activityId, int index) | ||||
267 | { | ||||
268 | addFavoriteTo(id, Activity(activityId), index); | ||||
269 | } | ||||
270 | | ||||
271 | void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const QString &activityId) | ||||
272 | { | ||||
273 | removeFavoriteFrom(id, Activity(activityId)); | ||||
274 | } | ||||
275 | | ||||
276 | void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const Activity &activity, int index) | ||||
277 | { | ||||
278 | if (index == -1) { | ||||
279 | index = m_sourceModel->rowCount(); | ||||
280 | setDropPlaceholderIndex(index); | ||||
281 | } | ||||
282 | | ||||
283 | setDropPlaceholderIndex(-1); | ||||
284 | | ||||
285 | if (id.isEmpty()) return; | ||||
286 | | ||||
287 | const auto url = urlForId(id); | ||||
288 | | ||||
289 | qDebug() << "Adding favorite to " << id << activity << url; | ||||
290 | | ||||
291 | // This is a file, we want to check that it exists | ||||
292 | if (url.isLocalFile() && !QFileInfo::exists(url.toLocalFile())) return; | ||||
293 | | ||||
294 | m_sourceModel->linkToActivity( | ||||
295 | url, activity, | ||||
296 | Agent(agentForScheme(url.scheme())) | ||||
297 | ); | ||||
298 | | ||||
299 | // Lets handle async repositioning of the item, see ::data | ||||
300 | qDebug() << "We need to add " << url << "at" << index << "<------------"; | ||||
301 | m_whereTheItemIsBeingDropped = index; | ||||
302 | | ||||
303 | if (index != -1) { | ||||
304 | m_whichIdIsBeingDropped = url.toLocalFile(); | ||||
305 | } else { | ||||
306 | m_whichIdIsBeingDropped.clear(); | ||||
307 | } | ||||
308 | } | ||||
309 | | ||||
310 | void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const Activity &activity) | ||||
311 | { | ||||
312 | const auto url = urlForId(id); | ||||
313 | | ||||
314 | qDebug() << "Removing favorite from " << id << activity << url; | ||||
315 | | ||||
316 | m_sourceModel->unlinkFromActivity( | ||||
317 | url, activity, | ||||
318 | Agent(agentForScheme(url.scheme())) | ||||
319 | ); | ||||
320 | } | ||||
321 | | ||||
322 | void KAStatsFavoritesModel::moveRow(int from, int to) | ||||
323 | { | ||||
324 | const QString id = | ||||
325 | PlaceholderModel::data(index(from, 0), ResultModel::ResourceRole).toString(); | ||||
326 | | ||||
327 | m_sourceModel->setResultPosition(urlForId(id).toString(), to); | ||||
328 | } | ||||
329 | | ||||
330 | AbstractModel *KAStatsFavoritesModel::favoritesModel() | ||||
331 | { | ||||
332 | return this; | ||||
333 | } | ||||
334 | | ||||
335 | void KAStatsFavoritesModel::refresh() | ||||
336 | { | ||||
337 | } | ||||
338 | | ||||
339 | AbstractEntry *KAStatsFavoritesModel::favoriteFromId(const QString &id) const | ||||
340 | { | ||||
341 | auto _this = const_cast<KAStatsFavoritesModel*>(this); | ||||
342 | | ||||
343 | if (!m_entries.contains(id)) { | ||||
344 | const QUrl url(id); | ||||
345 | const QString &scheme = url.scheme(); | ||||
346 | | ||||
347 | qDebug() << "favoriteFromId: " << id << " - " << url << " - " << scheme; | ||||
348 | | ||||
349 | AbstractEntry *entry = nullptr; | ||||
350 | | ||||
351 | if (scheme == "ktp") { | ||||
352 | entry = new ContactEntry(_this, id); | ||||
353 | } else { | ||||
354 | entry = new AppEntry(_this, id); | ||||
355 | } | ||||
356 | | ||||
357 | m_entries[id] = entry; | ||||
358 | } | ||||
359 | | ||||
360 | return m_entries[id]; | ||||
361 | } | ||||
362 | | ||||
363 | QUrl KAStatsFavoritesModel::urlForId(const QString &id) const | ||||
364 | { | ||||
365 | const auto entry = favoriteFromId(id); | ||||
366 | | ||||
367 | const auto url = entry && entry->isValid() ? entry->url() | ||||
368 | : QUrl(); | ||||
369 | | ||||
370 | // We want to resolve symbolic links not to have two paths | ||||
371 | // refer to the same .desktop file | ||||
372 | if (url.isLocalFile()) { | ||||
373 | QFileInfo file(url.toLocalFile()); | ||||
374 | | ||||
375 | if (file.exists()) { | ||||
376 | return QUrl::fromLocalFile(file.canonicalFilePath()); | ||||
377 | } | ||||
378 | } | ||||
379 | | ||||
380 | return url; | ||||
381 | } | ||||
382 | | ||||
383 | QString KAStatsFavoritesModel::agentForScheme(const QString &scheme) const | ||||
384 | { | ||||
385 | return scheme == | ||||
386 | QStringLiteral("ktp") ? "org.kde.plasma.favorites.contacts" | ||||
387 | : "org.kde.plasma.favorites.applications"; | ||||
388 | } | ||||
389 | | ||||
390 | QObject *KAStatsFavoritesModel::activities() const | ||||
391 | { | ||||
392 | return m_activities; | ||||
393 | } | ||||
394 | | ||||
395 | QString KAStatsFavoritesModel::activityNameForId(const QString &activityId) const | ||||
396 | { | ||||
397 | // It is safe to use a short-lived object here, | ||||
398 | // we are always synced with KAMD in plasma | ||||
399 | KActivities::Info info(activityId); | ||||
400 | return info.name(); | ||||
401 | } | ||||
402 | | ||||
403 | QStringList KAStatsFavoritesModel::linkedActivitiesFor(const QString &id) const | ||||
404 | { | ||||
405 | auto url = urlForId(id); | ||||
406 | | ||||
407 | if (!url.isValid()) { | ||||
408 | return {}; | ||||
409 | } | ||||
410 | | ||||
411 | auto urlString = | ||||
412 | url.scheme() == "file" ? | ||||
413 | url.toLocalFile() : url.toString(); | ||||
414 | | ||||
415 | qDebug() << "Fetching linked activities for: " << id << urlString; | ||||
416 | | ||||
417 | auto query = LinkedResources | ||||
418 | | Agent { | ||||
419 | "org.kde.plasma.favorites.applications", | ||||
420 | "org.kde.plasma.favorites.contacts" | ||||
421 | } | ||||
422 | | Type::any() | ||||
423 | | Activity::any() | ||||
424 | | Url(urlString); | ||||
425 | | ||||
426 | ResultSet results(query); | ||||
427 | | ||||
428 | for (const auto &result: results) { | ||||
429 | qDebug() << "Linked activities for " << id << "are" << result.linkedActivities(); | ||||
430 | return result.linkedActivities(); | ||||
431 | } | ||||
432 | | ||||
433 | qDebug() << "NO linked activities for " << id; | ||||
434 | return {}; | ||||
435 | } | ||||
436 | |