File indexing completed on 2024-11-24 04:50:40
0001 // SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com> 0002 // SPDX-License-Identifier: LGPL-2.1-or-later 0003 0004 #include "incidencewrapper.h" 0005 #include "merkuro_calendar_debug.h" 0006 #include "utils.h" 0007 #include <KLocalizedString> 0008 #include <QBitArray> 0009 #include <QJSValue> 0010 0011 IncidenceWrapper::IncidenceWrapper(CalendarManager *calendarManager, QObject *parent) 0012 : QObject(parent) 0013 , Akonadi::ItemMonitor() 0014 , m_calendarManager(calendarManager) 0015 { 0016 connect(this, &IncidenceWrapper::incidencePtrChanged, &m_attendeesModel, [this](KCalendarCore::Incidence::Ptr incidencePtr) { 0017 m_attendeesModel.setIncidencePtr(incidencePtr); 0018 }); 0019 connect(this, &IncidenceWrapper::incidencePtrChanged, &m_recurrenceExceptionsModel, [this](KCalendarCore::Incidence::Ptr incidencePtr) { 0020 m_recurrenceExceptionsModel.setIncidencePtr(incidencePtr); 0021 }); 0022 connect(this, &IncidenceWrapper::incidencePtrChanged, &m_attachmentsModel, [this](KCalendarCore::Incidence::Ptr incidencePtr) { 0023 m_attachmentsModel.setIncidencePtr(incidencePtr); 0024 }); 0025 0026 // While generally we know of the relationship an incidence has regarding its parent, 0027 // from the POV of an incidence, we have no idea of its relationship to its children. 0028 // This is a limitation of KCalendarCore, which only supports one type of relationship 0029 // type per incidence and throughout the PIM infrastructure it is always the 'parent' 0030 // relationship that is used. 0031 0032 // We therefore need to rely on the ETMCalendar for this information. Since the ETMCalendar 0033 // does not provide us with any specific information about the incidences changed when it 0034 // updates, we unfortunately have to this the coarse way and just update everything when 0035 // things change. 0036 connect(m_calendarManager, &CalendarManager::calendarChanged, this, &IncidenceWrapper::resetChildIncidences); 0037 0038 Akonadi::ItemFetchScope scope; 0039 scope.fetchFullPayload(); 0040 scope.fetchAllAttributes(); 0041 scope.setFetchRelations(true); 0042 scope.setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); 0043 setFetchScope(scope); 0044 0045 setNewEvent(); 0046 } 0047 0048 IncidenceWrapper::~IncidenceWrapper() 0049 { 0050 cleanupChildIncidences(); 0051 } 0052 0053 void IncidenceWrapper::notifyDataChanged() 0054 { 0055 Q_EMIT incidenceTypeChanged(); 0056 Q_EMIT incidenceTypeStrChanged(); 0057 Q_EMIT incidenceIconNameChanged(); 0058 Q_EMIT collectionIdChanged(); 0059 Q_EMIT parentChanged(); 0060 Q_EMIT parentIncidenceChanged(); 0061 Q_EMIT childIncidencesChanged(); 0062 Q_EMIT summaryChanged(); 0063 Q_EMIT categoriesChanged(); 0064 Q_EMIT descriptionChanged(); 0065 Q_EMIT locationChanged(); 0066 Q_EMIT incidenceStartChanged(); 0067 Q_EMIT incidenceStartDateDisplayChanged(); 0068 Q_EMIT incidenceStartTimeDisplayChanged(); 0069 Q_EMIT incidenceEndChanged(); 0070 Q_EMIT incidenceEndDateDisplayChanged(); 0071 Q_EMIT incidenceEndTimeDisplayChanged(); 0072 Q_EMIT timeZoneChanged(); 0073 Q_EMIT startTimeZoneUTCOffsetMinsChanged(); 0074 Q_EMIT endTimeZoneUTCOffsetMinsChanged(); 0075 Q_EMIT durationChanged(); 0076 Q_EMIT durationDisplayStringChanged(); 0077 Q_EMIT allDayChanged(); 0078 Q_EMIT priorityChanged(); 0079 Q_EMIT organizerChanged(); 0080 Q_EMIT attendeesModelChanged(); 0081 Q_EMIT recurrenceDataChanged(); 0082 Q_EMIT recurrenceExceptionsModelChanged(); 0083 Q_EMIT attachmentsModelChanged(); 0084 Q_EMIT todoCompletedChanged(); 0085 Q_EMIT todoCompletionDtChanged(); 0086 Q_EMIT todoPercentCompleteChanged(); 0087 Q_EMIT googleConferenceUrlChanged(); 0088 } 0089 0090 Akonadi::Item IncidenceWrapper::incidenceItem() const 0091 { 0092 return item(); 0093 } 0094 0095 void IncidenceWrapper::setIncidenceItem(const Akonadi::Item &incidenceItem) 0096 { 0097 if (incidenceItem.hasPayload<KCalendarCore::Incidence::Ptr>()) { 0098 setItem(incidenceItem); 0099 setIncidencePtr(incidenceItem.payload<KCalendarCore::Incidence::Ptr>()); 0100 0101 Q_EMIT incidenceItemChanged(); 0102 Q_EMIT collectionIdChanged(); 0103 } else { 0104 qCWarning(MERKURO_CALENDAR_LOG) << "This is not an incidence item."; 0105 } 0106 } 0107 0108 KCalendarCore::Incidence::Ptr IncidenceWrapper::incidencePtr() const 0109 { 0110 return m_incidence; 0111 } 0112 0113 void IncidenceWrapper::setIncidencePtr(const KCalendarCore::Incidence::Ptr incidencePtr) 0114 { 0115 m_incidence = incidencePtr; 0116 0117 KCalendarCore::Incidence::Ptr originalIncidence(incidencePtr->clone()); 0118 m_originalIncidence = originalIncidence; 0119 0120 Q_EMIT incidencePtrChanged(incidencePtr); 0121 Q_EMIT originalIncidencePtrChanged(); 0122 notifyDataChanged(); 0123 } 0124 0125 KCalendarCore::Incidence::Ptr IncidenceWrapper::originalIncidencePtr() 0126 { 0127 return m_originalIncidence; 0128 } 0129 0130 int IncidenceWrapper::incidenceType() const 0131 { 0132 return m_incidence->type(); 0133 } 0134 0135 QString IncidenceWrapper::incidenceTypeStr() const 0136 { 0137 return m_incidence->type() == KCalendarCore::Incidence::TypeTodo ? i18n("Task") : i18n(m_incidence->typeStr().constData()); 0138 } 0139 0140 QString IncidenceWrapper::incidenceIconName() const 0141 { 0142 return m_incidence->iconName(); 0143 } 0144 0145 QString IncidenceWrapper::uid() const 0146 { 0147 return m_incidence->uid(); 0148 } 0149 0150 qint64 IncidenceWrapper::collectionId() const 0151 { 0152 return m_collectionId < 0 ? item().parentCollection().id() : m_collectionId; 0153 } 0154 0155 void IncidenceWrapper::setCollectionId(qint64 collectionId) 0156 { 0157 m_collectionId = collectionId; 0158 Q_EMIT collectionIdChanged(); 0159 } 0160 0161 QString IncidenceWrapper::parent() const 0162 { 0163 return m_incidence->relatedTo(); 0164 } 0165 0166 void IncidenceWrapper::setParent(QString parent) 0167 { 0168 m_incidence->setRelatedTo(parent); 0169 updateParentIncidence(); 0170 Q_EMIT parentChanged(); 0171 } 0172 0173 IncidenceWrapper *IncidenceWrapper::parentIncidence() 0174 { 0175 updateParentIncidence(); 0176 return m_parentIncidence.data(); 0177 } 0178 0179 QVariantList IncidenceWrapper::childIncidences() 0180 { 0181 resetChildIncidences(); 0182 return m_childIncidences; 0183 } 0184 0185 QString IncidenceWrapper::summary() const 0186 { 0187 return m_incidence->summary(); 0188 } 0189 0190 void IncidenceWrapper::setSummary(const QString &summary) 0191 { 0192 m_incidence->setSummary(summary); 0193 Q_EMIT summaryChanged(); 0194 } 0195 0196 QStringList IncidenceWrapper::categories() 0197 { 0198 return m_incidence->categories(); 0199 } 0200 0201 void IncidenceWrapper::setCategories(QStringList categories) 0202 { 0203 m_incidence->setCategories(categories); 0204 Q_EMIT categoriesChanged(); 0205 } 0206 0207 QString IncidenceWrapper::description() const 0208 { 0209 return m_incidence->description(); 0210 } 0211 0212 void IncidenceWrapper::setDescription(const QString &description) 0213 { 0214 if (m_incidence->description() == description) { 0215 return; 0216 } 0217 m_incidence->setDescription(description); 0218 Q_EMIT descriptionChanged(); 0219 } 0220 0221 QString IncidenceWrapper::location() const 0222 { 0223 return m_incidence->location(); 0224 } 0225 0226 void IncidenceWrapper::setLocation(const QString &location) 0227 { 0228 m_incidence->setLocation(location); 0229 Q_EMIT locationChanged(); 0230 } 0231 0232 bool IncidenceWrapper::hasGeo() const 0233 { 0234 return m_incidence->hasGeo(); 0235 } 0236 0237 float IncidenceWrapper::geoLatitude() const 0238 { 0239 return m_incidence->geoLatitude(); 0240 } 0241 0242 float IncidenceWrapper::geoLongitude() const 0243 { 0244 return m_incidence->geoLongitude(); 0245 } 0246 0247 QDateTime IncidenceWrapper::incidenceStart() const 0248 { 0249 return m_incidence->dtStart(); 0250 } 0251 0252 void IncidenceWrapper::setIncidenceStart(const QDateTime &incidenceStart, bool respectTimeZone) 0253 { 0254 // When we receive dates from QML, these are all set to the local system timezone but 0255 // have the dates and times we want. We need to preserve date and time but set the new 0256 // QDateTime to have the correct timezone. 0257 0258 // When we set the timeZone property, however, we invariably also set the incidence start and end. 0259 // This object needs no change. We therefore need to make sure to preserve the entire QDateTime object here. 0260 auto oldStart = this->incidenceStart(); 0261 0262 if (respectTimeZone) { 0263 m_incidence->setDtStart(incidenceStart); 0264 auto newTzEnd = incidenceEnd(); 0265 newTzEnd.setTimeZone(incidenceStart.timeZone()); 0266 setIncidenceEnd(newTzEnd, true); 0267 } else { 0268 const auto date = incidenceStart.date(); 0269 const auto time = incidenceStart.time(); 0270 QDateTime start; 0271 start.setTimeZone(QTimeZone(timeZone())); 0272 start.setDate(date); 0273 start.setTime(time); 0274 m_incidence->setDtStart(start); 0275 } 0276 0277 auto oldStartEndDifference = oldStart.secsTo(incidenceEnd()); 0278 auto newEnd = this->incidenceStart().addSecs(oldStartEndDifference); 0279 setIncidenceEnd(newEnd); 0280 0281 Q_EMIT incidenceStartChanged(); 0282 Q_EMIT incidenceStartDateDisplayChanged(); 0283 Q_EMIT incidenceStartTimeDisplayChanged(); 0284 Q_EMIT durationChanged(); 0285 Q_EMIT durationDisplayStringChanged(); 0286 } 0287 0288 void IncidenceWrapper::setIncidenceStartDate(int day, int month, int year) 0289 { 0290 QDate date; 0291 date.setDate(year, month, day); 0292 0293 auto newStart = incidenceStart(); 0294 newStart.setDate(date); 0295 0296 setIncidenceStart(newStart, true); 0297 } 0298 0299 void IncidenceWrapper::setIncidenceStartTime(int hours, int minutes) 0300 { 0301 QTime time; 0302 time.setHMS(hours, minutes, 0); 0303 0304 auto newStart = incidenceStart(); 0305 newStart.setTime(time); 0306 0307 setIncidenceStart(newStart, true); 0308 } 0309 0310 QString IncidenceWrapper::incidenceStartDateDisplay() const 0311 { 0312 return QLocale::system().toString(incidenceStart().date(), QLocale::NarrowFormat); 0313 } 0314 0315 QString IncidenceWrapper::incidenceStartTimeDisplay() const 0316 { 0317 return QLocale::system().toString(incidenceStart().time(), QLocale::NarrowFormat); 0318 } 0319 0320 QDateTime IncidenceWrapper::incidenceEnd() const 0321 { 0322 if (m_incidence->type() == KCalendarCore::Incidence::IncidenceType::TypeEvent) { 0323 KCalendarCore::Event::Ptr event = m_incidence.staticCast<KCalendarCore::Event>(); 0324 return event->dtEnd(); 0325 } else if (m_incidence->type() == KCalendarCore::Incidence::IncidenceType::TypeTodo) { 0326 KCalendarCore::Todo::Ptr todo = m_incidence.staticCast<KCalendarCore::Todo>(); 0327 return todo->dtDue(); 0328 } 0329 return {}; 0330 } 0331 0332 void IncidenceWrapper::setIncidenceEnd(const QDateTime &incidenceEnd, bool respectTimeZone) 0333 { 0334 QDateTime end; 0335 if (respectTimeZone) { 0336 end = incidenceEnd; 0337 } else { 0338 const auto date = incidenceEnd.date(); 0339 const auto time = incidenceEnd.time(); 0340 end.setTimeZone(QTimeZone(timeZone())); 0341 end.setDate(date); 0342 end.setTime(time); 0343 } 0344 0345 if (m_incidence->type() == KCalendarCore::Incidence::IncidenceType::TypeEvent) { 0346 KCalendarCore::Event::Ptr event = m_incidence.staticCast<KCalendarCore::Event>(); 0347 event->setDtEnd(end); 0348 } else if (m_incidence->type() == KCalendarCore::Incidence::IncidenceType::TypeTodo) { 0349 KCalendarCore::Todo::Ptr todo = m_incidence.staticCast<KCalendarCore::Todo>(); 0350 todo->setDtDue(end); 0351 } else { 0352 qCWarning(MERKURO_CALENDAR_LOG) << "Unknown incidence type"; 0353 } 0354 Q_EMIT incidenceEndChanged(); 0355 Q_EMIT incidenceEndDateDisplayChanged(); 0356 Q_EMIT incidenceEndTimeDisplayChanged(); 0357 Q_EMIT durationChanged(); 0358 Q_EMIT durationDisplayStringChanged(); 0359 } 0360 0361 void IncidenceWrapper::setIncidenceEndDate(int day, int month, int year) 0362 { 0363 QDate date; 0364 date.setDate(year, month, day); 0365 0366 auto newEnd = incidenceEnd(); 0367 newEnd.setDate(date); 0368 0369 setIncidenceEnd(newEnd, true); 0370 } 0371 0372 void IncidenceWrapper::setIncidenceEndTime(int hours, int minutes) 0373 { 0374 QTime time; 0375 time.setHMS(hours, minutes, 0); 0376 0377 auto newEnd = incidenceEnd(); 0378 newEnd.setTime(time); 0379 0380 setIncidenceEnd(newEnd, true); 0381 } 0382 0383 QString IncidenceWrapper::incidenceEndDateDisplay() const 0384 { 0385 return QLocale::system().toString(incidenceEnd().date(), QLocale::NarrowFormat); 0386 } 0387 0388 QString IncidenceWrapper::incidenceEndTimeDisplay() const 0389 { 0390 return QLocale::system().toString(incidenceEnd().time(), QLocale::NarrowFormat); 0391 } 0392 0393 void IncidenceWrapper::setIncidenceTimeToNearestQuarterHour(bool setStartTime, bool setEndTime) 0394 { 0395 const int now = QDateTime::currentSecsSinceEpoch(); 0396 const int quarterHourInSecs = 15 * 60; 0397 const int secsToSet = now + (quarterHourInSecs - now % quarterHourInSecs); 0398 QDateTime startTime = QDateTime::currentDateTime(); 0399 startTime.setSecsSinceEpoch(secsToSet); 0400 if (setStartTime) { 0401 setIncidenceStart(startTime, true); 0402 } 0403 if (setEndTime) { 0404 setIncidenceEnd(startTime.addSecs(3600), true); 0405 } 0406 } 0407 0408 QByteArray IncidenceWrapper::timeZone() const 0409 { 0410 return incidenceEnd().timeZone().id(); 0411 } 0412 0413 void IncidenceWrapper::setTimeZone(const QByteArray &timeZone) 0414 { 0415 QDateTime start(incidenceStart()); 0416 if (start.isValid()) { 0417 start.setTimeZone(QTimeZone(timeZone)); 0418 setIncidenceStart(start, true); 0419 } 0420 0421 QDateTime end(incidenceEnd()); 0422 if (end.isValid()) { 0423 end.setTimeZone(QTimeZone(timeZone)); 0424 setIncidenceEnd(end, true); 0425 } 0426 0427 Q_EMIT timeZoneChanged(); 0428 Q_EMIT startTimeZoneUTCOffsetMinsChanged(); 0429 Q_EMIT endTimeZoneUTCOffsetMinsChanged(); 0430 } 0431 0432 int IncidenceWrapper::startTimeZoneUTCOffsetMins() 0433 { 0434 return QTimeZone(timeZone()).offsetFromUtc(incidenceStart()); 0435 } 0436 0437 int IncidenceWrapper::endTimeZoneUTCOffsetMins() 0438 { 0439 return QTimeZone(timeZone()).offsetFromUtc(incidenceEnd()); 0440 } 0441 0442 KCalendarCore::Duration IncidenceWrapper::duration() const 0443 { 0444 return m_incidence->duration(); 0445 } 0446 0447 QString IncidenceWrapper::durationDisplayString() const 0448 { 0449 return Utils::formatSpelloutDuration(duration(), m_format, allDay()); 0450 } 0451 0452 bool IncidenceWrapper::allDay() const 0453 { 0454 return m_incidence->allDay(); 0455 } 0456 0457 void IncidenceWrapper::setAllDay(bool allDay) 0458 { 0459 m_incidence->setAllDay(allDay); 0460 Q_EMIT allDayChanged(); 0461 } 0462 0463 int IncidenceWrapper::priority() const 0464 { 0465 return m_incidence->priority(); 0466 } 0467 0468 void IncidenceWrapper::setPriority(int priority) 0469 { 0470 m_incidence->setPriority(priority); 0471 Q_EMIT priorityChanged(); 0472 } 0473 0474 KCalendarCore::Recurrence *IncidenceWrapper::recurrence() const 0475 { 0476 KCalendarCore::Recurrence *recurrence = m_incidence->recurrence(); 0477 return recurrence; 0478 } 0479 0480 QVariantMap IncidenceWrapper::recurrenceData() 0481 { 0482 QBitArray weekDaysBits = m_incidence->recurrence()->days(); 0483 QList<bool> weekDaysBools(7); 0484 0485 for (int i = 0; i < weekDaysBits.size(); i++) { 0486 weekDaysBools[i] = weekDaysBits[i]; 0487 } 0488 0489 QVariantList monthPositions; 0490 const auto monthPositionsToConvert = m_incidence->recurrence()->monthPositions(); 0491 for (const auto &pos : monthPositionsToConvert) { 0492 QVariantMap positionToAdd; 0493 positionToAdd[QStringLiteral("day")] = pos.day(); 0494 positionToAdd[QStringLiteral("pos")] = pos.pos(); 0495 monthPositions.append(positionToAdd); 0496 } 0497 0498 // FYI: yearPositions() just calls monthPositions(), so we're cutting out the middleman 0499 return QVariantMap{ 0500 {QStringLiteral("weekdays"), QVariant::fromValue(weekDaysBools)}, 0501 {QStringLiteral("duration"), m_incidence->recurrence()->duration()}, 0502 {QStringLiteral("frequency"), m_incidence->recurrence()->frequency()}, 0503 {QStringLiteral("startDateTime"), m_incidence->recurrence()->startDateTime()}, 0504 {QStringLiteral("startDateTimeDisplay"), QLocale::system().toString(m_incidence->recurrence()->startDateTime(), QLocale::NarrowFormat)}, 0505 {QStringLiteral("endDateTime"), m_incidence->recurrence()->endDateTime()}, 0506 {QStringLiteral("endDateTimeDisplay"), QLocale::system().toString(m_incidence->recurrence()->endDateTime(), QLocale::NarrowFormat)}, 0507 {QStringLiteral("allDay"), m_incidence->recurrence()->allDay()}, 0508 {QStringLiteral("type"), m_incidence->recurrence()->recurrenceType()}, 0509 {QStringLiteral("monthDays"), QVariant::fromValue(m_incidence->recurrence()->monthDays())}, 0510 {QStringLiteral("monthPositions"), monthPositions}, 0511 {QStringLiteral("yearDays"), QVariant::fromValue(m_incidence->recurrence()->yearDays())}, 0512 {QStringLiteral("yearDates"), QVariant::fromValue(m_incidence->recurrence()->yearDates())}, 0513 {QStringLiteral("yearMonths"), QVariant::fromValue(m_incidence->recurrence()->yearMonths())}, 0514 }; 0515 } 0516 0517 void IncidenceWrapper::setRecurrenceDataItem(const QString &key, const QVariant &value) 0518 { 0519 QVariantMap map = recurrenceData(); 0520 if (map.contains(key)) { 0521 if (key == QStringLiteral("weekdays") && value.canConvert<QJSValue>()) { 0522 auto jsval = value.value<QJSValue>(); 0523 0524 if (!jsval.isArray()) { 0525 return; 0526 } 0527 0528 auto vlist = jsval.toVariant().value<QVariantList>(); 0529 QBitArray days(7); 0530 0531 for (int i = 0; i < vlist.size(); i++) { 0532 days[i] = vlist[i].toBool(); 0533 } 0534 0535 KCalendarCore::RecurrenceRule *rrule = m_incidence->recurrence()->defaultRRule(); 0536 QList<KCalendarCore::RecurrenceRule::WDayPos> positions; 0537 0538 for (int i = 0; i < 7; ++i) { 0539 if (days.testBit(i)) { 0540 KCalendarCore::RecurrenceRule::WDayPos p(0, i + 1); 0541 positions.append(p); 0542 } 0543 } 0544 0545 rrule->setByDays(positions); 0546 m_incidence->recurrence()->updated(); 0547 0548 } else if (key == QStringLiteral("duration")) { 0549 m_incidence->recurrence()->setDuration(value.toInt()); 0550 0551 } else if (key == QStringLiteral("frequency")) { 0552 m_incidence->recurrence()->setFrequency(value.toInt()); 0553 0554 } else if ((key == QStringLiteral("startDateTime") || key == QStringLiteral("endDateTime")) && value.toDateTime().isValid()) { 0555 auto dt = value.toDateTime(); 0556 QDateTime adjustedDt; 0557 adjustedDt.setTimeZone(incidenceEnd().timeZone()); 0558 adjustedDt.setDate(dt.date()); 0559 adjustedDt.setTime(dt.time()); 0560 0561 if (key == QStringLiteral("startDateTime")) { 0562 m_incidence->recurrence()->setStartDateTime(adjustedDt, false); 0563 0564 } else if (key == QStringLiteral("endDateTime")) { 0565 m_incidence->recurrence()->setEndDateTime(adjustedDt); 0566 } 0567 0568 } else if (key == QStringLiteral("allDay")) { 0569 m_incidence->recurrence()->setAllDay(value.toBool()); 0570 0571 } else if (key == QStringLiteral("monthDays") && value.canConvert<QList<int>>()) { 0572 m_incidence->recurrence()->setMonthlyDate(value.value<QList<int>>()); 0573 0574 } else if (key == QStringLiteral("yearDays") && value.canConvert<QList<int>>()) { 0575 m_incidence->recurrence()->setYearlyDay(value.value<QList<int>>()); 0576 0577 } else if (key == QStringLiteral("yearDates") && value.canConvert<QList<int>>()) { 0578 m_incidence->recurrence()->setYearlyDate(value.value<QList<int>>()); 0579 0580 } else if (key == QStringLiteral("yearMonths") && value.canConvert<QList<int>>()) { 0581 m_incidence->recurrence()->setYearlyMonth(value.value<QList<int>>()); 0582 0583 } else if (key == QStringLiteral("monthPositions") && value.canConvert<QList<QVariantMap>>()) { 0584 QList<KCalendarCore::RecurrenceRule::WDayPos> newMonthPositions; 0585 const auto values = value.value<QList<QVariantMap>>(); 0586 for (const auto &pos : values) { 0587 KCalendarCore::RecurrenceRule::WDayPos newPos; 0588 newPos.setDay(pos[QStringLiteral("day")].toInt()); 0589 newPos.setPos(pos[QStringLiteral("pos")].toInt()); 0590 newMonthPositions.append(newPos); 0591 } 0592 0593 m_incidence->recurrence()->setMonthlyPos(newMonthPositions); 0594 } 0595 } 0596 Q_EMIT recurrenceDataChanged(); 0597 } 0598 0599 QString IncidenceWrapper::googleConferenceUrl() 0600 { 0601 return m_incidence->customProperty("LIBKGAPI", "EventHangoutLink"); 0602 } 0603 0604 QVariantMap IncidenceWrapper::organizer() 0605 { 0606 auto organizerPerson = m_incidence->organizer(); 0607 return QVariantMap{{QStringLiteral("name"), organizerPerson.name()}, 0608 {QStringLiteral("email"), organizerPerson.email()}, 0609 {QStringLiteral("fullName"), organizerPerson.fullName()}}; 0610 } 0611 0612 KCalendarCore::Attendee::List IncidenceWrapper::attendees() const 0613 { 0614 return m_incidence->attendees(); 0615 } 0616 0617 AttendeesModel *IncidenceWrapper::attendeesModel() 0618 { 0619 return &m_attendeesModel; 0620 } 0621 0622 RecurrenceExceptionsModel *IncidenceWrapper::recurrenceExceptionsModel() 0623 { 0624 return &m_recurrenceExceptionsModel; 0625 } 0626 0627 AttachmentsModel *IncidenceWrapper::attachmentsModel() 0628 { 0629 return &m_attachmentsModel; 0630 } 0631 0632 bool IncidenceWrapper::todoCompleted() const 0633 { 0634 if (m_incidence->type() != KCalendarCore::IncidenceBase::TypeTodo) { 0635 return false; 0636 } 0637 0638 auto todo = m_incidence.staticCast<KCalendarCore::Todo>(); 0639 return todo->isCompleted(); 0640 } 0641 0642 void IncidenceWrapper::setTodoCompleted(bool completed) 0643 { 0644 if (m_incidence->type() != KCalendarCore::IncidenceBase::TypeTodo) { 0645 return; 0646 } 0647 0648 auto todo = m_incidence.staticCast<KCalendarCore::Todo>(); 0649 todo->setCompleted(completed); 0650 0651 Q_EMIT todoCompletionDtChanged(); 0652 Q_EMIT todoPercentCompleteChanged(); 0653 Q_EMIT incidenceIconNameChanged(); 0654 Q_EMIT todoCompletedChanged(); 0655 } 0656 0657 QDateTime IncidenceWrapper::todoCompletionDt() 0658 { 0659 if (m_incidence->type() != KCalendarCore::IncidenceBase::TypeTodo) { 0660 return {}; 0661 } 0662 0663 auto todo = m_incidence.staticCast<KCalendarCore::Todo>(); 0664 return todo->completed(); 0665 } 0666 0667 int IncidenceWrapper::todoPercentComplete() const 0668 { 0669 if (m_incidence->type() != KCalendarCore::IncidenceBase::TypeTodo) { 0670 return 0; 0671 } 0672 0673 auto todo = m_incidence.staticCast<KCalendarCore::Todo>(); 0674 return todo->percentComplete(); 0675 } 0676 0677 void IncidenceWrapper::setTodoPercentComplete(int todoPercentComplete) 0678 { 0679 if (m_incidence->type() != KCalendarCore::IncidenceBase::TypeTodo) { 0680 return; 0681 } 0682 0683 auto todo = m_incidence.staticCast<KCalendarCore::Todo>(); 0684 todo->setPercentComplete(todoPercentComplete); 0685 0686 Q_EMIT todoPercentCompleteChanged(); 0687 0688 if (todoPercentComplete < 100 && todoCompleted()) { 0689 setTodoCompleted(false); 0690 } 0691 0692 Q_EMIT todoCompletedChanged(); 0693 } 0694 0695 void IncidenceWrapper::triggerEditMode() // You edit a clone so that the original ptr isn't messed with 0696 { 0697 auto itemToEdit = item(); 0698 KCalendarCore::Incidence::Ptr clonedPtr(m_incidence->clone()); 0699 itemToEdit.setPayload<KCalendarCore::Incidence::Ptr>(clonedPtr); 0700 setIncidenceItem(itemToEdit); 0701 } 0702 0703 static int nearestQuarterHour(int secsSinceEpoch) 0704 { 0705 const int quarterHourInSecs = 60 * 15; 0706 return secsSinceEpoch + (quarterHourInSecs - secsSinceEpoch % quarterHourInSecs); 0707 } 0708 0709 void IncidenceWrapper::setNewEvent() 0710 { 0711 auto event = KCalendarCore::Event::Ptr(new KCalendarCore::Event); 0712 QDateTime start; 0713 start.setSecsSinceEpoch(nearestQuarterHour(QDateTime::currentSecsSinceEpoch())); 0714 event->setDtStart(start); 0715 event->setDtEnd(start.addSecs(60 * 60)); 0716 0717 KCalendarCore::Alarm::Ptr alarm(new KCalendarCore::Alarm(event.get())); 0718 alarm->setEnabled(true); 0719 alarm->setType(KCalendarCore::Alarm::Display); 0720 alarm->setStartOffset(-1 * 15 * 60); // 15 minutes 0721 0722 event->addAlarm(alarm); 0723 0724 setNewIncidence(event); 0725 } 0726 0727 void IncidenceWrapper::setNewTodo() 0728 { 0729 auto todo = KCalendarCore::Todo::Ptr(new KCalendarCore::Todo); 0730 setNewIncidence(todo); 0731 } 0732 0733 void IncidenceWrapper::setNewIncidence(KCalendarCore::Incidence::Ptr incidence) 0734 { 0735 Akonadi::Item incidenceItem; 0736 incidenceItem.setPayload<KCalendarCore::Incidence::Ptr>(incidence); 0737 setIncidenceItem(incidenceItem); 0738 } 0739 0740 // We need to be careful when we call updateParentIncidence and resetChildIncidences. 0741 // For instance, we always call them on-demand based on access to the properties and not 0742 // upon object construction on upon setting the incidence pointer. 0743 0744 // Calling them both recursively down a family tree can cause a cascade of infinite 0745 // new IncidenceWrappers being created. Say we create a new incidence wrapper here and 0746 // call this new incidence's updateParentIncidence and resetChildIncidences, creating 0747 // a new child wrapper, creating more wrappers there, and so on. 0748 0749 void IncidenceWrapper::updateParentIncidence() 0750 { 0751 if (!m_incidence) { 0752 return; 0753 } 0754 0755 if (!parent().isEmpty() && (!m_parentIncidence || m_parentIncidence->uid() != parent())) { 0756 m_parentIncidence.reset(new IncidenceWrapper(m_calendarManager, this)); 0757 m_parentIncidence->setIncidenceItem(m_calendarManager->incidenceItem(parent())); 0758 Q_EMIT parentIncidenceChanged(); 0759 } 0760 } 0761 0762 void IncidenceWrapper::resetChildIncidences() 0763 { 0764 cleanupChildIncidences(); 0765 0766 if (!m_incidence) { 0767 return; 0768 } 0769 0770 const auto incidences = m_calendarManager->childIncidences(uid()); 0771 QVariantList wrappedIncidences; 0772 0773 for (const auto &incidence : incidences) { 0774 const auto wrappedIncidence = new IncidenceWrapper(m_calendarManager, this); 0775 wrappedIncidence->setIncidenceItem(m_calendarManager->incidenceItem(incidence)); 0776 wrappedIncidences.append(QVariant::fromValue(wrappedIncidence)); 0777 } 0778 0779 m_childIncidences = wrappedIncidences; 0780 Q_EMIT childIncidencesChanged(); 0781 } 0782 0783 void IncidenceWrapper::cleanupChildIncidences() 0784 { 0785 while (!m_childIncidences.isEmpty()) { 0786 const auto incidence = m_childIncidences.takeFirst(); 0787 const auto incidencePtr = incidence.value<IncidenceWrapper *>(); 0788 0789 delete incidencePtr; 0790 } 0791 } 0792 0793 bool IncidenceWrapper::hasReminders() const 0794 { 0795 return !m_incidence->alarms().isEmpty(); 0796 } 0797 0798 void IncidenceWrapper::addAlarms(const KCalendarCore::Alarm::List &alarms) 0799 { 0800 for (int i = 0; i < alarms.size(); i++) { 0801 m_incidence->addAlarm(alarms[i]); 0802 } 0803 } 0804 0805 void IncidenceWrapper::setRegularRecurrence(IncidenceWrapper::RecurrenceIntervals interval, int freq) 0806 { 0807 switch (interval) { 0808 case Daily: 0809 m_incidence->recurrence()->setDaily(freq); 0810 Q_EMIT recurrenceDataChanged(); 0811 return; 0812 case Weekly: 0813 m_incidence->recurrence()->setWeekly(freq); 0814 Q_EMIT recurrenceDataChanged(); 0815 return; 0816 case Monthly: 0817 m_incidence->recurrence()->setMonthly(freq); 0818 Q_EMIT recurrenceDataChanged(); 0819 return; 0820 case Yearly: 0821 m_incidence->recurrence()->setYearly(freq); 0822 Q_EMIT recurrenceDataChanged(); 0823 return; 0824 default: 0825 qCWarning(MERKURO_CALENDAR_LOG) << "Unknown interval for recurrence" << interval; 0826 return; 0827 } 0828 } 0829 0830 void IncidenceWrapper::setMonthlyPosRecurrence(short pos, int day) 0831 { 0832 QBitArray daysBitArray(7); 0833 daysBitArray[day] = 1; 0834 m_incidence->recurrence()->addMonthlyPos(pos, daysBitArray); 0835 } 0836 0837 void IncidenceWrapper::setRecurrenceOccurrences(int occurrences) 0838 { 0839 m_incidence->recurrence()->setDuration(occurrences); 0840 Q_EMIT recurrenceDataChanged(); 0841 } 0842 0843 void IncidenceWrapper::clearRecurrences() 0844 { 0845 m_incidence->recurrence()->clear(); 0846 Q_EMIT recurrenceDataChanged(); 0847 } 0848 0849 void IncidenceWrapper::itemChanged(const Akonadi::Item &item) 0850 { 0851 if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) { 0852 qCDebug(MERKURO_CALENDAR_LOG) << item.payload<KCalendarCore::Incidence::Ptr>()->summary() << item.parentCollection().id(); 0853 setIncidenceItem(item); 0854 } 0855 } 0856 0857 // TODO remove with 22.08, won't be needed anymore 0858 void IncidenceWrapper::setCollection(const Akonadi::Collection &collection) 0859 { 0860 setCollectionId(collection.id()); 0861 } 0862 0863 #ifndef UNITY_CMAKE_SUPPORT 0864 Q_DECLARE_METATYPE(KCalendarCore::Incidence::Ptr) 0865 #endif 0866 0867 #include "moc_incidencewrapper.cpp"