/* This file is part of libkcal. Copyright (c) 1998 Preston Brown Copyright (c) 2001,2003,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. */ #include #include #include #include #include #include #include "vcaldrag.h" #include "vcalformat.h" #include "icalformat.h" #include "exceptions.h" #include "incidence.h" #include "journal.h" #include "filestorage.h" #include "calendarlocal.h" using namespace KCal; CalendarLocal::CalendarLocal( const TQString &timeZoneId ) : Calendar( timeZoneId ), mEvents( 47 ) { init(); } void CalendarLocal::init() { mDeletedIncidences.setAutoDelete( true ); mFileName = TQString::null; } CalendarLocal::~CalendarLocal() { close(); } bool CalendarLocal::load( const TQString &fileName, CalFormat *format ) { mFileName = fileName; FileStorage storage( this, fileName, format ); return storage.load(); } bool CalendarLocal::reload( const TQString &tz ) { const TQString filename = mFileName; save(); close(); mFileName = filename; setTimeZoneId( tz ); FileStorage storage( this, mFileName ); return storage.load(); } bool CalendarLocal::save( const TQString &fileName, CalFormat *format ) { // Save only if the calendar is either modified, or saved to a // different file than it was loaded from if ( mFileName != fileName || isModified() ) { FileStorage storage( this, fileName, format ); return storage.save(); } else { return true; } } void CalendarLocal::close() { setObserversEnabled( false ); mFileName = TQString::null; deleteAllEvents(); deleteAllTodos(); deleteAllJournals(); mDeletedIncidences.clear(); setModified( false ); setObserversEnabled( true ); } bool CalendarLocal::addEvent( Event *event ) { insertEvent( event ); event->registerObserver( this ); setModified( true ); notifyIncidenceAdded( event ); return true; } bool CalendarLocal::deleteEvent( Event *event ) { // kdDebug(5800) << "CalendarLocal::deleteEvent" << endl; if ( mEvents.remove( event->uid() ) ) { setModified( true ); notifyIncidenceDeleted( event ); mDeletedIncidences.append( event ); return true; } else { kdWarning() << "CalendarLocal::deleteEvent(): Event not found." << endl; return false; } } void CalendarLocal::deleteAllEvents() { // kdDebug(5800) << "CalendarLocal::deleteAllEvents" << endl; TQDictIterator it( mEvents ); while( it.current() ) { notifyIncidenceDeleted( it.current() ); ++it; } mEvents.setAutoDelete( true ); mEvents.clear(); mEvents.setAutoDelete( false ); } Event *CalendarLocal::event( const TQString &uid ) { // kdDebug(5800) << "CalendarLocal::event(): " << uid << endl; return mEvents[ uid ]; } bool CalendarLocal::addTodo( Todo *todo ) { mTodoList.append( todo ); todo->registerObserver( this ); // Set up subtask relations setupRelations( todo ); setModified( true ); notifyIncidenceAdded( todo ); return true; } bool CalendarLocal::deleteTodo( Todo *todo ) { // Handle orphaned children removeRelations( todo ); if ( mTodoList.removeRef( todo ) ) { setModified( true ); notifyIncidenceDeleted( todo ); mDeletedIncidences.append( todo ); return true; } else { kdWarning() << "CalendarLocal::deleteTodo(): Todo not found." << endl; return false; } } void CalendarLocal::deleteAllTodos() { // kdDebug(5800) << "CalendarLocal::deleteAllTodos()\n"; Todo::List::ConstIterator it; for( it = mTodoList.begin(); it != mTodoList.end(); ++it ) { notifyIncidenceDeleted( *it ); } mTodoList.setAutoDelete( true ); mTodoList.clearAll(); mTodoList.setAutoDelete( false ); } Todo::List CalendarLocal::rawTodos( TodoSortField sortField, SortDirection sortDirection ) { return sortTodos( &mTodoList, sortField, sortDirection ); } Todo *CalendarLocal::todo( const TQString &uid ) { Todo::List::ConstIterator it; for ( it = mTodoList.begin(); it != mTodoList.end(); ++it ) { if ( (*it)->uid() == uid ) return *it; } return 0; } Todo::List CalendarLocal::rawTodosForDate( const TQDate &date ) { Todo::List todos; Todo::List::ConstIterator it; for ( it = mTodoList.begin(); it != mTodoList.end(); ++it ) { Todo *todo = *it; if ( todo->hasDueDate() && todo->dtDue().date() == date ) { todos.append( todo ); } } return todos; } Alarm::List CalendarLocal::alarmsTo( const TQDateTime &to ) { return alarms( TQDateTime( TQDate( 1900, 1, 1 ) ), to ); } Alarm::List CalendarLocal::alarms( const TQDateTime &from, const TQDateTime &to ) { // kdDebug(5800) << "CalendarLocal::alarms(" << from.toString() << " - " // << to.toString() << ")" << endl; Alarm::List alarms; EventDictIterator it( mEvents ); for( ; it.current(); ++it ) { Event *e = *it; if ( e->doesRecur() ) appendRecurringAlarms( alarms, e, from, to ); else appendAlarms( alarms, e, from, to ); } Todo::List::ConstIterator it2; for( it2 = mTodoList.begin(); it2 != mTodoList.end(); ++it2 ) { if (! (*it2)->isCompleted() ) appendAlarms( alarms, *it2, from, to ); } return alarms; } void CalendarLocal::appendAlarms( Alarm::List &alarms, Incidence *incidence, const TQDateTime &from, const TQDateTime &to ) { TQDateTime preTime = from.addSecs(-1); Alarm::List::ConstIterator it; for( it = incidence->alarms().begin(); it != incidence->alarms().end(); ++it ) { if ( (*it)->enabled() ) { TQDateTime dt = (*it)->nextRepetition(preTime); if ( dt.isValid() && dt <= to ) { kdDebug(5800) << "CalendarLocal::appendAlarms() '" << incidence->summary() << "': " << dt.toString() << endl; alarms.append( *it ); } } } } void CalendarLocal::appendRecurringAlarms( Alarm::List &alarms, Incidence *incidence, const TQDateTime &from, const TQDateTime &to ) { TQDateTime qdt; int endOffset = 0; bool endOffsetValid = false; int period = from.secsTo(to); Alarm::List::ConstIterator it; for( it = incidence->alarms().begin(); it != incidence->alarms().end(); ++it ) { Alarm *alarm = *it; if ( alarm->enabled() ) { if ( alarm->hasTime() ) { // The alarm time is defined as an absolute date/time qdt = alarm->nextRepetition( from.addSecs(-1) ); if ( !qdt.isValid() || qdt > to ) continue; } else { // The 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. int offset = 0; if ( alarm->hasStartOffset() ) { offset = alarm->startOffset().asSeconds(); } else if ( alarm->hasEndOffset() ) { if ( !endOffsetValid ) { endOffset = incidence->dtStart().secsTo( incidence->dtEnd() ); endOffsetValid = true; } offset = alarm->endOffset().asSeconds() + endOffset; } // Adjust the 'from' date/time and find the next recurrence at or after it qdt = incidence->recurrence()->getNextDateTime( from.addSecs(-offset - 1) ); if ( !qdt.isValid() || (qdt = qdt.addSecs( offset )) > to ) // remove the adjustment to get the alarm time { // The next recurrence is too late. if ( !alarm->repeatCount() ) continue; // The alarm has repetitions, so check whether repetitions of previous // recurrences fall within the time period. bool found = false; qdt = from.addSecs( -offset ); while ( (qdt = incidence->recurrence()->getPreviousDateTime( qdt )).isValid() ) { int toFrom = qdt.secsTo( from ) - offset; if ( toFrom > alarm->duration() ) break; // this recurrence's last repetition is too early, so give up // The last repetition of this recurrence is at or after 'from' time. // Check if a repetition occurs between 'from' and 'to'. int snooze = alarm->snoozeTime() * 60; // in seconds if ( period >= snooze || toFrom % snooze == 0 || (toFrom / snooze + 1) * snooze <= toFrom + period ) { found = true; #ifndef NDEBUG qdt = qdt.addSecs( offset + ((toFrom-1) / snooze + 1) * snooze ); // for debug output #endif break; } } if ( !found ) continue; } } kdDebug(5800) << "CalendarLocal::appendAlarms() '" << incidence->summary() << "': " << qdt.toString() << endl; alarms.append( alarm ); } } } void CalendarLocal::incidenceUpdated( IncidenceBase *incidence ) { incidence->setSyncStatusSilent( 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 CalendarLocal::insertEvent( Event *event ) { TQString uid = event->uid(); if ( mEvents[ uid ] == 0 ) { mEvents.insert( uid, event ); } #ifndef NDEBUG else // if we already have an event with this UID, it has to be the same event, // otherwise something's really broken Q_ASSERT( mEvents[uid] == event ); #endif } Event::List CalendarLocal::rawEventsForDate( const TQDate &qd, EventSortField sortField, SortDirection sortDirection ) { Event::List eventList; EventDictIterator it( mEvents ); for( ; it.current(); ++it ) { Event *event = *it; if ( event->doesRecur() ) { if ( event->isMultiDay() ) { int extraDays = event->dtStart().date().daysTo( event->dtEnd().date() ); int i; for ( i = 0; i <= extraDays; i++ ) { if ( event->recursOn( qd.addDays( -i ) ) ) { eventList.append( event ); break; } } } else { if ( event->recursOn( qd ) ) eventList.append( event ); } } else { if ( event->dtStart().date() <= qd && event->dateEnd() >= qd ) { eventList.append( event ); } } } return sortEvents( &eventList, sortField, sortDirection ); } Event::List CalendarLocal::rawEvents( const TQDate &start, const TQDate &end, bool inclusive ) { Event::List eventList; TQDate yesterStart = start.addDays(-1); // Get non-recurring events EventDictIterator it( mEvents ); for( ; it.current(); ++it ) { Event *event = *it; TQDate rStart = event->dtStart().date(); if (end < rStart) { // kdDebug(5800) << "Skipping event starting after TOI" << endl; continue; } if ( inclusive && rStart < start) { // kdDebug(5800) << "Skipping event starting before TOI while inclusive" << endl; continue; } if ( ! event->doesRecur() ) { // non-recurring events TQDate rEnd = event->dtEnd().date(); if (rEnd < start) { // kdDebug(5800) << "Skipping event ending before TOI" << endl; continue; } if ( inclusive && end < rEnd ) { // kdDebug(5800) << "Skipping event ending after TOI while inclusive" << endl; continue; } } else { // recurring events switch ( event->recurrence()->duration() ) { case -1: // infinite if ( inclusive ) { // kdDebug(5800) << "Skipping infinite event because inclusive" << endl; continue; } break; case 0: // end date given default: // count given TQDate rEnd = event->recurrence()->endDate(); if ( ! rEnd.isValid() ) { // kdDebug(5800) << "Skipping recurring event without occurences" << endl; continue; } if ( rEnd < start ) { // kdDebug(5800) << "Skipping recurring event ending before TOI" << endl; continue; } if ( inclusive && end < rEnd ) { // kdDebug(5800) << "Skipping recurring event ending after TOI while inclusive" << endl; continue; } /* FIXME: too much conversion between TQDate and TQDateTime makes this useless: * freebusy(end=TQDateTime(day, "00:00:00")) -> * rawEvents(end=TQDate(day)) -> * durationTo(TQDateTime(day, "23:59:59")) * so events repeating at the end day match and are included. */ #if 0 int durationBeforeStart = event->recurrence()->durationTo(yesterStart); int durationUntilEnd = event->recurrence()->durationTo(end); if (durationBeforeStart == durationUntilEnd) { kdDebug(5800) << "Skipping recurring event without occurences in TOI" << endl; continue; } #endif break; } // switch(duration) } // if(doesRecur) eventList.append( event ); } return eventList; } Event::List CalendarLocal::rawEventsForDate( const TQDateTime &qdt ) { return rawEventsForDate( qdt.date() ); } Event::List CalendarLocal::rawEvents( EventSortField sortField, SortDirection sortDirection ) { Event::List eventList; EventDictIterator it( mEvents ); for( ; it.current(); ++it ) eventList.append( *it ); return sortEvents( &eventList, sortField, sortDirection ); } bool CalendarLocal::addJournal(Journal *journal) { // if (journal->dtStart().isValid()) // kdDebug(5800) << "Adding Journal on " << journal->dtStart().toString() << endl; // else // kdDebug(5800) << "Adding Journal without a DTSTART" << endl; mJournalList.append(journal); journal->registerObserver( this ); setModified( true ); notifyIncidenceAdded( journal ); return true; } bool CalendarLocal::deleteJournal( Journal *journal ) { if ( mJournalList.removeRef( journal ) ) { setModified( true ); notifyIncidenceDeleted( journal ); mDeletedIncidences.append( journal ); return true; } else { kdWarning() << "CalendarLocal::deleteJournal(): Journal not found." << endl; return false; } } void CalendarLocal::deleteAllJournals() { Journal::List::ConstIterator it; for( it = mJournalList.begin(); it != mJournalList.end(); ++it ) { notifyIncidenceDeleted( *it ); } mJournalList.setAutoDelete( true ); mJournalList.clearAll(); mJournalList.setAutoDelete( false ); } Journal *CalendarLocal::journal( const TQString &uid ) { Journal::List::ConstIterator it; for ( it = mJournalList.begin(); it != mJournalList.end(); ++it ) if ( (*it)->uid() == uid ) return *it; return 0; } Journal::List CalendarLocal::rawJournals( JournalSortField sortField, SortDirection sortDirection ) { return sortJournals( &mJournalList, sortField, sortDirection ); } Journal::List CalendarLocal::rawJournalsForDate( const TQDate &date ) { Journal::List journals; Journal::List::ConstIterator it; for ( it = mJournalList.begin(); it != mJournalList.end(); ++it ) { Journal *journal = *it; if ( journal->dtStart().date() == date ) { journals.append( journal ); } } return journals; } void CalendarLocal::setTimeZoneIdViewOnly( const TQString& tz ) { const TQString question( i18n("The timezone setting was changed. In order to display the calendar " "you are looking at in the new timezone, it needs to be saved. Do you want to save the pending " "changes or rather wait and apply the new timezone on the next reload?" ) ); int rc = KMessageBox::Yes; if ( isModified() ) { rc = KMessageBox::questionYesNo( 0, question, i18n("Save before applying timezones?"), KStdGuiItem::save(), KGuiItem(i18n("Apply Timezone Change on Next Reload")), "calendarLocalSaveBeforeTimezoneShift"); } if ( rc == KMessageBox::Yes ) { reload( tz ); } }