1 /*
2   This file is part of the kcalcore library.
3 
4   SPDX-FileCopyrightText: 1998 Preston Brown <pbrown@kde.org>
5   SPDX-FileCopyrightText: 2000-2004 Cornelius Schumacher <schumacher@kde.org>
6   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
7   SPDX-FileCopyrightText: 2006 David Jarvie <djarvie@kde.org>
8   SPDX-FileCopyrightText: 2021 Boris Shmarin <b.shmarin@omp.ru>
9 
10   SPDX-License-Identifier: LGPL-2.0-or-later
11 */
12 /**
13   @file
14   This file is part of the API for handling calendar data and
15   defines the Calendar class.
16 
17   @brief
18   Represents the main calendar class.
19 
20   @author Preston Brown \<pbrown@kde.org\>
21   @author Cornelius Schumacher \<schumacher@kde.org\>
22   @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
23   @author David Jarvie \<djarvie@kde.org\>
24 */
25 #include "calendar.h"
26 #include "calendar_p.h"
27 #include "calfilter.h"
28 #include "icaltimezones_p.h"
29 #include "sorting.h"
30 #include "visitor.h"
31 
32 #include "kcalendarcore_debug.h"
33 
34 
35 extern "C" {
36 #include <libical/icaltimezone.h>
37 }
38 
39 #include <algorithm>
40 #include <set>
41 
42 using namespace KCalendarCore;
43 
44 /**
45   Make a QHash::value that returns a QVector.
46 */
47 template<typename K, typename V>
values(const QMultiHash<K,V> & c)48 QVector<V> values(const QMultiHash<K, V> &c)
49 {
50     QVector<V> v;
51     v.reserve(c.size());
52     for (typename QMultiHash<K, V>::const_iterator it = c.begin(), end = c.end(); it != end; ++it) {
53         v.push_back(it.value());
54     }
55     return v;
56 }
57 
58 template<typename K, typename V>
values(const QMultiHash<K,V> & c,const K & x)59 QVector<V> values(const QMultiHash<K, V> &c, const K &x)
60 {
61     QVector<V> v;
62     typename QMultiHash<K, V>::const_iterator it = c.find(x);
63     while (it != c.end() && it.key() == x) {
64         v.push_back(it.value());
65         ++it;
66     }
67     return v;
68 }
69 
70 /**
71   Template for a class that implements a visitor for adding an Incidence
72   to a resource supporting addEvent(), addTodo() and addJournal() calls.
73 */
74 template<class T>
75 class AddVisitor : public Visitor
76 {
77 public:
AddVisitor(T * r)78     AddVisitor(T *r)
79         : mResource(r)
80     {
81     }
82 
visit(const Event::Ptr & e)83     bool visit(const Event::Ptr &e) override
84     {
85         return mResource->addEvent(e);
86     }
visit(const Todo::Ptr & t)87     bool visit(const Todo::Ptr &t) override
88     {
89         return mResource->addTodo(t);
90     }
visit(const Journal::Ptr & j)91     bool visit(const Journal::Ptr &j) override
92     {
93         return mResource->addJournal(j);
94     }
visit(const FreeBusy::Ptr &)95     bool visit(const FreeBusy::Ptr &) override
96     {
97         return false;
98     }
99 
100 private:
101     T *mResource;
102 };
103 
104 /**
105   Template for a class that implements a visitor for deleting an Incidence
106   from a resource supporting deleteEvent(), deleteTodo() and deleteJournal()
107   calls.
108 */
109 template<class T>
110 class DeleteVisitor : public Visitor
111 {
112 public:
DeleteVisitor(T * r)113     DeleteVisitor(T *r)
114         : mResource(r)
115     {
116     }
117 
visit(const Event::Ptr & e)118     bool visit(const Event::Ptr &e) override
119     {
120         mResource->deleteEvent(e);
121         return true;
122     }
visit(const Todo::Ptr & t)123     bool visit(const Todo::Ptr &t) override
124     {
125         mResource->deleteTodo(t);
126         return true;
127     }
visit(const Journal::Ptr & j)128     bool visit(const Journal::Ptr &j) override
129     {
130         mResource->deleteJournal(j);
131         return true;
132     }
visit(const FreeBusy::Ptr &)133     bool visit(const FreeBusy::Ptr &) override
134     {
135         return false;
136     }
137 
138 private:
139     T *mResource;
140 };
141 //@endcond
142 
Calendar(const QTimeZone & timeZone)143 Calendar::Calendar(const QTimeZone &timeZone)
144     : d(new KCalendarCore::Calendar::Private)
145 {
146     if (timeZone.isValid()) {
147         d->mTimeZone = timeZone;
148     } else {
149         d->mTimeZone = QTimeZone::systemTimeZone();
150     }
151 }
152 
Calendar(const QByteArray & timeZoneId)153 Calendar::Calendar(const QByteArray &timeZoneId)
154     : d(new KCalendarCore::Calendar::Private)
155 {
156     setTimeZoneId(timeZoneId);
157 }
158 
~Calendar()159 Calendar::~Calendar()
160 {
161     delete d;
162 }
163 
owner() const164 Person Calendar::owner() const
165 {
166     return d->mOwner;
167 }
168 
setOwner(const Person & owner)169 void Calendar::setOwner(const Person &owner)
170 {
171     if (owner != d->mOwner) {
172         d->mOwner = owner;
173         setModified(true);
174         Q_EMIT ownerChanged();
175     }
176 }
177 
setTimeZone(const QTimeZone & timeZone)178 void Calendar::setTimeZone(const QTimeZone &timeZone)
179 {
180     if (timeZone.isValid()) {
181         d->mTimeZone = timeZone;
182     } else {
183         d->mTimeZone = QTimeZone::systemTimeZone();
184     }
185 
186     doSetTimeZone(d->mTimeZone);
187 }
188 
timeZone() const189 QTimeZone Calendar::timeZone() const
190 {
191     return d->mTimeZone;
192 }
193 
setTimeZoneId(const QByteArray & timeZoneId)194 void Calendar::setTimeZoneId(const QByteArray &timeZoneId)
195 {
196     d->mTimeZone = d->timeZoneIdSpec(timeZoneId);
197 
198     doSetTimeZone(d->mTimeZone); // NOLINT false clang-analyzer-optin.cplusplus.VirtualCall
199 }
200 
201 //@cond PRIVATE
timeZoneIdSpec(const QByteArray & timeZoneId)202 QTimeZone Calendar::Private::timeZoneIdSpec(const QByteArray &timeZoneId)
203 {
204     if (timeZoneId == QByteArrayLiteral("UTC")) {
205         return QTimeZone::utc();
206     }
207     auto tz = QTimeZone(timeZoneId);
208     if (tz.isValid()) {
209         return tz;
210     }
211     return QTimeZone::systemTimeZone();
212 }
213 //@endcond
214 
timeZoneId() const215 QByteArray Calendar::timeZoneId() const
216 {
217     return d->mTimeZone.id();
218 }
219 
shiftTimes(const QTimeZone & oldZone,const QTimeZone & newZone)220 void Calendar::shiftTimes(const QTimeZone &oldZone, const QTimeZone &newZone)
221 {
222     setTimeZone(newZone);
223 
224     int i;
225     int end;
226     Event::List ev = events();
227     for (i = 0, end = ev.count(); i < end; ++i) {
228         ev[i]->shiftTimes(oldZone, newZone);
229     }
230 
231     Todo::List to = todos();
232     for (i = 0, end = to.count(); i < end; ++i) {
233         to[i]->shiftTimes(oldZone, newZone);
234     }
235 
236     Journal::List jo = journals();
237     for (i = 0, end = jo.count(); i < end; ++i) {
238         jo[i]->shiftTimes(oldZone, newZone);
239     }
240 }
241 
setFilter(CalFilter * filter)242 void Calendar::setFilter(CalFilter *filter)
243 {
244     if (filter) {
245         d->mFilter = filter;
246     } else {
247         d->mFilter = d->mDefaultFilter;
248     }
249     Q_EMIT filterChanged();
250 }
251 
filter() const252 CalFilter *Calendar::filter() const
253 {
254     return d->mFilter;
255 }
256 
categories() const257 QStringList Calendar::categories() const
258 {
259     const Incidence::List rawInc = rawIncidences();
260     QStringList uniqueCategories;
261     QStringList 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 (const Incidence::Ptr &inc : rawInc) {
265         thisCats = inc->categories();
266         for (const auto &cat : std::as_const(thisCats)) {
267             if (!uniqueCategories.contains(cat)) {
268                 uniqueCategories.append(cat);
269             }
270         }
271     }
272     return uniqueCategories;
273 }
274 
incidences(const QDate & date) const275 Incidence::List Calendar::incidences(const QDate &date) const
276 {
277     return mergeIncidenceList(events(date), todos(date), journals(date));
278 }
279 
incidences() const280 Incidence::List Calendar::incidences() const
281 {
282     return mergeIncidenceList(events(), todos(), journals());
283 }
284 
rawIncidences() const285 Incidence::List Calendar::rawIncidences() const
286 {
287     return mergeIncidenceList(rawEvents(), rawTodos(), rawJournals());
288 }
289 
instances(const Incidence::Ptr & incidence) const290 Incidence::List Calendar::instances(const Incidence::Ptr &incidence) const
291 {
292     if (incidence) {
293         Event::List elist;
294         Todo::List tlist;
295         Journal::List jlist;
296 
297         if (incidence->type() == Incidence::TypeEvent) {
298             elist = eventInstances(incidence);
299         } else if (incidence->type() == Incidence::TypeTodo) {
300             tlist = todoInstances(incidence);
301         } else if (incidence->type() == Incidence::TypeJournal) {
302             jlist = journalInstances(incidence);
303         }
304         return mergeIncidenceList(elist, tlist, jlist);
305     } else {
306         return Incidence::List();
307     }
308 }
309 
duplicates(const Incidence::Ptr & incidence)310 Incidence::List Calendar::duplicates(const Incidence::Ptr &incidence)
311 {
312     if (!incidence) {
313         return {};
314     }
315 
316     Incidence::List list;
317     const Incidence::List vals = values(d->mNotebookIncidences);
318     std::copy_if(vals.cbegin(), vals.cend(), std::back_inserter(list), [&](const Incidence::Ptr &in) {
319         return (incidence->dtStart() == in->dtStart() || (!incidence->dtStart().isValid() && !in->dtStart().isValid()))
320             && incidence->summary() == in->summary();
321     });
322     return list;
323 }
324 
addNotebook(const QString & notebook,bool isVisible)325 bool Calendar::addNotebook(const QString &notebook, bool isVisible)
326 {
327     if (d->mNotebooks.contains(notebook)) {
328         return false;
329     } else {
330         d->mNotebooks.insert(notebook, isVisible);
331         return true;
332     }
333 }
334 
updateNotebook(const QString & notebook,bool isVisible)335 bool Calendar::updateNotebook(const QString &notebook, bool isVisible)
336 {
337     if (!d->mNotebooks.contains(notebook)) {
338         return false;
339     } else {
340         d->mNotebooks.insert(notebook, isVisible);
341         for (auto noteIt = d->mNotebookIncidences.cbegin(); noteIt != d->mNotebookIncidences.cend(); ++noteIt) {
342             const Incidence::Ptr &incidence = noteIt.value();
343             auto visibleIt = d->mIncidenceVisibility.find(incidence);
344             if (visibleIt != d->mIncidenceVisibility.end()) {
345                 *visibleIt = isVisible;
346             }
347         }
348         return true;
349     }
350 }
351 
deleteNotebook(const QString & notebook)352 bool Calendar::deleteNotebook(const QString &notebook)
353 {
354     if (!d->mNotebooks.contains(notebook)) {
355         return false;
356     } else {
357         return d->mNotebooks.remove(notebook);
358     }
359 }
360 
setDefaultNotebook(const QString & notebook)361 bool Calendar::setDefaultNotebook(const QString &notebook)
362 {
363     if (!d->mNotebooks.contains(notebook)) {
364         return false;
365     } else {
366         d->mDefaultNotebook = notebook;
367         return true;
368     }
369 }
370 
defaultNotebook() const371 QString Calendar::defaultNotebook() const
372 {
373     return d->mDefaultNotebook;
374 }
375 
hasValidNotebook(const QString & notebook) const376 bool Calendar::hasValidNotebook(const QString &notebook) const
377 {
378     return d->mNotebooks.contains(notebook);
379 }
380 
isVisible(const Incidence::Ptr & incidence) const381 bool Calendar::isVisible(const Incidence::Ptr &incidence) const
382 {
383     if (d->mIncidenceVisibility.contains(incidence)) {
384         return d->mIncidenceVisibility[incidence];
385     }
386     const QString nuid = notebook(incidence);
387     bool rv;
388     if (d->mNotebooks.contains(nuid)) {
389         rv = d->mNotebooks.value(nuid);
390     } else {
391         // NOTE returns true also for nonexisting notebooks for compatibility
392         rv = true;
393     }
394     d->mIncidenceVisibility[incidence] = rv;
395     return rv;
396 }
397 
isVisible(const QString & notebook) const398 bool Calendar::isVisible(const QString &notebook) const
399 {
400     QHash<QString, bool>::ConstIterator it = d->mNotebooks.constFind(notebook);
401     return (it != d->mNotebooks.constEnd()) ? *it : true;
402 }
403 
clearNotebookAssociations()404 void Calendar::clearNotebookAssociations()
405 {
406     d->mNotebookIncidences.clear();
407     d->mUidToNotebook.clear();
408     d->mIncidenceVisibility.clear();
409 }
410 
setNotebook(const Incidence::Ptr & inc,const QString & notebook)411 bool Calendar::setNotebook(const Incidence::Ptr &inc, const QString &notebook)
412 {
413     if (!inc) {
414         return false;
415     }
416 
417     if (!notebook.isEmpty() && !incidence(inc->uid(), inc->recurrenceId())) {
418         qCWarning(KCALCORE_LOG) << "cannot set notebook until incidence has been added";
419         return false;
420     }
421 
422     if (d->mUidToNotebook.contains(inc->uid())) {
423         QString old = d->mUidToNotebook.value(inc->uid());
424         if (!old.isEmpty() && notebook != old) {
425             if (inc->hasRecurrenceId()) {
426                 qCWarning(KCALCORE_LOG) << "cannot set notebook for child incidences";
427                 return false;
428             }
429             // Move all possible children also.
430             const Incidence::List list = instances(inc);
431             for (const auto &incidence : list) {
432                 d->mNotebookIncidences.remove(old, incidence);
433                 d->mNotebookIncidences.insert(notebook, incidence);
434             }
435             notifyIncidenceChanged(inc); // for removing from old notebook
436             // don not remove from mUidToNotebook to keep deleted incidences
437             d->mNotebookIncidences.remove(old, inc);
438         }
439     }
440     if (!notebook.isEmpty()) {
441         d->mUidToNotebook.insert(inc->uid(), notebook);
442         d->mNotebookIncidences.insert(notebook, inc);
443         qCDebug(KCALCORE_LOG) << "setting notebook" << notebook << "for" << inc->uid();
444         notifyIncidenceChanged(inc); // for inserting into new notebook
445     }
446 
447     return true;
448 }
449 
notebook(const Incidence::Ptr & incidence) const450 QString Calendar::notebook(const Incidence::Ptr &incidence) const
451 {
452     if (incidence) {
453         return d->mUidToNotebook.value(incidence->uid());
454     } else {
455         return QString();
456     }
457 }
458 
notebook(const QString & uid) const459 QString Calendar::notebook(const QString &uid) const
460 {
461     return d->mUidToNotebook.value(uid);
462 }
463 
notebooks() const464 QStringList Calendar::notebooks() const
465 {
466     return d->mNotebookIncidences.uniqueKeys();
467 }
468 
incidences(const QString & notebook) const469 Incidence::List Calendar::incidences(const QString &notebook) const
470 {
471     if (notebook.isEmpty()) {
472         return values(d->mNotebookIncidences);
473     } else {
474         return values(d->mNotebookIncidences, notebook);
475     }
476 }
477 
478 /** static */
sortEvents(const Event::List & eventList,EventSortField sortField,SortDirection sortDirection)479 Event::List Calendar::sortEvents(const Event::List &eventList, EventSortField sortField, SortDirection sortDirection)
480 {
481     if (eventList.isEmpty()) {
482         return Event::List();
483     }
484 
485     Event::List eventListSorted;
486 
487     // Notice we alphabetically presort Summaries first.
488     // We do this so comparison "ties" stay in a nice order.
489     eventListSorted = eventList;
490     switch (sortField) {
491     case EventSortUnsorted:
492         break;
493 
494     case EventSortStartDate:
495         if (sortDirection == SortDirectionAscending) {
496             std::sort(eventListSorted.begin(), eventListSorted.end(), Events::startDateLessThan);
497         } else {
498             std::sort(eventListSorted.begin(), eventListSorted.end(), Events::startDateMoreThan);
499         }
500         break;
501 
502     case EventSortEndDate:
503         if (sortDirection == SortDirectionAscending) {
504             std::sort(eventListSorted.begin(), eventListSorted.end(), Events::endDateLessThan);
505         } else {
506             std::sort(eventListSorted.begin(), eventListSorted.end(), Events::endDateMoreThan);
507         }
508         break;
509 
510     case EventSortSummary:
511         if (sortDirection == SortDirectionAscending) {
512             std::sort(eventListSorted.begin(), eventListSorted.end(), Events::summaryLessThan);
513         } else {
514             std::sort(eventListSorted.begin(), eventListSorted.end(), Events::summaryMoreThan);
515         }
516         break;
517     }
518 
519     return eventListSorted;
520 }
521 
events(const QDate & date,const QTimeZone & timeZone,EventSortField sortField,SortDirection sortDirection) const522 Event::List Calendar::events(const QDate &date, const QTimeZone &timeZone, EventSortField sortField, SortDirection sortDirection) const
523 {
524     Event::List el = rawEventsForDate(date, timeZone, sortField, sortDirection);
525     d->mFilter->apply(&el);
526     return el;
527 }
528 
events(const QDateTime & dt) const529 Event::List Calendar::events(const QDateTime &dt) const
530 {
531     Event::List el = rawEventsForDate(dt);
532     d->mFilter->apply(&el);
533     return el;
534 }
535 
events(const QDate & start,const QDate & end,const QTimeZone & timeZone,bool inclusive) const536 Event::List Calendar::events(const QDate &start, const QDate &end, const QTimeZone &timeZone, bool inclusive) const
537 {
538     Event::List el = rawEvents(start, end, timeZone, inclusive);
539     d->mFilter->apply(&el);
540     return el;
541 }
542 
events(EventSortField sortField,SortDirection sortDirection) const543 Event::List Calendar::events(EventSortField sortField, SortDirection sortDirection) const
544 {
545     Event::List el = rawEvents(sortField, sortDirection);
546     d->mFilter->apply(&el);
547     return el;
548 }
549 
addIncidence(const Incidence::Ptr & incidence)550 bool Calendar::addIncidence(const Incidence::Ptr &incidence)
551 {
552     if (!incidence) {
553         return false;
554     }
555 
556     AddVisitor<Calendar> v(this);
557     return incidence->accept(v, incidence);
558 }
559 
deleteIncidence(const Incidence::Ptr & incidence)560 bool Calendar::deleteIncidence(const Incidence::Ptr &incidence)
561 {
562     if (!incidence) {
563         return false;
564     }
565 
566     if (beginChange(incidence)) {
567         DeleteVisitor<Calendar> v(this);
568         const bool result = incidence->accept(v, incidence);
569         endChange(incidence);
570         return result;
571     } else {
572         return false;
573     }
574 }
575 
createException(const Incidence::Ptr & incidence,const QDateTime & recurrenceId,bool thisAndFuture)576 Incidence::Ptr Calendar::createException(const Incidence::Ptr &incidence, const QDateTime &recurrenceId, bool thisAndFuture)
577 {
578     Q_ASSERT(recurrenceId.isValid());
579     if (!incidence || !incidence->recurs() || !recurrenceId.isValid()) {
580         return Incidence::Ptr();
581     }
582 
583     Incidence::Ptr newInc(incidence->clone());
584     const QDateTime current = QDateTime::currentDateTimeUtc();
585     newInc->setCreated(current);
586     newInc->setLastModified(current);
587     newInc->setRevision(0);
588     // Recurring exceptions are not support for now
589     newInc->clearRecurrence();
590 
591     newInc->setRecurrenceId(recurrenceId);
592     newInc->setThisAndFuture(thisAndFuture);
593     newInc->setDtStart(recurrenceId);
594 
595     // Calculate and set the new end of the incidence
596     QDateTime end = incidence->dateTime(IncidenceBase::RoleEnd);
597 
598     if (end.isValid()) {
599         if (incidence->allDay()) {
600             qint64 offset = incidence->dtStart().daysTo(recurrenceId);
601             end = end.addDays(offset);
602         } else {
603             qint64 offset = incidence->dtStart().secsTo(recurrenceId);
604             end = end.addSecs(offset);
605         }
606         newInc->setDateTime(end, IncidenceBase::RoleEnd);
607     }
608     return newInc;
609 }
610 
incidence(const QString & uid,const QDateTime & recurrenceId) const611 Incidence::Ptr Calendar::incidence(const QString &uid, const QDateTime &recurrenceId) const
612 {
613     Incidence::Ptr i = event(uid, recurrenceId);
614     if (i) {
615         return i;
616     }
617 
618     i = todo(uid, recurrenceId);
619     if (i) {
620         return i;
621     }
622 
623     i = journal(uid, recurrenceId);
624     return i;
625 }
626 
deleted(const QString & uid,const QDateTime & recurrenceId) const627 Incidence::Ptr Calendar::deleted(const QString &uid, const QDateTime &recurrenceId) const
628 {
629     Incidence::Ptr i = deletedEvent(uid, recurrenceId);
630     if (i) {
631         return i;
632     }
633 
634     i = deletedTodo(uid, recurrenceId);
635     if (i) {
636         return i;
637     }
638 
639     i = deletedJournal(uid, recurrenceId);
640     return i;
641 }
642 
incidencesFromSchedulingID(const QString & sid) const643 Incidence::List Calendar::incidencesFromSchedulingID(const QString &sid) const
644 {
645     Incidence::List result;
646     const Incidence::List incidences = rawIncidences();
647     std::copy_if(incidences.cbegin(), incidences.cend(), std::back_inserter(result), [&sid](const Incidence::Ptr &in) {
648         return in->schedulingID() == sid;
649     });
650     return result;
651 }
652 
incidenceFromSchedulingID(const QString & uid) const653 Incidence::Ptr Calendar::incidenceFromSchedulingID(const QString &uid) const
654 {
655     const Incidence::List incidences = rawIncidences();
656     const auto itEnd = incidences.cend();
657     auto it = std::find_if(incidences.cbegin(), itEnd, [&uid](const Incidence::Ptr &in) {
658         return in->schedulingID() == uid;
659     });
660 
661     return it != itEnd ? *it : Incidence::Ptr();
662 }
663 
664 /** static */
sortTodos(const Todo::List & todoList,TodoSortField sortField,SortDirection sortDirection)665 Todo::List Calendar::sortTodos(const Todo::List &todoList, TodoSortField sortField, SortDirection sortDirection)
666 {
667     if (todoList.isEmpty()) {
668         return Todo::List();
669     }
670 
671     Todo::List todoListSorted;
672 
673     // Notice we alphabetically presort Summaries first.
674     // We do this so comparison "ties" stay in a nice order.
675 
676     // Note that To-dos may not have Start DateTimes nor due DateTimes.
677 
678     todoListSorted = todoList;
679     switch (sortField) {
680     case TodoSortUnsorted:
681         break;
682 
683     case TodoSortStartDate:
684         if (sortDirection == SortDirectionAscending) {
685             std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::startDateLessThan);
686         } else {
687             std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::startDateMoreThan);
688         }
689         break;
690 
691     case TodoSortDueDate:
692         if (sortDirection == SortDirectionAscending) {
693             std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::dueDateLessThan);
694         } else {
695             std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::dueDateMoreThan);
696         }
697         break;
698 
699     case TodoSortPriority:
700         if (sortDirection == SortDirectionAscending) {
701             std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::priorityLessThan);
702         } else {
703             std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::priorityMoreThan);
704         }
705         break;
706 
707     case TodoSortPercentComplete:
708         if (sortDirection == SortDirectionAscending) {
709             std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::percentLessThan);
710         } else {
711             std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::percentMoreThan);
712         }
713         break;
714 
715     case TodoSortSummary:
716         if (sortDirection == SortDirectionAscending) {
717             std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::summaryLessThan);
718         } else {
719             std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::summaryMoreThan);
720         }
721         break;
722 
723     case TodoSortCreated:
724         if (sortDirection == SortDirectionAscending) {
725             std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::createdLessThan);
726         } else {
727             std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::createdMoreThan);
728         }
729         break;
730 
731     case TodoSortCategories:
732         if (sortDirection == SortDirectionAscending) {
733             std::sort(todoListSorted.begin(), todoListSorted.end(), Incidences::categoriesLessThan);
734         } else {
735             std::sort(todoListSorted.begin(), todoListSorted.end(), Incidences::categoriesMoreThan);
736         }
737         break;
738     }
739 
740     return todoListSorted;
741 }
742 
todos(TodoSortField sortField,SortDirection sortDirection) const743 Todo::List Calendar::todos(TodoSortField sortField, SortDirection sortDirection) const
744 {
745     Todo::List tl = rawTodos(sortField, sortDirection);
746     d->mFilter->apply(&tl);
747     return tl;
748 }
749 
todos(const QDate & date) const750 Todo::List Calendar::todos(const QDate &date) const
751 {
752     Todo::List el = rawTodosForDate(date);
753     d->mFilter->apply(&el);
754     return el;
755 }
756 
todos(const QDate & start,const QDate & end,const QTimeZone & timeZone,bool inclusive) const757 Todo::List Calendar::todos(const QDate &start, const QDate &end, const QTimeZone &timeZone, bool inclusive) const
758 {
759     Todo::List tl = rawTodos(start, end, timeZone, inclusive);
760     d->mFilter->apply(&tl);
761     return tl;
762 }
763 
764 /** static */
sortJournals(const Journal::List & journalList,JournalSortField sortField,SortDirection sortDirection)765 Journal::List Calendar::sortJournals(const Journal::List &journalList, JournalSortField sortField, SortDirection sortDirection)
766 {
767     if (journalList.isEmpty()) {
768         return Journal::List();
769     }
770 
771     Journal::List journalListSorted = journalList;
772 
773     switch (sortField) {
774     case JournalSortUnsorted:
775         break;
776 
777     case JournalSortDate:
778         if (sortDirection == SortDirectionAscending) {
779             std::sort(journalListSorted.begin(), journalListSorted.end(), Journals::dateLessThan);
780         } else {
781             std::sort(journalListSorted.begin(), journalListSorted.end(), Journals::dateMoreThan);
782         }
783         break;
784 
785     case JournalSortSummary:
786         if (sortDirection == SortDirectionAscending) {
787             std::sort(journalListSorted.begin(), journalListSorted.end(), Journals::summaryLessThan);
788         } else {
789             std::sort(journalListSorted.begin(), journalListSorted.end(), Journals::summaryMoreThan);
790         }
791         break;
792     }
793 
794     return journalListSorted;
795 }
796 
journals(JournalSortField sortField,SortDirection sortDirection) const797 Journal::List Calendar::journals(JournalSortField sortField, SortDirection sortDirection) const
798 {
799     Journal::List jl = rawJournals(sortField, sortDirection);
800     d->mFilter->apply(&jl);
801     return jl;
802 }
803 
journals(const QDate & date) const804 Journal::List Calendar::journals(const QDate &date) const
805 {
806     Journal::List el = rawJournalsForDate(date);
807     d->mFilter->apply(&el);
808     return el;
809 }
810 
811 // When this is called, the to-dos have already been added to the calendar.
812 // This method is only about linking related to-dos.
setupRelations(const Incidence::Ptr & forincidence)813 void Calendar::setupRelations(const Incidence::Ptr &forincidence)
814 {
815     if (!forincidence) {
816         return;
817     }
818 
819     const QString uid = forincidence->uid();
820 
821     // First, go over the list of orphans and see if this is their parent
822     Incidence::List l = values(d->mOrphans, uid);
823     d->mOrphans.remove(uid);
824     if (!l.isEmpty()) {
825         Incidence::List &relations = d->mIncidenceRelations[uid];
826         relations.reserve(relations.count() + l.count());
827         for (int i = 0, end = l.count(); i < end; ++i) {
828             relations.append(l[i]);
829             d->mOrphanUids.remove(l[i]->uid());
830         }
831     }
832 
833     // Now see about this incidences parent
834     if (!forincidence->relatedTo().isEmpty()) {
835         // Incidence has a uid it is related to but is not registered to it yet.
836         // Try to find it
837         Incidence::Ptr parent = incidence(forincidence->relatedTo());
838         if (parent) {
839             // Found it
840 
841             // look for hierarchy loops
842             if (isAncestorOf(forincidence, parent)) {
843                 forincidence->setRelatedTo(QString());
844                 qCWarning(KCALCORE_LOG) << "hierarchy loop between " << forincidence->uid() << " and " << parent->uid();
845             } else {
846                 d->mIncidenceRelations[parent->uid()].append(forincidence);
847             }
848         } else {
849             // Not found, put this in the mOrphans list
850             // Note that the mOrphans dict might contain multiple entries with the
851             // same key! which are multiple children that wait for the parent
852             // incidence to be inserted.
853             d->mOrphans.insert(forincidence->relatedTo(), forincidence);
854             d->mOrphanUids.insert(forincidence->uid(), forincidence);
855         }
856     }
857 }
858 
859 // If a to-do with sub-to-dos is deleted, move it's sub-to-dos to the orphan list
removeRelations(const Incidence::Ptr & incidence)860 void Calendar::removeRelations(const Incidence::Ptr &incidence)
861 {
862     if (!incidence) {
863         qCDebug(KCALCORE_LOG) << "Warning: incidence is 0";
864         return;
865     }
866 
867     const QString uid = incidence->uid();
868 
869     for (const Incidence::Ptr &i : qAsConst(d->mIncidenceRelations[uid])) {
870         if (!d->mOrphanUids.contains(i->uid())) {
871             d->mOrphans.insert(uid, i);
872             d->mOrphanUids.insert(i->uid(), i);
873             i->setRelatedTo(uid);
874         }
875     }
876 
877     const QString parentUid = incidence->relatedTo();
878 
879     // If this incidence is related to something else, tell that about it
880     if (!parentUid.isEmpty()) {
881         Incidence::List &relations = d->mIncidenceRelations[parentUid];
882         relations.erase(std::remove(relations.begin(), relations.end(), incidence), relations.end());
883     }
884 
885     // Remove this one from the orphans list
886     if (d->mOrphanUids.remove(uid)) {
887         // This incidence is located in the orphans list - it should be removed
888         // Since the mOrphans dict might contain the same key (with different
889         // child incidence pointers!) multiple times, take care that we remove
890         // the correct one. So we need to remove all items with the given
891         // parent UID, and re-add those that are not for this item. Also, there
892         // might be other entries with different UID that point to this
893         // incidence (this might happen when the relatedTo of the item is
894         // changed before its parent is inserted. This might happen with
895         // groupware servers....). Remove them, too
896         QStringList relatedToUids;
897 
898         // First, create a list of all keys in the mOrphans list which point
899         // to the removed item
900         relatedToUids << incidence->relatedTo();
901         for (auto it = d->mOrphans.cbegin(); it != d->mOrphans.cend(); ++it) {
902             if (it.value()->uid() == uid) {
903                 relatedToUids << it.key();
904             }
905         }
906 
907         // now go through all uids that have one entry that point to the incidence
908         for (const auto &relUid : std::as_const(relatedToUids)) {
909             // Remove all to get access to the remaining entries
910             Incidence::List lst = values(d->mOrphans, relUid);
911             d->mOrphans.remove(relUid);
912             lst.erase(std::remove(lst.begin(), lst.end(), incidence), lst.end());
913 
914             // Re-add those that point to a different orphan incidence
915             for (const auto &in : std::as_const(lst)) {
916                 d->mOrphans.insert(relUid, in);
917             }
918         }
919     }
920 
921     // Make sure the deleted incidence doesn't relate to a non-deleted incidence,
922     // since that would cause trouble in MemoryCalendar::close(), as the deleted
923     // incidences are destroyed after the non-deleted incidences. The destructor
924     // of the deleted incidences would then try to access the already destroyed
925     // non-deleted incidence, which would segfault.
926     //
927     // So in short: Make sure dead incidences don't point to alive incidences
928     // via the relation.
929     //
930     // This crash is tested in MemoryCalendarTest::testRelationsCrash().
931     //  incidence->setRelatedTo( Incidence::Ptr() );
932 }
933 
isAncestorOf(const Incidence::Ptr & ancestor,const Incidence::Ptr & incidence) const934 bool Calendar::isAncestorOf(const Incidence::Ptr &ancestor, const Incidence::Ptr &incidence) const
935 {
936     if (!incidence || incidence->relatedTo().isEmpty()) {
937         return false;
938     } else if (incidence->relatedTo() == ancestor->uid()) {
939         return true;
940     } else {
941         return isAncestorOf(ancestor, this->incidence(incidence->relatedTo()));
942     }
943 }
944 
relations(const QString & uid) const945 Incidence::List Calendar::relations(const QString &uid) const
946 {
947     return d->mIncidenceRelations[uid];
948 }
949 
~CalendarObserver()950 Calendar::CalendarObserver::~CalendarObserver()
951 {
952 }
953 
calendarModified(bool modified,Calendar * calendar)954 void Calendar::CalendarObserver::calendarModified(bool modified, Calendar *calendar)
955 {
956     Q_UNUSED(modified);
957     Q_UNUSED(calendar);
958 }
959 
calendarIncidenceAdded(const Incidence::Ptr & incidence)960 void Calendar::CalendarObserver::calendarIncidenceAdded(const Incidence::Ptr &incidence)
961 {
962     Q_UNUSED(incidence);
963 }
964 
calendarIncidenceChanged(const Incidence::Ptr & incidence)965 void Calendar::CalendarObserver::calendarIncidenceChanged(const Incidence::Ptr &incidence)
966 {
967     Q_UNUSED(incidence);
968 }
969 
calendarIncidenceAboutToBeDeleted(const Incidence::Ptr & incidence)970 void Calendar::CalendarObserver::calendarIncidenceAboutToBeDeleted(const Incidence::Ptr &incidence)
971 {
972     Q_UNUSED(incidence);
973 }
974 
calendarIncidenceDeleted(const Incidence::Ptr & incidence,const Calendar * calendar)975 void Calendar::CalendarObserver::calendarIncidenceDeleted(const Incidence::Ptr &incidence, const Calendar *calendar)
976 {
977     Q_UNUSED(incidence);
978     Q_UNUSED(calendar);
979 }
980 
calendarIncidenceAdditionCanceled(const Incidence::Ptr & incidence)981 void Calendar::CalendarObserver::calendarIncidenceAdditionCanceled(const Incidence::Ptr &incidence)
982 {
983     Q_UNUSED(incidence);
984 }
985 
registerObserver(CalendarObserver * observer)986 void Calendar::registerObserver(CalendarObserver *observer)
987 {
988     if (!observer) {
989         return;
990     }
991 
992     if (!d->mObservers.contains(observer)) {
993         d->mObservers.append(observer);
994     } else {
995         d->mNewObserver = true;
996     }
997 }
998 
unregisterObserver(CalendarObserver * observer)999 void Calendar::unregisterObserver(CalendarObserver *observer)
1000 {
1001     if (!observer) {
1002         return;
1003     } else {
1004         d->mObservers.removeAll(observer);
1005     }
1006 }
1007 
isSaving() const1008 bool Calendar::isSaving() const
1009 {
1010     return false;
1011 }
1012 
setModified(bool modified)1013 void Calendar::setModified(bool modified)
1014 {
1015     if (modified != d->mModified || d->mNewObserver) {
1016         d->mNewObserver = false;
1017         for (CalendarObserver *observer : qAsConst(d->mObservers)) {
1018             observer->calendarModified(modified, this);
1019         }
1020         d->mModified = modified;
1021     }
1022 }
1023 
isModified() const1024 bool Calendar::isModified() const
1025 {
1026     return d->mModified;
1027 }
1028 
save()1029 bool Calendar::save()
1030 {
1031     return true;
1032 }
1033 
reload()1034 bool Calendar::reload()
1035 {
1036     return true;
1037 }
1038 
incidenceUpdated(const QString & uid,const QDateTime & recurrenceId)1039 void Calendar::incidenceUpdated(const QString &uid, const QDateTime &recurrenceId)
1040 {
1041     Incidence::Ptr inc = incidence(uid, recurrenceId);
1042 
1043     if (!inc) {
1044         return;
1045     }
1046 
1047     inc->setLastModified(QDateTime::currentDateTimeUtc());
1048     // we should probably update the revision number here,
1049     // or internally in the Event itself when certain things change.
1050     // need to verify with ical documentation.
1051 
1052     notifyIncidenceChanged(inc);
1053 
1054     setModified(true);
1055 }
1056 
doSetTimeZone(const QTimeZone & timeZone)1057 void Calendar::doSetTimeZone(const QTimeZone &timeZone)
1058 {
1059     Q_UNUSED(timeZone);
1060 }
1061 
notifyIncidenceAdded(const Incidence::Ptr & incidence)1062 void Calendar::notifyIncidenceAdded(const Incidence::Ptr &incidence)
1063 {
1064     if (!incidence) {
1065         return;
1066     }
1067 
1068     if (!d->mObserversEnabled) {
1069         return;
1070     }
1071 
1072     for (CalendarObserver *observer : qAsConst(d->mObservers)) {
1073         observer->calendarIncidenceAdded(incidence);
1074     }
1075 
1076     for (auto role : {IncidenceBase::RoleStartTimeZone, IncidenceBase::RoleEndTimeZone}) {
1077         const auto dt = incidence->dateTime(role);
1078         if (dt.isValid() && dt.timeZone() != QTimeZone::utc()) {
1079             if (!d->mTimeZones.contains(dt.timeZone())) {
1080                 d->mTimeZones.push_back(dt.timeZone());
1081             }
1082         }
1083     }
1084 }
1085 
notifyIncidenceChanged(const Incidence::Ptr & incidence)1086 void Calendar::notifyIncidenceChanged(const Incidence::Ptr &incidence)
1087 {
1088     if (!incidence) {
1089         return;
1090     }
1091 
1092     if (!d->mObserversEnabled) {
1093         return;
1094     }
1095 
1096     for (CalendarObserver *observer : qAsConst(d->mObservers)) {
1097         observer->calendarIncidenceChanged(incidence);
1098     }
1099 }
1100 
notifyIncidenceAboutToBeDeleted(const Incidence::Ptr & incidence)1101 void Calendar::notifyIncidenceAboutToBeDeleted(const Incidence::Ptr &incidence)
1102 {
1103     if (!incidence) {
1104         return;
1105     }
1106 
1107     if (!d->mObserversEnabled) {
1108         return;
1109     }
1110 
1111     for (CalendarObserver *observer : qAsConst(d->mObservers)) {
1112         observer->calendarIncidenceAboutToBeDeleted(incidence);
1113     }
1114 }
1115 
notifyIncidenceDeleted(const Incidence::Ptr & incidence)1116 void Calendar::notifyIncidenceDeleted(const Incidence::Ptr &incidence)
1117 {
1118     if (!incidence) {
1119         return;
1120     }
1121 
1122     if (!d->mObserversEnabled) {
1123         return;
1124     }
1125 
1126     for (CalendarObserver *observer : qAsConst(d->mObservers)) {
1127         observer->calendarIncidenceDeleted(incidence, this);
1128     }
1129 }
1130 
notifyIncidenceAdditionCanceled(const Incidence::Ptr & incidence)1131 void Calendar::notifyIncidenceAdditionCanceled(const Incidence::Ptr &incidence)
1132 {
1133     if (!incidence) {
1134         return;
1135     }
1136 
1137     if (!d->mObserversEnabled) {
1138         return;
1139     }
1140 
1141     for (CalendarObserver *observer : qAsConst(d->mObservers)) {
1142         observer->calendarIncidenceAdditionCanceled(incidence);
1143     }
1144 }
1145 
customPropertyUpdated()1146 void Calendar::customPropertyUpdated()
1147 {
1148     setModified(true);
1149 }
1150 
setProductId(const QString & id)1151 void Calendar::setProductId(const QString &id)
1152 {
1153     d->mProductId = id;
1154 }
1155 
productId() const1156 QString Calendar::productId() const
1157 {
1158     return d->mProductId;
1159 }
1160 
1161 /** static */
mergeIncidenceList(const Event::List & events,const Todo::List & todos,const Journal::List & journals)1162 Incidence::List Calendar::mergeIncidenceList(const Event::List &events, const Todo::List &todos, const Journal::List &journals)
1163 {
1164     Incidence::List incidences;
1165     incidences.reserve(events.count() + todos.count() + journals.count());
1166 
1167     int i;
1168     int end;
1169     for (i = 0, end = events.count(); i < end; ++i) {
1170         incidences.append(events[i]);
1171     }
1172 
1173     for (i = 0, end = todos.count(); i < end; ++i) {
1174         incidences.append(todos[i]);
1175     }
1176 
1177     for (i = 0, end = journals.count(); i < end; ++i) {
1178         incidences.append(journals[i]);
1179     }
1180 
1181     return incidences;
1182 }
1183 
beginChange(const Incidence::Ptr & incidence)1184 bool Calendar::beginChange(const Incidence::Ptr &incidence)
1185 {
1186     Q_UNUSED(incidence);
1187     return true;
1188 }
1189 
endChange(const Incidence::Ptr & incidence)1190 bool Calendar::endChange(const Incidence::Ptr &incidence)
1191 {
1192     Q_UNUSED(incidence);
1193     return true;
1194 }
1195 
setObserversEnabled(bool enabled)1196 void Calendar::setObserversEnabled(bool enabled)
1197 {
1198     d->mObserversEnabled = enabled;
1199 }
1200 
appendAlarms(Alarm::List & alarms,const Incidence::Ptr & incidence,const QDateTime & from,const QDateTime & to) const1201 void Calendar::appendAlarms(Alarm::List &alarms, const Incidence::Ptr &incidence, const QDateTime &from, const QDateTime &to) const
1202 {
1203     QDateTime preTime = from.addSecs(-1);
1204 
1205     Alarm::List alarmlist = incidence->alarms();
1206     for (int i = 0, iend = alarmlist.count(); i < iend; ++i) {
1207         if (alarmlist[i]->enabled()) {
1208             QDateTime dt = alarmlist[i]->nextRepetition(preTime);
1209             if (dt.isValid() && dt <= to) {
1210                 qCDebug(KCALCORE_LOG) << incidence->summary() << "':" << dt.toString();
1211                 alarms.append(alarmlist[i]);
1212             }
1213         }
1214     }
1215 }
1216 
appendRecurringAlarms(Alarm::List & alarms,const Incidence::Ptr & incidence,const QDateTime & from,const QDateTime & to) const1217 void Calendar::appendRecurringAlarms(Alarm::List &alarms, const Incidence::Ptr &incidence, const QDateTime &from, const QDateTime &to) const
1218 {
1219     QDateTime dt;
1220     bool endOffsetValid = false;
1221     Duration endOffset(0);
1222     Duration period(from, to);
1223 
1224     Alarm::List alarmlist = incidence->alarms();
1225     for (int i = 0, iend = alarmlist.count(); i < iend; ++i) {
1226         Alarm::Ptr a = alarmlist[i];
1227         if (a->enabled()) {
1228             if (a->hasTime()) {
1229                 // The alarm time is defined as an absolute date/time
1230                 dt = a->nextRepetition(from.addSecs(-1));
1231                 if (!dt.isValid() || dt > to) {
1232                     continue;
1233                 }
1234             } else {
1235                 // Alarm time is defined by an offset from the event start or end time.
1236                 // Find the offset from the event start time, which is also used as the
1237                 // offset from the recurrence time.
1238                 Duration offset(0);
1239                 if (a->hasStartOffset()) {
1240                     offset = a->startOffset();
1241                 } else if (a->hasEndOffset()) {
1242                     offset = a->endOffset();
1243                     if (!endOffsetValid) {
1244                         endOffset = Duration(incidence->dtStart(), incidence->dateTime(Incidence::RoleAlarmEndOffset));
1245                         endOffsetValid = true;
1246                     }
1247                 }
1248 
1249                 // Find the incidence's earliest alarm
1250                 QDateTime alarmStart = offset.end(a->hasEndOffset() ? incidence->dateTime(Incidence::RoleAlarmEndOffset) : incidence->dtStart());
1251                 if (alarmStart > to) {
1252                     continue;
1253                 }
1254                 QDateTime baseStart = incidence->dtStart();
1255                 if (from > alarmStart) {
1256                     alarmStart = from; // don't look earlier than the earliest alarm
1257                     baseStart = (-offset).end((-endOffset).end(alarmStart));
1258                 }
1259 
1260                 // Adjust the 'alarmStart' date/time and find the next recurrence at or after it.
1261                 // Treat the two offsets separately in case one is daily and the other not.
1262                 dt = incidence->recurrence()->getNextDateTime(baseStart.addSecs(-1));
1263                 if (!dt.isValid() || (dt = endOffset.end(offset.end(dt))) > to) { // adjust 'dt' to get the alarm time
1264                     // The next recurrence is too late.
1265                     if (!a->repeatCount()) {
1266                         continue;
1267                     }
1268 
1269                     // The alarm has repetitions, so check whether repetitions of previous
1270                     // recurrences fall within the time period.
1271                     bool found = false;
1272                     Duration alarmDuration = a->duration();
1273                     for (QDateTime base = baseStart; (dt = incidence->recurrence()->getPreviousDateTime(base)).isValid(); base = dt) {
1274                         if (a->duration().end(dt) < base) {
1275                             break; // this recurrence's last repetition is too early, so give up
1276                         }
1277 
1278                         // The last repetition of this recurrence is at or after 'alarmStart' time.
1279                         // Check if a repetition occurs between 'alarmStart' and 'to'.
1280                         int snooze = a->snoozeTime().value(); // in seconds or days
1281                         if (a->snoozeTime().isDaily()) {
1282                             Duration toFromDuration(dt, base);
1283                             int toFrom = toFromDuration.asDays();
1284                             if (a->snoozeTime().end(from) <= to || (toFromDuration.isDaily() && toFrom % snooze == 0)
1285                                 || (toFrom / snooze + 1) * snooze <= toFrom + period.asDays()) {
1286                                 found = true;
1287 #ifndef NDEBUG
1288                                 // for debug output
1289                                 dt = offset.end(dt).addDays(((toFrom - 1) / snooze + 1) * snooze);
1290 #endif
1291                                 break;
1292                             }
1293                         } else {
1294                             int toFrom = dt.secsTo(base);
1295                             if (period.asSeconds() >= snooze || toFrom % snooze == 0 || (toFrom / snooze + 1) * snooze <= toFrom + period.asSeconds()) {
1296                                 found = true;
1297 #ifndef NDEBUG
1298                                 // for debug output
1299                                 dt = offset.end(dt).addSecs(((toFrom - 1) / snooze + 1) * snooze);
1300 #endif
1301                                 break;
1302                             }
1303                         }
1304                     }
1305                     if (!found) {
1306                         continue;
1307                     }
1308                 }
1309             }
1310             qCDebug(KCALCORE_LOG) << incidence->summary() << "':" << dt.toString();
1311             alarms.append(a);
1312         }
1313     }
1314 }
1315 
startBatchAdding()1316 void Calendar::startBatchAdding()
1317 {
1318     d->batchAddingInProgress = true;
1319 }
1320 
endBatchAdding()1321 void Calendar::endBatchAdding()
1322 {
1323     d->batchAddingInProgress = false;
1324 }
1325 
batchAdding() const1326 bool Calendar::batchAdding() const
1327 {
1328     return d->batchAddingInProgress;
1329 }
1330 
setDeletionTracking(bool enable)1331 void Calendar::setDeletionTracking(bool enable)
1332 {
1333     d->mDeletionTracking = enable;
1334 }
1335 
deletionTracking() const1336 bool Calendar::deletionTracking() const
1337 {
1338     return d->mDeletionTracking;
1339 }
1340 
alarmsTo(const QDateTime & to) const1341 Alarm::List Calendar::alarmsTo(const QDateTime &to) const
1342 {
1343     return alarms(QDateTime(QDate(1900, 1, 1), QTime(0, 0, 0)), to);
1344 }
1345 
virtual_hook(int id,void * data)1346 void Calendar::virtual_hook(int id, void *data)
1347 {
1348     Q_UNUSED(id);
1349     Q_UNUSED(data);
1350     Q_ASSERT(false);
1351 }
1352 
id() const1353 QString Calendar::id() const
1354 {
1355     return d->mId;
1356 }
1357 
setId(const QString & id)1358 void Calendar::setId(const QString &id)
1359 {
1360     if (d->mId != id) {
1361         d->mId = id;
1362         Q_EMIT idChanged();
1363     }
1364 }
1365 
name() const1366 QString Calendar::name() const
1367 {
1368     return d->mName;
1369 }
1370 
setName(const QString & name)1371 void Calendar::setName(const QString &name)
1372 {
1373     if (d->mName != name) {
1374         d->mName = name;
1375         Q_EMIT nameChanged();
1376     }
1377 }
1378 
icon() const1379 QIcon Calendar::icon() const
1380 {
1381     return d->mIcon;
1382 }
1383 
setIcon(const QIcon & icon)1384 void Calendar::setIcon(const QIcon &icon)
1385 {
1386     d->mIcon = icon;
1387     Q_EMIT iconChanged();
1388 }
1389 
accessMode() const1390 AccessMode Calendar::accessMode() const
1391 {
1392     return d->mAccessMode;
1393 }
1394 
setAccessMode(const AccessMode mode)1395 void Calendar::setAccessMode(const AccessMode mode)
1396 {
1397     if (d->mAccessMode != mode) {
1398         d->mAccessMode = mode;
1399         Q_EMIT accessModeChanged();
1400     }
1401 }
1402