File indexing completed on 2024-05-05 05:28:20

0001 /*
0002  * SPDX-FileCopyrightText: 2020 Dimitris Kardarakos <dimkard@posteo.net>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "incidencemodel.h"
0008 #include <KLocalizedString>
0009 #include <QDebug>
0010 #include <KCalendarCore/Sorting>
0011 
0012 #include "calendarcontroller.h"
0013 
0014 using namespace KCalendarCore;
0015 
0016 IncidenceModel::IncidenceModel(QObject *parent) :
0017     QAbstractListModel(parent),
0018     m_filter_mode(FilterModes::Invalid),
0019     m_filter_dt(QDate()),
0020     m_filter_hour(-1),
0021     m_filter_hide_completed(false),
0022     m_incidences(Incidence::List()),
0023     m_locale(QLocale::system()),
0024     m_cal_filter(new CalFilter())
0025 
0026 {
0027     connect(this, &IncidenceModel::filterModeChanged, this, &IncidenceModel::loadIncidences);
0028     connect(this, &IncidenceModel::filterDtChanged, this, &IncidenceModel::loadIncidences);
0029     connect(this, &IncidenceModel::filterHourChanged, this, &IncidenceModel::loadIncidences);
0030     connect(this, &IncidenceModel::calendarChanged, this, &IncidenceModel::loadIncidences);
0031     connect(this, &IncidenceModel::calendarFilterChanged, this, &IncidenceModel::loadIncidences);
0032 
0033     m_calendar = CalendarController::instance().activeCalendar();
0034 
0035     connect(m_calendar, &LocalCalendar::calendarChanged, this, &IncidenceModel::loadIncidences);
0036     connect(m_calendar, &LocalCalendar::eventsChanged, this, &IncidenceModel::loadIncidences);
0037     connect(m_calendar, &LocalCalendar::todosChanged, this, &IncidenceModel::loadIncidences);
0038 
0039     setCalendarFilter();
0040 }
0041 
0042 IncidenceModel::~IncidenceModel() = default;
0043 
0044 int IncidenceModel::filterMode() const
0045 {
0046     return m_filter_mode;
0047 }
0048 
0049 void IncidenceModel::setFilterMode(const int mode)
0050 {
0051     m_filter_mode = mode;
0052 
0053     Q_EMIT filterModeChanged();
0054 }
0055 
0056 QDate IncidenceModel::filterDt() const
0057 {
0058     return m_filter_dt;
0059 }
0060 
0061 void IncidenceModel::setFilterDt(const QDate &filterDate)
0062 {
0063     m_filter_dt = filterDate;
0064 
0065     Q_EMIT filterDtChanged();
0066 }
0067 
0068 int IncidenceModel::filterHour() const
0069 {
0070     return m_filter_hour;
0071 }
0072 
0073 void IncidenceModel::setFilterHour(const int hour)
0074 {
0075     m_filter_hour = hour;
0076 
0077     Q_EMIT filterHourChanged();
0078 }
0079 
0080 QHash<int, QByteArray> IncidenceModel::roleNames() const
0081 {
0082     return {
0083         { Uid, "uid" },
0084         { DtStart, "dtstart" },
0085         { AllDay, "allday" },
0086         { Description, "description" },
0087         { Summary, "summary" },
0088         { LastModified, "lastmodified" },
0089         { Location, "location" },
0090         { Categories, "categories" },
0091         { Priority, "priority" },
0092         { Created, "created" },
0093         { Secrecy, "secrecy" },
0094         { EndDate, "dtend" },
0095         { RepeatPeriodType, "repeatType" },
0096         { RepeatEvery, "repeatEvery" },
0097         { RepeatStopAfter, "repeatStopAfter" },
0098         { IsRepeating, "isRepeating" },
0099         { DisplayStartDate, "displayStartDate" },
0100         { DisplayDueDate, "displayDueDate" },
0101         { DisplayDueTime, "displayDueTime" },
0102         { DisplayStartEndTime, "displayStartEndTime" },
0103         { DisplayStartTime, "displayStartTime" },
0104         { DisplayType, "displayType" },
0105         { Completed, "completed" },
0106         { IncidenceType, "type" },
0107         { Due, "due" },
0108         { ValidStartDt, "validStartDt" },
0109         { ValidEndDt, "validEndDt" },
0110         { ValidDueDt, "validDueDt" },
0111         { AttendeeEmails, "attendeeEmails" },
0112         { DisplayAttendeeEmails, "displayAttendeeEmails" },
0113         { IncidenceStatus, "status" },
0114         { OrganizerName, "organizerName" },
0115         { DisplaytAttendeeNames, "displayAttendeeNames" }
0116     };
0117 }
0118 
0119 QVariant IncidenceModel::data(const QModelIndex &index, int role) const
0120 {
0121     if (!index.isValid()) {
0122         return QVariant();
0123     }
0124 
0125     int row = index.row();
0126     auto type = m_incidences.at(row)->type();
0127 
0128     switch (role) {
0129     case Uid:
0130         return m_incidences.at(row)->uid();
0131     case DtStart:
0132         return m_incidences.at(row)->dtStart().toTimeZone(QTimeZone::systemTimeZone());
0133     case AllDay:
0134         return m_incidences.at(row)->allDay();
0135     case Description:
0136         return m_incidences.at(row)->description();
0137     case Summary:
0138         return m_incidences.at(row)->summary();
0139     case LastModified:
0140         return m_incidences.at(row)->lastModified();
0141     case Location:
0142         return m_incidences.at(row)->location();
0143     case Categories:
0144         return m_incidences.at(row)->categories();
0145     case Priority:
0146         return m_incidences.at(row)->priority();
0147     case Created:
0148         return m_incidences.at(row)->created();
0149     case Secrecy:
0150         return m_incidences.at(row)->secrecy();
0151     case EndDate:
0152         return (type == IncidenceBase::TypeEvent) ? m_incidences.at(row).dynamicCast<Event>()->dtEnd().toTimeZone(QTimeZone::systemTimeZone()) : QDateTime();
0153     case RepeatPeriodType:
0154         return repeatPeriodType(row);
0155     case RepeatEvery:
0156         return repeatEvery(row);
0157     case RepeatStopAfter:
0158         return repeatStopAfter(row);
0159     case IsRepeating:
0160         return m_incidences.at(row)->recurs();
0161     case DisplayStartDate:
0162         return displayStartDate(row);
0163     case DisplayDueDate:
0164         return displayDueDate(row);
0165     case DisplayStartEndTime:
0166         return displayStartEndTime(row);
0167     case DisplayDueTime:
0168         return displayDueTime(row);
0169     case DisplayStartTime:
0170         return displayStartTime(row);
0171     case DisplayType: {
0172         if (type == IncidenceBase::TypeEvent) {
0173             return i18n("Event");
0174         } else if (type == IncidenceBase::TypeTodo) {
0175             return i18n("Task");
0176         } else {
0177             return QString();
0178         }
0179     }
0180     case Completed:
0181         return (type == IncidenceBase::TypeTodo) ? m_incidences.at(row).dynamicCast<Todo>()->isCompleted() : false;
0182     case IncidenceType:
0183         return type;
0184     case Due:
0185         return (type == IncidenceBase::TypeTodo) ? m_incidences.at(row).dynamicCast<Todo>()->dtDue().toTimeZone(QTimeZone::systemTimeZone()) : QDateTime();
0186     case ValidStartDt:
0187         return m_incidences.at(row)->dtStart().toTimeZone(QTimeZone::systemTimeZone()).isValid();
0188     case ValidEndDt:
0189         return ((type == IncidenceBase::TypeEvent) && m_incidences.at(row).dynamicCast<Event>()->hasEndDate());
0190     case ValidDueDt:
0191         return ((type == IncidenceBase::TypeTodo) && m_incidences.at(row).dynamicCast<Todo>()->hasDueDate());
0192     case AttendeeEmails:
0193         return attendeeEmails(row);
0194     case DisplayAttendeeEmails:
0195         return attendeeEmails(row).join(QStringLiteral(", "));
0196     case IncidenceStatus:
0197         return m_incidences.at(row)->status();
0198     case OrganizerName: {
0199         auto person = m_incidences.at(row)->organizer();
0200         if (!(person.isEmpty()) && !(person.name().isEmpty())) {
0201             return person.name();
0202         }
0203         return QString {};
0204     }
0205     case DisplaytAttendeeNames:
0206         return attendeeNames(row).join(QStringLiteral(", "));
0207     default:
0208         return QVariant();
0209     }
0210 }
0211 
0212 int IncidenceModel::rowCount(const QModelIndex &parent) const
0213 {
0214     if (parent.isValid()) {
0215         return 0;
0216     }
0217 
0218     return m_incidences.count();
0219 }
0220 
0221 void IncidenceModel::loadIncidences()
0222 {
0223     beginResetModel();
0224     m_incidences.clear();
0225     auto calendarReady = (m_calendar != nullptr) && (m_calendar->calendar() != nullptr);
0226     auto dayModelReady = calendarReady && m_filter_dt.isValid();
0227     auto hourModelReady = dayModelReady && (m_filter_hour >= 0);
0228 
0229     switch (m_filter_mode) {
0230     case FilterModes::HourIncidences: {
0231         m_incidences = (hourModelReady) ? hourIncidences() : m_incidences;
0232         break;
0233     }
0234     case FilterModes::HourEvents: {
0235         m_incidences = (hourModelReady) ? hourEvents() : m_incidences;
0236         break;
0237     }
0238     case FilterModes::HourTodos: {
0239         m_incidences = (hourModelReady) ? hourTodos() : m_incidences;
0240         break;
0241     }
0242     case FilterModes::DayIncidences: {
0243         m_incidences = (dayModelReady) ? dayIncidences() : m_incidences;
0244         break;
0245     }
0246     case FilterModes::DayEvents: {
0247         m_incidences = (dayModelReady) ? dayEvents() : m_incidences;
0248         break;
0249     }
0250     case FilterModes::DayTodos: {
0251         m_incidences = (dayModelReady) ? dayTodos() : m_incidences;
0252         break;
0253     }
0254     case FilterModes::AllIncidences: {
0255         m_incidences = (calendarReady) ? allIncidences() : m_incidences;
0256         break;
0257     }
0258     case FilterModes::AllEvents: {
0259         m_incidences = (calendarReady) ? allEvents() : m_incidences;
0260         break;
0261     }
0262     case FilterModes::AllTodos: {
0263         m_incidences = (calendarReady) ? allTodos() : m_incidences;
0264         break;
0265     }
0266     case Invalid:
0267         break;
0268     default:
0269         break;
0270     }
0271 
0272     endResetModel();
0273 }
0274 
0275 int IncidenceModel::repeatEvery(const int idx) const
0276 {
0277     if (!(m_incidences.at(idx)->recurs())) {
0278         return 0;
0279     }
0280 
0281     return m_incidences.at(idx)->recurrence()->frequency();
0282 }
0283 
0284 int IncidenceModel::repeatStopAfter(const int idx) const
0285 {
0286 
0287     if (!(m_incidences.at(idx)->recurs())) {
0288         return -1;
0289     }
0290 
0291     return m_incidences.at(idx)->recurrence()->duration();
0292 }
0293 
0294 ushort IncidenceModel::repeatPeriodType(const int idx) const
0295 {
0296     if (!(m_incidences.at(idx)->recurs())) {
0297         return Recurrence::rNone;
0298     }
0299 
0300     return m_incidences.at(idx)->recurrence()->recurrenceType();
0301 }
0302 
0303 Incidence::List IncidenceModel::hourIncidences() const
0304 {
0305     auto incidences = hourEvents();
0306     incidences.append(hourTodos());
0307     std::sort(incidences.begin(), incidences.end(), Incidences::dateLessThan);
0308 
0309     return incidences;
0310 }
0311 
0312 Incidence::List IncidenceModel::hourEvents() const
0313 {
0314     Incidence::List incidences;
0315     const auto dayEventList = dayEvents();
0316 
0317     for (const auto &d : dayEventList) {
0318         auto e = d.dynamicCast<Event>();
0319         if (isHourEvent(e)) {
0320             incidences.append(e);
0321         }
0322 
0323     }
0324 
0325     return incidences;
0326 }
0327 
0328 bool IncidenceModel::isHourEvent(const Event::Ptr event) const
0329 {
0330     auto startDate = event->dtStart().toTimeZone(QTimeZone::systemTimeZone()).date();
0331     auto endDate = event->dtEnd().toTimeZone(QTimeZone::systemTimeZone()).date();
0332     auto allDay = event->allDay();
0333     auto eventDuration = event->dtStart().secsTo(event->dtEnd());
0334 
0335     if (allDay) {
0336         return true;
0337     }
0338 
0339     if (!event->recurs()) { // Non-repeating event
0340         if ((m_filter_dt != startDate) && (m_filter_dt != endDate)) {
0341             // - The filter date is a date that the event does occur (because it has been included in the dayEventList)
0342             // - The filter date is not the first or the last - so it is in the middle
0343             // -> We include it whatever the filter hour
0344             return true;
0345         } else {
0346             if (withinFilter(event, m_filter_dt)) {
0347                 return true;
0348             }
0349         }
0350     } else { //Repeating event
0351 
0352         if (event->recursOn(m_filter_dt, QTimeZone::systemTimeZone())) {
0353             // filter date == event recurrence start date
0354             // in case of two day events that repeat daily, we check both start and end date
0355 
0356             if ((event->recurrence()->recurrenceType() == Recurrence::rDaily) && event->isMultiDay() && eventDuration < 86400 && (m_filter_dt != startDate)) {
0357                 return withinFilter(event, startDate) ||  withinFilter(event, endDate);
0358             } else {
0359                 return withinFilter(event, startDate);
0360             }
0361         } else { // We know that the event does occur on m_filter_dt. We know that m_filter_dt is not the first day of the event. Let's find the start date of the recurrence we are interested in
0362             auto d { m_filter_dt.startOfDay() };
0363             d.setTime(event->dtEnd().toTimeZone(QTimeZone::systemTimeZone()).time());
0364             d = d.addSecs(-1 * eventDuration);
0365             if (event->recursOn(d.date(), QTimeZone::systemTimeZone())) {
0366                 if (withinFilter(event, endDate)) {
0367                     return true;
0368                 }
0369             } else {
0370                 return true;
0371             }
0372         }
0373     }
0374 
0375     return false;
0376 }
0377 
0378 bool IncidenceModel::withinFilter(const KCalendarCore::Event::Ptr event, const QDate &filterDate) const
0379 {
0380     auto filterStart = filterDate.startOfDay(QTimeZone::systemTimeZone()).addSecs(m_filter_hour * 3600);
0381     auto filterEnd = filterDate.startOfDay(QTimeZone::systemTimeZone()).addSecs(m_filter_hour * 3600 + 3599);
0382 
0383     auto eventStartWithinFilter = event->dtStart().toTimeZone(QTimeZone::systemTimeZone()) >= filterStart && event->dtStart().toTimeZone(QTimeZone::systemTimeZone()) <= filterEnd;
0384     auto eventEndWithinFilter = event->dtEnd().toTimeZone(QTimeZone::systemTimeZone()) > filterStart && event->dtEnd().toTimeZone(QTimeZone::systemTimeZone()) <= filterEnd;
0385     auto filterWithinEvent =  event->dtStart().toTimeZone(QTimeZone::systemTimeZone()) < filterStart && filterEnd < event->dtEnd().toTimeZone(QTimeZone::systemTimeZone());
0386 
0387     if (eventStartWithinFilter || eventEndWithinFilter || filterWithinEvent) {
0388         return true;
0389     }
0390 
0391     return false;
0392 }
0393 
0394 Incidence::List IncidenceModel::hourTodos() const
0395 {
0396     Incidence::List incidences;
0397     const auto dayTodoList = dayTodos();
0398 
0399     for (const auto &t : dayTodoList) {
0400         auto todo =  t.dynamicCast<Todo>();
0401         auto k = t->allDay() ? 0 : (todo->dtDue().isValid() ? todo->dtDue().toTimeZone(QTimeZone::systemTimeZone()).time().hour() : todo->dtStart().toTimeZone(QTimeZone::systemTimeZone()).time().hour());
0402         if (k == m_filter_hour || t->allDay()) {
0403             incidences.append(t);
0404         }
0405     }
0406 
0407     return incidences;
0408 }
0409 
0410 Incidence::List IncidenceModel::dayIncidences() const
0411 {
0412     auto incidences = dayEvents();
0413     incidences.append(dayTodos());
0414     std::sort(incidences.begin(), incidences.end(), Incidences::dateLessThan);
0415 
0416     return incidences;
0417 }
0418 
0419 Incidence::List IncidenceModel::dayEvents() const
0420 {
0421     auto events = m_calendar->calendar()->rawEventsForDate(m_filter_dt, QTimeZone::systemTimeZone(), EventSortStartDate, SortDirectionAscending);
0422     return toIncidences(Calendar::sortEvents(std::move(events), EventSortField::EventSortStartDate, SortDirection::SortDirectionAscending));
0423 }
0424 
0425 Incidence::List IncidenceModel::dayTodos() const
0426 {
0427     auto todos =  m_calendar->calendar()->rawTodos(m_filter_dt, m_filter_dt);
0428 
0429     return toIncidences(Calendar::sortTodos(std::move(todos), TodoSortField::TodoSortDueDate, SortDirection::SortDirectionAscending));
0430 }
0431 
0432 Incidence::List IncidenceModel::allIncidences() const
0433 {
0434     auto incidences = m_calendar->calendar()->rawIncidences();
0435 
0436     return incidences;
0437 }
0438 
0439 Incidence::List IncidenceModel::allTodos() const
0440 {
0441     auto todos =  m_calendar->calendar()->todos(TodoSortDueDate, SortDirectionDescending);
0442 
0443     return toIncidences(todos);
0444 }
0445 
0446 Incidence::List IncidenceModel::allEvents() const
0447 {
0448     auto events = m_calendar->calendar()->rawEvents(EventSortStartDate, SortDirectionDescending);
0449 
0450     return toIncidences(events);
0451 }
0452 
0453 Incidence::List IncidenceModel::toIncidences(const Event::List &eventList) const
0454 {
0455     Incidence::List incidences;
0456 
0457     for (const auto &e : eventList) {
0458         incidences.append(e.dynamicCast<Incidence>());
0459     }
0460 
0461     return incidences;
0462 }
0463 
0464 Incidence::List IncidenceModel::toIncidences(const Todo::List &todoList) const
0465 {
0466     Incidence::List incidences;
0467 
0468     for (const auto &e : todoList) {
0469         incidences.append(e.dynamicCast<Incidence>());
0470     }
0471 
0472     return incidences;
0473 }
0474 
0475 QString IncidenceModel::displayStartEndTime(const int idx) const
0476 {
0477     auto incidence = m_incidences.at(idx);
0478 
0479     if (incidence->type() == IncidenceBase::TypeEvent) {
0480         return eventDisplayStartEndTime(incidence.dynamicCast<Event>());
0481     }
0482 
0483     return QString();
0484 }
0485 
0486 QString IncidenceModel::eventDisplayStartEndTime(const Event::Ptr event) const
0487 {
0488     auto startDateTime = event->dtStart().toTimeZone(QTimeZone::systemTimeZone());
0489     auto endDateTime = event->dtEnd().toTimeZone(QTimeZone::systemTimeZone());
0490 
0491     if (event->allDay()) {
0492         return QStringLiteral("%1 %2").arg(m_locale.toString(startDateTime, QStringLiteral("MMM d")), i18n("all-day"));
0493     }
0494 
0495     if (startDateTime.date() != endDateTime.date()) {
0496         return QStringLiteral("%1 %2 - %3 %4").arg(m_locale.toString(startDateTime, QStringLiteral("MMM d")), m_locale.toString(startDateTime, QStringLiteral("hh:mm")), m_locale.toString(endDateTime, QStringLiteral("MMM d")), m_locale.toString(endDateTime, QStringLiteral("hh:mm")));
0497     } else {
0498         return QStringLiteral("%1 - %2").arg(m_locale.toString(startDateTime, QStringLiteral("hh:mm")), m_locale.toString(endDateTime, QStringLiteral("hh:mm")));
0499     }
0500 
0501     return QString();
0502 }
0503 
0504 QString IncidenceModel::displayStartDate(const int idx) const
0505 {
0506     auto incidence = m_incidences.at(idx);
0507 
0508     if (incidence->dtStart().isValid()) {
0509         return m_locale.toString(incidence->dtStart().toTimeZone(QTimeZone::systemTimeZone()).date(), QStringLiteral("MMM d"));
0510     }
0511 
0512     return QString();
0513 }
0514 
0515 QString IncidenceModel::displayDueDate(const int idx) const
0516 {
0517     auto incidence = m_incidences.at(idx);
0518 
0519     if ((incidence->type() == IncidenceBase::TypeTodo) && (incidence.dynamicCast<Todo>()->dtDue().isValid())) {
0520         return m_locale.toString(incidence.dynamicCast<Todo>()->dtDue().toTimeZone(QTimeZone::systemTimeZone()).date(), QStringLiteral("MMM d"));
0521     }
0522 
0523     return i18n("No Due Date");
0524 }
0525 
0526 QString IncidenceModel::displayDueTime(const int idx) const
0527 {
0528     auto incidence = m_incidences.at(idx);
0529 
0530     if (incidence->allDay()) {
0531         return i18n("all-day");
0532     }
0533 
0534     if (incidence->type() == IncidenceBase::TypeTodo) {
0535         auto todo = incidence.dynamicCast<Todo>();
0536         return todo->dtDue().isValid() ? m_locale.toString(todo->dtDue().toTimeZone(QTimeZone::systemTimeZone()).time(), QStringLiteral("hh:mm")) : QString();
0537     }
0538 
0539     return QString();
0540 }
0541 
0542 QString IncidenceModel::displayStartTime(const int idx) const
0543 {
0544     auto incidence = m_incidences.at(idx);
0545     auto startDt = incidence->dtStart().toTimeZone(QTimeZone::systemTimeZone());
0546 
0547     if (incidence->allDay()) {
0548         return i18n("all-day");
0549     }
0550 
0551     return startDt.isValid() ? m_locale.toString(startDt.time(), QStringLiteral("hh:mm")) : QString();
0552 }
0553 
0554 void IncidenceModel::setAppLocale(const QLocale &qmlLocale)
0555 {
0556     m_locale = qmlLocale;
0557 
0558     Q_EMIT appLocaleChanged();
0559 }
0560 
0561 QLocale IncidenceModel::appLocale() const
0562 {
0563     return m_locale;
0564 }
0565 
0566 bool IncidenceModel::filterHideCompleted() const
0567 {
0568     return m_filter_hide_completed;
0569 }
0570 
0571 void IncidenceModel::setFilterHideCompleted(const bool hideCompleted)
0572 {
0573     m_filter_hide_completed = hideCompleted;
0574 
0575     m_cal_filter = new CalFilter;
0576     if (m_filter_hide_completed) {
0577         m_cal_filter->setCriteria(CalFilter::HideCompletedTodos);
0578     }
0579     setCalendarFilter();
0580 
0581     Q_EMIT filterHideCompletedChanged();
0582 }
0583 
0584 void IncidenceModel::setCalendarFilter()
0585 {
0586     if ((m_calendar != nullptr) && (m_calendar->calendar() != nullptr)) {
0587         m_calendar->calendar()->setFilter(m_cal_filter);
0588         Q_EMIT calendarFilterChanged();
0589     }
0590 }
0591 
0592 QStringList IncidenceModel::attendeeEmails(const int idx) const
0593 {
0594     auto attendees = m_incidences.at(idx)->attendees();
0595     QStringList emails {};
0596     for (const auto &a : attendees) {
0597         emails.append(a.email());
0598     }
0599     return emails;
0600 }
0601 
0602 QStringList IncidenceModel::attendeeNames(const int idx) const
0603 {
0604     auto attendees = m_incidences.at(idx)->attendees();
0605     QStringList names {};
0606     for (const auto &a : attendees) {
0607         names.append(a.name());
0608     }
0609     return names;
0610 }