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"