File indexing completed on 2024-04-28 15:19:06
0001 /* 0002 This file is part of the kcalcore library. 0003 0004 SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org> 0005 SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 /** 0010 @file 0011 This file is part of the API for handling calendar data and 0012 defines the Incidence class. 0013 0014 @brief 0015 Provides the class common to non-FreeBusy (Events, To-dos, Journals) 0016 calendar components known as incidences. 0017 0018 @author Cornelius Schumacher \<schumacher@kde.org\> 0019 @author Reinhold Kainhofer \<reinhold@kainhofer.com\> 0020 */ 0021 0022 #include "incidence.h" 0023 #include "incidence_p.h" 0024 #include "calformat.h" 0025 #include "kcalendarcore_debug.h" 0026 #include "utils_p.h" 0027 0028 #include <math.h> 0029 0030 #include <QStringList> 0031 #include <QTextDocument> // for .toHtmlEscaped() and Qt::mightBeRichText() 0032 #include <QTime> 0033 #include <QTimeZone> 0034 0035 using namespace KCalendarCore; 0036 0037 IncidencePrivate::IncidencePrivate() = default; 0038 0039 IncidencePrivate::IncidencePrivate(const IncidencePrivate &p) 0040 : IncidenceBasePrivate(p) 0041 , mCreated(p.mCreated) 0042 , mDescription(p.mDescription) 0043 , mSummary(p.mSummary) 0044 , mLocation(p.mLocation) 0045 , mCategories(p.mCategories) 0046 , mResources(p.mResources) 0047 , mStatusString(p.mStatusString) 0048 , mSchedulingID(p.mSchedulingID) 0049 , mRelatedToUid(p.mRelatedToUid) 0050 , mRecurrenceId(p.mRecurrenceId) 0051 , mConferences(p.mConferences) 0052 , mGeoLatitude(p.mGeoLatitude) 0053 , mGeoLongitude(p.mGeoLongitude) 0054 , mRecurrence(nullptr) 0055 , mRevision(p.mRevision) 0056 , mPriority(p.mPriority) 0057 , mStatus(p.mStatus) 0058 , mSecrecy(p.mSecrecy) 0059 , mColor(p.mColor) 0060 , mDescriptionIsRich(p.mDescriptionIsRich) 0061 , mSummaryIsRich(p.mSummaryIsRich) 0062 , mLocationIsRich(p.mLocationIsRich) 0063 , mThisAndFuture(p.mThisAndFuture) 0064 , mLocalOnly(false) 0065 { 0066 } 0067 0068 IncidencePrivate::IncidencePrivate(const Incidence &other) 0069 : IncidencePrivate(*other.d_func()) 0070 { 0071 } 0072 0073 void IncidencePrivate::clear() 0074 { 0075 mAlarms.clear(); 0076 mAttachments.clear(); 0077 delete mRecurrence; 0078 mRecurrence = nullptr; 0079 } 0080 0081 void IncidencePrivate::init(Incidence *q, const IncidencePrivate &other) 0082 { 0083 mRevision = other.mRevision; 0084 mCreated = other.mCreated; 0085 mDescription = other.mDescription; 0086 mDescriptionIsRich = other.mDescriptionIsRich; 0087 mSummary = other.mSummary; 0088 mSummaryIsRich = other.mSummaryIsRich; 0089 mCategories = other.mCategories; 0090 mRelatedToUid = other.mRelatedToUid; 0091 mResources = other.mResources; 0092 mStatusString = other.mStatusString; 0093 mStatus = other.mStatus; 0094 mSecrecy = other.mSecrecy; 0095 mPriority = other.mPriority; 0096 mLocation = other.mLocation; 0097 mLocationIsRich = other.mLocationIsRich; 0098 mGeoLatitude = other.mGeoLatitude; 0099 mGeoLongitude = other.mGeoLongitude; 0100 mRecurrenceId = other.mRecurrenceId; 0101 mConferences = other.mConferences; 0102 mThisAndFuture = other.mThisAndFuture; 0103 mLocalOnly = other.mLocalOnly; 0104 mColor = other.mColor; 0105 0106 // Alarms and Attachments are stored in ListBase<...>, which is a QValueList<...*>. 0107 // We need to really duplicate the objects stored therein, otherwise deleting 0108 // i will also delete all attachments from this object (setAutoDelete...) 0109 mAlarms.reserve(other.mAlarms.count()); 0110 for (const Alarm::Ptr &alarm : std::as_const(other.mAlarms)) { 0111 Alarm::Ptr b(new Alarm(*alarm.data())); 0112 b->setParent(q); 0113 mAlarms.append(b); 0114 } 0115 0116 mAttachments = other.mAttachments; 0117 if (other.mRecurrence) { 0118 mRecurrence = new Recurrence(*(other.mRecurrence)); 0119 mRecurrence->addObserver(q); 0120 } else { 0121 mRecurrence = nullptr; 0122 } 0123 } 0124 0125 bool IncidencePrivate::validStatus(Incidence::Status status) 0126 { 0127 return status == Incidence::StatusNone; 0128 } 0129 //@endcond 0130 0131 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 91) 0132 Incidence::Incidence() 0133 : IncidenceBase(new IncidencePrivate) 0134 { 0135 recreate(); 0136 resetDirtyFields(); 0137 } 0138 0139 Incidence::Incidence(const Incidence &i) 0140 : IncidenceBase(i, new IncidencePrivate(*(i.d_func()))) 0141 , Recurrence::RecurrenceObserver() 0142 { 0143 Q_D(Incidence); 0144 d->init(this, *(i.d_func())); 0145 resetDirtyFields(); 0146 } 0147 #endif 0148 0149 Incidence::Incidence(IncidencePrivate *p) 0150 : IncidenceBase(p) 0151 { 0152 recreate(); 0153 resetDirtyFields(); 0154 } 0155 0156 Incidence::Incidence(const Incidence &other, IncidencePrivate *p) 0157 : IncidenceBase(other, p) 0158 , Recurrence::RecurrenceObserver() 0159 { 0160 Q_D(Incidence); 0161 d->init(this, *(other.d_func())); 0162 resetDirtyFields(); 0163 } 0164 0165 Incidence::~Incidence() 0166 { 0167 // Alarm has a raw incidence pointer, so we must set it to 0 0168 // so Alarm doesn't use it after Incidence is destroyed 0169 Q_D(const Incidence); 0170 for (const Alarm::Ptr &alarm : std::as_const(d->mAlarms)) { 0171 alarm->setParent(nullptr); 0172 } 0173 delete d->mRecurrence; 0174 } 0175 0176 //@cond PRIVATE 0177 // A string comparison that considers that null and empty are the same 0178 static bool stringCompare(const QString &s1, const QString &s2) 0179 { 0180 return (s1.isEmpty() && s2.isEmpty()) || (s1 == s2); 0181 } 0182 0183 //@endcond 0184 IncidenceBase &Incidence::assign(const IncidenceBase &other) 0185 { 0186 Q_D(Incidence); 0187 if (&other != this) { 0188 d->clear(); 0189 // TODO: should relations be cleared out, as in destructor??? 0190 IncidenceBase::assign(other); 0191 const Incidence *i = static_cast<const Incidence *>(&other); 0192 d->init(this, *(i->d_func())); 0193 } 0194 0195 return *this; 0196 } 0197 0198 bool Incidence::equals(const IncidenceBase &incidence) const 0199 { 0200 if (!IncidenceBase::equals(incidence)) { 0201 return false; 0202 } 0203 0204 // If they weren't the same type IncidenceBase::equals would had returned false already 0205 const Incidence *i2 = static_cast<const Incidence *>(&incidence); 0206 0207 const Alarm::List alarmList = alarms(); 0208 const Alarm::List otherAlarmsList = i2->alarms(); 0209 if (alarmList.count() != otherAlarmsList.count()) { 0210 return false; 0211 } 0212 0213 auto matchFunc = [](const Alarm::Ptr &a, const Alarm::Ptr &b) { 0214 return *a == *b; 0215 }; 0216 0217 const auto [it1, it2] = std::mismatch(alarmList.cbegin(), alarmList.cend(), otherAlarmsList.cbegin(), otherAlarmsList.cend(), matchFunc); 0218 // Checking the iterator from one list only, since both lists are the same size 0219 if (it1 != alarmList.cend()) { 0220 return false; 0221 } 0222 0223 const Attachment::List attachmentList = attachments(); 0224 const Attachment::List otherAttachmentList = i2->attachments(); 0225 if (attachmentList.count() != otherAttachmentList.count()) { 0226 return false; 0227 } 0228 0229 const auto [at1, at2] = std::mismatch(attachmentList.cbegin(), attachmentList.cend(), otherAttachmentList.cbegin(), otherAttachmentList.cend()); 0230 0231 // Checking the iterator from one list only, since both lists are the same size 0232 if (at1 != attachmentList.cend()) { 0233 return false; 0234 } 0235 0236 Q_D(const Incidence); 0237 bool recurrenceEqual = (d->mRecurrence == nullptr && i2->d_func()->mRecurrence == nullptr); 0238 if (!recurrenceEqual) { 0239 recurrence(); // create if doesn't exist 0240 i2->recurrence(); // create if doesn't exist 0241 recurrenceEqual = d->mRecurrence != nullptr && i2->d_func()->mRecurrence != nullptr 0242 && *d->mRecurrence == *i2->d_func()->mRecurrence; 0243 } 0244 0245 if (!qFuzzyCompare(d->mGeoLatitude, i2->d_func()->mGeoLatitude) || !qFuzzyCompare(d->mGeoLongitude, i2->d_func()->mGeoLongitude)) { 0246 return false; 0247 } 0248 // clang-format off 0249 return 0250 recurrenceEqual 0251 && created() == i2->created() 0252 && stringCompare(description(), i2->description()) 0253 && descriptionIsRich() == i2->descriptionIsRich() 0254 && stringCompare(summary(), i2->summary()) 0255 && summaryIsRich() == i2->summaryIsRich() 0256 && categories() == i2->categories() 0257 && stringCompare(relatedTo(), i2->relatedTo()) 0258 && resources() == i2->resources() 0259 && d->mStatus == i2->d_func()->mStatus 0260 && (d->mStatus == StatusNone || stringCompare(d->mStatusString, i2->d_func()->mStatusString)) 0261 && secrecy() == i2->secrecy() 0262 && priority() == i2->priority() 0263 && stringCompare(location(), i2->location()) 0264 && locationIsRich() == i2->locationIsRich() 0265 && stringCompare(color(), i2->color()) 0266 && stringCompare(schedulingID(), i2->schedulingID()) 0267 && recurrenceId() == i2->recurrenceId() 0268 && conferences() == i2->conferences() 0269 && thisAndFuture() == i2->thisAndFuture(); 0270 // clang-format on 0271 } 0272 0273 QString Incidence::instanceIdentifier() const 0274 { 0275 if (hasRecurrenceId()) { 0276 return uid() + recurrenceId().toString(Qt::ISODate); 0277 } 0278 return uid(); 0279 } 0280 0281 void Incidence::recreate() 0282 { 0283 const QDateTime nowUTC = QDateTime::currentDateTimeUtc(); 0284 setCreated(nowUTC); 0285 0286 setSchedulingID(QString(), CalFormat::createUniqueId()); 0287 setRevision(0); 0288 setLastModified(nowUTC); // NOLINT false clang-analyzer-optin.cplusplus.VirtualCall 0289 } 0290 0291 void Incidence::setLastModified(const QDateTime &lm) 0292 { 0293 Q_D(const Incidence); 0294 if (!d->mLocalOnly) { 0295 IncidenceBase::setLastModified(lm); 0296 } 0297 } 0298 0299 void Incidence::setReadOnly(bool readOnly) 0300 { 0301 Q_D(const Incidence); 0302 IncidenceBase::setReadOnly(readOnly); 0303 if (d->mRecurrence) { 0304 d->mRecurrence->setRecurReadOnly(readOnly); 0305 } 0306 } 0307 0308 void Incidence::setLocalOnly(bool localOnly) 0309 { 0310 if (mReadOnly) { 0311 return; 0312 } 0313 Q_D(Incidence); 0314 d->mLocalOnly = localOnly; 0315 } 0316 0317 bool Incidence::localOnly() const 0318 { 0319 Q_D(const Incidence); 0320 return d->mLocalOnly; 0321 } 0322 0323 void Incidence::setAllDay(bool allDay) 0324 { 0325 if (mReadOnly) { 0326 return; 0327 } 0328 Q_D(const Incidence); 0329 if (d->mRecurrence) { 0330 d->mRecurrence->setAllDay(allDay); 0331 } 0332 IncidenceBase::setAllDay(allDay); 0333 } 0334 0335 void Incidence::setCreated(const QDateTime &created) 0336 { 0337 Q_D(Incidence); 0338 if (mReadOnly || d->mLocalOnly) { 0339 return; 0340 } 0341 0342 update(); 0343 d->mCreated = created.toUTC(); 0344 const auto ct = d->mCreated.time(); 0345 // Remove milliseconds 0346 d->mCreated.setTime(QTime(ct.hour(), ct.minute(), ct.second())); 0347 setFieldDirty(FieldCreated); 0348 updated(); 0349 } 0350 0351 QDateTime Incidence::created() const 0352 { 0353 Q_D(const Incidence); 0354 return d->mCreated; 0355 } 0356 0357 void Incidence::setRevision(int rev) 0358 { 0359 Q_D(Incidence); 0360 if (mReadOnly || d->mLocalOnly) { 0361 return; 0362 } 0363 0364 update(); 0365 d->mRevision = rev; 0366 setFieldDirty(FieldRevision); 0367 updated(); 0368 } 0369 0370 int Incidence::revision() const 0371 { 0372 Q_D(const Incidence); 0373 return d->mRevision; 0374 } 0375 0376 void Incidence::setDtStart(const QDateTime &dt) 0377 { 0378 Q_D(const Incidence); 0379 IncidenceBase::setDtStart(dt); 0380 if (d->mRecurrence && dirtyFields().contains(FieldDtStart)) { 0381 d->mRecurrence->setStartDateTime(dt, allDay()); 0382 } 0383 } 0384 0385 void Incidence::shiftTimes(const QTimeZone &oldZone, const QTimeZone &newZone) 0386 { 0387 Q_D(const Incidence); 0388 IncidenceBase::shiftTimes(oldZone, newZone); 0389 if (d->mRecurrence) { 0390 d->mRecurrence->shiftTimes(oldZone, newZone); 0391 } 0392 if (d->mAlarms.count() > 0) { 0393 update(); 0394 for (auto alarm : d->mAlarms) { 0395 alarm->shiftTimes(oldZone, newZone); 0396 } 0397 setFieldDirty(FieldAlarms); 0398 updated(); 0399 } 0400 } 0401 0402 void Incidence::setDescription(const QString &description, bool isRich) 0403 { 0404 if (mReadOnly) { 0405 return; 0406 } 0407 update(); 0408 Q_D(Incidence); 0409 d->mDescription = description; 0410 d->mDescriptionIsRich = isRich; 0411 setFieldDirty(FieldDescription); 0412 updated(); 0413 } 0414 0415 void Incidence::setDescription(const QString &description) 0416 { 0417 setDescription(description, Qt::mightBeRichText(description)); 0418 } 0419 0420 QString Incidence::description() const 0421 { 0422 Q_D(const Incidence); 0423 return d->mDescription; 0424 } 0425 0426 QString Incidence::richDescription() const 0427 { 0428 Q_D(const Incidence); 0429 if (descriptionIsRich()) { 0430 return d->mDescription; 0431 } else { 0432 return d->mDescription.toHtmlEscaped().replace(QLatin1Char('\n'), QStringLiteral("<br/>")); 0433 } 0434 } 0435 0436 bool Incidence::descriptionIsRich() const 0437 { 0438 Q_D(const Incidence); 0439 return d->mDescriptionIsRich; 0440 } 0441 0442 void Incidence::setSummary(const QString &summary, bool isRich) 0443 { 0444 if (mReadOnly) { 0445 return; 0446 } 0447 Q_D(Incidence); 0448 if (d->mSummary != summary || d->mSummaryIsRich != isRich) { 0449 update(); 0450 d->mSummary = summary; 0451 d->mSummaryIsRich = isRich; 0452 setFieldDirty(FieldSummary); 0453 updated(); 0454 } 0455 } 0456 0457 void Incidence::setSummary(const QString &summary) 0458 { 0459 setSummary(summary, Qt::mightBeRichText(summary)); 0460 } 0461 0462 QString Incidence::summary() const 0463 { 0464 Q_D(const Incidence); 0465 return d->mSummary; 0466 } 0467 0468 QString Incidence::richSummary() const 0469 { 0470 Q_D(const Incidence); 0471 if (summaryIsRich()) { 0472 return d->mSummary; 0473 } else { 0474 return d->mSummary.toHtmlEscaped().replace(QLatin1Char('\n'), QStringLiteral("<br/>")); 0475 } 0476 } 0477 0478 bool Incidence::summaryIsRich() const 0479 { 0480 Q_D(const Incidence); 0481 return d->mSummaryIsRich; 0482 } 0483 0484 void Incidence::setCategories(const QStringList &categories) 0485 { 0486 if (mReadOnly) { 0487 return; 0488 } 0489 0490 Q_D(Incidence); 0491 update(); 0492 d->mCategories = categories; 0493 setFieldDirty(FieldCategories); 0494 updated(); 0495 } 0496 0497 void Incidence::setCategories(const QString &catStr) 0498 { 0499 if (mReadOnly) { 0500 return; 0501 } 0502 update(); 0503 setFieldDirty(FieldCategories); 0504 0505 Q_D(Incidence); 0506 d->mCategories.clear(); 0507 0508 if (catStr.isEmpty()) { 0509 updated(); 0510 return; 0511 } 0512 0513 d->mCategories = catStr.split(QLatin1Char(',')); 0514 0515 for (auto &category : d->mCategories) { 0516 category = category.trimmed(); 0517 } 0518 0519 updated(); 0520 } 0521 0522 QStringList Incidence::categories() const 0523 { 0524 Q_D(const Incidence); 0525 return d->mCategories; 0526 } 0527 0528 QString Incidence::categoriesStr() const 0529 { 0530 Q_D(const Incidence); 0531 return d->mCategories.join(QLatin1Char(',')); 0532 } 0533 0534 void Incidence::setRelatedTo(const QString &relatedToUid, RelType relType) 0535 { 0536 // TODO: RFC says that an incidence can have more than one related-to field 0537 // even for the same relType. 0538 0539 Q_D(Incidence); 0540 if (d->mRelatedToUid[relType] != relatedToUid) { 0541 update(); 0542 d->mRelatedToUid[relType] = relatedToUid; 0543 setFieldDirty(FieldRelatedTo); 0544 updated(); 0545 } 0546 } 0547 0548 QString Incidence::relatedTo(RelType relType) const 0549 { 0550 Q_D(const Incidence); 0551 return d->mRelatedToUid.value(relType); 0552 } 0553 0554 void Incidence::setColor(const QString &colorName) 0555 { 0556 if (mReadOnly) { 0557 return; 0558 } 0559 Q_D(Incidence); 0560 if (!stringCompare(d->mColor, colorName)) { 0561 update(); 0562 d->mColor = colorName; 0563 setFieldDirty(FieldColor); 0564 updated(); 0565 } 0566 } 0567 0568 QString Incidence::color() const 0569 { 0570 Q_D(const Incidence); 0571 return d->mColor; 0572 } 0573 0574 // %%%%%%%%%%%% Recurrence-related methods %%%%%%%%%%%%%%%%%%%% 0575 0576 Recurrence *Incidence::recurrence() const 0577 { 0578 Q_D(const Incidence); 0579 if (!d->mRecurrence) { 0580 d->mRecurrence = new Recurrence(); 0581 d->mRecurrence->setStartDateTime(dateTime(RoleRecurrenceStart), allDay()); 0582 d->mRecurrence->setAllDay(allDay()); 0583 d->mRecurrence->setRecurReadOnly(mReadOnly); 0584 d->mRecurrence->addObserver(const_cast<KCalendarCore::Incidence *>(this)); 0585 } 0586 0587 return d->mRecurrence; 0588 } 0589 0590 void Incidence::clearRecurrence() 0591 { 0592 Q_D(const Incidence); 0593 delete d->mRecurrence; 0594 d->mRecurrence = nullptr; 0595 } 0596 0597 ushort Incidence::recurrenceType() const 0598 { 0599 Q_D(const Incidence); 0600 if (d->mRecurrence) { 0601 return d->mRecurrence->recurrenceType(); 0602 } else { 0603 return Recurrence::rNone; 0604 } 0605 } 0606 0607 bool Incidence::recurs() const 0608 { 0609 Q_D(const Incidence); 0610 if (d->mRecurrence) { 0611 return d->mRecurrence->recurs(); 0612 } else { 0613 return false; 0614 } 0615 } 0616 0617 bool Incidence::recursOn(const QDate &date, const QTimeZone &timeZone) const 0618 { 0619 Q_D(const Incidence); 0620 return d->mRecurrence && d->mRecurrence->recursOn(date, timeZone); 0621 } 0622 0623 bool Incidence::recursAt(const QDateTime &qdt) const 0624 { 0625 Q_D(const Incidence); 0626 return d->mRecurrence && d->mRecurrence->recursAt(qdt); 0627 } 0628 0629 QList<QDateTime> Incidence::startDateTimesForDate(const QDate &date, const QTimeZone &timeZone) const 0630 { 0631 QList<QDateTime> result; 0632 if (!date.isValid()) { 0633 qCWarning(KCALCORE_LOG) << "Invalid date encountered"; 0634 return result; 0635 } 0636 0637 QDateTime start = dtStart(); 0638 QDateTime end = dateTime(RoleEndRecurrenceBase); 0639 0640 // TODO_Recurrence: Also work if only due date is given... 0641 if (!start.isValid() && !end.isValid()) { 0642 return result; 0643 } 0644 0645 // if the incidence doesn't recur, 0646 QDateTime kdate(date, {}, timeZone); 0647 if (!recurs()) { 0648 if (start.date() <= date && end.date() >= date) { 0649 result << start; 0650 } 0651 return result; 0652 } 0653 0654 qint64 days = start.daysTo(end); 0655 // Account for possible recurrences going over midnight, while the original event doesn't 0656 QDate tmpday(date.addDays(-days - 1)); 0657 QDateTime tmp; 0658 while (tmpday <= date) { 0659 if (recurrence()->recursOn(tmpday, timeZone)) { 0660 const QList<QTime> times = recurrence()->recurTimesOn(tmpday, timeZone); 0661 for (const QTime &time : times) { 0662 tmp = QDateTime(tmpday, time, start.timeZone()); 0663 if (endDateForStart(tmp) >= kdate) { 0664 result << tmp; 0665 } 0666 } 0667 } 0668 tmpday = tmpday.addDays(1); 0669 } 0670 return result; 0671 } 0672 0673 QList<QDateTime> Incidence::startDateTimesForDateTime(const QDateTime &datetime) const 0674 { 0675 QList<QDateTime> result; 0676 if (!datetime.isValid()) { 0677 qCWarning(KCALCORE_LOG) << "Invalid datetime encountered"; 0678 return result; 0679 } 0680 0681 QDateTime start = dtStart(); 0682 QDateTime end = dateTime(RoleEndRecurrenceBase); 0683 0684 // TODO_Recurrence: Also work if only due date is given... 0685 if (!start.isValid() && !end.isValid()) { 0686 return result; 0687 } 0688 0689 // if the incidence doesn't recur, 0690 if (!recurs()) { 0691 if (!(start > datetime || end < datetime)) { 0692 result << start; 0693 } 0694 return result; 0695 } 0696 0697 qint64 days = start.daysTo(end); 0698 // Account for possible recurrences going over midnight, while the original event doesn't 0699 QDate tmpday(datetime.date().addDays(-days - 1)); 0700 QDateTime tmp; 0701 while (tmpday <= datetime.date()) { 0702 if (recurrence()->recursOn(tmpday, datetime.timeZone())) { 0703 // Get the times during the day (in start date's time zone) when recurrences happen 0704 const QList<QTime> times = recurrence()->recurTimesOn(tmpday, start.timeZone()); 0705 for (const QTime &time : times) { 0706 tmp = QDateTime(tmpday, time, start.timeZone()); 0707 if (!(tmp > datetime || endDateForStart(tmp) < datetime)) { 0708 result << tmp; 0709 } 0710 } 0711 } 0712 tmpday = tmpday.addDays(1); 0713 } 0714 return result; 0715 } 0716 0717 QDateTime Incidence::endDateForStart(const QDateTime &startDt) const 0718 { 0719 QDateTime start = dtStart(); 0720 QDateTime end = dateTime(RoleEndRecurrenceBase); 0721 if (!end.isValid()) { 0722 return start; 0723 } 0724 if (!start.isValid()) { 0725 return end; 0726 } 0727 0728 return startDt.addSecs(start.secsTo(end)); 0729 } 0730 0731 void Incidence::addAttachment(const Attachment &attachment) 0732 { 0733 if (mReadOnly || attachment.isEmpty()) { 0734 return; 0735 } 0736 0737 Q_D(Incidence); 0738 update(); 0739 d->mAttachments.append(attachment); 0740 setFieldDirty(FieldAttachment); 0741 updated(); 0742 } 0743 0744 void Incidence::deleteAttachments(const QString &mime) 0745 { 0746 Q_D(Incidence); 0747 auto it = std::remove_if(d->mAttachments.begin(), d->mAttachments.end(), [&mime](const Attachment &a) { 0748 return a.mimeType() == mime; 0749 }); 0750 if (it != d->mAttachments.end()) { 0751 update(); 0752 d->mAttachments.erase(it, d->mAttachments.end()); 0753 setFieldDirty(FieldAttachment); 0754 updated(); 0755 } 0756 } 0757 0758 Attachment::List Incidence::attachments() const 0759 { 0760 Q_D(const Incidence); 0761 return d->mAttachments; 0762 } 0763 0764 Attachment::List Incidence::attachments(const QString &mime) const 0765 { 0766 Q_D(const Incidence); 0767 Attachment::List attachments; 0768 for (const Attachment &attachment : std::as_const(d->mAttachments)) { 0769 if (attachment.mimeType() == mime) { 0770 attachments.append(attachment); 0771 } 0772 } 0773 return attachments; 0774 } 0775 0776 void Incidence::clearAttachments() 0777 { 0778 Q_D(Incidence); 0779 update(); 0780 setFieldDirty(FieldAttachment); 0781 d->mAttachments.clear(); 0782 updated(); 0783 } 0784 0785 void Incidence::setResources(const QStringList &resources) 0786 { 0787 if (mReadOnly) { 0788 return; 0789 } 0790 0791 update(); 0792 Q_D(Incidence); 0793 d->mResources = resources; 0794 setFieldDirty(FieldResources); 0795 updated(); 0796 } 0797 0798 QStringList Incidence::resources() const 0799 { 0800 Q_D(const Incidence); 0801 return d->mResources; 0802 } 0803 0804 void Incidence::setPriority(int priority) 0805 { 0806 if (mReadOnly) { 0807 return; 0808 } 0809 0810 if (priority < 0 || priority > 9) { 0811 qCWarning(KCALCORE_LOG) << "Ignoring invalid priority" << priority; 0812 return; 0813 } 0814 0815 update(); 0816 Q_D(Incidence); 0817 d->mPriority = priority; 0818 setFieldDirty(FieldPriority); 0819 updated(); 0820 } 0821 0822 int Incidence::priority() const 0823 { 0824 Q_D(const Incidence); 0825 return d->mPriority; 0826 } 0827 0828 void Incidence::setStatus(Incidence::Status status) 0829 { 0830 if (mReadOnly) { 0831 qCWarning(KCALCORE_LOG) << "Attempt to set status of read-only incidence"; 0832 return; 0833 } 0834 0835 Q_D(Incidence); 0836 if (d->validStatus(status)) { 0837 update(); 0838 d->mStatus = status; 0839 d->mStatusString.clear(); 0840 setFieldDirty(FieldStatus); 0841 updated(); 0842 } else { 0843 qCWarning(KCALCORE_LOG) << "Ignoring invalid status" << status << "for" << typeStr(); 0844 } 0845 } 0846 0847 void Incidence::setCustomStatus(const QString &status) 0848 { 0849 if (mReadOnly) { 0850 return; 0851 } 0852 0853 update(); 0854 Q_D(Incidence); 0855 d->mStatus = status.isEmpty() ? StatusNone : StatusX; 0856 d->mStatusString = status; 0857 setFieldDirty(FieldStatus); 0858 updated(); 0859 } 0860 0861 Incidence::Status Incidence::status() const 0862 { 0863 Q_D(const Incidence); 0864 return d->mStatus; 0865 } 0866 0867 QString Incidence::customStatus() const 0868 { 0869 Q_D(const Incidence); 0870 if (d->mStatus == StatusX) { 0871 return d->mStatusString; 0872 } else { 0873 return QString(); 0874 } 0875 } 0876 0877 void Incidence::setSecrecy(Incidence::Secrecy secrecy) 0878 { 0879 if (mReadOnly) { 0880 return; 0881 } 0882 0883 update(); 0884 Q_D(Incidence); 0885 d->mSecrecy = secrecy; 0886 setFieldDirty(FieldSecrecy); 0887 updated(); 0888 } 0889 0890 Incidence::Secrecy Incidence::secrecy() const 0891 { 0892 Q_D(const Incidence); 0893 return d->mSecrecy; 0894 } 0895 0896 Alarm::List Incidence::alarms() const 0897 { 0898 Q_D(const Incidence); 0899 return d->mAlarms; 0900 } 0901 0902 Alarm::Ptr Incidence::newAlarm() 0903 { 0904 Q_D(Incidence); 0905 Alarm::Ptr alarm(new Alarm(this)); 0906 addAlarm(alarm); 0907 return alarm; 0908 } 0909 0910 void Incidence::addAlarm(const Alarm::Ptr &alarm) 0911 { 0912 Q_D(Incidence); 0913 update(); 0914 d->mAlarms.append(alarm); 0915 setFieldDirty(FieldAlarms); 0916 updated(); 0917 } 0918 0919 void Incidence::removeAlarm(const Alarm::Ptr &alarm) 0920 { 0921 Q_D(Incidence); 0922 const int index = d->mAlarms.indexOf(alarm); 0923 if (index > -1) { 0924 update(); 0925 d->mAlarms.remove(index); 0926 setFieldDirty(FieldAlarms); 0927 updated(); 0928 } 0929 } 0930 0931 void Incidence::clearAlarms() 0932 { 0933 update(); 0934 Q_D(Incidence); 0935 d->mAlarms.clear(); 0936 setFieldDirty(FieldAlarms); 0937 updated(); 0938 } 0939 0940 bool Incidence::hasEnabledAlarms() const 0941 { 0942 Q_D(const Incidence); 0943 return std::any_of(d->mAlarms.cbegin(), d->mAlarms.cend(), [](const Alarm::Ptr &alarm) { 0944 return alarm->enabled(); 0945 }); 0946 } 0947 0948 Conference::List Incidence::conferences() const 0949 { 0950 Q_D(const Incidence); 0951 return d->mConferences; 0952 } 0953 0954 void Incidence::addConference(const Conference &conference) 0955 { 0956 update(); 0957 Q_D(Incidence); 0958 d->mConferences.push_back(conference); 0959 setFieldDirty(FieldConferences); 0960 updated(); 0961 } 0962 0963 void Incidence::setConferences(const Conference::List &conferences) 0964 { 0965 update(); 0966 Q_D(Incidence); 0967 d->mConferences = conferences; 0968 setFieldDirty(FieldConferences); 0969 updated(); 0970 } 0971 0972 void Incidence::clearConferences() 0973 { 0974 update(); 0975 Q_D(Incidence); 0976 d->mConferences.clear(); 0977 setFieldDirty(FieldConferences); 0978 updated(); 0979 } 0980 0981 void Incidence::setLocation(const QString &location, bool isRich) 0982 { 0983 if (mReadOnly) { 0984 return; 0985 } 0986 0987 Q_D(Incidence); 0988 if (d->mLocation != location || d->mLocationIsRich != isRich) { 0989 update(); 0990 d->mLocation = location; 0991 d->mLocationIsRich = isRich; 0992 setFieldDirty(FieldLocation); 0993 updated(); 0994 } 0995 } 0996 0997 void Incidence::setLocation(const QString &location) 0998 { 0999 setLocation(location, Qt::mightBeRichText(location)); 1000 } 1001 1002 QString Incidence::location() const 1003 { 1004 Q_D(const Incidence); 1005 return d->mLocation; 1006 } 1007 1008 QString Incidence::richLocation() const 1009 { 1010 Q_D(const Incidence); 1011 if (locationIsRich()) { 1012 return d->mLocation; 1013 } else { 1014 return d->mLocation.toHtmlEscaped().replace(QLatin1Char('\n'), QStringLiteral("<br/>")); 1015 } 1016 } 1017 1018 bool Incidence::locationIsRich() const 1019 { 1020 Q_D(const Incidence); 1021 return d->mLocationIsRich; 1022 } 1023 1024 void Incidence::setSchedulingID(const QString &sid, const QString &uid) 1025 { 1026 if (!uid.isEmpty()) { 1027 setUid(uid); 1028 } 1029 Q_D(Incidence); 1030 if (sid != d->mSchedulingID) { 1031 update(); 1032 d->mSchedulingID = sid; 1033 setFieldDirty(FieldSchedulingId); 1034 updated(); 1035 } 1036 } 1037 1038 QString Incidence::schedulingID() const 1039 { 1040 Q_D(const Incidence); 1041 if (d->mSchedulingID.isNull()) { 1042 // Nothing set, so use the normal uid 1043 return uid(); 1044 } 1045 return d->mSchedulingID; 1046 } 1047 1048 bool Incidence::hasGeo() const 1049 { 1050 Q_D(const Incidence); 1051 // For internal consistency, return false if either coordinate is invalid. 1052 return d->mGeoLatitude != INVALID_LATLON && d->mGeoLongitude != INVALID_LATLON; 1053 } 1054 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 89) 1055 void Incidence::setHasGeo(bool hasGeo) 1056 { 1057 if (mReadOnly) { 1058 return; 1059 } 1060 1061 Q_D(Incidence); 1062 if (!hasGeo) { 1063 update(); 1064 d->mGeoLatitude = INVALID_LATLON; 1065 d->mGeoLongitude = INVALID_LATLON; 1066 setFieldDirty(FieldGeoLatitude); 1067 setFieldDirty(FieldGeoLongitude); 1068 updated(); 1069 } 1070 // If hasGeo is true, the caller should set latitude and longitude to legal values.. 1071 } 1072 #endif 1073 float Incidence::geoLatitude() const 1074 { 1075 Q_D(const Incidence); 1076 // For internal consistency, both coordinates are considered invalid if either is. 1077 return (INVALID_LATLON == d->mGeoLongitude) ? INVALID_LATLON : d->mGeoLatitude; 1078 } 1079 1080 void Incidence::setGeoLatitude(float geolatitude) 1081 { 1082 if (mReadOnly) { 1083 return; 1084 } 1085 1086 if (isnan(geolatitude)) { 1087 geolatitude = INVALID_LATLON; 1088 } 1089 if (geolatitude != INVALID_LATLON && (geolatitude < -90.0 || geolatitude > 90.0)) { 1090 qCWarning(KCALCORE_LOG) << "Ignoring invalid latitude" << geolatitude; 1091 return; 1092 } 1093 1094 Q_D(Incidence); 1095 update(); 1096 d->mGeoLatitude = geolatitude; 1097 setFieldDirty(FieldGeoLatitude); 1098 updated(); 1099 } 1100 1101 float Incidence::geoLongitude() const 1102 { 1103 Q_D(const Incidence); 1104 // For internal consistency, both coordinates are considered invalid if either is. 1105 return (INVALID_LATLON == d->mGeoLatitude) ? INVALID_LATLON : d->mGeoLongitude; 1106 } 1107 1108 void Incidence::setGeoLongitude(float geolongitude) 1109 { 1110 if (mReadOnly) { 1111 return; 1112 } 1113 1114 if (isnan(geolongitude)) { 1115 geolongitude = INVALID_LATLON; 1116 } 1117 if (geolongitude != INVALID_LATLON && (geolongitude < -180.0 || geolongitude > 180.0)) { 1118 qCWarning(KCALCORE_LOG) << "Ignoring invalid longitude" << geolongitude; 1119 return; 1120 } 1121 1122 Q_D(Incidence); 1123 update(); 1124 d->mGeoLongitude = geolongitude; 1125 setFieldDirty(FieldGeoLongitude); 1126 updated(); 1127 } 1128 1129 bool Incidence::hasRecurrenceId() const 1130 { 1131 Q_D(const Incidence); 1132 return (allDay() && d->mRecurrenceId.date().isValid()) || d->mRecurrenceId.isValid(); 1133 } 1134 1135 QDateTime Incidence::recurrenceId() const 1136 { 1137 Q_D(const Incidence); 1138 return d->mRecurrenceId; 1139 } 1140 1141 void Incidence::setThisAndFuture(bool thisAndFuture) 1142 { 1143 Q_D(Incidence); 1144 d->mThisAndFuture = thisAndFuture; 1145 } 1146 1147 bool Incidence::thisAndFuture() const 1148 { 1149 Q_D(const Incidence); 1150 return d->mThisAndFuture; 1151 } 1152 1153 void Incidence::setRecurrenceId(const QDateTime &recurrenceId) 1154 { 1155 if (!mReadOnly) { 1156 update(); 1157 Q_D(Incidence); 1158 d->mRecurrenceId = recurrenceId; 1159 setFieldDirty(FieldRecurrenceId); 1160 updated(); 1161 } 1162 } 1163 1164 /** Observer interface for the recurrence class. If the recurrence is changed, 1165 this method will be called for the incidence the recurrence object 1166 belongs to. */ 1167 void Incidence::recurrenceUpdated(Recurrence *recurrence) 1168 { 1169 Q_D(const Incidence); 1170 if (recurrence == d->mRecurrence) { 1171 update(); 1172 setFieldDirty(FieldRecurrence); 1173 updated(); 1174 } 1175 } 1176 1177 //@cond PRIVATE 1178 #define ALT_DESC_FIELD "X-ALT-DESC" 1179 #define ALT_DESC_PARAMETERS QStringLiteral("FMTTYPE=text/html") 1180 //@endcond 1181 1182 bool Incidence::hasAltDescription() const 1183 { 1184 const QString value = nonKDECustomProperty(ALT_DESC_FIELD); 1185 const QString parameter = nonKDECustomPropertyParameters(ALT_DESC_FIELD); 1186 1187 return parameter == ALT_DESC_PARAMETERS && !value.isEmpty(); 1188 } 1189 1190 void Incidence::setAltDescription(const QString &altdescription) 1191 { 1192 if (altdescription.isEmpty()) { 1193 removeNonKDECustomProperty(ALT_DESC_FIELD); 1194 } else { 1195 setNonKDECustomProperty(ALT_DESC_FIELD, altdescription, ALT_DESC_PARAMETERS); 1196 } 1197 } 1198 1199 QString Incidence::altDescription() const 1200 { 1201 if (!hasAltDescription()) { 1202 return QString(); 1203 } else { 1204 return nonKDECustomProperty(ALT_DESC_FIELD); 1205 } 1206 } 1207 1208 /** static */ 1209 QStringList Incidence::mimeTypes() 1210 { 1211 return QStringList() << QStringLiteral("text/calendar") << KCalendarCore::Event::eventMimeType() << KCalendarCore::Todo::todoMimeType() 1212 << KCalendarCore::Journal::journalMimeType(); 1213 } 1214 1215 void Incidence::serialize(QDataStream &out) const 1216 { 1217 Q_D(const Incidence); 1218 serializeQDateTimeAsKDateTime(out, d->mCreated); 1219 out << d->mRevision << d->mDescription << d->mDescriptionIsRich << d->mSummary << d->mSummaryIsRich << d->mLocation << d->mLocationIsRich << d->mCategories 1220 << d->mResources << d->mStatusString << d->mPriority << d->mSchedulingID << d->mGeoLatitude << d->mGeoLongitude 1221 << hasGeo(); // No longer used, but serialized/deserialized for compatibility. 1222 serializeQDateTimeAsKDateTime(out, d->mRecurrenceId); 1223 out << d->mThisAndFuture << d->mLocalOnly << d->mStatus << d->mSecrecy << (d->mRecurrence ? true : false) << (qint32)d->mAttachments.count() 1224 << (qint32)d->mAlarms.count() << (qint32)d->mConferences.count() << d->mRelatedToUid; 1225 1226 if (d->mRecurrence) { 1227 out << d->mRecurrence; 1228 } 1229 1230 for (const Attachment &attachment : std::as_const(d->mAttachments)) { 1231 out << attachment; 1232 } 1233 1234 for (const Alarm::Ptr &alarm : std::as_const(d->mAlarms)) { 1235 out << alarm; 1236 } 1237 1238 for (const Conference &conf : std::as_const(d->mConferences)) { 1239 out << conf; 1240 } 1241 } 1242 1243 void Incidence::deserialize(QDataStream &in) 1244 { 1245 bool hasGeo; // No longer used, but serialized/deserialized for compatibility. 1246 quint32 status; 1247 quint32 secrecy; 1248 bool hasRecurrence; 1249 int attachmentCount; 1250 int alarmCount; 1251 int conferencesCount; 1252 QMap<int, QString> relatedToUid; 1253 Q_D(Incidence); 1254 deserializeKDateTimeAsQDateTime(in, d->mCreated); 1255 in >> d->mRevision >> d->mDescription >> d->mDescriptionIsRich >> d->mSummary >> d->mSummaryIsRich >> d->mLocation >> d->mLocationIsRich >> d->mCategories 1256 >> d->mResources >> d->mStatusString >> d->mPriority >> d->mSchedulingID >> d->mGeoLatitude >> d->mGeoLongitude >> hasGeo; 1257 deserializeKDateTimeAsQDateTime(in, d->mRecurrenceId); 1258 in >> d->mThisAndFuture >> d->mLocalOnly >> status >> secrecy >> hasRecurrence >> attachmentCount >> alarmCount >> conferencesCount >> relatedToUid; 1259 1260 if (hasRecurrence) { 1261 d->mRecurrence = new Recurrence(); 1262 d->mRecurrence->addObserver(const_cast<KCalendarCore::Incidence *>(this)); 1263 in >> d->mRecurrence; 1264 } 1265 1266 d->mAttachments.clear(); 1267 d->mAlarms.clear(); 1268 d->mConferences.clear(); 1269 1270 d->mAttachments.reserve(attachmentCount); 1271 for (int i = 0; i < attachmentCount; ++i) { 1272 Attachment attachment; 1273 in >> attachment; 1274 d->mAttachments.append(attachment); 1275 } 1276 1277 d->mAlarms.reserve(alarmCount); 1278 for (int i = 0; i < alarmCount; ++i) { 1279 Alarm::Ptr alarm = Alarm::Ptr(new Alarm(this)); 1280 in >> alarm; 1281 d->mAlarms.append(alarm); 1282 } 1283 1284 d->mConferences.reserve(conferencesCount); 1285 for (int i = 0; i < conferencesCount; ++i) { 1286 Conference conf; 1287 in >> conf; 1288 d->mConferences.push_back(conf); 1289 } 1290 1291 d->mStatus = static_cast<Incidence::Status>(status); 1292 d->mSecrecy = static_cast<Incidence::Secrecy>(secrecy); 1293 1294 d->mRelatedToUid.clear(); 1295 1296 auto it = relatedToUid.cbegin(); 1297 auto end = relatedToUid.cend(); 1298 for (; it != end; ++it) { 1299 d->mRelatedToUid.insert(static_cast<Incidence::RelType>(it.key()), it.value()); 1300 } 1301 } 1302 1303 namespace 1304 { 1305 template<typename T> 1306 QVariantList toVariantList(int size, typename QVector<T>::ConstIterator begin, typename QVector<T>::ConstIterator end) 1307 { 1308 QVariantList l; 1309 l.reserve(size); 1310 std::transform(begin, end, std::back_inserter(l), [](const T &val) { 1311 return QVariant::fromValue(val); 1312 }); 1313 return l; 1314 } 1315 1316 } // namespace 1317 1318 QVariantList Incidence::attachmentsVariant() const 1319 { 1320 Q_D(const Incidence); 1321 return toVariantList<Attachment>(d->mAttachments.size(), d->mAttachments.cbegin(), d->mAttachments.cend()); 1322 } 1323 1324 QVariantList Incidence::conferencesVariant() const 1325 { 1326 Q_D(const Incidence); 1327 return toVariantList<Conference>(d->mConferences.size(), d->mConferences.cbegin(), d->mConferences.cend()); 1328 } 1329 1330 #include "moc_incidence.cpp"