1/*
2 This file is part of the kcal library.
3
4 Copyright (c) 1998 Preston Brown <pbrown@kde.org>
5 Copyright (c) 2000-2004 Cornelius Schumacher <schumacher@kde.org>
6 Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
7 Copyright (c) 2006 David Jarvie <software@astrojar.org.uk>
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public
11 License as published by the Free Software Foundation; either
12 version 2 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Library General Public License for more details.
18
19 You should have received a copy of the GNU Library General Public License
20 along with this library; see the file COPYING.LIB. If not, write to
21 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 Boston, MA 02110-1301, USA.
23*/
24/**
25 @file
26 This file is part of the API for handling calendar data and
27 defines the Calendar class.
28
29 @brief
30 Represents the main calendar class.
31
32 @author Preston Brown \<pbrown@kde.org\>
33 @author Cornelius Schumacher \<schumacher@kde.org\>
34 @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
35 @author David Jarvie \<software@astrojar.org.uk\>
36*/
37
38#include "calendar.h"
39#include "exceptions.h"
40#include "calfilter.h"
41#include "icaltimezones.h"
42#include <kdebug.h>
43#include <klocalizedstring.h>
44
45extern "C" {
46 #include <icaltimezone.h>
47}
48
49using namespace KCal;
50
51/**
52 Private class that helps to provide binary compatibility between releases.
53 @internal
54*/
55//@cond PRIVATE
56class KCal::Calendar::Private
57{
58 public:
59 Private()
60 : mTimeZones( new ICalTimeZones ),
61 mModified( false ),
62 mNewObserver( false ),
63 mObserversEnabled( true ),
64 mDefaultFilter( new CalFilter )
65 {
66 // Setup default filter, which does nothing
67 mFilter = mDefaultFilter;
68 mFilter->setEnabled( false );
69
70 // user information...
71 mOwner.setName( i18n( "Unknown Name" ) );
72 mOwner.setEmail( i18n( "unknown@nowhere" ) );
73 }
74
75 ~Private()
76 {
77 delete mTimeZones;
78 if ( mFilter != mDefaultFilter ) {
79 delete mFilter;
80 }
81 delete mDefaultFilter;
82 }
83 KDateTime::Spec timeZoneIdSpec( const QString &timeZoneId, bool view );
84
85 QString mProductId;
86 Person mOwner;
87 ICalTimeZones *mTimeZones; // collection of time zones used in this calendar
88 ICalTimeZone mBuiltInTimeZone; // cached time zone lookup
89 ICalTimeZone mBuiltInViewTimeZone; // cached viewing time zone lookup
90 KDateTime::Spec mTimeSpec;
91 mutable KDateTime::Spec mViewTimeSpec;
92 bool mModified;
93 bool mNewObserver;
94 bool mObserversEnabled;
95 QList<CalendarObserver*> mObservers;
96
97 CalFilter *mDefaultFilter;
98 CalFilter *mFilter;
99
100 // These lists are used to put together related To-dos
101 QMultiHash<QString, Incidence*> mOrphans;
102 QMultiHash<QString, Incidence*> mOrphanUids;
103};
104//@endcond
105
106Calendar::Calendar( const KDateTime::Spec &timeSpec )
107 : d( new KCal::Calendar::Private )
108{
109 d->mTimeSpec = timeSpec;
110 d->mViewTimeSpec = timeSpec;
111}
112
113Calendar::Calendar( const QString &timeZoneId )
114 : d( new KCal::Calendar::Private )
115{
116 setTimeZoneId( timeZoneId );
117}
118
119Calendar::~Calendar()
120{
121 delete d;
122}
123
124Person Calendar::owner() const
125{
126 return d->mOwner;
127}
128
129void Calendar::setOwner( const Person &owner )
130{
131 d->mOwner = owner;
132
133 setModified( true );
134}
135
136void Calendar::setTimeSpec( const KDateTime::Spec &timeSpec )
137{
138 d->mTimeSpec = timeSpec;
139 d->mBuiltInTimeZone = ICalTimeZone();
140 setViewTimeSpec( timeSpec );
141
142 doSetTimeSpec( d->mTimeSpec );
143}
144
145KDateTime::Spec Calendar::timeSpec() const
146{
147 return d->mTimeSpec;
148}
149
150void Calendar::setTimeZoneId( const QString &timeZoneId )
151{
152 d->mTimeSpec = d->timeZoneIdSpec( timeZoneId, false );
153 d->mViewTimeSpec = d->mTimeSpec;
154 d->mBuiltInViewTimeZone = d->mBuiltInTimeZone;
155
156 doSetTimeSpec( d->mTimeSpec );
157}
158
159//@cond PRIVATE
160KDateTime::Spec Calendar::Private::timeZoneIdSpec( const QString &timeZoneId,
161 bool view )
162{
163 if ( view ) {
164 mBuiltInViewTimeZone = ICalTimeZone();
165 } else {
166 mBuiltInTimeZone = ICalTimeZone();
167 }
168 if ( timeZoneId == QLatin1String( "UTC" ) ) {
169 return KDateTime::UTC;
170 }
171 ICalTimeZone tz = mTimeZones->zone( timeZoneId );
172 if ( !tz.isValid() ) {
173 ICalTimeZoneSource tzsrc;
174 tz = tzsrc.parse( icaltimezone_get_builtin_timezone( timeZoneId.toLatin1() ) );
175 if ( view ) {
176 mBuiltInViewTimeZone = tz;
177 } else {
178 mBuiltInTimeZone = tz;
179 }
180 }
181 if ( tz.isValid() ) {
182 return tz;
183 } else {
184 return KDateTime::ClockTime;
185 }
186}
187//@endcond
188
189QString Calendar::timeZoneId() const
190{
191 KTimeZone tz = d->mTimeSpec.timeZone();
192 return tz.isValid() ? tz.name() : QString();
193}
194
195void Calendar::setViewTimeSpec( const KDateTime::Spec &timeSpec ) const
196{
197 d->mViewTimeSpec = timeSpec;
198 d->mBuiltInViewTimeZone = ICalTimeZone();
199}
200
201void Calendar::setViewTimeZoneId( const QString &timeZoneId ) const
202{
203 d->mViewTimeSpec = d->timeZoneIdSpec( timeZoneId, true );
204}
205
206KDateTime::Spec Calendar::viewTimeSpec() const
207{
208 return d->mViewTimeSpec;
209}
210
211QString Calendar::viewTimeZoneId() const
212{
213 KTimeZone tz = d->mViewTimeSpec.timeZone();
214 return tz.isValid() ? tz.name() : QString();
215}
216
217ICalTimeZones *Calendar::timeZones() const
218{
219 return d->mTimeZones;
220}
221
222void Calendar::shiftTimes( const KDateTime::Spec &oldSpec,
223 const KDateTime::Spec &newSpec )
224{
225 setTimeSpec( newSpec );
226
227 int i, end;
228 Event::List ev = events();
229 for ( i = 0, end = ev.count(); i < end; ++i ) {
230 ev[i]->shiftTimes( oldSpec, newSpec );
231 }
232
233 Todo::List to = todos();
234 for ( i = 0, end = to.count(); i < end; ++i ) {
235 to[i]->shiftTimes( oldSpec, newSpec );
236 }
237
238 Journal::List jo = journals();
239 for ( i = 0, end = jo.count(); i < end; ++i ) {
240 jo[i]->shiftTimes( oldSpec, newSpec );
241 }
242}
243
244void Calendar::setFilter( CalFilter *filter )
245{
246 if ( filter ) {
247 d->mFilter = filter;
248 } else {
249 d->mFilter = d->mDefaultFilter;
250 }
251}
252
253CalFilter *Calendar::filter()
254{
255 return d->mFilter;
256}
257
258QStringList Calendar::categories()
259{
260 Incidence::List rawInc( rawIncidences() );
261 QStringList cats, thisCats;
262 // @TODO: For now just iterate over all incidences. In the future,
263 // the list of categories should be built when reading the file.
264 for ( Incidence::List::ConstIterator i = rawInc.constBegin();
265 i != rawInc.constEnd(); ++i ) {
266 thisCats = (*i)->categories();
267 for ( QStringList::ConstIterator si = thisCats.constBegin();
268 si != thisCats.constEnd(); ++si ) {
269 if ( !cats.contains( *si ) ) {
270 cats.append( *si );
271 }
272 }
273 }
274 return cats;
275}
276
277Incidence::List Calendar::incidences( const QDate &date )
278{
279 return mergeIncidenceList( events( date ), todos( date ), journals( date ) );
280}
281
282Incidence::List Calendar::incidences()
283{
284 return mergeIncidenceList( events(), todos(), journals() );
285}
286
287Incidence::List Calendar::rawIncidences()
288{
289 return mergeIncidenceList( rawEvents(), rawTodos(), rawJournals() );
290}
291
292Event::List Calendar::sortEvents( Event::List *eventList,
293 EventSortField sortField,
294 SortDirection sortDirection )
295{
296 Event::List eventListSorted;
297 Event::List tempList;
298 Event::List alphaList;
299 Event::List::Iterator sortIt;
300 Event::List::Iterator eit;
301
302 // Notice we alphabetically presort Summaries first.
303 // We do this so comparison "ties" stay in a nice order.
304
305 switch( sortField ) {
306 case EventSortUnsorted:
307 eventListSorted = *eventList;
308 break;
309
310 case EventSortStartDate:
311 alphaList = sortEvents( eventList, EventSortSummary, sortDirection );
312 for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) {
313 if ( (*eit)->dtStart().isDateOnly() ) {
314 tempList.append( *eit );
315 continue;
316 }
317 sortIt = eventListSorted.begin();
318 if ( sortDirection == SortDirectionAscending ) {
319 while ( sortIt != eventListSorted.end() &&
320 (*eit)->dtStart() >= (*sortIt)->dtStart() ) {
321 ++sortIt;
322 }
323 } else {
324 while ( sortIt != eventListSorted.end() &&
325 (*eit)->dtStart() < (*sortIt)->dtStart() ) {
326 ++sortIt;
327 }
328 }
329 eventListSorted.insert( sortIt, *eit );
330 }
331 if ( sortDirection == SortDirectionAscending ) {
332 // Prepend the list of all-day Events
333 tempList += eventListSorted;
334 eventListSorted = tempList;
335 } else {
336 // Append the list of all-day Events
337 eventListSorted += tempList;
338 }
339 break;
340
341 case EventSortEndDate:
342 alphaList = sortEvents( eventList, EventSortSummary, sortDirection );
343 for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) {
344 if ( (*eit)->hasEndDate() ) {
345 sortIt = eventListSorted.begin();
346 if ( sortDirection == SortDirectionAscending ) {
347 while ( sortIt != eventListSorted.end() &&
348 (*eit)->dtEnd() >= (*sortIt)->dtEnd() ) {
349 ++sortIt;
350 }
351 } else {
352 while ( sortIt != eventListSorted.end() &&
353 (*eit)->dtEnd() < (*sortIt)->dtEnd() ) {
354 ++sortIt;
355 }
356 }
357 } else {
358 // Keep a list of the Events without End DateTimes
359 tempList.append( *eit );
360 }
361 eventListSorted.insert( sortIt, *eit );
362 }
363 if ( sortDirection == SortDirectionAscending ) {
364 // Append the list of Events without End DateTimes
365 eventListSorted += tempList;
366 } else {
367 // Prepend the list of Events without End DateTimes
368 tempList += eventListSorted;
369 eventListSorted = tempList;
370 }
371 break;
372
373 case EventSortSummary:
374 for ( eit = eventList->begin(); eit != eventList->end(); ++eit ) {
375 sortIt = eventListSorted.begin();
376 if ( sortDirection == SortDirectionAscending ) {
377 while ( sortIt != eventListSorted.end() &&
378 (*eit)->summary() >= (*sortIt)->summary() ) {
379 ++sortIt;
380 }
381 } else {
382 while ( sortIt != eventListSorted.end() &&
383 (*eit)->summary() < (*sortIt)->summary() ) {
384 ++sortIt;
385 }
386 }
387 eventListSorted.insert( sortIt, *eit );
388 }
389 break;
390 }
391
392 return eventListSorted;
393}
394
395Event::List Calendar::sortEventsForDate( Event::List *eventList,
396 const QDate &date,
397 const KDateTime::Spec &timeSpec,
398 EventSortField sortField,
399 SortDirection sortDirection )
400{
401 Event::List eventListSorted;
402 Event::List tempList;
403 Event::List alphaList;
404 Event::List::Iterator sortIt;
405 Event::List::Iterator eit;
406
407 switch( sortField ) {
408 case EventSortStartDate:
409 alphaList = sortEvents( eventList, EventSortSummary, sortDirection );
410 for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) {
411 if ( (*eit)->allDay() ) {
412 tempList.append( *eit );
413 continue;
414 }
415 sortIt = eventListSorted.begin();
416 if ( sortDirection == SortDirectionAscending ) {
417 while ( sortIt != eventListSorted.end() ) {
418 if ( !(*eit)->recurs() ) {
419 if ( (*eit)->dtStart().time() >= (*sortIt)->dtStart().time() ) {
420 ++sortIt;
421 } else {
422 break;
423 }
424 } else {
425 if ( (*eit)->recursOn( date, timeSpec ) ) {
426 if ( (*eit)->dtStart().time() >= (*sortIt)->dtStart().time() ) {
427 ++sortIt;
428 } else {
429 break;
430 }
431 } else {
432 ++sortIt;
433 }
434 }
435 }
436 } else { // descending
437 while ( sortIt != eventListSorted.end() ) {
438 if ( !(*eit)->recurs() ) {
439 if ( (*eit)->dtStart().time() < (*sortIt)->dtStart().time() ) {
440 ++sortIt;
441 } else {
442 break;
443 }
444 } else {
445 if ( (*eit)->recursOn( date, timeSpec ) ) {
446 if ( (*eit)->dtStart().time() < (*sortIt)->dtStart().time() ) {
447 ++sortIt;
448 } else {
449 break;
450 }
451 } else {
452 ++sortIt;
453 }
454 }
455 }
456 }
457 eventListSorted.insert( sortIt, *eit );
458 }
459 if ( sortDirection == SortDirectionAscending ) {
460 // Prepend the list of all-day Events
461 tempList += eventListSorted;
462 eventListSorted = tempList;
463 } else {
464 // Append the list of all-day Events
465 eventListSorted += tempList;
466 }
467 break;
468
469 case EventSortEndDate:
470 alphaList = sortEvents( eventList, EventSortSummary, sortDirection );
471 for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) {
472 if ( (*eit)->hasEndDate() ) {
473 sortIt = eventListSorted.begin();
474 if ( sortDirection == SortDirectionAscending ) {
475 while ( sortIt != eventListSorted.end() ) {
476 if ( !(*eit)->recurs() ) {
477 if ( (*eit)->dtEnd().time() >= (*sortIt)->dtEnd().time() ) {
478 ++sortIt;
479 } else {
480 break;
481 }
482 } else {
483 if ( (*eit)->recursOn( date, timeSpec ) ) {
484 if ( (*eit)->dtEnd().time() >= (*sortIt)->dtEnd().time() ) {
485 ++sortIt;
486 } else {
487 break;
488 }
489 } else {
490 ++sortIt;
491 }
492 }
493 }
494 } else { // descending
495 while ( sortIt != eventListSorted.end() ) {
496 if ( !(*eit)->recurs() ) {
497 if ( (*eit)->dtEnd().time() < (*sortIt)->dtEnd().time() ) {
498 ++sortIt;
499 } else {
500 break;
501 }
502 } else {
503 if ( (*eit)->recursOn( date, timeSpec ) ) {
504 if ( (*eit)->dtEnd().time() < (*sortIt)->dtEnd().time() ) {
505 ++sortIt;
506 } else {
507 break;
508 }
509 } else {
510 ++sortIt;
511 }
512 }
513 }
514 }
515 } else {
516 // Keep a list of the Events without End DateTimes
517 tempList.append( *eit );
518 }
519 eventListSorted.insert( sortIt, *eit );
520 }
521 if ( sortDirection == SortDirectionAscending ) {
522 // Prepend the list of Events without End DateTimes
523 tempList += eventListSorted;
524 eventListSorted = tempList;
525 } else {
526 // Append the list of Events without End DateTimes
527 eventListSorted += tempList;
528 }
529 break;
530
531 default:
532 eventListSorted = sortEvents( eventList, sortField, sortDirection );
533 break;
534 }
535
536 return eventListSorted;
537}
538
539Event::List Calendar::events( const QDate &date,
540 const KDateTime::Spec &timeSpec,
541 EventSortField sortField,
542 SortDirection sortDirection )
543{
544 Event::List el = rawEventsForDate( date, timeSpec, sortField, sortDirection );
545 d->mFilter->apply( &el );
546 return el;
547}
548
549Event::List Calendar::events( const KDateTime &dt )
550{
551 Event::List el = rawEventsForDate( dt );
552 d->mFilter->apply( &el );
553 return el;
554}
555
556Event::List Calendar::events( const QDate &start, const QDate &end,
557 const KDateTime::Spec &timeSpec,
558 bool inclusive )
559{
560 Event::List el = rawEvents( start, end, timeSpec, inclusive );
561 d->mFilter->apply( &el );
562 return el;
563}
564
565Event::List Calendar::events( EventSortField sortField,
566 SortDirection sortDirection )
567{
568 Event::List el = rawEvents( sortField, sortDirection );
569 d->mFilter->apply( &el );
570 return el;
571}
572
573bool Calendar::addIncidence( Incidence *incidence )
574{
575 Incidence::AddVisitor<Calendar> v( this );
576
577 return incidence->accept( v );
578}
579
580bool Calendar::deleteIncidence( Incidence *incidence )
581{
582 if ( beginChange( incidence ) ) {
583 Incidence::DeleteVisitor<Calendar> v( this );
584 bool result = incidence->accept( v );
585 endChange( incidence );
586 return result;
587 } else {
588 return false;
589 }
590}
591
592// Dissociate a single occurrence or all future occurrences from a recurring
593// sequence. The new incidence is returned, but not automatically inserted
594// into the calendar, which is left to the calling application.
595Incidence *Calendar::dissociateOccurrence( Incidence *incidence,
596 const QDate &date,
597 const KDateTime::Spec &spec,
598 bool single )
599{
600 if ( !incidence || !incidence->recurs() ) {
601 return 0;
602 }
603
604 Incidence *newInc = incidence->clone();
605 newInc->recreate();
606 // Do not call setRelatedTo() when dissociating recurring to-dos, otherwise the new to-do
607 // will appear as a child. Originally, we planned to set a relation with reltype SIBLING
608 // when dissociating to-dos, but currently kcal only supports reltype PARENT.
609 // We can uncomment the following line when we support the PARENT reltype.
610 //newInc->setRelatedTo( incidence );
611 Recurrence *recur = newInc->recurrence();
612 if ( single ) {
613 recur->clear();
614 } else {
615 // Adjust the recurrence for the future incidences. In particular adjust
616 // the "end after n occurrences" rules! "No end date" and "end by ..."
617 // don't need to be modified.
618 int duration = recur->duration();
619 if ( duration > 0 ) {
620 int doneduration = recur->durationTo( date.addDays( -1 ) );
621 if ( doneduration >= duration ) {
622 kDebug() << "The dissociated event already occurred more often"
623 << "than it was supposed to ever occur. ERROR!";
624 recur->clear();
625 } else {
626 recur->setDuration( duration - doneduration );
627 }
628 }
629 }
630 // Adjust the date of the incidence
631 if ( incidence->type() == "Event" ) {
632 Event *ev = static_cast<Event *>( newInc );
633 KDateTime start( ev->dtStart() );
634 int daysTo = start.toTimeSpec( spec ).date().daysTo( date );
635 ev->setDtStart( start.addDays( daysTo ) );
636 ev->setDtEnd( ev->dtEnd().addDays( daysTo ) );
637 } else if ( incidence->type() == "Todo" ) {
638 Todo *td = static_cast<Todo *>( newInc );
639 bool haveOffset = false;
640 int daysTo = 0;
641 if ( td->hasDueDate() ) {
642 KDateTime due( td->dtDue() );
643 daysTo = due.toTimeSpec( spec ).date().daysTo( date );
644 td->setDtDue( due.addDays( daysTo ), true );
645 haveOffset = true;
646 }
647 if ( td->hasStartDate() ) {
648 KDateTime start( td->dtStart() );
649 if ( !haveOffset ) {
650 daysTo = start.toTimeSpec( spec ).date().daysTo( date );
651 }
652 td->setDtStart( start.addDays( daysTo ) );
653 haveOffset = true;
654 }
655 }
656 recur = incidence->recurrence();
657 if ( recur ) {
658 if ( single ) {
659 recur->addExDate( date );
660 } else {
661 // Make sure the recurrence of the past events ends
662 // at the corresponding day
663 recur->setEndDate( date.addDays(-1) );
664 }
665 }
666 return newInc;
667}
668
669Incidence *Calendar::incidence( const QString &uid )
670{
671 Incidence *i = event( uid );
672 if ( i ) {
673 return i;
674 }
675
676 i = todo( uid );
677 if ( i ) {
678 return i;
679 }
680
681 i = journal( uid );
682 return i;
683}
684
685Incidence::List Calendar::incidencesFromSchedulingID( const QString &sid )
686{
687 Incidence::List result;
688 const Incidence::List incidences = rawIncidences();
689 Incidence::List::const_iterator it = incidences.begin();
690 for ( ; it != incidences.end(); ++it ) {
691 if ( (*it)->schedulingID() == sid ) {
692 result.append( *it );
693 }
694 }
695 return result;
696}
697
698Incidence *Calendar::incidenceFromSchedulingID( const QString &UID )
699{
700 const Incidence::List incidences = rawIncidences();
701 Incidence::List::const_iterator it = incidences.begin();
702 for ( ; it != incidences.end(); ++it ) {
703 if ( (*it)->schedulingID() == UID ) {
704 // Touchdown, and the crowd goes wild
705 return *it;
706 }
707 }
708 // Not found
709 return 0;
710}
711
712Todo::List Calendar::sortTodos( Todo::List *todoList,
713 TodoSortField sortField,
714 SortDirection sortDirection )
715{
716 Todo::List todoListSorted;
717 Todo::List tempList, t;
718 Todo::List alphaList;
719 Todo::List::Iterator sortIt;
720 Todo::List::Iterator eit;
721
722 // Notice we alphabetically presort Summaries first.
723 // We do this so comparison "ties" stay in a nice order.
724
725 // Note that To-dos may not have Start DateTimes nor due DateTimes.
726
727 switch( sortField ) {
728 case TodoSortUnsorted:
729 todoListSorted = *todoList;
730 break;
731
732 case TodoSortStartDate:
733 alphaList = sortTodos( todoList, TodoSortSummary, sortDirection );
734 for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) {
735 if ( (*eit)->hasStartDate() ) {
736 sortIt = todoListSorted.begin();
737 if ( sortDirection == SortDirectionAscending ) {
738 while ( sortIt != todoListSorted.end() &&
739 (*eit)->dtStart() >= (*sortIt)->dtStart() ) {
740 ++sortIt;
741 }
742 } else {
743 while ( sortIt != todoListSorted.end() &&
744 (*eit)->dtStart() < (*sortIt)->dtStart() ) {
745 ++sortIt;
746 }
747 }
748 todoListSorted.insert( sortIt, *eit );
749 } else {
750 // Keep a list of the To-dos without Start DateTimes
751 tempList.append( *eit );
752 }
753 }
754 if ( sortDirection == SortDirectionAscending ) {
755 // Append the list of To-dos without Start DateTimes
756 todoListSorted += tempList;
757 } else {
758 // Prepend the list of To-dos without Start DateTimes
759 tempList += todoListSorted;
760 todoListSorted = tempList;
761 }
762 break;
763
764 case TodoSortDueDate:
765 alphaList = sortTodos( todoList, TodoSortSummary, sortDirection );
766 for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) {
767 if ( (*eit)->hasDueDate() ) {
768 sortIt = todoListSorted.begin();
769 if ( sortDirection == SortDirectionAscending ) {
770 while ( sortIt != todoListSorted.end() &&
771 (*eit)->dtDue() >= (*sortIt)->dtDue() ) {
772 ++sortIt;
773 }
774 } else {
775 while ( sortIt != todoListSorted.end() &&
776 (*eit)->dtDue() < (*sortIt)->dtDue() ) {
777 ++sortIt;
778 }
779 }
780 todoListSorted.insert( sortIt, *eit );
781 } else {
782 // Keep a list of the To-dos without Due DateTimes
783 tempList.append( *eit );
784 }
785 }
786 if ( sortDirection == SortDirectionAscending ) {
787 // Append the list of To-dos without Due DateTimes
788 todoListSorted += tempList;
789 } else {
790 // Prepend the list of To-dos without Due DateTimes
791 tempList += todoListSorted;
792 todoListSorted = tempList;
793 }
794 break;
795
796 case TodoSortPriority:
797 alphaList = sortTodos( todoList, TodoSortSummary, sortDirection );
798 for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) {
799 sortIt = todoListSorted.begin();
800 if ( sortDirection == SortDirectionAscending ) {
801 while ( sortIt != todoListSorted.end() &&
802 (*eit)->priority() >= (*sortIt)->priority() ) {
803 ++sortIt;
804 }
805 } else {
806 while ( sortIt != todoListSorted.end() &&
807 (*eit)->priority() < (*sortIt)->priority() ) {
808 ++sortIt;
809 }
810 }
811 todoListSorted.insert( sortIt, *eit );
812 }
813 break;
814
815 case TodoSortPercentComplete:
816 alphaList = sortTodos( todoList, TodoSortSummary, sortDirection );
817 for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) {
818 sortIt = todoListSorted.begin();
819 if ( sortDirection == SortDirectionAscending ) {
820 while ( sortIt != todoListSorted.end() &&
821 (*eit)->percentComplete() >= (*sortIt)->percentComplete() ) {
822 ++sortIt;
823 }
824 } else {
825 while ( sortIt != todoListSorted.end() &&
826 (*eit)->percentComplete() < (*sortIt)->percentComplete() ) {
827 ++sortIt;
828 }
829 }
830 todoListSorted.insert( sortIt, *eit );
831 }
832 break;
833
834 case TodoSortSummary:
835 for ( eit = todoList->begin(); eit != todoList->end(); ++eit ) {
836 sortIt = todoListSorted.begin();
837 if ( sortDirection == SortDirectionAscending ) {
838 while ( sortIt != todoListSorted.end() &&
839 (*eit)->summary() >= (*sortIt)->summary() ) {
840 ++sortIt;
841 }
842 } else {
843 while ( sortIt != todoListSorted.end() &&
844 (*eit)->summary() < (*sortIt)->summary() ) {
845 ++sortIt;
846 }
847 }
848 todoListSorted.insert( sortIt, *eit );
849 }
850 break;
851 }
852
853 return todoListSorted;
854}
855
856Todo::List Calendar::todos( TodoSortField sortField,
857 SortDirection sortDirection )
858{
859 Todo::List tl = rawTodos( sortField, sortDirection );
860 d->mFilter->apply( &tl );
861 return tl;
862}
863
864Todo::List Calendar::todos( const QDate &date )
865{
866 Todo::List el = rawTodosForDate( date );
867 d->mFilter->apply( &el );
868 return el;
869}
870
871Journal::List Calendar::sortJournals( Journal::List *journalList,
872 JournalSortField sortField,
873 SortDirection sortDirection )
874{
875 Journal::List journalListSorted;
876 Journal::List::Iterator sortIt;
877 Journal::List::Iterator eit;
878
879 switch( sortField ) {
880 case JournalSortUnsorted:
881 journalListSorted = *journalList;
882 break;
883
884 case JournalSortDate:
885 for ( eit = journalList->begin(); eit != journalList->end(); ++eit ) {
886 sortIt = journalListSorted.begin();
887 if ( sortDirection == SortDirectionAscending ) {
888 while ( sortIt != journalListSorted.end() &&
889 (*eit)->dtStart() >= (*sortIt)->dtStart() ) {
890 ++sortIt;
891 }
892 } else {
893 while ( sortIt != journalListSorted.end() &&
894 (*eit)->dtStart() < (*sortIt)->dtStart() ) {
895 ++sortIt;
896 }
897 }
898 journalListSorted.insert( sortIt, *eit );
899 }
900 break;
901
902 case JournalSortSummary:
903 for ( eit = journalList->begin(); eit != journalList->end(); ++eit ) {
904 sortIt = journalListSorted.begin();
905 if ( sortDirection == SortDirectionAscending ) {
906 while ( sortIt != journalListSorted.end() &&
907 (*eit)->summary() >= (*sortIt)->summary() ) {
908 ++sortIt;
909 }
910 } else {
911 while ( sortIt != journalListSorted.end() &&
912 (*eit)->summary() < (*sortIt)->summary() ) {
913 ++sortIt;
914 }
915 }
916 journalListSorted.insert( sortIt, *eit );
917 }
918 break;
919 }
920
921 return journalListSorted;
922}
923
924Journal::List Calendar::journals( JournalSortField sortField,
925 SortDirection sortDirection )
926{
927 Journal::List jl = rawJournals( sortField, sortDirection );
928 d->mFilter->apply( &jl );
929 return jl;
930}
931
932Journal::List Calendar::journals( const QDate &date )
933{
934 Journal::List el = rawJournalsForDate( date );
935 d->mFilter->apply( &el );
936 return el;
937}
938
939void Calendar::beginBatchAdding()
940{
941 emit batchAddingBegins();
942}
943
944void Calendar::endBatchAdding()
945{
946 emit batchAddingEnds();
947}
948
949// When this is called, the to-dos have already been added to the calendar.
950// This method is only about linking related to-dos.
951void Calendar::setupRelations( Incidence *forincidence )
952{
953 if ( !forincidence ) {
954 return;
955 }
956
957 QString uid = forincidence->uid();
958
959 // First, go over the list of orphans and see if this is their parent
960 QList<Incidence*> l = d->mOrphans.values( uid );
961 d->mOrphans.remove( uid );
962 for ( int i = 0, end = l.count(); i < end; ++i ) {
963 l[i]->setRelatedTo( forincidence );
964 forincidence->addRelation( l[i] );
965 d->mOrphanUids.remove( l[i]->uid() );
966 }
967
968 // Now see about this incidences parent
969 if ( !forincidence->relatedTo() && !forincidence->relatedToUid().isEmpty() ) {
970 // Incidence has a uid it is related to but is not registered to it yet.
971 // Try to find it
972 Incidence *parent = incidence( forincidence->relatedToUid() );
973 if ( parent ) {
974 // Found it
975
976 // look for hierarchy loops
977 if ( isAncestorOf( forincidence, parent ) ) {
978 forincidence->setRelatedToUid( QString() );
979 kWarning() << "hierarchy loop beetween " << forincidence->uid() << " and " << parent->uid();
980 } else {
981 forincidence->setRelatedTo( parent );
982 parent->addRelation( forincidence );
983 }
984
985 } else {
986 // Not found, put this in the mOrphans list
987 // Note that the mOrphans dict might contain multiple entries with the
988 // same key! which are multiple children that wait for the parent
989 // incidence to be inserted.
990 d->mOrphans.insert( forincidence->relatedToUid(), forincidence );
991 d->mOrphanUids.insert( forincidence->uid(), forincidence );
992 }
993 }
994}
995
996// If a to-do with sub-to-dos is deleted, move it's sub-to-dos to the orphan list
997void Calendar::removeRelations( Incidence *incidence )
998{
999 if ( !incidence ) {
1000 kDebug() << "Warning: incidence is 0";
1001 return;
1002 }
1003
1004 QString uid = incidence->uid();
1005 foreach ( Incidence *i, incidence->relations() ) {
1006 if ( !d->mOrphanUids.contains( i->uid() ) ) {
1007 d->mOrphans.insert( uid, i );
1008 d->mOrphanUids.insert( i->uid(), i );
1009 i->setRelatedTo( 0 );
1010 i->setRelatedToUid( uid );
1011 }
1012 }
1013
1014 // If this incidence is related to something else, tell that about it
1015 if ( incidence->relatedTo() ) {
1016 incidence->relatedTo()->removeRelation( incidence );
1017 }
1018
1019 // Remove this one from the orphans list
1020 if ( d->mOrphanUids.remove( uid ) ) {
1021 // This incidence is located in the orphans list - it should be removed
1022 // Since the mOrphans dict might contain the same key (with different
1023 // child incidence pointers!) multiple times, take care that we remove
1024 // the correct one. So we need to remove all items with the given
1025 // parent UID, and readd those that are not for this item. Also, there
1026 // might be other entries with differnet UID that point to this
1027 // incidence (this might happen when the relatedTo of the item is
1028 // changed before its parent is inserted. This might happen with
1029 // groupware servers....). Remove them, too
1030 QStringList relatedToUids;
1031
1032 // First, create a list of all keys in the mOrphans list which point
1033 // to the removed item
1034 relatedToUids << incidence->relatedToUid();
1035 for ( QMultiHash<QString, Incidence*>::Iterator it = d->mOrphans.begin();
1036 it != d->mOrphans.end(); ++it ) {
1037 if ( it.value()->uid() == uid ) {
1038 relatedToUids << it.key();
1039 }
1040 }
1041
1042 // now go through all uids that have one entry that point to the incidence
1043 for ( QStringList::const_iterator uidit = relatedToUids.constBegin();
1044 uidit != relatedToUids.constEnd(); ++uidit ) {
1045 Incidence::List tempList;
1046 // Remove all to get access to the remaining entries
1047 QList<Incidence*> l = d->mOrphans.values( *uidit );
1048 d->mOrphans.remove( *uidit );
1049 foreach ( Incidence *i, l ) {
1050 if ( i != incidence ) {
1051 tempList.append( i );
1052 }
1053 }
1054 // Readd those that point to a different orphan incidence
1055 for ( Incidence::List::Iterator incit = tempList.begin();
1056 incit != tempList.end(); ++incit ) {
1057 d->mOrphans.insert( *uidit, *incit );
1058 }
1059 }
1060 }
1061
1062 // Make sure the deleted incidence doesn't relate to a non-deleted incidence,
1063 // since that would cause trouble in CalendarLocal::close(), as the deleted
1064 // incidences are destroyed after the non-deleted incidences. The destructor
1065 // of the deleted incidences would then try to access the already destroyed
1066 // non-deleted incidence, which would segfault.
1067 //
1068 // So in short: Make sure dead incidences don't point to alive incidences
1069 // via the relation.
1070 //
1071 // This crash is tested in CalendarLocalTest::testRelationsCrash().
1072 incidence->setRelatedTo( 0 );
1073}
1074
1075bool Calendar::isAncestorOf( Incidence *ancestor, Incidence *incidence )
1076{
1077 if ( !incidence || incidence->relatedToUid().isEmpty() ) {
1078 return false;
1079 } else if ( incidence->relatedToUid() == ancestor->uid() ) {
1080 return true;
1081 } else {
1082 return isAncestorOf( ancestor, this->incidence( incidence->relatedToUid() ) );
1083 }
1084}
1085
1086void Calendar::CalendarObserver::calendarModified( bool modified, Calendar *calendar )
1087{
1088 Q_UNUSED( modified );
1089 Q_UNUSED( calendar );
1090}
1091
1092void Calendar::CalendarObserver::calendarIncidenceAdded( Incidence *incidence )
1093{
1094 Q_UNUSED( incidence );
1095}
1096
1097void Calendar::CalendarObserver::calendarIncidenceChanged( Incidence *incidence )
1098{
1099 Q_UNUSED( incidence );
1100}
1101
1102void Calendar::CalendarObserver::calendarIncidenceDeleted( Incidence *incidence )
1103{
1104 Q_UNUSED( incidence );
1105}
1106
1107void Calendar::registerObserver( CalendarObserver *observer )
1108{
1109 if ( !d->mObservers.contains( observer ) ) {
1110 d->mObservers.append( observer );
1111 }
1112 d->mNewObserver = true;
1113}
1114
1115void Calendar::unregisterObserver( CalendarObserver *observer )
1116{
1117 d->mObservers.removeAll( observer );
1118}
1119
1120bool Calendar::isSaving()
1121{
1122 return false;
1123}
1124
1125void Calendar::setModified( bool modified )
1126{
1127 if ( modified != d->mModified || d->mNewObserver ) {
1128 d->mNewObserver = false;
1129 foreach ( CalendarObserver *observer, d->mObservers ) {
1130 observer->calendarModified( modified, this );
1131 }
1132 d->mModified = modified;
1133 }
1134}
1135
1136bool Calendar::isModified() const
1137{
1138 return d->mModified;
1139}
1140
1141void Calendar::incidenceUpdated( IncidenceBase *incidence )
1142{
1143 incidence->setLastModified( KDateTime::currentUtcDateTime() );
1144 // we should probably update the revision number here,
1145 // or internally in the Event itself when certain things change.
1146 // need to verify with ical documentation.
1147
1148 // The static_cast is ok as the CalendarLocal only observes Incidence objects
1149 notifyIncidenceChanged( static_cast<Incidence *>( incidence ) );
1150
1151 setModified( true );
1152}
1153
1154void Calendar::doSetTimeSpec( const KDateTime::Spec &timeSpec )
1155{
1156 Q_UNUSED( timeSpec );
1157}
1158
1159void Calendar::notifyIncidenceAdded( Incidence *i )
1160{
1161 if ( !d->mObserversEnabled ) {
1162 return;
1163 }
1164
1165 foreach ( CalendarObserver *observer, d->mObservers ) {
1166 observer->calendarIncidenceAdded( i );
1167 }
1168}
1169
1170void Calendar::notifyIncidenceChanged( Incidence *i )
1171{
1172 if ( !d->mObserversEnabled ) {
1173 return;
1174 }
1175
1176 foreach ( CalendarObserver *observer, d->mObservers ) {
1177 observer->calendarIncidenceChanged( i );
1178 }
1179}
1180
1181void Calendar::notifyIncidenceDeleted( Incidence *i )
1182{
1183 if ( !d->mObserversEnabled ) {
1184 return;
1185 }
1186
1187 foreach ( CalendarObserver *observer, d->mObservers ) {
1188 observer->calendarIncidenceDeleted( i );
1189 }
1190}
1191
1192void Calendar::customPropertyUpdated()
1193{
1194 setModified( true );
1195}
1196
1197void Calendar::setProductId( const QString &id )
1198{
1199 d->mProductId = id;
1200}
1201
1202QString Calendar::productId() const
1203{
1204 return d->mProductId;
1205}
1206
1207Incidence::List Calendar::mergeIncidenceList( const Event::List &events,
1208 const Todo::List &todos,
1209 const Journal::List &journals )
1210{
1211 Incidence::List incidences;
1212
1213 int i, end;
1214 for ( i = 0, end = events.count(); i < end; ++i ) {
1215 incidences.append( events[i] );
1216 }
1217
1218 for ( i = 0, end = todos.count(); i < end; ++i ) {
1219 incidences.append( todos[i] );
1220 }
1221
1222 for ( i = 0, end = journals.count(); i < end; ++i ) {
1223 incidences.append( journals[i] );
1224 }
1225
1226 return incidences;
1227}
1228
1229bool Calendar::beginChange( Incidence *incidence )
1230{
1231 Q_UNUSED( incidence );
1232 return true;
1233}
1234
1235bool Calendar::endChange( Incidence *incidence )
1236{
1237 Q_UNUSED( incidence );
1238 return true;
1239}
1240
1241void Calendar::setObserversEnabled( bool enabled )
1242{
1243 d->mObserversEnabled = enabled;
1244}
1245
1246void Calendar::appendAlarms( Alarm::List &alarms, Incidence *incidence,
1247 const KDateTime &from, const KDateTime &to )
1248{
1249 KDateTime preTime = from.addSecs(-1);
1250
1251 Alarm::List alarmlist = incidence->alarms();
1252 for ( int i = 0, iend = alarmlist.count(); i < iend; ++i ) {
1253 if ( alarmlist[i]->enabled() ) {
1254 KDateTime dt = alarmlist[i]->nextRepetition( preTime );
1255 if ( dt.isValid() && dt <= to ) {
1256 kDebug() << incidence->summary() << "':" << dt.toString();
1257 alarms.append( alarmlist[i] );
1258 }
1259 }
1260 }
1261}
1262
1263void Calendar::appendRecurringAlarms( Alarm::List &alarms,
1264 Incidence *incidence,
1265 const KDateTime &from,
1266 const KDateTime &to )
1267{
1268 KDateTime dt;
1269 Duration endOffset( 0 );
1270 bool endOffsetValid = false;
1271 Duration period( from, to );
1272
1273 Event *e = static_cast<Event *>( incidence );
1274 Todo *t = static_cast<Todo *>( incidence );
1275
1276 Alarm::List alarmlist = incidence->alarms();
1277 for ( int i = 0, iend = alarmlist.count(); i < iend; ++i ) {
1278 Alarm *a = alarmlist[i];
1279 if ( a->enabled() ) {
1280 if ( a->hasTime() ) {
1281 // The alarm time is defined as an absolute date/time
1282 dt = a->nextRepetition( from.addSecs( -1 ) );
1283 if ( !dt.isValid() || dt > to ) {
1284 continue;
1285 }
1286 } else {
1287 // Alarm time is defined by an offset from the event start or end time.
1288 // Find the offset from the event start time, which is also used as the
1289 // offset from the recurrence time.
1290 Duration offset( 0 );
1291 if ( a->hasStartOffset() ) {
1292 offset = a->startOffset();
1293 } else if ( a->hasEndOffset() ) {
1294 offset = a->endOffset();
1295 if ( !endOffsetValid ) {
1296 if ( incidence->type() == "Event" ) {
1297 endOffset = Duration( e->dtStart(), e->dtEnd() );
1298 endOffsetValid = true;
1299 } else if ( incidence->type() == "Todo" &&
1300 t->hasStartDate() && t->hasDueDate() ) {
1301 endOffset = Duration( t->dtStart(), t->dtEnd() );
1302 endOffsetValid = true;
1303 }
1304 }
1305 }
1306
1307 // Find the incidence's earliest alarm
1308 KDateTime alarmStart;
1309 if ( incidence->type() == "Event" ) {
1310 alarmStart =
1311 offset.end( a->hasEndOffset() ? e->dtEnd() : e->dtStart() );
1312 } else if ( incidence->type() == "Todo" ) {
1313 alarmStart =
1314 offset.end( a->hasEndOffset() ? t->dtDue() : t->dtStart() );
1315 }
1316
1317 if ( alarmStart.isValid() && alarmStart > to ) {
1318 continue;
1319 }
1320
1321 KDateTime baseStart;
1322 if ( incidence->type() == "Event" ) {
1323 baseStart = e->dtStart();
1324 } else if ( incidence->type() == "Todo" ) {
1325 baseStart = t->dtDue();
1326 }
1327 if ( alarmStart.isValid() && from > alarmStart ) {
1328 alarmStart = from; // don't look earlier than the earliest alarm
1329 baseStart = (-offset).end( (-endOffset).end( alarmStart ) );
1330 }
1331
1332 // Adjust the 'alarmStart' date/time and find the next recurrence
1333 // at or after it. Treat the two offsets separately in case one
1334 // is daily and the other not.
1335 dt = incidence->recurrence()->getNextDateTime( baseStart.addSecs( -1 ) );
1336 if ( !dt.isValid() ||
1337 ( dt = endOffset.end( offset.end( dt ) ) ) > to ) // adjust 'dt' to get the alarm time
1338 {
1339 // The next recurrence is too late.
1340 if ( !a->repeatCount() ) {
1341 continue;
1342 }
1343
1344 // The alarm has repetitions, so check whether repetitions of
1345 // previous recurrences fall within the time period.
1346 bool found = false;
1347 Duration alarmDuration = a->duration();
1348 for ( KDateTime base = baseStart;
1349 ( dt = incidence->recurrence()->getPreviousDateTime( base ) ).isValid();
1350 base = dt ) {
1351 if ( a->duration().end( dt ) < base ) {
1352 break; // this recurrence's last repetition is too early, so give up
1353 }
1354
1355 // The last repetition of this recurrence is at or after
1356 // 'alarmStart' time. Check if a repetition occurs between
1357 // 'alarmStart' and 'to'.
1358 int snooze = a->snoozeTime().value(); // in seconds or days
1359 if ( a->snoozeTime().isDaily() ) {
1360 Duration toFromDuration( dt, base );
1361 int toFrom = toFromDuration.asDays();
1362 if ( a->snoozeTime().end( from ) <= to ||
1363 ( toFromDuration.isDaily() && toFrom % snooze == 0 ) ||
1364 ( toFrom / snooze + 1 ) * snooze <= toFrom + period.asDays() ) {
1365 found = true;
1366#ifndef NDEBUG
1367 // for debug output
1368 dt = offset.end( dt ).addDays( ( ( toFrom - 1 ) / snooze + 1 ) * snooze );
1369#endif
1370 break;
1371 }
1372 } else {
1373 int toFrom = dt.secsTo( base );
1374 if ( period.asSeconds() >= snooze ||
1375 toFrom % snooze == 0 ||
1376 ( toFrom / snooze + 1 ) * snooze <= toFrom + period.asSeconds() )
1377 {
1378 found = true;
1379#ifndef NDEBUG
1380 // for debug output
1381 dt = offset.end( dt ).addSecs( ( ( toFrom - 1 ) / snooze + 1 ) * snooze );
1382#endif
1383 break;
1384 }
1385 }
1386 }
1387 if ( !found ) {
1388 continue;
1389 }
1390 }
1391 }
1392 kDebug() << incidence->summary() << "':" << dt.toString();
1393 alarms.append( a );
1394 }
1395 }
1396}
1397
1398