• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.14.38 API Reference
  • KDE Home
  • Contact Us
 

KNewStuff

  • knewstuff
  • knewstuff3
  • attica
atticaprovider.cpp
Go to the documentation of this file.
1/*
2 Copyright (c) 2009-2010 Frederik Gladhorn <gladhorn@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#include "atticaprovider.h"
19
20#include <kdebug.h>
21#include <klocale.h>
22#include <kio/job.h>
23#include <kmessagebox.h>
24
25#include <attica/providermanager.h>
26#include <attica/provider.h>
27#include <attica/listjob.h>
28#include <attica/content.h>
29#include <attica/downloaditem.h>
30#include <attica/accountbalance.h>
31#include <attica/person.h>
32
33using namespace Attica;
34
35namespace KNS3
36{
37
38AtticaProvider::AtticaProvider(const QStringList& categories)
39 : mEntryJob(0)
40 , mInitialized(false)
41{
42 // init categories map with invalid categories
43 foreach (const QString& category, categories)
44 mCategoryMap.insert(category, Attica::Category());
45
46 connect(&m_providerManager, SIGNAL(providerAdded(Attica::Provider)), SLOT(providerLoaded(Attica::Provider)));
47 connect(&m_providerManager, SIGNAL(authenticationCredentialsMissing(Provider)),
48 SLOT(authenticationCredentialsMissing(Provider)));
49}
50
51AtticaProvider::AtticaProvider(const Attica::Provider& provider, const QStringList& categories)
52 : mEntryJob(0)
53 , mInitialized(false)
54{
55 // init categories map with invalid categories
56 foreach (const QString& category, categories) {
57 mCategoryMap.insert(category, Attica::Category());
58 }
59 providerLoaded(provider);
60}
61
62QString AtticaProvider::id() const
63{
64 return m_provider.baseUrl().toString();
65}
66
67void AtticaProvider::authenticationCredentialsMissing(const KNS3::Provider& )
68{
69 kDebug() << "Authentication missing!";
70 // FIXME Show autentication dialog
71}
72
73bool AtticaProvider::setProviderXML(const QDomElement & xmldata)
74{
75 if (xmldata.tagName() != "provider")
76 return false;
77
78 // FIXME this is quite ugly, repackaging the xml into a string
79 QDomDocument doc("temp");
80 kDebug(550) << "setting provider xml" << doc.toString();
81
82 doc.appendChild(xmldata.cloneNode(true));
83 m_providerManager.addProviderFromXml(doc.toString());
84
85 if (!m_providerManager.providers().isEmpty()) {
86 kDebug() << "base url of attica provider:" << m_providerManager.providers().last().baseUrl().toString();
87 } else {
88 kError() << "Could not load provider.";
89 return false;
90 }
91 return true;
92}
93
94void AtticaProvider::setCachedEntries(const KNS3::EntryInternal::List& cachedEntries)
95{
96 mCachedEntries = cachedEntries;
97}
98
99void AtticaProvider::providerLoaded(const Attica::Provider& provider)
100{
101 mName = provider.name();
102 kDebug() << "Added provider: " << provider.name();
103
104 m_provider = provider;
105
106 Attica::ListJob<Attica::Category>* job = m_provider.requestCategories();
107 connect(job, SIGNAL(finished(Attica::BaseJob*)), SLOT(listOfCategoriesLoaded(Attica::BaseJob*)));
108 job->start();
109}
110
111void AtticaProvider::listOfCategoriesLoaded(Attica::BaseJob* listJob)
112{
113 if (!jobSuccess(listJob)) return;
114
115 kDebug() << "loading categories: " << mCategoryMap.keys();
116
117 Attica::ListJob<Attica::Category>* job = static_cast<Attica::ListJob<Attica::Category>*>(listJob);
118 Category::List categoryList = job->itemList();
119
120 foreach(const Category& category, categoryList) {
121 if (mCategoryMap.contains(category.name())) {
122 kDebug() << "Adding category: " << category.name();
123 mCategoryMap[category.name()] = category;
124 }
125 }
126 mInitialized = true;
127 emit providerInitialized(this);
128}
129
130bool AtticaProvider::isInitialized() const
131{
132 return mInitialized;
133}
134
135void AtticaProvider::loadEntries(const KNS3::Provider::SearchRequest& request)
136{
137 if (mEntryJob) {
138 mEntryJob->abort();
139 mEntryJob = 0;
140 }
141
142 mCurrentRequest = request;
143 if (request.sortMode == Installed) {
144 if (request.page == 0) {
145 emit loadingFinished(request, installedEntries());
146 } else {
147 emit loadingFinished(request, EntryInternal::List());
148 }
149 return;
150 }
151
152 if (request.sortMode == Updates) {
153 checkForUpdates();
154 return;
155 }
156
157 Attica::Provider::SortMode sorting = atticaSortMode(request.sortMode);
158 Attica::Category::List categoriesToSearch;
159
160 if (request.categories.isEmpty()) {
161 // search in all categories
162 categoriesToSearch = mCategoryMap.values();
163 } else {
164 foreach (const QString& categoryName, request.categories) {
165 categoriesToSearch.append(mCategoryMap.value(categoryName));
166 }
167 }
168
169 ListJob<Content>* job = m_provider.searchContents(categoriesToSearch, request.searchTerm, sorting, request.page, request.pageSize);
170 connect(job, SIGNAL(finished(Attica::BaseJob*)), SLOT(categoryContentsLoaded(Attica::BaseJob*)));
171
172 mEntryJob = job;
173 job->start();
174}
175
176void AtticaProvider::checkForUpdates()
177{
178 foreach (const EntryInternal& e, mCachedEntries) {
179 ItemJob<Content>* job = m_provider.requestContent(e.uniqueId());
180 connect(job, SIGNAL(finished(Attica::BaseJob*)), this, SLOT(detailsLoaded(Attica::BaseJob*)));
181 m_updateJobs.insert(job);
182 job->start();
183 kDebug() << "Checking for update: " << e.name();
184 }
185}
186
187void AtticaProvider::loadEntryDetails(const KNS3::EntryInternal& entry)
188{
189 ItemJob<Content>* job = m_provider.requestContent(entry.uniqueId());
190 connect(job, SIGNAL(finished(Attica::BaseJob*)), this, SLOT(detailsLoaded(Attica::BaseJob*)));
191 job->start();
192}
193
194void AtticaProvider::detailsLoaded(BaseJob* job)
195{
196 if (jobSuccess(job)) {
197 ItemJob<Content>* contentJob = static_cast<ItemJob<Content>*>(job);
198 Content content = contentJob->result();
199 EntryInternal entry = entryFromAtticaContent(content);
200 emit entryDetailsLoaded(entry);
201 kDebug() << "check update finished: " << entry.name();
202 }
203
204 if (m_updateJobs.remove(job) && m_updateJobs.isEmpty()) {
205 kDebug() << "check update finished.";
206 QList<EntryInternal> updatable;
207 foreach(const EntryInternal& entry, mCachedEntries) {
208 if (entry.status() == Entry::Updateable) {
209 updatable.append(entry);
210 }
211 }
212 emit loadingFinished(mCurrentRequest, updatable);
213 }
214}
215
216void AtticaProvider::categoryContentsLoaded(BaseJob* job)
217{
218 if (!jobSuccess(job)) return;
219
220 ListJob<Content>* listJob = static_cast<ListJob<Content>*>(job);
221 Content::List contents = listJob->itemList();
222
223 EntryInternal::List entries;
224 Q_FOREACH(const Content &content, contents) {
225 mCachedContent.insert(content.id(), content);
226 entries.append(entryFromAtticaContent(content));
227 }
228
229 kDebug() << "loaded: " << mCurrentRequest.hashForRequest() << " count: " << entries.size();
230 emit loadingFinished(mCurrentRequest, entries);
231 mEntryJob = 0;
232}
233
234Attica::Provider::SortMode AtticaProvider::atticaSortMode(const SortMode& sortMode)
235{
236 if (sortMode == Newest) {
237 return Attica::Provider::Newest;
238 }
239 if (sortMode == Alphabetical) {
240 return Attica::Provider::Alphabetical;
241 }
242 if (sortMode == Downloads) {
243 return Attica::Provider::Downloads;
244 }
245 return Attica::Provider::Rating;
246}
247
248void AtticaProvider::loadPayloadLink(const KNS3::EntryInternal& entry, int linkId)
249{
250 Attica::Content content = mCachedContent.value(entry.uniqueId());
251 DownloadDescription desc = content.downloadUrlDescription(linkId);
252
253 if (desc.hasPrice()) {
254 // Ask for balance, then show information...
255 ItemJob<AccountBalance>* job = m_provider.requestAccountBalance();
256 connect(job, SIGNAL(finished(Attica::BaseJob*)), SLOT(accountBalanceLoaded(Attica::BaseJob*)));
257 mDownloadLinkJobs[job] = qMakePair(entry, linkId);
258 job->start();
259
260 kDebug() << "get account balance";
261 } else {
262 ItemJob<DownloadItem>* job = m_provider.downloadLink(entry.uniqueId(), QString::number(linkId));
263 connect(job, SIGNAL(finished(Attica::BaseJob*)), SLOT(downloadItemLoaded(Attica::BaseJob*)));
264 mDownloadLinkJobs[job] = qMakePair(entry, linkId);
265 job->start();
266
267 kDebug() << " link for " << entry.uniqueId();
268 }
269}
270
271void AtticaProvider::accountBalanceLoaded(Attica::BaseJob* baseJob)
272{
273 if (!jobSuccess(baseJob)) return;
274
275 ItemJob<AccountBalance>* job = static_cast<ItemJob<AccountBalance>*>(baseJob);
276 AccountBalance item = job->result();
277
278 QPair<EntryInternal, int> pair = mDownloadLinkJobs.take(job);
279 EntryInternal entry(pair.first);
280 Content content = mCachedContent.value(entry.uniqueId());
281 if (content.downloadUrlDescription(pair.second).priceAmount() < item.balance()) {
282 kDebug() << "Your balance is greather than the price."
283 << content.downloadUrlDescription(pair.second).priceAmount() << " balance: " << item.balance();
284 if (KMessageBox::questionYesNo(0,
285 i18nc("the price of a download item, parameter 1 is the currency, 2 is the price",
286 "This item costs %1 %2.\nDo you want to buy it?",
287 item.currency(), content.downloadUrlDescription(pair.second).priceAmount()
288 )) == KMessageBox::Yes) {
289 ItemJob<DownloadItem>* job = m_provider.downloadLink(entry.uniqueId(), QString::number(pair.second));
290 connect(job, SIGNAL(finished(Attica::BaseJob*)), SLOT(downloadItemLoaded(Attica::BaseJob*)));
291 connect(job, SIGNAL(jobStarted(QNetworkReply*)), SLOT(atticaJobStarted(QNetworkReply*)));
292 mDownloadLinkJobs[job] = qMakePair(entry, pair.second);
293 job->start();
294 } else {
295 return;
296 }
297 } else {
298 kDebug() << "You don't have enough money on your account!"
299 << content.downloadUrlDescription(0).priceAmount() << " balance: " << item.balance();
300 KMessageBox::information(0, i18n("Your account balance is too low:\nYour balance: %1\nPrice: %2",
301 item.balance(),content.downloadUrlDescription(0).priceAmount()));
302 }
303}
304
305void AtticaProvider::downloadItemLoaded(BaseJob* baseJob)
306{
307 if (!jobSuccess(baseJob)) return;
308
309 ItemJob<DownloadItem>* job = static_cast<ItemJob<DownloadItem>*>(baseJob);
310 DownloadItem item = job->result();
311
312 EntryInternal entry = mDownloadLinkJobs.take(job).first;
313 entry.setPayload(QString(item.url().toString()));
314 emit payloadLinkLoaded(entry);
315}
316
317EntryInternal::List AtticaProvider::installedEntries() const
318{
319 EntryInternal::List entries;
320 foreach (const EntryInternal& entry, mCachedEntries) {
321 if (entry.status() == Entry::Installed || entry.status() == Entry::Updateable) {
322 entries.append(entry);
323 }
324 }
325 return entries;
326}
327
328void AtticaProvider::vote(const EntryInternal& entry, uint rating)
329{
330 PostJob * job = m_provider.voteForContent(entry.uniqueId(), rating);
331 connect(job, SIGNAL(finished(Attica::BaseJob*)), this, SLOT(votingFinished(Attica::BaseJob*)));
332 connect(job, SIGNAL(jobStarted(QNetworkReply*)), SLOT(atticaJobStarted(QNetworkReply*)));
333 job->start();
334}
335
336void AtticaProvider::votingFinished(Attica::BaseJob* job)
337{
338 if (!jobSuccess(job)) return;
339 emit signalInformation(i18nc("voting for an item (good/bad)", "Your vote was recorded."));
340}
341
342void AtticaProvider::becomeFan(const EntryInternal& entry)
343{
344 PostJob * job = m_provider.becomeFan(entry.uniqueId());
345 connect(job, SIGNAL(finished(Attica::BaseJob*)), this, SLOT(becomeFanFinished(Attica::BaseJob*)));
346 connect(job, SIGNAL(jobStarted(QNetworkReply*)), SLOT(atticaJobStarted(QNetworkReply*)));
347 job->start();
348}
349
350void AtticaProvider::becomeFanFinished(Attica::BaseJob* job)
351{
352 if (!jobSuccess(job)) return;
353 emit signalInformation(i18n("You are now a fan."));
354}
355
356bool AtticaProvider::jobSuccess(Attica::BaseJob* job) const
357{
358 if (job->metadata().error() == Attica::Metadata::NoError) {
359 return true;
360 }
361 kDebug() << "job error: " << job->metadata().error() << " status code: " << job->metadata().statusCode() << job->metadata().message();
362
363 if (job->metadata().error() == Attica::Metadata::NetworkError) {
364 emit signalError(i18n("Network error. (%1)", job->metadata().statusCode()));
365 }
366 if (job->metadata().error() == Attica::Metadata::OcsError) {
367 if (job->metadata().statusCode() == 200) {
368 emit signalError(i18n("Too many requests to server. Please try again in a few minutes."));
369 } else {
370 emit signalError(i18n("Unknown Open Collaboration Service API error. (%1)", job->metadata().statusCode()));
371 }
372 }
373 return false;
374}
375
376EntryInternal AtticaProvider::entryFromAtticaContent(const Attica::Content& content)
377{
378 EntryInternal entry;
379
380 entry.setProviderId(id());
381 entry.setUniqueId(content.id());
382 entry.setStatus(KNS3::Entry::Downloadable);
383 entry.setVersion(content.version());
384 entry.setReleaseDate(content.updated().date());
385
386 int index = mCachedEntries.indexOf(entry);
387 if (index >= 0) {
388 EntryInternal &cacheEntry = mCachedEntries[index];
389 // check if updateable
390 if (((cacheEntry.status() == Entry::Installed) || (cacheEntry.status() == Entry::Updateable)) &&
391 ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) {
392 cacheEntry.setStatus(Entry::Updateable);
393 cacheEntry.setUpdateVersion(entry.version());
394 cacheEntry.setUpdateReleaseDate(entry.releaseDate());
395 }
396 entry = cacheEntry;
397 } else {
398 mCachedEntries.append(entry);
399 }
400
401 entry.setName(content.name());
402 entry.setHomepage(content.detailpage());
403 entry.setRating(content.rating());
404 entry.setDownloadCount(content.downloads());
405 entry.setNumberFans(content.attribute("fans").toInt());
406 entry.setDonationLink(content.attribute("donationpage"));
407 entry.setKnowledgebaseLink(content.attribute("knowledgebasepage"));
408 entry.setNumberKnowledgebaseEntries(content.attribute("knowledgebaseentries").toInt());
409
410 entry.setPreviewUrl(content.smallPreviewPicture("1"), EntryInternal::PreviewSmall1);
411 entry.setPreviewUrl(content.smallPreviewPicture("2"), EntryInternal::PreviewSmall2);
412 entry.setPreviewUrl(content.smallPreviewPicture("3"), EntryInternal::PreviewSmall3);
413
414 entry.setPreviewUrl(content.previewPicture("1"), EntryInternal::PreviewBig1);
415 entry.setPreviewUrl(content.previewPicture("2"), EntryInternal::PreviewBig2);
416 entry.setPreviewUrl(content.previewPicture("3"), EntryInternal::PreviewBig3);
417
418 entry.setLicense(content.license());
419 Author author;
420 author.setName(content.author());
421 author.setHomepage(content.attribute("profilepage"));
422 entry.setAuthor(author);
423
424 entry.setSource(KNS3::EntryInternal::Online);
425 entry.setSummary(content.description());
426 entry.setChangelog(content.changelog());
427
428 entry.clearDownloadLinkInformation();
429 QList<Attica::DownloadDescription> descs = content.downloadUrlDescriptions();
430 foreach (Attica::DownloadDescription desc, descs) {
431 EntryInternal::DownloadLinkInformation info;
432 info.name = desc.name();
433 info.priceAmount = desc.priceAmount();
434 info.distributionType = desc.distributionType();
435 info.descriptionLink = desc.link();
436 info.id = desc.id();
437 info.isDownloadtypeLink = desc.isDownloadtypLink();
438 entry.appendDownloadLinkInformation(info);
439 }
440
441 return entry;
442}
443
444} // namespace
445
446
447#include "atticaprovider.moc"
atticaprovider.h
KMessageBox::Yes
Yes
KMessageBox::information
static void information(QWidget *parent, const QString &text, const QString &caption=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
KMessageBox::questionYesNo
static int questionYesNo(QWidget *parent, const QString &text, const QString &caption=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const QString &dontAskAgainName=QString(), Options options=Notify)
KNS3::AtticaProvider::id
virtual QString id() const
A unique Id for this provider (the url in most cases)
Definition: atticaprovider.cpp:62
KNS3::AtticaProvider::loadPayloadLink
virtual void loadPayloadLink(const EntryInternal &entry, int linkId)
Definition: atticaprovider.cpp:248
KNS3::AtticaProvider::becomeFan
virtual void becomeFan(const EntryInternal &entry)
Definition: atticaprovider.cpp:342
KNS3::AtticaProvider::AtticaProvider
AtticaProvider(const QStringList &categories)
Definition: atticaprovider.cpp:38
KNS3::AtticaProvider::setCachedEntries
virtual void setCachedEntries(const KNS3::EntryInternal::List &cachedEntries)
Definition: atticaprovider.cpp:94
KNS3::AtticaProvider::setProviderXML
virtual bool setProviderXML(const QDomElement &xmldata)
set the provider data xml, to initialize the provider
Definition: atticaprovider.cpp:73
KNS3::AtticaProvider::loadEntries
virtual void loadEntries(const KNS3::Provider::SearchRequest &request)
load the given search and return given page
Definition: atticaprovider.cpp:135
KNS3::AtticaProvider::isInitialized
virtual bool isInitialized() const
Definition: atticaprovider.cpp:130
KNS3::AtticaProvider::vote
virtual void vote(const EntryInternal &entry, uint rating)
Definition: atticaprovider.cpp:328
KNS3::AtticaProvider::loadEntryDetails
virtual void loadEntryDetails(const KNS3::EntryInternal &entry)
Definition: atticaprovider.cpp:187
KNS3::EntryInternal
KNewStuff data entry container.
Definition: entryinternal.h:55
KNS3::EntryInternal::name
QString name() const
Retrieve the name of the data object.
Definition: entryinternal.cpp:124
KNS3::EntryInternal::Online
@ Online
Definition: entryinternal.h:66
KNS3::EntryInternal::List
QList< EntryInternal > List
Definition: entryinternal.h:57
KNS3::EntryInternal::PreviewBig1
@ PreviewBig1
Definition: entryinternal.h:74
KNS3::EntryInternal::PreviewSmall1
@ PreviewSmall1
Definition: entryinternal.h:71
KNS3::EntryInternal::PreviewSmall3
@ PreviewSmall3
Definition: entryinternal.h:73
KNS3::EntryInternal::PreviewSmall2
@ PreviewSmall2
Definition: entryinternal.h:72
KNS3::EntryInternal::PreviewBig3
@ PreviewBig3
Definition: entryinternal.h:76
KNS3::EntryInternal::PreviewBig2
@ PreviewBig2
Definition: entryinternal.h:75
KNS3::EntryInternal::uniqueId
QString uniqueId() const
Definition: entryinternal.cpp:134
KNS3::Entry::Installed
@ Installed
Definition: knewstuff3/entry.h:61
KNS3::Entry::Updateable
@ Updateable
Definition: knewstuff3/entry.h:62
KNS3::Entry::Downloadable
@ Downloadable
Definition: knewstuff3/entry.h:60
KNS3::Provider
KNewStuff Base Provider class.
Definition: knewstuff3/core/provider.h:47
KNS3::Provider::signalInformation
void signalInformation(const QString &) const
KNS3::Provider::payloadLinkLoaded
void payloadLinkLoaded(const KNS3::EntryInternal &)
KNS3::Provider::signalError
void signalError(const QString &) const
KNS3::Provider::loadingFinished
void loadingFinished(const KNS3::Provider::SearchRequest &, const KNS3::EntryInternal::List &) const
KNS3::Provider::Updates
@ Updates
Definition: knewstuff3/core/provider.h:58
KNS3::Provider::Alphabetical
@ Alphabetical
Definition: knewstuff3/core/provider.h:54
KNS3::Provider::Installed
@ Installed
Definition: knewstuff3/core/provider.h:57
KNS3::Provider::Downloads
@ Downloads
Definition: knewstuff3/core/provider.h:56
KNS3::Provider::Newest
@ Newest
Definition: knewstuff3/core/provider.h:53
KNS3::Provider::mName
QString mName
Definition: knewstuff3/core/provider.h:148
KNS3::Provider::providerInitialized
void providerInitialized(KNS3::Provider *)
KNS3::Provider::entryDetailsLoaded
void entryDetailsLoaded(const KNS3::EntryInternal &)
QList< EntryInternal >
QNetworkReply
QPair
kDebug
#define kDebug
job.h
kdebug.h
klocale.h
i18n
QString i18n(const char *text)
i18nc
QString i18nc(const char *ctxt, const char *text)
kmessagebox.h
Attica
Definition: atticaprovider.h:28
KNS3
Definition: atticaprovider.cpp:36
KNS3::Provider::SearchRequest
used to keep track of a search
Definition: knewstuff3/core/provider.h:64
KNS3::Provider::SearchRequest::searchTerm
QString searchTerm
Definition: knewstuff3/core/provider.h:66
KNS3::Provider::SearchRequest::page
int page
Definition: knewstuff3/core/provider.h:68
KNS3::Provider::SearchRequest::pageSize
int pageSize
Definition: knewstuff3/core/provider.h:69
KNS3::Provider::SearchRequest::sortMode
SortMode sortMode
Definition: knewstuff3/core/provider.h:65
KNS3::Provider::SearchRequest::categories
QStringList categories
Definition: knewstuff3/core/provider.h:67
KNS3::Provider::SearchRequest::hashForRequest
QString hashForRequest() const
Definition: knewstuff3/core/provider.cpp:33
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 20 2023 00:00:00 by doxygen 1.9.6 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KNewStuff

Skip menu "KNewStuff"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs-4.14.38 API Reference

Skip menu "kdelibs-4.14.38 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal