libkcal

calendarlocal.cpp

00001 /*
00002     This file is part of libkcal.
00003 
00004     Copyright (c) 1998 Preston Brown <pbrown@kde.org>
00005     Copyright (c) 2001,2003,2004 Cornelius Schumacher <schumacher@kde.org>
00006     Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
00007 
00008     This library is free software; you can redistribute it and/or
00009     modify it under the terms of the GNU Library General Public
00010     License as published by the Free Software Foundation; either
00011     version 2 of the License, or (at your option) any later version.
00012 
00013     This library is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016     Library General Public License for more details.
00017 
00018     You should have received a copy of the GNU Library General Public License
00019     along with this library; see the file COPYING.LIB.  If not, write to
00020     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00021     Boston, MA 02110-1301, USA.
00022 */
00023 
00024 #include <qdatetime.h>
00025 #include <qstring.h>
00026 #include <qptrlist.h>
00027 
00028 #include <kdebug.h>
00029 #include <klocale.h>
00030 #include <kmessagebox.h>
00031 
00032 #include "vcaldrag.h"
00033 #include "vcalformat.h"
00034 #include "icalformat.h"
00035 #include "exceptions.h"
00036 #include "incidence.h"
00037 #include "journal.h"
00038 #include "filestorage.h"
00039 
00040 #include "calendarlocal.h"
00041 
00042 using namespace KCal;
00043 
00044 CalendarLocal::CalendarLocal( const QString &timeZoneId )
00045   : Calendar( timeZoneId ), mEvents( 47 )
00046 {
00047   init();
00048 }
00049 
00050 void CalendarLocal::init()
00051 {
00052   mDeletedIncidences.setAutoDelete( true );
00053   mFileName = QString::null;
00054 }
00055 
00056 
00057 CalendarLocal::~CalendarLocal()
00058 {
00059   close();
00060 }
00061 
00062 bool CalendarLocal::load( const QString &fileName, CalFormat *format )
00063 {
00064   mFileName = fileName;
00065   FileStorage storage( this, fileName, format );
00066   return storage.load();
00067 }
00068 
00069 bool CalendarLocal::reload( const QString &tz )
00070 {
00071   const QString filename = mFileName;
00072   save();
00073   close();
00074   mFileName = filename;
00075   setTimeZoneId( tz );
00076   FileStorage storage( this, mFileName );
00077   return storage.load();
00078 }
00079 
00080 bool CalendarLocal::save( const QString &fileName, CalFormat *format )
00081 {
00082   // Save only if the calendar is either modified, or saved to a
00083   // different file than it was loaded from
00084   if ( mFileName != fileName || isModified() ) {
00085     FileStorage storage( this, fileName, format );
00086     return storage.save();
00087   } else {
00088     return true;
00089   }
00090 }
00091 
00092 void CalendarLocal::close()
00093 {
00094   setObserversEnabled( false );
00095   mFileName = QString::null;
00096 
00097   deleteAllEvents();
00098   deleteAllTodos();
00099   deleteAllJournals();
00100 
00101   mDeletedIncidences.clear();
00102   setModified( false );
00103 
00104   setObserversEnabled( true );
00105 }
00106 
00107 
00108 bool CalendarLocal::addEvent( Event *event )
00109 {
00110   insertEvent( event );
00111 
00112   event->registerObserver( this );
00113 
00114   setModified( true );
00115 
00116   notifyIncidenceAdded( event );
00117 
00118   return true;
00119 }
00120 
00121 bool CalendarLocal::deleteEvent( Event *event )
00122 {
00123 //  kdDebug(5800) << "CalendarLocal::deleteEvent" << endl;
00124 
00125   if ( mEvents.remove( event->uid() ) ) {
00126     setModified( true );
00127     notifyIncidenceDeleted( event );
00128     mDeletedIncidences.append( event );
00129     return true;
00130   } else {
00131     kdWarning() << "CalendarLocal::deleteEvent(): Event not found." << endl;
00132     return false;
00133   }
00134 }
00135 
00136 void CalendarLocal::deleteAllEvents()
00137 {
00138   // kdDebug(5800) << "CalendarLocal::deleteAllEvents" << endl;
00139   QDictIterator<Event> it( mEvents );
00140   while( it.current() ) {
00141     notifyIncidenceDeleted( it.current() );
00142     ++it;
00143   }
00144 
00145   mEvents.setAutoDelete( true );
00146   mEvents.clear();
00147   mEvents.setAutoDelete( false );
00148 }
00149 
00150 Event *CalendarLocal::event( const QString &uid )
00151 {
00152 //  kdDebug(5800) << "CalendarLocal::event(): " << uid << endl;
00153   return mEvents[ uid ];
00154 }
00155 
00156 bool CalendarLocal::addTodo( Todo *todo )
00157 {
00158   mTodoList.append( todo );
00159 
00160   todo->registerObserver( this );
00161 
00162   // Set up subtask relations
00163   setupRelations( todo );
00164 
00165   setModified( true );
00166 
00167   notifyIncidenceAdded( todo );
00168 
00169   return true;
00170 }
00171 
00172 bool CalendarLocal::deleteTodo( Todo *todo )
00173 {
00174   // Handle orphaned children
00175   removeRelations( todo );
00176 
00177   if ( mTodoList.removeRef( todo ) ) {
00178     setModified( true );
00179     notifyIncidenceDeleted( todo );
00180     mDeletedIncidences.append( todo );
00181     return true;
00182   } else {
00183     kdWarning() << "CalendarLocal::deleteTodo(): Todo not found." << endl;
00184     return false;
00185   }
00186 }
00187 
00188 void CalendarLocal::deleteAllTodos()
00189 {
00190   // kdDebug(5800) << "CalendarLocal::deleteAllTodos()\n";
00191   Todo::List::ConstIterator it;
00192   for( it = mTodoList.begin(); it != mTodoList.end(); ++it ) {
00193     notifyIncidenceDeleted( *it );
00194   }
00195 
00196   mTodoList.setAutoDelete( true );
00197   mTodoList.clearAll();
00198   mTodoList.setAutoDelete( false );
00199 }
00200 
00201 Todo::List CalendarLocal::rawTodos( TodoSortField sortField,
00202                                     SortDirection sortDirection )
00203 {
00204   return sortTodos( &mTodoList, sortField, sortDirection );
00205 }
00206 
00207 Todo *CalendarLocal::todo( const QString &uid )
00208 {
00209   Todo::List::ConstIterator it;
00210   for ( it = mTodoList.begin(); it != mTodoList.end(); ++it ) {
00211     if ( (*it)->uid() == uid ) return *it;
00212   }
00213 
00214   return 0;
00215 }
00216 
00217 Todo::List CalendarLocal::rawTodosForDate( const QDate &date )
00218 {
00219   Todo::List todos;
00220 
00221   Todo::List::ConstIterator it;
00222   for ( it = mTodoList.begin(); it != mTodoList.end(); ++it ) {
00223     Todo *todo = *it;
00224     if ( todo->hasDueDate() && todo->dtDue().date() == date ) {
00225       todos.append( todo );
00226     }
00227   }
00228 
00229   return todos;
00230 }
00231 
00232 Alarm::List CalendarLocal::alarmsTo( const QDateTime &to )
00233 {
00234   return alarms( QDateTime( QDate( 1900, 1, 1 ) ), to );
00235 }
00236 
00237 Alarm::List CalendarLocal::alarms( const QDateTime &from, const QDateTime &to )
00238 {
00239 //  kdDebug(5800) << "CalendarLocal::alarms(" << from.toString() << " - "
00240 //                << to.toString() << ")" << endl;
00241 
00242   Alarm::List alarms;
00243 
00244   EventDictIterator it( mEvents );
00245   for( ; it.current(); ++it ) {
00246     Event *e = *it;
00247     if ( e->doesRecur() ) appendRecurringAlarms( alarms, e, from, to );
00248     else appendAlarms( alarms, e, from, to );
00249   }
00250 
00251   Todo::List::ConstIterator it2;
00252   for( it2 = mTodoList.begin(); it2 != mTodoList.end(); ++it2 ) {
00253     Todo *t = *it2;
00254     if ( t->isCompleted() ) {
00255       continue;
00256     }
00257     if ( t->doesRecur() ) appendRecurringAlarms( alarms, t, from, to );
00258     else appendAlarms( alarms, t, from, to );
00259   }
00260 
00261   return alarms;
00262 }
00263 
00264 void CalendarLocal::appendAlarms( Alarm::List &alarms, Incidence *incidence,
00265                                   const QDateTime &from, const QDateTime &to )
00266 {
00267   QDateTime preTime = from.addSecs(-1);
00268   Alarm::List::ConstIterator it;
00269   for( it = incidence->alarms().begin(); it != incidence->alarms().end();
00270        ++it ) {
00271     Alarm *alarm = *it;
00272     if ( alarm->enabled() ) {
00273       QDateTime dt = alarm->nextRepetition( preTime );
00274       if ( dt.isValid() && dt <= to ) {
00275         kdDebug(5800) << "CalendarLocal::appendAlarms() '"
00276                       << incidence->summary() << "': "
00277                       << dt.toString() << endl;
00278         alarms.append( alarm );
00279       }
00280     }
00281   }
00282 }
00283 
00284 void CalendarLocal::appendRecurringAlarms( Alarm::List &alarms,
00285                                            Incidence *incidence,
00286                                            const QDateTime &from,
00287                                            const QDateTime &to )
00288 {
00289   QDateTime dt;
00290   Duration endOffset( 0 );
00291   bool endOffsetValid = false;
00292   Duration period( from, to );
00293 
00294   Event *e = static_cast<Event *>( incidence );
00295   Todo *t = static_cast<Todo *>( incidence );
00296 
00297   Alarm::List::ConstIterator it;
00298   for( it = incidence->alarms().begin(); it != incidence->alarms().end();
00299        ++it ) {
00300     Alarm *alarm = *it;
00301     if ( alarm->enabled() ) {
00302       if ( alarm->hasTime() ) {
00303         // The alarm time is defined as an absolute date/time
00304         dt = alarm->nextRepetition( from.addSecs(-1) );
00305         if ( !dt.isValid() || dt > to ) {
00306           continue;
00307         }
00308       } else {
00309         // Alarm time is defined by an offset from the event start or end time.
00310         // Find the offset from the event start time, which is also used as the
00311         // offset from the recurrence time.
00312         Duration offset( 0 );
00313         if ( alarm->hasStartOffset() ) {
00314           offset = alarm->startOffset().asSeconds();
00315         } else if ( alarm->hasEndOffset() ) {
00316           offset = alarm->endOffset();
00317           if ( !endOffsetValid ) {
00318             if ( incidence->type() == "Event" ) {
00319               endOffset = Duration( e->dtStart(), e->dtEnd() );
00320               endOffsetValid = true;
00321             } else if ( incidence->type() == "Todo" &&
00322                         t->hasStartDate() && t->hasDueDate() ) {
00323               endOffset = Duration( t->dtStart(), t->dtDue() );
00324               endOffsetValid = true;
00325             }
00326           }
00327         }
00328 
00329         // Find the incidence's earliest alarm
00330         QDateTime alarmStart;
00331         if ( incidence->type() == "Event" ) {
00332           alarmStart =
00333             offset.end( alarm->hasEndOffset() ? e->dtEnd() : e->dtStart() );
00334         } else if ( incidence->type() == "Todo" ) {
00335           if ( alarm->hasEndOffset() ) {
00336             if ( t->hasDueDate() ) {
00337               alarmStart = offset.end( t->dtDue() );
00338             } else if ( t->hasStartDate() ) {
00339               alarmStart = offset.end( t->dtStart() );
00340             }
00341           }
00342         }
00343 
00344         if ( alarmStart.isValid() && alarmStart > to ) {
00345           continue;
00346         }
00347 
00348         QDateTime baseStart;
00349         if ( incidence->type() == "Event" ) {
00350           baseStart = e->dtStart();
00351         } else if ( incidence->type() == "Todo" ) {
00352           if ( t->hasStartDate() ) {
00353             baseStart = t->dtStart();
00354           } else if ( t->hasDueDate() ) {
00355             baseStart = t->dtDue();
00356           }
00357         }
00358         if ( alarmStart.isValid() && from > alarmStart ) {
00359           alarmStart = from;   // don't look earlier than the earliest alarm
00360           baseStart = (-offset).end( (-endOffset).end( alarmStart ) );
00361         }
00362 
00363         // Adjust the 'alarmStart' date/time and find the next recurrence
00364         // at or after it. Treat the two offsets separately in case one
00365         // is daily and the other not.
00366         dt = incidence->recurrence()->getNextDateTime( baseStart.addSecs(-1) );
00367         if ( !dt.isValid() ||
00368              ( dt = endOffset.end( offset.end( dt ) ) ) > to ) // adjust 'dt' to get the alarm time
00369         {
00370           // The next recurrence is too late.
00371           if ( !alarm->repeatCount() ) {
00372             continue;
00373           }
00374 
00375           // The alarm has repetitions, so check whether repetitions of
00376           // previous recurrences fall within the time period.
00377           bool found = false;
00378           Duration alarmDuration = alarm->duration();
00379           for ( QDateTime base = baseStart;
00380                 ( dt = incidence->recurrence()->getPreviousDateTime( base ) ).isValid();
00381                 base = dt ) {
00382             if ( alarm->duration().end( dt ) < base ) {
00383               break;  // recurrence's last repetition is too early, so give up
00384             }
00385 
00386             // The last repetition of this recurrence is on or after
00387             // 'alarmStart' time. Check if a repetition occurs between
00388             // 'alarmStart' and 'to'.
00389             int snooze = alarm->snoozeTime().value();   // in seconds or days
00390             if ( alarm->snoozeTime().isDaily() ) {
00391               Duration toFromDuration( dt, base );
00392               int toFrom = toFromDuration.asDays();
00393               if ( alarm->snoozeTime().end( from ) <= to ||
00394                    ( toFromDuration.isDaily() && toFrom % snooze == 0 ) ||
00395                    ( toFrom / snooze + 1 ) * snooze <= toFrom + period.asDays() ) {
00396                 found = true;
00397 #ifndef NDEBUG
00398                 // for debug output
00399                 dt = offset.end( dt ).addDays( ( ( toFrom - 1 ) / snooze + 1 ) * snooze );
00400 #endif
00401                 break;
00402               }
00403             } else {
00404               int toFrom = dt.secsTo( base );
00405               if ( period.asSeconds() >= snooze ||
00406                    toFrom % snooze == 0 ||
00407                    ( toFrom / snooze + 1 ) * snooze <= toFrom + period.asSeconds() )
00408               {
00409                 found = true;
00410 #ifndef NDEBUG
00411                 // for debug output
00412                 dt = offset.end( dt ).addSecs( ( ( toFrom - 1 ) / snooze + 1 ) * snooze );
00413 #endif
00414                 break;
00415               }
00416             }
00417           }
00418           if ( !found ) {
00419             continue;
00420           }
00421         }
00422       }
00423       kdDebug(5800) << "CalendarLocal::appendAlarms() '" << incidence->summary()
00424                     << "': " << dt.toString() << endl;
00425       alarms.append( alarm );
00426     }
00427   }
00428 }
00429 
00430 
00431 void CalendarLocal::incidenceUpdated( IncidenceBase *incidence )
00432 {
00433   incidence->setSyncStatusSilent( Event::SYNCMOD );
00434   incidence->setLastModified( QDateTime::currentDateTime() );
00435   // we should probably update the revision number here,
00436   // or internally in the Event itself when certain things change.
00437   // need to verify with ical documentation.
00438 
00439   // The static_cast is ok as the CalendarLocal only observes Incidence objects
00440   notifyIncidenceChanged( static_cast<Incidence *>( incidence ) );
00441 
00442   setModified( true );
00443 }
00444 
00445 void CalendarLocal::insertEvent( Event *event )
00446 {
00447   QString uid = event->uid();
00448   if ( mEvents[ uid ] == 0 ) {
00449     mEvents.insert( uid, event );
00450   }
00451 #ifndef NDEBUG
00452   else // if we already have an event with this UID, it has to be the same event,
00453       // otherwise something's really broken
00454       Q_ASSERT( mEvents[uid] == event );
00455 #endif
00456 }
00457 
00458 Event::List CalendarLocal::rawEventsForDate( const QDate &qd,
00459                                              EventSortField sortField,
00460                                              SortDirection sortDirection )
00461 {
00462   Event::List eventList;
00463 
00464   EventDictIterator it( mEvents );
00465   for( ; it.current(); ++it ) {
00466     Event *event = *it;
00467 
00468     if ( event->doesRecur() ) {
00469       if ( event->isMultiDay() ) {
00470         int extraDays = event->dtStart().date().daysTo( event->dtEnd().date() );
00471         int i;
00472         for ( i = 0; i <= extraDays; i++ ) {
00473           if ( event->recursOn( qd.addDays( -i ) ) ) {
00474             eventList.append( event );
00475             break;
00476           }
00477         }
00478       } else {
00479         if ( event->recursOn( qd ) )
00480           eventList.append( event );
00481       }
00482     } else {
00483       if ( event->dtStart().date() <= qd && event->dateEnd() >= qd ) {
00484         eventList.append( event );
00485       }
00486     }
00487   }
00488 
00489   return sortEventsForDate( &eventList, qd, sortField, sortDirection );
00490 }
00491 
00492 Event::List CalendarLocal::rawEvents( const QDate &start, const QDate &end,
00493                                           bool inclusive )
00494 {
00495   Event::List eventList;
00496   QDate yesterStart = start.addDays(-1);
00497 
00498   // Get non-recurring events
00499   EventDictIterator it( mEvents );
00500   for( ; it.current(); ++it ) {
00501     Event *event = *it;
00502 
00503     QDate rStart = event->dtStart().date();
00504     if (end < rStart) {
00505 //      kdDebug(5800) << "Skipping event starting after TOI" << endl;
00506       continue;
00507     }
00508     if ( inclusive && rStart < start) {
00509 //      kdDebug(5800) << "Skipping event starting before TOI while inclusive" << endl;
00510       continue;
00511     }
00512 
00513     if ( ! event->doesRecur() ) { // non-recurring events
00514       QDate rEnd = event->dtEnd().date();
00515       if (rEnd < start) {
00516 //        kdDebug(5800) << "Skipping event ending before TOI" << endl;
00517         continue;
00518       }
00519       if ( inclusive && end < rEnd ) {
00520 //        kdDebug(5800) << "Skipping event ending after TOI while inclusive" << endl;
00521         continue;
00522       }
00523     } else { // recurring events
00524       switch ( event->recurrence()->duration() ) {
00525         case -1: // infinite
00526           if ( inclusive ) {
00527 //            kdDebug(5800) << "Skipping infinite event because inclusive" << endl;
00528             continue;
00529           }
00530           break;
00531         case 0: // end date given
00532         default: // count given
00533           QDate rEnd = event->recurrence()->endDate();
00534           if ( ! rEnd.isValid() ) {
00535 //            kdDebug(5800) << "Skipping recurring event without occurences" << endl;
00536             continue;
00537           }
00538           if ( rEnd < start ) {
00539 //            kdDebug(5800) << "Skipping recurring event ending before TOI" << endl;
00540             continue;
00541           }
00542           if ( inclusive && end < rEnd ) {
00543 //            kdDebug(5800) << "Skipping recurring event ending after TOI while inclusive" << endl;
00544             continue;
00545           }
00546           /* FIXME: too much conversion between QDate and QDateTime makes this useless:
00547            *   freebusy(end=QDateTime(day, "00:00:00")) ->
00548            *   rawEvents(end=QDate(day)) ->
00549            *   durationTo(QDateTime(day, "23:59:59"))
00550            * so events repeating at the end day match and are included.
00551            */
00552 #if 0
00553           int durationBeforeStart = event->recurrence()->durationTo(yesterStart);
00554           int durationUntilEnd = event->recurrence()->durationTo(end);
00555           if (durationBeforeStart == durationUntilEnd) {
00556             kdDebug(5800) << "Skipping recurring event without occurences in TOI" << endl;
00557             continue;
00558           }
00559 #endif
00560           break;
00561       } // switch(duration)
00562     } // if(doesRecur)
00563 
00564     eventList.append( event );
00565   }
00566 
00567   return eventList;
00568 }
00569 
00570 Event::List CalendarLocal::rawEventsForDate( const QDateTime &qdt )
00571 {
00572   return rawEventsForDate( qdt.date() );
00573 }
00574 
00575 Event::List CalendarLocal::rawEvents( EventSortField sortField, SortDirection sortDirection )
00576 {
00577   Event::List eventList;
00578   EventDictIterator it( mEvents );
00579   for( ; it.current(); ++it )
00580     eventList.append( *it );
00581   return sortEvents( &eventList, sortField, sortDirection );
00582 }
00583 
00584 bool CalendarLocal::addJournal(Journal *journal)
00585 {
00586 //   if (journal->dtStart().isValid())
00587 //     kdDebug(5800) << "Adding Journal on " << journal->dtStart().toString() << endl;
00588 //   else
00589 //     kdDebug(5800) << "Adding Journal without a DTSTART" << endl;
00590 
00591   mJournalList.append(journal);
00592 
00593   journal->registerObserver( this );
00594 
00595   setModified( true );
00596 
00597   notifyIncidenceAdded( journal );
00598 
00599   return true;
00600 }
00601 
00602 bool CalendarLocal::deleteJournal( Journal *journal )
00603 {
00604   if ( mJournalList.removeRef( journal ) ) {
00605     setModified( true );
00606     notifyIncidenceDeleted( journal );
00607     mDeletedIncidences.append( journal );
00608     return true;
00609   } else {
00610     kdWarning() << "CalendarLocal::deleteJournal(): Journal not found." << endl;
00611     return false;
00612   }
00613 }
00614 
00615 void CalendarLocal::deleteAllJournals()
00616 {
00617   Journal::List::ConstIterator it;
00618   for( it = mJournalList.begin(); it != mJournalList.end(); ++it ) {
00619     notifyIncidenceDeleted( *it );
00620   }
00621 
00622   mJournalList.setAutoDelete( true );
00623   mJournalList.clearAll();
00624   mJournalList.setAutoDelete( false );
00625 }
00626 
00627 Journal *CalendarLocal::journal( const QString &uid )
00628 {
00629   Journal::List::ConstIterator it;
00630   for ( it = mJournalList.begin(); it != mJournalList.end(); ++it )
00631     if ( (*it)->uid() == uid )
00632       return *it;
00633 
00634   return 0;
00635 }
00636 
00637 Journal::List CalendarLocal::rawJournals( JournalSortField sortField, SortDirection sortDirection )
00638 {
00639   return sortJournals( &mJournalList, sortField, sortDirection );
00640 }
00641 
00642 Journal::List CalendarLocal::rawJournalsForDate( const QDate &date )
00643 {
00644   Journal::List journals;
00645 
00646   Journal::List::ConstIterator it;
00647   for ( it = mJournalList.begin(); it != mJournalList.end(); ++it ) {
00648     Journal *journal = *it;
00649     if ( journal->dtStart().date() == date ) {
00650       journals.append( journal );
00651     }
00652   }
00653 
00654   return journals;
00655 }
00656 
00657 void CalendarLocal::setTimeZoneIdViewOnly( const QString& tz )
00658 {
00659   const QString question( i18n("The timezone setting was changed. In order to display the calendar "
00660       "you are looking at in the new timezone, it needs to be saved. Do you want to save the pending "
00661       "changes or rather wait and apply the new timezone on the next reload?" ) );
00662   int rc = KMessageBox::Yes;
00663   if ( isModified() ) {
00664     rc = KMessageBox::questionYesNo( 0, question,
00665                                        i18n("Save before applying timezones?"),
00666                                        KStdGuiItem::save(),
00667                                        KGuiItem(i18n("Apply Timezone Change on Next Reload")),
00668                                        "calendarLocalSaveBeforeTimezoneShift");
00669   }
00670   if ( rc == KMessageBox::Yes ) {
00671     reload( tz );
00672   }
00673 }
KDE Home | KDE Accessibility Home | Description of Access Keys