/* This file is part of libkcal. Copyright (c) 1998 Preston Brown Copyright (c) 2000-2004 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 calendar.cpp Provides the main "calendar" object class. @author Preston Brown @author Cornelius Schumacher @author Reinhold Kainhofer */ #include #include #include #include "exceptions.h" #include "calfilter.h" #include "calendar.h" using namespace KCal; Calendar::Calendar( const TQString &timeZoneId ) { mTimeZoneId = timeZoneId; mLocalTime = false; init(); } void Calendar::init() { mNewObserver = false; mObserversEnabled = true; mModified = false; // Setup default filter, which does nothing mDefaultFilter = new CalFilter; mFilter = mDefaultFilter; mFilter->setEnabled( false ); // user information... setOwner( Person( i18n( "Unknown Name" ), i18n( "unknown@nowhere" ) ) ); } Calendar::~Calendar() { delete mDefaultFilter; } const Person &Calendar::getOwner() const { return mOwner; } void Calendar::setOwner( const Person &owner ) { mOwner = owner; setModified( true ); } void Calendar::setTimeZoneId( const TQString &timeZoneId ) { mTimeZoneId = timeZoneId; mLocalTime = false; setModified( true ); doSetTimeZoneId( timeZoneId ); } TQString Calendar::timeZoneId() const { return mTimeZoneId; } void Calendar::setLocalTime() { mLocalTime = true; mTimeZoneId = ""; setModified( true ); } bool Calendar::isLocalTime() const { return mLocalTime; } void Calendar::setFilter( CalFilter *filter ) { if ( filter ) { mFilter = filter; } else { mFilter = mDefaultFilter; } } CalFilter *Calendar::filter() { return mFilter; } TQStringList Calendar::categories() { Incidence::List rawInc( rawIncidences() ); TQStringList 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 ( TQStringList::ConstIterator si = thisCats.constBegin(); si != thisCats.constEnd(); ++si ) { if ( cats.find( *si ) == cats.end() ) { cats.append( *si ); } } } return cats; } Incidence::List Calendar::incidences( const TQDate &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 ) { 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 ); } 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 TQDate &date, EventSortField sortField, SortDirection sortDirection ) { Event::List el = rawEventsForDate( date, sortField, sortDirection ); mFilter->apply( &el ); return el; } Event::List Calendar::events( const TQDateTime &qdt ) { Event::List el = rawEventsForDate( qdt ); mFilter->apply( &el ); return el; } Event::List Calendar::events( const TQDate &start, const TQDate &end, bool inclusive) { Event::List el = rawEvents( start, end, inclusive ); mFilter->apply( &el ); return el; } Event::List Calendar::events( EventSortField sortField, SortDirection sortDirection ) { Event::List el = rawEvents( sortField, sortDirection ); 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 ) ) { if (incidence->hasRecurrenceID()) { // Delete this event's UID from the parent's list of children Incidence *parentIncidence; IncidenceList il = incidence->childIncidences(); IncidenceListIterator it; it = il.begin(); parentIncidence = this->incidence(*it); parentIncidence->deleteChildIncidence(incidence->uid()); } else { // Delete all children as well IncidenceList il = incidence->childIncidences(); IncidenceListIterator it; for ( it = il.begin(); it != il.end(); ++it ) { deleteIncidence( this->incidence(*it) ); // Avoid a crash, reset the iterator every time the list is modified it = il.begin(); } } 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, TQDate date, bool single ) { if ( !incidence || !incidence->doesRecur() ) return 0; Incidence *newInc = incidence->clone(); newInc->recreate(); newInc->setHasRecurrenceID(false); newInc->setRecurrenceID(QString()); 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 ) { kdDebug(5850) << "The dissociated event already occurred more often " << "than it was supposed to ever occur. ERROR!" << endl; recur->clear(); } else { recur->setDuration( duration - doneduration ); } } } // Adjust the date of the incidence if ( incidence->type() == "Event" ) { Event *ev = static_cast( newInc ); TQDateTime start( ev->dtStart() ); int daysTo = start.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() ) { TQDateTime due( td->dtDue() ); daysTo = due.date().daysTo( date ); td->setDtDue( due.addDays( daysTo ), true ); haveOffset = true; } if ( td->hasStartDate() ) { TQDateTime start( td->dtStart() ); if ( !haveOffset ) daysTo = start.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 TQString &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 TQString &UID ) { Incidence::List result; Incidence::List incidences = rawIncidences(); Incidence::List::iterator it = incidences.begin(); for ( ; it != incidences.end(); ++it ) if ( (*it)->schedulingID() == UID ) result.append( *it ); return result; } Incidence *Calendar::incidenceFromSchedulingID( const TQString &UID ) { Incidence::List incidences = rawIncidences(); Incidence::List::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 Todos 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 Todos without Start DateTimes tempList.append( *eit ); } } if ( sortDirection == SortDirectionAscending ) { // Append the list of Todos without Start DateTimes todoListSorted += tempList; } else { // Prepend the list of Todos 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 Todos without Due DateTimes tempList.append( *eit ); } } if ( sortDirection == SortDirectionAscending ) { // Append the list of Todos without Due DateTimes todoListSorted += tempList; } else { // Prepend the list of Todos 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 ); mFilter->apply( &tl ); return tl; } Todo::List Calendar::todos( const TQDate &date ) { Todo::List el = rawTodosForDate( date ); 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 ); mFilter->apply( &jl ); return jl; } Journal::List Calendar::journals( const TQDate &date ) { Journal::List el = rawJournalsForDate( date ); mFilter->apply( &el ); return el; } // When this is called, the todo have already been added to the calendar. // This method is only about linking related todos void Calendar::setupRelations( Incidence *forincidence ) { if ( !forincidence ) return; // kdDebug(5850) << "Calendar::setupRelations for incidence " << forincidence << " with UID " << forincidence->uid() << ", summary: " << forincidence->summary() << endl; TQString uid = forincidence->uid(); // First, go over the list of orphans and see if this is their parent while ( Incidence* i = mOrphans[ uid ] ) { mOrphans.remove( uid ); i->setRelatedTo( forincidence ); forincidence->addRelation( i ); mOrphanUids.remove( i->uid() ); } // Now see about this incidences parent if ( !forincidence->relatedTo() && !forincidence->relatedToUid().isEmpty() ) { // This 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 have several entries with the same key! That are // multiple children that wait for the parent incidence to be inserted. mOrphans.insert( forincidence->relatedToUid(), forincidence ); mOrphanUids.insert( forincidence->uid(), forincidence ); } } } // If a task with subtasks is deleted, move it's subtasks to the orphans list void Calendar::removeRelations( Incidence *incidence ) { if( !incidence ) { kdDebug(5800) << "Warning: Calendar::removeRelations( 0 )!\n"; return; } // kdDebug(5850) << "Calendar::removeRelations for incidence " << forincidence << " with UID " << forincidence->uid() << ", summary: " << forincidence->summary() << endl; TQString uid = incidence->uid(); Incidence::List relations = incidence->relations(); Incidence::List::ConstIterator it; for ( it = relations.begin(); it != relations.end(); ++it ) { Incidence *i = *it; if ( !mOrphanUids.find( i->uid() ) ) { mOrphans.insert( uid, i ); 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 ( 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 TQStringList relatedToUids; // First get the list of all keys in the mOrphans list that point to the removed item relatedToUids << incidence->relatedToUid(); for ( TQDictIterator it( mOrphans ); it.current(); ++it ) { if ( it.current()->uid() == uid ) { relatedToUids << it.currentKey(); } } // now go through all uids that have one entry that point to the incidence for ( TQStringList::Iterator uidit = relatedToUids.begin(); uidit != relatedToUids.end(); ++uidit ) { Incidence::List tempList; // Remove all to get access to the remaining entries while( Incidence* i = mOrphans[ *uidit ] ) { mOrphans.remove( *uidit ); 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 ) { mOrphans.insert( *uidit, *incit ); } } } } void Calendar::registerObserver( Observer *observer ) { if( !mObservers.contains( observer ) ) mObservers.append( observer ); mNewObserver = true; } void Calendar::unregisterObserver( Observer *observer ) { mObservers.remove( observer ); } void Calendar::setModified( bool modified ) { if ( modified != mModified || mNewObserver ) { mNewObserver = false; Observer *observer; for ( observer = mObservers.first(); observer; observer = mObservers.next() ) { observer->calendarModified( modified, this ); } mModified = modified; } } void Calendar::incidenceUpdated( IncidenceBase *incidence ) { incidence->setSyncStatus( Event::SYNCMOD ); incidence->setLastModified( TQDateTime::currentDateTime() ); // 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::notifyIncidenceAdded( Incidence *i ) { if ( !mObserversEnabled ) return; Observer *observer; for ( observer = mObservers.first(); observer; observer = mObservers.next() ) { observer->calendarIncidenceAdded( i ); } } void Calendar::notifyIncidenceChanged( Incidence *i ) { if ( !mObserversEnabled ) return; Observer *observer; for ( observer = mObservers.first(); observer; observer = mObservers.next() ) { observer->calendarIncidenceChanged( i ); } } void Calendar::notifyIncidenceDeleted( Incidence *i ) { if ( !mObserversEnabled ) return; Observer *observer; for ( observer = mObservers.first(); observer; observer = mObservers.next() ) { observer->calendarIncidenceDeleted( i ); } } void Calendar::customPropertyUpdated() { setModified( true ); } void Calendar::setProductId( const TQString &productId ) { mProductId = productId; } TQString Calendar::productId() { return mProductId; } Incidence::List Calendar::mergeIncidenceList( const Event::List &events, const Todo::List &todos, const Journal::List &journals ) { Incidence::List incidences; Event::List::ConstIterator it1; for ( it1 = events.begin(); it1 != events.end(); ++it1 ) incidences.append( *it1 ); Todo::List::ConstIterator it2; for ( it2 = todos.begin(); it2 != todos.end(); ++it2 ) incidences.append( *it2 ); Journal::List::ConstIterator it3; for ( it3 = journals.begin(); it3 != journals.end(); ++it3 ) incidences.append( *it3 ); return incidences; } bool Calendar::beginChange( Incidence * ) { return true; } bool Calendar::endChange( Incidence * ) { return true; } void Calendar::setObserversEnabled( bool enabled ) { mObserversEnabled = enabled; } #include "calendar.moc"