File indexing completed on 2024-05-05 16:05:30
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 Make a QHash::value that returns a QVector. 0046 */ 0047 template<typename K, typename V> 0048 QVector<V> values(const QMultiHash<K, V> &c) 0049 { 0050 QVector<V> v; 0051 v.reserve(c.size()); 0052 for (typename QMultiHash<K, V>::const_iterator it = c.begin(), end = c.end(); it != end; ++it) { 0053 v.push_back(it.value()); 0054 } 0055 return v; 0056 } 0057 0058 template<typename K, typename V> 0059 QVector<V> values(const QMultiHash<K, V> &c, const K &x) 0060 { 0061 QVector<V> v; 0062 typename QMultiHash<K, V>::const_iterator it = c.find(x); 0063 while (it != c.end() && it.key() == x) { 0064 v.push_back(it.value()); 0065 ++it; 0066 } 0067 return v; 0068 } 0069 0070 /** 0071 Template for a class that implements a visitor for adding an Incidence 0072 to a resource supporting addEvent(), addTodo() and addJournal() calls. 0073 */ 0074 template<class T> 0075 class AddVisitor : public Visitor 0076 { 0077 public: 0078 AddVisitor(T *r) 0079 : mResource(r) 0080 { 0081 } 0082 0083 bool visit(const Event::Ptr &e) override 0084 { 0085 return mResource->addEvent(e); 0086 } 0087 bool visit(const Todo::Ptr &t) override 0088 { 0089 return mResource->addTodo(t); 0090 } 0091 bool visit(const Journal::Ptr &j) override 0092 { 0093 return mResource->addJournal(j); 0094 } 0095 bool visit(const FreeBusy::Ptr &) override 0096 { 0097 return false; 0098 } 0099 0100 private: 0101 T *mResource; 0102 }; 0103 0104 /** 0105 Template for a class that implements a visitor for deleting an Incidence 0106 from a resource supporting deleteEvent(), deleteTodo() and deleteJournal() 0107 calls. 0108 */ 0109 template<class T> 0110 class DeleteVisitor : public Visitor 0111 { 0112 public: 0113 DeleteVisitor(T *r) 0114 : mResource(r) 0115 { 0116 } 0117 0118 bool visit(const Event::Ptr &e) override 0119 { 0120 mResource->deleteEvent(e); 0121 return true; 0122 } 0123 bool visit(const Todo::Ptr &t) override 0124 { 0125 mResource->deleteTodo(t); 0126 return true; 0127 } 0128 bool visit(const Journal::Ptr &j) override 0129 { 0130 mResource->deleteJournal(j); 0131 return true; 0132 } 0133 bool visit(const FreeBusy::Ptr &) override 0134 { 0135 return false; 0136 } 0137 0138 private: 0139 T *mResource; 0140 }; 0141 //@endcond 0142 0143 Calendar::Calendar(const QTimeZone &timeZone) 0144 : d(new KCalendarCore::Calendar::Private) 0145 { 0146 if (timeZone.isValid()) { 0147 d->mTimeZone = timeZone; 0148 } else { 0149 d->mTimeZone = QTimeZone::systemTimeZone(); 0150 } 0151 } 0152 0153 Calendar::Calendar(const QByteArray &timeZoneId) 0154 : d(new KCalendarCore::Calendar::Private) 0155 { 0156 setTimeZoneId(timeZoneId); 0157 } 0158 0159 Calendar::~Calendar() 0160 { 0161 delete d; 0162 } 0163 0164 Person Calendar::owner() const 0165 { 0166 return d->mOwner; 0167 } 0168 0169 void Calendar::setOwner(const Person &owner) 0170 { 0171 if (owner != d->mOwner) { 0172 d->mOwner = owner; 0173 setModified(true); 0174 Q_EMIT ownerChanged(); 0175 } 0176 } 0177 0178 void Calendar::setTimeZone(const QTimeZone &timeZone) 0179 { 0180 if (timeZone.isValid()) { 0181 d->mTimeZone = timeZone; 0182 } else { 0183 d->mTimeZone = QTimeZone::systemTimeZone(); 0184 } 0185 0186 doSetTimeZone(d->mTimeZone); 0187 } 0188 0189 QTimeZone Calendar::timeZone() const 0190 { 0191 return d->mTimeZone; 0192 } 0193 0194 void Calendar::setTimeZoneId(const QByteArray &timeZoneId) 0195 { 0196 d->mTimeZone = d->timeZoneIdSpec(timeZoneId); 0197 0198 doSetTimeZone(d->mTimeZone); // NOLINT false clang-analyzer-optin.cplusplus.VirtualCall 0199 } 0200 0201 //@cond PRIVATE 0202 QTimeZone Calendar::Private::timeZoneIdSpec(const QByteArray &timeZoneId) 0203 { 0204 if (timeZoneId == QByteArrayLiteral("UTC")) { 0205 return QTimeZone::utc(); 0206 } 0207 auto tz = QTimeZone(timeZoneId); 0208 if (tz.isValid()) { 0209 return tz; 0210 } 0211 return QTimeZone::systemTimeZone(); 0212 } 0213 //@endcond 0214 0215 QByteArray Calendar::timeZoneId() const 0216 { 0217 return d->mTimeZone.id(); 0218 } 0219 0220 void Calendar::shiftTimes(const QTimeZone &oldZone, const QTimeZone &newZone) 0221 { 0222 setTimeZone(newZone); 0223 0224 int i; 0225 int end; 0226 Event::List ev = events(); 0227 for (i = 0, end = ev.count(); i < end; ++i) { 0228 ev[i]->shiftTimes(oldZone, newZone); 0229 } 0230 0231 Todo::List to = todos(); 0232 for (i = 0, end = to.count(); i < end; ++i) { 0233 to[i]->shiftTimes(oldZone, newZone); 0234 } 0235 0236 Journal::List jo = journals(); 0237 for (i = 0, end = jo.count(); i < end; ++i) { 0238 jo[i]->shiftTimes(oldZone, newZone); 0239 } 0240 } 0241 0242 void Calendar::setFilter(CalFilter *filter) 0243 { 0244 if (filter) { 0245 d->mFilter = filter; 0246 } else { 0247 d->mFilter = d->mDefaultFilter; 0248 } 0249 Q_EMIT filterChanged(); 0250 } 0251 0252 CalFilter *Calendar::filter() const 0253 { 0254 return d->mFilter; 0255 } 0256 0257 QStringList Calendar::categories() const 0258 { 0259 const Incidence::List rawInc = rawIncidences(); 0260 QStringList uniqueCategories; 0261 QStringList thisCats; 0262 // @TODO: For now just iterate over all incidences. In the future, 0263 // the list of categories should be built when reading the file. 0264 for (const Incidence::Ptr &inc : rawInc) { 0265 thisCats = inc->categories(); 0266 for (const auto &cat : std::as_const(thisCats)) { 0267 if (!uniqueCategories.contains(cat)) { 0268 uniqueCategories.append(cat); 0269 } 0270 } 0271 } 0272 return uniqueCategories; 0273 } 0274 0275 Incidence::List Calendar::incidences(const QDate &date) const 0276 { 0277 return mergeIncidenceList(events(date), todos(date), journals(date)); 0278 } 0279 0280 Incidence::List Calendar::incidences() const 0281 { 0282 return mergeIncidenceList(events(), todos(), journals()); 0283 } 0284 0285 Incidence::List Calendar::rawIncidences() const 0286 { 0287 return mergeIncidenceList(rawEvents(), rawTodos(), rawJournals()); 0288 } 0289 0290 Incidence::List Calendar::instances(const Incidence::Ptr &incidence) const 0291 { 0292 if (incidence) { 0293 Event::List elist; 0294 Todo::List tlist; 0295 Journal::List jlist; 0296 0297 if (incidence->type() == Incidence::TypeEvent) { 0298 elist = eventInstances(incidence); 0299 } else if (incidence->type() == Incidence::TypeTodo) { 0300 tlist = todoInstances(incidence); 0301 } else if (incidence->type() == Incidence::TypeJournal) { 0302 jlist = journalInstances(incidence); 0303 } 0304 return mergeIncidenceList(elist, tlist, jlist); 0305 } else { 0306 return Incidence::List(); 0307 } 0308 } 0309 0310 Incidence::List Calendar::duplicates(const Incidence::Ptr &incidence) 0311 { 0312 if (!incidence) { 0313 return {}; 0314 } 0315 0316 Incidence::List list; 0317 const Incidence::List vals = values(d->mNotebookIncidences); 0318 std::copy_if(vals.cbegin(), vals.cend(), std::back_inserter(list), [&](const Incidence::Ptr &in) { 0319 return (incidence->dtStart() == in->dtStart() || (!incidence->dtStart().isValid() && !in->dtStart().isValid())) 0320 && incidence->summary() == in->summary(); 0321 }); 0322 return list; 0323 } 0324 0325 bool Calendar::addNotebook(const QString ¬ebook, bool isVisible) 0326 { 0327 if (d->mNotebooks.contains(notebook)) { 0328 return false; 0329 } else { 0330 d->mNotebooks.insert(notebook, isVisible); 0331 return true; 0332 } 0333 } 0334 0335 bool Calendar::updateNotebook(const QString ¬ebook, bool isVisible) 0336 { 0337 if (!d->mNotebooks.contains(notebook)) { 0338 return false; 0339 } else { 0340 d->mNotebooks.insert(notebook, isVisible); 0341 for (auto noteIt = d->mNotebookIncidences.cbegin(); noteIt != d->mNotebookIncidences.cend(); ++noteIt) { 0342 const Incidence::Ptr &incidence = noteIt.value(); 0343 auto visibleIt = d->mIncidenceVisibility.find(incidence); 0344 if (visibleIt != d->mIncidenceVisibility.end()) { 0345 *visibleIt = isVisible; 0346 } 0347 } 0348 return true; 0349 } 0350 } 0351 0352 bool Calendar::deleteNotebook(const QString ¬ebook) 0353 { 0354 if (!d->mNotebooks.contains(notebook)) { 0355 return false; 0356 } else { 0357 return d->mNotebooks.remove(notebook); 0358 } 0359 } 0360 0361 bool Calendar::setDefaultNotebook(const QString ¬ebook) 0362 { 0363 if (!d->mNotebooks.contains(notebook)) { 0364 return false; 0365 } else { 0366 d->mDefaultNotebook = notebook; 0367 return true; 0368 } 0369 } 0370 0371 QString Calendar::defaultNotebook() const 0372 { 0373 return d->mDefaultNotebook; 0374 } 0375 0376 bool Calendar::hasValidNotebook(const QString ¬ebook) const 0377 { 0378 return d->mNotebooks.contains(notebook); 0379 } 0380 0381 bool Calendar::isVisible(const Incidence::Ptr &incidence) const 0382 { 0383 if (d->mIncidenceVisibility.contains(incidence)) { 0384 return d->mIncidenceVisibility[incidence]; 0385 } 0386 const QString nuid = notebook(incidence); 0387 bool rv; 0388 if (d->mNotebooks.contains(nuid)) { 0389 rv = d->mNotebooks.value(nuid); 0390 } else { 0391 // NOTE returns true also for nonexisting notebooks for compatibility 0392 rv = true; 0393 } 0394 d->mIncidenceVisibility[incidence] = rv; 0395 return rv; 0396 } 0397 0398 bool Calendar::isVisible(const QString ¬ebook) const 0399 { 0400 QHash<QString, bool>::ConstIterator it = d->mNotebooks.constFind(notebook); 0401 return (it != d->mNotebooks.constEnd()) ? *it : true; 0402 } 0403 0404 void Calendar::clearNotebookAssociations() 0405 { 0406 d->mNotebookIncidences.clear(); 0407 d->mUidToNotebook.clear(); 0408 d->mIncidenceVisibility.clear(); 0409 } 0410 0411 bool Calendar::setNotebook(const Incidence::Ptr &inc, const QString ¬ebook) 0412 { 0413 if (!inc) { 0414 return false; 0415 } 0416 0417 if (!notebook.isEmpty() && !incidence(inc->uid(), inc->recurrenceId())) { 0418 qCWarning(KCALCORE_LOG) << "cannot set notebook until incidence has been added"; 0419 return false; 0420 } 0421 0422 if (d->mUidToNotebook.contains(inc->uid())) { 0423 QString old = d->mUidToNotebook.value(inc->uid()); 0424 if (!old.isEmpty() && notebook != old) { 0425 if (inc->hasRecurrenceId()) { 0426 qCWarning(KCALCORE_LOG) << "cannot set notebook for child incidences"; 0427 return false; 0428 } 0429 // Move all possible children also. 0430 const Incidence::List list = instances(inc); 0431 for (const auto &incidence : list) { 0432 d->mNotebookIncidences.remove(old, incidence); 0433 d->mNotebookIncidences.insert(notebook, incidence); 0434 } 0435 notifyIncidenceChanged(inc); // for removing from old notebook 0436 // do not remove from mUidToNotebook to keep deleted incidences 0437 d->mNotebookIncidences.remove(old, inc); 0438 } 0439 } 0440 if (!notebook.isEmpty()) { 0441 d->mUidToNotebook.insert(inc->uid(), notebook); 0442 d->mNotebookIncidences.insert(notebook, inc); 0443 qCDebug(KCALCORE_LOG) << "setting notebook" << notebook << "for" << inc->uid(); 0444 notifyIncidenceChanged(inc); // for inserting into new notebook 0445 const Incidence::List list = instances(inc); 0446 for (const auto &incidence : list) { 0447 notifyIncidenceChanged(incidence); 0448 } 0449 } 0450 0451 return true; 0452 } 0453 0454 QString Calendar::notebook(const Incidence::Ptr &incidence) const 0455 { 0456 if (incidence) { 0457 return d->mUidToNotebook.value(incidence->uid()); 0458 } else { 0459 return QString(); 0460 } 0461 } 0462 0463 QString Calendar::notebook(const QString &uid) const 0464 { 0465 return d->mUidToNotebook.value(uid); 0466 } 0467 0468 QStringList Calendar::notebooks() const 0469 { 0470 return d->mNotebookIncidences.uniqueKeys(); 0471 } 0472 0473 Incidence::List Calendar::incidences(const QString ¬ebook) const 0474 { 0475 if (notebook.isEmpty()) { 0476 return values(d->mNotebookIncidences); 0477 } else { 0478 return values(d->mNotebookIncidences, notebook); 0479 } 0480 } 0481 0482 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 95) 0483 /** static */ 0484 Event::List Calendar::sortEvents(const Event::List &eventList, EventSortField sortField, SortDirection sortDirection) 0485 { 0486 Event::List eventListSorted(eventList); 0487 return sortEvents(std::move(eventListSorted), sortField, sortDirection); 0488 } 0489 #endif 0490 0491 Event::List Calendar::sortEvents(Event::List &&eventList, EventSortField sortField, SortDirection sortDirection) 0492 { 0493 switch (sortField) { 0494 case EventSortUnsorted: 0495 break; 0496 0497 case EventSortStartDate: 0498 if (sortDirection == SortDirectionAscending) { 0499 std::sort(eventList.begin(), eventList.end(), Events::startDateLessThan); 0500 } else { 0501 std::sort(eventList.begin(), eventList.end(), Events::startDateMoreThan); 0502 } 0503 break; 0504 0505 case EventSortEndDate: 0506 if (sortDirection == SortDirectionAscending) { 0507 std::sort(eventList.begin(), eventList.end(), Events::endDateLessThan); 0508 } else { 0509 std::sort(eventList.begin(), eventList.end(), Events::endDateMoreThan); 0510 } 0511 break; 0512 0513 case EventSortSummary: 0514 if (sortDirection == SortDirectionAscending) { 0515 std::sort(eventList.begin(), eventList.end(), Events::summaryLessThan); 0516 } else { 0517 std::sort(eventList.begin(), eventList.end(), Events::summaryMoreThan); 0518 } 0519 break; 0520 } 0521 0522 return eventList; 0523 } 0524 0525 Event::List Calendar::events(const QDate &date, const QTimeZone &timeZone, EventSortField sortField, SortDirection sortDirection) const 0526 { 0527 Event::List el = rawEventsForDate(date, timeZone, sortField, sortDirection); 0528 d->mFilter->apply(&el); 0529 return el; 0530 } 0531 0532 Event::List Calendar::events(const QDateTime &dt) const 0533 { 0534 Event::List el = rawEventsForDate(dt.date(), dt.timeZone()); 0535 d->mFilter->apply(&el); 0536 return el; 0537 } 0538 0539 Event::List Calendar::events(const QDate &start, const QDate &end, const QTimeZone &timeZone, bool inclusive) const 0540 { 0541 Event::List el = rawEvents(start, end, timeZone, inclusive); 0542 d->mFilter->apply(&el); 0543 return el; 0544 } 0545 0546 Event::List Calendar::events(EventSortField sortField, SortDirection sortDirection) const 0547 { 0548 Event::List el = rawEvents(sortField, sortDirection); 0549 d->mFilter->apply(&el); 0550 return el; 0551 } 0552 0553 bool Calendar::addIncidence(const Incidence::Ptr &incidence) 0554 { 0555 if (!incidence) { 0556 return false; 0557 } 0558 0559 AddVisitor<Calendar> v(this); 0560 return incidence->accept(v, incidence); 0561 } 0562 0563 bool Calendar::deleteIncidence(const Incidence::Ptr &incidence) 0564 { 0565 if (!incidence) { 0566 return false; 0567 } 0568 0569 if (beginChange(incidence)) { 0570 DeleteVisitor<Calendar> v(this); 0571 const bool result = incidence->accept(v, incidence); 0572 endChange(incidence); 0573 return result; 0574 } else { 0575 return false; 0576 } 0577 } 0578 0579 Incidence::Ptr Calendar::createException(const Incidence::Ptr &incidence, const QDateTime &recurrenceId, bool thisAndFuture) 0580 { 0581 Q_ASSERT(recurrenceId.isValid()); 0582 if (!incidence || !incidence->recurs() || !recurrenceId.isValid()) { 0583 return Incidence::Ptr(); 0584 } 0585 0586 Incidence::Ptr newInc(incidence->clone()); 0587 const QDateTime current = QDateTime::currentDateTimeUtc(); 0588 newInc->setCreated(current); 0589 newInc->setLastModified(current); 0590 newInc->setRevision(0); 0591 // Recurring exceptions are not support for now 0592 newInc->clearRecurrence(); 0593 0594 newInc->setRecurrenceId(recurrenceId); 0595 newInc->setThisAndFuture(thisAndFuture); 0596 newInc->setDtStart(recurrenceId); 0597 0598 // Calculate and set the new end of the incidence 0599 QDateTime end = incidence->dateTime(IncidenceBase::RoleEnd); 0600 0601 if (end.isValid()) { 0602 if (incidence->allDay()) { 0603 qint64 offset = incidence->dtStart().daysTo(recurrenceId); 0604 end = end.addDays(offset); 0605 } else { 0606 qint64 offset = incidence->dtStart().secsTo(recurrenceId); 0607 end = end.addSecs(offset); 0608 } 0609 newInc->setDateTime(end, IncidenceBase::RoleEnd); 0610 } 0611 return newInc; 0612 } 0613 0614 Incidence::Ptr Calendar::incidence(const QString &uid, const QDateTime &recurrenceId) const 0615 { 0616 Incidence::Ptr i = event(uid, recurrenceId); 0617 if (i) { 0618 return i; 0619 } 0620 0621 i = todo(uid, recurrenceId); 0622 if (i) { 0623 return i; 0624 } 0625 0626 i = journal(uid, recurrenceId); 0627 return i; 0628 } 0629 0630 Incidence::Ptr Calendar::deleted(const QString &uid, const QDateTime &recurrenceId) const 0631 { 0632 Incidence::Ptr i = deletedEvent(uid, recurrenceId); 0633 if (i) { 0634 return i; 0635 } 0636 0637 i = deletedTodo(uid, recurrenceId); 0638 if (i) { 0639 return i; 0640 } 0641 0642 i = deletedJournal(uid, recurrenceId); 0643 return i; 0644 } 0645 0646 Incidence::List Calendar::incidencesFromSchedulingID(const QString &sid) const 0647 { 0648 Incidence::List result; 0649 const Incidence::List incidences = rawIncidences(); 0650 std::copy_if(incidences.cbegin(), incidences.cend(), std::back_inserter(result), [&sid](const Incidence::Ptr &in) { 0651 return in->schedulingID() == sid; 0652 }); 0653 return result; 0654 } 0655 0656 Incidence::Ptr Calendar::incidenceFromSchedulingID(const QString &uid) const 0657 { 0658 const Incidence::List incidences = rawIncidences(); 0659 const auto itEnd = incidences.cend(); 0660 auto it = std::find_if(incidences.cbegin(), itEnd, [&uid](const Incidence::Ptr &in) { 0661 return in->schedulingID() == uid; 0662 }); 0663 0664 return it != itEnd ? *it : Incidence::Ptr(); 0665 } 0666 0667 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 95) 0668 /** static */ 0669 Todo::List Calendar::sortTodos(const Todo::List &todoList, TodoSortField sortField, SortDirection sortDirection) 0670 { 0671 Todo::List todoListSorted(todoList); 0672 return sortTodos(std::move(todoListSorted), sortField, sortDirection); 0673 } 0674 #endif 0675 0676 Todo::List Calendar::sortTodos(Todo::List &&todoList, TodoSortField sortField, SortDirection sortDirection) 0677 { 0678 // Note that To-dos may not have Start DateTimes nor due DateTimes. 0679 switch (sortField) { 0680 case TodoSortUnsorted: 0681 break; 0682 0683 case TodoSortStartDate: 0684 if (sortDirection == SortDirectionAscending) { 0685 std::sort(todoList.begin(), todoList.end(), Todos::startDateLessThan); 0686 } else { 0687 std::sort(todoList.begin(), todoList.end(), Todos::startDateMoreThan); 0688 } 0689 break; 0690 0691 case TodoSortDueDate: 0692 if (sortDirection == SortDirectionAscending) { 0693 std::sort(todoList.begin(), todoList.end(), Todos::dueDateLessThan); 0694 } else { 0695 std::sort(todoList.begin(), todoList.end(), Todos::dueDateMoreThan); 0696 } 0697 break; 0698 0699 case TodoSortPriority: 0700 if (sortDirection == SortDirectionAscending) { 0701 std::sort(todoList.begin(), todoList.end(), Todos::priorityLessThan); 0702 } else { 0703 std::sort(todoList.begin(), todoList.end(), Todos::priorityMoreThan); 0704 } 0705 break; 0706 0707 case TodoSortPercentComplete: 0708 if (sortDirection == SortDirectionAscending) { 0709 std::sort(todoList.begin(), todoList.end(), Todos::percentLessThan); 0710 } else { 0711 std::sort(todoList.begin(), todoList.end(), Todos::percentMoreThan); 0712 } 0713 break; 0714 0715 case TodoSortSummary: 0716 if (sortDirection == SortDirectionAscending) { 0717 std::sort(todoList.begin(), todoList.end(), Todos::summaryLessThan); 0718 } else { 0719 std::sort(todoList.begin(), todoList.end(), Todos::summaryMoreThan); 0720 } 0721 break; 0722 0723 case TodoSortCreated: 0724 if (sortDirection == SortDirectionAscending) { 0725 std::sort(todoList.begin(), todoList.end(), Todos::createdLessThan); 0726 } else { 0727 std::sort(todoList.begin(), todoList.end(), Todos::createdMoreThan); 0728 } 0729 break; 0730 0731 case TodoSortCategories: 0732 if (sortDirection == SortDirectionAscending) { 0733 std::sort(todoList.begin(), todoList.end(), Incidences::categoriesLessThan); 0734 } else { 0735 std::sort(todoList.begin(), todoList.end(), Incidences::categoriesMoreThan); 0736 } 0737 break; 0738 } 0739 0740 return todoList; 0741 } 0742 0743 Todo::List Calendar::todos(TodoSortField sortField, SortDirection sortDirection) const 0744 { 0745 Todo::List tl = rawTodos(sortField, sortDirection); 0746 d->mFilter->apply(&tl); 0747 return tl; 0748 } 0749 0750 Todo::List Calendar::todos(const QDate &date) const 0751 { 0752 Todo::List el = rawTodosForDate(date); 0753 d->mFilter->apply(&el); 0754 return el; 0755 } 0756 0757 Todo::List Calendar::todos(const QDate &start, const QDate &end, const QTimeZone &timeZone, bool inclusive) const 0758 { 0759 Todo::List tl = rawTodos(start, end, timeZone, inclusive); 0760 d->mFilter->apply(&tl); 0761 return tl; 0762 } 0763 0764 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 95) 0765 /** static */ 0766 Journal::List Calendar::sortJournals(const Journal::List &journalList, JournalSortField sortField, SortDirection sortDirection) 0767 { 0768 Journal::List journalListSorted = journalList; 0769 return sortJournals(std::move(journalListSorted), sortField, sortDirection); 0770 } 0771 #endif 0772 0773 Journal::List Calendar::sortJournals(Journal::List &&journalList, JournalSortField sortField, SortDirection sortDirection) 0774 { 0775 switch (sortField) { 0776 case JournalSortUnsorted: 0777 break; 0778 0779 case JournalSortDate: 0780 if (sortDirection == SortDirectionAscending) { 0781 std::sort(journalList.begin(), journalList.end(), Journals::dateLessThan); 0782 } else { 0783 std::sort(journalList.begin(), journalList.end(), Journals::dateMoreThan); 0784 } 0785 break; 0786 0787 case JournalSortSummary: 0788 if (sortDirection == SortDirectionAscending) { 0789 std::sort(journalList.begin(), journalList.end(), Journals::summaryLessThan); 0790 } else { 0791 std::sort(journalList.begin(), journalList.end(), Journals::summaryMoreThan); 0792 } 0793 break; 0794 } 0795 0796 return journalList; 0797 } 0798 0799 Journal::List Calendar::journals(JournalSortField sortField, SortDirection sortDirection) const 0800 { 0801 Journal::List jl = rawJournals(sortField, sortDirection); 0802 d->mFilter->apply(&jl); 0803 return jl; 0804 } 0805 0806 Journal::List Calendar::journals(const QDate &date) const 0807 { 0808 Journal::List el = rawJournalsForDate(date); 0809 d->mFilter->apply(&el); 0810 return el; 0811 } 0812 0813 // When this is called, the to-dos have already been added to the calendar. 0814 // This method is only about linking related to-dos. 0815 void Calendar::setupRelations(const Incidence::Ptr &forincidence) 0816 { 0817 if (!forincidence) { 0818 return; 0819 } 0820 0821 const QString uid = forincidence->uid(); 0822 0823 // First, go over the list of orphans and see if this is their parent 0824 Incidence::List l = values(d->mOrphans, uid); 0825 d->mOrphans.remove(uid); 0826 if (!l.isEmpty()) { 0827 Incidence::List &relations = d->mIncidenceRelations[uid]; 0828 relations.reserve(relations.count() + l.count()); 0829 for (int i = 0, end = l.count(); i < end; ++i) { 0830 relations.append(l[i]); 0831 d->mOrphanUids.remove(l[i]->uid()); 0832 } 0833 } 0834 0835 // Now see about this incidences parent 0836 if (!forincidence->relatedTo().isEmpty()) { 0837 // Incidence has a uid it is related to but is not registered to it yet. 0838 // Try to find it 0839 Incidence::Ptr parent = incidence(forincidence->relatedTo()); 0840 if (parent) { 0841 // Found it 0842 0843 // look for hierarchy loops 0844 if (isAncestorOf(forincidence, parent)) { 0845 forincidence->setRelatedTo(QString()); 0846 qCWarning(KCALCORE_LOG) << "hierarchy loop between " << forincidence->uid() << " and " << parent->uid(); 0847 } else { 0848 d->mIncidenceRelations[parent->uid()].append(forincidence); 0849 } 0850 } else { 0851 // Not found, put this in the mOrphans list 0852 // Note that the mOrphans dict might contain multiple entries with the 0853 // same key! which are multiple children that wait for the parent 0854 // incidence to be inserted. 0855 d->mOrphans.insert(forincidence->relatedTo(), forincidence); 0856 d->mOrphanUids.insert(forincidence->uid(), forincidence); 0857 } 0858 } 0859 } 0860 0861 // If a to-do with sub-to-dos is deleted, move it's sub-to-dos to the orphan list 0862 void Calendar::removeRelations(const Incidence::Ptr &incidence) 0863 { 0864 if (!incidence) { 0865 qCDebug(KCALCORE_LOG) << "Warning: incidence is 0"; 0866 return; 0867 } 0868 0869 const QString uid = incidence->uid(); 0870 0871 for (const Incidence::Ptr &i : std::as_const(d->mIncidenceRelations[uid])) { 0872 if (!d->mOrphanUids.contains(i->uid())) { 0873 d->mOrphans.insert(uid, i); 0874 d->mOrphanUids.insert(i->uid(), i); 0875 i->setRelatedTo(uid); 0876 } 0877 } 0878 0879 const QString parentUid = incidence->relatedTo(); 0880 0881 // If this incidence is related to something else, tell that about it 0882 if (!parentUid.isEmpty()) { 0883 Incidence::List &relations = d->mIncidenceRelations[parentUid]; 0884 relations.erase(std::remove(relations.begin(), relations.end(), incidence), relations.end()); 0885 } 0886 0887 // Remove this one from the orphans list 0888 if (d->mOrphanUids.remove(uid)) { 0889 // This incidence is located in the orphans list - it should be removed 0890 // Since the mOrphans dict might contain the same key (with different 0891 // child incidence pointers!) multiple times, take care that we remove 0892 // the correct one. So we need to remove all items with the given 0893 // parent UID, and re-add those that are not for this item. Also, there 0894 // might be other entries with different UID that point to this 0895 // incidence (this might happen when the relatedTo of the item is 0896 // changed before its parent is inserted. This might happen with 0897 // groupware servers....). Remove them, too 0898 QStringList relatedToUids; 0899 0900 // First, create a list of all keys in the mOrphans list which point 0901 // to the removed item 0902 relatedToUids << incidence->relatedTo(); 0903 for (auto it = d->mOrphans.cbegin(); it != d->mOrphans.cend(); ++it) { 0904 if (it.value()->uid() == uid) { 0905 relatedToUids << it.key(); 0906 } 0907 } 0908 0909 // now go through all uids that have one entry that point to the incidence 0910 for (const auto &relUid : std::as_const(relatedToUids)) { 0911 // Remove all to get access to the remaining entries 0912 Incidence::List lst = values(d->mOrphans, relUid); 0913 d->mOrphans.remove(relUid); 0914 lst.erase(std::remove(lst.begin(), lst.end(), incidence), lst.end()); 0915 0916 // Re-add those that point to a different orphan incidence 0917 for (const auto &in : std::as_const(lst)) { 0918 d->mOrphans.insert(relUid, in); 0919 } 0920 } 0921 } 0922 0923 // Make sure the deleted incidence doesn't relate to a non-deleted incidence, 0924 // since that would cause trouble in MemoryCalendar::close(), as the deleted 0925 // incidences are destroyed after the non-deleted incidences. The destructor 0926 // of the deleted incidences would then try to access the already destroyed 0927 // non-deleted incidence, which would segfault. 0928 // 0929 // So in short: Make sure dead incidences don't point to alive incidences 0930 // via the relation. 0931 // 0932 // This crash is tested in MemoryCalendarTest::testRelationsCrash(). 0933 // incidence->setRelatedTo( Incidence::Ptr() ); 0934 } 0935 0936 bool Calendar::isAncestorOf(const Incidence::Ptr &ancestor, const Incidence::Ptr &incidence) const 0937 { 0938 if (!incidence || incidence->relatedTo().isEmpty()) { 0939 return false; 0940 } else if (incidence->relatedTo() == ancestor->uid()) { 0941 return true; 0942 } else { 0943 return isAncestorOf(ancestor, this->incidence(incidence->relatedTo())); 0944 } 0945 } 0946 0947 Incidence::List Calendar::relations(const QString &uid) const 0948 { 0949 return d->mIncidenceRelations[uid]; 0950 } 0951 0952 Calendar::CalendarObserver::~CalendarObserver() 0953 { 0954 } 0955 0956 void Calendar::CalendarObserver::calendarModified(bool modified, Calendar *calendar) 0957 { 0958 Q_UNUSED(modified); 0959 Q_UNUSED(calendar); 0960 } 0961 0962 void Calendar::CalendarObserver::calendarIncidenceAdded(const Incidence::Ptr &incidence) 0963 { 0964 Q_UNUSED(incidence); 0965 } 0966 0967 void Calendar::CalendarObserver::calendarIncidenceChanged(const Incidence::Ptr &incidence) 0968 { 0969 Q_UNUSED(incidence); 0970 } 0971 0972 void Calendar::CalendarObserver::calendarIncidenceAboutToBeDeleted(const Incidence::Ptr &incidence) 0973 { 0974 Q_UNUSED(incidence); 0975 } 0976 0977 void Calendar::CalendarObserver::calendarIncidenceDeleted(const Incidence::Ptr &incidence, const Calendar *calendar) 0978 { 0979 Q_UNUSED(incidence); 0980 Q_UNUSED(calendar); 0981 } 0982 0983 void Calendar::CalendarObserver::calendarIncidenceAdditionCanceled(const Incidence::Ptr &incidence) 0984 { 0985 Q_UNUSED(incidence); 0986 } 0987 0988 void Calendar::registerObserver(CalendarObserver *observer) 0989 { 0990 if (!observer) { 0991 return; 0992 } 0993 0994 if (!d->mObservers.contains(observer)) { 0995 d->mObservers.append(observer); 0996 } else { 0997 d->mNewObserver = true; 0998 } 0999 } 1000 1001 void Calendar::unregisterObserver(CalendarObserver *observer) 1002 { 1003 if (!observer) { 1004 return; 1005 } else { 1006 d->mObservers.removeAll(observer); 1007 } 1008 } 1009 1010 bool Calendar::isSaving() const 1011 { 1012 return false; 1013 } 1014 1015 void Calendar::setModified(bool modified) 1016 { 1017 if (modified != d->mModified || d->mNewObserver) { 1018 d->mNewObserver = false; 1019 for (CalendarObserver *observer : std::as_const(d->mObservers)) { 1020 observer->calendarModified(modified, this); 1021 } 1022 d->mModified = modified; 1023 } 1024 } 1025 1026 bool Calendar::isModified() const 1027 { 1028 return d->mModified; 1029 } 1030 1031 bool Calendar::save() 1032 { 1033 return true; 1034 } 1035 1036 bool Calendar::reload() 1037 { 1038 return true; 1039 } 1040 1041 void Calendar::incidenceUpdated(const QString &uid, const QDateTime &recurrenceId) 1042 { 1043 Incidence::Ptr inc = incidence(uid, recurrenceId); 1044 1045 if (!inc) { 1046 return; 1047 } 1048 1049 inc->setLastModified(QDateTime::currentDateTimeUtc()); 1050 // we should probably update the revision number here, 1051 // or internally in the Event itself when certain things change. 1052 // need to verify with ical documentation. 1053 1054 notifyIncidenceChanged(inc); 1055 1056 setModified(true); 1057 } 1058 1059 void Calendar::doSetTimeZone(const QTimeZone &timeZone) 1060 { 1061 Q_UNUSED(timeZone); 1062 } 1063 1064 void Calendar::notifyIncidenceAdded(const Incidence::Ptr &incidence) 1065 { 1066 if (!incidence) { 1067 return; 1068 } 1069 1070 if (!d->mObserversEnabled) { 1071 return; 1072 } 1073 1074 for (CalendarObserver *observer : std::as_const(d->mObservers)) { 1075 observer->calendarIncidenceAdded(incidence); 1076 } 1077 1078 for (auto role : {IncidenceBase::RoleStartTimeZone, IncidenceBase::RoleEndTimeZone}) { 1079 const auto dt = incidence->dateTime(role); 1080 if (dt.isValid() && dt.timeZone() != QTimeZone::utc()) { 1081 if (!d->mTimeZones.contains(dt.timeZone())) { 1082 d->mTimeZones.push_back(dt.timeZone()); 1083 } 1084 } 1085 } 1086 } 1087 1088 void Calendar::notifyIncidenceChanged(const Incidence::Ptr &incidence) 1089 { 1090 if (!incidence) { 1091 return; 1092 } 1093 1094 if (!d->mObserversEnabled) { 1095 return; 1096 } 1097 1098 for (CalendarObserver *observer : std::as_const(d->mObservers)) { 1099 observer->calendarIncidenceChanged(incidence); 1100 } 1101 } 1102 1103 void Calendar::notifyIncidenceAboutToBeDeleted(const Incidence::Ptr &incidence) 1104 { 1105 if (!incidence) { 1106 return; 1107 } 1108 1109 if (!d->mObserversEnabled) { 1110 return; 1111 } 1112 1113 for (CalendarObserver *observer : std::as_const(d->mObservers)) { 1114 observer->calendarIncidenceAboutToBeDeleted(incidence); 1115 } 1116 } 1117 1118 void Calendar::notifyIncidenceDeleted(const Incidence::Ptr &incidence) 1119 { 1120 if (!incidence) { 1121 return; 1122 } 1123 1124 if (!d->mObserversEnabled) { 1125 return; 1126 } 1127 1128 for (CalendarObserver *observer : std::as_const(d->mObservers)) { 1129 observer->calendarIncidenceDeleted(incidence, this); 1130 } 1131 } 1132 1133 void Calendar::notifyIncidenceAdditionCanceled(const Incidence::Ptr &incidence) 1134 { 1135 if (!incidence) { 1136 return; 1137 } 1138 1139 if (!d->mObserversEnabled) { 1140 return; 1141 } 1142 1143 for (CalendarObserver *observer : std::as_const(d->mObservers)) { 1144 observer->calendarIncidenceAdditionCanceled(incidence); 1145 } 1146 } 1147 1148 void Calendar::customPropertyUpdated() 1149 { 1150 setModified(true); 1151 } 1152 1153 void Calendar::setProductId(const QString &id) 1154 { 1155 d->mProductId = id; 1156 } 1157 1158 QString Calendar::productId() const 1159 { 1160 return d->mProductId; 1161 } 1162 1163 /** static */ 1164 Incidence::List Calendar::mergeIncidenceList(const Event::List &events, const Todo::List &todos, const Journal::List &journals) 1165 { 1166 Incidence::List incidences; 1167 incidences.reserve(events.count() + todos.count() + journals.count()); 1168 1169 int i; 1170 int end; 1171 for (i = 0, end = events.count(); i < end; ++i) { 1172 incidences.append(events[i]); 1173 } 1174 1175 for (i = 0, end = todos.count(); i < end; ++i) { 1176 incidences.append(todos[i]); 1177 } 1178 1179 for (i = 0, end = journals.count(); i < end; ++i) { 1180 incidences.append(journals[i]); 1181 } 1182 1183 return incidences; 1184 } 1185 1186 bool Calendar::beginChange(const Incidence::Ptr &incidence) 1187 { 1188 Q_UNUSED(incidence); 1189 return true; 1190 } 1191 1192 bool Calendar::endChange(const Incidence::Ptr &incidence) 1193 { 1194 Q_UNUSED(incidence); 1195 return true; 1196 } 1197 1198 void Calendar::setObserversEnabled(bool enabled) 1199 { 1200 d->mObserversEnabled = enabled; 1201 } 1202 1203 void Calendar::appendAlarms(Alarm::List &alarms, const Incidence::Ptr &incidence, const QDateTime &from, const QDateTime &to) const 1204 { 1205 QDateTime preTime = from.addSecs(-1); 1206 1207 Alarm::List alarmlist = incidence->alarms(); 1208 for (int i = 0, iend = alarmlist.count(); i < iend; ++i) { 1209 if (alarmlist[i]->enabled()) { 1210 QDateTime dt = alarmlist[i]->nextRepetition(preTime); 1211 if (dt.isValid() && dt <= to) { 1212 qCDebug(KCALCORE_LOG) << incidence->summary() << "':" << dt.toString(); 1213 alarms.append(alarmlist[i]); 1214 } 1215 } 1216 } 1217 } 1218 1219 void Calendar::appendRecurringAlarms(Alarm::List &alarms, const Incidence::Ptr &incidence, const QDateTime &from, const QDateTime &to) const 1220 { 1221 QDateTime dt; 1222 bool endOffsetValid = false; 1223 Duration endOffset(0); 1224 Duration period(from, to); 1225 1226 Alarm::List alarmlist = incidence->alarms(); 1227 for (int i = 0, iend = alarmlist.count(); i < iend; ++i) { 1228 Alarm::Ptr a = alarmlist[i]; 1229 if (a->enabled()) { 1230 if (a->hasTime()) { 1231 // The alarm time is defined as an absolute date/time 1232 dt = a->nextRepetition(from.addSecs(-1)); 1233 if (!dt.isValid() || dt > to) { 1234 continue; 1235 } 1236 } else { 1237 // Alarm time is defined by an offset from the event start or end time. 1238 // Find the offset from the event start time, which is also used as the 1239 // offset from the recurrence time. 1240 Duration offset(0); 1241 if (a->hasStartOffset()) { 1242 offset = a->startOffset(); 1243 } else if (a->hasEndOffset()) { 1244 offset = a->endOffset(); 1245 if (!endOffsetValid) { 1246 endOffset = Duration(incidence->dtStart(), incidence->dateTime(Incidence::RoleAlarmEndOffset)); 1247 endOffsetValid = true; 1248 } 1249 } 1250 1251 // Find the incidence's earliest alarm 1252 QDateTime alarmStart = offset.end(a->hasEndOffset() ? incidence->dateTime(Incidence::RoleAlarmEndOffset) : incidence->dtStart()); 1253 if (alarmStart > to) { 1254 continue; 1255 } 1256 QDateTime baseStart = incidence->dtStart(); 1257 if (from > alarmStart) { 1258 alarmStart = from; // don't look earlier than the earliest alarm 1259 baseStart = (-offset).end((-endOffset).end(alarmStart)); 1260 } 1261 1262 // Adjust the 'alarmStart' date/time and find the next recurrence at or after it. 1263 // Treat the two offsets separately in case one is daily and the other not. 1264 dt = incidence->recurrence()->getNextDateTime(baseStart.addSecs(-1)); 1265 if (!dt.isValid() || (dt = endOffset.end(offset.end(dt))) > to) { // adjust 'dt' to get the alarm time 1266 // The next recurrence is too late. 1267 if (!a->repeatCount()) { 1268 continue; 1269 } 1270 1271 // The alarm has repetitions, so check whether repetitions of previous 1272 // recurrences fall within the time period. 1273 bool found = false; 1274 Duration alarmDuration = a->duration(); 1275 for (QDateTime base = baseStart; (dt = incidence->recurrence()->getPreviousDateTime(base)).isValid(); base = dt) { 1276 if (a->duration().end(dt) < base) { 1277 break; // this recurrence's last repetition is too early, so give up 1278 } 1279 1280 // The last repetition of this recurrence is at or after 'alarmStart' time. 1281 // Check if a repetition occurs between 'alarmStart' and 'to'. 1282 int snooze = a->snoozeTime().value(); // in seconds or days 1283 if (a->snoozeTime().isDaily()) { 1284 Duration toFromDuration(dt, base); 1285 int toFrom = toFromDuration.asDays(); 1286 if (a->snoozeTime().end(from) <= to || (toFromDuration.isDaily() && toFrom % snooze == 0) 1287 || (toFrom / snooze + 1) * snooze <= toFrom + period.asDays()) { 1288 found = true; 1289 #ifndef NDEBUG 1290 // for debug output 1291 dt = offset.end(dt).addDays(((toFrom - 1) / snooze + 1) * snooze); 1292 #endif 1293 break; 1294 } 1295 } else { 1296 int toFrom = dt.secsTo(base); 1297 if (period.asSeconds() >= snooze || toFrom % snooze == 0 || (toFrom / snooze + 1) * snooze <= toFrom + period.asSeconds()) { 1298 found = true; 1299 #ifndef NDEBUG 1300 // for debug output 1301 dt = offset.end(dt).addSecs(((toFrom - 1) / snooze + 1) * snooze); 1302 #endif 1303 break; 1304 } 1305 } 1306 } 1307 if (!found) { 1308 continue; 1309 } 1310 } 1311 } 1312 qCDebug(KCALCORE_LOG) << incidence->summary() << "':" << dt.toString(); 1313 alarms.append(a); 1314 } 1315 } 1316 } 1317 1318 void Calendar::startBatchAdding() 1319 { 1320 d->batchAddingInProgress = true; 1321 } 1322 1323 void Calendar::endBatchAdding() 1324 { 1325 d->batchAddingInProgress = false; 1326 } 1327 1328 bool Calendar::batchAdding() const 1329 { 1330 return d->batchAddingInProgress; 1331 } 1332 1333 void Calendar::setDeletionTracking(bool enable) 1334 { 1335 d->mDeletionTracking = enable; 1336 } 1337 1338 bool Calendar::deletionTracking() const 1339 { 1340 return d->mDeletionTracking; 1341 } 1342 1343 Alarm::List Calendar::alarmsTo(const QDateTime &to) const 1344 { 1345 return alarms(QDateTime(QDate(1900, 1, 1), QTime(0, 0, 0)), to); 1346 } 1347 1348 void Calendar::virtual_hook(int id, void *data) 1349 { 1350 Q_UNUSED(id); 1351 Q_UNUSED(data); 1352 Q_ASSERT(false); 1353 } 1354 1355 QString Calendar::id() const 1356 { 1357 return d->mId; 1358 } 1359 1360 void Calendar::setId(const QString &id) 1361 { 1362 if (d->mId != id) { 1363 d->mId = id; 1364 Q_EMIT idChanged(); 1365 } 1366 } 1367 1368 QString Calendar::name() const 1369 { 1370 return d->mName; 1371 } 1372 1373 void Calendar::setName(const QString &name) 1374 { 1375 if (d->mName != name) { 1376 d->mName = name; 1377 Q_EMIT nameChanged(); 1378 } 1379 } 1380 1381 QIcon Calendar::icon() const 1382 { 1383 return d->mIcon; 1384 } 1385 1386 void Calendar::setIcon(const QIcon &icon) 1387 { 1388 d->mIcon = icon; 1389 Q_EMIT iconChanged(); 1390 } 1391 1392 AccessMode Calendar::accessMode() const 1393 { 1394 return d->mAccessMode; 1395 } 1396 1397 void Calendar::setAccessMode(const AccessMode mode) 1398 { 1399 if (d->mAccessMode != mode) { 1400 d->mAccessMode = mode; 1401 Q_EMIT accessModeChanged(); 1402 } 1403 } 1404 1405 bool Calendar::isLoading() const 1406 { 1407 return d->mIsLoading; 1408 } 1409 1410 void Calendar::setIsLoading(bool isLoading) 1411 { 1412 if (d->mIsLoading == isLoading) { 1413 return; 1414 } 1415 1416 d->mIsLoading = isLoading; 1417 Q_EMIT isLoadingChanged(); 1418 } 1419 1420 #include "moc_calendar.cpp"