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

KCalCore Library

vcalformat.cpp
Go to the documentation of this file.
00001 /*
00002   This file is part of the kcalcore library.
00003 
00004   Copyright (c) 1998 Preston Brown <pbrown@kde.org>
00005   Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
00006 
00007   This library is free software; you can redistribute it and/or
00008   modify it under the terms of the GNU Library General Public
00009   License as published by the Free Software Foundation; either
00010   version 2 of the License, or (at your option) any later version.
00011 
00012   This library is distributed in the hope that it will be useful,
00013   but WITHOUT ANY WARRANTY; without even the implied warranty of
00014   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015   Library General Public License for more details.
00016 
00017   You should have received a copy of the GNU Library General Public License
00018   along with this library; see the file COPYING.LIB.  If not, write to
00019   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00020   Boston, MA 02110-1301, USA.
00021 */
00037 #include "vcalformat.h"
00038 #include "calendar.h"
00039 #include "event.h"
00040 #include "exceptions.h"
00041 #include "icaltimezones.h"
00042 #include "todo.h"
00043 #include "versit/vcc.h"
00044 #include "versit/vobject.h"
00045 
00046 #include <KDebug>
00047 
00048 #include <QtCore/QBitArray>
00049 #include <QtCore/QFile>
00050 #include <QtGui/QTextDocument> // for Qt::escape() and Qt::mightBeRichText()
00051 
00052 using namespace KCalCore;
00053 
00058 //@cond PRIVATE
00059 template <typename K>
00060 void removeAllVCal( QVector< QSharedPointer<K> > &c, const QSharedPointer<K> &x )
00061 {
00062   Q_ASSERT( c.count( x ) == 1 );
00063   c.remove( c.indexOf( x ) );
00064 }
00065 
00066 class KCalCore::VCalFormat::Private
00067 {
00068   public:
00069     Calendar::Ptr mCalendar;
00070     Event::List mEventsRelate;  // Events with relations
00071     Todo::List mTodosRelate;    // To-dos with relations
00072     QSet<QByteArray> mManuallyWrittenExtensionFields; // X- fields that are manually dumped
00073 };
00074 //@endcond
00075 
00076 VCalFormat::VCalFormat() : d( new KCalCore::VCalFormat::Private )
00077 {
00078 #if defined(KCALCORE_FOR_SYMBIAN)
00079   d->mManuallyWrittenExtensionFields << VCRecurrenceIdProp;
00080   d->mManuallyWrittenExtensionFields << EPOCAgendaEntryTypeProp;
00081 #endif
00082   d->mManuallyWrittenExtensionFields << KPilotIdProp;
00083   d->mManuallyWrittenExtensionFields << KPilotStatusProp;
00084 }
00085 
00086 VCalFormat::~VCalFormat()
00087 {
00088   delete d;
00089 }
00090 
00091 bool VCalFormat::load( const Calendar::Ptr &calendar, const QString &fileName )
00092 {
00093   d->mCalendar = calendar;
00094 
00095   clearException();
00096 
00097   VObject *vcal = 0;
00098 
00099   // this is not necessarily only 1 vcal.  Could be many vcals, or include
00100   // a vcard...
00101   vcal = Parse_MIME_FromFileName( const_cast<char *>( QFile::encodeName( fileName ).data() ) );
00102 
00103   if ( !vcal ) {
00104     setException( new Exception( Exception::CalVersionUnknown ) );
00105     return false;
00106   }
00107 
00108   // any other top-level calendar stuff should be added/initialized here
00109 
00110   // put all vobjects into their proper places
00111   QString savedTimeZoneId = d->mCalendar->timeZoneId();
00112   populate( vcal, false, fileName );
00113   d->mCalendar->setTimeZoneId(savedTimeZoneId);
00114 
00115   // clean up from vcal API stuff
00116   cleanVObjects( vcal );
00117   cleanStrTbl();
00118 
00119   return true;
00120 }
00121 
00122 bool VCalFormat::save( const Calendar::Ptr &calendar, const QString &fileName )
00123 {
00124   d->mCalendar = calendar;
00125 
00126   ICalTimeZones *tzlist = d->mCalendar->timeZones();
00127 
00128   QString tmpStr;
00129   VObject *vcal, *vo;
00130 
00131   vcal = newVObject( VCCalProp );
00132 
00133   //  addPropValue(vcal,VCLocationProp, "0.0");
00134   addPropValue( vcal, VCProdIdProp, productId().toLatin1() );
00135   addPropValue( vcal, VCVersionProp, _VCAL_VERSION );
00136 
00137   // TODO STUFF
00138   Todo::List todoList = d->mCalendar->rawTodos();
00139   Todo::List::ConstIterator it;
00140   for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
00141     if ( (*it)->dtStart().timeZone().name().mid( 0, 4 ) == "VCAL" ) {
00142       ICalTimeZone zone = tzlist->zone( (*it)->dtStart().timeZone().name() );
00143       if ( zone.isValid() ) {
00144         QByteArray timezone = zone.vtimezone();
00145         addPropValue( vcal, VCTimeZoneProp, parseTZ( timezone ).toLocal8Bit() );
00146         QString dst = parseDst( timezone );
00147         while ( !dst.isEmpty() ) {
00148           addPropValue( vcal, VCDayLightProp, dst.toLocal8Bit() );
00149           dst = parseDst( timezone );
00150         }
00151       }
00152     }
00153     vo = eventToVTodo( *it );
00154     addVObjectProp( vcal, vo );
00155   }
00156   // EVENT STUFF
00157   Event::List events = d->mCalendar->rawEvents();
00158   Event::List::ConstIterator it2;
00159   for ( it2 = events.constBegin(); it2 != events.constEnd(); ++it2 ) {
00160     if ( (*it2)->dtStart().timeZone().name().mid( 0, 4 ) == "VCAL" ) {
00161       ICalTimeZone zone = tzlist->zone( (*it2)->dtStart().timeZone().name() );
00162       if ( zone.isValid() ) {
00163         QByteArray timezone = zone.vtimezone();
00164         addPropValue( vcal, VCTimeZoneProp, parseTZ( timezone ).toLocal8Bit() );
00165         QString dst = parseDst( timezone );
00166         while ( !dst.isEmpty() ) {
00167           addPropValue( vcal, VCDayLightProp, dst.toLocal8Bit() );
00168           dst = parseDst( timezone );
00169         }
00170       }
00171     }
00172     vo = eventToVEvent( *it2 );
00173     addVObjectProp( vcal, vo );
00174   }
00175   writeVObjectToFile( QFile::encodeName( fileName ).data(), vcal );
00176   cleanVObjects( vcal );
00177   cleanStrTbl();
00178 
00179   if ( QFile::exists( fileName ) ) {
00180     return true;
00181   } else {
00182     return false; // error
00183   }
00184 
00185   return false;
00186 }
00187 
00188 bool VCalFormat::fromString( const Calendar::Ptr &calendar, const QString &string,
00189                              bool deleted, const QString &notebook )
00190 {
00191   return fromRawString( calendar, string.toUtf8(), deleted, notebook );
00192 }
00193 
00194 bool VCalFormat::fromRawString( const Calendar::Ptr &calendar, const QByteArray &string,
00195                                 bool deleted, const QString &notebook )
00196 {
00197   d->mCalendar = calendar;
00198 
00199   if ( !string.size() ) {
00200     return false;
00201   }
00202 
00203   VObject *vcal = Parse_MIME( string.data(), string.size() );
00204   if ( !vcal ) {
00205     return false;
00206   }
00207 
00208   VObjectIterator i;
00209   initPropIterator( &i, vcal );
00210 
00211   // put all vobjects into their proper places
00212   QString savedTimeZoneId = d->mCalendar->timeZoneId();
00213   populate( vcal, deleted, notebook );
00214   d->mCalendar->setTimeZoneId(savedTimeZoneId);
00215 
00216   // clean up from vcal API stuff
00217   cleanVObjects( vcal );
00218   cleanStrTbl();
00219 
00220   return true;
00221 }
00222 
00223 QString VCalFormat::toString( const Calendar::Ptr &calendar,
00224                               const QString &notebook, bool deleted )
00225 {
00226   // TODO: Factor out VCalFormat::asString()
00227   d->mCalendar = calendar;
00228 
00229   ICalTimeZones *tzlist = d->mCalendar->timeZones();
00230 
00231   VObject *vo;
00232   VObject *vcal = newVObject( VCCalProp );
00233 
00234   addPropValue( vcal, VCProdIdProp, CalFormat::productId().toLatin1() );
00235   addPropValue( vcal, VCVersionProp, _VCAL_VERSION );
00236 
00237   // TODO STUFF
00238   Todo::List todoList = deleted ? d->mCalendar->deletedTodos() : d->mCalendar->rawTodos();
00239   Todo::List::ConstIterator it;
00240   for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
00241     if ( !deleted || !d->mCalendar->todo( (*it)->uid(), (*it)->recurrenceId() ) ) {
00242       // use existing ones, or really deleted ones
00243       if ( notebook.isEmpty() ||
00244            ( !calendar->notebook(*it).isEmpty() &&
00245              notebook.endsWith( calendar->notebook( *it ) ) ) ) {
00246         if ( (*it)->dtStart().timeZone().name().mid( 0, 4 ) == "VCAL" ) {
00247           ICalTimeZone zone = tzlist->zone( (*it)->dtStart().timeZone().name() );
00248           if ( zone.isValid() ) {
00249             QByteArray timezone = zone.vtimezone();
00250             addPropValue( vcal, VCTimeZoneProp, parseTZ( timezone ).toLocal8Bit() );
00251             QString dst = parseDst( timezone );
00252             while ( !dst.isEmpty() ) {
00253               addPropValue( vcal, VCDayLightProp, dst.toLocal8Bit() );
00254               dst = parseDst( timezone );
00255             }
00256           }
00257         }
00258         vo = eventToVTodo( *it );
00259         addVObjectProp( vcal, vo );
00260       }
00261     }
00262   }
00263 
00264   // EVENT STUFF
00265   Event::List events = deleted ? d->mCalendar->deletedEvents() : d->mCalendar->rawEvents();
00266   Event::List::ConstIterator it2;
00267   for ( it2 = events.constBegin(); it2 != events.constEnd(); ++it2 ) {
00268     if ( !deleted || !d->mCalendar->event( (*it2)->uid(), (*it2)->recurrenceId() ) ) {
00269       // use existing ones, or really deleted ones
00270       if ( notebook.isEmpty() ||
00271            ( !calendar->notebook( *it2 ).isEmpty() &&
00272              notebook.endsWith( calendar->notebook( *it2 ) ) ) ) {
00273         if ( (*it2)->dtStart().timeZone().name().mid( 0, 4 ) == "VCAL" ) {
00274           ICalTimeZone zone = tzlist->zone( (*it2)->dtStart().timeZone().name() );
00275           if ( zone.isValid() ) {
00276             QByteArray timezone = zone.vtimezone();
00277             addPropValue( vcal, VCTimeZoneProp, parseTZ( timezone ).toLocal8Bit() );
00278             QString dst = parseDst( timezone );
00279             while ( !dst.isEmpty() ) {
00280               addPropValue( vcal, VCDayLightProp, dst.toLocal8Bit() );
00281               dst = parseDst( timezone );
00282             }
00283           }
00284         }
00285         vo = eventToVEvent( *it2 );
00286         addVObjectProp( vcal, vo );
00287       }
00288     }
00289   }
00290 
00291   char *buf = writeMemVObject( 0, 0, vcal );
00292 
00293   QString result( buf );
00294 
00295   deleteStr( buf );
00296 
00297   cleanVObject( vcal );
00298 
00299   return result;
00300 }
00301 
00302 VObject *VCalFormat::eventToVTodo( const Todo::Ptr &anEvent )
00303 {
00304   VObject *vtodo;
00305   QString tmpStr;
00306 
00307   vtodo = newVObject( VCTodoProp );
00308 
00309   // due date
00310   if ( anEvent->hasDueDate() ) {
00311     tmpStr = kDateTimeToISO( anEvent->dtDue(), !anEvent->allDay() );
00312     addPropValue( vtodo, VCDueProp, tmpStr.toLocal8Bit() );
00313   }
00314 
00315   // start date
00316   if ( anEvent->hasStartDate() ) {
00317     tmpStr = kDateTimeToISO( anEvent->dtStart(), !anEvent->allDay() );
00318     addPropValue( vtodo, VCDTstartProp, tmpStr.toLocal8Bit() );
00319   }
00320 
00321   // creation date
00322   tmpStr = kDateTimeToISO( anEvent->created() );
00323   addPropValue( vtodo, VCDCreatedProp, tmpStr.toLocal8Bit() );
00324 
00325   // unique id
00326   addPropValue( vtodo, VCUniqueStringProp,
00327                 anEvent->uid().toLocal8Bit() );
00328 
00329   // revision
00330   tmpStr.sprintf( "%i", anEvent->revision() );
00331   addPropValue( vtodo, VCSequenceProp, tmpStr.toLocal8Bit() );
00332 
00333   // last modification date
00334   tmpStr = kDateTimeToISO( anEvent->lastModified() );
00335   addPropValue( vtodo, VCLastModifiedProp, tmpStr.toLocal8Bit() );
00336 
00337   // organizer stuff
00338   // @TODO: How about the common name?
00339   if ( !anEvent->organizer()->email().isEmpty() ) {
00340     tmpStr = "MAILTO:" + anEvent->organizer()->email();
00341     addPropValue( vtodo, ICOrganizerProp, tmpStr.toLocal8Bit() );
00342   }
00343 
00344   // attendees
00345   if ( anEvent->attendeeCount() > 0 ) {
00346     Attendee::List::ConstIterator it;
00347     Attendee::Ptr curAttendee;
00348     for ( it = anEvent->attendees().constBegin(); it != anEvent->attendees().constEnd();
00349           ++it ) {
00350       curAttendee = *it;
00351       if ( !curAttendee->email().isEmpty() && !curAttendee->name().isEmpty() ) {
00352         tmpStr = "MAILTO:" + curAttendee->name() + " <" + curAttendee->email() + '>';
00353       } else if ( curAttendee->name().isEmpty() && curAttendee->email().isEmpty() ) {
00354         tmpStr = "MAILTO: ";
00355         kDebug() << "warning! this Event has an attendee w/o name or email!";
00356       } else if ( curAttendee->name().isEmpty() ) {
00357         tmpStr = "MAILTO: " + curAttendee->email();
00358       } else {
00359         tmpStr = "MAILTO: " + curAttendee->name();
00360       }
00361       VObject *aProp = addPropValue( vtodo, VCAttendeeProp, tmpStr.toLocal8Bit() );
00362       addPropValue( aProp, VCRSVPProp, curAttendee->RSVP() ? "TRUE" : "FALSE" );
00363       addPropValue( aProp, VCStatusProp, writeStatus( curAttendee->status() ) );
00364     }
00365   }
00366 
00367   // description BL:
00368   if ( !anEvent->description().isEmpty() ) {
00369     VObject *d = addPropValue( vtodo, VCDescriptionProp,
00370                                anEvent->description().toLocal8Bit() );
00371     if ( anEvent->description().indexOf( '\n' ) != -1 ) {
00372       addPropValue( d, VCEncodingProp, VCQuotedPrintableProp );
00373     }
00374   }
00375 
00376   // summary
00377   if ( !anEvent->summary().isEmpty() ) {
00378     addPropValue( vtodo, VCSummaryProp, anEvent->summary().toLocal8Bit() );
00379   }
00380 
00381   // location
00382   if ( !anEvent->location().isEmpty() ) {
00383     addPropValue( vtodo, VCLocationProp, anEvent->location().toLocal8Bit() );
00384   }
00385 
00386   // completed status
00387   // backward compatibility, KOrganizer used to interpret only these two values
00388   addPropValue( vtodo, VCStatusProp, anEvent->isCompleted() ? "COMPLETED" : "NEEDS ACTION" );
00389 
00390   // completion date
00391   if ( anEvent->hasCompletedDate() ) {
00392     tmpStr = kDateTimeToISO( anEvent->completed() );
00393     addPropValue( vtodo, VCCompletedProp, tmpStr.toLocal8Bit() );
00394   }
00395 
00396   // priority
00397   tmpStr.sprintf( "%i", anEvent->priority() );
00398   addPropValue( vtodo, VCPriorityProp, tmpStr.toLocal8Bit() );
00399 
00400   // related event
00401   if ( !anEvent->relatedTo().isEmpty() ) {
00402     addPropValue( vtodo, VCRelatedToProp,
00403                   anEvent->relatedTo().toLocal8Bit() );
00404   }
00405 
00406   // secrecy
00407   const char *text = 0;
00408   switch ( anEvent->secrecy() ) {
00409   case Incidence::SecrecyPublic:
00410     text = "PUBLIC";
00411     break;
00412   case Incidence::SecrecyPrivate:
00413     text = "PRIVATE";
00414     break;
00415   case Incidence::SecrecyConfidential:
00416     text = "CONFIDENTIAL";
00417     break;
00418   }
00419   if ( text ) {
00420     addPropValue( vtodo, VCClassProp, text );
00421   }
00422 
00423   // categories
00424   const QStringList tmpStrList = anEvent->categories();
00425   tmpStr = "";
00426   QString catStr;
00427   QStringList::const_iterator its;
00428   for ( its = tmpStrList.constBegin(); its != tmpStrList.constEnd(); ++its ) {
00429     catStr = *its;
00430     if ( catStr[0] == ' ' ) {
00431       tmpStr += catStr.mid( 1 );
00432     } else {
00433       tmpStr += catStr;
00434     }
00435     // this must be a ';' character as the vCalendar specification requires!
00436     // vcc.y has been hacked to translate the ';' to a ',' when the vcal is
00437     // read in.
00438     tmpStr += ';';
00439   }
00440   if ( !tmpStr.isEmpty() ) {
00441     tmpStr.truncate( tmpStr.length() - 1 );
00442     addPropValue( vtodo, VCCategoriesProp, tmpStr.toLocal8Bit() );
00443   }
00444 
00445   // alarm stuff
00446   Alarm::List::ConstIterator it;
00447   for ( it = anEvent->alarms().constBegin(); it != anEvent->alarms().constEnd(); ++it ) {
00448     Alarm::Ptr alarm = *it;
00449     if ( alarm->enabled() ) {
00450       VObject *a;
00451       if ( alarm->type() == Alarm::Display ) {
00452         a = addProp( vtodo, VCDAlarmProp );
00453         tmpStr = kDateTimeToISO( alarm->time() );
00454         addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
00455         addPropValue( a, VCRepeatCountProp, "1" );
00456         if ( alarm->text().isNull() ) {
00457           addPropValue( a, VCDisplayStringProp, "beep!" );
00458         } else {
00459           addPropValue( a, VCDisplayStringProp, alarm->text().toAscii().data() );
00460         }
00461       } else if ( alarm->type() == Alarm::Audio ) {
00462         a = addProp( vtodo, VCAAlarmProp );
00463         tmpStr = kDateTimeToISO( alarm->time() );
00464         addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
00465         addPropValue( a, VCRepeatCountProp, "1" );
00466         addPropValue( a, VCAudioContentProp, QFile::encodeName( alarm->audioFile() ) );
00467       } else if ( alarm->type() == Alarm::Procedure ) {
00468         a = addProp( vtodo, VCPAlarmProp );
00469         tmpStr = kDateTimeToISO( alarm->time() );
00470         addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
00471         addPropValue( a, VCRepeatCountProp, "1" );
00472         addPropValue( a, VCProcedureNameProp, QFile::encodeName( alarm->programFile() ) );
00473       }
00474     }
00475   }
00476 
00477   QString pilotId = anEvent->nonKDECustomProperty( KPilotIdProp );
00478   if ( !pilotId.isEmpty() ) {
00479     // pilot sync stuff
00480     addPropValue( vtodo, KPilotIdProp, pilotId.toLocal8Bit() );
00481     addPropValue( vtodo, KPilotStatusProp,
00482                   anEvent->nonKDECustomProperty( KPilotStatusProp ).toLocal8Bit() );
00483   }
00484 #if defined(KCALCORE_FOR_SYMBIAN)
00485   if ( anEvent->nonKDECustomProperty( EPOCAgendaEntryTypeProp ).isEmpty() ) {
00486     // Propagate braindeath by setting this property also so that
00487     // S60 is happy
00488     addPropValue( vtodo, EPOCAgendaEntryTypeProp, "TODO" );
00489   }
00490 
00491   writeCustomProperties( vtodo, anEvent );
00492 #endif
00493 
00494   return vtodo;
00495 }
00496 
00497 VObject *VCalFormat::eventToVEvent( const Event::Ptr &anEvent )
00498 {
00499   VObject *vevent;
00500   QString tmpStr;
00501 
00502   vevent = newVObject( VCEventProp );
00503 
00504   // start and end time
00505   tmpStr = kDateTimeToISO( anEvent->dtStart(), !anEvent->allDay() );
00506   addPropValue( vevent, VCDTstartProp, tmpStr.toLocal8Bit() );
00507 
00508 #if !defined(KCALCORE_FOR_MEEGO)
00509   // events that have time associated but take up no time should
00510   // not have both DTSTART and DTEND.
00511   if ( anEvent->dtStart() != anEvent->dtEnd() ) {
00512     tmpStr = kDateTimeToISO( anEvent->dtEnd(), !anEvent->allDay() );
00513     addPropValue( vevent, VCDTendProp, tmpStr.toLocal8Bit() );
00514   }
00515 #else
00516   // N900 and s60-phones need enddate
00517   tmpStr = kDateTimeToISO( anEvent->dtEnd(), !anEvent->allDay() );
00518   addPropValue( vevent, VCDTendProp, tmpStr.toLocal8Bit() );
00519 #endif
00520 
00521   // creation date
00522   tmpStr = kDateTimeToISO( anEvent->created() );
00523   addPropValue( vevent, VCDCreatedProp, tmpStr.toLocal8Bit() );
00524 
00525   // unique id
00526   addPropValue( vevent, VCUniqueStringProp,
00527                 anEvent->uid().toLocal8Bit() );
00528 
00529   // revision
00530   tmpStr.sprintf( "%i", anEvent->revision() );
00531   addPropValue( vevent, VCSequenceProp, tmpStr.toLocal8Bit() );
00532 
00533   // last modification date
00534   tmpStr = kDateTimeToISO( anEvent->lastModified() );
00535   addPropValue( vevent, VCLastModifiedProp, tmpStr.toLocal8Bit() );
00536 
00537   // attendee and organizer stuff
00538   // TODO: What to do with the common name?
00539   if ( !anEvent->organizer()->email().isEmpty() ) {
00540     tmpStr = "MAILTO:" + anEvent->organizer()->email();
00541     addPropValue( vevent, ICOrganizerProp, tmpStr.toLocal8Bit() );
00542   }
00543 
00544   // TODO: Put this functionality into Attendee class
00545   if ( anEvent->attendeeCount() > 0 ) {
00546     Attendee::List::ConstIterator it;
00547     for ( it = anEvent->attendees().constBegin(); it != anEvent->attendees().constEnd();
00548           ++it ) {
00549       Attendee::Ptr curAttendee = *it;
00550       if ( !curAttendee->email().isEmpty() && !curAttendee->name().isEmpty() ) {
00551         tmpStr = "MAILTO:" + curAttendee->name() + " <" + curAttendee->email() + '>';
00552       } else if ( curAttendee->name().isEmpty() && curAttendee->email().isEmpty() ) {
00553         tmpStr = "MAILTO: ";
00554         kDebug() << "warning! this Event has an attendee w/o name or email!";
00555       } else if ( curAttendee->name().isEmpty() ) {
00556         tmpStr = "MAILTO: " + curAttendee->email();
00557       } else {
00558         tmpStr = "MAILTO: " + curAttendee->name();
00559       }
00560       VObject *aProp = addPropValue( vevent, VCAttendeeProp, tmpStr.toLocal8Bit() );
00561       addPropValue( aProp, VCRSVPProp, curAttendee->RSVP() ? "TRUE" : "FALSE" );
00562       addPropValue( aProp, VCStatusProp, writeStatus( curAttendee->status() ) );
00563     }
00564   }
00565 
00566   // recurrence rule stuff
00567   const Recurrence *recur = anEvent->recurrence();
00568   if ( recur->recurs() ) {
00569     bool validRecur = true;
00570     QString tmpStr2;
00571     switch ( recur->recurrenceType() ) {
00572     case Recurrence::rDaily:
00573       tmpStr.sprintf( "D%i ", recur->frequency() );
00574       break;
00575     case Recurrence::rWeekly:
00576       tmpStr.sprintf( "W%i ", recur->frequency() );
00577       for ( int i = 0; i < 7; ++i ) {
00578         QBitArray days ( recur->days() );
00579         if ( days.testBit(i) ) {
00580           tmpStr += dayFromNum( i );
00581         }
00582       }
00583       break;
00584     case Recurrence::rMonthlyPos:
00585     {
00586       tmpStr.sprintf( "MP%i ", recur->frequency() );
00587       // write out all rMonthPos's
00588       QList<RecurrenceRule::WDayPos> tmpPositions = recur->monthPositions();
00589       for ( QList<RecurrenceRule::WDayPos>::ConstIterator posit = tmpPositions.constBegin();
00590             posit != tmpPositions.constEnd(); ++posit ) {
00591         int pos = (*posit).pos();
00592         tmpStr2.sprintf( "%i", ( pos > 0 ) ? pos : (-pos) );
00593         if ( pos < 0 ) {
00594           tmpStr2 += "- ";
00595         } else {
00596           tmpStr2 += "+ ";
00597         }
00598         tmpStr += tmpStr2;
00599         tmpStr += dayFromNum( (*posit).day() - 1 );
00600       }
00601       break;
00602     }
00603     case Recurrence::rMonthlyDay:
00604     {
00605       tmpStr.sprintf( "MD%i ", recur->frequency() );
00606       // write out all rMonthDays;
00607       const QList<int> tmpDays = recur->monthDays();
00608       for ( QList<int>::ConstIterator tmpDay = tmpDays.constBegin();
00609             tmpDay != tmpDays.constEnd(); ++tmpDay ) {
00610         tmpStr2.sprintf( "%i ", *tmpDay );
00611         tmpStr += tmpStr2;
00612       }
00613       break;
00614     }
00615     case Recurrence::rYearlyMonth:
00616     {
00617       tmpStr.sprintf( "YM%i ", recur->frequency() );
00618       // write out all the months;'
00619       // TODO: Any way to write out the day within the month???
00620       const QList<int> months = recur->yearMonths();
00621       for ( QList<int>::ConstIterator mit = months.constBegin();
00622             mit != months.constEnd(); ++mit ) {
00623         tmpStr2.sprintf( "%i ", *mit );
00624         tmpStr += tmpStr2;
00625       }
00626       break;
00627     }
00628     case Recurrence::rYearlyDay:
00629     {
00630       tmpStr.sprintf( "YD%i ", recur->frequency() );
00631       // write out all the rYearNums;
00632       const QList<int> tmpDays = recur->yearDays();
00633       for ( QList<int>::ConstIterator tmpDay = tmpDays.begin();
00634             tmpDay != tmpDays.end(); ++tmpDay ) {
00635         tmpStr2.sprintf( "%i ", *tmpDay );
00636         tmpStr += tmpStr2;
00637       }
00638       break;
00639     }
00640     default:
00641       // TODO: Write rYearlyPos and arbitrary rules!
00642       kDebug() << "ERROR, it should never get here in eventToVEvent!";
00643       validRecur = false;
00644       break;
00645     } // switch
00646 
00647     if ( recur->duration() > 0 ) {
00648       tmpStr2.sprintf( "#%i", recur->duration() );
00649       tmpStr += tmpStr2;
00650     } else if ( recur->duration() == -1 ) {
00651       tmpStr += "#0"; // defined as repeat forever
00652     } else {
00653 #if !defined(KCALCORE_FOR_MEEGO)
00654       tmpStr += kDateTimeToISO( recur->endDateTime(), false );
00655 #else
00656       tmpStr +=
00657         kDateTimeToISO( recur->endDateTime().toTimeSpec( d->mCalendar->timeSpec() ), false );
00658 #endif
00659     }
00660     // Only write out the rrule if we have a valid recurrence (i.e. a known
00661     // type in thee switch above)
00662     if ( validRecur ) {
00663       addPropValue( vevent, VCRRuleProp, tmpStr.toLocal8Bit() );
00664     }
00665 
00666   } // event repeats
00667 
00668   // exceptions dates to recurrence
00669   DateList dateList = recur->exDates();
00670   DateList::ConstIterator it;
00671   QString tmpStr2;
00672 
00673   for ( it = dateList.constBegin(); it != dateList.constEnd(); ++it ) {
00674     tmpStr = qDateToISO(*it) + ';';
00675     tmpStr2 += tmpStr;
00676   }
00677   if ( !tmpStr2.isEmpty() ) {
00678     tmpStr2.truncate( tmpStr2.length() - 1 );
00679     addPropValue( vevent, VCExpDateProp, tmpStr2.toLocal8Bit() );
00680   }
00681   // exceptions datetimes to recurrence
00682   DateTimeList dateTimeList = recur->exDateTimes();
00683   DateTimeList::ConstIterator idt;
00684   tmpStr2.clear();
00685 
00686   for ( idt = dateTimeList.constBegin(); idt != dateTimeList.constEnd(); ++idt ) {
00687     tmpStr = kDateTimeToISO( *idt ) + ';';
00688     tmpStr2 += tmpStr;
00689   }
00690   if ( !tmpStr2.isEmpty() ) {
00691     tmpStr2.truncate( tmpStr2.length() - 1 );
00692     addPropValue( vevent, VCExpDateProp, tmpStr2.toLocal8Bit() );
00693   }
00694 
00695   // description
00696   if ( !anEvent->description().isEmpty() ) {
00697     VObject *d = addPropValue( vevent, VCDescriptionProp,
00698                                anEvent->description().toLocal8Bit() );
00699     if ( anEvent->description().indexOf( '\n' ) != -1 ) {
00700       addPropValue( d, VCEncodingProp, VCQuotedPrintableProp );
00701     }
00702   }
00703 
00704   // summary
00705   if ( !anEvent->summary().isEmpty() ) {
00706     addPropValue( vevent, VCSummaryProp, anEvent->summary().toLocal8Bit() );
00707   }
00708 
00709   // location
00710   if ( !anEvent->location().isEmpty() ) {
00711     addPropValue( vevent, VCLocationProp, anEvent->location().toLocal8Bit() );
00712   }
00713 
00714   // status
00715 // TODO: define Event status
00716 //  addPropValue( vevent, VCStatusProp, anEvent->statusStr().toLocal8Bit() );
00717 
00718   // secrecy
00719   const char *text = 0;
00720   switch ( anEvent->secrecy() ) {
00721   case Incidence::SecrecyPublic:
00722     text = "PUBLIC";
00723     break;
00724   case Incidence::SecrecyPrivate:
00725     text = "PRIVATE";
00726     break;
00727   case Incidence::SecrecyConfidential:
00728     text = "CONFIDENTIAL";
00729     break;
00730   }
00731   if ( text ) {
00732     addPropValue( vevent, VCClassProp, text );
00733   }
00734 
00735   // categories
00736   QStringList tmpStrList = anEvent->categories();
00737   tmpStr = "";
00738   QString catStr;
00739   for ( QStringList::const_iterator it = tmpStrList.constBegin(); it != tmpStrList.constEnd();
00740         ++it ) {
00741     catStr = *it;
00742     if ( catStr[0] == ' ' ) {
00743       tmpStr += catStr.mid( 1 );
00744     } else {
00745       tmpStr += catStr;
00746     }
00747     // this must be a ';' character as the vCalendar specification requires!
00748     // vcc.y has been hacked to translate the ';' to a ',' when the vcal is
00749     // read in.
00750     tmpStr += ';';
00751   }
00752   if ( !tmpStr.isEmpty() ) {
00753     tmpStr.truncate( tmpStr.length() - 1 );
00754     addPropValue( vevent, VCCategoriesProp, tmpStr.toLocal8Bit() );
00755   }
00756 
00757   // attachments
00758   // TODO: handle binary attachments!
00759   Attachment::List attachments = anEvent->attachments();
00760   Attachment::List::ConstIterator atIt;
00761   for ( atIt = attachments.constBegin(); atIt != attachments.constEnd(); ++atIt ) {
00762     addPropValue( vevent, VCAttachProp, (*atIt)->uri().toLocal8Bit() );
00763   }
00764 
00765   // resources
00766   tmpStrList = anEvent->resources();
00767   tmpStr = tmpStrList.join( ";" );
00768   if ( !tmpStr.isEmpty() ) {
00769     addPropValue( vevent, VCResourcesProp, tmpStr.toLocal8Bit() );
00770   }
00771 
00772   // alarm stuff
00773   Alarm::List::ConstIterator it2;
00774   for ( it2 = anEvent->alarms().constBegin(); it2 != anEvent->alarms().constEnd(); ++it2 ) {
00775     Alarm::Ptr alarm = *it2;
00776     if ( alarm->enabled() ) {
00777       VObject *a;
00778       if ( alarm->type() == Alarm::Display ) {
00779         a = addProp( vevent, VCDAlarmProp );
00780         tmpStr = kDateTimeToISO( alarm->time() );
00781         addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
00782         addPropValue( a, VCRepeatCountProp, "1" );
00783         if ( alarm->text().isNull() ) {
00784           addPropValue( a, VCDisplayStringProp, "beep!" );
00785         } else {
00786           addPropValue( a, VCDisplayStringProp, alarm->text().toAscii().data() );
00787         }
00788       } else if ( alarm->type() == Alarm::Audio ) {
00789         a = addProp( vevent, VCAAlarmProp );
00790         tmpStr = kDateTimeToISO( alarm->time() );
00791         addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
00792         addPropValue( a, VCRepeatCountProp, "1" );
00793         addPropValue( a, VCAudioContentProp, QFile::encodeName( alarm->audioFile() ) );
00794       }
00795       if ( alarm->type() == Alarm::Procedure ) {
00796         a = addProp( vevent, VCPAlarmProp );
00797         tmpStr = kDateTimeToISO( alarm->time() );
00798         addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
00799         addPropValue( a, VCRepeatCountProp, "1" );
00800         addPropValue( a, VCProcedureNameProp, QFile::encodeName( alarm->programFile() ) );
00801       }
00802     }
00803   }
00804 
00805   // priority
00806   tmpStr.sprintf( "%i", anEvent->priority() );
00807   addPropValue( vevent, VCPriorityProp, tmpStr.toLocal8Bit() );
00808 
00809   // transparency
00810   tmpStr.sprintf( "%i", anEvent->transparency() );
00811   addPropValue( vevent, VCTranspProp, tmpStr.toLocal8Bit() );
00812 
00813   // related event
00814   if ( !anEvent->relatedTo().isEmpty() ) {
00815     addPropValue( vevent, VCRelatedToProp, anEvent->relatedTo().toLocal8Bit() );
00816   }
00817 
00818   QString pilotId = anEvent->nonKDECustomProperty( KPilotIdProp );
00819   if ( !pilotId.isEmpty() ) {
00820     // pilot sync stuff
00821     addPropValue( vevent, KPilotIdProp, pilotId.toLocal8Bit() );
00822     addPropValue( vevent, KPilotStatusProp,
00823                   anEvent->nonKDECustomProperty( KPilotStatusProp ).toLocal8Bit() );
00824   }
00825 
00826 #if defined(KCALCORE_FOR_SYMBIAN)
00827   if ( anEvent->nonKDECustomProperty( EPOCAgendaEntryTypeProp ).isEmpty() ) {
00828     // Propagate braindeath by setting this property also so that
00829     // S60 is happy
00830     if ( anEvent->allDay() ) {
00831       addPropValue( vevent, EPOCAgendaEntryTypeProp, "EVENT" );
00832     } else {
00833       addPropValue( vevent, EPOCAgendaEntryTypeProp, "APPOINTMENT" );
00834     }
00835   }
00836 
00837   if ( anEvent->hasRecurrenceId() ) {
00838       tmpStr = kDateTimeToISO( anEvent->recurrenceId(), true );
00839       addPropValue( vevent, VCRecurrenceIdProp, tmpStr.toLocal8Bit() );
00840   }
00841   writeCustomProperties( vevent, anEvent );
00842 #endif
00843 
00844   return vevent;
00845 }
00846 
00847 Todo::Ptr VCalFormat::VTodoToEvent( VObject *vtodo )
00848 {
00849   VObject *vo;
00850   VObjectIterator voi;
00851   char *s;
00852 
00853   Todo::Ptr anEvent( new Todo );
00854 
00855   // creation date
00856   if ( ( vo = isAPropertyOf( vtodo, VCDCreatedProp ) ) != 0 ) {
00857       anEvent->setCreated( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
00858       deleteStr( s );
00859   }
00860 
00861   // unique id
00862   vo = isAPropertyOf( vtodo, VCUniqueStringProp );
00863   // while the UID property is preferred, it is not required.  We'll use the
00864   // default Event UID if none is given.
00865   if ( vo ) {
00866     anEvent->setUid( s = fakeCString( vObjectUStringZValue( vo ) ) );
00867     deleteStr( s );
00868   }
00869 
00870   // last modification date
00871   if ( ( vo = isAPropertyOf( vtodo, VCLastModifiedProp ) ) != 0 ) {
00872     anEvent->setLastModified( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
00873     deleteStr( s );
00874   } else {
00875     anEvent->setLastModified( KDateTime::currentUtcDateTime() );
00876   }
00877 
00878   // organizer
00879   // if our extension property for the event's ORGANIZER exists, add it.
00880   if ( ( vo = isAPropertyOf( vtodo, ICOrganizerProp ) ) != 0 ) {
00881     anEvent->setOrganizer( s = fakeCString( vObjectUStringZValue( vo ) ) );
00882     deleteStr( s );
00883   } else {
00884     if ( d->mCalendar->owner()->name() != "Unknown Name" ) {
00885       anEvent->setOrganizer( d->mCalendar->owner() );
00886     }
00887   }
00888 
00889   // attendees.
00890   initPropIterator( &voi, vtodo );
00891   while ( moreIteration( &voi ) ) {
00892     vo = nextVObject( &voi );
00893     if ( strcmp( vObjectName( vo ), VCAttendeeProp ) == 0 ) {
00894       Attendee::Ptr a;
00895       VObject *vp;
00896       s = fakeCString( vObjectUStringZValue( vo ) );
00897       QString tmpStr = QString::fromLocal8Bit( s );
00898       deleteStr( s );
00899       tmpStr = tmpStr.simplified();
00900       int emailPos1, emailPos2;
00901       if ( ( emailPos1 = tmpStr.indexOf( '<' ) ) > 0 ) {
00902         // both email address and name
00903         emailPos2 = tmpStr.lastIndexOf( '>' );
00904         a = Attendee::Ptr( new Attendee( tmpStr.left( emailPos1 - 1 ),
00905                                          tmpStr.mid( emailPos1 + 1,
00906                                          emailPos2 - ( emailPos1 + 1 ) ) ) );
00907       } else if ( tmpStr.indexOf( '@' ) > 0 ) {
00908         // just an email address
00909           a = Attendee::Ptr( new Attendee( 0, tmpStr ) );
00910       } else {
00911         // just a name
00912         // WTF??? Replacing the spaces of a name and using this as email?
00913         QString email = tmpStr.replace( ' ', '.' );
00914         a = Attendee::Ptr( new Attendee( tmpStr, email ) );
00915       }
00916 
00917       // is there an RSVP property?
00918       if ( ( vp = isAPropertyOf( vo, VCRSVPProp ) ) != 0 ) {
00919         a->setRSVP( vObjectStringZValue( vp ) );
00920       }
00921       // is there a status property?
00922       if ( ( vp = isAPropertyOf( vo, VCStatusProp ) ) != 0 ) {
00923         a->setStatus( readStatus( vObjectStringZValue( vp ) ) );
00924       }
00925       // add the attendee
00926       anEvent->addAttendee( a );
00927     }
00928   }
00929 
00930   // description for todo
00931   if ( ( vo = isAPropertyOf( vtodo, VCDescriptionProp ) ) != 0 ) {
00932     s = fakeCString( vObjectUStringZValue( vo ) );
00933     anEvent->setDescription( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
00934     deleteStr( s );
00935   }
00936 
00937   // summary
00938   if ( ( vo = isAPropertyOf( vtodo, VCSummaryProp ) ) ) {
00939     s = fakeCString( vObjectUStringZValue( vo ) );
00940     anEvent->setSummary( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
00941     deleteStr( s );
00942   }
00943 
00944   // location
00945   if ( ( vo = isAPropertyOf( vtodo, VCLocationProp ) ) != 0 ) {
00946     s = fakeCString( vObjectUStringZValue( vo ) );
00947     anEvent->setLocation( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
00948     deleteStr( s );
00949   }
00950 
00951   // completed
00952   // was: status
00953   if ( ( vo = isAPropertyOf( vtodo, VCStatusProp ) ) != 0 ) {
00954     s = fakeCString( vObjectUStringZValue( vo ) );
00955     if ( s && strcmp( s, "COMPLETED" ) == 0 ) {
00956       anEvent->setCompleted( true );
00957     } else {
00958       anEvent->setCompleted( false );
00959     }
00960     deleteStr( s );
00961   } else {
00962     anEvent->setCompleted( false );
00963   }
00964 
00965   // completion date
00966   if ( ( vo = isAPropertyOf( vtodo, VCCompletedProp ) ) != 0 ) {
00967     anEvent->setCompleted( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
00968     deleteStr( s );
00969   }
00970 
00971   // priority
00972   if ( ( vo = isAPropertyOf( vtodo, VCPriorityProp ) ) ) {
00973     s = fakeCString( vObjectUStringZValue( vo ) );
00974     if ( s ) {
00975       anEvent->setPriority( atoi( s ) );
00976       deleteStr( s );
00977     }
00978   }
00979 
00980   anEvent->setAllDay( false );
00981 
00982   // due date
00983   if ( ( vo = isAPropertyOf( vtodo, VCDueProp ) ) != 0 ) {
00984     anEvent->setDtDue( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
00985     deleteStr( s );
00986     anEvent->setHasDueDate( true );
00987     if ( anEvent->dtDue().time().hour() == 0 &&
00988          anEvent->dtDue().time().minute() == 0 &&
00989          anEvent->dtDue().time().second() == 0 ) {
00990       anEvent->setAllDay( true );
00991     }
00992   } else {
00993     anEvent->setHasDueDate( false );
00994   }
00995 
00996   // start time
00997   if ( ( vo = isAPropertyOf( vtodo, VCDTstartProp ) ) != 0 ) {
00998     anEvent->setDtStart( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
00999     deleteStr( s );
01000     anEvent->setHasStartDate( true );
01001     if ( anEvent->dtStart().time().hour() == 0 &&
01002          anEvent->dtStart().time().minute() == 0 &&
01003          anEvent->dtStart().time().second() == 0 ) {
01004       anEvent->setAllDay( true );
01005     }
01006   } else {
01007     anEvent->setHasStartDate( false );
01008   }
01009 
01010   // alarm stuff
01011   if ( ( vo = isAPropertyOf( vtodo, VCDAlarmProp ) ) ) {
01012     Alarm::Ptr alarm;
01013     VObject *a;
01014     VObject *b;
01015     a = isAPropertyOf( vo, VCRunTimeProp );
01016     b = isAPropertyOf( vo, VCDisplayStringProp );
01017 
01018     if ( a || b ) {
01019       alarm = anEvent->newAlarm();
01020       if ( a ) {
01021         alarm->setTime( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( a ) ) ) );
01022         deleteStr( s );
01023       }
01024       alarm->setEnabled( true );
01025       if ( b ) {
01026         s = fakeCString( vObjectUStringZValue( b ) );
01027         alarm->setDisplayAlarm( QString( s ) );
01028         deleteStr( s );
01029       } else {
01030         alarm->setDisplayAlarm( QString() );
01031       }
01032     }
01033   }
01034 
01035   if ( ( vo = isAPropertyOf( vtodo, VCAAlarmProp ) ) ) {
01036     Alarm::Ptr alarm;
01037     VObject *a;
01038     VObject *b;
01039     a = isAPropertyOf( vo, VCRunTimeProp );
01040     b = isAPropertyOf( vo, VCAudioContentProp );
01041 
01042     if ( a || b ) {
01043       alarm = anEvent->newAlarm();
01044       if ( a ) {
01045         alarm->setTime( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( a ) ) ) );
01046         deleteStr( s );
01047       }
01048       alarm->setEnabled( true );
01049       if ( b ) {
01050         s = fakeCString( vObjectUStringZValue( b ) );
01051         alarm->setAudioAlarm( QFile::decodeName( s ) );
01052         deleteStr( s );
01053       } else {
01054         alarm->setAudioAlarm( QString() );
01055       }
01056     }
01057   }
01058 
01059   if ( ( vo = isAPropertyOf( vtodo, VCPAlarmProp ) ) ) {
01060     Alarm::Ptr alarm;
01061     VObject *a;
01062     VObject *b;
01063     a = isAPropertyOf( vo, VCRunTimeProp );
01064     b = isAPropertyOf( vo, VCProcedureNameProp );
01065 
01066     if ( a || b ) {
01067       alarm = anEvent->newAlarm();
01068       if ( a ) {
01069         alarm->setTime( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( a ) ) ) );
01070         deleteStr( s );
01071       }
01072       alarm->setEnabled( true );
01073 
01074       if ( b ) {
01075         s = fakeCString( vObjectUStringZValue( b ) );
01076         alarm->setProcedureAlarm( QFile::decodeName( s ) );
01077         deleteStr( s );
01078       } else {
01079         alarm->setProcedureAlarm( QString() );
01080       }
01081     }
01082   }
01083 
01084   // related todo
01085   if ( ( vo = isAPropertyOf( vtodo, VCRelatedToProp ) ) != 0 ) {
01086     anEvent->setRelatedTo( s = fakeCString( vObjectUStringZValue( vo ) ) );
01087     deleteStr( s );
01088     d->mTodosRelate.append( anEvent );
01089   }
01090 
01091   // secrecy
01092   Incidence::Secrecy secrecy = Incidence::SecrecyPublic;
01093   if ( ( vo = isAPropertyOf( vtodo, VCClassProp ) ) != 0 ) {
01094     s = fakeCString( vObjectUStringZValue( vo ) );
01095     if ( s && strcmp( s, "PRIVATE" ) == 0 ) {
01096       secrecy = Incidence::SecrecyPrivate;
01097     } else if ( s && strcmp( s, "CONFIDENTIAL" ) == 0 ) {
01098       secrecy = Incidence::SecrecyConfidential;
01099     }
01100     deleteStr( s );
01101   }
01102   anEvent->setSecrecy( secrecy );
01103 
01104   // categories
01105   if ( ( vo = isAPropertyOf( vtodo, VCCategoriesProp ) ) != 0 ) {
01106     s = fakeCString( vObjectUStringZValue( vo ) );
01107     QString categories = QString::fromLocal8Bit( s );
01108     deleteStr( s );
01109     QStringList tmpStrList = categories.split( ';' );
01110     anEvent->setCategories( tmpStrList );
01111   }
01112 
01113   /* PILOT SYNC STUFF */
01114   if ( ( vo = isAPropertyOf( vtodo, KPilotIdProp ) ) ) {
01115     anEvent->setNonKDECustomProperty(
01116       KPilotIdProp, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
01117     deleteStr( s );
01118     if ( ( vo = isAPropertyOf( vtodo, KPilotStatusProp ) ) ) {
01119       anEvent->setNonKDECustomProperty(
01120         KPilotStatusProp, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
01121       deleteStr( s );
01122     } else {
01123       anEvent->setNonKDECustomProperty( KPilotStatusProp, QString::number( int( SYNCMOD ) ) );
01124     }
01125   }
01126 
01127   return anEvent;
01128 }
01129 
01130 Event::Ptr VCalFormat::VEventToEvent( VObject *vevent )
01131 {
01132   VObject *vo;
01133   VObjectIterator voi;
01134   char *s;
01135 
01136   Event::Ptr anEvent( new Event );
01137 
01138   // creation date
01139   if ( ( vo = isAPropertyOf( vevent, VCDCreatedProp ) ) != 0 ) {
01140     anEvent->setCreated( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
01141     deleteStr( s );
01142   }
01143 
01144   // unique id
01145   vo = isAPropertyOf( vevent, VCUniqueStringProp );
01146   // while the UID property is preferred, it is not required.  We'll use the
01147   // default Event UID if none is given.
01148   if ( vo ) {
01149     anEvent->setUid( s = fakeCString( vObjectUStringZValue( vo ) ) );
01150     deleteStr( s );
01151   }
01152 
01153 #if defined(KCALCORE_FOR_SYMBIAN)
01154   // recurrence id
01155   vo = isAPropertyOf( vevent, VCRecurrenceIdProp );
01156   if ( vo ) {
01157     anEvent->setRecurrenceId( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
01158     deleteStr( s );
01159   }
01160 #endif
01161 
01162   // revision
01163   // again NSCAL doesn't give us much to work with, so we improvise...
01164   anEvent->setRevision( 0 );
01165   if ( ( vo = isAPropertyOf( vevent, VCSequenceProp ) ) != 0 ) {
01166     s = fakeCString( vObjectUStringZValue( vo ) );
01167     if ( s ) {
01168       anEvent->setRevision( atoi( s ) );
01169       deleteStr( s );
01170     }
01171   }
01172 
01173   // last modification date
01174   if ( ( vo = isAPropertyOf( vevent, VCLastModifiedProp ) ) != 0 ) {
01175     anEvent->setLastModified( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
01176     deleteStr( s );
01177   } else {
01178     anEvent->setLastModified( KDateTime::currentUtcDateTime() );
01179   }
01180 
01181   // organizer
01182   // if our extension property for the event's ORGANIZER exists, add it.
01183   if ( ( vo = isAPropertyOf( vevent, ICOrganizerProp ) ) != 0 ) {
01184     // FIXME:  Also use the full name, not just the email address
01185     anEvent->setOrganizer( s = fakeCString( vObjectUStringZValue( vo ) ) );
01186     deleteStr( s );
01187   } else {
01188     if ( d->mCalendar->owner()->name() != "Unknown Name" ) {
01189       anEvent->setOrganizer( d->mCalendar->owner() );
01190     }
01191   }
01192 
01193   // deal with attendees.
01194   initPropIterator( &voi, vevent );
01195   while ( moreIteration( &voi ) ) {
01196     vo = nextVObject( &voi );
01197     if ( strcmp( vObjectName( vo ), VCAttendeeProp ) == 0 ) {
01198       Attendee::Ptr a;
01199       VObject *vp;
01200       s = fakeCString( vObjectUStringZValue( vo ) );
01201       QString tmpStr = QString::fromLocal8Bit( s );
01202       deleteStr( s );
01203       tmpStr = tmpStr.simplified();
01204       int emailPos1, emailPos2;
01205       if ( ( emailPos1 = tmpStr.indexOf( '<' ) ) > 0 ) {
01206         // both email address and name
01207         emailPos2 = tmpStr.lastIndexOf( '>' );
01208         a = Attendee::Ptr( new Attendee( tmpStr.left( emailPos1 - 1 ),
01209                                          tmpStr.mid( emailPos1 + 1,
01210                                          emailPos2 - ( emailPos1 + 1 ) ) ) );
01211       } else if ( tmpStr.indexOf( '@' ) > 0 ) {
01212         // just an email address
01213         a = Attendee::Ptr( new Attendee( 0, tmpStr ) );
01214       } else {
01215         // just a name
01216         QString email = tmpStr.replace( ' ', '.' );
01217         a = Attendee::Ptr( new Attendee( tmpStr, email ) );
01218       }
01219 
01220       // is there an RSVP property?
01221       if ( ( vp = isAPropertyOf( vo, VCRSVPProp ) ) != 0 ) {
01222         a->setRSVP( vObjectStringZValue( vp ) );
01223       }
01224       // is there a status property?
01225       if ( ( vp = isAPropertyOf( vo, VCStatusProp ) ) != 0 ) {
01226         a->setStatus( readStatus( vObjectStringZValue( vp ) ) );
01227       }
01228       // add the attendee
01229       anEvent->addAttendee( a );
01230     }
01231   }
01232 
01233   // This isn't strictly true.  An event that doesn't have a start time
01234   // or an end time isn't all-day, it has an anchor in time but it doesn't
01235   // "take up" any time.
01236   /*if ((isAPropertyOf(vevent, VCDTstartProp) == 0) ||
01237       (isAPropertyOf(vevent, VCDTendProp) == 0)) {
01238     anEvent->setAllDay(true);
01239     } else {
01240     }*/
01241 
01242   anEvent->setAllDay( false );
01243 
01244   // start time
01245   if ( ( vo = isAPropertyOf( vevent, VCDTstartProp ) ) != 0 ) {
01246     anEvent->setDtStart( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
01247     deleteStr( s );
01248 
01249     if ( anEvent->dtStart().time().hour() == 0 &&
01250          anEvent->dtStart().time().minute() == 0 &&
01251          anEvent->dtStart().time().second() == 0 ) {
01252       anEvent->setAllDay( true );
01253     }
01254   }
01255 
01256   // stop time
01257   if ( ( vo = isAPropertyOf( vevent, VCDTendProp ) ) != 0 ) {
01258     anEvent->setDtEnd( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
01259     deleteStr( s );
01260 
01261     if ( anEvent->dtEnd().time().hour() == 0 &&
01262          anEvent->dtEnd().time().minute() == 0 &&
01263          anEvent->dtEnd().time().second() == 0 ) {
01264       anEvent->setAllDay( true );
01265     }
01266   }
01267 #if defined(KCALCORE_FOR_MEEGO)
01268   if ( anEvent->allDay() ) {
01269     if ( anEvent->dtEnd() == anEvent->dtStart() ) {
01270       anEvent->setDtEnd( anEvent->dtEnd().addDays( 1 ) );
01271     }
01272   }
01273 #endif
01274 
01275   // at this point, there should be at least a start or end time.
01276   // fix up for events that take up no time but have a time associated
01277   if ( !isAPropertyOf( vevent, VCDTstartProp ) ) {
01278     anEvent->setDtStart( anEvent->dtEnd() );
01279   }
01280   if ( ! isAPropertyOf( vevent, VCDTendProp ) ) {
01281     anEvent->setDtEnd( anEvent->dtStart() );
01282   }
01283 
01285 
01286   // repeat stuff
01287   if ( ( vo = isAPropertyOf( vevent, VCRRuleProp ) ) != 0 ) {
01288     QString tmpStr = ( s = fakeCString( vObjectUStringZValue( vo ) ) );
01289     deleteStr( s );
01290     tmpStr.simplified();
01291     tmpStr = tmpStr.toUpper();
01292     // first, read the type of the recurrence
01293     int typelen = 1;
01294     uint type = Recurrence::rNone;
01295     if ( tmpStr.left(1) == "D" ) {
01296       type = Recurrence::rDaily;
01297     } else if ( tmpStr.left(1) == "W" ) {
01298       type = Recurrence::rWeekly;
01299     } else {
01300       typelen = 2;
01301       if ( tmpStr.left(2) == "MP" ) {
01302         type = Recurrence::rMonthlyPos;
01303       } else if ( tmpStr.left(2) == "MD" ) {
01304         type = Recurrence::rMonthlyDay;
01305       } else if ( tmpStr.left(2) == "YM" ) {
01306         type = Recurrence::rYearlyMonth;
01307       } else if ( tmpStr.left(2) == "YD" ) {
01308         type = Recurrence::rYearlyDay;
01309       }
01310     }
01311 
01312     if ( type != Recurrence::rNone ) {
01313 
01314       // Immediately after the type is the frequency
01315       int index = tmpStr.indexOf( ' ' );
01316       int last = tmpStr.lastIndexOf( ' ' ) + 1; // find last entry
01317       int rFreq = tmpStr.mid( typelen, ( index - 1 ) ).toInt();
01318       ++index; // advance to beginning of stuff after freq
01319 
01320       // Read the type-specific settings
01321       switch ( type ) {
01322       case Recurrence::rDaily:
01323         anEvent->recurrence()->setDaily(rFreq);
01324         break;
01325 
01326       case Recurrence::rWeekly:
01327       {
01328         QBitArray qba(7);
01329         QString dayStr;
01330         if ( index == last ) {
01331           // e.g. W1 #0
01332           qba.setBit( anEvent->dtStart().date().dayOfWeek() - 1 );
01333         } else {
01334           // e.g. W1 SU #0
01335           while ( index < last ) {
01336             dayStr = tmpStr.mid( index, 3 );
01337             int dayNum = numFromDay( dayStr );
01338             if ( dayNum >= 0 ) {
01339               qba.setBit( dayNum );
01340             }
01341             index += 3; // advance to next day, or possibly "#"
01342           }
01343         }
01344         anEvent->recurrence()->setWeekly( rFreq, qba );
01345         break;
01346       }
01347 
01348       case Recurrence::rMonthlyPos:
01349       {
01350         anEvent->recurrence()->setMonthly( rFreq );
01351 
01352         QBitArray qba(7);
01353         short tmpPos;
01354         if ( index == last ) {
01355           // e.g. MP1 #0
01356           tmpPos = anEvent->dtStart().date().day() / 7 + 1;
01357           if ( tmpPos == 5 ) {
01358             tmpPos = -1;
01359           }
01360           qba.setBit( anEvent->dtStart().date().dayOfWeek() - 1 );
01361           anEvent->recurrence()->addMonthlyPos( tmpPos, qba );
01362         } else {
01363           // e.g. MP1 1+ SU #0
01364           while ( index < last ) {
01365             tmpPos = tmpStr.mid( index, 1 ).toShort();
01366             index += 1;
01367             if ( tmpStr.mid( index, 1 ) == "-" ) {
01368               // convert tmpPos to negative
01369               tmpPos = 0 - tmpPos;
01370             }
01371             index += 2; // advance to day(s)
01372             while ( numFromDay( tmpStr.mid( index, 3 ) ) >= 0 ) {
01373               int dayNum = numFromDay( tmpStr.mid( index, 3 ) );
01374               qba.setBit( dayNum );
01375               index += 3; // advance to next day, or possibly pos or "#"
01376             }
01377             anEvent->recurrence()->addMonthlyPos( tmpPos, qba );
01378             qba.detach();
01379             qba.fill( false ); // clear out
01380           } // while != "#"
01381         }
01382         break;
01383       }
01384 
01385       case Recurrence::rMonthlyDay:
01386         anEvent->recurrence()->setMonthly( rFreq );
01387         if( index == last ) {
01388           // e.g. MD1 #0
01389           short tmpDay = anEvent->dtStart().date().day();
01390           anEvent->recurrence()->addMonthlyDate( tmpDay );
01391         } else {
01392           // e.g. MD1 3 #0
01393           while ( index < last ) {
01394             int index2 = tmpStr.indexOf( ' ', index );
01395             if ( ( tmpStr.mid( ( index2 - 1 ), 1 ) == "-" ) ||
01396                  ( tmpStr.mid( ( index2 - 1 ), 1 ) == "+" ) ) {
01397               index2 = index2 - 1;
01398             }
01399             short tmpDay = tmpStr.mid( index, ( index2 - index ) ).toShort();
01400             index = index2;
01401             if ( tmpStr.mid( index, 1 ) == "-" ) {
01402               tmpDay = 0 - tmpDay;
01403             }
01404             index += 2; // advance the index;
01405             anEvent->recurrence()->addMonthlyDate( tmpDay );
01406           } // while != #
01407         }
01408         break;
01409 
01410       case Recurrence::rYearlyMonth:
01411         anEvent->recurrence()->setYearly( rFreq );
01412 
01413         if ( index == last ) {
01414           // e.g. YM1 #0
01415           short tmpMonth = anEvent->dtStart().date().month();
01416           anEvent->recurrence()->addYearlyMonth( tmpMonth );
01417         } else {
01418           // e.g. YM1 3 #0
01419           while ( index < last ) {
01420             int index2 = tmpStr.indexOf( ' ', index );
01421             short tmpMonth = tmpStr.mid( index, ( index2 - index ) ).toShort();
01422             index = index2 + 1;
01423             anEvent->recurrence()->addYearlyMonth( tmpMonth );
01424           } // while != #
01425         }
01426         break;
01427 
01428       case Recurrence::rYearlyDay:
01429         anEvent->recurrence()->setYearly( rFreq );
01430 
01431         if ( index == last ) {
01432           // e.g. YD1 #0
01433           short tmpDay = anEvent->dtStart().date().dayOfYear();
01434           anEvent->recurrence()->addYearlyDay( tmpDay );
01435         } else {
01436           // e.g. YD1 123 #0
01437           while ( index < last ) {
01438             int index2 = tmpStr.indexOf( ' ', index );
01439             short tmpDay = tmpStr.mid( index, ( index2 - index ) ).toShort();
01440             index = index2 + 1;
01441             anEvent->recurrence()->addYearlyDay( tmpDay );
01442           } // while != #
01443         }
01444         break;
01445 
01446       default:
01447         break;
01448       }
01449 
01450       // find the last field, which is either the duration or the end date
01451       index = last;
01452       if ( tmpStr.mid( index, 1 ) == "#" ) {
01453         // Nr of occurrences
01454         index++;
01455         int rDuration = tmpStr.mid( index, tmpStr.length() - index ).toInt();
01456         if ( rDuration > 0 ) {
01457           anEvent->recurrence()->setDuration( rDuration );
01458         }
01459       } else if ( tmpStr.indexOf( 'T', index ) != -1 ) {
01460         KDateTime rEndDate = ISOToKDateTime( tmpStr.mid( index, tmpStr.length() - index ) );
01461         anEvent->recurrence()->setEndDateTime( rEndDate );
01462       }
01463 // anEvent->recurrence()->dump();
01464 
01465     } else {
01466       kDebug() << "we don't understand this type of recurrence!";
01467     } // if known recurrence type
01468   } // repeats
01469 
01470   // recurrence exceptions
01471   if ( ( vo = isAPropertyOf( vevent, VCExpDateProp ) ) != 0 ) {
01472     s = fakeCString( vObjectUStringZValue( vo ) );
01473     QStringList exDates = QString::fromLocal8Bit( s ).split( ',' );
01474     QStringList::ConstIterator it;
01475     for ( it = exDates.constBegin(); it != exDates.constEnd(); ++it ) {
01476       KDateTime exDate = ISOToKDateTime(*it);
01477       if ( exDate.time().hour() == 0 &&
01478            exDate.time().minute() == 0 &&
01479            exDate.time().second() == 0 ) {
01480         anEvent->recurrence()->addExDate( ISOToQDate( *it ) );
01481       } else {
01482         anEvent->recurrence()->addExDateTime( exDate );
01483       }
01484     }
01485     deleteStr( s );
01486   }
01487 
01488   // summary
01489   if ( ( vo = isAPropertyOf( vevent, VCSummaryProp ) ) ) {
01490     s = fakeCString( vObjectUStringZValue( vo ) );
01491     anEvent->setSummary( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
01492     deleteStr( s );
01493   }
01494 
01495   // description
01496   if ( ( vo = isAPropertyOf( vevent, VCDescriptionProp ) ) != 0 ) {
01497     s = fakeCString( vObjectUStringZValue( vo ) );
01498     bool isRich = Qt::mightBeRichText( s );
01499     if ( !anEvent->description().isEmpty() ) {
01500       anEvent->setDescription(
01501         anEvent->description() + '\n' + QString::fromLocal8Bit( s ), isRich );
01502     } else {
01503       anEvent->setDescription( QString::fromLocal8Bit( s ), isRich );
01504     }
01505     deleteStr( s );
01506   }
01507 
01508   // location
01509   if ( ( vo = isAPropertyOf( vevent, VCLocationProp ) ) != 0 ) {
01510     s = fakeCString( vObjectUStringZValue( vo ) );
01511     anEvent->setLocation( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
01512     deleteStr( s );
01513   }
01514 
01515   // some stupid vCal exporters ignore the standard and use Description
01516   // instead of Summary for the default field.  Correct for this.
01517   if ( anEvent->summary().isEmpty() && !( anEvent->description().isEmpty() ) ) {
01518     QString tmpStr = anEvent->description().simplified();
01519     anEvent->setDescription( "" );
01520     anEvent->setSummary( tmpStr );
01521   }
01522 
01523 #if 0
01524   // status
01525   if ( ( vo = isAPropertyOf( vevent, VCStatusProp ) ) != 0 ) {
01526     QString tmpStr( s = fakeCString( vObjectUStringZValue( vo ) ) );
01527     deleteStr( s );
01528 // TODO: Define Event status
01529 //    anEvent->setStatus( tmpStr );
01530   } else {
01531 //    anEvent->setStatus( "NEEDS ACTION" );
01532   }
01533 #endif
01534 
01535   // secrecy
01536   Incidence::Secrecy secrecy = Incidence::SecrecyPublic;
01537   if ( ( vo = isAPropertyOf( vevent, VCClassProp ) ) != 0 ) {
01538     s = fakeCString( vObjectUStringZValue( vo ) );
01539     if ( s && strcmp( s, "PRIVATE" ) == 0 ) {
01540       secrecy = Incidence::SecrecyPrivate;
01541     } else if ( s && strcmp( s, "CONFIDENTIAL" ) == 0 ) {
01542       secrecy = Incidence::SecrecyConfidential;
01543     }
01544     deleteStr( s );
01545   }
01546   anEvent->setSecrecy( secrecy );
01547 
01548   // categories
01549   if ( ( vo = isAPropertyOf( vevent, VCCategoriesProp ) ) != 0 ) {
01550     s = fakeCString( vObjectUStringZValue( vo ) );
01551     QString categories = QString::fromLocal8Bit( s );
01552     deleteStr( s );
01553     QStringList tmpStrList = categories.split( ',' );
01554     anEvent->setCategories( tmpStrList );
01555   }
01556 
01557   // attachments
01558   initPropIterator( &voi, vevent );
01559   while ( moreIteration( &voi ) ) {
01560     vo = nextVObject( &voi );
01561     if ( strcmp( vObjectName( vo ), VCAttachProp ) == 0 ) {
01562       s = fakeCString( vObjectUStringZValue( vo ) );
01563       anEvent->addAttachment( Attachment::Ptr( new Attachment( QString( s ) ) ) );
01564       deleteStr( s );
01565     }
01566   }
01567 
01568   // resources
01569   if ( ( vo = isAPropertyOf( vevent, VCResourcesProp ) ) != 0 ) {
01570     QString resources = ( s = fakeCString( vObjectUStringZValue( vo ) ) );
01571     deleteStr( s );
01572     QStringList tmpStrList = resources.split( ';' );
01573     anEvent->setResources( tmpStrList );
01574   }
01575 
01576   // alarm stuff
01577   if ( ( vo = isAPropertyOf( vevent, VCDAlarmProp ) ) ) {
01578     Alarm::Ptr alarm;
01579     VObject *a;
01580     VObject *b;
01581     a = isAPropertyOf( vo, VCRunTimeProp );
01582     b = isAPropertyOf( vo, VCDisplayStringProp );
01583 
01584     if ( a || b ) {
01585       alarm = anEvent->newAlarm();
01586       if ( a ) {
01587         alarm->setTime( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( a ) ) ) );
01588         deleteStr( s );
01589       }
01590       alarm->setEnabled( true );
01591 
01592       if ( b ) {
01593         s = fakeCString( vObjectUStringZValue( b ) );
01594         alarm->setDisplayAlarm( QString( s ) );
01595         deleteStr( s );
01596       } else {
01597         alarm->setDisplayAlarm( QString() );
01598       }
01599     }
01600   }
01601 
01602   if ( ( vo = isAPropertyOf( vevent, VCAAlarmProp ) ) ) {
01603     Alarm::Ptr alarm;
01604     VObject *a;
01605     VObject *b;
01606     a = isAPropertyOf( vo, VCRunTimeProp );
01607     b = isAPropertyOf( vo, VCAudioContentProp );
01608 
01609     if ( a || b ) {
01610       alarm = anEvent->newAlarm();
01611       if ( a ) {
01612         alarm->setTime( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( a ) ) ) );
01613         deleteStr( s );
01614       }
01615       alarm->setEnabled( true );
01616 
01617       if ( b ) {
01618         s = fakeCString( vObjectUStringZValue( b ) );
01619         alarm->setAudioAlarm( QFile::decodeName( s ) );
01620         deleteStr( s );
01621       } else {
01622         alarm->setAudioAlarm( QString() );
01623       }
01624     }
01625   }
01626 
01627   if ( ( vo = isAPropertyOf( vevent, VCPAlarmProp ) ) ) {
01628     Alarm::Ptr alarm;
01629     VObject *a;
01630     VObject *b;
01631     a = isAPropertyOf( vo, VCRunTimeProp );
01632     b = isAPropertyOf( vo, VCProcedureNameProp );
01633 
01634     if ( a || b ) {
01635       alarm = anEvent->newAlarm();
01636       if ( a ) {
01637         alarm->setTime( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( a ) ) ) );
01638         deleteStr( s );
01639       }
01640       alarm->setEnabled( true );
01641 
01642       if ( b ) {
01643         s = fakeCString( vObjectUStringZValue( b ) );
01644         alarm->setProcedureAlarm( QFile::decodeName( s ) );
01645         deleteStr( s );
01646       } else {
01647         alarm->setProcedureAlarm( QString() );
01648       }
01649     }
01650   }
01651 
01652   // priority
01653   if ( ( vo = isAPropertyOf( vevent, VCPriorityProp ) ) ) {
01654     s = fakeCString( vObjectUStringZValue( vo ) );
01655     if ( s ) {
01656       anEvent->setPriority( atoi( s ) );
01657       deleteStr( s );
01658     }
01659   }
01660 
01661   // transparency
01662   if ( ( vo = isAPropertyOf( vevent, VCTranspProp ) ) != 0 ) {
01663     s = fakeCString( vObjectUStringZValue( vo ) );
01664     if ( s ) {
01665       int i = atoi( s );
01666       anEvent->setTransparency( i == 1 ? Event::Transparent : Event::Opaque );
01667       deleteStr( s );
01668     }
01669   }
01670 
01671   // related event
01672   if ( ( vo = isAPropertyOf( vevent, VCRelatedToProp ) ) != 0 ) {
01673     anEvent->setRelatedTo( s = fakeCString( vObjectUStringZValue( vo ) ) );
01674     deleteStr( s );
01675     d->mEventsRelate.append( anEvent );
01676   }
01677 
01678   /* PILOT SYNC STUFF */
01679   if ( ( vo = isAPropertyOf( vevent, KPilotIdProp ) ) ) {
01680     anEvent->setNonKDECustomProperty(
01681       KPilotIdProp, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
01682     deleteStr( s );
01683     if ( ( vo = isAPropertyOf( vevent, KPilotStatusProp ) ) ) {
01684       anEvent->setNonKDECustomProperty(
01685         KPilotStatusProp, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
01686       deleteStr( s );
01687     } else {
01688       anEvent->setNonKDECustomProperty( KPilotStatusProp, QString::number( int( SYNCMOD ) ) );
01689     }
01690   }
01691 
01692   /* Rest of the custom properties */
01693   readCustomProperties( vevent, anEvent );
01694 
01695   return anEvent;
01696 }
01697 
01698 QString VCalFormat::parseTZ( const QByteArray &timezone ) const
01699 {
01700   QString pZone = timezone.mid( timezone.indexOf( "TZID:VCAL" ) + 9 );
01701   return pZone.mid( 0, pZone.indexOf(QChar( QLatin1Char( '\n' ) ) ) );
01702 }
01703 
01704 QString VCalFormat::parseDst( QByteArray &timezone ) const
01705 {
01706   if ( !timezone.contains( "BEGIN:DAYLIGHT" ) ) {
01707     return QString();
01708   }
01709 
01710   timezone = timezone.mid( timezone.indexOf( "BEGIN:DAYLIGHT" ) );
01711   timezone = timezone.mid( timezone.indexOf( "TZNAME:" ) + 7 );
01712   QString sStart = timezone.mid( 0, ( timezone.indexOf( "COMMENT:" ) ) );
01713   sStart.chop( 2 );
01714   timezone = timezone.mid( timezone.indexOf( "TZOFFSETTO:" ) + 11 );
01715   QString sOffset = timezone.mid( 0, ( timezone.indexOf( "DTSTART:" ) ) );
01716   sOffset.chop( 2 );
01717   sOffset.insert( 3, QString( ":" ) );
01718   timezone = timezone.mid( timezone.indexOf( "TZNAME:" ) + 7 );
01719   QString sEnd = timezone.mid( 0, ( timezone.indexOf( "COMMENT:" ) ) );
01720   sEnd.chop( 2 );
01721 
01722   return "TRUE;" + sOffset + ';' + sStart + ';' + sEnd + ";;";
01723 }
01724 
01725 QString VCalFormat::qDateToISO( const QDate &qd )
01726 {
01727   QString tmpStr;
01728 
01729   if ( !qd.isValid() ) {
01730     return QString();
01731   }
01732 
01733   tmpStr.sprintf( "%.2d%.2d%.2d", qd.year(), qd.month(), qd.day() );
01734   return tmpStr;
01735 
01736 }
01737 
01738 QString VCalFormat::kDateTimeToISO( const KDateTime &dt, bool zulu )
01739 {
01740   QString tmpStr;
01741 
01742   if ( !dt.isValid() ) {
01743     return QString();
01744   }
01745 
01746   QDateTime tmpDT;
01747   if ( zulu ) {
01748     tmpDT = dt.toUtc().dateTime();
01749   } else {
01750 #if !defined(KCALCORE_FOR_MEEGO)
01751     tmpDT = dt.toTimeSpec( d->mCalendar->timeSpec() ).dateTime();
01752 #else
01753     tmpDT = dt.dateTime();
01754 #endif
01755   }
01756   tmpStr.sprintf( "%.2d%.2d%.2dT%.2d%.2d%.2d",
01757                   tmpDT.date().year(), tmpDT.date().month(),
01758                   tmpDT.date().day(), tmpDT.time().hour(),
01759                   tmpDT.time().minute(), tmpDT.time().second() );
01760   if ( zulu || dt.isUtc() ) {
01761     tmpStr += 'Z';
01762   }
01763   return tmpStr;
01764 }
01765 
01766 KDateTime VCalFormat::ISOToKDateTime( const QString &dtStr )
01767 {
01768   QDate tmpDate;
01769   QTime tmpTime;
01770   QString tmpStr;
01771   int year, month, day, hour, minute, second;
01772 
01773   tmpStr = dtStr;
01774   year = tmpStr.left( 4 ).toInt();
01775   month = tmpStr.mid( 4, 2 ).toInt();
01776   day = tmpStr.mid( 6, 2 ).toInt();
01777   hour = tmpStr.mid( 9, 2 ).toInt();
01778   minute = tmpStr.mid( 11, 2 ).toInt();
01779   second = tmpStr.mid( 13, 2 ).toInt();
01780   tmpDate.setYMD( year, month, day );
01781   tmpTime.setHMS( hour, minute, second );
01782 
01783   if ( tmpDate.isValid() && tmpTime.isValid() ) {
01784     // correct for GMT if string is in Zulu format
01785     if ( dtStr.at( dtStr.length() - 1 ) == 'Z' ) {
01786       return KDateTime( tmpDate, tmpTime, KDateTime::UTC );
01787     } else {
01788       return KDateTime( tmpDate, tmpTime, d->mCalendar->timeSpec() );
01789     }
01790   } else {
01791     return KDateTime();
01792   }
01793 }
01794 
01795 QDate VCalFormat::ISOToQDate( const QString &dateStr )
01796 {
01797   int year, month, day;
01798 
01799   year = dateStr.left( 4 ).toInt();
01800   month = dateStr.mid( 4, 2 ).toInt();
01801   day = dateStr.mid( 6, 2 ).toInt();
01802 
01803   return QDate( year, month, day );
01804 }
01805 
01806 bool VCalFormat::parseTZOffsetISO8601( const QString &s, int &result )
01807 {
01808   // ISO8601 format(s):
01809   // +- hh : mm
01810   // +- hh mm
01811   // +- hh
01812 
01813   // We also accept broken one without +
01814   int mod = 1;
01815   int v = 0;
01816   QString str = s.trimmed();
01817   int ofs = 0;
01818   result = 0;
01819 
01820   // Check for end
01821   if ( str.size() <= ofs ) {
01822     return false;
01823   }
01824   if ( str[ofs] == '-' ) {
01825     mod = -1;
01826     ofs++;
01827   } else if ( str[ofs] == '+' ) {
01828     ofs++;
01829   }
01830   if ( str.size() <= ofs ) {
01831     return false;
01832   }
01833 
01834   // Make sure next two values are numbers
01835   bool ok;
01836 
01837   if ( str.size() < ( ofs + 2 ) ) {
01838     return false;
01839   }
01840 
01841   v = str.mid( ofs, 2 ).toInt( &ok ) * 60;
01842   if ( !ok ) {
01843     return false;
01844   }
01845   ofs += 2;
01846 
01847   if ( str.size() > ofs ) {
01848     if ( str[ofs] == ':' ) {
01849       ofs++;
01850     }
01851     if ( str.size() > ofs ) {
01852       if ( str.size() < ( ofs + 2 ) ) {
01853         return false;
01854       }
01855       v += str.mid( ofs, 2 ).toInt( &ok );
01856       if ( !ok ) {
01857         return false;
01858       }
01859     }
01860   }
01861   result = v * mod * 60;
01862   return true;
01863 }
01864 
01865 // take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc.
01866 // and break it down from it's tree-like format into the dictionary format
01867 // that is used internally in the VCalFormat.
01868 void VCalFormat::populate( VObject *vcal, bool deleted, const QString &notebook )
01869 {
01870   Q_UNUSED( notebook );
01871   // this function will populate the caldict dictionary and other event
01872   // lists. It turns vevents into Events and then inserts them.
01873 
01874   VObjectIterator i;
01875   VObject *curVO, *curVOProp;
01876   Event::Ptr anEvent;
01877   bool hasTimeZone = false; //The calendar came with a TZ and not UTC
01878   KDateTime::Spec previousSpec; //If we add a new TZ we should leave the spec as it was before
01879 
01880   if ( ( curVO = isAPropertyOf( vcal, ICMethodProp ) ) != 0 ) {
01881     char *methodType = 0;
01882     methodType = fakeCString( vObjectUStringZValue( curVO ) );
01883     kDebug() << "This calendar is an iTIP transaction of type '"
01884              << methodType << "'";
01885     deleteStr( methodType );
01886   }
01887 
01888   // warn the user that we might have trouble reading non-known calendar.
01889   if ( ( curVO = isAPropertyOf( vcal, VCProdIdProp ) ) != 0 ) {
01890     char *s = fakeCString( vObjectUStringZValue( curVO ) );
01891     if ( !s || strcmp( productId().toLocal8Bit(), s ) != 0 ) {
01892       kDebug() << "This vCalendar file was not created by KOrganizer or"
01893                << "any other product we support. Loading anyway...";
01894     }
01895     setLoadedProductId( s );
01896     deleteStr( s );
01897   }
01898 
01899   // warn the user we might have trouble reading this unknown version.
01900   if ( ( curVO = isAPropertyOf( vcal, VCVersionProp ) ) != 0 ) {
01901     char *s = fakeCString( vObjectUStringZValue( curVO ) );
01902     if ( !s || strcmp( _VCAL_VERSION, s ) != 0 ) {
01903       kDebug() << "This vCalendar file has version" << s
01904                << "We only support" << _VCAL_VERSION;
01905     }
01906     deleteStr( s );
01907   }
01908 
01909   // set the time zone (this is a property of the view, so just discard!)
01910   if ( ( curVO = isAPropertyOf( vcal, VCTimeZoneProp ) ) != 0 ) {
01911     char *s = fakeCString( vObjectUStringZValue( curVO ) );
01912     QString ts( s );
01913     QString name = "VCAL" + ts;
01914     deleteStr( s );
01915 
01916     // TODO: While using the timezone-offset + vcal as timezone is is
01917     // most likely unique, we should REALLY actually create something
01918     // like vcal-tzoffset-daylightoffsets, or better yet,
01919     // vcal-hash<the former>
01920 
01921     QStringList tzList;
01922     QString tz;
01923     int utcOffset;
01924     int utcOffsetDst;
01925     if ( parseTZOffsetISO8601( ts, utcOffset ) ) {
01926       kDebug() << "got standard offset" << ts << utcOffset;
01927       // standard from tz
01928       // starting date for now 01011900
01929       KDateTime dt = KDateTime( QDateTime( QDate( 1900, 1, 1 ), QTime( 0, 0, 0 ) ) );
01930       tz = QString( "STD;%1;false;%2" ).arg( QString::number( utcOffset ) ).arg( dt.toString() );
01931       tzList.append( tz );
01932 
01933       // go through all the daylight tags
01934       initPropIterator( &i, vcal );
01935       while ( moreIteration( &i ) ) {
01936         curVO = nextVObject( &i );
01937         if ( strcmp( vObjectName( curVO ), VCDayLightProp ) == 0 ) {
01938           char *s = fakeCString( vObjectUStringZValue( curVO ) );
01939           QString dst = QString( s );
01940           QStringList argl = dst.split( ',' );
01941           deleteStr( s );
01942 
01943           // Too short -> not interesting
01944           if ( argl.size() < 4 ) {
01945             continue;
01946           }
01947 
01948           // We don't care about the non-DST periods
01949           if ( argl[0] != "TRUE" ) {
01950             continue;
01951           }
01952 
01953           if ( parseTZOffsetISO8601( argl[1], utcOffsetDst ) ) {
01954 
01955             kDebug() << "got DST offset" << argl[1] << utcOffsetDst;
01956             // standard
01957             QString strDate = argl[3];
01958             KDateTime endDate = ISOToKDateTime( strDate );
01959             tz = QString( "%1;%2;false;%3" ).
01960                  arg( strDate ).
01961                  arg( QString::number( utcOffset ) ).
01962                  arg( endDate.toString() );
01963             tzList.append( tz );
01964 
01965             // daylight
01966             strDate = argl[2];
01967             KDateTime startDate = ISOToKDateTime( strDate );
01968             tz = QString( "%1;%2;true;%3" ).
01969                  arg( strDate ).
01970                  arg( QString::number( utcOffsetDst ) ).
01971                  arg( startDate.toString() );
01972             tzList.append( tz );
01973           } else {
01974             kDebug() << "unable to parse dst" << argl[1];
01975           }
01976         }
01977       }
01978       ICalTimeZones *tzlist = d->mCalendar->timeZones();
01979       ICalTimeZoneSource tzs;
01980       ICalTimeZone zone = tzs.parse( name, tzList, *tzlist );
01981       if ( !zone.isValid() ) {
01982         kDebug() << "zone is not valid, parsing error" << tzList;
01983       } else {
01984         previousSpec = d->mCalendar->timeSpec();
01985         d->mCalendar->setTimeZoneId( name );
01986         hasTimeZone = true;
01987       }
01988     } else {
01989       kDebug() << "unable to parse tzoffset" << ts;
01990     }
01991   }
01992 
01993   // Store all events with a relatedTo property in a list for post-processing
01994   d->mEventsRelate.clear();
01995   d->mTodosRelate.clear();
01996 
01997   initPropIterator( &i, vcal );
01998 
01999   // go through all the vobjects in the vcal
02000   while ( moreIteration( &i ) ) {
02001     curVO = nextVObject( &i );
02002 
02003     /************************************************************************/
02004 
02005     // now, check to see that the object is an event or todo.
02006     if ( strcmp( vObjectName( curVO ), VCEventProp ) == 0 ) {
02007 
02008       if ( ( curVOProp = isAPropertyOf( curVO, KPilotStatusProp ) ) != 0 ) {
02009         char *s;
02010         s = fakeCString( vObjectUStringZValue( curVOProp ) );
02011         // check to see if event was deleted by the kpilot conduit
02012         if ( s ) {
02013           if ( atoi( s ) == SYNCDEL ) {
02014             deleteStr( s );
02015             kDebug() << "skipping pilot-deleted event";
02016             goto SKIP;
02017           }
02018           deleteStr( s );
02019         }
02020       }
02021 
02022       if ( !isAPropertyOf( curVO, VCDTstartProp ) &&
02023            !isAPropertyOf( curVO, VCDTendProp ) ) {
02024         kDebug() << "found a VEvent with no DTSTART and no DTEND! Skipping...";
02025         goto SKIP;
02026       }
02027 
02028       anEvent = VEventToEvent( curVO );
02029       if ( anEvent ) {
02030         if ( hasTimeZone && !anEvent->allDay() && anEvent->dtStart().isUtc() ) {
02031           //This sounds stupid but is how others are doing it, so here
02032           //we go. If there is a TZ in the VCALENDAR even if the dtStart
02033           //and dtend are in UTC, clients interpret it using also the TZ defined
02034           //in the Calendar. I know it sounds braindead but oh well
02035           int utcOffSet = anEvent->dtStart().utcOffset();
02036           KDateTime dtStart( anEvent->dtStart().dateTime().addSecs( utcOffSet ),
02037                              d->mCalendar->timeSpec() );
02038           KDateTime dtEnd( anEvent->dtEnd().dateTime().addSecs( utcOffSet ),
02039                              d->mCalendar->timeSpec() );
02040           anEvent->setDtStart( dtStart );
02041           anEvent->setDtEnd( dtEnd );
02042         }
02043         Event::Ptr old = !anEvent->hasRecurrenceId() ?
02044       d->mCalendar->event( anEvent->uid() ) :
02045         d->mCalendar->event( anEvent->uid(), anEvent->recurrenceId() );
02046 
02047         if ( old ) {
02048           if ( deleted ) {
02049             d->mCalendar->deleteEvent( old ); // move old to deleted
02050             removeAllVCal( d->mEventsRelate, old );
02051           } else if ( anEvent->revision() > old->revision() ) {
02052             d->mCalendar->deleteEvent( old ); // move old to deleted
02053             removeAllVCal( d->mEventsRelate, old );
02054             d->mCalendar->addEvent( anEvent ); // and replace it with this one
02055           }
02056         } else if ( deleted ) {
02057           old = !anEvent->hasRecurrenceId() ?
02058           d->mCalendar->deletedEvent( anEvent->uid() ) :
02059             d->mCalendar->deletedEvent( anEvent->uid(), anEvent->recurrenceId() );
02060           if ( !old ) {
02061             d->mCalendar->addEvent( anEvent ); // add this one
02062             d->mCalendar->deleteEvent( anEvent ); // and move it to deleted
02063           }
02064         } else {
02065           d->mCalendar->addEvent( anEvent ); // just add this one
02066         }
02067       }
02068     } else if ( strcmp( vObjectName( curVO ), VCTodoProp ) == 0 ) {
02069       Todo::Ptr aTodo = VTodoToEvent( curVO );
02070       if ( aTodo ) {
02071         if ( hasTimeZone && !aTodo->allDay()  && aTodo->dtStart().isUtc() ) {
02072           //This sounds stupid but is how others are doing it, so here
02073           //we go. If there is a TZ in the VCALENDAR even if the dtStart
02074           //and dtend are in UTC, clients interpret it usint alse the TZ defined
02075           //in the Calendar. I know it sounds braindead but oh well
02076           int utcOffSet = aTodo->dtStart().utcOffset();
02077           KDateTime dtStart( aTodo->dtStart().dateTime().addSecs( utcOffSet ),
02078                             d->mCalendar->timeSpec() );
02079           aTodo->setDtStart( dtStart );
02080       if ( aTodo->hasDueDate() ) {
02081             KDateTime dtDue( aTodo->dtDue().dateTime().addSecs( utcOffSet ),
02082                             d->mCalendar->timeSpec() );
02083         aTodo->setDtDue( dtDue );
02084       }
02085         }
02086         Todo::Ptr old = !aTodo->hasRecurrenceId() ?
02087       d->mCalendar->todo( aTodo->uid() ) :
02088         d->mCalendar->todo( aTodo->uid(), aTodo->recurrenceId() );
02089         if ( old ) {
02090           if ( deleted ) {
02091             d->mCalendar->deleteTodo( old ); // move old to deleted
02092             removeAllVCal( d->mTodosRelate, old );
02093           } else if ( aTodo->revision() > old->revision() ) {
02094             d->mCalendar->deleteTodo( old ); // move old to deleted
02095             removeAllVCal( d->mTodosRelate, old );
02096             d->mCalendar->addTodo( aTodo ); // and replace it with this one
02097           }
02098         } else if ( deleted ) {
02099           old = d->mCalendar->deletedTodo( aTodo->uid(), aTodo->recurrenceId() );
02100           if ( !old ) {
02101             d->mCalendar->addTodo( aTodo ); // add this one
02102             d->mCalendar->deleteTodo( aTodo ); // and move it to deleted
02103           }
02104         } else {
02105           d->mCalendar->addTodo( aTodo ); // just add this one
02106         }
02107       }
02108     } else if ( ( strcmp( vObjectName( curVO ), VCVersionProp ) == 0 ) ||
02109                 ( strcmp( vObjectName( curVO ), VCProdIdProp ) == 0 ) ||
02110                 ( strcmp( vObjectName( curVO ), VCTimeZoneProp ) == 0 ) ) {
02111       // do nothing, we know these properties and we want to skip them.
02112       // we have either already processed them or are ignoring them.
02113       ;
02114     } else if ( strcmp( vObjectName( curVO ), VCDayLightProp ) == 0 ) {
02115       // do nothing daylights are already processed
02116       ;
02117     } else {
02118       kDebug() << "Ignoring unknown vObject \"" << vObjectName(curVO) << "\"";
02119     }
02120     SKIP:
02121       ;
02122   } // while
02123 
02124   // Post-Process list of events with relations, put Event objects in relation
02125   Event::List::ConstIterator eIt;
02126   for ( eIt = d->mEventsRelate.constBegin(); eIt != d->mEventsRelate.constEnd(); ++eIt ) {
02127     (*eIt)->setRelatedTo( (*eIt)->relatedTo() );
02128   }
02129   Todo::List::ConstIterator tIt;
02130   for ( tIt = d->mTodosRelate.constBegin(); tIt != d->mTodosRelate.constEnd(); ++tIt ) {
02131     (*tIt)->setRelatedTo( (*tIt)->relatedTo() );
02132    }
02133 
02134    //Now lets put the TZ back as it was if we have changed it.
02135   if ( hasTimeZone ) {
02136     d->mCalendar->setTimeSpec(previousSpec);
02137   }
02138 
02139 }
02140 
02141 const char *VCalFormat::dayFromNum( int day )
02142 {
02143   const char *days[7] = { "MO ", "TU ", "WE ", "TH ", "FR ", "SA ", "SU " };
02144 
02145   return days[day];
02146 }
02147 
02148 int VCalFormat::numFromDay( const QString &day )
02149 {
02150   if ( day == "MO " ) {
02151     return 0;
02152   }
02153   if ( day == "TU " ) {
02154     return 1;
02155   }
02156   if ( day == "WE " ) {
02157     return 2;
02158   }
02159   if ( day == "TH " ) {
02160     return 3;
02161   }
02162   if ( day == "FR " ) {
02163     return 4;
02164   }
02165   if ( day == "SA " ) {
02166     return 5;
02167   }
02168   if ( day == "SU " ) {
02169     return 6;
02170   }
02171 
02172   return -1; // something bad happened. :)
02173 }
02174 
02175 Attendee::PartStat VCalFormat::readStatus( const char *s ) const
02176 {
02177   QString statStr = s;
02178   statStr = statStr.toUpper();
02179   Attendee::PartStat status;
02180 
02181   if ( statStr == "X-ACTION" ) {
02182     status = Attendee::NeedsAction;
02183   } else if ( statStr == "NEEDS ACTION" ) {
02184     status = Attendee::NeedsAction;
02185   } else if ( statStr == "ACCEPTED" ) {
02186     status = Attendee::Accepted;
02187   } else if ( statStr == "SENT" ) {
02188     status = Attendee::NeedsAction;
02189   } else if ( statStr == "TENTATIVE" ) {
02190     status = Attendee::Tentative;
02191   } else if ( statStr == "CONFIRMED" ) {
02192     status = Attendee::Accepted;
02193   } else if ( statStr == "DECLINED" ) {
02194     status = Attendee::Declined;
02195   } else if ( statStr == "COMPLETED" ) {
02196     status = Attendee::Completed;
02197   } else if ( statStr == "DELEGATED" ) {
02198     status = Attendee::Delegated;
02199   } else {
02200     kDebug() << "error setting attendee mStatus, unknown mStatus!";
02201     status = Attendee::NeedsAction;
02202   }
02203 
02204   return status;
02205 }
02206 
02207 QByteArray VCalFormat::writeStatus( Attendee::PartStat status ) const
02208 {
02209   switch( status ) {
02210   default:
02211   case Attendee::NeedsAction:
02212     return "NEEDS ACTION";
02213     break;
02214   case Attendee::Accepted:
02215     return "ACCEPTED";
02216     break;
02217   case Attendee::Declined:
02218     return "DECLINED";
02219     break;
02220   case Attendee::Tentative:
02221     return "TENTATIVE";
02222     break;
02223   case Attendee::Delegated:
02224     return "DELEGATED";
02225     break;
02226   case Attendee::Completed:
02227     return "COMPLETED";
02228     break;
02229   case Attendee::InProcess:
02230     return "NEEDS ACTION";
02231     break;
02232   }
02233 }
02234 
02235 void VCalFormat::readCustomProperties( VObject *o, const Incidence::Ptr &i )
02236 {
02237   VObjectIterator iter;
02238   VObject *cur;
02239   const char *curname;
02240   char *s;
02241 
02242   initPropIterator( &iter, o );
02243   while ( moreIteration( &iter ) ) {
02244     cur = nextVObject( &iter );
02245     curname = vObjectName( cur );
02246     Q_ASSERT( curname );
02247     if ( ( curname[0] == 'X' && curname[1] == '-' ) &&
02248          strcmp( curname, ICOrganizerProp ) != 0 ) {
02249       // TODO - for the time being, we ignore the parameters part
02250       // and just do the value handling here
02251       i->setNonKDECustomProperty(
02252         curname, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( cur ) ) ) );
02253       deleteStr( s );
02254     }
02255   }
02256 }
02257 
02258 void VCalFormat::writeCustomProperties( VObject *o, const Incidence::Ptr &i )
02259 {
02260   const QMap<QByteArray, QString> custom = i->customProperties();
02261   for ( QMap<QByteArray, QString>::ConstIterator c = custom.begin();
02262         c != custom.end();  ++c ) {
02263     if ( d->mManuallyWrittenExtensionFields.contains( c.key() ) ) {
02264       continue;
02265     }
02266 
02267     addPropValue( o, c.key(), c.value().toLocal8Bit() );
02268   }
02269 }
02270 
02271 void VCalFormat::virtual_hook( int id, void *data )
02272 {
02273   Q_UNUSED( id );
02274   Q_UNUSED( data );
02275   Q_ASSERT( false );
02276 }

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