00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
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
00083
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
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
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
00153 return mEvents[ uid ];
00154 }
00155
00156 bool CalendarLocal::addTodo( Todo *todo )
00157 {
00158 mTodoList.append( todo );
00159
00160 todo->registerObserver( this );
00161
00162
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
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
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
00240
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
00304 dt = alarm->nextRepetition( from.addSecs(-1) );
00305 if ( !dt.isValid() || dt > to ) {
00306 continue;
00307 }
00308 } else {
00309
00310
00311
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
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;
00360 baseStart = (-offset).end( (-endOffset).end( alarmStart ) );
00361 }
00362
00363
00364
00365
00366 dt = incidence->recurrence()->getNextDateTime( baseStart.addSecs(-1) );
00367 if ( !dt.isValid() ||
00368 ( dt = endOffset.end( offset.end( dt ) ) ) > to )
00369 {
00370
00371 if ( !alarm->repeatCount() ) {
00372 continue;
00373 }
00374
00375
00376
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;
00384 }
00385
00386
00387
00388
00389 int snooze = alarm->snoozeTime().value();
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
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
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
00436
00437
00438
00439
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
00453
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
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
00506 continue;
00507 }
00508 if ( inclusive && rStart < start) {
00509
00510 continue;
00511 }
00512
00513 if ( ! event->doesRecur() ) {
00514 QDate rEnd = event->dtEnd().date();
00515 if (rEnd < start) {
00516
00517 continue;
00518 }
00519 if ( inclusive && end < rEnd ) {
00520
00521 continue;
00522 }
00523 } else {
00524 switch ( event->recurrence()->duration() ) {
00525 case -1:
00526 if ( inclusive ) {
00527
00528 continue;
00529 }
00530 break;
00531 case 0:
00532 default:
00533 QDate rEnd = event->recurrence()->endDate();
00534 if ( ! rEnd.isValid() ) {
00535
00536 continue;
00537 }
00538 if ( rEnd < start ) {
00539
00540 continue;
00541 }
00542 if ( inclusive && end < rEnd ) {
00543
00544 continue;
00545 }
00546
00547
00548
00549
00550
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 }
00562 }
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
00587
00588
00589
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 }