• Skip to content
  • Skip to link menu
KDE 4.7 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • Contact Us
 

KCalCore Library

icalformat.cpp
Go to the documentation of this file.
00001 /*
00002   This file is part of the kcalcore library.
00003 
00004   Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
00005 
00006   This library is free software; you can redistribute it and/or
00007   modify it under the terms of the GNU Library General Public
00008   License as published by the Free Software Foundation; either
00009   version 2 of the License, or (at your option) any later version.
00010 
00011   This library is distributed in the hope that it will be useful,
00012   but WITHOUT ANY WARRANTY; without even the implied warranty of
00013   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014   Library General Public License for more details.
00015 
00016   You should have received a copy of the GNU Library General Public License
00017   along with this library; see the file COPYING.LIB.  If not, write to
00018   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019   Boston, MA 02110-1301, USA.
00020 */
00031 #include "icalformat.h"
00032 #include "icalformat_p.h"
00033 #include "icaltimezones.h"
00034 #include "freebusy.h"
00035 #include "memorycalendar.h"
00036 
00037 #include <KDebug>
00038 #include <KSaveFile>
00039 
00040 #include <QtCore/QFile>
00041 
00042 extern "C" {
00043   #include <libical/ical.h>
00044   #include <libical/icalss.h>
00045   #include <libical/icalparser.h>
00046   #include <libical/icalrestriction.h>
00047   #include <libical/icalmemory.h>
00048 }
00049 
00050 using namespace KCalCore;
00051 
00052 //@cond PRIVATE
00053 class KCalCore::ICalFormat::Private
00054 {
00055   public:
00056     Private( ICalFormat *parent )
00057       : mImpl( new ICalFormatImpl( parent ) ),
00058         mTimeSpec( KDateTime::UTC )
00059     {}
00060     ~Private()  { delete mImpl; }
00061     ICalFormatImpl *mImpl;
00062     KDateTime::Spec mTimeSpec;
00063 };
00064 //@endcond
00065 
00066 ICalFormat::ICalFormat()
00067   : d( new Private( this ) )
00068 {
00069 }
00070 
00071 ICalFormat::~ICalFormat()
00072 {
00073   delete d;
00074 }
00075 
00076 bool ICalFormat::load( const Calendar::Ptr &calendar, const QString &fileName )
00077 {
00078   kDebug() << fileName;
00079 
00080   clearException();
00081 
00082   QFile file( fileName );
00083   if ( !file.open( QIODevice::ReadOnly ) ) {
00084     kDebug() << "load error";
00085     setException( new Exception( Exception::LoadError ) );
00086     return false;
00087   }
00088   QTextStream ts( &file );
00089   ts.setCodec( "UTF-8" );
00090   QByteArray text = ts.readAll().trimmed().toUtf8();
00091   file.close();
00092 
00093   if ( text.isEmpty() ) {
00094     // empty files are valid
00095     return true;
00096   } else {
00097     return fromRawString( calendar, text, false, fileName );
00098   }
00099 }
00100 
00101 bool ICalFormat::save( const Calendar::Ptr &calendar, const QString &fileName )
00102 {
00103   kDebug() << fileName;
00104 
00105   clearException();
00106 
00107   QString text = toString( calendar );
00108   if ( text.isEmpty() ) {
00109     return false;
00110   }
00111 
00112   // Write backup file
00113   KSaveFile::backupFile( fileName );
00114 
00115   KSaveFile file( fileName );
00116   if ( !file.open() ) {
00117     kDebug() << "file open error:" << file.errorString();
00118     setException( new Exception( Exception::SaveErrorOpenFile,
00119                                  QStringList( fileName ) ) );
00120 
00121     return false;
00122   }
00123 
00124   // Convert to UTF8 and save
00125   QByteArray textUtf8 = text.toUtf8();
00126   file.write( textUtf8.data(), textUtf8.size() );
00127 
00128   if ( !file.finalize() ) {
00129     kDebug() << "file finalize error:" << file.errorString();
00130     setException( new Exception( Exception::SaveErrorSaveFile,
00131                                  QStringList( fileName ) ) );
00132 
00133     return false;
00134   }
00135 
00136   return true;
00137 }
00138 
00139 bool ICalFormat::fromString( const Calendar::Ptr &cal, const QString &string,
00140                              bool deleted, const QString &notebook )
00141 {
00142   return fromRawString( cal, string.toUtf8(), deleted, notebook );
00143 }
00144 
00145 bool ICalFormat::fromRawString( const Calendar::Ptr &cal, const QByteArray &string,
00146                                 bool deleted, const QString &notebook )
00147 {
00148   Q_UNUSED( notebook );
00149   // Get first VCALENDAR component.
00150   // TODO: Handle more than one VCALENDAR or non-VCALENDAR top components
00151   icalcomponent *calendar;
00152 
00153   // Let's defend const correctness until the very gates of hell^Wlibical
00154   calendar = icalcomponent_new_from_string( const_cast<char*>( ( const char * )string ) );
00155   if ( !calendar ) {
00156     kDebug() << "parse error";
00157     setException( new Exception( Exception::ParseErrorIcal ) );
00158     return false;
00159   }
00160 
00161   bool success = true;
00162 
00163   if ( icalcomponent_isa( calendar ) == ICAL_XROOT_COMPONENT ) {
00164     icalcomponent *comp;
00165     for ( comp = icalcomponent_get_first_component( calendar, ICAL_VCALENDAR_COMPONENT );
00166           comp; comp = icalcomponent_get_next_component( calendar, ICAL_VCALENDAR_COMPONENT ) ) {
00167       // put all objects into their proper places
00168       if ( !d->mImpl->populate( cal, comp, deleted ) ) {
00169         kDebug() << "Could not populate calendar";
00170         if ( !exception() ) {
00171           setException( new Exception( Exception::ParseErrorKcal ) );
00172         }
00173         success = false;
00174       } else {
00175         setLoadedProductId( d->mImpl->loadedProductId() );
00176       }
00177     }
00178   } else if ( icalcomponent_isa( calendar ) != ICAL_VCALENDAR_COMPONENT ) {
00179     kDebug() << "No VCALENDAR component found";
00180     setException( new Exception( Exception::NoCalendar ) );
00181     success = false;
00182   } else {
00183     // put all objects into their proper places
00184     if ( !d->mImpl->populate( cal, calendar, deleted ) ) {
00185       kDebug() << "Could not populate calendar";
00186       if ( !exception() ) {
00187         setException( new Exception( Exception::ParseErrorKcal ) );
00188       }
00189       success = false;
00190     } else {
00191       setLoadedProductId( d->mImpl->loadedProductId() );
00192     }
00193   }
00194 
00195   icalcomponent_free( calendar );
00196   icalmemory_free_ring();
00197 
00198   return success;
00199 }
00200 
00201 Incidence::Ptr ICalFormat::fromString( const QString &string )
00202 {
00203   MemoryCalendar::Ptr cal( new MemoryCalendar( d->mTimeSpec ) );
00204   fromString( cal, string );
00205 
00206   Incidence::Ptr ical;
00207   Event::List elist = cal->events();
00208   if ( elist.count() > 0 ) {
00209     ical = elist.first();
00210   } else {
00211     Todo::List tlist = cal->todos();
00212     if ( tlist.count() > 0 ) {
00213       ical = tlist.first();
00214     } else {
00215       Journal::List jlist = cal->journals();
00216       if ( jlist.count() > 0 ) {
00217         ical = jlist.first();
00218       }
00219     }
00220   }
00221 
00222   return ical ? Incidence::Ptr( ical->clone() ) : Incidence::Ptr();
00223 }
00224 
00225 QString ICalFormat::toString( const Calendar::Ptr &cal,
00226                               const QString &notebook, bool deleted )
00227 {
00228   icalcomponent *calendar = d->mImpl->createCalendarComponent( cal );
00229   icalcomponent *component;
00230 
00231   ICalTimeZones *tzlist = cal->timeZones();  // time zones possibly used in the calendar
00232   ICalTimeZones tzUsedList;                  // time zones actually used in the calendar
00233 
00234   // todos
00235   Todo::List todoList = deleted ? cal->deletedTodos() : cal->rawTodos();
00236   Todo::List::ConstIterator it;
00237   for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
00238     if ( !deleted || !cal->todo( (*it)->uid(), (*it)->recurrenceId() ) ) {
00239       // use existing ones, or really deleted ones
00240       if ( notebook.isEmpty() ||
00241            ( !cal->notebook( *it ).isEmpty() && notebook.endsWith( cal->notebook( *it ) ) ) ) {
00242         component = d->mImpl->writeTodo( *it, tzlist, &tzUsedList );
00243         icalcomponent_add_component( calendar, component );
00244       }
00245     }
00246   }
00247   // events
00248   Event::List events = deleted ? cal->deletedEvents() : cal->rawEvents();
00249   Event::List::ConstIterator it2;
00250   for ( it2 = events.constBegin(); it2 != events.constEnd(); ++it2 ) {
00251     if ( !deleted || !cal->event( (*it2)->uid(), (*it2)->recurrenceId() ) ) {
00252       // use existing ones, or really deleted ones
00253       if ( notebook.isEmpty() ||
00254            ( !cal->notebook( *it2 ).isEmpty() && notebook.endsWith( cal->notebook( *it2 ) ) ) ) {
00255         component = d->mImpl->writeEvent( *it2, tzlist, &tzUsedList );
00256         icalcomponent_add_component( calendar, component );
00257       }
00258     }
00259   }
00260 
00261   // journals
00262   Journal::List journals = deleted ? cal->deletedJournals() : cal->rawJournals();
00263   Journal::List::ConstIterator it3;
00264   for ( it3 = journals.constBegin(); it3 != journals.constEnd(); ++it3 ) {
00265     if ( !deleted || !cal->journal( (*it3)->uid(), (*it3)->recurrenceId() ) ) {
00266       // use existing ones, or really deleted ones
00267       if ( notebook.isEmpty() ||
00268            ( !cal->notebook( *it3 ).isEmpty() && notebook.endsWith( cal->notebook( *it3 ) ) ) ) {
00269         component = d->mImpl->writeJournal( *it3, tzlist, &tzUsedList );
00270         icalcomponent_add_component( calendar, component );
00271       }
00272     }
00273   }
00274 
00275   // time zones
00276   ICalTimeZones::ZoneMap zones = tzUsedList.zones();
00277   if ( todoList.isEmpty() && events.isEmpty() && journals.isEmpty() ) {
00278     // no incidences means no used timezones, use all timezones
00279     // this will export a calendar having only timezone definitions
00280     zones = tzlist->zones();
00281   }
00282   for ( ICalTimeZones::ZoneMap::ConstIterator it=zones.constBegin();
00283         it != zones.constEnd(); ++it ) {
00284     icaltimezone *tz = (*it).icalTimezone();
00285     if ( !tz ) {
00286       kError() << "bad time zone";
00287     } else {
00288       component = icalcomponent_new_clone( icaltimezone_get_component( tz ) );
00289       icalcomponent_add_component( calendar, component );
00290       icaltimezone_free( tz, 1 );
00291     }
00292   }
00293 
00294   QString text = QString::fromUtf8( icalcomponent_as_ical_string( calendar ) );
00295 
00296   icalcomponent_free( calendar );
00297   icalmemory_free_ring();
00298 
00299   if ( text.isEmpty() ) {
00300     setException( new Exception( Exception::LibICalError ) );
00301   }
00302 
00303   return text;
00304 }
00305 
00306 QString ICalFormat::toICalString( const Incidence::Ptr &incidence )
00307 {
00308   MemoryCalendar::Ptr cal( new MemoryCalendar( d->mTimeSpec ) );
00309   cal->addIncidence( Incidence::Ptr( incidence->clone() ) );
00310   return toString( cal.staticCast<Calendar>() );
00311 }
00312 
00313 QString ICalFormat::toString( const Incidence::Ptr &incidence )
00314 {
00315   return QString::fromUtf8( toRawString( incidence ) );
00316 }
00317 
00318 QByteArray ICalFormat::toRawString( const Incidence::Ptr &incidence )
00319 {
00320   icalcomponent *component;
00321 
00322   component = d->mImpl->writeIncidence( incidence );
00323 
00324   QByteArray text = icalcomponent_as_ical_string( component );
00325 
00326   icalcomponent_free( component );
00327 
00328   return text;
00329 }
00330 
00331 QString ICalFormat::toString( RecurrenceRule *recurrence )
00332 {
00333   icalproperty *property;
00334   property = icalproperty_new_rrule( d->mImpl->writeRecurrenceRule( recurrence ) );
00335   QString text = QString::fromUtf8( icalproperty_as_ical_string( property ) );
00336   icalproperty_free( property );
00337   return text;
00338 }
00339 
00340 bool ICalFormat::fromString( RecurrenceRule *recurrence, const QString &rrule )
00341 {
00342   if ( !recurrence ) {
00343     return false;
00344   }
00345   bool success = true;
00346   icalerror_clear_errno();
00347   struct icalrecurrencetype recur = icalrecurrencetype_from_string( rrule.toLatin1() );
00348   if ( icalerrno != ICAL_NO_ERROR ) {
00349     kDebug() << "Recurrence parsing error:" << icalerror_strerror( icalerrno );
00350     success = false;
00351   }
00352 
00353   if ( success ) {
00354     d->mImpl->readRecurrence( recur, recurrence );
00355   }
00356 
00357   return success;
00358 }
00359 
00360 QString ICalFormat::createScheduleMessage( const IncidenceBase::Ptr &incidence,
00361                                            iTIPMethod method )
00362 {
00363   icalcomponent *message = 0;
00364 
00365   if ( incidence->type() == Incidence::TypeEvent ||
00366        incidence->type() == Incidence::TypeTodo ) {
00367 
00368     Incidence::Ptr i = incidence.staticCast<Incidence>();
00369 
00370     // Recurring events need timezone information to allow proper calculations
00371     // across timezones with different DST.
00372     const bool useUtcTimes = !i->recurs();
00373 
00374     const bool hasSchedulingId = (i->schedulingID() != i->uid());
00375 
00376     const bool incidenceNeedChanges = (useUtcTimes || hasSchedulingId);
00377 
00378     if ( incidenceNeedChanges ) {
00379       // The incidence need changes, so clone it before we continue
00380       i = Incidence::Ptr( i->clone() );
00381 
00382       // Handle conversion to UTC times
00383       if ( useUtcTimes ) {
00384         i->shiftTimes( KDateTime::Spec::UTC(), KDateTime::Spec::UTC() );
00385       }
00386 
00387       // Handle scheduling ID being present
00388       if ( hasSchedulingId ) {
00389         // We have a separation of scheduling ID and UID
00390         i->setSchedulingID( QString(), i->schedulingID() );
00391 
00392       }
00393 
00394       // Build the message with the cloned incidence
00395       message = d->mImpl->createScheduleComponent( i, method );
00396     }
00397   }
00398 
00399   if ( message == 0 ) {
00400     message = d->mImpl->createScheduleComponent( incidence, method );
00401   }
00402 
00403   QString messageText = QString::fromUtf8( icalcomponent_as_ical_string( message ) );
00404 
00405   icalcomponent_free( message );
00406   return messageText;
00407 }
00408 
00409 FreeBusy::Ptr ICalFormat::parseFreeBusy( const QString &str )
00410 {
00411   clearException();
00412 
00413   icalcomponent *message;
00414   message = icalparser_parse_string( str.toUtf8() );
00415 
00416   if ( !message ) {
00417     return FreeBusy::Ptr();
00418   }
00419 
00420   FreeBusy::Ptr freeBusy;
00421 
00422   icalcomponent *c;
00423   for ( c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT );
00424         c != 0; c = icalcomponent_get_next_component( message, ICAL_VFREEBUSY_COMPONENT ) ) {
00425     FreeBusy::Ptr fb = d->mImpl->readFreeBusy( c );
00426 
00427     if ( freeBusy ) {
00428       freeBusy->merge( fb );
00429     } else {
00430       freeBusy = fb;
00431     }
00432   }
00433 
00434   if ( !freeBusy ) {
00435     kDebug() << "object is not a freebusy.";
00436   }
00437 
00438   icalcomponent_free( message );
00439 
00440   return freeBusy;
00441 }
00442 
00443 ScheduleMessage::Ptr ICalFormat::parseScheduleMessage( const Calendar::Ptr &cal,
00444                                                        const QString &messageText )
00445 {
00446   setTimeSpec( cal->timeSpec() );
00447   clearException();
00448 
00449   if ( messageText.isEmpty() ) {
00450     setException(
00451       new Exception( Exception::ParseErrorEmptyMessage ) );
00452     return ScheduleMessage::Ptr();
00453   }
00454 
00455   icalcomponent *message;
00456   message = icalparser_parse_string( messageText.toUtf8() );
00457 
00458   if ( !message ) {
00459     setException(
00460       new Exception( Exception::ParseErrorUnableToParse ) );
00461 
00462     return ScheduleMessage::Ptr();
00463   }
00464 
00465   icalproperty *m =
00466     icalcomponent_get_first_property( message, ICAL_METHOD_PROPERTY );
00467   if ( !m ) {
00468     setException(
00469       new Exception( Exception::ParseErrorMethodProperty ) );
00470 
00471     return ScheduleMessage::Ptr();
00472   }
00473 
00474   // Populate the message's time zone collection with all VTIMEZONE components
00475   ICalTimeZones tzlist;
00476   ICalTimeZoneSource tzs;
00477   tzs.parse( message, tzlist );
00478 
00479   icalcomponent *c;
00480 
00481   IncidenceBase::Ptr incidence;
00482   c = icalcomponent_get_first_component( message, ICAL_VEVENT_COMPONENT );
00483   if ( c ) {
00484     incidence = d->mImpl->readEvent( c, &tzlist ).staticCast<IncidenceBase>();
00485   }
00486 
00487   if ( !incidence ) {
00488     c = icalcomponent_get_first_component( message, ICAL_VTODO_COMPONENT );
00489     if ( c ) {
00490       incidence = d->mImpl->readTodo( c, &tzlist ).staticCast<IncidenceBase>();
00491     }
00492   }
00493 
00494   if ( !incidence ) {
00495     c = icalcomponent_get_first_component( message, ICAL_VJOURNAL_COMPONENT );
00496     if ( c ) {
00497       incidence = d->mImpl->readJournal( c, &tzlist ).staticCast<IncidenceBase>();
00498     }
00499   }
00500 
00501   if ( !incidence ) {
00502     c = icalcomponent_get_first_component( message, ICAL_VFREEBUSY_COMPONENT );
00503     if ( c ) {
00504       incidence = d->mImpl->readFreeBusy( c ).staticCast<IncidenceBase>();
00505     }
00506   }
00507 
00508   if ( !incidence ) {
00509     kDebug() << "object is not a freebusy, event, todo or journal";
00510     setException( new Exception( Exception::ParseErrorNotIncidence ) );
00511 
00512     return ScheduleMessage::Ptr();
00513   }
00514 
00515   icalproperty_method icalmethod = icalproperty_get_method( m );
00516   iTIPMethod method;
00517 
00518   switch ( icalmethod ) {
00519   case ICAL_METHOD_PUBLISH:
00520     method = iTIPPublish;
00521     break;
00522   case ICAL_METHOD_REQUEST:
00523     method = iTIPRequest;
00524     break;
00525   case ICAL_METHOD_REFRESH:
00526     method = iTIPRefresh;
00527     break;
00528   case ICAL_METHOD_CANCEL:
00529     method = iTIPCancel;
00530     break;
00531   case ICAL_METHOD_ADD:
00532     method = iTIPAdd;
00533     break;
00534   case ICAL_METHOD_REPLY:
00535     method = iTIPReply;
00536     break;
00537   case ICAL_METHOD_COUNTER:
00538     method = iTIPCounter;
00539     break;
00540   case ICAL_METHOD_DECLINECOUNTER:
00541     method = iTIPDeclineCounter;
00542     break;
00543   default:
00544     method = iTIPNoMethod;
00545     kDebug() << "Unknown method";
00546     break;
00547   }
00548 
00549   if ( !icalrestriction_check( message ) ) {
00550     kWarning() << endl
00551                << "kcalcore library reported a problem while parsing:";
00552     kWarning() << ScheduleMessage::methodName( method ) << ":"  //krazy:exclude=kdebug
00553                << d->mImpl->extractErrorProperty( c );
00554   }
00555 
00556   Incidence::Ptr existingIncidence = cal->incidence( incidence->uid() );
00557 
00558   icalcomponent *calendarComponent = 0;
00559   if ( existingIncidence ) {
00560     calendarComponent = d->mImpl->createCalendarComponent( cal );
00561 
00562     // TODO: check, if cast is required, or if it can be done by virtual funcs.
00563     // TODO: Use a visitor for this!
00564     if ( existingIncidence->type() == Incidence::TypeTodo ) {
00565       Todo::Ptr todo = existingIncidence.staticCast<Todo>();
00566       icalcomponent_add_component( calendarComponent,
00567                                    d->mImpl->writeTodo( todo ) );
00568     }
00569     if ( existingIncidence->type() == Incidence::TypeEvent ) {
00570       Event::Ptr event = existingIncidence.staticCast<Event>();
00571       icalcomponent_add_component( calendarComponent,
00572                                    d->mImpl->writeEvent( event ) );
00573     }
00574   } else {
00575     icalcomponent_free( message );
00576     return ScheduleMessage::Ptr( new ScheduleMessage( incidence, method,
00577                                                       ScheduleMessage::Unknown ) );
00578   }
00579 
00580   icalproperty_xlicclass result =
00581     icalclassify( message, calendarComponent, static_cast<const char *>( "" ) );
00582 
00583   ScheduleMessage::Status status;
00584 
00585   switch ( result ) {
00586   case ICAL_XLICCLASS_PUBLISHNEW:
00587     status = ScheduleMessage::PublishNew;
00588     break;
00589   case ICAL_XLICCLASS_PUBLISHUPDATE:
00590     status = ScheduleMessage::PublishUpdate;
00591     break;
00592   case ICAL_XLICCLASS_OBSOLETE:
00593     status = ScheduleMessage::Obsolete;
00594     break;
00595   case ICAL_XLICCLASS_REQUESTNEW:
00596     status = ScheduleMessage::RequestNew;
00597     break;
00598   case ICAL_XLICCLASS_REQUESTUPDATE:
00599     status = ScheduleMessage::RequestUpdate;
00600     break;
00601   case ICAL_XLICCLASS_UNKNOWN:
00602   default:
00603     status = ScheduleMessage::Unknown;
00604     break;
00605   }
00606 
00607   icalcomponent_free( message );
00608   icalcomponent_free( calendarComponent );
00609 
00610   return ScheduleMessage::Ptr( new ScheduleMessage( incidence, method, status ) );
00611 }
00612 
00613 void ICalFormat::setTimeSpec( const KDateTime::Spec &timeSpec )
00614 {
00615   d->mTimeSpec = timeSpec;
00616 }
00617 
00618 KDateTime::Spec ICalFormat::timeSpec() const
00619 {
00620   return d->mTimeSpec;
00621 }
00622 
00623 QString ICalFormat::timeZoneId() const
00624 {
00625   KTimeZone tz = d->mTimeSpec.timeZone();
00626   return tz.isValid() ? tz.name() : QString();
00627 }
00628 
00629 void ICalFormat::virtual_hook( int id, void *data )
00630 {
00631   Q_UNUSED( id );
00632   Q_UNUSED( data );
00633   Q_ASSERT( false );
00634 }

KCalCore Library

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

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.5
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal