MauiKit Calendar

incidencewrapper.cpp
1// SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com>
2// SPDX-License-Identifier: LGPL-2.1-or-later
3
4#include <KLocalizedString>
5#include <QBitArray>
6#include <QJSValue>
7#include <incidencewrapper.h>
8#include <QDebug>
9
10IncidenceWrapper::IncidenceWrapper(QObject *parent)
11: QObject(parent)
12, Akonadi::ItemMonitor()
13{
14// connect(this, &IncidenceWrapper::incidencePtrChanged, &m_attendeesModel, [=](KCalendarCore::Incidence::Ptr incidencePtr) {
15// m_attendeesModel.setIncidencePtr(incidencePtr);
16// });
17// connect(this, &IncidenceWrapper::incidencePtrChanged, &m_recurrenceExceptionsModel, [=](KCalendarCore::Incidence::Ptr incidencePtr) {
18// m_recurrenceExceptionsModel.setIncidencePtr(incidencePtr);
19// });
20// connect(this, &IncidenceWrapper::incidencePtrChanged, &m_attachmentsModel, [=](KCalendarCore::Incidence::Ptr incidencePtr) {
21// m_attachmentsModel.setIncidencePtr(incidencePtr);
22// });
23//
24 // While generally we know of the relationship an incidence has regarding its parent,
25 // from the POV of an incidence, we have no idea of its relationship to its children.
26 // This is a limitation of KCalendarCore, which only supports one type of relationship
27 // type per incidence and throughout the PIM infrastructure it is always the 'parent'
28 // relationship that is used.
29
30 // We therefore need to rely on the ETMCalendar for this information. Since the ETMCalendar
31 // does not provide us with any specific information about the incidences changed when it
32 // updates, we unfortunately have to this the coarse way and just update everything when
33 // things change.
34 connect(CalendarManager::instance(), &CalendarManager::calendarChanged, this, &IncidenceWrapper::resetChildIncidences);
35
37 scope.fetchFullPayload();
38 scope.fetchAllAttributes();
40 setFetchScope(scope);
41
42 setNewEvent();
43}
44
45IncidenceWrapper::~IncidenceWrapper()
46{
47 cleanupChildIncidences();
48}
49
50void IncidenceWrapper::notifyDataChanged()
51{
52 Q_EMIT incidenceTypeChanged();
53 Q_EMIT incidenceTypeStrChanged();
54 Q_EMIT incidenceIconNameChanged();
55 Q_EMIT collectionIdChanged();
56 Q_EMIT parentChanged();
57 Q_EMIT parentIncidenceChanged();
58 Q_EMIT childIncidencesChanged();
59 Q_EMIT summaryChanged();
60 Q_EMIT categoriesChanged();
61 Q_EMIT descriptionChanged();
62 Q_EMIT locationChanged();
63 Q_EMIT incidenceStartChanged();
64 Q_EMIT incidenceStartDateDisplayChanged();
65 Q_EMIT incidenceStartTimeDisplayChanged();
66 Q_EMIT incidenceEndChanged();
67 Q_EMIT incidenceEndDateDisplayChanged();
68 Q_EMIT incidenceEndTimeDisplayChanged();
69 Q_EMIT timeZoneChanged();
70 Q_EMIT startTimeZoneUTCOffsetMinsChanged();
71 Q_EMIT endTimeZoneUTCOffsetMinsChanged();
72 Q_EMIT durationChanged();
73 Q_EMIT durationDisplayStringChanged();
74 Q_EMIT allDayChanged();
75 Q_EMIT priorityChanged();
76 Q_EMIT organizerChanged();
77 // Q_EMIT attendeesModelChanged();
78 Q_EMIT recurrenceDataChanged();
79 // Q_EMIT recurrenceExceptionsModelChanged();
80 // Q_EMIT attachmentsModelChanged();
81 Q_EMIT todoCompletedChanged();
82 Q_EMIT todoCompletionDtChanged();
83 Q_EMIT todoPercentCompleteChanged();
84 Q_EMIT googleConferenceUrlChanged();
85}
86
87Akonadi::Item IncidenceWrapper::incidenceItem() const
88{
89 return item();
90}
91
92void IncidenceWrapper::setIncidenceItem(const Akonadi::Item &incidenceItem)
93{
94 if (incidenceItem.hasPayload<KCalendarCore::Incidence::Ptr>()) {
95 setItem(incidenceItem);
96 setIncidencePtr(incidenceItem.payload<KCalendarCore::Incidence::Ptr>());
97
98 Q_EMIT incidenceItemChanged();
99 Q_EMIT collectionIdChanged();
100 } else {
101 qWarning() << "This is not an incidence item.";
102 }
103}
104
105KCalendarCore::Incidence::Ptr IncidenceWrapper::incidencePtr() const
106{
107 return m_incidence;
108}
109
110void IncidenceWrapper::setIncidencePtr(const KCalendarCore::Incidence::Ptr incidencePtr)
111{
112 m_incidence = incidencePtr;
113
114 KCalendarCore::Incidence::Ptr originalIncidence(incidencePtr->clone());
115 m_originalIncidence = originalIncidence;
116
117 Q_EMIT incidencePtrChanged(incidencePtr);
118 Q_EMIT originalIncidencePtrChanged();
119 notifyDataChanged();
120}
121
122KCalendarCore::Incidence::Ptr IncidenceWrapper::originalIncidencePtr()
123{
124 return m_originalIncidence;
125}
126
127int IncidenceWrapper::incidenceType() const
128{
129 return m_incidence->type();
130}
131
132QString IncidenceWrapper::incidenceTypeStr() const
133{
134 return m_incidence->type() == KCalendarCore::Incidence::TypeTodo ? i18n("Task") : i18n(m_incidence->typeStr().constData());
135}
136
137QString IncidenceWrapper::incidenceIconName() const
138{
139 return m_incidence->iconName();
140}
141
142QString IncidenceWrapper::uid() const
143{
144 return m_incidence->uid();
145}
146
147qint64 IncidenceWrapper::collectionId() const
148{
149 return m_collectionId < 0 ? item().parentCollection().id() : m_collectionId;
150}
151
152void IncidenceWrapper::setCollectionId(qint64 collectionId)
153{
154 m_collectionId = collectionId;
155 Q_EMIT collectionIdChanged();
156}
157
158QString IncidenceWrapper::parent() const
159{
160 return m_incidence->relatedTo();
161}
162
163void IncidenceWrapper::setParent(QString parent)
164{
165 m_incidence->setRelatedTo(parent);
166 updateParentIncidence();
167 Q_EMIT parentChanged();
168}
169
170IncidenceWrapper *IncidenceWrapper::parentIncidence()
171{
172 updateParentIncidence();
173 return m_parentIncidence.data();
174}
175
176QVariantList IncidenceWrapper::childIncidences()
177{
178 resetChildIncidences();
179 return m_childIncidences;
180}
181
182QString IncidenceWrapper::summary() const
183{
184 return m_incidence->summary();
185}
186
187void IncidenceWrapper::setSummary(const QString &summary)
188{
189 m_incidence->setSummary(summary);
190 Q_EMIT summaryChanged();
191}
192
193QStringList IncidenceWrapper::categories()
194{
195 return m_incidence->categories();
196}
197
198void IncidenceWrapper::setCategories(QStringList categories)
199{
200 m_incidence->setCategories(categories);
201 Q_EMIT categoriesChanged();
202}
203
204QString IncidenceWrapper::description() const
205{
206 return m_incidence->description();
207}
208
209void IncidenceWrapper::setDescription(const QString &description)
210{
211 if (m_incidence->description() == description) {
212 return;
213 }
214 m_incidence->setDescription(description);
215 Q_EMIT descriptionChanged();
216}
217
218QString IncidenceWrapper::location() const
219{
220 return m_incidence->location();
221}
222
223void IncidenceWrapper::setLocation(const QString &location)
224{
225 m_incidence->setLocation(location);
226 Q_EMIT locationChanged();
227}
228
229bool IncidenceWrapper::hasGeo() const
230{
231 return m_incidence->hasGeo();
232}
233
234float IncidenceWrapper::geoLatitude() const
235{
236 return m_incidence->geoLatitude();
237}
238
239float IncidenceWrapper::geoLongitude() const
240{
241 return m_incidence->geoLongitude();
242}
243
244QDateTime IncidenceWrapper::incidenceStart() const
245{
246 return m_incidence->dtStart();
247}
248
249void IncidenceWrapper::setIncidenceStart(const QDateTime &incidenceStart, bool respectTimeZone)
250{
251 // When we receive dates from QML, these are all set to the local system timezone but
252 // have the dates and times we want. We need to preserve date and time but set the new
253 // QDateTime to have the correct timezone.
254
255 // When we set the timeZone property, however, we invariably also set the incidence start and end.
256 // This object needs no change. We therefore need to make sure to preserve the entire QDateTime object here.
257 auto oldStart = this->incidenceStart();
258
259 if (respectTimeZone) {
260 m_incidence->setDtStart(incidenceStart);
261 auto newTzEnd = incidenceEnd();
262 newTzEnd.setTimeZone(incidenceStart.timeZone());
263 setIncidenceEnd(newTzEnd, true);
264 } else {
265 const auto date = incidenceStart.date();
266 const auto time = incidenceStart.time();
267 QDateTime start;
268 start.setTimeZone(QTimeZone(timeZone()));
269 start.setDate(date);
270 start.setTime(time);
271 m_incidence->setDtStart(start);
272 }
273
274 auto oldStartEndDifference = oldStart.secsTo(incidenceEnd());
275 auto newEnd = this->incidenceStart().addSecs(oldStartEndDifference);
276 setIncidenceEnd(newEnd);
277
278 Q_EMIT incidenceStartChanged();
279 Q_EMIT incidenceStartDateDisplayChanged();
280 Q_EMIT incidenceStartTimeDisplayChanged();
281 Q_EMIT durationChanged();
282 Q_EMIT durationDisplayStringChanged();
283}
284
285void IncidenceWrapper::setIncidenceStartDate(int day, int month, int year)
286{
287 QDate date;
288 date.setDate(year, month, day);
289
290 auto newStart = incidenceStart();
291 newStart.setDate(date);
292
293 setIncidenceStart(newStart, true);
294}
295
296void IncidenceWrapper::setIncidenceStartTime(int hours, int minutes)
297{
298 QTime time;
299 time.setHMS(hours, minutes, 0);
300
301 auto newStart = incidenceStart();
302 newStart.setTime(time);
303
304 setIncidenceStart(newStart, true);
305}
306
307QString IncidenceWrapper::incidenceStartDateDisplay() const
308{
309 return QLocale::system().toString(incidenceStart().date(), QLocale::NarrowFormat);
310}
311
312QString IncidenceWrapper::incidenceStartTimeDisplay() const
313{
314 return QLocale::system().toString(incidenceStart().time(), QLocale::NarrowFormat);
315}
316
317QDateTime IncidenceWrapper::incidenceEnd() const
318{
319 if (m_incidence->type() == KCalendarCore::Incidence::IncidenceType::TypeEvent) {
320 KCalendarCore::Event::Ptr event = m_incidence.staticCast<KCalendarCore::Event>();
321 return event->dtEnd();
322 } else if (m_incidence->type() == KCalendarCore::Incidence::IncidenceType::TypeTodo) {
323 KCalendarCore::Todo::Ptr todo = m_incidence.staticCast<KCalendarCore::Todo>();
324 return todo->dtDue();
325 }
326 return {};
327}
328
329void IncidenceWrapper::setIncidenceEnd(const QDateTime &incidenceEnd, bool respectTimeZone)
330{
331 QDateTime end;
332 if (respectTimeZone) {
333 end = incidenceEnd;
334 } else {
335 const auto date = incidenceEnd.date();
336 const auto time = incidenceEnd.time();
337 end.setTimeZone(QTimeZone(timeZone()));
338 end.setDate(date);
339 end.setTime(time);
340 }
341
342 if (m_incidence->type() == KCalendarCore::Incidence::IncidenceType::TypeEvent) {
343 KCalendarCore::Event::Ptr event = m_incidence.staticCast<KCalendarCore::Event>();
344 event->setDtEnd(end);
345 } else if (m_incidence->type() == KCalendarCore::Incidence::IncidenceType::TypeTodo) {
346 KCalendarCore::Todo::Ptr todo = m_incidence.staticCast<KCalendarCore::Todo>();
347 todo->setDtDue(end);
348 } else {
349 qWarning() << "Unknown incidence type";
350 }
351 Q_EMIT incidenceEndChanged();
352 Q_EMIT incidenceEndDateDisplayChanged();
353 Q_EMIT incidenceEndTimeDisplayChanged();
354 Q_EMIT durationChanged();
355 Q_EMIT durationDisplayStringChanged();
356}
357
358void IncidenceWrapper::setIncidenceEndDate(int day, int month, int year)
359{
360 QDate date;
361 date.setDate(year, month, day);
362
363 auto newEnd = incidenceEnd();
364 newEnd.setDate(date);
365
366 setIncidenceEnd(newEnd, true);
367}
368
369void IncidenceWrapper::setIncidenceEndTime(int hours, int minutes)
370{
371 QTime time;
372 time.setHMS(hours, minutes, 0);
373
374 auto newEnd = incidenceEnd();
375 newEnd.setTime(time);
376
377 setIncidenceEnd(newEnd, true);
378}
379
380QString IncidenceWrapper::incidenceEndDateDisplay() const
381{
382 return QLocale::system().toString(incidenceEnd().date(), QLocale::NarrowFormat);
383}
384
385QString IncidenceWrapper::incidenceEndTimeDisplay() const
386{
387 return QLocale::system().toString(incidenceEnd().time(), QLocale::NarrowFormat);
388}
389
390void IncidenceWrapper::setIncidenceTimeToNearestQuarterHour(bool setStartTime, bool setEndTime)
391{
392 const int now = QDateTime::currentSecsSinceEpoch();
393 const int quarterHourInSecs = 15 * 60;
394 const int secsToSet = now + (quarterHourInSecs - now % quarterHourInSecs);
395 QDateTime startTime = QDateTime::currentDateTime();
396 startTime.setSecsSinceEpoch(secsToSet);
397 if (setStartTime) {
398 setIncidenceStart(startTime, true);
399 }
400 if (setEndTime) {
401 setIncidenceEnd(startTime.addSecs(3600), true);
402 }
403}
404
405QByteArray IncidenceWrapper::timeZone() const
406{
407 return incidenceEnd().timeZone().id();
408}
409
410void IncidenceWrapper::setTimeZone(const QByteArray &timeZone)
411{
412 QDateTime start(incidenceStart());
413 if (start.isValid()) {
414 start.setTimeZone(QTimeZone(timeZone));
415 setIncidenceStart(start, true);
416 }
417
418 QDateTime end(incidenceEnd());
419 if (end.isValid()) {
420 end.setTimeZone(QTimeZone(timeZone));
421 setIncidenceEnd(end, true);
422 }
423
424 Q_EMIT timeZoneChanged();
425 Q_EMIT startTimeZoneUTCOffsetMinsChanged();
426 Q_EMIT endTimeZoneUTCOffsetMinsChanged();
427}
428
429int IncidenceWrapper::startTimeZoneUTCOffsetMins()
430{
431 return QTimeZone(timeZone()).offsetFromUtc(incidenceStart());
432}
433
434int IncidenceWrapper::endTimeZoneUTCOffsetMins()
435{
436 return QTimeZone(timeZone()).offsetFromUtc(incidenceEnd());
437}
438
439KCalendarCore::Duration IncidenceWrapper::duration() const
440{
441 return m_incidence->duration();
442}
443
444static QString formatSpelloutDuration(const KCalendarCore::Duration &duration, const KFormat &format, bool allDay)
445{
446 if (duration.asSeconds() == 0) {
447 return QString();
448 } else {
449 if (allDay) {
450 return format.formatSpelloutDuration(duration.asSeconds() * 1000 + 24 * 60 * 60 * 1000);
451 } else {
452 return format.formatSpelloutDuration(duration.asSeconds() * 1000);
453 }
454 }
455}
456
457QString IncidenceWrapper::durationDisplayString() const
458{
459 return formatSpelloutDuration(duration(), m_format, allDay());
460}
461
462bool IncidenceWrapper::allDay() const
463{
464 return m_incidence->allDay();
465}
466
467void IncidenceWrapper::setAllDay(bool allDay)
468{
469 m_incidence->setAllDay(allDay);
470 Q_EMIT allDayChanged();
471}
472
473int IncidenceWrapper::priority() const
474{
475 return m_incidence->priority();
476}
477
478void IncidenceWrapper::setPriority(int priority)
479{
480 m_incidence->setPriority(priority);
481 Q_EMIT priorityChanged();
482}
483
484KCalendarCore::Recurrence *IncidenceWrapper::recurrence() const
485{
486 KCalendarCore::Recurrence *recurrence = m_incidence->recurrence();
487 return recurrence;
488}
489
490QVariantMap IncidenceWrapper::recurrenceData()
491{
492 QBitArray weekDaysBits = m_incidence->recurrence()->days();
493 QVector<bool> weekDaysBools(7);
494
495 for (int i = 0; i < weekDaysBits.size(); i++) {
496 weekDaysBools[i] = weekDaysBits[i];
497 }
498
499 QVariantList monthPositions;
500 const auto monthPositionsToConvert = m_incidence->recurrence()->monthPositions();
501 for (const auto &pos : monthPositionsToConvert) {
502 QVariantMap positionToAdd;
503 positionToAdd[QStringLiteral("day")] = pos.day();
504 positionToAdd[QStringLiteral("pos")] = pos.pos();
505 monthPositions.append(positionToAdd);
506 }
507
508 // FYI: yearPositions() just calls monthPositions(), so we're cutting out the middleman
509 return QVariantMap{
510 {QStringLiteral("weekdays"), QVariant::fromValue(weekDaysBools)},
511 {QStringLiteral("duration"), m_incidence->recurrence()->duration()},
512 {QStringLiteral("frequency"), m_incidence->recurrence()->frequency()},
513 {QStringLiteral("startDateTime"), m_incidence->recurrence()->startDateTime()},
514 {QStringLiteral("startDateTimeDisplay"), QLocale::system().toString(m_incidence->recurrence()->startDateTime(), QLocale::NarrowFormat)},
515 {QStringLiteral("endDateTime"), m_incidence->recurrence()->endDateTime()},
516 {QStringLiteral("endDateTimeDisplay"), QLocale::system().toString(m_incidence->recurrence()->endDateTime(), QLocale::NarrowFormat)},
517 {QStringLiteral("allDay"), m_incidence->recurrence()->allDay()},
518 {QStringLiteral("type"), m_incidence->recurrence()->recurrenceType()},
519 {QStringLiteral("monthDays"), QVariant::fromValue(m_incidence->recurrence()->monthDays())},
520 {QStringLiteral("monthPositions"), monthPositions},
521 {QStringLiteral("yearDays"), QVariant::fromValue(m_incidence->recurrence()->yearDays())},
522 {QStringLiteral("yearDates"), QVariant::fromValue(m_incidence->recurrence()->yearDates())},
523 {QStringLiteral("yearMonths"), QVariant::fromValue(m_incidence->recurrence()->yearMonths())},
524 };
525}
526
527void IncidenceWrapper::setRecurrenceDataItem(const QString &key, const QVariant &value)
528{
529 QVariantMap map = recurrenceData();
530 if (map.contains(key)) {
531 if (key == QStringLiteral("weekdays") && value.canConvert<QJSValue>()) {
532 auto jsval = value.value<QJSValue>();
533
534 if (!jsval.isArray()) {
535 return;
536 }
537
538 auto vlist = jsval.toVariant().value<QVariantList>();
539 QBitArray days(7);
540
541 for (int i = 0; i < vlist.size(); i++) {
542 days[i] = vlist[i].toBool();
543 }
544
545 KCalendarCore::RecurrenceRule *rrule = m_incidence->recurrence()->defaultRRule();
546 QList<KCalendarCore::RecurrenceRule::WDayPos> positions;
547
548 for (int i = 0; i < 7; ++i) {
549 if (days.testBit(i)) {
550 KCalendarCore::RecurrenceRule::WDayPos p(0, i + 1);
551 positions.append(p);
552 }
553 }
554
555 rrule->setByDays(positions);
556 m_incidence->recurrence()->updated();
557
558 } else if (key == QStringLiteral("duration")) {
559 m_incidence->recurrence()->setDuration(value.toInt());
560
561 } else if (key == QStringLiteral("frequency")) {
562 m_incidence->recurrence()->setFrequency(value.toInt());
563
564 } else if ((key == QStringLiteral("startDateTime") || key == QStringLiteral("endDateTime")) && value.toDateTime().isValid()) {
565 auto dt = value.toDateTime();
566 QDateTime adjustedDt;
567 adjustedDt.setTimeZone(incidenceEnd().timeZone());
568 adjustedDt.setDate(dt.date());
569 adjustedDt.setTime(dt.time());
570
571 if (key == QStringLiteral("startDateTime")) {
572 m_incidence->recurrence()->setStartDateTime(adjustedDt, false);
573
574 } else if (key == QStringLiteral("endDateTime")) {
575 m_incidence->recurrence()->setEndDateTime(adjustedDt);
576 }
577
578 } else if (key == QStringLiteral("allDay")) {
579 m_incidence->recurrence()->setAllDay(value.toBool());
580
581 } else if (key == QStringLiteral("monthDays") && value.canConvert<QList<int>>()) {
582 m_incidence->recurrence()->setMonthlyDate(value.value<QList<int>>());
583
584 } else if (key == QStringLiteral("yearDays") && value.canConvert<QList<int>>()) {
585 m_incidence->recurrence()->setYearlyDay(value.value<QList<int>>());
586
587 } else if (key == QStringLiteral("yearDates") && value.canConvert<QList<int>>()) {
588 m_incidence->recurrence()->setYearlyDate(value.value<QList<int>>());
589
590 } else if (key == QStringLiteral("yearMonths") && value.canConvert<QList<int>>()) {
591 m_incidence->recurrence()->setYearlyMonth(value.value<QList<int>>());
592
593 } else if (key == QStringLiteral("monthPositions") && value.canConvert<QList<QVariantMap>>()) {
594 QList<KCalendarCore::RecurrenceRule::WDayPos> newMonthPositions;
595 const auto values = value.value<QList<QVariantMap>>();
596 for (const auto &pos : values) {
597 KCalendarCore::RecurrenceRule::WDayPos newPos;
598 newPos.setDay(pos[QStringLiteral("day")].toInt());
599 newPos.setPos(pos[QStringLiteral("pos")].toInt());
600 newMonthPositions.append(newPos);
601 }
602
603 m_incidence->recurrence()->setMonthlyPos(newMonthPositions);
604 }
605 }
606 Q_EMIT recurrenceDataChanged();
607}
608
609QString IncidenceWrapper::googleConferenceUrl()
610{
611 return m_incidence->customProperty("LIBKGAPI", "EventHangoutLink");
612}
613
614QVariantMap IncidenceWrapper::organizer()
615{
616 auto organizerPerson = m_incidence->organizer();
617 return QVariantMap{{QStringLiteral("name"), organizerPerson.name()},
618 {QStringLiteral("email"), organizerPerson.email()},
619 {QStringLiteral("fullName"), organizerPerson.fullName()}};
620}
621
622// KCalendarCore::Attendee::List IncidenceWrapper::attendees() const
623// {
624// return m_incidence->attendees();
625// }
626//
627// AttendeesModel *IncidenceWrapper::attendeesModel()
628// {
629// return &m_attendeesModel;
630// }
631
632// RecurrenceExceptionsModel *IncidenceWrapper::recurrenceExceptionsModel()
633// {
634// return &m_recurrenceExceptionsModel;
635// }
636
637// AttachmentsModel *IncidenceWrapper::attachmentsModel()
638// {
639// return &m_attachmentsModel;
640// }
641
642bool IncidenceWrapper::todoCompleted()
643{
644 if (m_incidence->type() != KCalendarCore::IncidenceBase::TypeTodo) {
645 return false;
646 }
647
648 auto todo = m_incidence.staticCast<KCalendarCore::Todo>();
649 return todo->isCompleted();
650}
651
652void IncidenceWrapper::setTodoCompleted(bool completed)
653{
654 if (m_incidence->type() != KCalendarCore::IncidenceBase::TypeTodo) {
655 return;
656 }
657
658 auto todo = m_incidence.staticCast<KCalendarCore::Todo>();
659 todo->setCompleted(completed);
660
661 Q_EMIT todoCompletionDtChanged();
662 Q_EMIT todoPercentCompleteChanged();
663 Q_EMIT incidenceIconNameChanged();
664 Q_EMIT todoCompletedChanged();
665}
666
667QDateTime IncidenceWrapper::todoCompletionDt()
668{
669 if (m_incidence->type() != KCalendarCore::IncidenceBase::TypeTodo) {
670 return {};
671 }
672
673 auto todo = m_incidence.staticCast<KCalendarCore::Todo>();
674 return todo->completed();
675}
676
677int IncidenceWrapper::todoPercentComplete()
678{
679 if (m_incidence->type() != KCalendarCore::IncidenceBase::TypeTodo) {
680 return 0;
681 }
682
683 auto todo = m_incidence.staticCast<KCalendarCore::Todo>();
684 return todo->percentComplete();
685}
686
687void IncidenceWrapper::setTodoPercentComplete(int todoPercentComplete)
688{
689 if (m_incidence->type() != KCalendarCore::IncidenceBase::TypeTodo) {
690 return;
691 }
692
693 auto todo = m_incidence.staticCast<KCalendarCore::Todo>();
694 todo->setPercentComplete(todoPercentComplete);
695
696 Q_EMIT todoPercentCompleteChanged();
697
698 if (todoPercentComplete < 100 && todoCompleted()) {
699 setTodoCompleted(false);
700 }
701
702 Q_EMIT todoCompletedChanged();
703}
704
705void IncidenceWrapper::triggerEditMode() // You edit a clone so that the original ptr isn't messed with
706{
707 auto itemToEdit = item();
708 KCalendarCore::Incidence::Ptr clonedPtr(m_incidence->clone());
709 itemToEdit.setPayload<KCalendarCore::Incidence::Ptr>(clonedPtr);
710 setIncidenceItem(itemToEdit);
711}
712
713static int nearestQuarterHour(int secsSinceEpoch)
714{
715 const int quarterHourInSecs = 60 * 15;
716 return secsSinceEpoch + (quarterHourInSecs - secsSinceEpoch % quarterHourInSecs);
717}
718
719void IncidenceWrapper::setNewEvent()
720{
721 auto event = KCalendarCore::Event::Ptr(new KCalendarCore::Event);
722 QDateTime start;
723 start.setSecsSinceEpoch(nearestQuarterHour(QDateTime::currentSecsSinceEpoch()));
724 event->setDtStart(start);
725 event->setDtEnd(start.addSecs(60 * 60));
726
727 KCalendarCore::Alarm::Ptr alarm(new KCalendarCore::Alarm(event.get()));
728 alarm->setEnabled(true);
729 alarm->setType(KCalendarCore::Alarm::Display);
730 alarm->setStartOffset(-1 * 15 * 60); // 15 minutes
731
732 event->addAlarm(alarm);
733
734 setNewIncidence(event);
735}
736
737void IncidenceWrapper::setNewTodo()
738{
739 auto todo = KCalendarCore::Todo::Ptr(new KCalendarCore::Todo);
740 setNewIncidence(todo);
741}
742
743void IncidenceWrapper::setNewIncidence(KCalendarCore::Incidence::Ptr incidence)
744{
745 Akonadi::Item incidenceItem;
746 incidenceItem.setPayload<KCalendarCore::Incidence::Ptr>(incidence);
747 setIncidenceItem(incidenceItem);
748}
749
750// We need to be careful when we call updateParentIncidence and resetChildIncidences.
751// For instance, we always call them on-demand based on access to the properties and not
752// upon object construction on upon setting the incidence pointer.
753
754// Calling them both recursively down a family tree can cause a cascade of infinite
755// new IncidenceWrappers being created. Say we create a new incidence wrapper here and
756// call this new incidence's updateParentIncidence and resetChildIncidences, creating
757// a new child wrapper, creating more wrappers there, and so on.
758
759void IncidenceWrapper::updateParentIncidence()
760{
761 if (!m_incidence) {
762 return;
763 }
764
765 if (!parent().isEmpty() && (!m_parentIncidence || m_parentIncidence->uid() != parent())) {
766 m_parentIncidence.reset(new IncidenceWrapper);
767 m_parentIncidence->setIncidenceItem(CalendarManager::instance()->incidenceItem(parent()));
768 Q_EMIT parentIncidenceChanged();
769 }
770}
771
772void IncidenceWrapper::resetChildIncidences()
773{
774 cleanupChildIncidences();
775
776 if (!m_incidence) {
777 return;
778 }
779
780 const auto incidences = CalendarManager::instance()->childIncidences(uid());
781 QVariantList wrappedIncidences;
782
783 for (const auto &incidence : incidences) {
784 const auto wrappedIncidence = new IncidenceWrapper;
785 wrappedIncidence->setIncidenceItem(CalendarManager::instance()->incidenceItem(incidence));
786 wrappedIncidences.append(QVariant::fromValue(wrappedIncidence));
787 }
788
789 m_childIncidences = wrappedIncidences;
790 Q_EMIT childIncidencesChanged();
791}
792
793void IncidenceWrapper::cleanupChildIncidences()
794{
795 while (!m_childIncidences.isEmpty()) {
796 const auto incidence = m_childIncidences.takeFirst();
797 const auto incidencePtr = incidence.value<IncidenceWrapper *>();
798
799 delete incidencePtr;
800 }
801}
802
803void IncidenceWrapper::addAlarms(KCalendarCore::Alarm::List alarms)
804{
805 for (int i = 0; i < alarms.size(); i++) {
806 m_incidence->addAlarm(alarms[i]);
807 }
808}
809
810void IncidenceWrapper::setRegularRecurrence(IncidenceWrapper::RecurrenceIntervals interval, int freq)
811{
812 switch (interval) {
813 case Daily:
814 m_incidence->recurrence()->setDaily(freq);
815 Q_EMIT recurrenceDataChanged();
816 return;
817 case Weekly:
818 m_incidence->recurrence()->setWeekly(freq);
819 Q_EMIT recurrenceDataChanged();
820 return;
821 case Monthly:
822 m_incidence->recurrence()->setMonthly(freq);
823 Q_EMIT recurrenceDataChanged();
824 return;
825 case Yearly:
826 m_incidence->recurrence()->setYearly(freq);
827 Q_EMIT recurrenceDataChanged();
828 return;
829 default:
830 qWarning() << "Unknown interval for recurrence" << interval;
831 return;
832 }
833}
834
835void IncidenceWrapper::setMonthlyPosRecurrence(short pos, int day)
836{
837 QBitArray daysBitArray(7);
838 daysBitArray[day] = 1;
839 m_incidence->recurrence()->addMonthlyPos(pos, daysBitArray);
840}
841
842void IncidenceWrapper::setRecurrenceOccurrences(int occurrences)
843{
844 m_incidence->recurrence()->setDuration(occurrences);
845 Q_EMIT recurrenceDataChanged();
846}
847
848void IncidenceWrapper::clearRecurrences()
849{
850 m_incidence->recurrence()->clear();
851 Q_EMIT recurrenceDataChanged();
852}
853
854void IncidenceWrapper::itemChanged(const Akonadi::Item &item)
855{
856 if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
857 qDebug()<< item.payload<KCalendarCore::Incidence::Ptr>()->summary() << item.parentCollection().id();
858 setIncidenceItem(item);
859 }
860}
861
862// TODO remove with 22.08, won't be needed anymore
863void IncidenceWrapper::setCollection(const Akonadi::Collection &collection)
864{
865 setCollectionId(collection.id());
866}
867
868#ifndef UNITY_CMAKE_SUPPORT
869Q_DECLARE_METATYPE(KCalendarCore::Incidence::Ptr)
870#endif
void setAncestorRetrieval(AncestorRetrieval ancestorDepth)
void fetchAllAttributes(bool fetch=true)
void fetchFullPayload(bool fetch=true)
void setItem(const Item &item)
Collection & parentCollection()
This class is a wrapper for a KCalendarCore::Incidence::Ptr object.
QSharedPointer< Alarm > Ptr
QSharedPointer< Event > Ptr
QSharedPointer< Incidence > Ptr
QBitArray days() const
QSharedPointer< Todo > Ptr
QString formatSpelloutDuration(quint64 msecs) const
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
AKONADI_CALENDAR_EXPORT KCalendarCore::Todo::Ptr todo(const Akonadi::Item &item)
const QList< QKeySequence > & end()
qsizetype size() const const
bool setDate(int year, int month, int day)
QDateTime currentDateTime()
qint64 currentSecsSinceEpoch()
bool isValid() const const
void setDate(QDate date)
void setTime(QTime time)
void setTimeZone(const QTimeZone &toZone)
void append(QList< T > &&value)
qsizetype size() const const
QLocale system()
QString toString(QDate date, FormatType format) const const
Q_EMITQ_EMIT
virtual bool event(QEvent *e)
QSharedPointer< X > staticCast() const const
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool setHMS(int h, int m, int s, int ms)
bool canConvert() const const
QVariant fromValue(T &&value)
bool toBool() const const
QDateTime toDateTime() const const
int toInt(bool *ok) const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:47:16 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.