CalendarSupport

utils.cpp
1/*
2 SPDX-FileCopyrightText: 2009, 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
3 SPDX-FileContributor: Frank Osterfeld <osterfeld@kde.org>
4 SPDX-FileContributor: Andras Mantia <andras@kdab.com>
5
6 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
7*/
8
9#include "utils.h"
10#include "calendarsupport_debug.h"
11#include "kcalprefs.h"
12
13#include <Akonadi/AgentInstance>
14#include <Akonadi/AgentManager>
15#include <Akonadi/CalendarUtils>
16#include <Akonadi/CollectionUtils>
17#include <Akonadi/EntityDisplayAttribute>
18#include <Akonadi/EntityTreeModel>
19
20#include <Akonadi/CollectionDialog>
21
22#include <Akonadi/BlockAlarmsAttribute>
23#include <Akonadi/ETMCalendar>
24
25#include <KHolidays/HolidayRegion>
26
27#include <KCalendarCore/CalFilter>
28#include <KCalendarCore/FileStorage>
29#include <KCalendarCore/FreeBusy>
30#include <KCalendarCore/MemoryCalendar>
31
32#include <KCalUtils/DndFactory>
33#include <KCalUtils/ICalDrag>
34#include <KCalUtils/VCalDrag>
35
36#include <KLocalizedString>
37
38#include <QApplication>
39#include <QDrag>
40#include <QFile>
41#include <QMimeData>
42#include <QPointer>
43#include <QStyle>
44#include <QUrlQuery>
45
46KCalendarCore::Event::Ptr CalendarSupport::event(const KCalendarCore::Incidence::Ptr &incidence)
47{
48 if (hasEvent(incidence)) {
50 }
51 return {};
52}
53
54KCalendarCore::Incidence::List CalendarSupport::incidencesFromItems(const Akonadi::Item::List &items)
55{
57 for (const Akonadi::Item &item : items) {
59 incidences.push_back(e);
60 }
61 }
62 return incidences;
63}
64
65KCalendarCore::Todo::Ptr CalendarSupport::todo(const KCalendarCore::Incidence::Ptr &incidence)
66{
67 if (hasTodo(incidence)) {
69 }
70 return {};
71}
72
73KCalendarCore::Journal::Ptr CalendarSupport::journal(const KCalendarCore::Incidence::Ptr &incidence)
74{
75 if (hasJournal(incidence)) {
77 }
78 return {};
79}
80
81bool CalendarSupport::hasIncidence(const Akonadi::Item &item)
82{
84}
85
86bool CalendarSupport::hasEvent(const Akonadi::Item &item)
87{
89}
90
91bool CalendarSupport::hasEvent(const KCalendarCore::Incidence::Ptr &incidence)
92{
94}
95
96bool CalendarSupport::hasTodo(const Akonadi::Item &item)
97{
99}
100
101bool CalendarSupport::hasTodo(const KCalendarCore::Incidence::Ptr &incidence)
102{
104}
105
106bool CalendarSupport::hasJournal(const Akonadi::Item &item)
107{
109}
110
111bool CalendarSupport::hasJournal(const KCalendarCore::Incidence::Ptr &incidence)
112{
114}
115
116#ifndef QT_NO_DRAGANDDROP
117QDrag *CalendarSupport::createDrag(const Akonadi::Item &item, QObject *parent)
118{
119 return createDrag(Akonadi::Item::List() << item, parent);
120}
121
122#endif
123
124static KCalendarCore::IncidenceBase::IncidenceType findMostCommonType(const Akonadi::Item::List &items)
125{
127 for (const Akonadi::Item &item : items) {
128 if (!CalendarSupport::hasIncidence(item)) {
129 continue;
130 }
131 const auto type = Akonadi::CalendarUtils::incidence(item)->type();
132 if (prev != KCalendarCore::IncidenceBase::TypeUnknown && type != prev) {
134 }
135 prev = type;
136 }
137 return prev;
138}
139
140#ifndef QT_NO_DRAGANDDROP
141QDrag *CalendarSupport::createDrag(const Akonadi::Item::List &items, QObject *parent)
142{
143 std::unique_ptr<QDrag> drag(new QDrag(parent));
144 drag->setMimeData(Akonadi::CalendarUtils::createMimeData(items));
145
146 const auto common = findMostCommonType(items);
148 drag->setPixmap(QIcon::fromTheme(QStringLiteral("view-calendar-day")).pixmap(qApp->style()->pixelMetric(QStyle::PM_ToolBarIconSize)));
149 } else if (common == KCalendarCore::IncidenceBase::TypeTodo) {
150 drag->setPixmap(QIcon::fromTheme(QStringLiteral("view-calendar-tasks")).pixmap(qApp->style()->pixelMetric(QStyle::PM_ToolBarIconSize)));
151 }
152
153 return drag.release();
154}
155
156#endif
157
158static bool itemMatches(const Akonadi::Item &item, const KCalendarCore::CalFilter *filter)
159{
160 assert(filter);
162 if (!inc) {
163 return false;
164 }
165 return filter->filterIncidence(inc);
166}
167
168Akonadi::Item::List CalendarSupport::applyCalFilter(const Akonadi::Item::List &items_, const KCalendarCore::CalFilter *filter)
169{
170 Q_ASSERT(filter);
171 Akonadi::Item::List items(items_);
172 items.erase(std::remove_if(items.begin(),
173 items.end(),
174 [filter](const Akonadi::Item &item) {
175 return !itemMatches(item, filter);
176 }),
177 items.end());
178 return items;
179}
180
181bool CalendarSupport::isValidIncidenceItemUrl(const QUrl &url, const QStringList &supportedMimeTypes)
182{
183 if (!url.isValid()) {
184 return false;
185 }
186
187 if (url.scheme() != QLatin1StringView("akonadi")) {
188 return false;
189 }
190
191 return supportedMimeTypes.contains(QUrlQuery(url).queryItemValue(QStringLiteral("type")));
192}
193
194bool CalendarSupport::isValidIncidenceItemUrl(const QUrl &url)
195{
196 return isValidIncidenceItemUrl(url,
199}
200
201static bool containsValidIncidenceItemUrl(const QList<QUrl> &urls)
202{
203 return std::find_if(urls.begin(),
204 urls.end(),
205 [](const QUrl &url) {
206 return CalendarSupport::isValidIncidenceItemUrl(url);
207 })
208 != urls.constEnd();
209}
210
211bool CalendarSupport::canDecode(const QMimeData *md)
212{
213 if (md) {
214 return containsValidIncidenceItemUrl(md->urls()) || KCalUtils::ICalDrag::canDecode(md) || KCalUtils::VCalDrag::canDecode(md);
215 } else {
216 return false;
217 }
218}
219
220QList<QUrl> CalendarSupport::incidenceItemUrls(const QMimeData *mimeData)
221{
222 QList<QUrl> urls;
223 const QList<QUrl> urlsList = mimeData->urls();
224 for (const QUrl &i : urlsList) {
225 if (isValidIncidenceItemUrl(i)) {
226 urls.push_back(i);
227 }
228 }
229 return urls;
230}
231
232QList<QUrl> CalendarSupport::todoItemUrls(const QMimeData *mimeData)
233{
234 QList<QUrl> urls;
235
236 const QList<QUrl> urlList = mimeData->urls();
237 for (const QUrl &i : urlList) {
238 if (isValidIncidenceItemUrl(i, QStringList() << KCalendarCore::Todo::todoMimeType())) {
239 urls.push_back(i);
240 }
241 }
242 return urls;
243}
244
245bool CalendarSupport::mimeDataHasIncidence(const QMimeData *mimeData)
246{
247 return !incidenceItemUrls(mimeData).isEmpty() || !incidences(mimeData).isEmpty();
248}
249
250KCalendarCore::Todo::List CalendarSupport::todos(const QMimeData *mimeData)
251{
253
254#ifndef QT_NO_DRAGANDDROP
256 if (cal) {
257 const KCalendarCore::Todo::List calTodos = cal->todos();
258 todos.reserve(calTodos.count());
259 for (const KCalendarCore::Todo::Ptr &i : calTodos) {
260 todos.push_back(KCalendarCore::Todo::Ptr(i->clone()));
261 }
262 }
263#endif
264
265 return todos;
266}
267
268KCalendarCore::Incidence::List CalendarSupport::incidences(const QMimeData *mimeData)
269{
271
272#ifndef QT_NO_DRAGANDDROP
274 if (cal) {
275 const KCalendarCore::Incidence::List calIncidences = cal->incidences();
276 incidences.reserve(calIncidences.count());
277 for (const KCalendarCore::Incidence::Ptr &i : calIncidences) {
278 incidences.push_back(KCalendarCore::Incidence::Ptr(i->clone()));
279 }
280 }
281#endif
282
283 return incidences;
284}
285
286Akonadi::Collection CalendarSupport::selectCollection(QWidget *parent, int &dialogCode, const QStringList &mimeTypes, const Akonadi::Collection &defCollection)
287{
289 dlg->setWindowTitle(i18nc("@title:window", "Select Calendar"));
290 dlg->setDescription(i18n("Select the calendar where this item will be stored."));
291 dlg->changeCollectionDialogOptions(Akonadi::CollectionDialog::KeepTreeExpanded);
292 qCDebug(CALENDARSUPPORT_LOG) << "selecting collections with mimeType in " << mimeTypes;
293
294 dlg->setMimeTypeFilter(mimeTypes);
295 dlg->setAccessRightsFilter(Akonadi::Collection::CanCreateItem);
296 if (defCollection.isValid()) {
297 dlg->setDefaultCollection(defCollection);
298 }
299 Akonadi::Collection collection;
300
301 // FIXME: don't use exec.
302 dialogCode = dlg->exec();
303 if (dlg && dialogCode == QDialog::Accepted) {
304 collection = dlg->selectedCollection();
305
306 if (!collection.isValid()) {
307 qCWarning(CALENDARSUPPORT_LOG) << "An invalid collection was selected!";
308 }
309 }
310 delete dlg;
311 return collection;
312}
313
314Akonadi::Item CalendarSupport::itemFromIndex(const QModelIndex &idx)
315{
318 return item;
319}
320
321Akonadi::Collection::List CalendarSupport::collectionsFromModel(const QAbstractItemModel *model, const QModelIndex &parentIndex, int start, int end)
322{
323 const int endRow = end >= 0 ? end : model->rowCount(parentIndex) - 1;
324 Akonadi::Collection::List collections;
325 int row = start;
326 QModelIndex i = model->index(row, 0, parentIndex);
327 while (row <= endRow) {
329 if (collection.isValid()) {
330 collections << collection;
331 QModelIndex childIndex = model->index(0, 0, i);
332 if (childIndex.isValid()) {
333 collections << collectionsFromModel(model, i);
334 }
335 }
336 ++row;
337 i = i.sibling(row, 0);
338 }
339 return collections;
340}
341
342Akonadi::Item::List CalendarSupport::itemsFromModel(const QAbstractItemModel *model, const QModelIndex &parentIndex, int start, int end)
343{
344 const int endRow = end >= 0 ? end : model->rowCount(parentIndex) - 1;
346 int row = start;
347 QModelIndex i = model->index(row, 0, parentIndex);
348 while (row <= endRow) {
349 const Akonadi::Item item = itemFromIndex(i);
350 if (CalendarSupport::hasIncidence(item)) {
351 items << item;
352 } else {
353 QModelIndex childIndex = model->index(0, 0, i);
354 if (childIndex.isValid()) {
355 items << itemsFromModel(model, i);
356 }
357 }
358 ++row;
359 i = i.sibling(row, 0);
360 }
361 return items;
362}
363
364Akonadi::Collection::Id CalendarSupport::collectionIdFromIndex(const QModelIndex &index)
365{
367}
368
369Akonadi::Collection::List CalendarSupport::collectionsFromIndexes(const QModelIndexList &indexes)
370{
372 l.reserve(indexes.count());
373 for (const QModelIndex &idx : indexes) {
375 }
376 return l;
377}
378
379QString CalendarSupport::toolTipString(const Akonadi::Collection &coll, bool richText)
380{
381 Q_UNUSED(richText)
382
383 QString str = QStringLiteral("<qt>");
384
385 // Display Name
389 }
390
391 if (displayName.isEmpty()) {
392 displayName = coll.name();
393 }
394 if (coll.id() == CalendarSupport::KCalPrefs::instance()->defaultCalendarId()) {
395 displayName = i18nc("this is the default calendar", "%1 (Default Calendar)", displayName);
396 }
397 str += QLatin1StringView("<b>") + displayName + QLatin1StringView("</b>");
398 str += QLatin1StringView("<hr>");
399
400 // Calendar Type
401 QString calendarType;
402 if (!coll.isVirtual()) {
404 calendarType = instance.type().name();
405 } else {
406 calendarType = i18nc("a virtual folder type", "Virtual");
407 }
408 str += QLatin1StringView("<i>") + i18n("Folder type:") + QLatin1StringView("</i>");
409 str += QLatin1StringView("&nbsp;") + calendarType;
410
411 // Content Type
412 QStringList mimeTypes = coll.contentMimeTypes();
413 mimeTypes.removeAll(QStringLiteral("inode/directory"));
414 QString mimeTypeStr;
415 if (!mimeTypes.isEmpty()) {
416 mimeTypeStr = QLocale().createSeparatedList(mimeTypes.replaceInStrings(QStringLiteral("application/x-vnd.akonadi.calendar."), QString()));
417 } else {
418 mimeTypeStr = i18nc("collection has no mimetypes to show the user", "none");
419 }
420 str += QLatin1StringView("<br>");
421 str += QLatin1StringView("<i>") + i18n("Content type:") + QLatin1StringView("</i>");
422 str += QLatin1StringView("&nbsp;") + mimeTypeStr;
423 str += QLatin1StringView("</br>");
424
425 // Read only?
426 bool isReadOnly = !(coll.rights() & Akonadi::Collection::CanChangeItem);
427 str += QLatin1StringView("<br>");
428 str += QLatin1StringView("<i>") + i18n("Rights:") + QLatin1StringView("</i>");
429 str += QLatin1StringView("&nbsp;");
430 if (isReadOnly) {
431 str += i18nc("the calendar is read-only", "read-only");
432 } else {
433 str += i18nc("the calendar is read and write", "read+write");
434 }
435 str += QLatin1StringView("</br>");
436
437 // Blocking reminders?
438 QStringList blockList;
441 blockList << i18nc("blocking all reminders for this calendar", "all");
442 } else {
444 blockList << i18nc("blocking audio reminders for this calendar", "audio");
446 blockList << i18nc("blocking display pop-up dialog reminders for this calendar", "display");
448 blockList << i18nc("blocking email reminders for this calendar", "email");
450 blockList << i18nc("blocking run a command reminders for this calendar", "procedure");
451 } else {
452 blockList << i18nc("blocking unknown type reminders for this calendar", "other");
453 }
454 }
455 } else {
456 blockList << i18nc("not blocking any reminder types for this calendar", "none");
457 }
458 str += QLatin1StringView("<br>");
459 str += QLatin1StringView("<i>") + i18n("Blocked Reminders:") + QLatin1StringView("</i>");
460 str += QLatin1StringView("&nbsp;");
461 str += QLocale().createSeparatedList(blockList);
462 str += QLatin1StringView("</br>");
463
464 str += QLatin1StringView("</qt>");
465 return str;
466}
467
468QString CalendarSupport::subMimeTypeForIncidence(const KCalendarCore::Incidence::Ptr &incidence)
469{
470 return incidence->mimeType();
471}
472
473QList<QDate> CalendarSupport::workDays(QDate startDate, QDate endDate)
474{
475 QList<QDate> result;
476
477 const int mask(~(KCalPrefs::instance()->mWorkWeekMask));
478 const int numDays = startDate.daysTo(endDate) + 1;
479
480 for (int i = 0; i < numDays; ++i) {
481 const QDate date = startDate.addDays(i);
482 if (!(mask & (1 << (date.dayOfWeek() - 1)))) {
483 result.append(date);
484 }
485 }
486
487 if (KCalPrefs::instance()->mExcludeHolidays) {
488 const QStringList holidays = KCalPrefs::instance()->mHolidays;
489 for (const QString &regionStr : holidays) {
490 KHolidays::HolidayRegion region(regionStr);
491 if (region.isValid()) {
492 for (auto const &h : region.rawHolidaysWithAstroSeasons(startDate, endDate)) {
493 if (h.dayType() == KHolidays::Holiday::NonWorkday) {
494 for (int i = 0; i < h.duration(); i++) {
495 result.removeOne(h.observedStartDate().addDays(i));
496 }
497 }
498 }
499 }
500 }
501 }
502
503 return result;
504}
505
506QStringList CalendarSupport::holiday(QDate date)
507{
508 QStringList hdays;
509
510 bool showCountryCode = (KCalPrefs::instance()->mHolidays.count() > 1);
511 const QStringList holidays = KCalPrefs::instance()->mHolidays;
512 for (const QString &regionStr : holidays) {
513 KHolidays::HolidayRegion region(regionStr);
514 if (region.isValid()) {
515 const KHolidays::Holiday::List list = region.rawHolidaysWithAstroSeasons(date);
516 const int listCount = list.count();
517 for (int i = 0; i < listCount; ++i) {
518 // don't add duplicates.
519 // TODO: won't find duplicates in different languages however.
520 const QString name = list.at(i).name();
521 if (showCountryCode) {
522 // If more than one holiday region, append the country code to the holiday
523 // display name to help the user identify which region it belongs to.
524 const QRegularExpression holidaySE(i18nc("search pattern for holidayname", "^%1", name));
525 if (hdays.filter(holidaySE).isEmpty()) {
526 const QString pholiday = i18n("%1 (%2)", name, region.countryCode());
527 hdays.append(pholiday);
528 } else {
529 // More than 1 region has the same holiday => remove the country code
530 // i.e don't show "Holiday (US)" and "Holiday(FR)"; just show "Holiday".
531 const QRegularExpression holidayRE(i18nc("replace pattern for holidayname (countrycode)", "^%1 \\(.*\\)", name));
532 hdays.replaceInStrings(holidayRE, name);
533 hdays.removeDuplicates();
534 }
535 } else {
536 if (!hdays.contains(name)) {
537 hdays.append(name);
538 }
539 }
540 }
541 }
542 }
543
544 return hdays;
545}
546
547QStringList CalendarSupport::categories(const KCalendarCore::Incidence::List &incidences)
548{
549 QStringList cats;
550 QStringList thisCats;
551 // @TODO: For now just iterate over all incidences. In the future,
552 // the list of categories should be built when reading the file.
553 for (const KCalendarCore::Incidence::Ptr &incidence : incidences) {
554 thisCats = incidence->categories();
555 const QStringList::ConstIterator send(thisCats.constEnd());
556 for (QStringList::ConstIterator si = thisCats.constBegin(); si != send; ++si) {
557 if (!cats.contains(*si)) {
558 cats.append(*si);
559 }
560 }
561 }
562 return cats;
563}
564
565bool CalendarSupport::mergeCalendar(const QString &srcFilename, const KCalendarCore::Calendar::Ptr &destCalendar)
566{
567 if (srcFilename.isEmpty()) {
568 qCCritical(CALENDARSUPPORT_LOG) << "Empty filename.";
569 return false;
570 }
571
572 if (!QFile::exists(srcFilename)) {
573 qCCritical(CALENDARSUPPORT_LOG) << "File'" << srcFilename << "' doesn't exist.";
574 }
575
576 // merge in a file
577 destCalendar->startBatchAdding();
578 KCalendarCore::FileStorage storage(destCalendar);
579 storage.setFileName(srcFilename);
580 bool loadedSuccesfully = storage.load();
581 destCalendar->endBatchAdding();
582
583 return loadedSuccesfully;
584}
585
586void CalendarSupport::createAlarmReminder(const KCalendarCore::Alarm::Ptr &alarm, KCalendarCore::IncidenceBase::IncidenceType type)
587{
588 int duration; // in secs
589 switch (CalendarSupport::KCalPrefs::instance()->mReminderTimeUnits) {
590 default:
591 case 0: // mins
592 duration = CalendarSupport::KCalPrefs::instance()->mReminderTime * 60;
593 break;
594 case 1: // hours
595 duration = CalendarSupport::KCalPrefs::instance()->mReminderTime * 60 * 60;
596 break;
597 case 2: // days
598 duration = CalendarSupport::KCalPrefs::instance()->mReminderTime * 60 * 60 * 24;
599 break;
600 }
601 alarm->setType(KCalendarCore::Alarm::Display);
602 alarm->setEnabled(true);
604 alarm->setStartOffset(KCalendarCore::Duration(-duration));
605 } else {
606 alarm->setEndOffset(KCalendarCore::Duration(-duration));
607 }
608}
AgentType type() const
static AgentManager * self()
AgentInstance instance(const QString &identifier) const
QString name() const
bool isAlarmTypeBlocked(KCalendarCore::Alarm::Type type) const
QString resource() const
QStringList contentMimeTypes() const
bool isValid() const
const T * attribute() const
Rights rights() const
QString name() const
bool hasAttribute() const
bool isVirtual() const
void setParentCollection(const Collection &parent)
bool hasPayload() const
KCalendarCore::Calendar::Ptr createDropCalendar(QDropEvent *de)
static QLatin1String eventMimeType()
static QLatin1String freeBusyMimeType()
static QLatin1String journalMimeType()
static QLatin1String todoMimeType()
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
AKONADI_CALENDAR_EXPORT QMimeData * createMimeData(const Akonadi::Item::List &items)
AKONADICORE_EXPORT Collection fromIndex(const QModelIndex &index)
Type type(const QSqlDatabase &db)
KCALUTILS_EXPORT bool canDecode(const QMimeData *)
KCALUTILS_EXPORT bool canDecode(const QMimeData *)
KGUIADDONS_EXPORT QDrag * createDrag(const QColor &color, QObject *dragsource)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
const QList< QKeySequence > & end()
QString name(StandardShortcut id)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
virtual int rowCount(const QModelIndex &parent) const const=0
QDate addDays(qint64 ndays) const const
int dayOfWeek() const const
qint64 daysTo(QDate d) const const
bool exists() const const
QIcon fromTheme(const QString &name)
typedef ConstIterator
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
iterator end()
iterator erase(const_iterator begin, const_iterator end)
bool isEmpty() const const
void push_back(parameter_type value)
qsizetype removeAll(const AT &t)
bool removeOne(const AT &t)
void reserve(qsizetype size)
QString createSeparatedList(const QStringList &list) const const
QList< QUrl > urls() const const
QVariant data(int role) const const
bool isValid() const const
QModelIndex sibling(int row, int column) const const
T qobject_cast(QObject *object)
QSharedPointer< X > staticCast() const const
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QStringList filter(QStringView str, Qt::CaseSensitivity cs) const const
qsizetype removeDuplicates()
QStringList & replaceInStrings(QStringView before, QStringView after, Qt::CaseSensitivity cs)
PM_ToolBarIconSize
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
bool isValid() const const
QString scheme() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:16:32 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.