diff --git a/kcal/calendar.cpp b/kcal/calendar.cpp index f13d9e097..9a0ae194d 100644 --- a/kcal/calendar.cpp +++ b/kcal/calendar.cpp @@ -1,1188 +1,1198 @@ /* This file is part of the kcal library. Copyright (c) 1998 Preston Brown Copyright (c) 2000-2004 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (c) 2006 David Jarvie This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the Calendar class. @brief Represents the main calendar class. @author Preston Brown \ @author Cornelius Schumacher \ @author Reinhold Kainhofer \ @author David Jarvie \ */ #include "calendar.h" #include "exceptions.h" #include "calfilter.h" #include "icaltimezones.h" #include #include extern "C" { #include } using namespace KCal; /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class KCal::Calendar::Private { public: Private() : mTimeZones( new ICalTimeZones ), mModified( false ), mNewObserver( false ), mObserversEnabled( true ), mDefaultFilter( new CalFilter ) { // Setup default filter, which does nothing mFilter = mDefaultFilter; mFilter->setEnabled( false ); // user information... mOwner.setName( i18n( "Unknown Name" ) ); mOwner.setEmail( i18n( "unknown@nowhere" ) ); } ~Private() { delete mTimeZones; delete mDefaultFilter; } KDateTime::Spec timeZoneIdSpec( const QString &timeZoneId, bool view ); QString mProductId; Person mOwner; ICalTimeZones *mTimeZones; // collection of time zones used in this calendar ICalTimeZone mBuiltInTimeZone; // cached time zone lookup ICalTimeZone mBuiltInViewTimeZone; // cached viewing time zone lookup KDateTime::Spec mTimeSpec; mutable KDateTime::Spec mViewTimeSpec; bool mModified; bool mNewObserver; bool mObserversEnabled; QList mObservers; CalFilter *mDefaultFilter; CalFilter *mFilter; // These lists are used to put together related To-dos QMultiHash mOrphans; QMultiHash mOrphanUids; }; //@endcond Calendar::Calendar( const KDateTime::Spec &timeSpec ) : d( new KCal::Calendar::Private ) { d->mTimeSpec = timeSpec; d->mViewTimeSpec = timeSpec; } Calendar::Calendar( const QString &timeZoneId ) : d( new KCal::Calendar::Private ) { setTimeZoneId( timeZoneId ); } Calendar::~Calendar() { delete d; } Person Calendar::owner() const { return d->mOwner; } void Calendar::setOwner( const Person &owner ) { d->mOwner = owner; setModified( true ); } void Calendar::setTimeSpec( const KDateTime::Spec &timeSpec ) { d->mTimeSpec = timeSpec; d->mBuiltInTimeZone = ICalTimeZone(); setViewTimeSpec( timeSpec ); doSetTimeSpec( d->mTimeSpec ); } KDateTime::Spec Calendar::timeSpec() const { return d->mTimeSpec; } void Calendar::setTimeZoneId( const QString &timeZoneId ) { d->mTimeSpec = d->timeZoneIdSpec( timeZoneId, false ); d->mViewTimeSpec = d->mTimeSpec; d->mBuiltInViewTimeZone = d->mBuiltInTimeZone; doSetTimeSpec( d->mTimeSpec ); } //@cond PRIVATE KDateTime::Spec Calendar::Private::timeZoneIdSpec( const QString &timeZoneId, bool view ) { if ( view ) { mBuiltInViewTimeZone = ICalTimeZone(); } else { mBuiltInTimeZone = ICalTimeZone(); } if ( timeZoneId == QLatin1String( "UTC" ) ) { return KDateTime::UTC; } ICalTimeZone tz = mTimeZones->zone( timeZoneId ); if ( !tz.isValid() ) { ICalTimeZoneSource tzsrc; tz = tzsrc.parse( icaltimezone_get_builtin_timezone( timeZoneId.toLatin1() ) ); if ( view ) { mBuiltInViewTimeZone = tz; } else { mBuiltInTimeZone = tz; } } if ( tz.isValid() ) { return tz; } else { return KDateTime::ClockTime; } } //@endcond QString Calendar::timeZoneId() const { KTimeZone tz = d->mTimeSpec.timeZone(); return tz.isValid() ? tz.name() : QString(); } void Calendar::setViewTimeSpec( const KDateTime::Spec &timeSpec ) const { d->mViewTimeSpec = timeSpec; d->mBuiltInViewTimeZone = ICalTimeZone(); } void Calendar::setViewTimeZoneId( const QString &timeZoneId ) const { d->mViewTimeSpec = d->timeZoneIdSpec( timeZoneId, true ); } KDateTime::Spec Calendar::viewTimeSpec() const { return d->mViewTimeSpec; } QString Calendar::viewTimeZoneId() const { KTimeZone tz = d->mViewTimeSpec.timeZone(); return tz.isValid() ? tz.name() : QString(); } ICalTimeZones *Calendar::timeZones() const { return d->mTimeZones; } void Calendar::shiftTimes( const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec ) { setTimeSpec( newSpec ); int i, end; Event::List ev = events(); for ( i = 0, end = ev.count(); i < end; ++i ) { ev[i]->shiftTimes( oldSpec, newSpec ); } Todo::List to = todos(); for ( i = 0, end = to.count(); i < end; ++i ) { to[i]->shiftTimes( oldSpec, newSpec ); } Journal::List jo = journals(); for ( i = 0, end = jo.count(); i < end; ++i ) { jo[i]->shiftTimes( oldSpec, newSpec ); } } void Calendar::setFilter( CalFilter *filter ) { if ( filter ) { d->mFilter = filter; } else { d->mFilter = d->mDefaultFilter; } } CalFilter *Calendar::filter() { return d->mFilter; } QStringList Calendar::categories() { Incidence::List rawInc( rawIncidences() ); QStringList cats, thisCats; // @TODO: For now just iterate over all incidences. In the future, // the list of categories should be built when reading the file. for ( Incidence::List::ConstIterator i = rawInc.constBegin(); i != rawInc.constEnd(); ++i ) { thisCats = (*i)->categories(); for ( QStringList::ConstIterator si = thisCats.constBegin(); si != thisCats.constEnd(); ++si ) { if ( !cats.contains( *si ) ) { cats.append( *si ); } } } return cats; } Incidence::List Calendar::incidences( const QDate &date ) { return mergeIncidenceList( events( date ), todos( date ), journals( date ) ); } Incidence::List Calendar::incidences() { return mergeIncidenceList( events(), todos(), journals() ); } Incidence::List Calendar::rawIncidences() { return mergeIncidenceList( rawEvents(), rawTodos(), rawJournals() ); } Event::List Calendar::sortEvents( Event::List *eventList, EventSortField sortField, SortDirection sortDirection ) { Event::List eventListSorted; Event::List tempList, t; Event::List alphaList; Event::List::Iterator sortIt; Event::List::Iterator eit; // Notice we alphabetically presort Summaries first. // We do this so comparison "ties" stay in a nice order. switch( sortField ) { case EventSortUnsorted: eventListSorted = *eventList; break; case EventSortStartDate: alphaList = sortEvents( eventList, EventSortSummary, sortDirection ); for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) { if ( (*eit)->dtStart().isDateOnly() ) { tempList.append( *eit ); continue; } sortIt = eventListSorted.begin(); if ( sortDirection == SortDirectionAscending ) { while ( sortIt != eventListSorted.end() && (*eit)->dtStart() >= (*sortIt)->dtStart() ) { ++sortIt; } } else { while ( sortIt != eventListSorted.end() && (*eit)->dtStart() < (*sortIt)->dtStart() ) { ++sortIt; } } eventListSorted.insert( sortIt, *eit ); } if ( sortDirection == SortDirectionAscending ) { // Prepend the list of Events without End DateTimes tempList += eventListSorted; eventListSorted = tempList; } else { // Append the list of Events without End DateTimes eventListSorted += tempList; } break; case EventSortEndDate: alphaList = sortEvents( eventList, EventSortSummary, sortDirection ); for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) { if ( (*eit)->hasEndDate() ) { sortIt = eventListSorted.begin(); if ( sortDirection == SortDirectionAscending ) { while ( sortIt != eventListSorted.end() && (*eit)->dtEnd() >= (*sortIt)->dtEnd() ) { ++sortIt; } } else { while ( sortIt != eventListSorted.end() && (*eit)->dtEnd() < (*sortIt)->dtEnd() ) { ++sortIt; } } } else { // Keep a list of the Events without End DateTimes tempList.append( *eit ); } eventListSorted.insert( sortIt, *eit ); } if ( sortDirection == SortDirectionAscending ) { // Append the list of Events without End DateTimes eventListSorted += tempList; } else { // Prepend the list of Events without End DateTimes tempList += eventListSorted; eventListSorted = tempList; } break; case EventSortSummary: for ( eit = eventList->begin(); eit != eventList->end(); ++eit ) { sortIt = eventListSorted.begin(); if ( sortDirection == SortDirectionAscending ) { while ( sortIt != eventListSorted.end() && (*eit)->summary() >= (*sortIt)->summary() ) { ++sortIt; } } else { while ( sortIt != eventListSorted.end() && (*eit)->summary() < (*sortIt)->summary() ) { ++sortIt; } } eventListSorted.insert( sortIt, *eit ); } break; } return eventListSorted; } Event::List Calendar::events( const QDate &date, const KDateTime::Spec &timeSpec, EventSortField sortField, SortDirection sortDirection ) { Event::List el = rawEventsForDate( date, timeSpec, sortField, sortDirection ); d->mFilter->apply( &el ); return el; } Event::List Calendar::events( const KDateTime &dt ) { Event::List el = rawEventsForDate( dt ); d->mFilter->apply( &el ); return el; } Event::List Calendar::events( const QDate &start, const QDate &end, const KDateTime::Spec &timeSpec, bool inclusive ) { Event::List el = rawEvents( start, end, timeSpec, inclusive ); d->mFilter->apply( &el ); return el; } Event::List Calendar::events( EventSortField sortField, SortDirection sortDirection ) { Event::List el = rawEvents( sortField, sortDirection ); d->mFilter->apply( &el ); return el; } bool Calendar::addIncidence( Incidence *incidence ) { Incidence::AddVisitor v( this ); return incidence->accept( v ); } bool Calendar::deleteIncidence( Incidence *incidence ) { if ( beginChange( incidence ) ) { Incidence::DeleteVisitor v( this ); bool result = incidence->accept( v ); endChange( incidence ); return result; } else { return false; } } // Dissociate a single occurrence or all future occurrences from a recurring // sequence. The new incidence is returned, but not automatically inserted // into the calendar, which is left to the calling application. Incidence *Calendar::dissociateOccurrence( Incidence *incidence, const QDate &date, const KDateTime::Spec &spec, bool single ) { if ( !incidence || !incidence->recurs() ) { return 0; } Incidence *newInc = incidence->clone(); newInc->recreate(); // Do not call setRelatedTo() when dissociating recurring to-dos, otherwise the new to-do // will appear as a child. Originally, we planned to set a relation with reltype SIBLING // when dissociating to-dos, but currently kcal only supports reltype PARENT. // We can uncomment the following line when we support the PARENT reltype. //newInc->setRelatedTo( incidence ); Recurrence *recur = newInc->recurrence(); if ( single ) { recur->clear(); } else { // Adjust the recurrence for the future incidences. In particular adjust // the "end after n occurrences" rules! "No end date" and "end by ..." // don't need to be modified. int duration = recur->duration(); if ( duration > 0 ) { int doneduration = recur->durationTo( date.addDays( -1 ) ); if ( doneduration >= duration ) { kDebug() << "The dissociated event already occurred more often" << "than it was supposed to ever occur. ERROR!"; recur->clear(); } else { recur->setDuration( duration - doneduration ); } } } // Adjust the date of the incidence if ( incidence->type() == "Event" ) { Event *ev = static_cast( newInc ); KDateTime start( ev->dtStart() ); int daysTo = start.toTimeSpec( spec ).date().daysTo( date ); ev->setDtStart( start.addDays( daysTo ) ); ev->setDtEnd( ev->dtEnd().addDays( daysTo ) ); } else if ( incidence->type() == "Todo" ) { Todo *td = static_cast( newInc ); bool haveOffset = false; int daysTo = 0; if ( td->hasDueDate() ) { KDateTime due( td->dtDue() ); daysTo = due.toTimeSpec( spec ).date().daysTo( date ); td->setDtDue( due.addDays( daysTo ), true ); haveOffset = true; } if ( td->hasStartDate() ) { KDateTime start( td->dtStart() ); if ( !haveOffset ) { daysTo = start.toTimeSpec( spec ).date().daysTo( date ); } td->setDtStart( start.addDays( daysTo ) ); haveOffset = true; } } recur = incidence->recurrence(); if ( recur ) { if ( single ) { recur->addExDate( date ); } else { // Make sure the recurrence of the past events ends // at the corresponding day recur->setEndDate( date.addDays(-1) ); } } return newInc; } Incidence *Calendar::incidence( const QString &uid ) { Incidence *i = event( uid ); if ( i ) { return i; } i = todo( uid ); if ( i ) { return i; } i = journal( uid ); return i; } Incidence::List Calendar::incidencesFromSchedulingID( const QString &sid ) { Incidence::List result; const Incidence::List incidences = rawIncidences(); Incidence::List::const_iterator it = incidences.begin(); for ( ; it != incidences.end(); ++it ) { if ( (*it)->schedulingID() == sid ) { result.append( *it ); } } return result; } Incidence *Calendar::incidenceFromSchedulingID( const QString &UID ) { const Incidence::List incidences = rawIncidences(); Incidence::List::const_iterator it = incidences.begin(); for ( ; it != incidences.end(); ++it ) { if ( (*it)->schedulingID() == UID ) { // Touchdown, and the crowd goes wild return *it; } } // Not found return 0; } Todo::List Calendar::sortTodos( Todo::List *todoList, TodoSortField sortField, SortDirection sortDirection ) { Todo::List todoListSorted; Todo::List tempList, t; Todo::List alphaList; Todo::List::Iterator sortIt; Todo::List::Iterator eit; // Notice we alphabetically presort Summaries first. // We do this so comparison "ties" stay in a nice order. // Note that To-dos may not have Start DateTimes nor due DateTimes. switch( sortField ) { case TodoSortUnsorted: todoListSorted = *todoList; break; case TodoSortStartDate: alphaList = sortTodos( todoList, TodoSortSummary, sortDirection ); for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) { if ( (*eit)->hasStartDate() ) { sortIt = todoListSorted.begin(); if ( sortDirection == SortDirectionAscending ) { while ( sortIt != todoListSorted.end() && (*eit)->dtStart() >= (*sortIt)->dtStart() ) { ++sortIt; } } else { while ( sortIt != todoListSorted.end() && (*eit)->dtStart() < (*sortIt)->dtStart() ) { ++sortIt; } } todoListSorted.insert( sortIt, *eit ); } else { // Keep a list of the To-dos without Start DateTimes tempList.append( *eit ); } } if ( sortDirection == SortDirectionAscending ) { // Append the list of To-dos without Start DateTimes todoListSorted += tempList; } else { // Prepend the list of To-dos without Start DateTimes tempList += todoListSorted; todoListSorted = tempList; } break; case TodoSortDueDate: alphaList = sortTodos( todoList, TodoSortSummary, sortDirection ); for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) { if ( (*eit)->hasDueDate() ) { sortIt = todoListSorted.begin(); if ( sortDirection == SortDirectionAscending ) { while ( sortIt != todoListSorted.end() && (*eit)->dtDue() >= (*sortIt)->dtDue() ) { ++sortIt; } } else { while ( sortIt != todoListSorted.end() && (*eit)->dtDue() < (*sortIt)->dtDue() ) { ++sortIt; } } todoListSorted.insert( sortIt, *eit ); } else { // Keep a list of the To-dos without Due DateTimes tempList.append( *eit ); } } if ( sortDirection == SortDirectionAscending ) { // Append the list of To-dos without Due DateTimes todoListSorted += tempList; } else { // Prepend the list of To-dos without Due DateTimes tempList += todoListSorted; todoListSorted = tempList; } break; case TodoSortPriority: alphaList = sortTodos( todoList, TodoSortSummary, sortDirection ); for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) { sortIt = todoListSorted.begin(); if ( sortDirection == SortDirectionAscending ) { while ( sortIt != todoListSorted.end() && (*eit)->priority() >= (*sortIt)->priority() ) { ++sortIt; } } else { while ( sortIt != todoListSorted.end() && (*eit)->priority() < (*sortIt)->priority() ) { ++sortIt; } } todoListSorted.insert( sortIt, *eit ); } break; case TodoSortPercentComplete: alphaList = sortTodos( todoList, TodoSortSummary, sortDirection ); for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) { sortIt = todoListSorted.begin(); if ( sortDirection == SortDirectionAscending ) { while ( sortIt != todoListSorted.end() && (*eit)->percentComplete() >= (*sortIt)->percentComplete() ) { ++sortIt; } } else { while ( sortIt != todoListSorted.end() && (*eit)->percentComplete() < (*sortIt)->percentComplete() ) { ++sortIt; } } todoListSorted.insert( sortIt, *eit ); } break; case TodoSortSummary: for ( eit = todoList->begin(); eit != todoList->end(); ++eit ) { sortIt = todoListSorted.begin(); if ( sortDirection == SortDirectionAscending ) { while ( sortIt != todoListSorted.end() && (*eit)->summary() >= (*sortIt)->summary() ) { ++sortIt; } } else { while ( sortIt != todoListSorted.end() && (*eit)->summary() < (*sortIt)->summary() ) { ++sortIt; } } todoListSorted.insert( sortIt, *eit ); } break; } return todoListSorted; } Todo::List Calendar::todos( TodoSortField sortField, SortDirection sortDirection ) { Todo::List tl = rawTodos( sortField, sortDirection ); d->mFilter->apply( &tl ); return tl; } Todo::List Calendar::todos( const QDate &date ) { Todo::List el = rawTodosForDate( date ); d->mFilter->apply( &el ); return el; } Journal::List Calendar::sortJournals( Journal::List *journalList, JournalSortField sortField, SortDirection sortDirection ) { Journal::List journalListSorted; Journal::List::Iterator sortIt; Journal::List::Iterator eit; switch( sortField ) { case JournalSortUnsorted: journalListSorted = *journalList; break; case JournalSortDate: for ( eit = journalList->begin(); eit != journalList->end(); ++eit ) { sortIt = journalListSorted.begin(); if ( sortDirection == SortDirectionAscending ) { while ( sortIt != journalListSorted.end() && (*eit)->dtStart() >= (*sortIt)->dtStart() ) { ++sortIt; } } else { while ( sortIt != journalListSorted.end() && (*eit)->dtStart() < (*sortIt)->dtStart() ) { ++sortIt; } } journalListSorted.insert( sortIt, *eit ); } break; case JournalSortSummary: for ( eit = journalList->begin(); eit != journalList->end(); ++eit ) { sortIt = journalListSorted.begin(); if ( sortDirection == SortDirectionAscending ) { while ( sortIt != journalListSorted.end() && (*eit)->summary() >= (*sortIt)->summary() ) { ++sortIt; } } else { while ( sortIt != journalListSorted.end() && (*eit)->summary() < (*sortIt)->summary() ) { ++sortIt; } } journalListSorted.insert( sortIt, *eit ); } break; } return journalListSorted; } Journal::List Calendar::journals( JournalSortField sortField, SortDirection sortDirection ) { Journal::List jl = rawJournals( sortField, sortDirection ); d->mFilter->apply( &jl ); return jl; } Journal::List Calendar::journals( const QDate &date ) { Journal::List el = rawJournalsForDate( date ); d->mFilter->apply( &el ); return el; } +void Calendar::beginBatchAdding() +{ + emit batchAddingBegins(); +} + +void Calendar::endBatchAdding() +{ + emit batchAddingEnds(); +} + // When this is called, the to-dos have already been added to the calendar. // This method is only about linking related to-dos. void Calendar::setupRelations( Incidence *forincidence ) { if ( !forincidence ) { return; } QString uid = forincidence->uid(); // First, go over the list of orphans and see if this is their parent QList l = d->mOrphans.values( uid ); d->mOrphans.remove( uid ); for ( int i = 0, end = l.count(); i < end; ++i ) { l[i]->setRelatedTo( forincidence ); forincidence->addRelation( l[i] ); d->mOrphanUids.remove( l[i]->uid() ); } // Now see about this incidences parent if ( !forincidence->relatedTo() && !forincidence->relatedToUid().isEmpty() ) { // Incidence has a uid it is related to but is not registered to it yet. // Try to find it Incidence *parent = incidence( forincidence->relatedToUid() ); if ( parent ) { // Found it forincidence->setRelatedTo( parent ); parent->addRelation( forincidence ); } else { // Not found, put this in the mOrphans list // Note that the mOrphans dict might contain multiple entries with the // same key! which are multiple children that wait for the parent // incidence to be inserted. d->mOrphans.insert( forincidence->relatedToUid(), forincidence ); d->mOrphanUids.insert( forincidence->uid(), forincidence ); } } } // If a to-do with sub-to-dos is deleted, move it's sub-to-dos to the orphan list void Calendar::removeRelations( Incidence *incidence ) { if ( !incidence ) { kDebug() << "Warning: incidence is 0"; return; } QString uid = incidence->uid(); foreach ( Incidence *i, incidence->relations() ) { if ( !d->mOrphanUids.contains( i->uid() ) ) { d->mOrphans.insert( uid, i ); d->mOrphanUids.insert( i->uid(), i ); i->setRelatedTo( 0 ); i->setRelatedToUid( uid ); } } // If this incidence is related to something else, tell that about it if ( incidence->relatedTo() ) { incidence->relatedTo()->removeRelation( incidence ); } // Remove this one from the orphans list if ( d->mOrphanUids.remove( uid ) ) { // This incidence is located in the orphans list - it should be removed // Since the mOrphans dict might contain the same key (with different // child incidence pointers!) multiple times, take care that we remove // the correct one. So we need to remove all items with the given // parent UID, and readd those that are not for this item. Also, there // might be other entries with differnet UID that point to this // incidence (this might happen when the relatedTo of the item is // changed before its parent is inserted. This might happen with // groupware servers....). Remove them, too QStringList relatedToUids; // First, create a list of all keys in the mOrphans list which point // to the removed item relatedToUids << incidence->relatedToUid(); for ( QMultiHash::Iterator it = d->mOrphans.begin(); it != d->mOrphans.end(); ++it ) { if ( it.value()->uid() == uid ) { relatedToUids << it.key(); } } // now go through all uids that have one entry that point to the incidence for ( QStringList::const_iterator uidit = relatedToUids.constBegin(); uidit != relatedToUids.constEnd(); ++uidit ) { Incidence::List tempList; // Remove all to get access to the remaining entries QList l = d->mOrphans.values( *uidit ); d->mOrphans.remove( *uidit ); foreach ( Incidence *i, l ) { if ( i != incidence ) { tempList.append( i ); } } // Readd those that point to a different orphan incidence for ( Incidence::List::Iterator incit = tempList.begin(); incit != tempList.end(); ++incit ) { d->mOrphans.insert( *uidit, *incit ); } } } } void Calendar::CalendarObserver::calendarModified( bool modified, Calendar *calendar ) { Q_UNUSED( modified ); Q_UNUSED( calendar ); } void Calendar::CalendarObserver::calendarIncidenceAdded( Incidence *incidence ) { Q_UNUSED( incidence ); } void Calendar::CalendarObserver::calendarIncidenceChanged( Incidence *incidence ) { Q_UNUSED( incidence ); } void Calendar::CalendarObserver::calendarIncidenceDeleted( Incidence *incidence ) { Q_UNUSED( incidence ); } void Calendar::registerObserver( CalendarObserver *observer ) { if ( !d->mObservers.contains( observer ) ) { d->mObservers.append( observer ); } d->mNewObserver = true; } void Calendar::unregisterObserver( CalendarObserver *observer ) { d->mObservers.removeAll( observer ); } bool Calendar::isSaving() { return false; } void Calendar::setModified( bool modified ) { if ( modified != d->mModified || d->mNewObserver ) { d->mNewObserver = false; foreach ( CalendarObserver *observer, d->mObservers ) { observer->calendarModified( modified, this ); } d->mModified = modified; } } bool Calendar::isModified() const { return d->mModified; } void Calendar::incidenceUpdated( IncidenceBase *incidence ) { incidence->setLastModified( KDateTime::currentUtcDateTime() ); // we should probably update the revision number here, // or internally in the Event itself when certain things change. // need to verify with ical documentation. // The static_cast is ok as the CalendarLocal only observes Incidence objects notifyIncidenceChanged( static_cast( incidence ) ); setModified( true ); } void Calendar::doSetTimeSpec( const KDateTime::Spec &timeSpec ) { Q_UNUSED( timeSpec ); } void Calendar::notifyIncidenceAdded( Incidence *i ) { if ( !d->mObserversEnabled ) { return; } foreach ( CalendarObserver *observer, d->mObservers ) { observer->calendarIncidenceAdded( i ); } } void Calendar::notifyIncidenceChanged( Incidence *i ) { if ( !d->mObserversEnabled ) { return; } foreach ( CalendarObserver *observer, d->mObservers ) { observer->calendarIncidenceChanged( i ); } } void Calendar::notifyIncidenceDeleted( Incidence *i ) { if ( !d->mObserversEnabled ) { return; } foreach ( CalendarObserver *observer, d->mObservers ) { observer->calendarIncidenceDeleted( i ); } } void Calendar::customPropertyUpdated() { setModified( true ); } void Calendar::setProductId( const QString &id ) { d->mProductId = id; } QString Calendar::productId() const { return d->mProductId; } Incidence::List Calendar::mergeIncidenceList( const Event::List &events, const Todo::List &todos, const Journal::List &journals ) { Incidence::List incidences; int i, end; for ( i = 0, end = events.count(); i < end; ++i ) { incidences.append( events[i] ); } for ( i = 0, end = todos.count(); i < end; ++i ) { incidences.append( todos[i] ); } for ( i = 0, end = journals.count(); i < end; ++i ) { incidences.append( journals[i] ); } return incidences; } bool Calendar::beginChange( Incidence *incidence ) { Q_UNUSED( incidence ); return true; } bool Calendar::endChange( Incidence *incidence ) { Q_UNUSED( incidence ); return true; } void Calendar::setObserversEnabled( bool enabled ) { d->mObserversEnabled = enabled; } void Calendar::appendAlarms( Alarm::List &alarms, Incidence *incidence, const KDateTime &from, const KDateTime &to ) { KDateTime preTime = from.addSecs(-1); Alarm::List alarmlist = incidence->alarms(); for ( int i = 0, iend = alarmlist.count(); i < iend; ++i ) { if ( alarmlist[i]->enabled() ) { KDateTime dt = alarmlist[i]->nextRepetition( preTime ); if ( dt.isValid() && dt <= to ) { kDebug() << incidence->summary() << "':" << dt.toString(); alarms.append( alarmlist[i] ); } } } } void Calendar::appendRecurringAlarms( Alarm::List &alarms, Incidence *incidence, const KDateTime &from, const KDateTime &to ) { KDateTime dt; bool endOffsetValid = false; Duration endOffset( 0 ); Duration period( from, to ); Alarm::List alarmlist = incidence->alarms(); for ( int i = 0, iend = alarmlist.count(); i < iend; ++i ) { Alarm *a = alarmlist[i]; if ( a->enabled() ) { if ( a->hasTime() ) { // The alarm time is defined as an absolute date/time dt = a->nextRepetition( from.addSecs(-1) ); if ( !dt.isValid() || dt > to ) { continue; } } else { // Alarm time is defined by an offset from the event start or end time. // Find the offset from the event start time, which is also used as the // offset from the recurrence time. Duration offset( 0 ); if ( a->hasStartOffset() ) { offset = a->startOffset(); } else if ( a->hasEndOffset() ) { offset = a->endOffset(); if ( !endOffsetValid ) { endOffset = Duration( incidence->dtStart(), incidence->dtEnd() ); endOffsetValid = true; } } // Find the incidence's earliest alarm KDateTime alarmStart = offset.end( a->hasEndOffset() ? incidence->dtEnd() : incidence->dtStart() ); // KDateTime alarmStart = incidence->dtStart().addSecs( offset ); if ( alarmStart > to ) { continue; } KDateTime baseStart = incidence->dtStart(); if ( from > alarmStart ) { alarmStart = from; // don't look earlier than the earliest alarm baseStart = (-offset).end( (-endOffset).end( alarmStart ) ); } // Adjust the 'alarmStart' date/time and find the next recurrence at or after it. // Treate the two offsets separately in case one is daily and the other not. dt = incidence->recurrence()->getNextDateTime( baseStart.addSecs(-1) ); if ( !dt.isValid() || ( dt = endOffset.end( offset.end( dt ) ) ) > to ) // adjust 'dt' to get the alarm time { // The next recurrence is too late. if ( !a->repeatCount() ) { continue; } // The alarm has repetitions, so check whether repetitions of previous // recurrences fall within the time period. bool found = false; Duration alarmDuration = a->duration(); for ( KDateTime base = baseStart; ( dt = incidence->recurrence()->getPreviousDateTime( base ) ).isValid(); base = dt ) { if ( a->duration().end( dt ) < base ) { break; // this recurrence's last repetition is too early, so give up } // The last repetition of this recurrence is at or after 'alarmStart' time. // Check if a repetition occurs between 'alarmStart' and 'to'. int snooze = a->snoozeTime().value(); // in seconds or days if ( a->snoozeTime().isDaily() ) { Duration toFromDuration( dt, base ); int toFrom = toFromDuration.asDays(); if ( a->snoozeTime().end( from ) <= to || ( toFromDuration.isDaily() && toFrom % snooze == 0 ) || ( toFrom / snooze + 1 ) * snooze <= toFrom + period.asDays() ) { found = true; #ifndef NDEBUG // for debug output dt = offset.end( dt ).addDays( ( ( toFrom - 1 ) / snooze + 1 ) * snooze ); #endif break; } } else { int toFrom = dt.secsTo( base ); if ( period.asSeconds() >= snooze || toFrom % snooze == 0 || ( toFrom / snooze + 1 ) * snooze <= toFrom + period.asSeconds() ) { found = true; #ifndef NDEBUG // for debug output dt = offset.end( dt ).addSecs( ( ( toFrom - 1 ) / snooze + 1 ) * snooze ); #endif break; } } } if ( !found ) { continue; } } } kDebug() << incidence->summary() << "':" << dt.toString(); alarms.append( a ); } } } #include "calendar.moc" diff --git a/kcal/calendar.h b/kcal/calendar.h index 125acea35..afd9e6647 100644 --- a/kcal/calendar.h +++ b/kcal/calendar.h @@ -1,1076 +1,1112 @@ /* This file is part of the kcal library. Copyright (c) 1998 Preston Brown Copyright (c) 2001,2003,2004 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (c) 2006 David Jarvie This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the Calendar class. @author Preston Brown \ @author Cornelius Schumacher \ @author Reinhold Kainhofer \ @author David Jarvie \ */ #ifndef KCAL_CALENDAR_H #define KCAL_CALENDAR_H #include #include #include #include #include #include "customproperties.h" #include "event.h" #include "todo.h" #include "journal.h" #include "kcalversion.h" namespace KCal { class ICalTimeZone; class ICalTimeZones; class CalFilter; class Person; /** Calendar Incidence sort directions. */ enum SortDirection { SortDirectionAscending, /**< Sort in ascending order (first to last) */ SortDirectionDescending /**< Sort in descending order (last to first) */ }; /** Calendar Event sort keys. */ enum EventSortField { EventSortUnsorted, /**< Do not sort Events */ EventSortStartDate, /**< Sort Events chronologically, by start date */ EventSortEndDate, /**< Sort Events chronologically, by end date */ EventSortSummary /**< Sort Events alphabetically, by summary */ }; /** Calendar Todo sort keys. */ enum TodoSortField { TodoSortUnsorted, /**< Do not sort Todos */ TodoSortStartDate, /**< Sort Todos chronologically, by start date */ TodoSortDueDate, /**< Sort Todos chronologically, by due date */ TodoSortPriority, /**< Sort Todos by priority */ TodoSortPercentComplete, /**< Sort Todos by percentage completed */ TodoSortSummary /**< Sort Todos alphabetically, by summary */ }; /** Calendar Journal sort keys. */ enum JournalSortField { JournalSortUnsorted, /**< Do not sort Journals */ JournalSortDate, /**< Sort Journals chronologically by date */ JournalSortSummary /**< Sort Journals alphabetically, by summary */ }; /** @brief Represents the main calendar class. A calendar contains information like incidences (events, to-dos, journals), alarms, time zones, and other useful information. This is an abstract base class defining the interface to a calendar. It is implemented by subclasses like CalendarLocal, which use different methods to store and access the data. Ownership of Incidences: Incidence ownership is handled by the following policy: as soon as an incidence (or any other subclass of IncidenceBase) is added to the Calendar by an add...() method it is owned by the Calendar object. The Calendar takes care of deleting the incidence using the delete...() methods. All Incidences returned by the query functions are returned as pointers so that changes to the returned Incidences are immediately visible in the Calendar. Do Not attempt to 'delete' any Incidence object you get from Calendar -- use the delete...() methods. */ class KCAL_EXPORT Calendar : public QObject, public CustomProperties, public IncidenceBase::IncidenceObserver { Q_OBJECT public: /** Constructs a calendar with a specified time zone @p timeZoneid. The time specification is used as the default for creating or modifying incidences in the Calendar. The time specification does not alter existing incidences. The constructor also calls setViewTimeSpec(@p timeSpec). @param timeSpec time specification */ explicit Calendar( const KDateTime::Spec &timeSpec ); /** Construct Calendar object using a time zone ID. The time zone ID is used as the default for creating or modifying incidences in the Calendar. The time zone does not alter existing incidences. The constructor also calls setViewTimeZoneId(@p timeZoneId). @param timeZoneId is a string containing a time zone ID, which is assumed to be valid. If no time zone is found, the viewing time specification is set to local clock time. @e Example: "Europe/Berlin" */ explicit Calendar( const QString &timeZoneId ); /** Destroys the calendar. */ virtual ~Calendar(); /** Sets the calendar Product ID to @p id. @param id is a string containing the Product ID. @see productId() const */ void setProductId( const QString &id ); /** Returns the calendar's Product ID. @see setProductId() */ QString productId() const; /** Sets the owner of the calendar to @p owner. @param owner is a Person object. @see owner() */ void setOwner( const Person &owner ); /** Returns the owner of the calendar. @return the owner Person object. @see setOwner() */ Person owner() const; /** Sets the default time specification (time zone, etc.) used for creating or modifying incidences in the Calendar. The method also calls setViewTimeSpec(@p timeSpec). @param timeSpec time specification */ void setTimeSpec( const KDateTime::Spec &timeSpec ); /** Get the time specification (time zone etc.) used for creating or modifying incidences in the Calendar. @return time specification */ KDateTime::Spec timeSpec() const; /** Sets the time zone ID used for creating or modifying incidences in the Calendar. This method has no effect on existing incidences. The method also calls setViewTimeZoneId(@p timeZoneId). @param timeZoneId is a string containing a time zone ID, which is assumed to be valid. The time zone ID is used to set the time zone for viewing Incidence date/times. If no time zone is found, the viewing time specification is set to local clock time. @e Example: "Europe/Berlin" @see setTimeSpec() */ void setTimeZoneId( const QString &timeZoneId ); /** Returns the time zone ID used for creating or modifying incidences in the calendar. @return the string containing the time zone ID, or empty string if the creation/modification time specification is not a time zone. */ QString timeZoneId() const; /** Notes the time specification which the client application intends to use for viewing the incidences in this calendar. This is simply a convenience method which makes a note of the new time zone so that it can be read back by viewTimeSpec(). The client application must convert date/time values to the desired time zone itself. The time specification is not used in any way by the Calendar or its incidences; it is solely for use by the client application. @param timeSpec time specification @see viewTimeSpec() */ void setViewTimeSpec( const KDateTime::Spec &timeSpec ) const; /** Notes the time zone Id which the client application intends to use for viewing the incidences in this calendar. This is simply a convenience method which makes a note of the new time zone so that it can be read back by viewTimeId(). The client application must convert date/time values to the desired time zone itself. The Id is not used in any way by the Calendar or its incidences. It is solely for use by the client application. @param timeZoneId is a string containing a time zone ID, which is assumed to be valid. The time zone ID is used to set the time zone for viewing Incidence date/times. If no time zone is found, the viewing time specification is set to local clock time. @e Example: "Europe/Berlin" @see viewTimeZoneId() */ void setViewTimeZoneId( const QString &timeZoneId ) const; /** Returns the time specification used for viewing the incidences in this calendar. This simply returns the time specification last set by setViewTimeSpec(). @see setViewTimeSpec(). */ KDateTime::Spec viewTimeSpec() const; /** Returns the time zone Id used for viewing the incidences in this calendar. This simply returns the time specification last set by setViewTimeSpec(). @see setViewTimeZoneId(). */ QString viewTimeZoneId() const; /** Shifts the times of all incidences so that they appear at the same clock time as before but in a new time zone. The shift is done from a viewing time zone rather than from the actual incidence time zone. For example, shifting an incidence whose start time is 09:00 America/New York, using an old viewing time zone (@p oldSpec) of Europe/London, to a new time zone (@p newSpec) of Europe/Paris, will result in the time being shifted from 14:00 (which is the London time of the incidence start) to 14:00 Paris time. @param oldSpec the time specification which provides the clock times @param newSpec the new time specification @see isLocalTime() */ void shiftTimes( const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec ); /** Returns the time zone collection used by the calendar. @return the time zones collection. @see setLocalTime() */ ICalTimeZones *timeZones() const; /** Set the time zone collection used by the calendar. @param zones time zones collection. Important: all time zones references in the calendar must be included in the collection. */ void setTimeZones( const ICalTimeZones &zones ); /** Sets if the calendar has been modified. @param modified is true if the calendar has been modified since open or last save. @see isModified() */ void setModified( bool modified ); /** Determine the calendar's modification status. @return true if the calendar has been modified since open or last save. @see setModified() */ bool isModified() const; /** Clears out the current calendar, freeing all used memory etc. */ virtual void close() = 0; /** Syncs changes in memory to persistent storage. @return true if the save was successful; false otherwise. */ virtual bool save() = 0; /** Loads the calendar contents from storage. This requires that the calendar has been previously loaded (initialized). @return true if the reload was successful; otherwise false. */ virtual bool reload() = 0; /** Determine if the calendar is currently being saved. @return true if the calendar is currently being saved; false otherwise. */ virtual bool isSaving(); /** Returns a list of all categories used by Incidences in this Calendar. @return a QStringList containing all the categories. */ QStringList categories(); // Incidence Specific Methods // /** Inserts an Incidence into the calendar. @param incidence is a pointer to the Incidence to insert. @return true if the Incidence was successfully inserted; false otherwise. @see deleteIncidence() */ virtual bool addIncidence( Incidence *incidence ); /** Removes an Incidence from the calendar. @param incidence is a pointer to the Incidence to remove. @return true if the Incidence was successfully removed; false otherwise. @see addIncidence() */ virtual bool deleteIncidence( Incidence *incidence ); /** Returns a filtered list of all Incidences for this Calendar. @return the list of all filtered Incidences. */ virtual Incidence::List incidences(); /** Returns a filtered list of all Incidences which occur on the given date. @param date request filtered Incidence list for this QDate only. @return the list of filtered Incidences occurring on the specified date. */ virtual Incidence::List incidences( const QDate &date ); /** Returns an unfiltered list of all Incidences for this Calendar. @return the list of all unfiltered Incidences. */ virtual Incidence::List rawIncidences(); /** Returns the Incidence associated with the given unique identifier. @param uid is a unique identifier string. @return a pointer to the Incidence. A null pointer is returned if no such Incidence exists. */ Incidence *incidence( const QString &uid ); /** Returns the Incidence associated with the given scheduling identifier. @param sid is a unique scheduling identifier string. @return a pointer to the Incidence. A null pointer is returned if no such Incidence exists. */ Incidence *incidenceFromSchedulingID( const QString &sid ); /** Searches all events and todos for an incidence with this scheduling identifiere. Returns a list of matching results. @param sid is a unique scheduling identifier string. */ Incidence::List incidencesFromSchedulingID( const QString &sid ); /** Create a merged list of Events, Todos, and Journals. @param events is an Event list to merge. @param todos is a Todo list to merge. @param journals is a Journal list to merge. @return a list of merged Incidences. */ static Incidence::List mergeIncidenceList( const Event::List &events, const Todo::List &todos, const Journal::List &journals ); /** Flag that a change to a Calendar Incidence is starting. @param incidence is a pointer to the Incidence that will be changing. */ virtual bool beginChange( Incidence *incidence ); /** Flag that a change to a Calendar Incidence has completed. @param incidence is a pointer to the Incidence that was changed. */ virtual bool endChange( Incidence *incidence ); /** Dissociate an Incidence from a recurring Incidence. By default, only one single Incidence for the specified @a date will be dissociated and returned. If @a single is false, then the recurrence will be split at @a date, the old Incidence will have its recurrence ending at @a date and the new Incidence will have all recurrences past the @a date. @param incidence is a pointer to a recurring Incidence. @param date is the QDate within the recurring Incidence on which the dissociation will be performed. @param spec is the spec in which the @a date is formulated. @param single is a flag meaning that a new Incidence should be created from the recurring Incidences after @a date. @return a pointer to a new recurring Incidence if @a single is false. */ Incidence *dissociateOccurrence( Incidence *incidence, const QDate &date, const KDateTime::Spec &spec, bool single = true ); // Event Specific Methods // /** Inserts an Event into the calendar. @param event is a pointer to the Event to insert. @return true if the Event was successfully inserted; false otherwise. @see deleteEvent() */ virtual bool addEvent( Event *event ) = 0; /** Removes an Event from the calendar. @param event is a pointer to the Event to remove. @return true if the Event was successfully remove; false otherwise. @see addEvent(), deleteAllEvents() */ virtual bool deleteEvent( Event *event ) = 0; /** Removes all Events from the calendar. @see deleteEvent() */ virtual void deleteAllEvents() = 0; /** Sort a list of Events. @param eventList is a pointer to a list of Events. @param sortField specifies the EventSortField. @param sortDirection specifies the SortDirection. @return a list of Events sorted as specified. */ static Event::List sortEvents( Event::List *eventList, EventSortField sortField, SortDirection sortDirection ); /** Returns a sorted, filtered list of all Events for this Calendar. @param sortField specifies the EventSortField. @param sortDirection specifies the SortDirection. @return the list of all filtered Events sorted as specified. */ virtual Event::List events( EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); /** Returns a filtered list of all Events which occur on the given timestamp. @param dt request filtered Event list for this KDateTime only. @return the list of filtered Events occurring on the specified timestamp. */ Event::List events( const KDateTime &dt ); /** Returns a filtered list of all Events occurring within a date range. @param start is the starting date. @param end is the ending date. @param timeSpec time zone etc. to interpret @p start and @p end, or the calendar's default time spec if none is specified @param inclusive if true only Events which are completely included within the date range are returned. @return the list of filtered Events occurring within the specified date range. */ Event::List events( const QDate &start, const QDate &end, const KDateTime::Spec &timeSpec = KDateTime::Spec(), bool inclusive = false ); /** Returns a sorted, filtered list of all Events which occur on the given date. The Events are sorted according to @a sortField and @a sortDirection. @param date request filtered Event list for this QDate only. @param timeSpec time zone etc. to interpret @p start and @p end, or the calendar's default time spec if none is specified @param sortField specifies the EventSortField. @param sortDirection specifies the SortDirection. @return the list of sorted, filtered Events occurring on @a date. */ Event::List events( const QDate &date, const KDateTime::Spec &timeSpec = KDateTime::Spec(), EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); /** Returns a sorted, unfiltered list of all Events for this Calendar. @param sortField specifies the EventSortField. @param sortDirection specifies the SortDirection. @return the list of all unfiltered Events sorted as specified. */ virtual Event::List rawEvents( EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending ) = 0; /** Returns an unfiltered list of all Events which occur on the given timestamp. @param dt request unfiltered Event list for this KDateTime only. @return the list of unfiltered Events occurring on the specified timestamp. */ virtual Event::List rawEventsForDate( const KDateTime &dt ) = 0; /** Returns an unfiltered list of all Events occurring within a date range. @param start is the starting date @param end is the ending date @param timeSpec time zone etc. to interpret @p start and @p end, or the calendar's default time spec if none is specified @param inclusive if true only Events which are completely included within the date range are returned. @return the list of unfiltered Events occurring within the specified date range. */ virtual Event::List rawEvents( const QDate &start, const QDate &end, const KDateTime::Spec &timeSpec = KDateTime::Spec(), bool inclusive = false ) = 0; /** Returns a sorted, unfiltered list of all Events which occur on the given date. The Events are sorted according to @a sortField and @a sortDirection. @param date request unfiltered Event list for this QDate only @param timeSpec time zone etc. to interpret @p date, or the calendar's default time spec if none is specified @param sortField specifies the EventSortField @param sortDirection specifies the SortDirection @return the list of sorted, unfiltered Events occurring on @p date */ virtual Event::List rawEventsForDate( const QDate &date, const KDateTime::Spec &timeSpec = KDateTime::Spec(), EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending ) = 0; /** Returns the Event associated with the given unique identifier. @param uid is a unique identifier string. @return a pointer to the Event. A null pointer is returned if no such Event exists. */ virtual Event *event( const QString &uid ) = 0; // Todo Specific Methods // /** Inserts a Todo into the calendar. @param todo is a pointer to the Todo to insert. @return true if the Todo was successfully inserted; false otherwise. @see deleteTodo() */ virtual bool addTodo( Todo *todo ) = 0; /** Removes a Todo from the calendar. @param todo is a pointer to the Todo to remove. @return true if the Todo was successfully removed; false otherwise. @see addTodo(), deleteAllTodos() */ virtual bool deleteTodo( Todo *todo ) = 0; /** Removes all To-dos from the calendar. @see deleteTodo() */ virtual void deleteAllTodos() = 0; /** Sort a list of Todos. @param todoList is a pointer to a list of Todos. @param sortField specifies the TodoSortField. @param sortDirection specifies the SortDirection. @return a list of Todos sorted as specified. */ static Todo::List sortTodos( Todo::List *todoList, TodoSortField sortField, SortDirection sortDirection ); /** Returns a sorted, filtered list of all Todos for this Calendar. @param sortField specifies the TodoSortField. @param sortDirection specifies the SortDirection. @return the list of all filtered Todos sorted as specified. */ virtual Todo::List todos( TodoSortField sortField = TodoSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); /** Returns a filtered list of all Todos which are due on the specified date. @param date request filtered Todos due on this QDate. @return the list of filtered Todos due on the specified date. */ virtual Todo::List todos( const QDate &date ); /** Returns a sorted, unfiltered list of all Todos for this Calendar. @param sortField specifies the TodoSortField. @param sortDirection specifies the SortDirection. @return the list of all unfiltered Todos sorted as specified. */ virtual Todo::List rawTodos( TodoSortField sortField = TodoSortUnsorted, SortDirection sortDirection = SortDirectionAscending ) = 0; /** Returns an unfiltered list of all Todos which due on the specified date. @param date request unfiltered Todos due on this QDate. @return the list of unfiltered Todos due on the specified date. */ virtual Todo::List rawTodosForDate( const QDate &date ) = 0; /** Returns the Todo associated with the given unique identifier. @param uid is a unique identifier string. @return a pointer to the Todo. A null pointer is returned if no such Todo exists. */ virtual Todo *todo( const QString &uid ) = 0; // Journal Specific Methods // /** Inserts a Journal into the calendar. @param journal is a pointer to the Journal to insert. @return true if the Journal was successfully inserted; false otherwise. @see deleteJournal() */ virtual bool addJournal( Journal *journal ) = 0; /** Removes a Journal from the calendar. @param journal is a pointer to the Journal to remove. @return true if the Journal was successfully removed; false otherwise. @see addJournal(), deleteAllJournals() */ virtual bool deleteJournal( Journal *journal ) = 0; /** Removes all Journals from the calendar. @see deleteJournal() */ virtual void deleteAllJournals() = 0; /** Sort a list of Journals. @param journalList is a pointer to a list of Journals. @param sortField specifies the JournalSortField. @param sortDirection specifies the SortDirection. @return a list of Journals sorted as specified. */ static Journal::List sortJournals( Journal::List *journalList, JournalSortField sortField, SortDirection sortDirection ); /** Returns a sorted, filtered list of all Journals for this Calendar. @param sortField specifies the JournalSortField. @param sortDirection specifies the SortDirection. @return the list of all filtered Journals sorted as specified. */ virtual Journal::List journals( JournalSortField sortField = JournalSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); /** Returns a filtered list of all Journals for on the specified date. @param date request filtered Journals for this QDate only. @return the list of filtered Journals for the specified date. */ virtual Journal::List journals( const QDate &date ); /** Returns a sorted, unfiltered list of all Journals for this Calendar. @param sortField specifies the JournalSortField. @param sortDirection specifies the SortDirection. @return the list of all unfiltered Journals sorted as specified. */ virtual Journal::List rawJournals( JournalSortField sortField = JournalSortUnsorted, SortDirection sortDirection = SortDirectionAscending ) = 0; /** Returns an unfiltered list of all Journals for on the specified date. @param date request unfiltered Journals for this QDate only. @return the list of unfiltered Journals for the specified date. */ virtual Journal::List rawJournalsForDate( const QDate &date ) = 0; /** Returns the Journal associated with the given unique identifier. @param uid is a unique identifier string. @return a pointer to the Journal. A null pointer is returned if no such Journal exists. */ virtual Journal *journal( const QString &uid ) = 0; + /** + Emits the beginBatchAdding() signal. + + This should be called before adding a batch of incidences with + addIncidence( Incidence *), addTodo( Todo *), addEvent( Event *) + or addJournal( Journal *). Some Calendars are connected to this + signal, e.g: CalendarResources uses it to know a series of + incidenceAdds are related so the user isn't prompted multiple + times which resource to save the incidence to + + @since 4.4 + */ + void beginBatchAdding(); + + /** + Emits the endBatchAdding() signal. + + Used with beginBatchAdding(). Should be called after + adding all incidences. + + @since 4.4 + */ + void endBatchAdding(); + // Relations Specific Methods // /** Setup Relations for an Incidence. @param incidence is a pointer to the Incidence to have a Relation setup. */ virtual void setupRelations( Incidence *incidence ); /** Removes all Relations from an Incidence. @param incidence is a pointer to the Incidence to have a Relation removed. */ virtual void removeRelations( Incidence *incidence ); // Filter Specific Methods // /** Sets the calendar filter. @param filter a pointer to a CalFilter object which will be used to filter Calendar Incidences. @see filter() */ void setFilter( CalFilter *filter ); /** Returns the calendar filter. @return a pointer to the calendar CalFilter. A null pointer is returned if no such CalFilter exists. @see setFilter() */ CalFilter *filter(); // Alarm Specific Methods // /** Returns a list of Alarms within a time range for this Calendar. @param from is the starting timestamp. @param to is the ending timestamp. @return the list of Alarms for the for the specified time range. */ virtual Alarm::List alarms( const KDateTime &from, const KDateTime &to ) = 0; // Observer Specific Methods // /** @class CalendarObserver The CalendarObserver class. */ class KCAL_EXPORT CalendarObserver //krazy:exclude=dpointer { public: /** Destructor. */ virtual ~CalendarObserver() {} /** Notify the Observer that a Calendar has been modified. @param modified set if the calendar has been modified. @param calendar is a pointer to the Calendar object that is being observed. */ virtual void calendarModified( bool modified, Calendar *calendar ); /** Notify the Observer that an Incidence has been inserted. @param incidence is a pointer to the Incidence that was inserted. */ virtual void calendarIncidenceAdded( Incidence *incidence ); /** Notify the Observer that an Incidence has been modified. @param incidence is a pointer to the Incidence that was modified. */ virtual void calendarIncidenceChanged( Incidence *incidence ); /** Notify the Observer that an Incidence has been removed. @param incidence is a pointer to the Incidence that was removed. */ virtual void calendarIncidenceDeleted( Incidence *incidence ); }; /** Registers an Observer for this Calendar. @param observer is a pointer to an Observer object that will be watching this Calendar. @see unregisterObserver() */ void registerObserver( CalendarObserver *observer ); /** Unregisters an Observer for this Calendar. @param observer is a pointer to an Observer object that has been watching this Calendar. @see registerObserver() */ void unregisterObserver( CalendarObserver *observer ); using QObject::event; // prevent warning about hidden virtual method Q_SIGNALS: /** Signals that the calendar has been modified. */ void calendarChanged(); /** Signals that the calendar has been saved. */ void calendarSaved(); /** Signals that the calendar has been loaded into memory. */ void calendarLoaded(); + /** + @see beginBatchAdding() + @since 4.4 + */ + void batchAddingBegins(); + + /** + @see endBatchAdding() + @since 4.4 + */ + void batchAddingEnds(); + protected: /** The Observer interface. So far not implemented. @param incidenceBase is a pointer an IncidenceBase object. */ void incidenceUpdated( IncidenceBase *incidenceBase ); /** Let Calendar subclasses set the time specification. @param timeSpec is the time specification (time zone, etc.) for viewing Incidence dates.\n */ virtual void doSetTimeSpec( const KDateTime::Spec &timeSpec ); /** Let Calendar subclasses notify that they inserted an Incidence. @param incidence is a pointer to the Incidence object that was inserted. */ void notifyIncidenceAdded( Incidence *incidence ); /** Let Calendar subclasses notify that they modified an Incidence. @param incidence is a pointer to the Incidence object that was modified. */ void notifyIncidenceChanged( Incidence *incidence ); /** Let Calendar subclasses notify that they removed an Incidence. @param incidence is a pointer to the Incidence object that was removed. */ void notifyIncidenceDeleted( Incidence *incidence ); /** @copydoc CustomProperties::customPropertyUpdated() */ virtual void customPropertyUpdated(); /** Let Calendar subclasses notify that they enabled an Observer. @param enabled if true tells the calendar that a subclass has enabled an Observer. */ void setObserversEnabled( bool enabled ); /** Appends alarms of incidence in interval to list of alarms. @param alarms is a List of Alarms to be appended onto. @param incidence is a pointer to an Incidence containing the Alarm to be appended. @param from is the lower range of the next Alarm repitition. @param to is the upper range of the next Alarm repitition. */ void appendAlarms( Alarm::List &alarms, Incidence *incidence, const KDateTime &from, const KDateTime &to ); /** Appends alarms of recurring events in interval to list of alarms. @param alarms is a List of Alarms to be appended onto. @param incidence is a pointer to an Incidence containing the Alarm to be appended. @param from is the lower range of the next Alarm repitition. @param to is the upper range of the next Alarm repitition. */ void appendRecurringAlarms( Alarm::List &alarms, Incidence *incidence, const KDateTime &from, const KDateTime &to ); private: //@cond PRIVATE class Private; Private *const d; //@endcond Q_DISABLE_COPY( Calendar ) }; } #endif diff --git a/kcal/calendarresources.cpp b/kcal/calendarresources.cpp index 1e5253293..59bfb8425 100644 --- a/kcal/calendarresources.cpp +++ b/kcal/calendarresources.cpp @@ -1,954 +1,983 @@ /* This file is part of the kcal library. Copyright (c) 2003 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the CalendarResources class. @brief This class provides a Calendar which is composed of other Calendars known as "Resources". @author Cornelius Schumacher \ @author Reinhold Kainhofer \ */ #include "calendarresources.moc" #include "incidence.h" #include "journal.h" #include "resourcecalendar.h" #include "kresources/manager.h" #include "kresources/selectdialog.h" #include "kabc/lock.h" #include #include #include #include #include #include #include using namespace KCal; /** Private classes that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class KCal::CalendarResources::Private { public: Private( const QString &family ) - : mManager( new CalendarResourceManager( family ) ), + : mAddingInProgress( false ), + mLastUsedResource( 0 ), + mManager( new CalendarResourceManager( family ) ), mStandardPolicy( new StandardDestinationPolicy( mManager ) ), mDestinationPolicy( mStandardPolicy ), mAskPolicy( new AskDestinationPolicy( mManager ) ), mException( 0 ), mPendingDeleteFromResourceMap( false ) {} ~Private() { delete mManager; delete mStandardPolicy; delete mAskPolicy; } + bool mAddingInProgress; + ResourceCalendar *mLastUsedResource; + bool mOpen; //flag that indicates if the resources are "open" KRES::Manager* mManager; QMap mResourceMap; StandardDestinationPolicy *mStandardPolicy; DestinationPolicy *mDestinationPolicy; AskDestinationPolicy *mAskPolicy; QMap mTickets; QMap mChangeCounts; ErrorFormat *mException; bool mPendingDeleteFromResourceMap; template< class IncidenceList > void appendIncidences( IncidenceList &result, const IncidenceList &extra, ResourceCalendar * ); }; class KCal::CalendarResources::DestinationPolicy::Private { public: Private( CalendarResourceManager *manager, QWidget *parent ) : mManager( manager ), mParent( parent ) {} CalendarResourceManager *mManager; QWidget *mParent; }; class KCal::CalendarResources::StandardDestinationPolicy::Private { public: Private() {} }; class KCal::CalendarResources::AskDestinationPolicy::Private { public: Private() {} }; class KCal::CalendarResources::Ticket::Private { public: Private( ResourceCalendar *resource ) : mResource( resource ) {} ResourceCalendar *mResource; }; //@endcond CalendarResources::DestinationPolicy::DestinationPolicy( CalendarResourceManager *manager, QWidget *parent ) : d( new KCal::CalendarResources::DestinationPolicy::Private( manager, parent ) ) { } CalendarResources::DestinationPolicy::~DestinationPolicy() { delete d; } QWidget *CalendarResources::DestinationPolicy::parent() { return d->mParent; } void CalendarResources::DestinationPolicy::setParent( QWidget *parent ) { d->mParent = parent; } CalendarResourceManager *CalendarResources::DestinationPolicy::resourceManager() { return d->mManager; } bool CalendarResources::DestinationPolicy::hasCalendarResources() { CalendarResourceManager::ActiveIterator it; for ( it = resourceManager()->activeBegin(); it != resourceManager()->activeEnd(); ++it ) { if ( !(*it)->readOnly() ) { if ( resourceManager()->standardResource() == *it ) { return true; } else { return true; } } } return false; } CalendarResources::StandardDestinationPolicy::StandardDestinationPolicy( CalendarResourceManager *manager, QWidget *parent ) : DestinationPolicy( manager, parent ), d( new KCal::CalendarResources::StandardDestinationPolicy::Private ) { } CalendarResources::StandardDestinationPolicy::~StandardDestinationPolicy() { delete d; } ResourceCalendar *CalendarResources::StandardDestinationPolicy::destination( Incidence *incidence ) { Q_UNUSED( incidence ); return resourceManager()->standardResource(); } CalendarResources::AskDestinationPolicy::AskDestinationPolicy( CalendarResourceManager *manager, QWidget *parent ) : DestinationPolicy( manager, parent ), d( new KCal::CalendarResources::AskDestinationPolicy::Private ) { } CalendarResources::AskDestinationPolicy::~AskDestinationPolicy() { delete d; } ResourceCalendar *CalendarResources::AskDestinationPolicy::destination( Incidence *incidence ) { Q_UNUSED( incidence ); QList list; CalendarResourceManager::ActiveIterator it; for ( it = resourceManager()->activeBegin(); it != resourceManager()->activeEnd(); ++it ) { if ( !(*it)->readOnly() ) { //Insert the first the Standard resource to get be the default selected. if ( resourceManager()->standardResource() == *it ) { list.insert( 0, *it ); } else { list.append( *it ); } } } KRES::Resource *r; r = KRES::SelectDialog::getResource( list, parent() ); return static_cast( r ); } CalendarResources::CalendarResources( const KDateTime::Spec &timeSpec, const QString &family ) : Calendar( timeSpec ), d( new KCal::CalendarResources::Private( family ) ) { + + connect( this, SIGNAL(batchAddingBegins()), this, SLOT(beginAddingIncidences()) ); + connect( this, SIGNAL(batchAddingEnds()), this, SLOT(endAddingIncidences()) ); + d->mManager->addObserver( this ); } CalendarResources::CalendarResources( const QString &timeZoneId, const QString &family ) : Calendar( timeZoneId ), d( new KCal::CalendarResources::Private( family ) ) { + connect( this, SIGNAL(batchAddingBegins()), this, SLOT(beginAddingIncidences()) ); + connect( this, SIGNAL(batchAddingEnds()), this, SLOT(endAddingIncidences()) ); + d->mManager->addObserver( this ); } CalendarResources::~CalendarResources() { close(); clearException(); delete d; } void CalendarResources::clearException() { delete d->mException; d->mException = 0; } ErrorFormat *CalendarResources::exception() { return d->mException; } void CalendarResources::readConfig( KConfig *config ) { d->mManager->readConfig( config ); CalendarResourceManager::Iterator it; for ( it = d->mManager->begin(); it != d->mManager->end(); ++it ) { connectResource( *it ); } } void CalendarResources::load() { if ( !d->mManager->standardResource() ) { kDebug() << "Warning! No standard resource yet."; } // set the timezone for all resources. Otherwise we'll have those terrible tz // troubles ;-(( CalendarResourceManager::Iterator i1; for ( i1 = d->mManager->begin(); i1 != d->mManager->end(); ++i1 ) { (*i1)->setTimeSpec( timeSpec() ); } QList failed; // Open all active resources CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { if ( !(*it)->load() ) { failed.append( *it ); } Incidence::List incidences = (*it)->rawIncidences(); Incidence::List::Iterator incit; for ( incit = incidences.begin(); incit != incidences.end(); ++incit ) { (*incit)->registerObserver( this ); notifyIncidenceAdded( *incit ); } } QList::ConstIterator it2; for ( it2 = failed.constBegin(); it2 != failed.constEnd(); ++it2 ) { (*it2)->setActive( false ); emit signalResourceModified( *it2 ); } d->mOpen = true; emit calendarLoaded(); } bool CalendarResources::reload() { save(); close(); load(); return true; } CalendarResourceManager *CalendarResources::resourceManager() const { return d->mManager; } void CalendarResources::setStandardDestinationPolicy() { d->mDestinationPolicy = d->mStandardPolicy; } void CalendarResources::setAskDestinationPolicy() { d->mDestinationPolicy = d->mAskPolicy; } QWidget *CalendarResources::dialogParentWidget() { return d->mDestinationPolicy->parent(); } void CalendarResources::setDialogParentWidget( QWidget *parent ) { d->mDestinationPolicy->setParent( parent ); } void CalendarResources::close() { if ( d->mOpen ) { CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { (*it)->close(); } setModified( false ); d->mOpen = false; } } bool CalendarResources::save() { bool status = true; if ( d->mOpen && isModified() ) { status = false; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { status = (*it)->save() || status; } setModified( false ); } return status; } bool CalendarResources::isSaving() { CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { if ( (*it)->isSaving() ) { return true; } } return false; } bool CalendarResources::addIncidence( Incidence *incidence, ResourceCalendar *resource ) { // FIXME: Use proper locking via begin/endChange! bool validRes = false; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { if ( (*it) == resource ) { validRes = true; } } ResourceCalendar *oldResource = 0; if ( d->mResourceMap.contains( incidence ) ) { oldResource = d->mResourceMap[incidence]; } d->mResourceMap[incidence] = resource; if ( validRes && beginChange( incidence ) && resource->addIncidence( incidence ) ) { // d->mResourceMap[incidence] = resource; incidence->registerObserver( this ); notifyIncidenceAdded( incidence ); setModified( true ); endChange( incidence ); return true; } else { if ( oldResource ) { d->mResourceMap[incidence] = oldResource; } else { d->mResourceMap.remove( incidence ); } } return false; } bool CalendarResources::hasCalendarResources() { return d->mDestinationPolicy->hasCalendarResources(); } bool CalendarResources::addIncidence( Incidence *incidence ) { clearException(); - ResourceCalendar *resource = d->mDestinationPolicy->destination( incidence ); + + ResourceCalendar *resource = d->mLastUsedResource; + + if ( !d->mAddingInProgress || d->mLastUsedResource == 0 ) { + resource = d->mDestinationPolicy->destination( incidence ); + d->mLastUsedResource = resource; + } if ( resource ) { d->mResourceMap[ incidence ] = resource; if ( beginChange( incidence ) && resource->addIncidence( incidence ) ) { incidence->registerObserver( this ); notifyIncidenceAdded( incidence ); d->mResourceMap[ incidence ] = resource; setModified( true ); endChange( incidence ); return true; } else { d->mResourceMap.remove( incidence ); } } else { d->mException = new ErrorFormat( ErrorFormat::UserCancel ); } return false; } bool CalendarResources::addEvent( Event *event ) { return addIncidence( event ); } bool CalendarResources::addEvent( Event *Event, ResourceCalendar *resource ) { return addIncidence( Event, resource ); } bool CalendarResources::deleteEvent( Event *event ) { bool status; if ( d->mResourceMap.find( event ) != d->mResourceMap.end() ) { status = d->mResourceMap[event]->deleteEvent( event ); if ( status ) { d->mPendingDeleteFromResourceMap = true; } } else { status = false; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { status = (*it)->deleteEvent( event ) || status; } } if ( status ) { notifyIncidenceDeleted( event ); } setModified( status ); return status; } void CalendarResources::deleteAllEvents() { CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { (*it)->deleteAllEvents(); } } Event *CalendarResources::event( const QString &uid ) { CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { Event *event = (*it)->event( uid ); if ( event ) { d->mResourceMap[event] = *it; return event; } } // Not found return 0; } bool CalendarResources::addTodo( Todo *todo ) { return addIncidence( todo ); } bool CalendarResources::addTodo( Todo *todo, ResourceCalendar *resource ) { return addIncidence( todo, resource ); } bool CalendarResources::deleteTodo( Todo *todo ) { bool status; if ( d->mResourceMap.find( todo ) != d->mResourceMap.end() ) { status = d->mResourceMap[todo]->deleteTodo( todo ); if ( status ) { d->mPendingDeleteFromResourceMap = true; } } else { CalendarResourceManager::ActiveIterator it; status = false; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { status = (*it)->deleteTodo( todo ) || status; } } setModified( status ); return status; } void CalendarResources::deleteAllTodos() { CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { (*it)->deleteAllTodos(); } } Todo::List CalendarResources::rawTodos( TodoSortField sortField, SortDirection sortDirection ) { Todo::List result; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { d->appendIncidences( result, (*it)->rawTodos( TodoSortUnsorted ), *it ); } return sortTodos( &result, sortField, sortDirection ); } Todo *CalendarResources::todo( const QString &uid ) { CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { Todo *todo = (*it)->todo( uid ); if ( todo ) { d->mResourceMap[todo] = *it; return todo; } } // Not found return 0; } Todo::List CalendarResources::rawTodosForDate( const QDate &date ) { Todo::List result; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { d->appendIncidences( result, (*it)->rawTodosForDate( date ), *it ); } return result; } Alarm::List CalendarResources::alarmsTo( const KDateTime &to ) { Alarm::List result; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { result += (*it)->alarmsTo( to ); } return result; } Alarm::List CalendarResources::alarms( const KDateTime &from, const KDateTime &to ) { Alarm::List result; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { result += (*it)->alarms( from, to ); } return result; } /****************************** PROTECTED METHODS ****************************/ Event::List CalendarResources::rawEventsForDate( const QDate &date, const KDateTime::Spec &timeSpec, EventSortField sortField, SortDirection sortDirection ) { Event::List result; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { d->appendIncidences( result, (*it)->rawEventsForDate( date, timeSpec ), *it ); } return sortEvents( &result, sortField, sortDirection ); } Event::List CalendarResources::rawEvents( const QDate &start, const QDate &end, const KDateTime::Spec &timeSpec, bool inclusive ) { Event::List result; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { d->appendIncidences( result, (*it)->rawEvents( start, end, timeSpec, inclusive ), *it ); } return result; } Event::List CalendarResources::rawEventsForDate( const KDateTime &kdt ) { // @TODO: Remove the code duplication by the resourcemap iteration block. Event::List result; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { d->appendIncidences( result, (*it)->rawEventsForDate( kdt ), *it ); } return result; } Event::List CalendarResources::rawEvents( EventSortField sortField, SortDirection sortDirection ) { Event::List result; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { d->appendIncidences( result, (*it)->rawEvents( EventSortUnsorted ), *it ); } return sortEvents( &result, sortField, sortDirection ); } bool CalendarResources::addJournal( Journal *journal ) { return addIncidence( journal ); } bool CalendarResources::deleteJournal( Journal *journal ) { bool status; if ( d->mResourceMap.find( journal ) != d->mResourceMap.end() ) { status = d->mResourceMap[journal]->deleteJournal( journal ); if ( status ) { d->mPendingDeleteFromResourceMap = true; } } else { CalendarResourceManager::ActiveIterator it; status = false; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { status = (*it)->deleteJournal( journal ) || status; } } setModified( status ); return status; } void CalendarResources::deleteAllJournals() { CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { (*it)->deleteAllJournals(); } } bool CalendarResources::addJournal( Journal *journal, ResourceCalendar *resource ) { return addIncidence( journal, resource ); } Journal *CalendarResources::journal( const QString &uid ) { CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { Journal *journal = (*it)->journal( uid ); if ( journal ) { d->mResourceMap[journal] = *it; return journal; } } // Not found return 0; } Journal::List CalendarResources::rawJournals( JournalSortField sortField, SortDirection sortDirection ) { Journal::List result; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { d->appendIncidences( result, (*it)->rawJournals( JournalSortUnsorted ), *it ); } return sortJournals( &result, sortField, sortDirection ); } Journal::List CalendarResources::rawJournalsForDate( const QDate &date ) { Journal::List result; CalendarResourceManager::ActiveIterator it; for ( it = d->mManager->activeBegin(); it != d->mManager->activeEnd(); ++it ) { d->appendIncidences( result, (*it)->rawJournalsForDate( date ), *it ); } return result; } //@cond PRIVATE template< class IncidenceList > void CalendarResources::Private::appendIncidences( IncidenceList &result, const IncidenceList &extra, ResourceCalendar *resource ) { result += extra; for ( typename IncidenceList::ConstIterator it = extra.begin(); it != extra.end(); ++it ) { mResourceMap[ *it ] = resource; } } //@endcond void CalendarResources::connectResource( ResourceCalendar *resource ) { connect( resource, SIGNAL( resourceChanged( ResourceCalendar * ) ), SIGNAL( calendarChanged() ) ); connect( resource, SIGNAL( resourceSaved( ResourceCalendar * ) ), SIGNAL( calendarSaved() ) ); connect( resource, SIGNAL( resourceLoadError( ResourceCalendar *, const QString & ) ), SLOT( slotLoadError( ResourceCalendar *, const QString & ) ) ); connect( resource, SIGNAL( resourceSaveError( ResourceCalendar *, const QString & ) ), SLOT( slotSaveError( ResourceCalendar *, const QString & ) ) ); } ResourceCalendar *CalendarResources::resource( Incidence *incidence ) { if ( d->mResourceMap.find( incidence ) != d->mResourceMap.end() ) { return d->mResourceMap[ incidence ]; } return 0; } void CalendarResources::resourceAdded( ResourceCalendar *resource ) { if ( !resource->isActive() ) { return; } if ( resource->open() ) { resource->load(); } connectResource( resource ); emit signalResourceAdded( resource ); } void CalendarResources::resourceModified( ResourceCalendar *resource ) { emit signalResourceModified( resource ); } void CalendarResources::resourceDeleted( ResourceCalendar *resource ) { emit signalResourceDeleted( resource ); } void CalendarResources::doSetTimeSpec( const KDateTime::Spec &timeSpec ) { // set the timezone for all resources. Otherwise we'll have those terrible // tz troubles ;-(( CalendarResourceManager::Iterator i1; for ( i1 = d->mManager->begin(); i1 != d->mManager->end(); ++i1 ) { (*i1)->setTimeSpec( timeSpec ); } } CalendarResources::Ticket::Ticket( ResourceCalendar *resource ) : d( new KCal::CalendarResources::Ticket::Private( resource ) ) { } CalendarResources::Ticket::~Ticket() { delete d; } CalendarResources::Ticket *CalendarResources::requestSaveTicket( ResourceCalendar *resource ) { KABC::Lock *lock = resource->lock(); if ( !lock ) { return 0; } if ( lock->lock() ) { return new Ticket( resource ); } else { return 0; } } ResourceCalendar *CalendarResources::Ticket::resource() const { return d->mResource; } bool CalendarResources::save( Ticket *ticket, Incidence *incidence ) { if ( !ticket || !ticket->resource() ) { return false; } // @TODO: Check if the resource was changed at all. If not, don't save. if ( ticket->resource()->save( incidence ) ) { releaseSaveTicket( ticket ); return true; } return false; } void CalendarResources::releaseSaveTicket( Ticket *ticket ) { ticket->resource()->lock()->unlock(); delete ticket; } bool CalendarResources::beginChange( Incidence *incidence ) { ResourceCalendar *r = resource( incidence ); if ( !r ) { r = d->mDestinationPolicy->destination( incidence ); if ( !r ) { kError() << "Unable to get destination resource."; return false; } d->mResourceMap[ incidence ] = r; } d->mPendingDeleteFromResourceMap = false; int count = incrementChangeCount( r ); if ( count == 1 ) { Ticket *ticket = requestSaveTicket( r ); if ( !ticket ) { kDebug() << "unable to get ticket."; decrementChangeCount( r ); return false; } else { d->mTickets[ r ] = ticket; } } return true; } bool CalendarResources::endChange( Incidence *incidence ) { ResourceCalendar *r = resource( incidence ); if ( !r ) { return false; } int count = decrementChangeCount( r ); if ( d->mPendingDeleteFromResourceMap ) { d->mResourceMap.remove( incidence ); d->mPendingDeleteFromResourceMap = false; } if ( count == 0 ) { bool ok = save( d->mTickets[ r ], incidence ); if ( ok ) { d->mTickets.remove( r ); } else { return false; } } return true; } +void CalendarResources::beginAddingIncidences() +{ + d->mAddingInProgress = true; +} + +void CalendarResources::endAddingIncidences() +{ + d->mAddingInProgress = false; + d->mLastUsedResource = 0; +} + int CalendarResources::incrementChangeCount( ResourceCalendar *r ) { if ( !d->mChangeCounts.contains( r ) ) { d->mChangeCounts.insert( r, 0 ); } int count = d->mChangeCounts[ r ]; ++count; d->mChangeCounts[ r ] = count; return count; } int CalendarResources::decrementChangeCount( ResourceCalendar *r ) { if ( !d->mChangeCounts.contains( r ) ) { kError() << "No change count for resource."; return 0; } int count = d->mChangeCounts[ r ]; --count; if ( count < 0 ) { kError() << "Can't decrement change count. It already is 0."; count = 0; } d->mChangeCounts[ r ] = count; return count; } void CalendarResources::slotLoadError( ResourceCalendar *r, const QString &err ) { Q_UNUSED( r ); emit signalErrorMessage( err ); } void CalendarResources::slotSaveError( ResourceCalendar *r, const QString &err ) { Q_UNUSED( r ); emit signalErrorMessage( err ); } diff --git a/kcal/calendarresources.h b/kcal/calendarresources.h index 7bf286183..ef4a64f53 100644 --- a/kcal/calendarresources.h +++ b/kcal/calendarresources.h @@ -1,745 +1,759 @@ /* This file is part of the kcal library. Copyright (c) 2003 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the CalendarResources class. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ */ #ifndef KCAL_CALENDARRESOURCES_H #define KCAL_CALENDARRESOURCES_H #include #include "calendar.h" #include "exceptions.h" #include "resourcecalendar.h" #include "kcal_export.h" class QWidget; namespace KCal { /** @brief This class provides a Calendar which is composed of other Calendars known as "Resources". Examples of Calendar Resources are: - Calendars stored as local ICS formatted files - a set of incidences (one-per-file) within a local directory - birthdays and anniversaries contained in an addressbook */ class KCAL_EXPORT CalendarResources : public Calendar, public KRES::ManagerObserver { Q_OBJECT public: /** @class DestinationPolicy */ class DestinationPolicy { public: /** Constructs a destination policy. @param manager is a pointer to the CalendarResourceManager. @param parent is a pointer to a QWidget to use for new dialogs. */ explicit DestinationPolicy( CalendarResourceManager *manager, QWidget *parent = 0 ); /** Destructor. */ virtual ~DestinationPolicy(); /** Returns parent widget to use for new dialogs. */ virtual QWidget *parent(); /** Sets the parent widget for new dialogs. @param parent is a pointer to a QWidget containing the parent. */ virtual void setParent( QWidget *parent ); /** Returns the destination ResourceCalendar for the specified incidence. @param incidence is a pointer to a valid Incidence object. */ virtual ResourceCalendar *destination( Incidence *incidence ) = 0; /** Return true if we have resources configure. Otherwise returns false. @since 4.3 */ bool hasCalendarResources(); protected: /** Returns the CalendarResourceManager used by this calendar. */ CalendarResourceManager *resourceManager(); private: //@cond PRIVATE Q_DISABLE_COPY( DestinationPolicy ) class Private; Private *d; //@endcond }; /** @class StandardDestinationPolicy */ class StandardDestinationPolicy : public DestinationPolicy { public: /** Constructs a standard destination policy. @param manager is a pointer to the CalendarResourceManager. @param parent is a pointer to a QWidget to use for new dialogs. */ explicit StandardDestinationPolicy( CalendarResourceManager *manager, QWidget *parent = 0 ); /** Destructor. */ virtual ~StandardDestinationPolicy(); /** Returns the destination ResourceCalendar for the specified incidence. @param incidence is a pointer to a valid Incidence object. */ ResourceCalendar *destination( Incidence *incidence ); private: //@cond PRIVATE Q_DISABLE_COPY( StandardDestinationPolicy ) class Private; Private *d; //@endcond }; /** @class AskDestinationPolicy */ class AskDestinationPolicy : public DestinationPolicy { public: /** Constructs an Ask destination policy. @param manager is a pointer to the CalendarResourceManager. @param parent is a pointer to a QWidget to use for new dialogs. */ explicit AskDestinationPolicy( CalendarResourceManager *manager, QWidget *parent = 0 ); /** Destructor. */ virtual ~AskDestinationPolicy(); /** Returns the destination ResourceCalendar for the specified incidence. @param incidence is a pointer to a valid Incidence object. */ ResourceCalendar *destination( Incidence *incidence ); private: //@cond PRIVATE Q_DISABLE_COPY( AskDestinationPolicy ) class Private; Private *d; //@endcond }; /** @class Ticket */ class Ticket { friend class CalendarResources; public: /** Returns the ResourceCalendar associated with the ticket. */ ResourceCalendar *resource() const; /** Destructor. */ ~Ticket(); private: /** Constructs a Ticket for a ResourceCalendar. @param resource is a pointer to a valid ResourceCalendar object. */ Ticket( ResourceCalendar *resource ); //@cond PRIVATE Q_DISABLE_COPY( Ticket ) class Private; Private *d; //@endcond }; /** Construct CalendarResource object using a time specification (time zone, etc.) and a Family name. @param timeSpec is a time specification which is used for creating or modifying incidences in the Calendar. It is also used for viewing incidences (see setViewTimeSpec()). @param family is any QString representing a unique name. */ CalendarResources( const KDateTime::Spec &timeSpec, const QString &family = QLatin1String( "calendar" ) ); /** Construct CalendarResource object using a time zone ID and a Family name. @param timeZoneId is used for creating or modifying incidences in the Calendar. It is also used for viewing incidences. The time zone does not alter existing incidences. @param family is any QString representing a unique name. */ CalendarResources( const QString &timeZoneId, const QString &family = QLatin1String( "calendar" ) ); /** Destroys the Calendar Resources. */ ~CalendarResources(); /** Clears the exception status. @since 4.2 */ void clearException(); /** Returns an exception, if there is any, containing information about the last error that occurred. @since 4.2 */ ErrorFormat *exception(); /** Loads all Incidences from the Resources. The Resources must be added first using either readConfig(KConfig *config), which adds the system Resources, or manually using resourceAdded(ResourceCalendar *resource). */ void load(); /** * Reloads all Incidences from all Resources. * @return true if the reload was successful; otherwise failure. */ bool reload(); /** @copydoc Calendar::close() */ void close(); /** Saves this Calendar. If the save is successful the Ticket is deleted. Otherwise, the caller must release the Ticket with releaseSaveTicket() to abandon the save operation or call save() to try the save again. @param ticket is a pointer to the Ticket object. @param incidence is a pointer to the Incidence object. If incidence is null, save the entire Calendar (all Resources) else only the specified Incidence is saved. @return true if the save was successful; false otherwise. */ virtual bool save( Ticket *ticket, Incidence *incidence = 0 ); /** @copydoc Calendar::save() */ bool save(); /** @copydoc Calendar::isSaving() */ bool isSaving(); /** Returns the CalendarResourceManager used by this calendar. */ CalendarResourceManager *resourceManager() const; /** Returns the Resource associated with a specified Incidence. @param incidence is a pointer to an Incidence whose Resource is to be located. */ ResourceCalendar *resource( Incidence *incidence ); /** Reads the Resources settings from a config file. @param config The KConfig object which points to the config file. If no object is given (@p config is 0) the standard config file is used. @note Call this method before load(). */ void readConfig( KConfig *config = 0 ); /** Set the destination policy such that Incidences are always added to the standard Resource. */ void setStandardDestinationPolicy(); /** Set the destination policy such that Incidences are added to a Resource which is queried. */ void setAskDestinationPolicy(); /** Return true if we have resources configure. Otherwise returns false. @since 4.3 */ bool hasCalendarResources(); /** Returns the current parent for new dialogs. This is a bad hack, but we need to properly set the parent for the resource selection dialog. Otherwise the dialog will not be modal to the editor dialog in korganizer and the user can still work in the editor dialog (and thus crash korganizer). Afterwards we need to reset it (to avoid pointers to widgets that are already deleted) so we also need the accessor @return a pointer to the parent QWidget. @see setDialogParentWidget() */ QWidget *dialogParentWidget(); /** Set the widget parent for new dialogs. This is a bad hack, but we need to properly set the parent for the resource selection dialog. Otherwise the dialog will not be modal to the editor dialog in korganizer and the user can still work in the editor dialog (and thus crash korganizer). @param parent is a pointer to the parent QWidget. @see dialogparentWidget() */ void setDialogParentWidget( QWidget *parent ); /** Requests a ticket for saving the Calendar. If a ticket is returned the Calendar is locked for write access until save() or releaseSaveTicket() is called. @param resource is a pointer to the ResourceCalendar object. @return a pointer to a Ticket object indicating that the Calendar is locked for write access; otherwise a null pointer. @see releaseSaveTicket() */ Ticket *requestSaveTicket( ResourceCalendar *resource ); /** Releases the save Ticket. The Calendar is unlocked without saving. @param ticket is a pointer to a Ticket object. @see requestSaveTicket() */ virtual void releaseSaveTicket( Ticket *ticket ); /** Add an active Resource to the Calendar, and loads that resource if it is open. Additionally, emits the @b signalResourceAdded signal. @note This method must be public, because in-process added Resources do not emit the corresponding signal, so this method has to be called manually! @param resource is a pointer to the ResourceCalendar to add. @see signalResourceAdded() */ void resourceAdded( ResourceCalendar *resource ); // Incidence Specific Methods // /** Inserts an Incidence into the calendar. @param incidence is a pointer to the Incidence to insert. @return true if the Incidence was successfully inserted; false otherwise. @return Will also return false if there are multiple writable resources and the user declines to select one to those resources in which to save the Incidence. */ bool addIncidence( Incidence *incidence ); /** Inserts an Incidence into a Calendar Resource. @param incidence is a pointer to the Incidence to insert. @param resource is a pointer to the ResourceCalendar to be added to. @return true if the Incidence was successfully inserted; false otherwise. */ bool addIncidence( Incidence *incidence, ResourceCalendar *resource ); /** @copydoc Calendar::beginChange() */ bool beginChange( Incidence *incidence ); /** @copydoc Calendar::endChange() */ bool endChange( Incidence *incidence ); // Event Specific Methods // /** @copydoc Calendar::addEvent() */ bool addEvent( Event *event ); /** Inserts an Event into a Calendar Resource. @param event is a pointer to the Event to insert. @param resource is a pointer to the ResourceCalendar to be added to. @return true if the Event was successfully inserted; false otherwise. @note In most cases use addIncidence( Incidence *incidence, ResourceCalendar *resource ) instead. */ bool addEvent( Event *event, ResourceCalendar *resource ); /** @copydoc Calendar::deleteEvent() */ bool deleteEvent( Event *event ); /** @copydoc Calendar::deleteAllEvents() */ void deleteAllEvents(); /** @copydoc Calendar::rawEvents(EventSortField, SortDirection) */ Event::List rawEvents( EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); /** @copydoc Calendar::rawEventsForDate( const KDateTime &) */ Event::List rawEventsForDate( const KDateTime &dt ); /** @copydoc Calendar::rawEvents(const QDate &, const QDate &, const KDateTime::Spec &, bool) */ Event::List rawEvents( const QDate &start, const QDate &end, const KDateTime::Spec &timeSpec = KDateTime::Spec(), bool inclusive = false ); /** Returns an unfiltered list of all Events which occur on the given date. @param date request unfiltered Event list for this QDate only. @param timeSpec time zone etc. to interpret @p date, or the calendar's default time spec if none is specified @param sortField specifies the EventSortField. @param sortDirection specifies the SortDirection. @return the list of unfiltered Events occurring on the specified QDate. */ Event::List rawEventsForDate( const QDate &date, const KDateTime::Spec &timeSpec = KDateTime::Spec(), EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); /** @copydoc Calendar::event() */ Event *event( const QString &uid ); // Todo Specific Methods // /** @copydoc Calendar::addTodo() */ bool addTodo( Todo *todo ); /** Inserts a Todo into a Calendar Resource. @param todo is a pointer to the Todo to insert. @param resource is a pointer to the ResourceCalendar to be added to. @return true if the Todo was successfully inserted; false otherwise. @note In most cases use addIncidence( Incidence *incidence, ResourceCalendar *resource ) instead. */ bool addTodo( Todo *todo, ResourceCalendar *resource ); /** @copydoc Calendar::deleteTodo() */ bool deleteTodo( Todo *todo ); /** @copydoc Calendar::deleteAllTodos() */ void deleteAllTodos(); /** @copydoc Calendar::rawTodos() */ Todo::List rawTodos( TodoSortField sortField = TodoSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); /** @copydoc Calendar::rawTodosForDate() */ Todo::List rawTodosForDate( const QDate &date ); /** @copydoc Calendar::todo() */ Todo *todo( const QString &uid ); // Journal Specific Methods // /** @copydoc Calendar::addJournal() */ bool addJournal( Journal *journal ); /** Inserts a Journal into a Calendar Resource. @param journal is a pointer to the Journal to insert. @param resource is a pointer to the ResourceCalendar to be added to. @return true if the Journal was successfully inserted; false otherwise. @note In most cases use addIncidence( Incidence *incidence, ResourceCalendar *resource ) instead. */ bool addJournal( Journal *journal, ResourceCalendar *resource ); /** @copydoc Calendar::deleteJournal() */ bool deleteJournal( Journal *journal ); /** @copydoc Calendar::deleteAllJournals() */ void deleteAllJournals(); /** @copydoc Calendar::rawJournals() */ Journal::List rawJournals( JournalSortField sortField = JournalSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); /** @copydoc Calendar::rawJournalsForDate() */ Journal::List rawJournalsForDate( const QDate &date ); /** @copydoc Calendar::journal() */ Journal *journal( const QString &uid ); // Alarm Specific Methods // /** @copydoc Calendar::alarms() */ Alarm::List alarms( const KDateTime &from, const KDateTime &to ); /** Return a list of Alarms that occur before the specified timestamp. @param to is the ending timestamp. @return the list of Alarms occurring before the specified KDateTime. */ Alarm::List alarmsTo( const KDateTime &to ); using QObject::event; // prevent warning about hidden virtual method Q_SIGNALS: /** Signals that the Resource has been modified. @param resource is a pointer to a ResourceCalendar that was changed. @see resourceModified() */ void signalResourceModified( ResourceCalendar *resource ); /** Signals that an Incidence has been inserted to the Resource. @param resource is a pointer to a ResourceCalendar that was added. @see resourceAdded() */ void signalResourceAdded( ResourceCalendar *resource ); /** Signals that an Incidence has been removed from the Resource. @param resource is a pointer to a ResourceCalendar that was removed. @see resourceDeleted() */ void signalResourceDeleted( ResourceCalendar *resource ); /** Signals an error message. @param err is the error message. */ void signalErrorMessage( const QString &err ); protected: /** Connects all necessary signals and slots to the resource. @param resource is a pointer to a ResourceCalendar. */ void connectResource( ResourceCalendar *resource ); /** Emits the @b signalResourceModified signal for the specified @p resource. @param resource is a pointer to a ResourceCalendar that was changed. @see signalResourceDeleted() */ void resourceModified( ResourceCalendar *resource ); /** Emits the @b signalResourceDeleted signal for the specified @p resource. @param resource is a pointer to a ResourceCalendar that was removed. @see signalResourceModified() */ void resourceDeleted( ResourceCalendar *resource ); /** @copydoc Calendar::doSetTimeSpec() */ virtual void doSetTimeSpec( const KDateTime::Spec &timeSpec ); /** Increment the number of times this Resource has been changed by 1. @param resource is a pointer to the ResourceCalendar to be counted. @return the new number of times this Resource has been changed. @see decrementChangeCount() */ int incrementChangeCount( ResourceCalendar *resource ); /** Decrement the number of times this Resource has been changed by 1. @param resource is a pointer to the ResourceCalendar to be counted. @return the new number of times this Resource has been changed. @see incrementChangeCount() */ int decrementChangeCount( ResourceCalendar *resource ); protected Q_SLOTS: /** Emits the @b signalErrorMessage signal with an error message when an error occurs loading a ResourceCalendar. @param resource is a pointer to the ResourceCalendar that failed to load. @param err is the error message. @see slotSaveError() */ void slotLoadError( ResourceCalendar *resource, const QString &err ); /** Emits the @b signalErrorMessage signal with an error message when an error occurs saving a ResourceCalendar. @param resource is a pointer to the ResourceCalendar that failed to save. @param err is the error message. @see slotLoadError() */ void slotSaveError( ResourceCalendar *resource, const QString &err ); + /** + All addIncidence( Incidence * ), addTodo( Todo * ) addEvent( Event * ) + and addJournal( Journal * ) calls made between beginAddingIncidences() + and endAddingIncidences() will only ask the user to choose a resource once. + @since 4.4 + */ + void beginAddingIncidences(); + + /** + @see beginAddingIncidences() + @since 4.4 + */ + void endAddingIncidences(); + private: //@cond PRIVATE Q_DISABLE_COPY( CalendarResources ) class Private; Private *d; //@endcond }; } #endif diff --git a/kcal/icalformat_p.cpp b/kcal/icalformat_p.cpp index 5e232b740..0662cf4a9 100644 --- a/kcal/icalformat_p.cpp +++ b/kcal/icalformat_p.cpp @@ -1,2671 +1,2675 @@ /* This file is part of the kcal library. Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (c) 2006 David Jarvie This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the internal ICalFormat classes. @brief This class provides the libical dependent functions for ICalFormat. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ @author David Jarvie \ */ #include "icalformat_p.h" #include "icalformat.h" #include "icaltimezones.h" #include "calendar.h" #include "compat.h" #include "journal.h" extern "C" { #include #include #include } #include #include #include #include #include #include #include #include #include #include #include using namespace KCal; /* Static helpers */ /* static void _dumpIcaltime( const icaltimetype& t) { kDebug() << "--- Y:" << t.year << "M:" << t.month << "D:" << t.day; kDebug() << "--- H:" << t.hour << "M:" << t.minute << "S:" << t.second; kDebug() << "--- isUtc:" << icaltime_is_utc( t ); kDebug() << "--- zoneId:" << icaltimezone_get_tzid( const_cast( t.zone ) ); } */ //@cond PRIVATE static QString quoteForParam( const QString &text ) { QString tmp = text; tmp.remove( '"' ); if ( tmp.contains( ';' ) || tmp.contains( ':' ) || tmp.contains( ',' ) ) { return tmp; // libical quotes in this case already, see icalparameter_as_ical_string() } return QString::fromLatin1( "\"" ) + tmp + QString::fromLatin1( "\"" ); } const int gSecondsPerMinute = 60; const int gSecondsPerHour = gSecondsPerMinute * 60; const int gSecondsPerDay = gSecondsPerHour * 24; const int gSecondsPerWeek = gSecondsPerDay * 7; class ToComponentVisitor : public IncidenceBase::Visitor { public: ToComponentVisitor( ICalFormatImpl *impl, iTIPMethod m ) : mImpl( impl ), mComponent( 0 ), mMethod( m ) {} bool visit( Event *e ) { mComponent = mImpl->writeEvent( e ); return true; } bool visit( Todo *e ) { mComponent = mImpl->writeTodo( e ); return true; } bool visit( Journal *e ) { mComponent = mImpl->writeJournal( e ); return true; } bool visit( FreeBusy *fb ) { mComponent = mImpl->writeFreeBusy( fb, mMethod ); return true; } icalcomponent *component() { return mComponent; } private: ICalFormatImpl *mImpl; icalcomponent *mComponent; iTIPMethod mMethod; }; class ICalFormatImpl::Private { public: Private( ICalFormatImpl *impl, ICalFormat *parent ) : mImpl( impl ), mParent( parent ), mCompat( new Compat ) {} ~Private() { delete mCompat; } void writeIncidenceBase( icalcomponent *parent, IncidenceBase * ); void readIncidenceBase( icalcomponent *parent, IncidenceBase * ); void writeCustomProperties( icalcomponent *parent, CustomProperties * ); void readCustomProperties( icalcomponent *parent, CustomProperties * ); ICalFormatImpl *mImpl; ICalFormat *mParent; QString mLoadedProductId; // PRODID string loaded from calendar file Event::List mEventsRelate; // events with relations Todo::List mTodosRelate; // todos with relations Compat *mCompat; }; //@endcond inline icaltimetype ICalFormatImpl::writeICalUtcDateTime ( const KDateTime &dt ) { return writeICalDateTime( dt.toUtc() ); } ICalFormatImpl::ICalFormatImpl( ICalFormat *parent ) : d( new Private( this, parent ) ) { } ICalFormatImpl::~ICalFormatImpl() { delete d; } QString ICalFormatImpl::loadedProductId() const { return d->mLoadedProductId; } icalcomponent *ICalFormatImpl::writeIncidence( IncidenceBase *incidence, iTIPMethod method ) { ToComponentVisitor v( this, method ); if ( incidence->accept(v) ) { return v.component(); } else { return 0; } } icalcomponent *ICalFormatImpl::writeTodo( Todo *todo, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList ) { QString tmpStr; QStringList tmpStrList; icalcomponent *vtodo = icalcomponent_new( ICAL_VTODO_COMPONENT ); writeIncidence( vtodo, todo, tzlist, tzUsedList ); // due date icalproperty *prop; if ( todo->hasDueDate() ) { icaltimetype due; if ( todo->allDay() ) { due = writeICalDate( todo->dtDue( true ).date() ); prop = icalproperty_new_due(due); } else { prop = writeICalDateTimeProperty( ICAL_DUE_PROPERTY, todo->dtDue(true), tzlist, tzUsedList ); } icalcomponent_add_property( vtodo, prop ); } // start time if ( todo->hasStartDate() ) { icaltimetype start; if ( todo->allDay() ) { start = writeICalDate( todo->dtStart( true ).date() ); prop = icalproperty_new_dtstart( start ); } else { prop = writeICalDateTimeProperty( ICAL_DTSTART_PROPERTY, todo->dtStart( true ), tzlist, tzUsedList ); } icalcomponent_add_property( vtodo, prop ); } // completion date (UTC) if ( todo->isCompleted() ) { if ( !todo->hasCompletedDate() ) { // If the todo was created by KOrganizer<2.2 it does not have // a correct completion date. Set one now. todo->setCompleted( KDateTime::currentUtcDateTime() ); } icaltimetype completed = writeICalUtcDateTime( todo->completed() ); icalcomponent_add_property( vtodo, icalproperty_new_completed ( completed ) ); } icalcomponent_add_property( vtodo, icalproperty_new_percentcomplete( todo->percentComplete() ) ); if ( todo->recurs() ) { icalcomponent_add_property( vtodo, writeICalDateTimeProperty( ICAL_RECURRENCEID_PROPERTY, todo->dtDue(), tzlist, tzUsedList ) ); } return vtodo; } icalcomponent *ICalFormatImpl::writeEvent( Event *event, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList ) { icalcomponent *vevent = icalcomponent_new( ICAL_VEVENT_COMPONENT ); writeIncidence( vevent, event, tzlist, tzUsedList ); // start time icalproperty *prop; icaltimetype start; if ( event->allDay() ) { start = writeICalDate( event->dtStart().date() ); prop = icalproperty_new_dtstart( start ); } else { prop = writeICalDateTimeProperty( ICAL_DTSTART_PROPERTY, event->dtStart(), tzlist, tzUsedList ); } icalcomponent_add_property( vevent, prop ); if ( event->hasEndDate() ) { // End time. // RFC2445 says that if DTEND is present, it has to be greater than DTSTART. icaltimetype end; KDateTime dt = event->dtEnd(); if ( event->allDay() ) { // +1 day because end date is non-inclusive. end = writeICalDate( dt.date().addDays( 1 ) ); icalcomponent_add_property( vevent, icalproperty_new_dtend(end) ); } else { if ( dt != event->dtStart() ) { icalcomponent_add_property( vevent, writeICalDateTimeProperty( ICAL_DTEND_PROPERTY, dt, tzlist, tzUsedList ) ); } } } // TODO: resources #if 0 // resources QStringList tmpStrList = anEvent->resources(); QString tmpStr = tmpStrList.join( ";" ); if ( !tmpStr.isEmpty() ) { addPropValue( vevent, VCResourcesProp, tmpStr.toUtf8() ); } #endif // Transparency switch( event->transparency() ) { case Event::Transparent: icalcomponent_add_property( vevent, icalproperty_new_transp( ICAL_TRANSP_TRANSPARENT ) ); break; case Event::Opaque: icalcomponent_add_property( vevent, icalproperty_new_transp( ICAL_TRANSP_OPAQUE ) ); break; } return vevent; } icalcomponent *ICalFormatImpl::writeFreeBusy( FreeBusy *freebusy, iTIPMethod method ) { icalcomponent *vfreebusy = icalcomponent_new( ICAL_VFREEBUSY_COMPONENT ); d->writeIncidenceBase( vfreebusy, freebusy ); icalcomponent_add_property( vfreebusy, icalproperty_new_dtstart( writeICalUtcDateTime( freebusy->dtStart() ) ) ); icalcomponent_add_property( vfreebusy, icalproperty_new_dtend( writeICalUtcDateTime( freebusy->dtEnd() ) ) ); if ( method == iTIPRequest ) { icalcomponent_add_property( vfreebusy, icalproperty_new_uid( freebusy->uid().toUtf8() ) ); } //Loops through all the periods in the freebusy object QList list = freebusy->busyPeriods(); icalperiodtype period = icalperiodtype_null_period(); for ( int i = 0, count = list.count(); i < count; ++i ) { period.start = writeICalUtcDateTime( list[i].start() ); if ( list[i].hasDuration() ) { period.duration = writeICalDuration( list[i].duration() ); } else { period.end = writeICalUtcDateTime( list[i].end() ); } icalcomponent_add_property( vfreebusy, icalproperty_new_freebusy( period ) ); } return vfreebusy; } icalcomponent *ICalFormatImpl::writeJournal( Journal *journal, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList ) { icalcomponent *vjournal = icalcomponent_new( ICAL_VJOURNAL_COMPONENT ); writeIncidence( vjournal, journal, tzlist, tzUsedList ); // start time icalproperty *prop; KDateTime dt = journal->dtStart(); if ( dt.isValid() ) { icaltimetype start; if ( journal->allDay() ) { start = writeICalDate( dt.date() ); prop = icalproperty_new_dtstart( start ); } else { prop = writeICalDateTimeProperty( ICAL_DTSTART_PROPERTY, dt, tzlist, tzUsedList ); } icalcomponent_add_property( vjournal, prop ); } return vjournal; } void ICalFormatImpl::writeIncidence( icalcomponent *parent, Incidence *incidence, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList ) { if ( incidence->schedulingID() != incidence->uid() ) { // We need to store the UID in here. The rawSchedulingID will // go into the iCal UID component incidence->setCustomProperty( "LIBKCAL", "ID", incidence->uid() ); } else { incidence->removeCustomProperty( "LIBKCAL", "ID" ); } d->writeIncidenceBase( parent, incidence ); // creation date icalcomponent_add_property( parent, writeICalDateTimeProperty( ICAL_CREATED_PROPERTY, incidence->created() ) ); // unique id // If the scheduling ID is different from the real UID, the real // one is stored on X-REALID above if ( !incidence->schedulingID().isEmpty() ) { icalcomponent_add_property( parent, icalproperty_new_uid( incidence->schedulingID().toUtf8() ) ); } // revision if ( incidence->revision() > 0 ) { // 0 is default, so don't write that out icalcomponent_add_property( parent, icalproperty_new_sequence( incidence->revision() ) ); } // last modification date if ( incidence->lastModified().isValid() ) { icalcomponent_add_property( parent, writeICalDateTimeProperty( ICAL_LASTMODIFIED_PROPERTY, incidence->lastModified() ) ); } // description if ( !incidence->description().isEmpty() ) { icalcomponent_add_property( parent, writeDescription( incidence->description(), incidence->descriptionIsRich() ) ); } // summary if ( !incidence->summary().isEmpty() ) { icalcomponent_add_property( parent, writeSummary( incidence->summary(), incidence->summaryIsRich() ) ); } // location if ( !incidence->location().isEmpty() ) { icalcomponent_add_property( parent, writeLocation( incidence->location(), incidence->locationIsRich() ) ); } // status icalproperty_status status = ICAL_STATUS_NONE; switch ( incidence->status() ) { case Incidence::StatusTentative: status = ICAL_STATUS_TENTATIVE; break; case Incidence::StatusConfirmed: status = ICAL_STATUS_CONFIRMED; break; case Incidence::StatusCompleted: status = ICAL_STATUS_COMPLETED; break; case Incidence::StatusNeedsAction: status = ICAL_STATUS_NEEDSACTION; break; case Incidence::StatusCanceled: status = ICAL_STATUS_CANCELLED; break; case Incidence::StatusInProcess: status = ICAL_STATUS_INPROCESS; break; case Incidence::StatusDraft: status = ICAL_STATUS_DRAFT; break; case Incidence::StatusFinal: status = ICAL_STATUS_FINAL; break; case Incidence::StatusX: { icalproperty *p = icalproperty_new_status( ICAL_STATUS_X ); icalvalue_set_x( icalproperty_get_value( p ), incidence->statusStr().toUtf8() ); icalcomponent_add_property( parent, p ); break; } case Incidence::StatusNone: default: break; } if ( status != ICAL_STATUS_NONE ) { icalcomponent_add_property( parent, icalproperty_new_status( status ) ); } // secrecy icalproperty_class secClass; switch ( incidence->secrecy() ) { case Incidence::SecrecyPublic: secClass = ICAL_CLASS_PUBLIC; break; case Incidence::SecrecyConfidential: secClass = ICAL_CLASS_CONFIDENTIAL; break; case Incidence::SecrecyPrivate: default: secClass = ICAL_CLASS_PRIVATE; break; } if ( secClass != ICAL_CLASS_PUBLIC ) { icalcomponent_add_property( parent, icalproperty_new_class( secClass ) ); } // geo if ( incidence->hasGeo() ) { icalgeotype geo; geo.lat = incidence->geoLatitude(); geo.lon = incidence->geoLongitude(); icalcomponent_add_property( parent, icalproperty_new_geo( geo ) ); } // priority if ( incidence->priority() > 0 ) { // 0 is undefined priority icalcomponent_add_property( parent, icalproperty_new_priority( incidence->priority() ) ); } // categories QString categories = incidence->categories().join( "," ); if ( !categories.isEmpty() ) { icalcomponent_add_property( parent, icalproperty_new_categories( categories.toUtf8() ) ); } // related event if ( !incidence->relatedToUid().isEmpty() ) { icalcomponent_add_property( parent, icalproperty_new_relatedto( incidence->relatedToUid().toUtf8() ) ); } RecurrenceRule::List rrules( incidence->recurrence()->rRules() ); RecurrenceRule::List::ConstIterator rit; for ( rit = rrules.constBegin(); rit != rrules.constEnd(); ++rit ) { icalcomponent_add_property( parent, icalproperty_new_rrule( writeRecurrenceRule( (*rit) ) ) ); } RecurrenceRule::List exrules( incidence->recurrence()->exRules() ); RecurrenceRule::List::ConstIterator exit; for ( exit = exrules.constBegin(); exit != exrules.constEnd(); ++exit ) { icalcomponent_add_property( parent, icalproperty_new_exrule( writeRecurrenceRule( (*exit) ) ) ); } DateList dateList = incidence->recurrence()->exDates(); DateList::ConstIterator exIt; for ( exIt = dateList.constBegin(); exIt != dateList.constEnd(); ++exIt ) { icalcomponent_add_property( parent, icalproperty_new_exdate( writeICalDate(*exIt) ) ); } DateTimeList dateTimeList = incidence->recurrence()->exDateTimes(); DateTimeList::ConstIterator extIt; for ( extIt = dateTimeList.constBegin(); extIt != dateTimeList.constEnd(); ++extIt ) { icalcomponent_add_property( parent, writeICalDateTimeProperty( ICAL_EXDATE_PROPERTY, *extIt, tzlist, tzUsedList ) ); } dateList = incidence->recurrence()->rDates(); DateList::ConstIterator rdIt; for ( rdIt = dateList.constBegin(); rdIt != dateList.constEnd(); ++rdIt ) { icalcomponent_add_property( parent, icalproperty_new_rdate( writeICalDatePeriod(*rdIt) ) ); } dateTimeList = incidence->recurrence()->rDateTimes(); DateTimeList::ConstIterator rdtIt; for ( rdtIt = dateTimeList.constBegin(); rdtIt != dateTimeList.constEnd(); ++rdtIt ) { icalcomponent_add_property( parent, writeICalDateTimeProperty( ICAL_RDATE_PROPERTY, *rdtIt, tzlist, tzUsedList ) ); } // attachments Attachment::List attachments = incidence->attachments(); Attachment::List::ConstIterator atIt; for ( atIt = attachments.constBegin(); atIt != attachments.constEnd(); ++atIt ) { icalcomponent_add_property( parent, writeAttachment( *atIt ) ); } // alarms Alarm::List::ConstIterator alarmIt; for ( alarmIt = incidence->alarms().constBegin(); alarmIt != incidence->alarms().constEnd(); ++alarmIt ) { if ( (*alarmIt)->enabled() ) { icalcomponent_add_component( parent, writeAlarm( *alarmIt ) ); } } // duration if ( incidence->hasDuration() ) { icaldurationtype duration; duration = writeICalDuration( incidence->duration() ); icalcomponent_add_property( parent, icalproperty_new_duration( duration ) ); } } //@cond PRIVATE void ICalFormatImpl::Private::writeIncidenceBase( icalcomponent *parent, IncidenceBase *incidenceBase ) { icalcomponent_add_property( parent, writeICalDateTimeProperty( ICAL_DTSTAMP_PROPERTY, KDateTime::currentUtcDateTime() ) ); // organizer stuff if ( !incidenceBase->organizer().isEmpty() ) { icalproperty *p = mImpl->writeOrganizer( incidenceBase->organizer() ); if ( p ) { icalcomponent_add_property( parent, p ); } } // attendees if ( incidenceBase->attendeeCount() > 0 ) { Attendee::List::ConstIterator it; for ( it = incidenceBase->attendees().constBegin(); it != incidenceBase->attendees().constEnd(); ++it ) { icalproperty *p = mImpl->writeAttendee( *it ); if ( p ) { icalcomponent_add_property( parent, p ); } } } // comments QStringList comments = incidenceBase->comments(); for ( QStringList::const_iterator it = comments.constBegin(); it != comments.constEnd(); ++it ) { icalcomponent_add_property( parent, icalproperty_new_comment( (*it).toUtf8() ) ); } // custom properties writeCustomProperties( parent, incidenceBase ); } void ICalFormatImpl::Private::writeCustomProperties( icalcomponent *parent, CustomProperties *properties ) { const QMap custom = properties->customProperties(); for ( QMap::ConstIterator c = custom.begin(); c != custom.end(); ++c ) { icalproperty *p = icalproperty_new_x( c.value().toUtf8() ); if ( !c.key().startsWith( "X-KDE" ) && //krazy:exclude=strings !c.key().startsWith( "X-LibKCal" ) && //krazy:exclude=strings !c.key().startsWith( "X-MICROSOFT" ) && //krazy:exclude=strings !c.key().startsWith( "X-MOZILLA" ) && //krazy:exclude=strings !c.key().startsWith( "X-PILOT" ) ) { //krazy:exclude=strings // use text values for the typical X-FOO property. // except for vendor specific X-FOO properties. icalvalue *text = icalvalue_new_text( c.value().toUtf8().data() ); icalproperty_set_value( p, text ); } icalproperty_set_x_name( p, c.key() ); icalcomponent_add_property( parent, p ); } } //@endcond icalproperty *ICalFormatImpl::writeOrganizer( const Person &organizer ) { if ( organizer.email().isEmpty() ) { return 0; } icalproperty *p = icalproperty_new_organizer( "MAILTO:" + organizer.email().toUtf8() ); if ( !organizer.name().isEmpty() ) { icalproperty_add_parameter( p, icalparameter_new_cn( quoteForParam( organizer.name() ).toUtf8() ) ); } // TODO: Write dir, sent-by and language return p; } icalproperty *ICalFormatImpl::writeDescription( const QString &description, bool isRich ) { icalproperty *p = icalproperty_new_description( description.toUtf8() ); if ( isRich ) { icalproperty_add_parameter( p, icalparameter_new_from_string( "X-KDE-TEXTFORMAT=HTML" ) ); } return p; } icalproperty *ICalFormatImpl::writeSummary( const QString &summary, bool isRich ) { icalproperty *p = icalproperty_new_summary( summary.toUtf8() ); if ( isRich ) { icalproperty_add_parameter( p, icalparameter_new_from_string( "X-KDE-TEXTFORMAT=HTML" ) ); } return p; } icalproperty *ICalFormatImpl::writeLocation( const QString &location, bool isRich ) { icalproperty *p = icalproperty_new_location( location.toUtf8() ); if ( isRich ) { icalproperty_add_parameter( p, icalparameter_new_from_string( "X-KDE-TEXTFORMAT=HTML" ) ); } return p; } icalproperty *ICalFormatImpl::writeAttendee( Attendee *attendee ) { if ( attendee->email().isEmpty() ) { return 0; } icalproperty *p = icalproperty_new_attendee( "mailto:" + attendee->email().toUtf8() ); if ( !attendee->name().isEmpty() ) { icalproperty_add_parameter( p, icalparameter_new_cn( quoteForParam( attendee->name() ).toUtf8() ) ); } icalproperty_add_parameter( p, icalparameter_new_rsvp( attendee->RSVP() ? ICAL_RSVP_TRUE : ICAL_RSVP_FALSE ) ); icalparameter_partstat status = ICAL_PARTSTAT_NEEDSACTION; switch ( attendee->status() ) { default: case Attendee::NeedsAction: status = ICAL_PARTSTAT_NEEDSACTION; break; case Attendee::Accepted: status = ICAL_PARTSTAT_ACCEPTED; break; case Attendee::Declined: status = ICAL_PARTSTAT_DECLINED; break; case Attendee::Tentative: status = ICAL_PARTSTAT_TENTATIVE; break; case Attendee::Delegated: status = ICAL_PARTSTAT_DELEGATED; break; case Attendee::Completed: status = ICAL_PARTSTAT_COMPLETED; break; case Attendee::InProcess: status = ICAL_PARTSTAT_INPROCESS; break; } icalproperty_add_parameter( p, icalparameter_new_partstat( status ) ); icalparameter_role role = ICAL_ROLE_REQPARTICIPANT; switch ( attendee->role() ) { case Attendee::Chair: role = ICAL_ROLE_CHAIR; break; default: case Attendee::ReqParticipant: role = ICAL_ROLE_REQPARTICIPANT; break; case Attendee::OptParticipant: role = ICAL_ROLE_OPTPARTICIPANT; break; case Attendee::NonParticipant: role = ICAL_ROLE_NONPARTICIPANT; break; } icalproperty_add_parameter( p, icalparameter_new_role( role ) ); if ( !attendee->uid().isEmpty() ) { icalparameter *icalparameter_uid = icalparameter_new_x( attendee->uid().toUtf8() ); icalparameter_set_xname( icalparameter_uid, "X-UID" ); icalproperty_add_parameter( p, icalparameter_uid ); } if ( !attendee->delegate().isEmpty() ) { icalparameter *icalparameter_delegate = icalparameter_new_delegatedto( attendee->delegate().toUtf8() ); icalproperty_add_parameter( p, icalparameter_delegate ); } if ( !attendee->delegator().isEmpty() ) { icalparameter *icalparameter_delegator = icalparameter_new_delegatedfrom( attendee->delegator().toUtf8() ); icalproperty_add_parameter( p, icalparameter_delegator ); } return p; } icalproperty *ICalFormatImpl::writeAttachment( Attachment *att ) { icalattach *attach; if ( att->isUri() ) { attach = icalattach_new_from_url( att->uri().toUtf8().data() ); } else { attach = icalattach_new_from_data ( ( unsigned char * )att->data(), 0, 0 ); } icalproperty *p = icalproperty_new_attach( attach ); if ( !att->mimeType().isEmpty() ) { icalproperty_add_parameter( p, icalparameter_new_fmttype( att->mimeType().toUtf8().data() ) ); } if ( att->isBinary() ) { icalproperty_add_parameter( p, icalparameter_new_value( ICAL_VALUE_BINARY ) ); icalproperty_add_parameter( p, icalparameter_new_encoding( ICAL_ENCODING_BASE64 ) ); } if ( att->showInline() ) { icalparameter *icalparameter_inline = icalparameter_new_x( "inline" ); icalparameter_set_xname( icalparameter_inline, "X-CONTENT-DISPOSITION" ); icalproperty_add_parameter( p, icalparameter_inline ); } if ( !att->label().isEmpty() ) { icalparameter *icalparameter_label = icalparameter_new_x( att->label().toUtf8() ); icalparameter_set_xname( icalparameter_label, "X-LABEL" ); icalproperty_add_parameter( p, icalparameter_label ); } if ( att->isLocal() ) { icalparameter *icalparameter_local = icalparameter_new_x( "local" ); icalparameter_set_xname( icalparameter_local, "X-KONTACT-TYPE" ); icalproperty_add_parameter( p, icalparameter_local ); } return p; } icalrecurrencetype ICalFormatImpl::writeRecurrenceRule( RecurrenceRule *recur ) { icalrecurrencetype r; icalrecurrencetype_clear( &r ); switch( recur->recurrenceType() ) { case RecurrenceRule::rSecondly: r.freq = ICAL_SECONDLY_RECURRENCE; break; case RecurrenceRule::rMinutely: r.freq = ICAL_MINUTELY_RECURRENCE; break; case RecurrenceRule::rHourly: r.freq = ICAL_HOURLY_RECURRENCE; break; case RecurrenceRule::rDaily: r.freq = ICAL_DAILY_RECURRENCE; break; case RecurrenceRule::rWeekly: r.freq = ICAL_WEEKLY_RECURRENCE; break; case RecurrenceRule::rMonthly: r.freq = ICAL_MONTHLY_RECURRENCE; break; case RecurrenceRule::rYearly: r.freq = ICAL_YEARLY_RECURRENCE; break; default: r.freq = ICAL_NO_RECURRENCE; kDebug() << "no recurrence"; break; } int index = 0; QList bys; QList::ConstIterator it; // Now write out the BY* parts: bys = recur->bySeconds(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_second[index++] = *it; } bys = recur->byMinutes(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_minute[index++] = *it; } bys = recur->byHours(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_hour[index++] = *it; } bys = recur->byMonthDays(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_month_day[index++] = icalrecurrencetype_day_position( (*it) * 8 ); } bys = recur->byYearDays(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_year_day[index++] = *it; } bys = recur->byWeekNumbers(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_week_no[index++] = *it; } bys = recur->byMonths(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_month[index++] = *it; } bys = recur->bySetPos(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_set_pos[index++] = *it; } QList byd = recur->byDays(); int day; index = 0; for ( QList::ConstIterator dit = byd.constBegin(); dit != byd.constEnd(); ++dit ) { day = (*dit).day() % 7 + 1; // convert from Monday=1 to Sunday=1 if ( (*dit).pos() < 0 ) { day += ( -(*dit).pos() ) * 8; day = -day; } else { day += (*dit).pos() * 8; } r.by_day[index++] = day; } r.week_start = static_cast( recur->weekStart() % 7 + 1 ); if ( recur->frequency() > 1 ) { // Dont' write out INTERVAL=1, because that's the default anyway r.interval = recur->frequency(); } if ( recur->duration() > 0 ) { r.count = recur->duration(); } else if ( recur->duration() == -1 ) { r.count = 0; } else { if ( recur->allDay() ) { r.until = writeICalDate( recur->endDt().date() ); } else { r.until = writeICalUtcDateTime( recur->endDt() ); } } return r; } icalcomponent *ICalFormatImpl::writeAlarm( Alarm *alarm ) { icalcomponent *a = icalcomponent_new( ICAL_VALARM_COMPONENT ); icalproperty_action action; icalattach *attach = 0; switch ( alarm->type() ) { case Alarm::Procedure: action = ICAL_ACTION_PROCEDURE; attach = icalattach_new_from_url( QFile::encodeName( alarm->programFile() ).data() ); icalcomponent_add_property( a, icalproperty_new_attach( attach ) ); if ( !alarm->programArguments().isEmpty() ) { icalcomponent_add_property( a, icalproperty_new_description( alarm->programArguments().toUtf8() ) ); } break; case Alarm::Audio: action = ICAL_ACTION_AUDIO; if ( !alarm->audioFile().isEmpty() ) { attach = icalattach_new_from_url( QFile::encodeName( alarm->audioFile() ).data() ); icalcomponent_add_property( a, icalproperty_new_attach( attach ) ); } break; case Alarm::Email: { action = ICAL_ACTION_EMAIL; const QList addresses = alarm->mailAddresses(); for ( QList::ConstIterator ad = addresses.constBegin(); ad != addresses.constEnd(); ++ad ) { if ( !(*ad).email().isEmpty() ) { icalproperty *p = icalproperty_new_attendee( "MAILTO:" + (*ad).email().toUtf8() ); if ( !(*ad).name().isEmpty() ) { icalproperty_add_parameter( p, icalparameter_new_cn( quoteForParam( (*ad).name() ).toUtf8() ) ); } icalcomponent_add_property( a, p ); } } icalcomponent_add_property( a, icalproperty_new_summary( alarm->mailSubject().toUtf8() ) ); icalcomponent_add_property( a, icalproperty_new_description( alarm->mailText().toUtf8() ) ); QStringList attachments = alarm->mailAttachments(); if ( attachments.count() > 0 ) { for ( QStringList::const_iterator at = attachments.constBegin(); at != attachments.constEnd(); ++at ) { attach = icalattach_new_from_url( QFile::encodeName( *at ).data() ); icalcomponent_add_property( a, icalproperty_new_attach( attach ) ); } } break; } case Alarm::Display: action = ICAL_ACTION_DISPLAY; icalcomponent_add_property( a, icalproperty_new_description( alarm->text().toUtf8() ) ); break; case Alarm::Invalid: default: kDebug() << "Unknown type of alarm"; action = ICAL_ACTION_NONE; break; } icalcomponent_add_property( a, icalproperty_new_action( action ) ); // Trigger time icaltriggertype trigger; if ( alarm->hasTime() ) { trigger.time = writeICalUtcDateTime( alarm->time() ); trigger.duration = icaldurationtype_null_duration(); } else { trigger.time = icaltime_null_time(); Duration offset; if ( alarm->hasStartOffset() ) { offset = alarm->startOffset(); } else { offset = alarm->endOffset(); } trigger.duration = writeICalDuration( offset ); } icalproperty *p = icalproperty_new_trigger( trigger ); if ( alarm->hasEndOffset() ) { icalproperty_add_parameter( p, icalparameter_new_related( ICAL_RELATED_END ) ); } icalcomponent_add_property( a, p ); // Repeat count and duration if ( alarm->repeatCount() ) { icalcomponent_add_property( a, icalproperty_new_repeat( alarm->repeatCount() ) ); icalcomponent_add_property( a, icalproperty_new_duration( writeICalDuration( alarm->snoozeTime() ) ) ); } // Custom properties const QMap custom = alarm->customProperties(); for ( QMap::ConstIterator c = custom.begin(); c != custom.end(); ++c ) { icalproperty *p = icalproperty_new_x( c.value().toUtf8() ); icalproperty_set_x_name( p, c.key() ); icalcomponent_add_property( a, p ); } return a; } Todo *ICalFormatImpl::readTodo( icalcomponent *vtodo, ICalTimeZones *tzlist ) { Todo *todo = new Todo; readIncidence( vtodo, todo, tzlist ); icalproperty *p = icalcomponent_get_first_property( vtodo, ICAL_ANY_PROPERTY ); while ( p ) { icalproperty_kind kind = icalproperty_isa(p); switch ( kind ) { case ICAL_DUE_PROPERTY: { // due date/time KDateTime kdt = readICalDateTimeProperty( p, tzlist ); todo->setDtDue( kdt, true ); todo->setHasDueDate( true ); todo->setAllDay( kdt.isDateOnly() ); break; } case ICAL_COMPLETED_PROPERTY: // completion date/time todo->setCompleted( readICalDateTimeProperty( p, tzlist ) ); break; case ICAL_PERCENTCOMPLETE_PROPERTY: // Percent completed todo->setPercentComplete( icalproperty_get_percentcomplete( p ) ); break; case ICAL_RELATEDTO_PROPERTY: // related todo (parent) todo->setRelatedToUid( QString::fromUtf8( icalproperty_get_relatedto( p ) ) ); d->mTodosRelate.append( todo ); break; case ICAL_DTSTART_PROPERTY: // Flag that todo has start date. Value is read in by readIncidence(). if ( todo->comments().filter( "NoStartDate" ).count() ) { todo->setHasStartDate( false ); } else { todo->setHasStartDate( true ); } break; case ICAL_RECURRENCEID_PROPERTY: todo->setDtRecurrence( readICalDateTimeProperty( p, tzlist ) ); break; default: // TODO: do something about unknown properties? break; } p = icalcomponent_get_next_property( vtodo, ICAL_ANY_PROPERTY ); } if ( d->mCompat ) { d->mCompat->fixEmptySummary( todo ); } return todo; } Event *ICalFormatImpl::readEvent( icalcomponent *vevent, ICalTimeZones *tzlist ) { Event *event = new Event; readIncidence( vevent, event, tzlist ); icalproperty *p = icalcomponent_get_first_property( vevent, ICAL_ANY_PROPERTY ); bool dtEndProcessed = false; while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_DTEND_PROPERTY: { // end date and time KDateTime kdt = readICalDateTimeProperty( p, tzlist ); if ( kdt.isDateOnly() ) { // End date is non-inclusive QDate endDate = kdt.date().addDays( -1 ); if ( d->mCompat ) { d->mCompat->fixFloatingEnd( endDate ); } if ( endDate < event->dtStart().date() ) { endDate = event->dtStart().date(); } event->setDtEnd( KDateTime( endDate, event->dtStart().timeSpec() ) ); } else { event->setDtEnd( kdt ); event->setAllDay( false ); } dtEndProcessed = true; break; } case ICAL_RELATEDTO_PROPERTY: // related event (parent) event->setRelatedToUid( QString::fromUtf8( icalproperty_get_relatedto( p ) ) ); d->mEventsRelate.append( event ); break; case ICAL_TRANSP_PROPERTY: // Transparency { icalproperty_transp transparency = icalproperty_get_transp( p ); if ( transparency == ICAL_TRANSP_TRANSPARENT ) { event->setTransparency( Event::Transparent ); } else { event->setTransparency( Event::Opaque ); } break; } default: // TODO: do something about unknown properties? break; } p = icalcomponent_get_next_property( vevent, ICAL_ANY_PROPERTY ); } // according to rfc2445 the dtend shouldn't be written when it equals // start date. so assign one equal to start date. if ( !dtEndProcessed && !event->hasDuration() ) { event->setDtEnd( event->dtStart() ); event->setHasEndDate( false ); } QString msade = event->nonKDECustomProperty( "X-MICROSOFT-CDO-ALLDAYEVENT" ); if ( !msade.isEmpty() ) { bool allDay = ( msade == QLatin1String( "TRUE" ) ); event->setAllDay( allDay ); } if ( d->mCompat ) { d->mCompat->fixEmptySummary( event ); } return event; } FreeBusy *ICalFormatImpl::readFreeBusy( icalcomponent *vfreebusy ) { FreeBusy *freebusy = new FreeBusy; d->readIncidenceBase( vfreebusy, freebusy ); icalproperty *p = icalcomponent_get_first_property( vfreebusy, ICAL_ANY_PROPERTY ); FreeBusyPeriod::List periods; while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_DTSTART_PROPERTY: // start date and time (UTC) freebusy->setDtStart( readICalUtcDateTimeProperty( p ) ); break; case ICAL_DTEND_PROPERTY: // end Date and Time (UTC) freebusy->setDtEnd( readICalUtcDateTimeProperty( p ) ); break; case ICAL_FREEBUSY_PROPERTY: //Any FreeBusy Times (UTC) { icalperiodtype icalperiod = icalproperty_get_freebusy( p ); KDateTime period_start = readICalUtcDateTime( p, icalperiod.start ); FreeBusyPeriod period; if ( !icaltime_is_null_time( icalperiod.end ) ) { KDateTime period_end = readICalUtcDateTime( p, icalperiod.end ); period = FreeBusyPeriod( period_start, period_end ); } else { Duration duration ( readICalDuration( icalperiod.duration ) ); period = FreeBusyPeriod( period_start, duration ); } QByteArray param = icalproperty_get_parameter_as_string( p, "X-SUMMARY" ); period.setSummary( QString::fromUtf8( KCodecs::base64Decode( param ) ) ); param = icalproperty_get_parameter_as_string( p, "X-LOCATION" ); period.setLocation( QString::fromUtf8( KCodecs::base64Decode( param ) ) ); periods.append( period ); break; } default: // TODO: do something about unknown properties? break; } p = icalcomponent_get_next_property( vfreebusy, ICAL_ANY_PROPERTY ); } freebusy->addPeriods( periods ); return freebusy; } Journal *ICalFormatImpl::readJournal( icalcomponent *vjournal, ICalTimeZones *tzlist ) { Journal *journal = new Journal; readIncidence( vjournal, journal, tzlist ); return journal; } Attendee *ICalFormatImpl::readAttendee( icalproperty *attendee ) { // the following is a hack to support broken calendars (like WebCalendar 1.0.x) // that include non-RFC-compliant attendees. Otherwise libical 0.42 asserts. if ( !icalproperty_get_value( attendee ) ) { return 0; } icalparameter *p = 0; QString email = QString::fromUtf8( icalproperty_get_attendee( attendee ) ); if ( email.startsWith( QLatin1String( "mailto:" ), Qt::CaseInsensitive ) ) { email = email.mid( 7 ); } QString name; QString uid; p = icalproperty_get_first_parameter( attendee, ICAL_CN_PARAMETER ); if ( p ) { name = QString::fromUtf8( icalparameter_get_cn( p ) ); } else { } bool rsvp = false; p = icalproperty_get_first_parameter( attendee, ICAL_RSVP_PARAMETER ); if ( p ) { icalparameter_rsvp rsvpParameter = icalparameter_get_rsvp( p ); if ( rsvpParameter == ICAL_RSVP_TRUE ) { rsvp = true; } } Attendee::PartStat status = Attendee::NeedsAction; p = icalproperty_get_first_parameter( attendee, ICAL_PARTSTAT_PARAMETER ); if ( p ) { icalparameter_partstat partStatParameter = icalparameter_get_partstat( p ); switch( partStatParameter ) { default: case ICAL_PARTSTAT_NEEDSACTION: status = Attendee::NeedsAction; break; case ICAL_PARTSTAT_ACCEPTED: status = Attendee::Accepted; break; case ICAL_PARTSTAT_DECLINED: status = Attendee::Declined; break; case ICAL_PARTSTAT_TENTATIVE: status = Attendee::Tentative; break; case ICAL_PARTSTAT_DELEGATED: status = Attendee::Delegated; break; case ICAL_PARTSTAT_COMPLETED: status = Attendee::Completed; break; case ICAL_PARTSTAT_INPROCESS: status = Attendee::InProcess; break; } } Attendee::Role role = Attendee::ReqParticipant; p = icalproperty_get_first_parameter( attendee, ICAL_ROLE_PARAMETER ); if ( p ) { icalparameter_role roleParameter = icalparameter_get_role( p ); switch( roleParameter ) { case ICAL_ROLE_CHAIR: role = Attendee::Chair; break; default: case ICAL_ROLE_REQPARTICIPANT: role = Attendee::ReqParticipant; break; case ICAL_ROLE_OPTPARTICIPANT: role = Attendee::OptParticipant; break; case ICAL_ROLE_NONPARTICIPANT: role = Attendee::NonParticipant; break; } } p = icalproperty_get_first_parameter( attendee, ICAL_X_PARAMETER ); while ( p ) { QString xname = QString( icalparameter_get_xname( p ) ).toUpper(); QString xvalue = QString::fromUtf8( icalparameter_get_xvalue( p ) ); if ( xname == "X-UID" ) { uid = xvalue; } p = icalproperty_get_next_parameter( attendee, ICAL_X_PARAMETER ); } Attendee *a = new Attendee( name, email, rsvp, status, role, uid ); p = icalproperty_get_first_parameter( attendee, ICAL_DELEGATEDTO_PARAMETER ); if ( p ) { a->setDelegate( icalparameter_get_delegatedto( p ) ); } p = icalproperty_get_first_parameter( attendee, ICAL_DELEGATEDFROM_PARAMETER ); if ( p ) { a->setDelegator( icalparameter_get_delegatedfrom( p ) ); } return a; } Person ICalFormatImpl::readOrganizer( icalproperty *organizer ) { QString email = QString::fromUtf8( icalproperty_get_organizer( organizer ) ); if ( email.startsWith( QLatin1String( "mailto:" ), Qt::CaseInsensitive ) ) { email = email.mid( 7 ); } QString cn; icalparameter *p = icalproperty_get_first_parameter( organizer, ICAL_CN_PARAMETER ); if ( p ) { cn = QString::fromUtf8( icalparameter_get_cn( p ) ); } Person org( cn, email ); // TODO: Treat sent-by, dir and language here, too return org; } Attachment *ICalFormatImpl::readAttachment( icalproperty *attach ) { Attachment *attachment = 0; const char *p; icalvalue *value = icalproperty_get_value( attach ); switch( icalvalue_isa( value ) ) { case ICAL_ATTACH_VALUE: { icalattach *a = icalproperty_get_attach( attach ); if ( !icalattach_get_is_url( a ) ) { p = (const char *)icalattach_get_data( a ); if ( p ) { attachment = new Attachment( p ); } } else { p = icalattach_get_url( a ); if ( p ) { attachment = new Attachment( QString::fromUtf8( p ) ); } } break; } case ICAL_BINARY_VALUE: { icalattach *a = icalproperty_get_attach( attach ); p = (const char *)icalattach_get_data( a ); if ( p ) { attachment = new Attachment( p ); } break; } case ICAL_URI_VALUE: p = icalvalue_get_uri( value ); attachment = new Attachment( QString::fromUtf8( p ) ); break; default: break; } if ( attachment ) { icalparameter *p = icalproperty_get_first_parameter( attach, ICAL_FMTTYPE_PARAMETER ); if ( p ) { attachment->setMimeType( QString( icalparameter_get_fmttype( p ) ) ); } p = icalproperty_get_first_parameter( attach, ICAL_X_PARAMETER ); while ( p ) { QString xname = QString( icalparameter_get_xname( p ) ).toUpper(); QString xvalue = QString::fromUtf8( icalparameter_get_xvalue( p ) ); if ( xname == "X-CONTENT-DISPOSITION" ) { attachment->setShowInline( xvalue.toLower() == "inline" ); } if ( xname == "X-LABEL" ) { attachment->setLabel( xvalue ); } if ( xname == "X-KONTACT-TYPE" ) { attachment->setLocal( xvalue.toLower() == "local" ); } p = icalproperty_get_next_parameter( attach, ICAL_X_PARAMETER ); } p = icalproperty_get_first_parameter( attach, ICAL_X_PARAMETER ); while ( p ) { if ( strncmp ( icalparameter_get_xname( p ), "X-LABEL", 7 ) == 0 ) { attachment->setLabel( icalparameter_get_xvalue( p ) ); } p = icalproperty_get_next_parameter( attach, ICAL_X_PARAMETER ); } } return attachment; } void ICalFormatImpl::readIncidence( icalcomponent *parent, Incidence *incidence, ICalTimeZones *tzlist ) { d->readIncidenceBase( parent, incidence ); icalproperty *p = icalcomponent_get_first_property( parent, ICAL_ANY_PROPERTY ); const char *text; int intvalue, inttext; icaldurationtype icalduration; KDateTime kdt; QStringList categories; while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_CREATED_PROPERTY: incidence->setCreated( readICalDateTimeProperty( p, tzlist ) ); break; case ICAL_SEQUENCE_PROPERTY: // sequence intvalue = icalproperty_get_sequence( p ); incidence->setRevision( intvalue ); break; case ICAL_LASTMODIFIED_PROPERTY: // last modification UTC date/time incidence->setLastModified( readICalDateTimeProperty( p, tzlist ) ); break; case ICAL_DTSTART_PROPERTY: // start date and time kdt = readICalDateTimeProperty( p, tzlist ); incidence->setDtStart( kdt ); incidence->setAllDay( kdt.isDateOnly() ); break; case ICAL_DURATION_PROPERTY: // start date and time icalduration = icalproperty_get_duration( p ); incidence->setDuration( readICalDuration( icalduration ) ); break; case ICAL_DESCRIPTION_PROPERTY: // description { QString textStr = QString::fromUtf8( icalproperty_get_description( p ) ); if ( !textStr.isEmpty() ) { QString valStr = QString::fromUtf8( icalproperty_get_parameter_as_string( p, "X-KDE-TEXTFORMAT" ) ); if ( !valStr.compare( "HTML", Qt::CaseInsensitive ) ) { incidence->setDescription( textStr, true ); } else { incidence->setDescription( textStr, false ); } } } break; case ICAL_SUMMARY_PROPERTY: // summary { QString textStr = QString::fromUtf8( icalproperty_get_summary( p ) ); if ( !textStr.isEmpty() ) { QString valStr = QString::fromUtf8( icalproperty_get_parameter_as_string( p, "X-KDE-TEXTFORMAT" ) ); if ( !valStr.compare( "HTML", Qt::CaseInsensitive ) ) { incidence->setSummary( textStr, true ); } else { incidence->setSummary( textStr, false ); } } } break; case ICAL_LOCATION_PROPERTY: // location { if ( !icalproperty_get_value( p ) ) { //Fix for #191472. This is a pre-crash guard in case libical was //compiled in superstrict mode (--enable-icalerrors-are-fatal) //TODO: pre-crash guard other property getters too. break; } QString textStr = QString::fromUtf8( icalproperty_get_location( p ) ); if ( !textStr.isEmpty() ) { QString valStr = QString::fromUtf8( icalproperty_get_parameter_as_string( p, "X-KDE-TEXTFORMAT" ) ); if ( !valStr.compare( "HTML", Qt::CaseInsensitive ) ) { incidence->setLocation( textStr, true ); } else { incidence->setLocation( textStr, false ); } } } break; case ICAL_STATUS_PROPERTY: // status { Incidence::Status stat; switch ( icalproperty_get_status( p ) ) { case ICAL_STATUS_TENTATIVE: stat = Incidence::StatusTentative; break; case ICAL_STATUS_CONFIRMED: stat = Incidence::StatusConfirmed; break; case ICAL_STATUS_COMPLETED: stat = Incidence::StatusCompleted; break; case ICAL_STATUS_NEEDSACTION: stat = Incidence::StatusNeedsAction; break; case ICAL_STATUS_CANCELLED: stat = Incidence::StatusCanceled; break; case ICAL_STATUS_INPROCESS: stat = Incidence::StatusInProcess; break; case ICAL_STATUS_DRAFT: stat = Incidence::StatusDraft; break; case ICAL_STATUS_FINAL: stat = Incidence::StatusFinal; break; case ICAL_STATUS_X: incidence->setCustomStatus( QString::fromUtf8( icalvalue_get_x( icalproperty_get_value( p ) ) ) ); stat = Incidence::StatusX; break; case ICAL_STATUS_NONE: default: stat = Incidence::StatusNone; break; } if ( stat != Incidence::StatusX ) { incidence->setStatus( stat ); } break; } case ICAL_GEO_PROPERTY: // geo { icalgeotype geo = icalproperty_get_geo( p ); incidence->setGeoLatitude( geo.lat ); incidence->setGeoLongitude( geo.lon ); incidence->setHasGeo( true ); break; } case ICAL_PRIORITY_PROPERTY: // priority intvalue = icalproperty_get_priority( p ); if ( d->mCompat ) { intvalue = d->mCompat->fixPriority( intvalue ); } incidence->setPriority( intvalue ); break; case ICAL_CATEGORIES_PROPERTY: // categories { // We have always supported multiple CATEGORIES properties per component // even though the RFC seems to indicate only 1 is permitted. // We can't change that -- in order to retain backwards compatibility. text = icalproperty_get_categories( p ); const QString val = QString::fromUtf8( text ); foreach ( const QString &cat, val.split( ',', QString::SkipEmptyParts ) ) { // ensure no duplicates if ( !categories.contains( cat ) ) { categories.append( cat ); } } break; } case ICAL_RRULE_PROPERTY: readRecurrenceRule( p, incidence ); break; case ICAL_RDATE_PROPERTY: kdt = readICalDateTimeProperty( p, tzlist ); if ( kdt.isValid() ) { if ( kdt.isDateOnly() ) { incidence->recurrence()->addRDate( kdt.date() ); } else { incidence->recurrence()->addRDateTime( kdt ); } } else { // TODO: RDates as period are not yet implemented! } break; case ICAL_EXRULE_PROPERTY: readExceptionRule( p, incidence ); break; case ICAL_EXDATE_PROPERTY: kdt = readICalDateTimeProperty( p, tzlist ); if ( kdt.isDateOnly() ) { incidence->recurrence()->addExDate( kdt.date() ); } else { incidence->recurrence()->addExDateTime( kdt ); } break; case ICAL_CLASS_PROPERTY: inttext = icalproperty_get_class( p ); if ( inttext == ICAL_CLASS_PUBLIC ) { incidence->setSecrecy( Incidence::SecrecyPublic ); } else if ( inttext == ICAL_CLASS_CONFIDENTIAL ) { incidence->setSecrecy( Incidence::SecrecyConfidential ); } else { incidence->setSecrecy( Incidence::SecrecyPrivate ); } break; case ICAL_ATTACH_PROPERTY: // attachments incidence->addAttachment( readAttachment( p ) ); break; default: // TODO: do something about unknown properties? break; } p = icalcomponent_get_next_property( parent, ICAL_ANY_PROPERTY ); } // Set the scheduling ID const QString uid = incidence->customProperty( "LIBKCAL", "ID" ); if ( !uid.isNull() ) { // The UID stored in incidencebase is actually the scheduling ID // It has to be stored in the iCal UID component for compatibility // with other iCal applications incidence->setSchedulingID( incidence->uid() ); incidence->setUid( uid ); } // Now that recurrence and exception stuff is completely set up, // do any backwards compatibility adjustments. if ( incidence->recurs() && d->mCompat ) { d->mCompat->fixRecurrence( incidence ); } // add categories incidence->setCategories( categories ); // iterate through all alarms for ( icalcomponent *alarm = icalcomponent_get_first_component( parent, ICAL_VALARM_COMPONENT ); alarm; alarm = icalcomponent_get_next_component( parent, ICAL_VALARM_COMPONENT ) ) { readAlarm( alarm, incidence, tzlist ); } // Fix incorrect alarm settings by other applications (like outloook 9) if ( d->mCompat ) { d->mCompat->fixAlarms( incidence ); } } //@cond PRIVATE void ICalFormatImpl::Private::readIncidenceBase( icalcomponent *parent, IncidenceBase *incidenceBase ) { icalproperty *p = icalcomponent_get_first_property( parent, ICAL_ANY_PROPERTY ); while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_UID_PROPERTY: // unique id incidenceBase->setUid( QString::fromUtf8( icalproperty_get_uid( p ) ) ); break; case ICAL_ORGANIZER_PROPERTY: // organizer incidenceBase->setOrganizer( mImpl->readOrganizer( p ) ); break; case ICAL_ATTENDEE_PROPERTY: // attendee incidenceBase->addAttendee( mImpl->readAttendee( p ) ); break; case ICAL_COMMENT_PROPERTY: incidenceBase->addComment( QString::fromUtf8( icalproperty_get_comment( p ) ) ); break; default: break; } p = icalcomponent_get_next_property( parent, ICAL_ANY_PROPERTY ); } // custom properties readCustomProperties( parent, incidenceBase ); } void ICalFormatImpl::Private::readCustomProperties( icalcomponent *parent, CustomProperties *properties ) { QMap customProperties; QString lastProperty; icalproperty *p = icalcomponent_get_first_property( parent, ICAL_X_PROPERTY ); while ( p ) { QString value = QString::fromUtf8( icalproperty_get_x( p ) ); const char *name = icalproperty_get_x_name( p ); if ( lastProperty != name ) { customProperties[name] = value; } else { customProperties[name] = customProperties[name].append( "," ).append( value ); } p = icalcomponent_get_next_property( parent, ICAL_X_PROPERTY ); lastProperty = name; } properties->setCustomProperties( customProperties ); } //@endcond void ICalFormatImpl::readRecurrenceRule( icalproperty *rrule, Incidence *incidence ) { Recurrence *recur = incidence->recurrence(); struct icalrecurrencetype r = icalproperty_get_rrule( rrule ); // dumpIcalRecurrence(r); RecurrenceRule *recurrule = new RecurrenceRule( /*incidence*/); recurrule->setStartDt( incidence->dtStart() ); readRecurrence( r, recurrule ); recur->addRRule( recurrule ); } void ICalFormatImpl::readExceptionRule( icalproperty *rrule, Incidence *incidence ) { struct icalrecurrencetype r = icalproperty_get_exrule( rrule ); // dumpIcalRecurrence(r); RecurrenceRule *recurrule = new RecurrenceRule( /*incidence*/); recurrule->setStartDt( incidence->dtStart() ); readRecurrence( r, recurrule ); Recurrence *recur = incidence->recurrence(); recur->addExRule( recurrule ); } void ICalFormatImpl::readRecurrence( const struct icalrecurrencetype &r, RecurrenceRule *recur ) { // Generate the RRULE string recur->setRRule( QString( icalrecurrencetype_as_string( const_cast( &r ) ) ) ); // Period switch ( r.freq ) { case ICAL_SECONDLY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rSecondly ); break; case ICAL_MINUTELY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rMinutely ); break; case ICAL_HOURLY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rHourly ); break; case ICAL_DAILY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rDaily ); break; case ICAL_WEEKLY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rWeekly ); break; case ICAL_MONTHLY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rMonthly ); break; case ICAL_YEARLY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rYearly ); break; case ICAL_NO_RECURRENCE: default: recur->setRecurrenceType( RecurrenceRule::rNone ); } // Frequency recur->setFrequency( r.interval ); // Duration & End Date if ( !icaltime_is_null_time( r.until ) ) { icaltimetype t = r.until; recur->setEndDt( readICalUtcDateTime( 0, t ) ); } else { if ( r.count == 0 ) { recur->setDuration( -1 ); } else { recur->setDuration( r.count ); } } // Week start setting int wkst = ( r.week_start + 5 ) % 7 + 1; recur->setWeekStart( wkst ); // And now all BY* QList lst; int i; int index = 0; //@cond PRIVATE #define readSetByList( rrulecomp, setfunc ) \ index = 0; \ lst.clear(); \ while ( ( i = r.rrulecomp[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { \ lst.append( i ); \ } \ if ( !lst.isEmpty() ) { \ recur->setfunc( lst ); \ } //@endcond // BYSECOND, MINUTE and HOUR, MONTHDAY, YEARDAY, WEEKNUMBER, MONTH // and SETPOS are standard int lists, so we can treat them with the // same macro readSetByList( by_second, setBySeconds ); readSetByList( by_minute, setByMinutes ); readSetByList( by_hour, setByHours ); readSetByList( by_month_day, setByMonthDays ); readSetByList( by_year_day, setByYearDays ); readSetByList( by_week_no, setByWeekNumbers ); readSetByList( by_month, setByMonths ); readSetByList( by_set_pos, setBySetPos ); #undef readSetByList // BYDAY is a special case, since it's not an int list QList wdlst; short day; index=0; while ( ( day = r.by_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { RecurrenceRule::WDayPos pos; pos.setDay( ( icalrecurrencetype_day_day_of_week( day ) + 5 ) % 7 + 1 ); pos.setPos( icalrecurrencetype_day_position( day ) ); wdlst.append( pos ); } if ( !wdlst.isEmpty() ) { recur->setByDays( wdlst ); } // TODO: Store all X- fields of the RRULE inside the recurrence (so they are // preserved } void ICalFormatImpl::readAlarm( icalcomponent *alarm, Incidence *incidence, ICalTimeZones *tzlist ) { Alarm *ialarm = incidence->newAlarm(); ialarm->setRepeatCount( 0 ); ialarm->setEnabled( true ); // Determine the alarm's action type icalproperty *p = icalcomponent_get_first_property( alarm, ICAL_ACTION_PROPERTY ); Alarm::Type type = Alarm::Display; icalproperty_action action = ICAL_ACTION_DISPLAY; if ( !p ) { kDebug() << "Unknown type of alarm, using default"; // TODO: do something about unknown alarm type? } else { action = icalproperty_get_action( p ); switch ( action ) { case ICAL_ACTION_DISPLAY: type = Alarm::Display; break; case ICAL_ACTION_AUDIO: type = Alarm::Audio; break; case ICAL_ACTION_PROCEDURE: type = Alarm::Procedure; break; case ICAL_ACTION_EMAIL: type = Alarm::Email; break; default: break; // TODO: do something about invalid alarm type? } } ialarm->setType( type ); p = icalcomponent_get_first_property( alarm, ICAL_ANY_PROPERTY ); while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_TRIGGER_PROPERTY: { icaltriggertype trigger = icalproperty_get_trigger( p ); if ( !icaltime_is_null_time( trigger.time ) ) { //set the trigger to a specific time (which is not in rfc2445, btw) ialarm->setTime( readICalUtcDateTime( p, trigger.time, tzlist ) ); } else { //set the trigger to an offset from the incidence start or end time. if ( !icaldurationtype_is_bad_duration( trigger.duration ) ) { Duration duration( readICalDuration( trigger.duration ) ); icalparameter *param = icalproperty_get_first_parameter( p, ICAL_RELATED_PARAMETER ); if ( param && icalparameter_get_related( param ) == ICAL_RELATED_END ) { ialarm->setEndOffset( duration ); } else { ialarm->setStartOffset( duration ); } } else { // a bad duration was encountered, just set a 0 duration from start ialarm->setStartOffset( Duration( 0 ) ); } } break; } case ICAL_DURATION_PROPERTY: { icaldurationtype duration = icalproperty_get_duration( p ); ialarm->setSnoozeTime( readICalDuration( duration ) ); break; } case ICAL_REPEAT_PROPERTY: ialarm->setRepeatCount( icalproperty_get_repeat( p ) ); break; case ICAL_DESCRIPTION_PROPERTY: { // Only in DISPLAY and EMAIL and PROCEDURE alarms QString description = QString::fromUtf8( icalproperty_get_description( p ) ); switch ( action ) { case ICAL_ACTION_DISPLAY: ialarm->setText( description ); break; case ICAL_ACTION_PROCEDURE: ialarm->setProgramArguments( description ); break; case ICAL_ACTION_EMAIL: ialarm->setMailText( description ); break; default: break; } break; } case ICAL_SUMMARY_PROPERTY: // Only in EMAIL alarm ialarm->setMailSubject( QString::fromUtf8( icalproperty_get_summary( p ) ) ); break; case ICAL_ATTENDEE_PROPERTY: { // Only in EMAIL alarm QString email = QString::fromUtf8( icalproperty_get_attendee( p ) ); if ( email.startsWith( QLatin1String( "mailto:" ), Qt::CaseInsensitive ) ) { email = email.mid( 7 ); } QString name; icalparameter *param = icalproperty_get_first_parameter( p, ICAL_CN_PARAMETER ); if ( param ) { name = QString::fromUtf8( icalparameter_get_cn( param ) ); } ialarm->addMailAddress( Person( name, email ) ); break; } case ICAL_ATTACH_PROPERTY: { // Only in AUDIO and EMAIL and PROCEDURE alarms Attachment *attach = readAttachment( p ); if ( attach && attach->isUri() ) { switch ( action ) { case ICAL_ACTION_AUDIO: ialarm->setAudioFile( attach->uri() ); break; case ICAL_ACTION_PROCEDURE: ialarm->setProgramFile( attach->uri() ); break; case ICAL_ACTION_EMAIL: ialarm->addMailAttachment( attach->uri() ); break; default: break; } } else { kDebug() << "Alarm attachments currently only support URIs," << "but no binary data"; } delete attach; break; } default: break; } p = icalcomponent_get_next_property( alarm, ICAL_ANY_PROPERTY ); } // custom properties d->readCustomProperties( alarm, ialarm ); // TODO: check for consistency of alarm properties } icaldatetimeperiodtype ICalFormatImpl::writeICalDatePeriod( const QDate &date ) { icaldatetimeperiodtype t; t.time = writeICalDate( date ); t.period = icalperiodtype_null_period(); return t; } icaltimetype ICalFormatImpl::writeICalDate( const QDate &date ) { icaltimetype t = icaltime_null_time(); t.year = date.year(); t.month = date.month(); t.day = date.day(); t.hour = 0; t.minute = 0; t.second = 0; t.is_date = 1; t.is_utc = 0; t.zone = 0; return t; } icaltimetype ICalFormatImpl::writeICalDateTime( const KDateTime &datetime ) { icaltimetype t = icaltime_null_time(); t.year = datetime.date().year(); t.month = datetime.date().month(); t.day = datetime.date().day(); t.hour = datetime.time().hour(); t.minute = datetime.time().minute(); t.second = datetime.time().second(); t.is_date = 0; t.zone = 0; // zone is NOT set t.is_utc = datetime.isUtc() ? 1 : 0; // _dumpIcaltime( t ); return t; } icalproperty *ICalFormatImpl::writeICalDateTimeProperty( const icalproperty_kind type, const KDateTime &dt, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList ) { icaltimetype t; switch ( type ) { case ICAL_DTSTAMP_PROPERTY: case ICAL_CREATED_PROPERTY: case ICAL_LASTMODIFIED_PROPERTY: t = writeICalDateTime( dt.toUtc() ); break; default: t = writeICalDateTime( dt ); break; } icalproperty *p; switch ( type ) { case ICAL_DTSTAMP_PROPERTY: p = icalproperty_new_dtstamp( t ); break; case ICAL_CREATED_PROPERTY: p = icalproperty_new_created( t ); break; case ICAL_LASTMODIFIED_PROPERTY: p = icalproperty_new_lastmodified( t ); break; case ICAL_DTSTART_PROPERTY: // start date and time p = icalproperty_new_dtstart( t ); break; case ICAL_DTEND_PROPERTY: // end date and time p = icalproperty_new_dtend( t ); break; case ICAL_DUE_PROPERTY: p = icalproperty_new_due( t ); break; case ICAL_RECURRENCEID_PROPERTY: p = icalproperty_new_recurrenceid( t ); break; case ICAL_EXDATE_PROPERTY: p = icalproperty_new_exdate( t ); break; default: { icaldatetimeperiodtype tp; tp.time = t; tp.period = icalperiodtype_null_period(); switch ( type ) { case ICAL_RDATE_PROPERTY: p = icalproperty_new_rdate( tp ); break; default: return 0; } } } KTimeZone ktz; if ( !t.is_utc ) { ktz = dt.timeZone(); } if ( ktz.isValid() ) { if ( tzlist ) { ICalTimeZone tz = tzlist->zone( ktz.name() ); if ( !tz.isValid() ) { // The time zone isn't in the list of known zones for the calendar // - add it to the calendar's zone list ICalTimeZone tznew( ktz ); tzlist->add( tznew ); tz = tznew; } if ( tzUsedList ) { tzUsedList->add( tz ); } } icalproperty_add_parameter( p, icalparameter_new_tzid( ktz.name().toUtf8() ) ); } return p; } KDateTime ICalFormatImpl::readICalDateTime( icalproperty *p, const icaltimetype &t, ICalTimeZones *tzlist, bool utc ) { // kDebug(); // _dumpIcaltime( t ); KDateTime::Spec timeSpec; if ( t.is_utc || t.zone == icaltimezone_get_utc_timezone() ) { timeSpec = KDateTime::UTC; // the time zone is UTC utc = false; // no need to convert to UTC } else { if ( !tzlist ) { utc = true; // should be UTC, but it isn't } icalparameter *param = p ? icalproperty_get_first_parameter( p, ICAL_TZID_PARAMETER ) : 0; const char *tzid = param ? icalparameter_get_tzid( param ) : 0; if ( !tzid ) { timeSpec = KDateTime::ClockTime; } else { QString tzidStr = QString::fromUtf8( tzid ); ICalTimeZone tz; if ( tzlist ) { tz = tzlist->zone( tzidStr ); } if ( !tz.isValid() ) { // The time zone is not in the existing list for the calendar. // Try to read it from the system or libical databases. ICalTimeZoneSource tzsource; ICalTimeZone newtz = tzsource.standardZone( tzidStr ); if ( newtz.isValid() && tzlist ) { tzlist->add( newtz ); } tz = newtz; } timeSpec = tz.isValid() ? KDateTime::Spec( tz ) : KDateTime::LocalZone; } } KDateTime result( QDate( t.year, t.month, t.day ), QTime( t.hour, t.minute, t.second ), timeSpec ); return utc ? result.toUtc() : result; } QDate ICalFormatImpl::readICalDate( icaltimetype t ) { return QDate( t.year, t.month, t.day ); } KDateTime ICalFormatImpl::readICalDateTimeProperty( icalproperty *p, ICalTimeZones *tzlist, bool utc ) { icaldatetimeperiodtype tp; icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_CREATED_PROPERTY: // UTC date/time tp.time = icalproperty_get_created( p ); utc = true; break; case ICAL_LASTMODIFIED_PROPERTY: // last modification UTC date/time tp.time = icalproperty_get_lastmodified( p ); utc = true; break; case ICAL_DTSTART_PROPERTY: // start date and time (UTC for freebusy) tp.time = icalproperty_get_dtstart( p ); break; case ICAL_DTEND_PROPERTY: // end date and time (UTC for freebusy) tp.time = icalproperty_get_dtend( p ); break; case ICAL_DUE_PROPERTY: // due date/time tp.time = icalproperty_get_due( p ); break; case ICAL_COMPLETED_PROPERTY: // UTC completion date/time tp.time = icalproperty_get_completed( p ); utc = true; break; case ICAL_RECURRENCEID_PROPERTY: tp.time = icalproperty_get_recurrenceid( p ); break; case ICAL_EXDATE_PROPERTY: tp.time = icalproperty_get_exdate( p ); break; default: switch ( kind ) { case ICAL_RDATE_PROPERTY: tp = icalproperty_get_rdate( p ); break; default: return KDateTime(); } if ( !icaltime_is_valid_time( tp.time ) ) { return KDateTime(); // a time period was found (not implemented yet) } break; } if ( tp.time.is_date ) { return KDateTime( readICalDate( tp.time ), KDateTime::Spec::ClockTime() ); } else { return readICalDateTime( p, tp.time, tzlist, utc ); } } icaldurationtype ICalFormatImpl::writeICalDuration( const Duration &duration ) { // should be able to use icaldurationtype_from_int(), except we know // that some older tools do not properly support weeks. So we never // set a week duration, only days icaldurationtype d; int value = duration.value(); d.is_neg = ( value < 0 ) ? 1 : 0; if ( value < 0 ) { value = -value; } if ( duration.isDaily() ) { d.weeks = 0; d.days = value; d.hours = d.minutes = d.seconds = 0; } else { d.weeks = 0; d.days = value / gSecondsPerDay; value %= gSecondsPerDay; d.hours = value / gSecondsPerHour; value %= gSecondsPerHour; d.minutes = value / gSecondsPerMinute; value %= gSecondsPerMinute; d.seconds = value; } return d; } Duration ICalFormatImpl::readICalDuration( icaldurationtype d ) { int days = d.weeks * 7; days += d.days; int seconds = d.hours * gSecondsPerHour; seconds += d.minutes * gSecondsPerMinute; seconds += d.seconds; if ( seconds ) { seconds += days * gSecondsPerDay; if ( d.is_neg ) { seconds = -seconds; } return Duration( seconds, Duration::Seconds ); } else { if ( d.is_neg ) { days = -days; } return Duration( days, Duration::Days ); } } icalcomponent *ICalFormatImpl::createCalendarComponent( Calendar *cal ) { icalcomponent *calendar; // Root component calendar = icalcomponent_new( ICAL_VCALENDAR_COMPONENT ); icalproperty *p; // Product Identifier p = icalproperty_new_prodid( CalFormat::productId().toUtf8() ); icalcomponent_add_property( calendar, p ); // iCalendar version (2.0) p = icalproperty_new_version( const_cast(_ICAL_VERSION) ); icalcomponent_add_property( calendar, p ); // Add time zone if ( cal && cal->timeZones() ) { const ICalTimeZones::ZoneMap zmaps = cal->timeZones()->zones(); for ( ICalTimeZones::ZoneMap::ConstIterator it=zmaps.constBegin(); it != zmaps.constEnd(); ++it ) { icaltimezone *icaltz = (*it).icalTimezone(); if ( !icaltz ) { kError() << "bad time zone"; } else { icalcomponent *tz = icalcomponent_new_clone( icaltimezone_get_component( icaltz ) ); icalcomponent_add_component( calendar, tz ); icaltimezone_free( icaltz, 1 ); } } } // Custom properties if( cal != 0 ) { d->writeCustomProperties( calendar, cal ); } return calendar; } // take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc. // and break it down from its tree-like format into the dictionary format // that is used internally in the ICalFormatImpl. bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar ) { // this function will populate the caldict dictionary and other event // lists. It turns vevents into Events and then inserts them. if ( !calendar ) { return false; } // TODO: check for METHOD icalproperty *p; p = icalcomponent_get_first_property( calendar, ICAL_PRODID_PROPERTY ); if ( !p ) { kDebug() << "No PRODID property found"; d->mLoadedProductId = ""; } else { d->mLoadedProductId = QString::fromUtf8( icalproperty_get_prodid( p ) ); delete d->mCompat; d->mCompat = CompatFactory::createCompat( d->mLoadedProductId ); } p = icalcomponent_get_first_property( calendar, ICAL_VERSION_PROPERTY ); if ( !p ) { kDebug() << "No VERSION property found"; d->mParent->setException( new ErrorFormat( ErrorFormat::CalVersionUnknown ) ); return false; } else { const char *version = icalproperty_get_version( p ); if ( !version ) { kDebug() << "No VERSION property found"; d->mParent->setException( new ErrorFormat( ErrorFormat::CalVersionUnknown, i18n( "No VERSION property found" ) ) ); return false; } if ( strcmp( version, "1.0" ) == 0 ) { kDebug() << "Expected iCalendar, got vCalendar"; d->mParent->setException( new ErrorFormat( ErrorFormat::CalVersion1, i18n( "Expected iCalendar, got vCalendar format" ) ) ); return false; } else if ( strcmp( version, "2.0" ) != 0 ) { kDebug() << "Expected iCalendar, got unknown format"; d->mParent->setException( new ErrorFormat( ErrorFormat::CalVersionUnknown, i18n( "Expected iCalendar, got unknown format" ) ) ); return false; } } // Populate the calendar's time zone collection with all VTIMEZONE components ICalTimeZones *tzlist = cal->timeZones(); ICalTimeZoneSource tzs; tzs.parse( calendar, *tzlist ); // custom properties d->readCustomProperties( calendar, cal ); // Store all events with a relatedTo property in a list for post-processing d->mEventsRelate.clear(); d->mTodosRelate.clear(); // TODO: make sure that only actually added events go to this lists. icalcomponent *c; // Iterate through all todos + cal->beginBatchAdding(); + c = icalcomponent_get_first_component( calendar, ICAL_VTODO_COMPONENT ); while ( c ) { Todo *todo = readTodo( c, tzlist ); if ( todo ) { Todo *old = cal->todo( todo->uid() ); if ( old ) { cal->deleteTodo( old ); d->mTodosRelate.removeAll( old ); } cal->addTodo( todo ); } c = icalcomponent_get_next_component( calendar, ICAL_VTODO_COMPONENT ); } // Iterate through all events c = icalcomponent_get_first_component( calendar, ICAL_VEVENT_COMPONENT ); while ( c ) { Event *event = readEvent( c, tzlist ); if ( event ) { Event *old = cal->event( event->uid() ); if ( old ) { cal->deleteEvent( old ); d->mEventsRelate.removeAll( old ); } cal->addEvent( event ); } c = icalcomponent_get_next_component( calendar, ICAL_VEVENT_COMPONENT ); } // Iterate through all journals c = icalcomponent_get_first_component( calendar, ICAL_VJOURNAL_COMPONENT ); while ( c ) { Journal *journal = readJournal( c, tzlist ); if ( journal ) { Journal *old = cal->journal( journal->uid() ); if ( old ) { cal->deleteJournal( old ); } cal->addJournal( journal ); } c = icalcomponent_get_next_component( calendar, ICAL_VJOURNAL_COMPONENT ); } + cal->endBatchAdding(); + // Post-Process list of events with relations, put Event objects in relation Event::List::ConstIterator eIt; for ( eIt = d->mEventsRelate.constBegin(); eIt != d->mEventsRelate.constEnd(); ++eIt ) { (*eIt)->setRelatedTo( cal->incidence( (*eIt)->relatedToUid() ) ); } Todo::List::ConstIterator tIt; for ( tIt = d->mTodosRelate.constBegin(); tIt != d->mTodosRelate.constEnd(); ++tIt ) { (*tIt)->setRelatedTo( cal->incidence( (*tIt)->relatedToUid() ) ); } // TODO: Remove any previous time zones no longer referenced in the calendar return true; } QString ICalFormatImpl::extractErrorProperty( icalcomponent *c ) { QString errorMessage; icalproperty *error; error = icalcomponent_get_first_property( c, ICAL_XLICERROR_PROPERTY ); while ( error ) { errorMessage += icalproperty_get_xlicerror( error ); errorMessage += '\n'; error = icalcomponent_get_next_property( c, ICAL_XLICERROR_PROPERTY ); } return errorMessage; } void ICalFormatImpl::dumpIcalRecurrence( icalrecurrencetype r ) { int i; kDebug() << " Freq:" << r.freq; kDebug() << " Until:" << icaltime_as_ical_string( r.until ); kDebug() << " Count:" << r.count; if ( r.by_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Day: "; while ( ( i = r.by_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { out.append( QString::number( i ) + ' ' ); } kDebug() << out; } if ( r.by_month_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Month Day: "; while ( ( i = r.by_month_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { out.append( QString::number( i ) + ' ' ); } kDebug() << out; } if ( r.by_year_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Year Day: "; while ( ( i = r.by_year_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { out.append( QString::number( i ) + ' ' ); } kDebug() << out; } if ( r.by_month[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Month: "; while ( ( i = r.by_month[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { out.append( QString::number( i ) + ' ' ); } kDebug() << out; } if ( r.by_set_pos[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Set Pos: "; while ( ( i = r.by_set_pos[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { kDebug() << "=========" << i; out.append( QString::number( i ) + ' ' ); } kDebug() << out; } } icalcomponent *ICalFormatImpl::createScheduleComponent( IncidenceBase *incidence, iTIPMethod method ) { icalcomponent *message = createCalendarComponent(); // Create VTIMEZONE components for this incidence ICalTimeZones zones; if ( incidence ) { if ( incidence->type() == "Event" ) { Event *ev = static_cast( incidence ); if ( ev ) { if ( ev->dtStart().isValid() ) { zones.add( ICalTimeZone( ev->dtStart().timeZone() ) ); } if ( ev->hasEndDate() && ev->dtEnd().isValid() ) { zones.add( ICalTimeZone( ev->dtEnd().timeZone() ) ); } } } else if ( incidence->type() == "Todo" ) { Todo *t = static_cast( incidence ); if ( t ) { if ( t->hasStartDate() && t->dtStart().isValid() ) { zones.add( ICalTimeZone( t->dtStart( true ).timeZone() ) ); } if ( t->hasDueDate() && t->dtDue().isValid() ) { zones.add( ICalTimeZone( t->dtDue().timeZone() ) ); } } } else if ( incidence->type() == "Journal" ) { Journal *j = static_cast( incidence ); if ( j ) { if ( j->dtStart().isValid() ) { zones.add( ICalTimeZone( j->dtStart().timeZone() ) ); } } } const ICalTimeZones::ZoneMap zmaps = zones.zones(); for ( ICalTimeZones::ZoneMap::ConstIterator it=zmaps.constBegin(); it != zmaps.constEnd(); ++it ) { icaltimezone *icaltz = (*it).icalTimezone(); if ( !icaltz ) { kError() << "bad time zone"; } else { icalcomponent *tz = icalcomponent_new_clone( icaltimezone_get_component( icaltz ) ); icalcomponent_add_component( message, tz ); icaltimezone_free( icaltz, 1 ); } } } icalproperty_method icalmethod = ICAL_METHOD_NONE; switch (method) { case iTIPPublish: icalmethod = ICAL_METHOD_PUBLISH; break; case iTIPRequest: icalmethod = ICAL_METHOD_REQUEST; break; case iTIPRefresh: icalmethod = ICAL_METHOD_REFRESH; break; case iTIPCancel: icalmethod = ICAL_METHOD_CANCEL; break; case iTIPAdd: icalmethod = ICAL_METHOD_ADD; break; case iTIPReply: icalmethod = ICAL_METHOD_REPLY; break; case iTIPCounter: icalmethod = ICAL_METHOD_COUNTER; break; case iTIPDeclineCounter: icalmethod = ICAL_METHOD_DECLINECOUNTER; break; default: kDebug() << "Unknown method"; return message; } icalcomponent_add_property( message, icalproperty_new_method( icalmethod ) ); icalcomponent *inc = writeIncidence( incidence, method ); /* * RFC 2446 states in section 3.4.3 ( REPLY to a VTODO ), that * a REQUEST-STATUS property has to be present. For the other two, event and * free busy, it can be there, but is optional. Until we do more * fine grained handling, assume all is well. Note that this is the * status of the _request_, not the attendee. Just to avoid confusion. * - till */ if ( icalmethod == ICAL_METHOD_REPLY ) { struct icalreqstattype rst; rst.code = ICAL_2_0_SUCCESS_STATUS; rst.desc = 0; rst.debug = 0; icalcomponent_add_property( inc, icalproperty_new_requeststatus( rst ) ); } icalcomponent_add_component( message, inc ); return message; }