File indexing completed on 2024-04-21 03:52:44

0001 /*
0002   This file is part of the kcalcore library.
0003 
0004   SPDX-FileCopyrightText: 1998 Preston Brown <pbrown@kde.org>
0005   SPDX-FileCopyrightText: 2000-2004 Cornelius Schumacher <schumacher@kde.org>
0006   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0007   SPDX-FileCopyrightText: 2006 David Jarvie <djarvie@kde.org>
0008   SPDX-FileCopyrightText: 2021 Boris Shmarin <b.shmarin@omp.ru>
0009 
0010   SPDX-License-Identifier: LGPL-2.0-or-later
0011 */
0012 /**
0013   @file
0014   This file is part of the API for handling calendar data and
0015   defines the Calendar class.
0016 
0017   @brief
0018   Represents the main calendar class.
0019 
0020   @author Preston Brown \<pbrown@kde.org\>
0021   @author Cornelius Schumacher \<schumacher@kde.org\>
0022   @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
0023   @author David Jarvie \<djarvie@kde.org\>
0024 */
0025 #include "calendar.h"
0026 #include "calendar_p.h"
0027 #include "calfilter.h"
0028 #include "icaltimezones_p.h"
0029 #include "sorting.h"
0030 #include "visitor.h"
0031 
0032 #include "kcalendarcore_debug.h"
0033 
0034 
0035 extern "C" {
0036 #include <libical/icaltimezone.h>
0037 }
0038 
0039 #include <algorithm>
0040 #include <set>
0041 
0042 using namespace KCalendarCore;
0043 
0044 /**
0045   Template for a class that implements a visitor for adding an Incidence
0046   to a resource supporting addEvent(), addTodo() and addJournal() calls.
0047 */
0048 template<class T>
0049 class AddVisitor : public Visitor
0050 {
0051 public:
0052     AddVisitor(T *r)
0053         : mResource(r)
0054     {
0055     }
0056 
0057     bool visit(const Event::Ptr &e) override
0058     {
0059         return mResource->addEvent(e);
0060     }
0061     bool visit(const Todo::Ptr &t) override
0062     {
0063         return mResource->addTodo(t);
0064     }
0065     bool visit(const Journal::Ptr &j) override
0066     {
0067         return mResource->addJournal(j);
0068     }
0069     bool visit(const FreeBusy::Ptr &) override
0070     {
0071         return false;
0072     }
0073 
0074 private:
0075     T *mResource;
0076 };
0077 
0078 /**
0079   Template for a class that implements a visitor for deleting an Incidence
0080   from a resource supporting deleteEvent(), deleteTodo() and deleteJournal()
0081   calls.
0082 */
0083 template<class T>
0084 class DeleteVisitor : public Visitor
0085 {
0086 public:
0087     DeleteVisitor(T *r)
0088         : mResource(r)
0089     {
0090     }
0091 
0092     bool visit(const Event::Ptr &e) override
0093     {
0094         mResource->deleteEvent(e);
0095         return true;
0096     }
0097     bool visit(const Todo::Ptr &t) override
0098     {
0099         mResource->deleteTodo(t);
0100         return true;
0101     }
0102     bool visit(const Journal::Ptr &j) override
0103     {
0104         mResource->deleteJournal(j);
0105         return true;
0106     }
0107     bool visit(const FreeBusy::Ptr &) override
0108     {
0109         return false;
0110     }
0111 
0112 private:
0113     T *mResource;
0114 };
0115 //@endcond
0116 
0117 Calendar::Calendar(const QTimeZone &timeZone)
0118     : d(new KCalendarCore::Calendar::Private)
0119 {
0120     if (timeZone.isValid()) {
0121         d->mTimeZone = timeZone;
0122     } else {
0123         d->mTimeZone = QTimeZone::systemTimeZone();
0124     }
0125 }
0126 
0127 Calendar::Calendar(const QByteArray &timeZoneId)
0128     : d(new KCalendarCore::Calendar::Private)
0129 {
0130     setTimeZoneId(timeZoneId);
0131 }
0132 
0133 Calendar::~Calendar()
0134 {
0135     delete d;
0136 }
0137 
0138 Person Calendar::owner() const
0139 {
0140     return d->mOwner;
0141 }
0142 
0143 void Calendar::setOwner(const Person &owner)
0144 {
0145     if (owner != d->mOwner) {
0146         d->mOwner = owner;
0147         setModified(true);
0148         Q_EMIT ownerChanged();
0149     }
0150 }
0151 
0152 void Calendar::setTimeZone(const QTimeZone &timeZone)
0153 {
0154     if (timeZone.isValid()) {
0155         d->mTimeZone = timeZone;
0156     } else {
0157         d->mTimeZone = QTimeZone::systemTimeZone();
0158     }
0159 
0160     doSetTimeZone(d->mTimeZone);
0161 }
0162 
0163 QTimeZone Calendar::timeZone() const
0164 {
0165     return d->mTimeZone;
0166 }
0167 
0168 void Calendar::setTimeZoneId(const QByteArray &timeZoneId)
0169 {
0170     d->mTimeZone = d->timeZoneIdSpec(timeZoneId);
0171 
0172     doSetTimeZone(d->mTimeZone); // NOLINT false clang-analyzer-optin.cplusplus.VirtualCall
0173 }
0174 
0175 //@cond PRIVATE
0176 QTimeZone Calendar::Private::timeZoneIdSpec(const QByteArray &timeZoneId)
0177 {
0178     if (timeZoneId == QByteArrayLiteral("UTC")) {
0179         return QTimeZone::utc();
0180     }
0181     auto tz = QTimeZone(timeZoneId);
0182     if (tz.isValid()) {
0183         return tz;
0184     }
0185     return QTimeZone::systemTimeZone();
0186 }
0187 //@endcond
0188 
0189 QByteArray Calendar::timeZoneId() const
0190 {
0191     return d->mTimeZone.id();
0192 }
0193 
0194 void Calendar::shiftTimes(const QTimeZone &oldZone, const QTimeZone &newZone)
0195 {
0196     setTimeZone(newZone);
0197 
0198     int i;
0199     int end;
0200     Event::List ev = events();
0201     for (i = 0, end = ev.count(); i < end; ++i) {
0202         ev[i]->shiftTimes(oldZone, newZone);
0203     }
0204 
0205     Todo::List to = todos();
0206     for (i = 0, end = to.count(); i < end; ++i) {
0207         to[i]->shiftTimes(oldZone, newZone);
0208     }
0209 
0210     Journal::List jo = journals();
0211     for (i = 0, end = jo.count(); i < end; ++i) {
0212         jo[i]->shiftTimes(oldZone, newZone);
0213     }
0214 }
0215 
0216 void Calendar::setFilter(CalFilter *filter)
0217 {
0218     if (filter) {
0219         d->mFilter = filter;
0220     } else {
0221         d->mFilter = d->mDefaultFilter;
0222     }
0223     Q_EMIT filterChanged();
0224 }
0225 
0226 CalFilter *Calendar::filter() const
0227 {
0228     return d->mFilter;
0229 }
0230 
0231 QStringList Calendar::categories() const
0232 {
0233     const Incidence::List rawInc = rawIncidences();
0234     QStringList uniqueCategories;
0235     QStringList thisCats;
0236     // @TODO: For now just iterate over all incidences. In the future,
0237     // the list of categories should be built when reading the file.
0238     for (const Incidence::Ptr &inc : rawInc) {
0239         thisCats = inc->categories();
0240         for (const auto &cat : std::as_const(thisCats)) {
0241             if (!uniqueCategories.contains(cat)) {
0242                 uniqueCategories.append(cat);
0243             }
0244         }
0245     }
0246     return uniqueCategories;
0247 }
0248 
0249 Incidence::List Calendar::incidences(const QDate &date) const
0250 {
0251     return mergeIncidenceList(events(date), todos(date), journals(date));
0252 }
0253 
0254 Incidence::List Calendar::incidences() const
0255 {
0256     return mergeIncidenceList(events(), todos(), journals());
0257 }
0258 
0259 Incidence::List Calendar::rawIncidences() const
0260 {
0261     return mergeIncidenceList(rawEvents(), rawTodos(), rawJournals());
0262 }
0263 
0264 Incidence::List Calendar::instances(const Incidence::Ptr &incidence) const
0265 {
0266     if (incidence) {
0267         Event::List elist;
0268         Todo::List tlist;
0269         Journal::List jlist;
0270 
0271         if (incidence->type() == Incidence::TypeEvent) {
0272             elist = eventInstances(incidence);
0273         } else if (incidence->type() == Incidence::TypeTodo) {
0274             tlist = todoInstances(incidence);
0275         } else if (incidence->type() == Incidence::TypeJournal) {
0276             jlist = journalInstances(incidence);
0277         }
0278         return mergeIncidenceList(elist, tlist, jlist);
0279     } else {
0280         return Incidence::List();
0281     }
0282 }
0283 
0284 Event::List Calendar::sortEvents(Event::List &&eventList, EventSortField sortField, SortDirection sortDirection)
0285 {
0286     switch (sortField) {
0287     case EventSortUnsorted:
0288         break;
0289 
0290     case EventSortStartDate:
0291         if (sortDirection == SortDirectionAscending) {
0292             std::sort(eventList.begin(), eventList.end(), Events::startDateLessThan);
0293         } else {
0294             std::sort(eventList.begin(), eventList.end(), Events::startDateMoreThan);
0295         }
0296         break;
0297 
0298     case EventSortEndDate:
0299         if (sortDirection == SortDirectionAscending) {
0300             std::sort(eventList.begin(), eventList.end(), Events::endDateLessThan);
0301         } else {
0302             std::sort(eventList.begin(), eventList.end(), Events::endDateMoreThan);
0303         }
0304         break;
0305 
0306     case EventSortSummary:
0307         if (sortDirection == SortDirectionAscending) {
0308             std::sort(eventList.begin(), eventList.end(), Events::summaryLessThan);
0309         } else {
0310             std::sort(eventList.begin(), eventList.end(), Events::summaryMoreThan);
0311         }
0312         break;
0313     }
0314 
0315     return eventList;
0316 }
0317 
0318 Event::List Calendar::events(const QDate &date, const QTimeZone &timeZone, EventSortField sortField, SortDirection sortDirection) const
0319 {
0320     Event::List el = rawEventsForDate(date, timeZone, sortField, sortDirection);
0321     d->mFilter->apply(&el);
0322     return el;
0323 }
0324 
0325 Event::List Calendar::events(const QDateTime &dt) const
0326 {
0327     Event::List el = rawEventsForDate(dt.date(), dt.timeZone());
0328     d->mFilter->apply(&el);
0329     return el;
0330 }
0331 
0332 Event::List Calendar::events(const QDate &start, const QDate &end, const QTimeZone &timeZone, bool inclusive) const
0333 {
0334     Event::List el = rawEvents(start, end, timeZone, inclusive);
0335     d->mFilter->apply(&el);
0336     return el;
0337 }
0338 
0339 Event::List Calendar::events(EventSortField sortField, SortDirection sortDirection) const
0340 {
0341     Event::List el = rawEvents(sortField, sortDirection);
0342     d->mFilter->apply(&el);
0343     return el;
0344 }
0345 
0346 bool Calendar::addIncidence(const Incidence::Ptr &incidence)
0347 {
0348     if (!incidence) {
0349         return false;
0350     }
0351 
0352     AddVisitor<Calendar> v(this);
0353     return incidence->accept(v, incidence);
0354 }
0355 
0356 bool Calendar::deleteIncidence(const Incidence::Ptr &incidence)
0357 {
0358     if (!incidence) {
0359         return false;
0360     }
0361 
0362     if (beginChange(incidence)) {
0363         DeleteVisitor<Calendar> v(this);
0364         const bool result = incidence->accept(v, incidence);
0365         endChange(incidence);
0366         return result;
0367     } else {
0368         return false;
0369     }
0370 }
0371 
0372 Incidence::Ptr Calendar::createException(const Incidence::Ptr &incidence, const QDateTime &recurrenceId, bool thisAndFuture)
0373 {
0374     Q_ASSERT(recurrenceId.isValid());
0375     if (!incidence || !incidence->recurs() || !recurrenceId.isValid()) {
0376         return Incidence::Ptr();
0377     }
0378 
0379     Incidence::Ptr newInc(incidence->clone());
0380     const QDateTime current = QDateTime::currentDateTimeUtc();
0381     newInc->setCreated(current);
0382     newInc->setLastModified(current);
0383     newInc->setRevision(0);
0384     // Recurring exceptions are not support for now
0385     newInc->clearRecurrence();
0386 
0387     newInc->setRecurrenceId(recurrenceId);
0388     newInc->setThisAndFuture(thisAndFuture);
0389     newInc->setDtStart(recurrenceId);
0390 
0391     // Calculate and set the new end of the incidence
0392     QDateTime end = incidence->dateTime(IncidenceBase::RoleEnd);
0393 
0394     if (end.isValid()) {
0395         if (incidence->allDay()) {
0396             qint64 offset = incidence->dtStart().daysTo(recurrenceId);
0397             end = end.addDays(offset);
0398         } else {
0399             qint64 offset = incidence->dtStart().secsTo(recurrenceId);
0400             end = end.addSecs(offset);
0401         }
0402         newInc->setDateTime(end, IncidenceBase::RoleEnd);
0403     }
0404     return newInc;
0405 }
0406 
0407 Incidence::Ptr Calendar::incidence(const QString &uid, const QDateTime &recurrenceId) const
0408 {
0409     Incidence::Ptr i = event(uid, recurrenceId);
0410     if (i) {
0411         return i;
0412     }
0413 
0414     i = todo(uid, recurrenceId);
0415     if (i) {
0416         return i;
0417     }
0418 
0419     i = journal(uid, recurrenceId);
0420     return i;
0421 }
0422 
0423 Incidence::List Calendar::incidencesFromSchedulingID(const QString &sid) const
0424 {
0425     Incidence::List result;
0426     const Incidence::List incidences = rawIncidences();
0427     std::copy_if(incidences.cbegin(), incidences.cend(), std::back_inserter(result), [&sid](const Incidence::Ptr &in) {
0428         return in->schedulingID() == sid;
0429     });
0430     return result;
0431 }
0432 
0433 Incidence::Ptr Calendar::incidenceFromSchedulingID(const QString &uid) const
0434 {
0435     const Incidence::List incidences = rawIncidences();
0436     const auto itEnd = incidences.cend();
0437     auto it = std::find_if(incidences.cbegin(), itEnd, [&uid](const Incidence::Ptr &in) {
0438         return in->schedulingID() == uid;
0439     });
0440 
0441     return it != itEnd ? *it : Incidence::Ptr();
0442 }
0443 
0444 Todo::List Calendar::sortTodos(Todo::List &&todoList, TodoSortField sortField, SortDirection sortDirection)
0445 {
0446     // Note that To-dos may not have Start DateTimes nor due DateTimes.
0447     switch (sortField) {
0448     case TodoSortUnsorted:
0449         break;
0450 
0451     case TodoSortStartDate:
0452         if (sortDirection == SortDirectionAscending) {
0453             std::sort(todoList.begin(), todoList.end(), Todos::startDateLessThan);
0454         } else {
0455             std::sort(todoList.begin(), todoList.end(), Todos::startDateMoreThan);
0456         }
0457         break;
0458 
0459     case TodoSortDueDate:
0460         if (sortDirection == SortDirectionAscending) {
0461             std::sort(todoList.begin(), todoList.end(), Todos::dueDateLessThan);
0462         } else {
0463             std::sort(todoList.begin(), todoList.end(), Todos::dueDateMoreThan);
0464         }
0465         break;
0466 
0467     case TodoSortPriority:
0468         if (sortDirection == SortDirectionAscending) {
0469             std::sort(todoList.begin(), todoList.end(), Todos::priorityLessThan);
0470         } else {
0471             std::sort(todoList.begin(), todoList.end(), Todos::priorityMoreThan);
0472         }
0473         break;
0474 
0475     case TodoSortPercentComplete:
0476         if (sortDirection == SortDirectionAscending) {
0477             std::sort(todoList.begin(), todoList.end(), Todos::percentLessThan);
0478         } else {
0479             std::sort(todoList.begin(), todoList.end(), Todos::percentMoreThan);
0480         }
0481         break;
0482 
0483     case TodoSortSummary:
0484         if (sortDirection == SortDirectionAscending) {
0485             std::sort(todoList.begin(), todoList.end(), Todos::summaryLessThan);
0486         } else {
0487             std::sort(todoList.begin(), todoList.end(), Todos::summaryMoreThan);
0488         }
0489         break;
0490 
0491     case TodoSortCreated:
0492         if (sortDirection == SortDirectionAscending) {
0493             std::sort(todoList.begin(), todoList.end(), Todos::createdLessThan);
0494         } else {
0495             std::sort(todoList.begin(), todoList.end(), Todos::createdMoreThan);
0496         }
0497         break;
0498 
0499     case TodoSortCategories:
0500         if (sortDirection == SortDirectionAscending) {
0501             std::sort(todoList.begin(), todoList.end(), Incidences::categoriesLessThan);
0502         } else {
0503             std::sort(todoList.begin(), todoList.end(), Incidences::categoriesMoreThan);
0504         }
0505         break;
0506     }
0507 
0508     return todoList;
0509 }
0510 
0511 Todo::List Calendar::todos(TodoSortField sortField, SortDirection sortDirection) const
0512 {
0513     Todo::List tl = rawTodos(sortField, sortDirection);
0514     d->mFilter->apply(&tl);
0515     return tl;
0516 }
0517 
0518 Todo::List Calendar::todos(const QDate &date) const
0519 {
0520     Todo::List el = rawTodosForDate(date);
0521     d->mFilter->apply(&el);
0522     return el;
0523 }
0524 
0525 Todo::List Calendar::todos(const QDate &start, const QDate &end, const QTimeZone &timeZone, bool inclusive) const
0526 {
0527     Todo::List tl = rawTodos(start, end, timeZone, inclusive);
0528     d->mFilter->apply(&tl);
0529     return tl;
0530 }
0531 
0532 Journal::List Calendar::sortJournals(Journal::List &&journalList, JournalSortField sortField, SortDirection sortDirection)
0533 {
0534     switch (sortField) {
0535     case JournalSortUnsorted:
0536         break;
0537 
0538     case JournalSortDate:
0539         if (sortDirection == SortDirectionAscending) {
0540             std::sort(journalList.begin(), journalList.end(), Journals::dateLessThan);
0541         } else {
0542             std::sort(journalList.begin(), journalList.end(), Journals::dateMoreThan);
0543         }
0544         break;
0545 
0546     case JournalSortSummary:
0547         if (sortDirection == SortDirectionAscending) {
0548             std::sort(journalList.begin(), journalList.end(), Journals::summaryLessThan);
0549         } else {
0550             std::sort(journalList.begin(), journalList.end(), Journals::summaryMoreThan);
0551         }
0552         break;
0553     }
0554 
0555     return journalList;
0556 }
0557 
0558 Journal::List Calendar::journals(JournalSortField sortField, SortDirection sortDirection) const
0559 {
0560     Journal::List jl = rawJournals(sortField, sortDirection);
0561     d->mFilter->apply(&jl);
0562     return jl;
0563 }
0564 
0565 Journal::List Calendar::journals(const QDate &date) const
0566 {
0567     Journal::List el = rawJournalsForDate(date);
0568     d->mFilter->apply(&el);
0569     return el;
0570 }
0571 
0572 Calendar::CalendarObserver::~CalendarObserver()
0573 {
0574 }
0575 
0576 void Calendar::CalendarObserver::calendarModified(bool modified, Calendar *calendar)
0577 {
0578     Q_UNUSED(modified);
0579     Q_UNUSED(calendar);
0580 }
0581 
0582 void Calendar::CalendarObserver::calendarIncidenceAdded(const Incidence::Ptr &incidence)
0583 {
0584     Q_UNUSED(incidence);
0585 }
0586 
0587 void Calendar::CalendarObserver::calendarIncidenceChanged(const Incidence::Ptr &incidence)
0588 {
0589     Q_UNUSED(incidence);
0590 }
0591 
0592 void Calendar::CalendarObserver::calendarIncidenceAboutToBeDeleted(const Incidence::Ptr &incidence)
0593 {
0594     Q_UNUSED(incidence);
0595 }
0596 
0597 void Calendar::CalendarObserver::calendarIncidenceDeleted(const Incidence::Ptr &incidence, const Calendar *calendar)
0598 {
0599     Q_UNUSED(incidence);
0600     Q_UNUSED(calendar);
0601 }
0602 
0603 void Calendar::CalendarObserver::calendarIncidenceAdditionCanceled(const Incidence::Ptr &incidence)
0604 {
0605     Q_UNUSED(incidence);
0606 }
0607 
0608 void Calendar::registerObserver(CalendarObserver *observer)
0609 {
0610     if (!observer) {
0611         return;
0612     }
0613 
0614     if (!d->mObservers.contains(observer)) {
0615         d->mObservers.append(observer);
0616     } else {
0617         d->mNewObserver = true;
0618     }
0619 }
0620 
0621 void Calendar::unregisterObserver(CalendarObserver *observer)
0622 {
0623     if (!observer) {
0624         return;
0625     } else {
0626         d->mObservers.removeAll(observer);
0627     }
0628 }
0629 
0630 void Calendar::setModified(bool modified)
0631 {
0632     if (modified != d->mModified || d->mNewObserver) {
0633         d->mNewObserver = false;
0634         for (CalendarObserver *observer : std::as_const(d->mObservers)) {
0635             observer->calendarModified(modified, this);
0636         }
0637         d->mModified = modified;
0638     }
0639 }
0640 
0641 bool Calendar::isModified() const
0642 {
0643     return d->mModified;
0644 }
0645 
0646 void Calendar::incidenceUpdated(const QString &uid, const QDateTime &recurrenceId)
0647 {
0648     Incidence::Ptr inc = incidence(uid, recurrenceId);
0649 
0650     if (!inc) {
0651         return;
0652     }
0653 
0654     inc->setLastModified(QDateTime::currentDateTimeUtc());
0655     // we should probably update the revision number here,
0656     // or internally in the Event itself when certain things change.
0657     // need to verify with ical documentation.
0658 
0659     notifyIncidenceChanged(inc);
0660 
0661     setModified(true);
0662 }
0663 
0664 void Calendar::doSetTimeZone(const QTimeZone &timeZone)
0665 {
0666     Q_UNUSED(timeZone);
0667 }
0668 
0669 void Calendar::notifyIncidenceAdded(const Incidence::Ptr &incidence)
0670 {
0671     if (!incidence) {
0672         return;
0673     }
0674 
0675     if (!d->mObserversEnabled) {
0676         return;
0677     }
0678 
0679     for (CalendarObserver *observer : std::as_const(d->mObservers)) {
0680         observer->calendarIncidenceAdded(incidence);
0681     }
0682 
0683     for (auto role : {IncidenceBase::RoleStartTimeZone, IncidenceBase::RoleEndTimeZone}) {
0684         const auto dt = incidence->dateTime(role);
0685         if (dt.isValid() && dt.timeZone() != QTimeZone::utc()) {
0686             if (!d->mTimeZones.contains(dt.timeZone())) {
0687                 d->mTimeZones.push_back(dt.timeZone());
0688             }
0689         }
0690     }
0691 }
0692 
0693 void Calendar::notifyIncidenceChanged(const Incidence::Ptr &incidence)
0694 {
0695     if (!incidence) {
0696         return;
0697     }
0698 
0699     if (!d->mObserversEnabled) {
0700         return;
0701     }
0702 
0703     for (CalendarObserver *observer : std::as_const(d->mObservers)) {
0704         observer->calendarIncidenceChanged(incidence);
0705     }
0706 }
0707 
0708 void Calendar::notifyIncidenceAboutToBeDeleted(const Incidence::Ptr &incidence)
0709 {
0710     if (!incidence) {
0711         return;
0712     }
0713 
0714     if (!d->mObserversEnabled) {
0715         return;
0716     }
0717 
0718     for (CalendarObserver *observer : std::as_const(d->mObservers)) {
0719         observer->calendarIncidenceAboutToBeDeleted(incidence);
0720     }
0721 }
0722 
0723 void Calendar::notifyIncidenceDeleted(const Incidence::Ptr &incidence)
0724 {
0725     if (!incidence) {
0726         return;
0727     }
0728 
0729     if (!d->mObserversEnabled) {
0730         return;
0731     }
0732 
0733     for (CalendarObserver *observer : std::as_const(d->mObservers)) {
0734         observer->calendarIncidenceDeleted(incidence, this);
0735     }
0736 }
0737 
0738 void Calendar::notifyIncidenceAdditionCanceled(const Incidence::Ptr &incidence)
0739 {
0740     if (!incidence) {
0741         return;
0742     }
0743 
0744     if (!d->mObserversEnabled) {
0745         return;
0746     }
0747 
0748     for (CalendarObserver *observer : std::as_const(d->mObservers)) {
0749         observer->calendarIncidenceAdditionCanceled(incidence);
0750     }
0751 }
0752 
0753 void Calendar::customPropertyUpdated()
0754 {
0755     setModified(true);
0756 }
0757 
0758 void Calendar::setProductId(const QString &id)
0759 {
0760     d->mProductId = id;
0761 }
0762 
0763 QString Calendar::productId() const
0764 {
0765     return d->mProductId;
0766 }
0767 
0768 /** static */
0769 Incidence::List Calendar::mergeIncidenceList(const Event::List &events, const Todo::List &todos, const Journal::List &journals)
0770 {
0771     Incidence::List incidences;
0772     incidences.reserve(events.count() + todos.count() + journals.count());
0773 
0774     int i;
0775     int end;
0776     for (i = 0, end = events.count(); i < end; ++i) {
0777         incidences.append(events[i]);
0778     }
0779 
0780     for (i = 0, end = todos.count(); i < end; ++i) {
0781         incidences.append(todos[i]);
0782     }
0783 
0784     for (i = 0, end = journals.count(); i < end; ++i) {
0785         incidences.append(journals[i]);
0786     }
0787 
0788     return incidences;
0789 }
0790 
0791 bool Calendar::beginChange(const Incidence::Ptr &incidence)
0792 {
0793     Q_UNUSED(incidence);
0794     return true;
0795 }
0796 
0797 bool Calendar::endChange(const Incidence::Ptr &incidence)
0798 {
0799     Q_UNUSED(incidence);
0800     return true;
0801 }
0802 
0803 void Calendar::setObserversEnabled(bool enabled)
0804 {
0805     d->mObserversEnabled = enabled;
0806 }
0807 
0808 void Calendar::appendAlarms(Alarm::List &alarms, const Incidence::Ptr &incidence, const QDateTime &from, const QDateTime &to) const
0809 {
0810     QDateTime preTime = from.addSecs(-1);
0811 
0812     Alarm::List alarmlist = incidence->alarms();
0813     for (int i = 0, iend = alarmlist.count(); i < iend; ++i) {
0814         if (alarmlist[i]->enabled()) {
0815             QDateTime dt = alarmlist[i]->nextRepetition(preTime);
0816             if (dt.isValid() && dt <= to) {
0817                 qCDebug(KCALCORE_LOG) << incidence->summary() << "':" << dt.toString();
0818                 alarms.append(alarmlist[i]);
0819             }
0820         }
0821     }
0822 }
0823 
0824 void Calendar::appendRecurringAlarms(Alarm::List &alarms, const Incidence::Ptr &incidence, const QDateTime &from, const QDateTime &to) const
0825 {
0826     QDateTime dt;
0827     bool endOffsetValid = false;
0828     Duration endOffset(0);
0829     Duration period(from, to);
0830 
0831     Alarm::List alarmlist = incidence->alarms();
0832     for (int i = 0, iend = alarmlist.count(); i < iend; ++i) {
0833         Alarm::Ptr a = alarmlist[i];
0834         if (a->enabled()) {
0835             if (a->hasTime()) {
0836                 // The alarm time is defined as an absolute date/time
0837                 dt = a->nextRepetition(from.addSecs(-1));
0838                 if (!dt.isValid() || dt > to) {
0839                     continue;
0840                 }
0841             } else {
0842                 // Alarm time is defined by an offset from the event start or end time.
0843                 // Find the offset from the event start time, which is also used as the
0844                 // offset from the recurrence time.
0845                 Duration offset(0);
0846                 if (a->hasStartOffset()) {
0847                     offset = a->startOffset();
0848                 } else if (a->hasEndOffset()) {
0849                     offset = a->endOffset();
0850                     if (!endOffsetValid) {
0851                         endOffset = Duration(incidence->dtStart(), incidence->dateTime(Incidence::RoleAlarmEndOffset));
0852                         endOffsetValid = true;
0853                     }
0854                 }
0855 
0856                 // Find the incidence's earliest alarm
0857                 QDateTime alarmStart = offset.end(a->hasEndOffset() ? incidence->dateTime(Incidence::RoleAlarmEndOffset) : incidence->dtStart());
0858                 if (alarmStart > to) {
0859                     continue;
0860                 }
0861                 QDateTime baseStart = incidence->dtStart();
0862                 if (from > alarmStart) {
0863                     alarmStart = from; // don't look earlier than the earliest alarm
0864                     baseStart = (-offset).end((-endOffset).end(alarmStart));
0865                 }
0866 
0867                 // Adjust the 'alarmStart' date/time and find the next recurrence at or after it.
0868                 // Treat the two offsets separately in case one is daily and the other not.
0869                 dt = incidence->recurrence()->getNextDateTime(baseStart.addSecs(-1));
0870                 if (!dt.isValid() || (dt = endOffset.end(offset.end(dt))) > to) { // adjust 'dt' to get the alarm time
0871                     // The next recurrence is too late.
0872                     if (!a->repeatCount()) {
0873                         continue;
0874                     }
0875 
0876                     // The alarm has repetitions, so check whether repetitions of previous
0877                     // recurrences fall within the time period.
0878                     bool found = false;
0879                     Duration alarmDuration = a->duration();
0880                     for (QDateTime base = baseStart; (dt = incidence->recurrence()->getPreviousDateTime(base)).isValid(); base = dt) {
0881                         if (a->duration().end(dt) < base) {
0882                             break; // this recurrence's last repetition is too early, so give up
0883                         }
0884 
0885                         // The last repetition of this recurrence is at or after 'alarmStart' time.
0886                         // Check if a repetition occurs between 'alarmStart' and 'to'.
0887                         int snooze = a->snoozeTime().value(); // in seconds or days
0888                         if (a->snoozeTime().isDaily()) {
0889                             Duration toFromDuration(dt, base);
0890                             int toFrom = toFromDuration.asDays();
0891                             if (a->snoozeTime().end(from) <= to || (toFromDuration.isDaily() && toFrom % snooze == 0)
0892                                 || (toFrom / snooze + 1) * snooze <= toFrom + period.asDays()) {
0893                                 found = true;
0894 #ifndef NDEBUG
0895                                 // for debug output
0896                                 dt = offset.end(dt).addDays(((toFrom - 1) / snooze + 1) * snooze);
0897 #endif
0898                                 break;
0899                             }
0900                         } else {
0901                             int toFrom = dt.secsTo(base);
0902                             if (period.asSeconds() >= snooze || toFrom % snooze == 0 || (toFrom / snooze + 1) * snooze <= toFrom + period.asSeconds()) {
0903                                 found = true;
0904 #ifndef NDEBUG
0905                                 // for debug output
0906                                 dt = offset.end(dt).addSecs(((toFrom - 1) / snooze + 1) * snooze);
0907 #endif
0908                                 break;
0909                             }
0910                         }
0911                     }
0912                     if (!found) {
0913                         continue;
0914                     }
0915                 }
0916             }
0917             qCDebug(KCALCORE_LOG) << incidence->summary() << "':" << dt.toString();
0918             alarms.append(a);
0919         }
0920     }
0921 }
0922 
0923 void Calendar::startBatchAdding()
0924 {
0925     d->batchAddingInProgress = true;
0926 }
0927 
0928 void Calendar::endBatchAdding()
0929 {
0930     d->batchAddingInProgress = false;
0931 }
0932 
0933 bool Calendar::batchAdding() const
0934 {
0935     return d->batchAddingInProgress;
0936 }
0937 
0938 Alarm::List Calendar::alarmsTo(const QDateTime &to) const
0939 {
0940     return alarms(QDateTime(QDate(1900, 1, 1), QTime(0, 0, 0)), to);
0941 }
0942 
0943 void Calendar::virtual_hook(int id, void *data)
0944 {
0945     Q_UNUSED(id);
0946     Q_UNUSED(data);
0947     Q_ASSERT(false);
0948 }
0949 
0950 QString Calendar::id() const
0951 {
0952     return d->mId;
0953 }
0954 
0955 void Calendar::setId(const QString &id)
0956 {
0957     if (d->mId != id) {
0958         d->mId = id;
0959         Q_EMIT idChanged();
0960     }
0961 }
0962 
0963 QString Calendar::name() const
0964 {
0965     return d->mName;
0966 }
0967 
0968 void Calendar::setName(const QString &name)
0969 {
0970     if (d->mName != name) {
0971         d->mName = name;
0972         Q_EMIT nameChanged();
0973     }
0974 }
0975 
0976 QIcon Calendar::icon() const
0977 {
0978     return d->mIcon;
0979 }
0980 
0981 void Calendar::setIcon(const QIcon &icon)
0982 {
0983     d->mIcon = icon;
0984     Q_EMIT iconChanged();
0985 }
0986 
0987 AccessMode Calendar::accessMode() const
0988 {
0989     return d->mAccessMode;
0990 }
0991 
0992 void Calendar::setAccessMode(const AccessMode mode)
0993 {
0994     if (d->mAccessMode != mode) {
0995         d->mAccessMode = mode;
0996         Q_EMIT accessModeChanged();
0997     }
0998 }
0999 
1000 bool Calendar::isLoading() const
1001 {
1002     return d->mIsLoading;
1003 }
1004 
1005 void Calendar::setIsLoading(bool isLoading)
1006 {
1007     if (d->mIsLoading == isLoading) {
1008         return;
1009     }
1010 
1011     d->mIsLoading = isLoading;
1012     Q_EMIT isLoadingChanged();
1013 }
1014 
1015 #include "moc_calendar.cpp"