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