• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.14.10 API Reference
  • KDE Home
  • Contact Us
 

akonadi

  • akonadi
  • notes
noteutils.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2011 Christian Mollekopf <chrigi_1@fastmail.fm>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 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 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18*/
19
20#include "noteutils.h"
21
22#include <klocalizedstring.h>
23#include <kdatetime.h>
24#include <kmime/kmime_message.h>
25#include <kdebug.h>
26
27#include <qstring.h>
28#include <quuid.h>
29#include <qdom.h>
30#include <QTextDocument>
31
32namespace Akonadi {
33namespace NoteUtils {
34
35#define X_NOTES_UID_HEADER "X-Akonotes-UID"
36#define X_NOTES_LASTMODIFIED_HEADER "X-Akonotes-LastModified"
37#define X_NOTES_CLASSIFICATION_HEADER "X-Akonotes-Classification"
38#define X_NOTES_CUSTOM_HEADER "X-Akonotes-Custom"
39
40#define CLASSIFICATION_PUBLIC "Public"
41#define CLASSIFICATION_PRIVATE "Private"
42#define CLASSIFICATION_CONFIDENTIAL "Confidential"
43
44#define X_NOTES_URL_HEADER "X-Akonotes-Url"
45#define X_NOTES_LABEL_HEADER "X-Akonotes-Label"
46#define X_NOTES_CONTENTTYPE_HEADER "X-Akonotes-Type"
47#define CONTENT_TYPE_CUSTOM "custom"
48#define CONTENT_TYPE_ATTACHMENT "attachment"
49
50#define ENCODING "utf-8"
51
52class Attachment::AttachmentPrivate
53{
54 public:
55 AttachmentPrivate( const QUrl& url, const QString& mimetype )
56 : mUrl( url ),
57 mMimetype( mimetype )
58 {}
59
60 AttachmentPrivate( const QByteArray& data, const QString& mimetype )
61 : mData( data ),
62 mMimetype( mimetype )
63 {}
64
65 AttachmentPrivate( const AttachmentPrivate &other )
66 {
67 *this = other;
68 }
69
70 QUrl mUrl;
71 QByteArray mData;
72 QString mMimetype;
73 QString mLabel;
74};
75
76Attachment::Attachment( const QUrl& url, const QString& mimetype )
77: d_ptr( new Attachment::AttachmentPrivate( url, mimetype ) )
78{
79}
80
81Attachment::Attachment( const QByteArray& data, const QString& mimetype )
82: d_ptr( new Attachment::AttachmentPrivate( data, mimetype ) )
83{
84}
85
86Attachment::Attachment( const Attachment &other )
87: d_ptr(new AttachmentPrivate(*other.d_func()) )
88{
89
90}
91
92Attachment::~Attachment()
93{
94 delete d_ptr;
95}
96
97bool Attachment::operator==( const Attachment &a ) const
98{
99 const Q_D( Attachment );
100 if ( d->mUrl.isEmpty() ) {
101 return d->mUrl == a.d_func()->mUrl &&
102 d->mMimetype == a.d_func()->mMimetype &&
103 d->mLabel == a.d_func()->mLabel;
104 }
105 return d->mData == a.d_func()->mData &&
106 d->mMimetype == a.d_func()->mMimetype &&
107 d->mLabel == a.d_func()->mLabel;
108}
109
110void Attachment::operator=( const Attachment &a )
111{
112 *d_ptr = *a.d_ptr;
113}
114
115QUrl Attachment::url() const
116{
117 const Q_D( Attachment );
118 return d->mUrl;
119}
120
121QByteArray Attachment::data() const
122{
123 const Q_D( Attachment );
124 return d->mData;
125}
126
127QString Attachment::mimetype() const
128{
129 const Q_D( Attachment );
130 return d->mMimetype;
131}
132
133void Attachment::setLabel( const QString& label )
134{
135 Q_D( Attachment );
136 d->mLabel = label;
137}
138
139QString Attachment::label() const
140{
141 const Q_D( Attachment );
142 return d->mLabel;
143}
144
145class NoteMessageWrapper::NoteMessageWrapperPrivate
146{
147 public:
148 NoteMessageWrapperPrivate()
149 : classification( Public )
150 {
151 }
152
153 NoteMessageWrapperPrivate( const KMime::Message::Ptr &msg )
154 : classification( Public ),
155 textFormat( Qt::PlainText )
156 {
157 readMimeMessage(msg);
158 }
159
160 void readMimeMessage(const KMime::Message::Ptr &msg );
161
162 KMime::Content* createCustomPart() const;
163 void parseCustomPart( KMime::Content * );
164
165 KMime::Content* createAttachmentPart( const Attachment & ) const;
166 void parseAttachmentPart( KMime::Content * );
167
168 QString uid;
169 QString title;
170 QString text;
171 QString from;
172 KDateTime creationDate;
173 KDateTime lastModifiedDate;
174 QMap< QString, QString > custom;
175 QList<Attachment> attachments;
176 Classification classification;
177 Qt::TextFormat textFormat;
178};
179
180void NoteMessageWrapper::NoteMessageWrapperPrivate::readMimeMessage(const KMime::Message::Ptr& msg)
181{
182 if (!msg.get()) {
183 kWarning() << "Empty message";
184 return;
185 }
186 title = msg->subject( true )->asUnicodeString();
187 text = msg->mainBodyPart()->decodedText( true ); //remove trailing whitespace, so we get rid of " " in empty notes
188 if ( msg->from( false ) )
189 from = msg->from( false )->asUnicodeString();
190 creationDate = msg->date( true )->dateTime();
191 if ( msg->mainBodyPart()->contentType( false ) && msg->mainBodyPart()->contentType()->mimeType() == "text/html" ) {
192 textFormat = Qt::RichText;
193 }
194
195 if (KMime::Headers::Base *lastmod = msg->headerByType(X_NOTES_LASTMODIFIED_HEADER)) {
196 const QByteArray &s = lastmod->asUnicodeString().toLatin1();
197 const char *cursor = s.constData();
198 if (!KMime::HeaderParsing::parseDateTime( cursor, cursor + s.length(), lastModifiedDate)) {
199 kWarning() << "failed to parse lastModifiedDate";
200 }
201 }
202
203 if (KMime::Headers::Base *uidHeader = msg->headerByType(X_NOTES_UID_HEADER)) {
204 uid = uidHeader->asUnicodeString();
205 }
206
207 if (KMime::Headers::Base *classificationHeader = msg->headerByType(X_NOTES_CLASSIFICATION_HEADER)) {
208 const QString &c = classificationHeader->asUnicodeString();
209 if ( c == CLASSIFICATION_PRIVATE ) {
210 classification = Private;
211 } else if ( c == CLASSIFICATION_CONFIDENTIAL ) {
212 classification = Confidential;
213 }
214 }
215
216 const KMime::Content::List list = msg->contents();
217 Q_FOREACH(KMime::Content *c, msg->contents()) {
218 if (KMime::Headers::Base *typeHeader = c->headerByType(X_NOTES_CONTENTTYPE_HEADER)) {
219 const QString &type = typeHeader->asUnicodeString();
220 if ( type == CONTENT_TYPE_CUSTOM ) {
221 parseCustomPart(c);
222 } else if ( type == CONTENT_TYPE_ATTACHMENT ) {
223 parseAttachmentPart(c);
224 } else {
225 qWarning() << "unknown type " << type;
226 }
227 }
228 }
229}
230
231QDomDocument createXMLDocument()
232{
233 QDomDocument document;
234 QString p = "version=\"1.0\" encoding=\"UTF-8\"";
235 document.appendChild(document.createProcessingInstruction( "xml", p ) );
236 return document;
237}
238
239QDomDocument loadDocument(KMime::Content *part)
240{
241 QString errorMsg;
242 int errorLine, errorColumn;
243 QDomDocument document;
244 bool ok = document.setContent( part->body(), &errorMsg, &errorLine, &errorColumn );
245 if ( !ok ) {
246 kWarning() << part->body();
247 qWarning( "Error loading document: %s, line %d, column %d", qPrintable( errorMsg ), errorLine, errorColumn );
248 return QDomDocument();
249 }
250 return document;
251}
252
253KMime::Content* NoteMessageWrapper::NoteMessageWrapperPrivate::createCustomPart() const
254{
255 KMime::Content* content = new KMime::Content();
256 content->appendHeader( new KMime::Headers::Generic( X_NOTES_CONTENTTYPE_HEADER, content, CONTENT_TYPE_CUSTOM, ENCODING ) );
257 QDomDocument document = createXMLDocument();
258 QDomElement element = document.createElement( "custom" );
259 element.setAttribute( "version", "1.0" );
260 for ( QMap <QString, QString >::const_iterator it = custom.begin(); it != custom.end(); ++it ) {
261 QDomElement e = element.ownerDocument().createElement( it.key() );
262 QDomText t = element.ownerDocument().createTextNode( it.value() );
263 e.appendChild( t );
264 element.appendChild( e );
265 document.appendChild( element );
266 }
267 content->setBody( document.toString().toLatin1() );
268 return content;
269}
270
271void NoteMessageWrapper::NoteMessageWrapperPrivate::parseCustomPart( KMime::Content* part )
272{
273 QDomDocument document = loadDocument( part );
274 if (document.isNull()) {
275 return;
276 }
277 QDomElement top = document.documentElement();
278 if ( top.tagName() != "custom" ) {
279 qWarning( "XML error: Top tag was %s instead of the expected custom",
280 top.tagName().toLatin1().data() );
281 return;
282 }
283
284 for ( QDomNode n = top.firstChild(); !n.isNull(); n = n.nextSibling() ) {
285 if ( n.isElement() ) {
286 QDomElement e = n.toElement();
287 custom.insert(e.tagName(), e.text());
288 } else {
289 kDebug() <<"Node is not an element";
290 Q_ASSERT(false);
291 }
292 }
293}
294
295KMime::Content* NoteMessageWrapper::NoteMessageWrapperPrivate::createAttachmentPart( const Attachment &a ) const
296{
297 KMime::Content* content = new KMime::Content();
298 content->appendHeader( new KMime::Headers::Generic( X_NOTES_CONTENTTYPE_HEADER, content, CONTENT_TYPE_ATTACHMENT, ENCODING ) );
299 if (a.url().isValid()) {
300 content->appendHeader( new KMime::Headers::Generic( X_NOTES_URL_HEADER, content, a.url().toString().toLatin1(), ENCODING ) );
301 } else {
302 content->setBody( a.data() );
303 }
304 content->contentType()->setMimeType( a.mimetype().toLatin1() );
305 if (!a.label().isEmpty()) {
306 content->appendHeader( new KMime::Headers::Generic( X_NOTES_LABEL_HEADER, content, a.label().toLatin1(), ENCODING ) );
307 }
308 content->contentTransferEncoding()->setEncoding( KMime::Headers::CEbase64 );
309 content->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
310 content->contentDisposition()->setFilename( "attachment" );
311 return content;
312}
313
314void NoteMessageWrapper::NoteMessageWrapperPrivate::parseAttachmentPart( KMime::Content *part )
315{
316 QString label;
317 if ( KMime::Headers::Base *labelHeader = part->headerByType( X_NOTES_LABEL_HEADER ) ) {
318 label = labelHeader->asUnicodeString();
319 }
320 if ( KMime::Headers::Base *header = part->headerByType( X_NOTES_URL_HEADER ) ) {
321 Attachment attachment( QUrl( header->asUnicodeString() ), part->contentType()->mimeType() );
322 attachment.setLabel( label );
323 attachments.append(attachment);
324 } else {
325 Attachment attachment( part->decodedContent(), part->contentType()->mimeType() );
326 attachment.setLabel( label );
327 attachments.append(attachment);
328 }
329}
330
331NoteMessageWrapper::NoteMessageWrapper()
332: d_ptr( new NoteMessageWrapperPrivate() )
333{
334}
335
336NoteMessageWrapper::NoteMessageWrapper( const KMime::Message::Ptr &msg )
337: d_ptr( new NoteMessageWrapperPrivate(msg) )
338{
339}
340
341NoteMessageWrapper::~NoteMessageWrapper()
342{
343 delete d_ptr;
344}
345
346KMime::Message::Ptr NoteMessageWrapper::message() const
347{
348 const Q_D( NoteMessageWrapper );
349 KMime::Message::Ptr msg = KMime::Message::Ptr( new KMime::Message() );
350
351 QString title = i18nc( "The default name for new notes.", "New Note" );
352 if ( !d->title.isEmpty() ) {
353 title = d->title;
354 }
355 // Need a non-empty body part so that the serializer regards this as a valid message.
356 QString text = QLatin1String(" ");
357 if ( !d->text.isEmpty() ) {
358 text = d->text;
359 }
360
361 KDateTime creationDate = KDateTime::currentLocalDateTime();
362 if ( d->creationDate.isValid() ) {
363 creationDate = d->creationDate;
364 }
365
366 KDateTime lastModifiedDate = KDateTime::currentLocalDateTime();
367 if ( d->lastModifiedDate.isValid() ) {
368 lastModifiedDate = d->lastModifiedDate;
369 }
370
371 QString uid;
372 if ( !d->uid.isEmpty() ) {
373 uid = d->uid;
374 } else {
375 uid = QUuid::createUuid();
376 }
377
378 msg->subject( true )->fromUnicodeString( title, ENCODING );
379 msg->date( true )->setDateTime( creationDate );
380 msg->from( true )->fromUnicodeString( d->from, ENCODING );
381 msg->mainBodyPart()->fromUnicodeString( text );
382 msg->mainBodyPart()->contentType( true )->setMimeType( d->textFormat == Qt::RichText ? "text/html" : "text/plain" );
383 msg->appendHeader( new KMime::Headers::Generic(X_NOTES_LASTMODIFIED_HEADER, msg.get(), lastModifiedDate.toString( KDateTime::RFCDateDay ).toLatin1(), ENCODING ) );
384 msg->appendHeader( new KMime::Headers::Generic( X_NOTES_UID_HEADER, msg.get(), uid, ENCODING ) );
385
386 QString classification = QString::fromLatin1(CLASSIFICATION_PUBLIC);
387 switch ( d->classification ) {
388 case Private:
389 classification = QString::fromLatin1(CLASSIFICATION_PRIVATE);
390 break;
391 case Confidential:
392 classification = QString::fromLatin1(CLASSIFICATION_CONFIDENTIAL);
393 break;
394 default:
395 //do nothing
396 break;
397 }
398 msg->appendHeader( new KMime::Headers::Generic( X_NOTES_CLASSIFICATION_HEADER, msg.get(), classification, ENCODING ) );
399
400 foreach (const Attachment &a, d->attachments) {
401 msg->addContent( d->createAttachmentPart(a) );
402 }
403
404 if ( !d->custom.isEmpty() ) {
405 msg->addContent( d->createCustomPart() );
406 }
407
408 msg->assemble();
409 return msg;
410}
411
412void NoteMessageWrapper::setUid( const QString &uid )
413{
414 Q_D( NoteMessageWrapper );
415 d->uid = uid;
416}
417
418QString NoteMessageWrapper::uid() const
419{
420 const Q_D( NoteMessageWrapper );
421 return d->uid;
422}
423
424void NoteMessageWrapper::setClassification( NoteMessageWrapper::Classification classification )
425{
426 Q_D( NoteMessageWrapper );
427 d->classification = classification;
428}
429
430NoteMessageWrapper::Classification NoteMessageWrapper::classification() const
431{
432 const Q_D( NoteMessageWrapper );
433 return d->classification;
434}
435
436void NoteMessageWrapper::setLastModifiedDate( const KDateTime& lastModifiedDate )
437{
438 Q_D( NoteMessageWrapper );
439 d->lastModifiedDate = lastModifiedDate;
440}
441
442KDateTime NoteMessageWrapper::lastModifiedDate() const
443{
444 const Q_D( NoteMessageWrapper );
445 return d->lastModifiedDate;
446}
447
448void NoteMessageWrapper::setCreationDate( const KDateTime &creationDate )
449{
450 Q_D( NoteMessageWrapper );
451 d->creationDate = creationDate;
452}
453
454KDateTime NoteMessageWrapper::creationDate() const
455{
456 const Q_D( NoteMessageWrapper );
457 return d->creationDate;
458}
459
460void NoteMessageWrapper::setFrom( const QString &from )
461{
462 Q_D( NoteMessageWrapper );
463 d->from = from;
464}
465
466QString NoteMessageWrapper::from() const
467{
468 const Q_D( NoteMessageWrapper );
469 return d->from;
470}
471
472void NoteMessageWrapper::setTitle( const QString &title )
473{
474 Q_D( NoteMessageWrapper );
475 d->title = title;
476}
477
478QString NoteMessageWrapper::title() const
479{
480 const Q_D( NoteMessageWrapper );
481 return d->title;
482}
483
484void NoteMessageWrapper::setText( const QString &text, Qt::TextFormat format )
485{
486 Q_D( NoteMessageWrapper );
487 d->text = text;
488 d->textFormat = format;
489}
490
491QString NoteMessageWrapper::text() const
492{
493 const Q_D( NoteMessageWrapper );
494 return d->text;
495}
496
497Qt::TextFormat NoteMessageWrapper::textFormat() const
498{
499 const Q_D( NoteMessageWrapper );
500 return d->textFormat;
501}
502
503QString NoteMessageWrapper::toPlainText() const
504{
505 const Q_D( NoteMessageWrapper );
506 if ( d->textFormat == Qt::PlainText ) {
507 return d->text;
508 }
509
510 //From cleanHtml in kdepimlibs/kcalutils/incidenceformatter.cpp
511 QRegExp rx( QLatin1String("<body[^>]*>(.*)</body>"), Qt::CaseInsensitive );
512 rx.indexIn( d->text );
513 QString body = rx.cap( 1 );
514
515 return Qt::escape( body.remove( QRegExp( QLatin1String("<[^>]*>") ) ).trimmed() );
516}
517
518QList<Attachment> &NoteMessageWrapper::attachments()
519{
520 Q_D( NoteMessageWrapper );
521 return d->attachments;
522}
523
524QMap< QString, QString > &NoteMessageWrapper::custom()
525{
526 Q_D( NoteMessageWrapper );
527 return d->custom;
528}
529
530QString noteIconName()
531{
532 return QString::fromLatin1( "text-plain" );
533}
534
535QString noteMimeType()
536{
537 return QString::fromLatin1( "text/x-vnd.akonadi.note" );
538}
539
540} //End Namepsace
541} //End Namepsace
Akonadi::NoteUtils::Attachment
An attachment for a note.
Definition: noteutils.h:58
Akonadi::NoteUtils::Attachment::label
QString label() const
Returns the label of the attachment.
Definition: noteutils.cpp:139
Akonadi::NoteUtils::Attachment::data
QByteArray data() const
Returns the date for inline attachments.
Definition: noteutils.cpp:121
Akonadi::NoteUtils::Attachment::mimetype
QString mimetype() const
Returns the mimetype.
Definition: noteutils.cpp:127
Akonadi::NoteUtils::Attachment::Attachment
Attachment(const QUrl &url, const QString &mimetype)
Create an attachment referencing a url only.
Definition: noteutils.cpp:76
Akonadi::NoteUtils::Attachment::setLabel
void setLabel(const QString &label)
Sets the label to be presented to the user.
Definition: noteutils.cpp:133
Akonadi::NoteUtils::Attachment::url
QUrl url() const
Returns the url for url-only attachments.
Definition: noteutils.cpp:115
Akonadi::NoteUtils::NoteMessageWrapper
A convenience wrapper around KMime::Message::Ptr for notes.
Definition: noteutils.h:145
Akonadi::NoteUtils::NoteMessageWrapper::custom
QMap< QString, QString > & custom()
Returns a reference to the custom-value map.
Definition: noteutils.cpp:524
Akonadi::NoteUtils::NoteMessageWrapper::setText
void setText(const QString &text, Qt::TextFormat format=Qt::PlainText)
Set the text of the note.
Definition: noteutils.cpp:484
Akonadi::NoteUtils::NoteMessageWrapper::from
QString from() const
Returns the origin (creator) of the note.
Definition: noteutils.cpp:466
Akonadi::NoteUtils::NoteMessageWrapper::attachments
QList< Attachment > & attachments()
Returns a reference to the list of attachments of the note.
Definition: noteutils.cpp:518
Akonadi::NoteUtils::NoteMessageWrapper::setFrom
void setFrom(const QString &from)
Set the origin (creator) of the note (stored in the mime header) This is usually the application crea...
Definition: noteutils.cpp:460
Akonadi::NoteUtils::NoteMessageWrapper::textFormat
Qt::TextFormat textFormat() const
Definition: noteutils.cpp:497
Akonadi::NoteUtils::NoteMessageWrapper::text
QString text() const
Returns the text of the note.
Definition: noteutils.cpp:491
Akonadi::NoteUtils::NoteMessageWrapper::setUid
void setUid(const QString &uid)
Set the uid of the note.
Definition: noteutils.cpp:412
Akonadi::NoteUtils::NoteMessageWrapper::message
KMime::MessagePtr message() const
Assemble a KMime message with the given values.
Definition: noteutils.cpp:346
Akonadi::NoteUtils::NoteMessageWrapper::creationDate
KDateTime creationDate() const
Returns the creation date of the note.
Definition: noteutils.cpp:454
Akonadi::NoteUtils::NoteMessageWrapper::setClassification
void setClassification(Classification)
Set the classification of the note.
Definition: noteutils.cpp:424
Akonadi::NoteUtils::NoteMessageWrapper::setLastModifiedDate
void setLastModifiedDate(const KDateTime &lastModifiedDate)
Set the lastModified-date of the note.
Definition: noteutils.cpp:436
Akonadi::NoteUtils::NoteMessageWrapper::setCreationDate
void setCreationDate(const KDateTime &creationDate)
Set the creation date of the note (stored in the mime header)
Definition: noteutils.cpp:448
Akonadi::NoteUtils::NoteMessageWrapper::title
QString title() const
Returns the title of the note.
Definition: noteutils.cpp:478
Akonadi::NoteUtils::NoteMessageWrapper::classification
Classification classification() const
Returns the classification of the note.
Definition: noteutils.cpp:430
Akonadi::NoteUtils::NoteMessageWrapper::setTitle
void setTitle(const QString &title)
Set the title of the note.
Definition: noteutils.cpp:472
Akonadi::NoteUtils::NoteMessageWrapper::lastModifiedDate
KDateTime lastModifiedDate() const
Returns the lastModified-date of the note.
Definition: noteutils.cpp:442
Akonadi::NoteUtils::NoteMessageWrapper::toPlainText
QString toPlainText() const
Definition: noteutils.cpp:503
Akonadi::NoteUtils::NoteMessageWrapper::uid
QString uid() const
Returns the uid of the note.
Definition: noteutils.cpp:418
Akonadi
FreeBusyManager::Singleton.
Definition: actionstatemanager_p.h:28
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jul 21 2022 00:00:00 by doxygen 1.9.5 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

akonadi

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

kdepimlibs-4.14.10 API Reference

Skip menu "kdepimlibs-4.14.10 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
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