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 | |
45 | extern "C" { |
46 | #include <icaltimezone.h> |
47 | } |
48 | |
49 | using namespace KCal; |
50 | |
51 | /** |
52 | Private class that helps to provide binary compatibility between releases. |
53 | @internal |
54 | */ |
55 | //@cond PRIVATE |
56 | class 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 | |
106 | Calendar::Calendar( const KDateTime::Spec &timeSpec ) |
107 | : d( new KCal::Calendar::Private ) |
108 | { |
109 | d->mTimeSpec = timeSpec; |
110 | d->mViewTimeSpec = timeSpec; |
111 | } |
112 | |
113 | Calendar::Calendar( const QString &timeZoneId ) |
114 | : d( new KCal::Calendar::Private ) |
115 | { |
116 | setTimeZoneId( timeZoneId ); |
117 | } |
118 | |
119 | Calendar::~Calendar() |
120 | { |
121 | delete d; |
122 | } |
123 | |
124 | Person Calendar::owner() const |
125 | { |
126 | return d->mOwner; |
127 | } |
128 | |
129 | void Calendar::setOwner( const Person &owner ) |
130 | { |
131 | d->mOwner = owner; |
132 | |
133 | setModified( true ); |
134 | } |
135 | |
136 | void 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 | |
145 | KDateTime::Spec Calendar::timeSpec() const |
146 | { |
147 | return d->mTimeSpec; |
148 | } |
149 | |
150 | void 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 |
160 | KDateTime::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 | |
189 | QString Calendar::timeZoneId() const |
190 | { |
191 | KTimeZone tz = d->mTimeSpec.timeZone(); |
192 | return tz.isValid() ? tz.name() : QString(); |
193 | } |
194 | |
195 | void Calendar::setViewTimeSpec( const KDateTime::Spec &timeSpec ) const |
196 | { |
197 | d->mViewTimeSpec = timeSpec; |
198 | d->mBuiltInViewTimeZone = ICalTimeZone(); |
199 | } |
200 | |
201 | void Calendar::setViewTimeZoneId( const QString &timeZoneId ) const |
202 | { |
203 | d->mViewTimeSpec = d->timeZoneIdSpec( timeZoneId, true ); |
204 | } |
205 | |
206 | KDateTime::Spec Calendar::viewTimeSpec() const |
207 | { |
208 | return d->mViewTimeSpec; |
209 | } |
210 | |
211 | QString Calendar::viewTimeZoneId() const |
212 | { |
213 | KTimeZone tz = d->mViewTimeSpec.timeZone(); |
214 | return tz.isValid() ? tz.name() : QString(); |
215 | } |
216 | |
217 | ICalTimeZones *Calendar::timeZones() const |
218 | { |
219 | return d->mTimeZones; |
220 | } |
221 | |
222 | void 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 | |
244 | void Calendar::setFilter( CalFilter *filter ) |
245 | { |
246 | if ( filter ) { |
247 | d->mFilter = filter; |
248 | } else { |
249 | d->mFilter = d->mDefaultFilter; |
250 | } |
251 | } |
252 | |
253 | CalFilter *Calendar::filter() |
254 | { |
255 | return d->mFilter; |
256 | } |
257 | |
258 | QStringList 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 | |
277 | Incidence::List Calendar::incidences( const QDate &date ) |
278 | { |
279 | return mergeIncidenceList( events( date ), todos( date ), journals( date ) ); |
280 | } |
281 | |
282 | Incidence::List Calendar::incidences() |
283 | { |
284 | return mergeIncidenceList( events(), todos(), journals() ); |
285 | } |
286 | |
287 | Incidence::List Calendar::rawIncidences() |
288 | { |
289 | return mergeIncidenceList( rawEvents(), rawTodos(), rawJournals() ); |
290 | } |
291 | |
292 | Event::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 | |
395 | Event::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 | |
539 | Event::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 | |
549 | Event::List Calendar::events( const KDateTime &dt ) |
550 | { |
551 | Event::List el = rawEventsForDate( dt ); |
552 | d->mFilter->apply( &el ); |
553 | return el; |
554 | } |
555 | |
556 | Event::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 | |
565 | Event::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 | |
573 | bool Calendar::addIncidence( Incidence *incidence ) |
574 | { |
575 | Incidence::AddVisitor<Calendar> v( this ); |
576 | |
577 | return incidence->accept( v ); |
578 | } |
579 | |
580 | bool 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. |
595 | Incidence *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 | |
669 | Incidence *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 | |
685 | Incidence::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 | |
698 | Incidence *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 | |
712 | Todo::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 | |
856 | Todo::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 | |
864 | Todo::List Calendar::todos( const QDate &date ) |
865 | { |
866 | Todo::List el = rawTodosForDate( date ); |
867 | d->mFilter->apply( &el ); |
868 | return el; |
869 | } |
870 | |
871 | Journal::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 | |
924 | Journal::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 | |
932 | Journal::List Calendar::journals( const QDate &date ) |
933 | { |
934 | Journal::List el = rawJournalsForDate( date ); |
935 | d->mFilter->apply( &el ); |
936 | return el; |
937 | } |
938 | |
939 | void Calendar::beginBatchAdding() |
940 | { |
941 | emit batchAddingBegins(); |
942 | } |
943 | |
944 | void 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. |
951 | void 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 |
997 | void 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 | |
1075 | bool 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 | |
1086 | void Calendar::CalendarObserver::calendarModified( bool modified, Calendar *calendar ) |
1087 | { |
1088 | Q_UNUSED( modified ); |
1089 | Q_UNUSED( calendar ); |
1090 | } |
1091 | |
1092 | void Calendar::CalendarObserver::calendarIncidenceAdded( Incidence *incidence ) |
1093 | { |
1094 | Q_UNUSED( incidence ); |
1095 | } |
1096 | |
1097 | void Calendar::CalendarObserver::calendarIncidenceChanged( Incidence *incidence ) |
1098 | { |
1099 | Q_UNUSED( incidence ); |
1100 | } |
1101 | |
1102 | void Calendar::CalendarObserver::calendarIncidenceDeleted( Incidence *incidence ) |
1103 | { |
1104 | Q_UNUSED( incidence ); |
1105 | } |
1106 | |
1107 | void Calendar::registerObserver( CalendarObserver *observer ) |
1108 | { |
1109 | if ( !d->mObservers.contains( observer ) ) { |
1110 | d->mObservers.append( observer ); |
1111 | } |
1112 | d->mNewObserver = true; |
1113 | } |
1114 | |
1115 | void Calendar::unregisterObserver( CalendarObserver *observer ) |
1116 | { |
1117 | d->mObservers.removeAll( observer ); |
1118 | } |
1119 | |
1120 | bool Calendar::isSaving() |
1121 | { |
1122 | return false; |
1123 | } |
1124 | |
1125 | void 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 | |
1136 | bool Calendar::isModified() const |
1137 | { |
1138 | return d->mModified; |
1139 | } |
1140 | |
1141 | void 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 | |
1154 | void Calendar::doSetTimeSpec( const KDateTime::Spec &timeSpec ) |
1155 | { |
1156 | Q_UNUSED( timeSpec ); |
1157 | } |
1158 | |
1159 | void 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 | |
1170 | void 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 | |
1181 | void 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 | |
1192 | void Calendar::customPropertyUpdated() |
1193 | { |
1194 | setModified( true ); |
1195 | } |
1196 | |
1197 | void Calendar::setProductId( const QString &id ) |
1198 | { |
1199 | d->mProductId = id; |
1200 | } |
1201 | |
1202 | QString Calendar::productId() const |
1203 | { |
1204 | return d->mProductId; |
1205 | } |
1206 | |
1207 | Incidence::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 | |
1229 | bool Calendar::beginChange( Incidence *incidence ) |
1230 | { |
1231 | Q_UNUSED( incidence ); |
1232 | return true; |
1233 | } |
1234 | |
1235 | bool Calendar::endChange( Incidence *incidence ) |
1236 | { |
1237 | Q_UNUSED( incidence ); |
1238 | return true; |
1239 | } |
1240 | |
1241 | void Calendar::setObserversEnabled( bool enabled ) |
1242 | { |
1243 | d->mObserversEnabled = enabled; |
1244 | } |
1245 | |
1246 | void 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 | |
1263 | void 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 | |