1/*
2 SPDX-FileCopyrightText: 2016 Dan Leinir Turthra Jensen <admin@leinir.dk>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "quickitemsmodel.h"
8#include "core/commentsmodel.h"
9#include "downloadlinkinfo.h"
10#include "itemsmodel.h"
11#include "quickengine.h"
12
13#include <KShell>
14#include <QProcess>
15
16class ItemsModelPrivate
17{
18public:
19 ItemsModelPrivate(ItemsModel *qq)
20 : q(qq)
21 , model(nullptr)
22 , engine(nullptr)
23 {
24 }
25 ItemsModel *q;
26 KNSCore::ItemsModel *model;
27 Engine *engine;
28
29 QHash<QString, KNSCore::CommentsModel *> commentsModels;
30
31 bool initModel()
32 {
33 if (model) {
34 return true;
35 }
36 if (!engine) {
37 return false;
38 }
39 model = new KNSCore::ItemsModel(engine, q);
40
41 q->connect(sender: engine, signal: &Engine::signalProvidersLoaded, context: engine, slot: &Engine::reloadEntries);
42 // Entries have been fetched and should be shown:
43 q->connect(sender: engine, signal: &Engine::signalEntriesLoaded, context: model, slot: [this](const KNSCore::Entry::List &entries) {
44 model->slotEntriesLoaded(entries);
45 });
46 q->connect(sender: engine, signal: &Engine::entryEvent, context: model, slot: [this](const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event) {
47 if (event == KNSCore::Entry::DetailsLoadedEvent && engine->filter() != KNSCore::Provider::Installed
48 && engine->filter() != KNSCore::Provider::Updates) {
49 model->slotEntriesLoaded(entries: KNSCore::Entry::List{entry});
50 }
51 });
52
53 // Check if we need intermediate states
54 q->connect(sender: engine, signal: &Engine::entryEvent, context: q, slot: [this](const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event) {
55 onEntryEvent(entry, event);
56 });
57 q->connect(sender: engine, signal: &Engine::signalResetView, context: model, slot: &KNSCore::ItemsModel::clearEntries);
58
59 q->connect(sender: model, signal: &KNSCore::ItemsModel::loadPreview, context: engine, slot: &Engine::loadPreview);
60 q->connect(sender: engine, signal: &Engine::entryPreviewLoaded, context: model, slot: &KNSCore::ItemsModel::slotEntryPreviewLoaded);
61
62 q->connect(sender: model, signal: &KNSCore::ItemsModel::rowsInserted, context: q, slot: &ItemsModel::rowsInserted);
63 q->connect(sender: model, signal: &KNSCore::ItemsModel::rowsRemoved, context: q, slot: &ItemsModel::rowsRemoved);
64 q->connect(sender: model, signal: &KNSCore::ItemsModel::dataChanged, context: q, slot: &ItemsModel::dataChanged);
65 q->connect(sender: model, signal: &KNSCore::ItemsModel::modelAboutToBeReset, context: q, slot: &ItemsModel::modelAboutToBeReset);
66 q->connect(sender: model, signal: &KNSCore::ItemsModel::modelReset, context: q, slot: &ItemsModel::modelReset);
67 return true;
68 }
69
70 void onEntryEvent(const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event)
71 {
72 if (event == KNSCore::Entry::StatusChangedEvent) {
73 model->slotEntryChanged(entry);
74 Q_EMIT q->entryChanged(entry);
75
76 // If we update/uninstall an entry we have to update the UI, see BUG: 425135
77 if (engine->filter() == KNSCore::Provider::Updates && entry.status() != KNSCore::Entry::Updateable && entry.status() != KNSCore::Entry::Updating) {
78 model->removeEntry(entry);
79 } else if (engine->filter() == KNSCore::Provider::Installed && entry.status() == KNSCore::Entry::Deleted) {
80 model->removeEntry(entry);
81 }
82 }
83
84 if (event == KNSCore::Entry::DetailsLoadedEvent) {
85 model->slotEntryChanged(entry);
86 Q_EMIT q->entryChanged(entry);
87 }
88 }
89};
90
91ItemsModel::ItemsModel(QObject *parent)
92 : QAbstractListModel(parent)
93 , d(new ItemsModelPrivate(this))
94{
95}
96
97ItemsModel::~ItemsModel() = default;
98
99QHash<int, QByteArray> ItemsModel::roleNames() const
100{
101 static const QHash<int, QByteArray> roles = QHash<int, QByteArray>{
102 {Qt::DisplayRole, "display"},
103 {NameRole, "name"},
104 {UniqueIdRole, "uniqueId"},
105 {CategoryRole, "category"},
106 {HomepageRole, "homepage"},
107 {AuthorRole, "author"},
108 {LicenseRole, "license"},
109 {ShortSummaryRole, "shortSummary"},
110 {SummaryRole, "summary"},
111 {ChangelogRole, "changelog"},
112 {VersionRole, "version"},
113 {ReleaseDateRole, "releaseDate"},
114 {UpdateVersionRole, "updateVersion"},
115 {UpdateReleaseDateRole, "updateReleaseDate"},
116 {PayloadRole, "payload"},
117 {Qt::DecorationRole, "decoration"},
118 {PreviewsSmallRole, "previewsSmall"},
119 {PreviewsRole, "previews"},
120 {InstalledFilesRole, "installedFiles"},
121 {UnInstalledFilesRole, "uninstalledFiles"},
122 {RatingRole, "rating"},
123 {NumberOfCommentsRole, "numberOfComments"},
124 {DownloadCountRole, "downloadCount"},
125 {NumberFansRole, "numberFans"},
126 {NumberKnowledgebaseEntriesRole, "numberKnowledgebaseEntries"},
127 {KnowledgebaseLinkRole, "knowledgebaseLink"},
128 {DownloadLinksRole, "downloadLinks"},
129 {DonationLinkRole, "donationLink"},
130 {ProviderIdRole, "providerId"},
131 {SourceRole, "source"},
132 {EntryRole, "entry"},
133 };
134 return roles;
135}
136
137int ItemsModel::rowCount(const QModelIndex &parent) const
138{
139 if (parent.isValid()) {
140 return 0;
141 }
142 if (d->initModel()) {
143 return d->model->rowCount(parent: QModelIndex());
144 }
145 return 0;
146}
147
148QVariant ItemsModel::data(const QModelIndex &index, int role) const
149{
150 if (index.isValid() && d->initModel()) {
151 KNSCore::Entry entry = d->model->data(index: d->model->index(row: index.row()), role: Qt::UserRole).value<KNSCore::Entry>();
152 switch (role) {
153 case NameRole:
154 case Qt::DisplayRole:
155 return entry.name();
156 case EntryRole:
157 return QVariant::fromValue(value: entry);
158 case UniqueIdRole:
159 return entry.uniqueId();
160 case CategoryRole:
161 return entry.category();
162 case HomepageRole:
163 return entry.homepage();
164 break;
165 case AuthorRole: {
166 KNSCore::Author author = entry.author();
167 QVariantMap returnAuthor;
168 returnAuthor[QStringLiteral("id")] = author.id();
169 returnAuthor[QStringLiteral("name")] = author.name();
170 returnAuthor[QStringLiteral("email")] = author.email();
171 returnAuthor[QStringLiteral("homepage")] = author.homepage();
172 returnAuthor[QStringLiteral("jabber")] = author.jabber();
173 returnAuthor[QStringLiteral("avatarUrl")] = author.avatarUrl();
174 returnAuthor[QStringLiteral("description")] = author.description();
175 return returnAuthor;
176 } break;
177 case LicenseRole:
178 return entry.license();
179 case ShortSummaryRole:
180 return entry.shortSummary();
181 case SummaryRole:
182 return entry.summary();
183 case ChangelogRole:
184 return entry.changelog();
185 case VersionRole:
186 return entry.version();
187 case ReleaseDateRole:
188 return entry.releaseDate();
189 case UpdateVersionRole:
190 return entry.updateVersion();
191 case UpdateReleaseDateRole:
192 return entry.updateReleaseDate();
193 case PayloadRole:
194 return entry.payload();
195 case Qt::DecorationRole:
196 return entry.previewUrl(type: KNSCore::Entry::PreviewSmall1);
197 case PreviewsSmallRole: {
198 QStringList previews;
199 previews << entry.previewUrl(type: KNSCore::Entry::PreviewSmall1);
200 previews << entry.previewUrl(type: KNSCore::Entry::PreviewSmall2);
201 previews << entry.previewUrl(type: KNSCore::Entry::PreviewSmall3);
202 while (!previews.isEmpty() && previews.last().isEmpty()) {
203 previews.takeLast();
204 }
205 return previews;
206 }
207 case PreviewsRole: {
208 QStringList previews;
209 previews << entry.previewUrl(type: KNSCore::Entry::PreviewBig1);
210 previews << entry.previewUrl(type: KNSCore::Entry::PreviewBig2);
211 previews << entry.previewUrl(type: KNSCore::Entry::PreviewBig3);
212 while (!previews.isEmpty() && previews.last().isEmpty()) {
213 previews.takeLast();
214 }
215 return previews;
216 }
217 case InstalledFilesRole:
218 return entry.installedFiles();
219 case UnInstalledFilesRole:
220 return entry.uninstalledFiles();
221 case RatingRole:
222 return entry.rating();
223 case NumberOfCommentsRole:
224 return entry.numberOfComments();
225 case DownloadCountRole:
226 return entry.downloadCount();
227 case NumberFansRole:
228 return entry.numberFans();
229 case NumberKnowledgebaseEntriesRole:
230 return entry.numberKnowledgebaseEntries();
231 case KnowledgebaseLinkRole:
232 return entry.knowledgebaseLink();
233 case DownloadLinksRole: {
234 // This would be good to cache... but it also needs marking as dirty, somehow...
235 const QList<KNSCore::Entry::DownloadLinkInformation> dllinks = entry.downloadLinkInformationList();
236 QVariantList list;
237 for (const KNSCore::Entry::DownloadLinkInformation &link : dllinks) {
238 list.append(t: QVariant::fromValue(value: DownloadLinkInfo(link)));
239 }
240 if (list.isEmpty() && !entry.payload().isEmpty()) {
241 KNSCore::Entry::DownloadLinkInformation data;
242 data.descriptionLink = entry.payload();
243 list.append(t: QVariant::fromValue(value: DownloadLinkInfo(data)));
244 }
245 return QVariant::fromValue(value: list);
246 }
247 case DonationLinkRole:
248 return entry.donationLink();
249 case ProviderIdRole:
250 return entry.providerId();
251 case SourceRole: {
252 KNSCore::Entry::Source src = entry.source();
253 switch (src) {
254 case KNSCore::Entry::Cache:
255 return QStringLiteral("Cache");
256 case KNSCore::Entry::Online:
257 return QStringLiteral("Online");
258 case KNSCore::Entry::Registry:
259 return QStringLiteral("Registry");
260 default:
261 return QStringLiteral("Unknown source - shouldn't be possible");
262 }
263 }
264 case CommentsModelRole: {
265 KNSCore::CommentsModel *commentsModel{nullptr};
266 if (!d->commentsModels.contains(key: entry.uniqueId())) {
267 commentsModel = new KNSCore::CommentsModel(d->engine);
268 commentsModel->setEntry(entry);
269 d->commentsModels[entry.uniqueId()] = commentsModel;
270 } else {
271 commentsModel = d->commentsModels[entry.uniqueId()];
272 }
273 return QVariant::fromValue(value: commentsModel);
274 }
275 default:
276 return QStringLiteral("Unknown role");
277 }
278 }
279 return QVariant();
280}
281
282bool ItemsModel::canFetchMore(const QModelIndex &parent) const
283{
284 return !parent.isValid() && d->engine && d->engine->categoriesMetadata().count() > 0;
285}
286
287void ItemsModel::fetchMore(const QModelIndex &parent)
288{
289 if (parent.isValid() || !d->engine) {
290 return;
291 }
292 d->engine->requestMoreData();
293}
294
295QObject *ItemsModel::engine() const
296{
297 return d->engine;
298}
299
300void ItemsModel::setEngine(QObject *newEngine)
301{
302 if (d->engine != newEngine) {
303 beginResetModel();
304 d->engine = qobject_cast<Engine *>(object: newEngine);
305 if (d->model) {
306 d->model->deleteLater();
307 d->model = nullptr;
308 }
309 Q_EMIT engineChanged();
310 endResetModel();
311 }
312}
313
314int ItemsModel::indexOfEntryId(const QString &providerId, const QString &entryId)
315{
316 int idx{-1};
317 if (d->engine && d->model) {
318 for (int i = 0; i < rowCount(); ++i) {
319 KNSCore::Entry testEntry = d->model->data(index: d->model->index(row: i), role: Qt::UserRole).value<KNSCore::Entry>();
320 if (providerId == testEntry.providerId() && entryId == testEntry.uniqueId()) {
321 idx = i;
322 break;
323 }
324 }
325 }
326 return idx;
327}
328
329#include "moc_quickitemsmodel.cpp"
330

source code of knewstuff/src/qtquick/quickitemsmodel.cpp