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