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 }