File indexing completed on 2025-01-05 04:47:55
0001 /* 0002 This file is part of KOrganizer. 0003 0004 SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0007 */ 0008 0009 #include "whatsnextview.h" 0010 #include "calendarview_debug.h" 0011 0012 #include <Akonadi/CalendarUtils> 0013 #include <CalendarSupport/KCalPrefs> 0014 #include <CalendarSupport/Utils> 0015 0016 #include <KCalUtils/IncidenceFormatter> 0017 0018 #include <QBoxLayout> 0019 #include <QUrlQuery> 0020 0021 #include <optional> 0022 0023 using namespace EventViews; 0024 void WhatsNextTextBrowser::doSetSource(const QUrl &name, QTextDocument::ResourceType type) 0025 { 0026 Q_UNUSED(type); 0027 if (name.scheme() == QLatin1StringView("event")) { 0028 Q_EMIT showIncidence(name); 0029 } else if (name.scheme() == QLatin1StringView("todo")) { 0030 Q_EMIT showIncidence(name); 0031 } else { 0032 QTextBrowser::setSource(name); 0033 } 0034 } 0035 0036 WhatsNextView::WhatsNextView(QWidget *parent) 0037 : EventView(parent) 0038 , mView(new WhatsNextTextBrowser(this)) 0039 { 0040 connect(mView, &WhatsNextTextBrowser::showIncidence, this, &WhatsNextView::showIncidence); 0041 0042 QBoxLayout *topLayout = new QVBoxLayout(this); 0043 topLayout->setContentsMargins(0, 0, 0, 0); 0044 topLayout->addWidget(mView); 0045 } 0046 0047 WhatsNextView::~WhatsNextView() = default; 0048 0049 int WhatsNextView::currentDateCount() const 0050 { 0051 return mStartDate.daysTo(mEndDate); 0052 } 0053 0054 void WhatsNextView::createTaskRow(KIconLoader *kil) 0055 { 0056 QString ipath; 0057 kil->loadIcon(QStringLiteral("view-calendar-tasks"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath); 0058 mText += QLatin1StringView("<h2><img src=\""); 0059 mText += ipath; 0060 mText += QLatin1StringView(R"(" width="22" height="22">)"); 0061 mText += i18n("To-dos:") + QLatin1StringView("</h2>\n"); 0062 mText += QLatin1StringView("<ul>\n"); 0063 } 0064 0065 void WhatsNextView::updateView() 0066 { 0067 KIconLoader *kil = KIconLoader::global(); 0068 QString ipath; 0069 kil->loadIcon(QStringLiteral("office-calendar"), KIconLoader::NoGroup, 32, KIconLoader::DefaultState, QStringList(), &ipath); 0070 0071 mText = QStringLiteral("<table width=\"100%\">\n"); 0072 mText += QLatin1StringView("<tr bgcolor=\"#3679AD\"><td><h1>"); 0073 mText += QLatin1StringView("<img src=\""); 0074 mText += ipath; 0075 mText += QLatin1StringView(R"(" width="32" height="32">)"); 0076 mText += QLatin1StringView("<font color=\"white\"> "); 0077 mText += i18n("What's Next?") + QLatin1StringView("</font></h1>"); 0078 mText += QLatin1StringView("</td></tr>\n<tr><td>"); 0079 0080 mText += QLatin1StringView("<h2>"); 0081 if (mStartDate.daysTo(mEndDate) < 1) { 0082 mText += QLocale::system().toString(mStartDate); 0083 } else { 0084 mText += i18nc("date from - to", "%1 - %2", QLocale::system().toString(mStartDate), QLocale::system().toString(mEndDate)); 0085 } 0086 mText += QLatin1StringView("</h2>\n"); 0087 0088 KCalendarCore::Event::List events; 0089 for (const auto &calendar : calendars()) { 0090 events += calendar->events(mStartDate, mEndDate, QTimeZone::systemTimeZone(), false); 0091 } 0092 0093 events = KCalendarCore::Calendar::sortEvents(std::move(events), KCalendarCore::EventSortStartDate, KCalendarCore::SortDirectionAscending); 0094 if (!events.isEmpty()) { 0095 mText += QLatin1StringView("<p></p>"); 0096 kil->loadIcon(QStringLiteral("view-calendar-day"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath); 0097 mText += QLatin1StringView("<h2><img src=\""); 0098 mText += ipath; 0099 mText += QLatin1StringView(R"(" width="22" height="22">)"); 0100 mText += i18n("Events:") + QLatin1StringView("</h2>\n"); 0101 mText += QLatin1StringView("<table>\n"); 0102 for (const KCalendarCore::Event::Ptr &ev : std::as_const(events)) { 0103 const auto calendar = calendar3(ev); 0104 if (!ev->recurs()) { 0105 appendEvent(calendar, ev); 0106 } else { 0107 KCalendarCore::Recurrence *recur = ev->recurrence(); 0108 int duration = ev->dtStart().secsTo(ev->dtEnd()); 0109 QDateTime start = recur->getPreviousDateTime(QDateTime(mStartDate, QTime(), Qt::LocalTime)); 0110 QDateTime end = start.addSecs(duration); 0111 QDateTime endDate(mEndDate, QTime(23, 59, 59), Qt::LocalTime); 0112 if (end.date() >= mStartDate) { 0113 appendEvent(calendar, ev, start.toLocalTime(), end.toLocalTime()); 0114 } 0115 const auto times = recur->timesInInterval(start, endDate); 0116 int count = times.count(); 0117 if (count > 0) { 0118 int i = 0; 0119 if (times[0] == start) { 0120 ++i; // start has already been appended 0121 } 0122 if (!times[count - 1].isValid()) { 0123 --count; // list overflow 0124 } 0125 for (; i < count && times[i].date() <= mEndDate; ++i) { 0126 appendEvent(calendar, ev, times[i].toLocalTime()); 0127 } 0128 } 0129 } 0130 } 0131 mText += QLatin1StringView("</table>\n"); 0132 } 0133 0134 mTodos.clear(); 0135 KCalendarCore::Todo::List todos; 0136 for (const auto &calendar : calendars()) { 0137 todos += calendar->todos(KCalendarCore::TodoSortDueDate, KCalendarCore::SortDirectionAscending); 0138 } 0139 if (!todos.isEmpty()) { 0140 bool taskHeaderWasCreated = false; 0141 for (const KCalendarCore::Todo::Ptr &todo : std::as_const(todos)) { 0142 const auto calendar = calendar3(todo); 0143 if (!todo->isCompleted() && todo->hasDueDate() && todo->dtDue().date() <= mEndDate) { 0144 if (!taskHeaderWasCreated) { 0145 createTaskRow(kil); 0146 taskHeaderWasCreated = true; 0147 } 0148 appendTodo(calendar, todo); 0149 } 0150 } 0151 bool gotone = false; 0152 int priority = 1; 0153 while (!gotone && priority <= 9) { 0154 for (const KCalendarCore::Todo::Ptr &todo : std::as_const(todos)) { 0155 const auto calendar = calendar3(todo); 0156 if (!todo->isCompleted() && (todo->priority() == priority)) { 0157 if (!taskHeaderWasCreated) { 0158 createTaskRow(kil); 0159 taskHeaderWasCreated = true; 0160 } 0161 appendTodo(calendar, todo); 0162 gotone = true; 0163 } 0164 } 0165 priority++; 0166 } 0167 if (taskHeaderWasCreated) { 0168 mText += QLatin1StringView("</ul>\n"); 0169 } 0170 } 0171 0172 QStringList myEmails(CalendarSupport::KCalPrefs::instance()->allEmails()); 0173 int replies = 0; 0174 events.clear(); 0175 for (const auto &calendar : calendars()) { 0176 events += calendar->events(QDate::currentDate(), QDate(2975, 12, 6), QTimeZone::systemTimeZone()); 0177 } 0178 for (const KCalendarCore::Event::Ptr &ev : std::as_const(events)) { 0179 const auto calendar = calendar3(ev); 0180 KCalendarCore::Attendee me = ev->attendeeByMails(myEmails); 0181 if (!me.isNull()) { 0182 if (me.status() == KCalendarCore::Attendee::NeedsAction && me.RSVP()) { 0183 if (replies == 0) { 0184 mText += QLatin1StringView("<p></p>"); 0185 kil->loadIcon(QStringLiteral("mail-reply-sender"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath); 0186 mText += QLatin1StringView("<h2><img src=\""); 0187 mText += ipath; 0188 mText += QLatin1StringView(R"(" width="22" height="22">)"); 0189 mText += i18n("Events and to-dos that need a reply:") + QLatin1StringView("</h2>\n"); 0190 mText += QLatin1StringView("<table>\n"); 0191 } 0192 replies++; 0193 appendEvent(calendar, ev); 0194 } 0195 } 0196 } 0197 0198 todos.clear(); 0199 for (const auto &calendar : calendars()) { 0200 todos += calendar->todos(); 0201 } 0202 for (const KCalendarCore::Todo::Ptr &to : std::as_const(todos)) { 0203 const auto calendar = calendar3(to); 0204 KCalendarCore::Attendee me = to->attendeeByMails(myEmails); 0205 if (!me.isNull()) { 0206 if (me.status() == KCalendarCore::Attendee::NeedsAction && me.RSVP()) { 0207 if (replies == 0) { 0208 mText += QLatin1StringView("<p></p>"); 0209 kil->loadIcon(QStringLiteral("mail-reply-sender"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath); 0210 mText += QLatin1StringView("<h2><img src=\""); 0211 mText += ipath; 0212 mText += QLatin1StringView(R"(" width="22" height="22">)"); 0213 mText += i18n("Events and to-dos that need a reply:") + QLatin1StringView("</h2>\n"); 0214 mText += QLatin1StringView("<table>\n"); 0215 } 0216 replies++; 0217 appendEvent(calendar, to); 0218 } 0219 } 0220 } 0221 if (replies > 0) { 0222 mText += QLatin1StringView("</table>\n"); 0223 } 0224 0225 mText += QLatin1StringView("</td></tr>\n</table>\n"); 0226 0227 mView->setText(mText); 0228 } 0229 0230 void WhatsNextView::showDates(const QDate &start, const QDate &end, const QDate &) 0231 { 0232 mStartDate = start; 0233 mEndDate = end; 0234 updateView(); 0235 } 0236 0237 void WhatsNextView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) 0238 { 0239 Q_UNUSED(incidenceList) 0240 Q_UNUSED(date) 0241 } 0242 0243 void WhatsNextView::changeIncidenceDisplay(const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType) 0244 { 0245 updateView(); 0246 } 0247 0248 void WhatsNextView::appendEvent(const Akonadi::CollectionCalendar::Ptr &calendar, 0249 const KCalendarCore::Incidence::Ptr &incidence, 0250 const QDateTime &start, 0251 const QDateTime &end) 0252 { 0253 mText += QLatin1StringView("<tr><td><b>"); 0254 if (const KCalendarCore::Event::Ptr event = incidence.dynamicCast<KCalendarCore::Event>()) { 0255 auto starttime = start.toLocalTime(); 0256 if (!starttime.isValid()) { 0257 starttime = event->dtStart().toLocalTime(); 0258 } 0259 auto endtime = end.toLocalTime(); 0260 if (!endtime.isValid()) { 0261 endtime = starttime.addSecs(event->dtStart().secsTo(event->dtEnd())); 0262 } 0263 0264 if (starttime.date().daysTo(endtime.date()) >= 1) { 0265 if (event->allDay()) { 0266 mText += i18nc("date from - to", 0267 "%1 - %2", 0268 QLocale().toString(starttime.date(), QLocale::ShortFormat), 0269 QLocale().toString(endtime.date(), QLocale::ShortFormat)); 0270 } else { 0271 mText += 0272 i18nc("date from - to", "%1 - %2", QLocale().toString(starttime, QLocale::ShortFormat), QLocale().toString(endtime, QLocale::ShortFormat)); 0273 } 0274 } else { 0275 if (event->allDay()) { 0276 mText += QLocale().toString(starttime.date(), QLocale::ShortFormat); 0277 } else { 0278 mText += i18nc("date, from - to", 0279 "%1, %2 - %3", 0280 QLocale().toString(starttime.date(), QLocale::ShortFormat), 0281 QLocale().toString(starttime.time(), QLocale::ShortFormat), 0282 QLocale().toString(endtime.time(), QLocale::ShortFormat)); 0283 } 0284 } 0285 } 0286 mText += QLatin1StringView("</b></td>"); 0287 const QString proto = incidence->type() == KCalendarCore::Incidence::TypeTodo ? QStringLiteral("todo") : QStringLiteral("event"); 0288 mText += QStringLiteral(R"(<td><a href="%1:%2?itemId=%3&calendarId=%5">%4</a></td>)") 0289 .arg(proto, incidence->uid(), incidence->customProperty("VOLATILE", "AKONADI-ID"), incidence->summary()) 0290 .arg(calendar->collection().id()); 0291 mText += QLatin1StringView("</tr>\n"); 0292 } 0293 0294 void WhatsNextView::appendTodo(const Akonadi::CollectionCalendar::Ptr &calendar, const KCalendarCore::Incidence::Ptr &incidence) 0295 { 0296 Akonadi::Item aitem = calendar->item(incidence); 0297 if (mTodos.contains(aitem)) { 0298 return; 0299 } 0300 mTodos.append(aitem); 0301 mText += QLatin1StringView("<li>"); 0302 mText += QStringLiteral(R"(<a href="todo:%1?itemId=%2&calendarId=%4">%3</a>)") 0303 .arg(incidence->uid(), incidence->customProperty("VOLATILE", "AKONADI-ID"), incidence->summary()) 0304 .arg(calendar->collection().id()); 0305 0306 if (const KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(aitem)) { 0307 if (todo->hasDueDate()) { 0308 mText += i18nc("to-do due date", " (Due: %1)", KCalUtils::IncidenceFormatter::dateTimeToString(todo->dtDue(), todo->allDay())); 0309 } 0310 mText += QLatin1StringView("</li>\n"); 0311 } 0312 } 0313 0314 static std::optional<Akonadi::Item::Id> idFromQuery(const QUrlQuery &query, const QString &queryValue) 0315 { 0316 const auto strVal = query.queryItemValue(queryValue); 0317 if (strVal.isEmpty()) { 0318 return {}; 0319 } 0320 0321 bool ok = false; 0322 const auto intVal = strVal.toLongLong(&ok); 0323 if (!ok) { 0324 return {}; 0325 } 0326 0327 return intVal; 0328 } 0329 0330 void WhatsNextView::showIncidence(const QUrl &uri) 0331 { 0332 QUrlQuery query(uri); 0333 const auto itemId = idFromQuery(query, QStringLiteral("itemId")); 0334 const auto calendarId = idFromQuery(query, QStringLiteral("calendarId")); 0335 0336 if (!itemId || !calendarId) { 0337 qCWarning(CALENDARVIEW_LOG) << "Invalid URI:" << uri; 0338 return; 0339 } 0340 0341 auto calendar = calendarForCollection(*calendarId); 0342 if (!calendar) { 0343 qCWarning(CALENDARVIEW_LOG) << "Calendar for collection " << *calendarId << " not present in current view"; 0344 return; 0345 } 0346 0347 auto item = calendar->item(*itemId); 0348 if (!item.isValid()) { 0349 qCWarning(CALENDARVIEW_LOG) << "Item " << *itemId << " not found in collection " << *calendarId; 0350 return; 0351 } 0352 0353 Q_EMIT showIncidenceSignal(item); 0354 } 0355 0356 #include "moc_whatsnextview.cpp"