libkcal

calendarlocal.cpp
1 /*
2  This file is part of libkcal.
3 
4  Copyright (c) 1998 Preston Brown <pbrown@kde.org>
5  Copyright (c) 2001,2003,2004 Cornelius Schumacher <schumacher@kde.org>
6  Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
7 
8  This library is free software; you can redistribute it and/or
9  modify it under the terms of the GNU Library General Public
10  License as published by the Free Software Foundation; either
11  version 2 of the License, or (at your option) any later version.
12 
13  This library is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  Library General Public License for more details.
17 
18  You should have received a copy of the GNU Library General Public License
19  along with this library; see the file COPYING.LIB. If not, write to
20  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  Boston, MA 02110-1301, USA.
22 */
23 
24 #include <tqdatetime.h>
25 #include <tqstring.h>
26 #include <tqptrlist.h>
27 
28 #include <kdebug.h>
29 #include <tdelocale.h>
30 #include <tdemessagebox.h>
31 
32 #include "vcaldrag.h"
33 #include "vcalformat.h"
34 #include "icalformat.h"
35 #include "exceptions.h"
36 #include "incidence.h"
37 #include "journal.h"
38 #include "filestorage.h"
39 
40 #include "calendarlocal.h"
41 
42 using namespace KCal;
43 
44 CalendarLocal::CalendarLocal( const TQString &timeZoneId )
45  : Calendar( timeZoneId ), mEvents( 47 )
46 {
47  init();
48 }
49 
50 void CalendarLocal::init()
51 {
52  mDeletedIncidences.setAutoDelete( true );
53  mFileName = TQString();
54 }
55 
56 
57 CalendarLocal::~CalendarLocal()
58 {
59  close();
60 }
61 
62 bool CalendarLocal::load( const TQString &fileName, CalFormat *format )
63 {
64  mFileName = fileName;
65  FileStorage storage( this, fileName, format );
66  return storage.load();
67 }
68 
69 bool CalendarLocal::reload( const TQString &tz )
70 {
71  const TQString filename = mFileName;
72  save();
73  close();
74  mFileName = filename;
75  setTimeZoneId( tz );
76  FileStorage storage( this, mFileName );
77  return storage.load();
78 }
79 
80 bool CalendarLocal::save( const TQString &fileName, CalFormat *format )
81 {
82  // Save only if the calendar is either modified, or saved to a
83  // different file than it was loaded from
84  if ( mFileName != fileName || isModified() ) {
85  FileStorage storage( this, fileName, format );
86  return storage.save();
87  } else {
88  return true;
89  }
90 }
91 
93 {
94  setObserversEnabled( false );
95  mFileName = TQString();
96 
100 
101  mDeletedIncidences.clear();
102  setModified( false );
103 
104  setObserversEnabled( true );
105 }
106 
108 {
109  setObserversEnabled( false );
110  mFileName = TQString();
111 
112  deleteAllEvents();
113 
114  mDeletedIncidences.clear();
115  setModified( false );
116 
117  setObserversEnabled( true );
118 }
119 
121 {
122  setObserversEnabled( false );
123  mFileName = TQString();
124 
125  deleteAllTodos();
126 
127  mDeletedIncidences.clear();
128  setModified( false );
129 
130  setObserversEnabled( true );
131 }
132 
134 {
135  setObserversEnabled( false );
136  mFileName = TQString();
137 
139 
140  mDeletedIncidences.clear();
141  setModified( false );
142 
143  setObserversEnabled( true );
144 }
145 
146 
148 {
149  insertEvent( event );
150 
151  event->registerObserver( this );
152 
153  setModified( true );
154 
155  notifyIncidenceAdded( event );
156 
157  return true;
158 }
159 
161 {
162 // kdDebug(5800) << "CalendarLocal::deleteEvent" << endl;
163 
164  if ( mEvents.remove( event->uid() ) ) {
165  setModified( true );
166  notifyIncidenceDeleted( event );
167  mDeletedIncidences.append( event );
168  // Delete child events
169  if (!event->hasRecurrenceID()) {
170  deleteChildEvents(event);
171  }
172  return true;
173  } else {
174  kdWarning() << "CalendarLocal::deleteEvent(): Event not found." << endl;
175  return false;
176  }
177 }
178 
180 {
181  EventDictIterator it( mEvents );
182  for( ; it.current(); ++it ) {
183  Event *e = *it;
184  if (e->uid() == event->uid()) {
185  if ( e->hasRecurrenceID() ) {
186  deleteEvent(( e ));
187  }
188  }
189  }
190 
191  return true;
192 }
193 
195 {
196  // kdDebug(5800) << "CalendarLocal::deleteAllEvents" << endl;
197  TQDictIterator<Event> it( mEvents );
198  while( it.current() ) {
199  notifyIncidenceDeleted( it.current() );
200  ++it;
201  }
202 
203  mEvents.setAutoDelete( true );
204  mEvents.clear();
205  mEvents.setAutoDelete( false );
206 }
207 
208 Event *CalendarLocal::event( const TQString &uid )
209 {
210 // kdDebug(5800) << "CalendarLocal::event(): " << uid << endl;
211  return mEvents[ uid ];
212 }
213 
215 {
216  mTodoList.append( todo );
217 
218  todo->registerObserver( this );
219 
220  // Set up subtask relations
221  setupRelations( todo );
222 
223  setModified( true );
224 
225  notifyIncidenceAdded( todo );
226 
227  return true;
228 }
229 
231 {
232  // Handle orphaned children
233  removeRelations( todo );
234 
235  if ( mTodoList.removeRef( todo ) ) {
236  setModified( true );
237  notifyIncidenceDeleted( todo );
238  mDeletedIncidences.append( todo );
239  // Delete child todos
240  if (!todo->hasRecurrenceID()) {
241  deleteChildTodos(todo);
242  }
243  return true;
244  } else {
245  kdWarning() << "CalendarLocal::deleteTodo(): Todo not found." << endl;
246  return false;
247  }
248 }
249 
251 {
252  Todo::List::ConstIterator it;
253  for( it = mTodoList.begin(); it != mTodoList.end(); ++it ) {
254  Todo *t = *it;
255  if (t->uid() == todo->uid()) {
256  if ( t->hasRecurrenceID() ) {
257  deleteTodo(( t ));
258  }
259  }
260  }
261 
262  return true;
263 }
264 
266 {
267  // kdDebug(5800) << "CalendarLocal::deleteAllTodos()\n";
268  Todo::List::ConstIterator it;
269  for( it = mTodoList.begin(); it != mTodoList.end(); ++it ) {
270  notifyIncidenceDeleted( *it );
271  }
272 
273  mTodoList.setAutoDelete( true );
274  mTodoList.clearAll();
275  mTodoList.setAutoDelete( false );
276 }
277 
279  SortDirection sortDirection )
280 {
281  return sortTodos( &mTodoList, sortField, sortDirection );
282 }
283 
284 Todo *CalendarLocal::todo( const TQString &uid )
285 {
286  Todo::List::ConstIterator it;
287  for ( it = mTodoList.begin(); it != mTodoList.end(); ++it ) {
288  if ( (*it)->uid() == uid ) return *it;
289  }
290 
291  return 0;
292 }
293 
295 {
297 
298  Todo::List::ConstIterator it;
299  for ( it = mTodoList.begin(); it != mTodoList.end(); ++it ) {
300  Todo *todo = *it;
301  if ( todo->hasDueDate() && todo->dtDue().date() == date ) {
302  todos.append( todo );
303  }
304  }
305 
306  return todos;
307 }
308 
309 Alarm::List CalendarLocal::alarmsTo( const TQDateTime &to )
310 {
311  return alarms( TQDateTime( TQDate( 1900, 1, 1 ) ), to );
312 }
313 
314 Alarm::List CalendarLocal::alarms( const TQDateTime &from, const TQDateTime &to )
315 {
316 // kdDebug(5800) << "CalendarLocal::alarms(" << from.toString() << " - "
317 // << to.toString() << ")" << endl;
318 
320 
321  EventDictIterator it( mEvents );
322  for( ; it.current(); ++it ) {
323  Event *e = *it;
324  if ( e->doesRecur() ) appendRecurringAlarms( alarms, e, from, to );
325  else appendAlarms( alarms, e, from, to );
326  }
327 
328  Todo::List::ConstIterator it2;
329  for( it2 = mTodoList.begin(); it2 != mTodoList.end(); ++it2 ) {
330  Todo *t = *it2;
331  if ( t->isCompleted() ) {
332  continue;
333  }
334  if ( t->doesRecur() ) appendRecurringAlarms( alarms, t, from, to );
335  else appendAlarms( alarms, t, from, to );
336  }
337 
338  return alarms;
339 }
340 
342  const TQDateTime &from, const TQDateTime &to )
343 {
344  TQDateTime preTime = from.addSecs(-1);
345  Alarm::List::ConstIterator it;
346  for( it = incidence->alarms().begin(); it != incidence->alarms().end();
347  ++it ) {
348  Alarm *alarm = *it;
349  if ( alarm->enabled() ) {
350  TQDateTime dt = alarm->nextRepetition( preTime );
351  if ( dt.isValid() && dt <= to ) {
352  kdDebug(5800) << "CalendarLocal::appendAlarms() '"
353  << incidence->summary() << "': "
354  << dt.toString() << endl;
355  alarms.append( alarm );
356  }
357  }
358  }
359 }
360 
362  Incidence *incidence,
363  const TQDateTime &from,
364  const TQDateTime &to )
365 {
366  TQDateTime dt;
367  Duration endOffset( 0 );
368  bool endOffsetValid = false;
369  Duration period( from, to );
370 
371  Event *e = static_cast<Event *>( incidence );
372  Todo *t = static_cast<Todo *>( incidence );
373 
374  Alarm::List::ConstIterator it;
375  for( it = incidence->alarms().begin(); it != incidence->alarms().end();
376  ++it ) {
377  Alarm *alarm = *it;
378  if ( alarm->enabled() ) {
379  if ( alarm->hasTime() ) {
380  // The alarm time is defined as an absolute date/time
381  dt = alarm->nextRepetition( from.addSecs(-1) );
382  if ( !dt.isValid() || dt > to ) {
383  continue;
384  }
385  } else {
386  // Alarm time is defined by an offset from the event start or end time.
387  // Find the offset from the event start time, which is also used as the
388  // offset from the recurrence time.
389  Duration offset( 0 );
390  if ( alarm->hasStartOffset() ) {
391  offset = alarm->startOffset().asSeconds();
392  } else if ( alarm->hasEndOffset() ) {
393  offset = alarm->endOffset().asSeconds();
394  if ( !endOffsetValid ) {
395  if ( incidence->type() == "Event" ) {
396  endOffset = Duration( e->dtStart(), e->dtEnd() );
397  endOffsetValid = true;
398  } else if ( incidence->type() == "Todo" &&
399  t->hasStartDate() && t->hasDueDate() ) {
400  endOffset = Duration( t->dtStart(), t->dtDue() );
401  endOffsetValid = true;
402  }
403  }
404  }
405 
406  // Find the incidence's earliest alarm
407  TQDateTime alarmStart;
408  if ( incidence->type() == "Event" ) {
409  alarmStart =
410  offset.end( alarm->hasEndOffset() ? e->dtEnd() : e->dtStart() );
411  } else if ( incidence->type() == "Todo" ) {
412  alarmStart =
413  offset.end( alarm->hasEndOffset() ? t->dtDue() : t->dtStart() );
414  }
415 
416  if ( alarmStart.isValid() && alarmStart > to ) {
417  continue;
418  }
419 
420  TQDateTime baseStart;
421  if ( incidence->type() == "Event" ) {
422  baseStart = e->dtStart();
423  } else if ( incidence->type() == "Todo" ) {
424  baseStart = t->dtDue();
425  }
426  if ( alarmStart.isValid() && from > alarmStart ) {
427  alarmStart = from; // don't look earlier than the earliest alarm
428  baseStart = (-offset).end( (-endOffset).end( alarmStart ) );
429  }
430 
431  // Adjust the 'alarmStart' date/time and find the next recurrence
432  // at or after it. Treat the two offsets separately in case one
433  // is daily and the other not.
434  dt = incidence->recurrence()->getNextDateTime( baseStart.addSecs(-1) );
435  if ( !dt.isValid() ||
436  ( dt = endOffset.end( offset.end( dt ) ) ) > to ) // adjust 'dt' to get the alarm time
437  {
438  // The next recurrence is too late.
439  if ( !alarm->repeatCount() ) {
440  continue;
441  }
442 
443  // The alarm has repetitions, so check whether repetitions of
444  // previous recurrences fall within the time period.
445  bool found = false;
446  Duration alarmDuration = alarm->duration();
447  for ( TQDateTime base = baseStart;
448  ( dt = incidence->recurrence()->getPreviousDateTime( base ) ).isValid();
449  base = dt ) {
450  if ( alarm->duration().end( dt ) < base ) {
451  break; // recurrence's last repetition is too early, so give up
452  }
453 
454  // The last repetition of this recurrence is on or after
455  // 'alarmStart' time. Check if a repetition occurs between
456  // 'alarmStart' and 'to'.
457  int snooze = alarm->snoozeTime().value(); // in seconds or days
458  if ( alarm->snoozeTime().isDaily() ) {
459  Duration toFromDuration( dt, base );
460  int toFrom = toFromDuration.asDays();
461  if ( alarm->snoozeTime().end( from ) <= to ||
462  ( toFromDuration.isDaily() && toFrom % snooze == 0 ) ||
463  ( toFrom / snooze + 1 ) * snooze <= toFrom + period.asDays() ) {
464  found = true;
465 #ifndef NDEBUG
466  // for debug output
467  dt = offset.end( dt ).addDays( ( ( toFrom - 1 ) / snooze + 1 ) * snooze );
468 #endif
469  break;
470  }
471  } else {
472  int toFrom = dt.secsTo( base );
473  if ( period.asSeconds() >= snooze ||
474  toFrom % snooze == 0 ||
475  ( toFrom / snooze + 1 ) * snooze <= toFrom + period.asSeconds() )
476  {
477  found = true;
478 #ifndef NDEBUG
479  // for debug output
480  dt = offset.end( dt ).addSecs( ( ( toFrom - 1 ) / snooze + 1 ) * snooze );
481 #endif
482  break;
483  }
484  }
485  }
486  if ( !found ) {
487  continue;
488  }
489  }
490  }
491  kdDebug(5800) << "CalendarLocal::appendAlarms() '" << incidence->summary()
492  << "': " << dt.toString() << endl;
493  alarms.append( alarm );
494  }
495  }
496 }
497 
498 
500 {
501  incidence->setSyncStatusSilent( Event::SYNCMOD );
502  incidence->setLastModified( TQDateTime::currentDateTime() );
503  // we should probably update the revision number here,
504  // or internally in the Event itself when certain things change.
505  // need to verify with ical documentation.
506 
507  // The static_cast is ok as the CalendarLocal only observes Incidence objects
508  notifyIncidenceChanged( static_cast<Incidence *>( incidence ) );
509 
510  setModified( true );
511 }
512 
514 {
515  TQString uid = event->uid();
516  if ( mEvents[ uid ] == 0 ) {
517  mEvents.insert( uid, event );
518  }
519 #ifndef NDEBUG
520  else // if we already have an event with this UID, it has to be the same event,
521  // otherwise something's really broken
522  Q_ASSERT( mEvents[uid] == event );
523 #endif
524 }
525 
527  EventSortField sortField,
528  SortDirection sortDirection )
529 {
530  Event::List eventList;
531 
532  EventDictIterator it( mEvents );
533  for( ; it.current(); ++it ) {
534  Event *event = *it;
535 
536  if ( event->doesRecur() ) {
537  if ( event->isMultiDay() ) {
538  int extraDays = event->dtStart().date().daysTo( event->dtEnd().date() );
539  int i;
540  for ( i = 0; i <= extraDays; i++ ) {
541  if ( event->recursOn( qd.addDays( -i ), this ) ) {
542  eventList.append( event );
543  break;
544  }
545  }
546  } else {
547  if ( event->recursOn( qd, this ) )
548  eventList.append( event );
549  }
550  } else {
551  if ( event->dtStart().date() <= qd && event->dateEnd() >= qd ) {
552  eventList.append( event );
553  }
554  }
555  }
556 
557  return sortEventsForDate( &eventList, qd, sortField, sortDirection );
558 }
559 
560 Event::List CalendarLocal::rawEvents( const TQDate &start, const TQDate &end,
561  bool inclusive )
562 {
563  Event::List eventList;
564  TQDate yesterStart = start.addDays(-1);
565 
566  // Get non-recurring events
567  EventDictIterator it( mEvents );
568  for( ; it.current(); ++it ) {
569  Event *event = *it;
570 
571  TQDate rStart = event->dtStart().date();
572  if (end < rStart) {
573 // kdDebug(5800) << "Skipping event starting after TOI" << endl;
574  continue;
575  }
576  if ( inclusive && rStart < start) {
577 // kdDebug(5800) << "Skipping event starting before TOI while inclusive" << endl;
578  continue;
579  }
580 
581  if ( ! event->doesRecur() ) { // non-recurring events
582  TQDate rEnd = event->dtEnd().date();
583  if (rEnd < start) {
584 // kdDebug(5800) << "Skipping event ending before TOI" << endl;
585  continue;
586  }
587  if ( inclusive && end < rEnd ) {
588 // kdDebug(5800) << "Skipping event ending after TOI while inclusive" << endl;
589  continue;
590  }
591  } else { // recurring events
592  switch ( event->recurrence()->duration() ) {
593  case -1: // infinite
594  if ( inclusive ) {
595 // kdDebug(5800) << "Skipping infinite event because inclusive" << endl;
596  continue;
597  }
598  break;
599  case 0: // end date given
600  default: // count given
601  TQDate rEnd = event->recurrence()->endDate();
602  if ( ! rEnd.isValid() ) {
603 // kdDebug(5800) << "Skipping recurring event without occurences" << endl;
604  continue;
605  }
606  if ( rEnd < start ) {
607 // kdDebug(5800) << "Skipping recurring event ending before TOI" << endl;
608  continue;
609  }
610  if ( inclusive && end < rEnd ) {
611 // kdDebug(5800) << "Skipping recurring event ending after TOI while inclusive" << endl;
612  continue;
613  }
614  /* FIXME: too much conversion between TQDate and TQDateTime makes this useless:
615  * freebusy(end=TQDateTime(day, "00:00:00")) ->
616  * rawEvents(end=TQDate(day)) ->
617  * durationTo(TQDateTime(day, "23:59:59"))
618  * so events repeating at the end day match and are included.
619  */
620 #if 0
621  int durationBeforeStart = event->recurrence()->durationTo(yesterStart);
622  int durationUntilEnd = event->recurrence()->durationTo(end);
623  if (durationBeforeStart == durationUntilEnd) {
624  kdDebug(5800) << "Skipping recurring event without occurences in TOI" << endl;
625  continue;
626  }
627 #endif
628  break;
629  } // switch(duration)
630  } // if(doesRecur)
631 
632  eventList.append( event );
633  }
634 
635  return eventList;
636 }
637 
639 {
640  return rawEventsForDate( qdt.date() );
641 }
642 
644 {
645  Event::List eventList;
646  EventDictIterator it( mEvents );
647  for( ; it.current(); ++it )
648  eventList.append( *it );
649  return sortEvents( &eventList, sortField, sortDirection );
650 }
651 
653 {
654 // if (journal->dtStart().isValid())
655 // kdDebug(5800) << "Adding Journal on " << journal->dtStart().toString() << endl;
656 // else
657 // kdDebug(5800) << "Adding Journal without a DTSTART" << endl;
658 
659  mJournalList.append(journal);
660 
661  journal->registerObserver( this );
662 
663  setModified( true );
664 
665  notifyIncidenceAdded( journal );
666 
667  return true;
668 }
669 
671 {
672  if ( mJournalList.removeRef( journal ) ) {
673  setModified( true );
674  notifyIncidenceDeleted( journal );
675  mDeletedIncidences.append( journal );
676  // Delete child journals
677  if (!journal->hasRecurrenceID()) {
678  deleteChildJournals(journal);
679  }
680  return true;
681  } else {
682  kdWarning() << "CalendarLocal::deleteJournal(): Journal not found." << endl;
683  return false;
684  }
685 }
686 
688 {
689  Journal::List::ConstIterator it;
690  for( it = mJournalList.begin(); it != mJournalList.end(); ++it ) {
691  Journal *j = *it;
692  if (j->uid() == journal->uid()) {
693  if ( j->hasRecurrenceID() ) {
694  deleteJournal(( j ));
695  }
696  }
697  }
698 
699  return true;
700 }
701 
703 {
704  Journal::List::ConstIterator it;
705  for( it = mJournalList.begin(); it != mJournalList.end(); ++it ) {
706  notifyIncidenceDeleted( *it );
707  }
708 
709  mJournalList.setAutoDelete( true );
710  mJournalList.clearAll();
711  mJournalList.setAutoDelete( false );
712 }
713 
714 Journal *CalendarLocal::journal( const TQString &uid )
715 {
716  Journal::List::ConstIterator it;
717  for ( it = mJournalList.begin(); it != mJournalList.end(); ++it )
718  if ( (*it)->uid() == uid )
719  return *it;
720 
721  return 0;
722 }
723 
725 {
726  return sortJournals( &mJournalList, sortField, sortDirection );
727 }
728 
730 {
732 
733  Journal::List::ConstIterator it;
734  for ( it = mJournalList.begin(); it != mJournalList.end(); ++it ) {
735  Journal *journal = *it;
736  if ( journal->dtStart().date() == date ) {
737  journals.append( journal );
738  }
739  }
740 
741  return journals;
742 }
743 
744 void CalendarLocal::setTimeZoneIdViewOnly( const TQString& tz )
745 {
746  const TQString question( i18n("The timezone setting was changed. In order to display the calendar "
747  "you are looking at in the new timezone, it needs to be saved. Do you want to save the pending "
748  "changes or rather wait and apply the new timezone on the next reload?" ) );
749  int rc = KMessageBox::Yes;
750  if ( isModified() ) {
751  rc = KMessageBox::questionYesNo( 0, question,
752  i18n("Save before applying timezones?"),
753  KStdGuiItem::save(),
754  KGuiItem(i18n("Apply Timezone Change on Next Reload")),
755  "calendarLocalSaveBeforeTimezoneShift");
756  }
757  if ( rc == KMessageBox::Yes ) {
758  reload( tz );
759  }
760 }