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/EntityDisplayAttribute>
16 #include <Akonadi/EntityTreeModel>
17
18 #include <Akonadi/CollectionDialog>
19
20 #include <Akonadi/Calendar/BlockAlarmsAttribute>
21 #include <Akonadi/Calendar/ETMCalendar>
22
23 #include <KHolidays/HolidayRegion>
24
25 #include <KCalendarCore/CalFilter>
26 #include <KCalendarCore/FileStorage>
27 #include <KCalendarCore/FreeBusy>
28 #include <KCalendarCore/MemoryCalendar>
29
30 #include <KCalUtils/DndFactory>
31 #include <KCalUtils/ICalDrag>
32 #include <KCalUtils/VCalDrag>
33
34 #include <KLocalizedString>
35
36 #include <QApplication>
37 #include <QDrag>
38 #include <QFile>
39 #include <QMimeData>
40 #include <QPointer>
41 #include <QStyle>
42 #include <QUrlQuery>
43
incidence(const Akonadi::Item & item)44 KCalendarCore::Incidence::Ptr CalendarSupport::incidence(const Akonadi::Item &item)
45 {
46 // relying on exception for performance reasons
47 try {
48 return item.payload<KCalendarCore::Incidence::Ptr>();
49 } catch (const Akonadi::PayloadException &) {
50 return KCalendarCore::Incidence::Ptr();
51 }
52 }
53
event(const Akonadi::Item & item)54 KCalendarCore::Event::Ptr CalendarSupport::event(const Akonadi::Item &item)
55 {
56 // relying on exception for performance reasons
57 try {
58 auto incidence = item.payload<KCalendarCore::Incidence::Ptr>();
59 if (hasEvent(incidence)) {
60 return item.payload<KCalendarCore::Event::Ptr>();
61 }
62 } catch (const Akonadi::PayloadException &) {
63 return KCalendarCore::Event::Ptr();
64 }
65 return KCalendarCore::Event::Ptr();
66 }
67
event(const KCalendarCore::Incidence::Ptr & incidence)68 KCalendarCore::Event::Ptr CalendarSupport::event(const KCalendarCore::Incidence::Ptr &incidence)
69 {
70 if (hasEvent(incidence)) {
71 return incidence.staticCast<KCalendarCore::Event>();
72 }
73 return KCalendarCore::Event::Ptr();
74 }
75
incidencesFromItems(const Akonadi::Item::List & items)76 KCalendarCore::Incidence::List CalendarSupport::incidencesFromItems(const Akonadi::Item::List &items)
77 {
78 KCalendarCore::Incidence::List incidences;
79 for (const Akonadi::Item &item : items) {
80 if (const KCalendarCore::Incidence::Ptr e = CalendarSupport::incidence(item)) {
81 incidences.push_back(e);
82 }
83 }
84 return incidences;
85 }
86
todo(const Akonadi::Item & item)87 KCalendarCore::Todo::Ptr CalendarSupport::todo(const Akonadi::Item &item)
88 {
89 try {
90 auto incidence = item.payload<KCalendarCore::Incidence::Ptr>();
91 if (hasTodo(incidence)) {
92 return item.payload<KCalendarCore::Todo::Ptr>();
93 }
94 } catch (const Akonadi::PayloadException &) {
95 return KCalendarCore::Todo::Ptr();
96 }
97 return KCalendarCore::Todo::Ptr();
98 }
99
todo(const KCalendarCore::Incidence::Ptr & incidence)100 KCalendarCore::Todo::Ptr CalendarSupport::todo(const KCalendarCore::Incidence::Ptr &incidence)
101 {
102 if (hasTodo(incidence)) {
103 return incidence.staticCast<KCalendarCore::Todo>();
104 }
105 return KCalendarCore::Todo::Ptr();
106 }
107
journal(const Akonadi::Item & item)108 KCalendarCore::Journal::Ptr CalendarSupport::journal(const Akonadi::Item &item)
109 {
110 try {
111 auto incidence = item.payload<KCalendarCore::Incidence::Ptr>();
112 if (hasJournal(incidence)) {
113 return item.payload<KCalendarCore::Journal::Ptr>();
114 }
115 } catch (const Akonadi::PayloadException &) {
116 return KCalendarCore::Journal::Ptr();
117 }
118 return KCalendarCore::Journal::Ptr();
119 }
120
journal(const KCalendarCore::Incidence::Ptr & incidence)121 KCalendarCore::Journal::Ptr CalendarSupport::journal(const KCalendarCore::Incidence::Ptr &incidence)
122 {
123 if (hasJournal(incidence)) {
124 return incidence.staticCast<KCalendarCore::Journal>();
125 }
126 return KCalendarCore::Journal::Ptr();
127 }
128
hasIncidence(const Akonadi::Item & item)129 bool CalendarSupport::hasIncidence(const Akonadi::Item &item)
130 {
131 return item.hasPayload<KCalendarCore::Incidence::Ptr>();
132 }
133
hasEvent(const Akonadi::Item & item)134 bool CalendarSupport::hasEvent(const Akonadi::Item &item)
135 {
136 return item.hasPayload<KCalendarCore::Event::Ptr>();
137 }
138
hasEvent(const KCalendarCore::Incidence::Ptr & incidence)139 bool CalendarSupport::hasEvent(const KCalendarCore::Incidence::Ptr &incidence)
140 {
141 return incidence && incidence->type() == KCalendarCore::Incidence::TypeEvent;
142 }
143
hasTodo(const Akonadi::Item & item)144 bool CalendarSupport::hasTodo(const Akonadi::Item &item)
145 {
146 return item.hasPayload<KCalendarCore::Todo::Ptr>();
147 }
148
hasTodo(const KCalendarCore::Incidence::Ptr & incidence)149 bool CalendarSupport::hasTodo(const KCalendarCore::Incidence::Ptr &incidence)
150 {
151 return incidence && incidence->type() == KCalendarCore::Incidence::TypeTodo;
152 }
153
hasJournal(const Akonadi::Item & item)154 bool CalendarSupport::hasJournal(const Akonadi::Item &item)
155 {
156 return item.hasPayload<KCalendarCore::Journal::Ptr>();
157 }
158
hasJournal(const KCalendarCore::Incidence::Ptr & incidence)159 bool CalendarSupport::hasJournal(const KCalendarCore::Incidence::Ptr &incidence)
160 {
161 return incidence && incidence->type() == KCalendarCore::Incidence::TypeJournal;
162 }
163
createMimeData(const Akonadi::Item::List & items)164 QMimeData *CalendarSupport::createMimeData(const Akonadi::Item::List &items)
165 {
166 if (items.isEmpty()) {
167 return nullptr;
168 }
169
170 KCalendarCore::MemoryCalendar::Ptr cal(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone()));
171
172 QList<QUrl> urls;
173 int incidencesFound = 0;
174 for (const Akonadi::Item &item : items) {
175 const KCalendarCore::Incidence::Ptr incidence(CalendarSupport::incidence(item));
176 if (!incidence) {
177 continue;
178 }
179 ++incidencesFound;
180 urls.push_back(item.url());
181 KCalendarCore::Incidence::Ptr i(incidence->clone());
182 cal->addIncidence(i);
183 }
184
185 if (incidencesFound == 0) {
186 return nullptr;
187 }
188
189 std::unique_ptr<QMimeData> mimeData(new QMimeData);
190
191 mimeData->setUrls(urls);
192
193 if (KCalUtils::ICalDrag::populateMimeData(mimeData.get(), cal)) {
194 return mimeData.release();
195 } else {
196 return nullptr;
197 }
198 }
199
createMimeData(const Akonadi::Item & item)200 QMimeData *CalendarSupport::createMimeData(const Akonadi::Item &item)
201 {
202 return createMimeData(Akonadi::Item::List() << item);
203 }
204
205 #ifndef QT_NO_DRAGANDDROP
createDrag(const Akonadi::Item & item,QWidget * parent)206 QDrag *CalendarSupport::createDrag(const Akonadi::Item &item, QWidget *parent)
207 {
208 return createDrag(Akonadi::Item::List() << item, parent);
209 }
210
211 #endif
212
findMostCommonType(const Akonadi::Item::List & items)213 static QByteArray findMostCommonType(const Akonadi::Item::List &items)
214 {
215 QByteArray prev;
216 if (items.isEmpty()) {
217 return "Incidence";
218 }
219
220 for (const Akonadi::Item &item : items) {
221 if (!CalendarSupport::hasIncidence(item)) {
222 continue;
223 }
224 const QByteArray type = CalendarSupport::incidence(item)->typeStr();
225 if (!prev.isEmpty() && type != prev) {
226 return "Incidence";
227 }
228 prev = type;
229 }
230 return prev;
231 }
232
233 #ifndef QT_NO_DRAGANDDROP
createDrag(const Akonadi::Item::List & items,QWidget * parent)234 QDrag *CalendarSupport::createDrag(const Akonadi::Item::List &items, QWidget *parent)
235 {
236 std::unique_ptr<QDrag> drag(new QDrag(parent));
237 drag->setMimeData(CalendarSupport::createMimeData(items));
238
239 const QByteArray common = findMostCommonType(items);
240 if (common == "Event") {
241 drag->setPixmap(QIcon::fromTheme(QStringLiteral("view-calendar-day")).pixmap(qApp->style()->pixelMetric(QStyle::PM_ToolBarIconSize)));
242 } else if (common == "Todo") {
243 drag->setPixmap(QIcon::fromTheme(QStringLiteral("view-calendar-tasks")).pixmap(qApp->style()->pixelMetric(QStyle::PM_ToolBarIconSize)));
244 }
245
246 return drag.release();
247 }
248
249 #endif
250
itemMatches(const Akonadi::Item & item,const KCalendarCore::CalFilter * filter)251 static bool itemMatches(const Akonadi::Item &item, const KCalendarCore::CalFilter *filter)
252 {
253 assert(filter);
254 KCalendarCore::Incidence::Ptr inc = CalendarSupport::incidence(item);
255 if (!inc) {
256 return false;
257 }
258 return filter->filterIncidence(inc);
259 }
260
applyCalFilter(const Akonadi::Item::List & items_,const KCalendarCore::CalFilter * filter)261 Akonadi::Item::List CalendarSupport::applyCalFilter(const Akonadi::Item::List &items_, const KCalendarCore::CalFilter *filter)
262 {
263 Q_ASSERT(filter);
264 Akonadi::Item::List items(items_);
265 items.erase(std::remove_if(items.begin(),
266 items.end(),
267 [filter](const Akonadi::Item &item) {
268 return !itemMatches(item, filter);
269 }),
270 items.end());
271 return items;
272 }
273
isValidIncidenceItemUrl(const QUrl & url,const QStringList & supportedMimeTypes)274 bool CalendarSupport::isValidIncidenceItemUrl(const QUrl &url, const QStringList &supportedMimeTypes)
275 {
276 if (!url.isValid()) {
277 return false;
278 }
279
280 if (url.scheme() != QLatin1String("akonadi")) {
281 return false;
282 }
283
284 return supportedMimeTypes.contains(QUrlQuery(url).queryItemValue(QStringLiteral("type")));
285 }
286
isValidIncidenceItemUrl(const QUrl & url)287 bool CalendarSupport::isValidIncidenceItemUrl(const QUrl &url)
288 {
289 return isValidIncidenceItemUrl(url,
290 QStringList() << KCalendarCore::Event::eventMimeType() << KCalendarCore::Todo::todoMimeType()
291 << KCalendarCore::Journal::journalMimeType() << KCalendarCore::FreeBusy::freeBusyMimeType());
292 }
293
containsValidIncidenceItemUrl(const QList<QUrl> & urls)294 static bool containsValidIncidenceItemUrl(const QList<QUrl> &urls)
295 {
296 return std::find_if(urls.begin(),
297 urls.end(),
298 [](const QUrl &url) {
299 return CalendarSupport::isValidIncidenceItemUrl(url);
300 })
301 != urls.constEnd();
302 }
303
canDecode(const QMimeData * md)304 bool CalendarSupport::canDecode(const QMimeData *md)
305 {
306 if (md) {
307 return containsValidIncidenceItemUrl(md->urls()) || KCalUtils::ICalDrag::canDecode(md) || KCalUtils::VCalDrag::canDecode(md);
308 } else {
309 return false;
310 }
311 }
312
incidenceItemUrls(const QMimeData * mimeData)313 QList<QUrl> CalendarSupport::incidenceItemUrls(const QMimeData *mimeData)
314 {
315 QList<QUrl> urls;
316 const QList<QUrl> urlsList = mimeData->urls();
317 for (const QUrl &i : urlsList) {
318 if (isValidIncidenceItemUrl(i)) {
319 urls.push_back(i);
320 }
321 }
322 return urls;
323 }
324
todoItemUrls(const QMimeData * mimeData)325 QList<QUrl> CalendarSupport::todoItemUrls(const QMimeData *mimeData)
326 {
327 QList<QUrl> urls;
328
329 const QList<QUrl> urlList = mimeData->urls();
330 for (const QUrl &i : urlList) {
331 if (isValidIncidenceItemUrl(i, QStringList() << KCalendarCore::Todo::todoMimeType())) {
332 urls.push_back(i);
333 }
334 }
335 return urls;
336 }
337
mimeDataHasIncidence(const QMimeData * mimeData)338 bool CalendarSupport::mimeDataHasIncidence(const QMimeData *mimeData)
339 {
340 return !incidenceItemUrls(mimeData).isEmpty() || !incidences(mimeData).isEmpty();
341 }
342
todos(const QMimeData * mimeData)343 KCalendarCore::Todo::List CalendarSupport::todos(const QMimeData *mimeData)
344 {
345 KCalendarCore::Todo::List todos;
346
347 #ifndef QT_NO_DRAGANDDROP
348 KCalendarCore::Calendar::Ptr cal(KCalUtils::DndFactory::createDropCalendar(mimeData));
349 if (cal) {
350 const KCalendarCore::Todo::List calTodos = cal->todos();
351 todos.reserve(calTodos.count());
352 for (const KCalendarCore::Todo::Ptr &i : calTodos) {
353 todos.push_back(KCalendarCore::Todo::Ptr(i->clone()));
354 }
355 }
356 #endif
357
358 return todos;
359 }
360
incidences(const QMimeData * mimeData)361 KCalendarCore::Incidence::List CalendarSupport::incidences(const QMimeData *mimeData)
362 {
363 KCalendarCore::Incidence::List incidences;
364
365 #ifndef QT_NO_DRAGANDDROP
366 KCalendarCore::Calendar::Ptr cal(KCalUtils::DndFactory::createDropCalendar(mimeData));
367 if (cal) {
368 const KCalendarCore::Incidence::List calIncidences = cal->incidences();
369 incidences.reserve(calIncidences.count());
370 for (const KCalendarCore::Incidence::Ptr &i : calIncidences) {
371 incidences.push_back(KCalendarCore::Incidence::Ptr(i->clone()));
372 }
373 }
374 #endif
375
376 return incidences;
377 }
378
selectCollection(QWidget * parent,int & dialogCode,const QStringList & mimeTypes,const Akonadi::Collection & defCollection)379 Akonadi::Collection CalendarSupport::selectCollection(QWidget *parent, int &dialogCode, const QStringList &mimeTypes, const Akonadi::Collection &defCollection)
380 {
381 QPointer<Akonadi::CollectionDialog> dlg(new Akonadi::CollectionDialog(parent));
382 dlg->setWindowTitle(i18nc("@title:window", "Select Calendar"));
383 dlg->setDescription(i18n("Select the calendar where this item will be stored."));
384 dlg->changeCollectionDialogOptions(Akonadi::CollectionDialog::KeepTreeExpanded);
385 qCDebug(CALENDARSUPPORT_LOG) << "selecting collections with mimeType in " << mimeTypes;
386
387 dlg->setMimeTypeFilter(mimeTypes);
388 dlg->setAccessRightsFilter(Akonadi::Collection::CanCreateItem);
389 if (defCollection.isValid()) {
390 dlg->setDefaultCollection(defCollection);
391 }
392 Akonadi::Collection collection;
393
394 // FIXME: don't use exec.
395 dialogCode = dlg->exec();
396 if (dlg && dialogCode == QDialog::Accepted) {
397 collection = dlg->selectedCollection();
398
399 if (!collection.isValid()) {
400 qCWarning(CALENDARSUPPORT_LOG) << "An invalid collection was selected!";
401 }
402 }
403 delete dlg;
404 return collection;
405 }
406
itemFromIndex(const QModelIndex & idx)407 Akonadi::Item CalendarSupport::itemFromIndex(const QModelIndex &idx)
408 {
409 auto item = idx.data(Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
410 item.setParentCollection(idx.data(Akonadi::EntityTreeModel::ParentCollectionRole).value<Akonadi::Collection>());
411 return item;
412 }
413
collectionsFromModel(const QAbstractItemModel * model,const QModelIndex & parentIndex,int start,int end)414 Akonadi::Collection::List CalendarSupport::collectionsFromModel(const QAbstractItemModel *model, const QModelIndex &parentIndex, int start, int end)
415 {
416 const int endRow = end >= 0 ? end : model->rowCount(parentIndex) - 1;
417 Akonadi::Collection::List collections;
418 int row = start;
419 QModelIndex i = model->index(row, 0, parentIndex);
420 while (row <= endRow) {
421 const Akonadi::Collection collection = collectionFromIndex(i);
422 if (collection.isValid()) {
423 collections << collection;
424 QModelIndex childIndex = model->index(0, 0, i);
425 if (childIndex.isValid()) {
426 collections << collectionsFromModel(model, i);
427 }
428 }
429 ++row;
430 i = i.sibling(row, 0);
431 }
432 return collections;
433 }
434
itemsFromModel(const QAbstractItemModel * model,const QModelIndex & parentIndex,int start,int end)435 Akonadi::Item::List CalendarSupport::itemsFromModel(const QAbstractItemModel *model, const QModelIndex &parentIndex, int start, int end)
436 {
437 const int endRow = end >= 0 ? end : model->rowCount(parentIndex) - 1;
438 Akonadi::Item::List items;
439 int row = start;
440 QModelIndex i = model->index(row, 0, parentIndex);
441 while (row <= endRow) {
442 const Akonadi::Item item = itemFromIndex(i);
443 if (CalendarSupport::hasIncidence(item)) {
444 items << item;
445 } else {
446 QModelIndex childIndex = model->index(0, 0, i);
447 if (childIndex.isValid()) {
448 items << itemsFromModel(model, i);
449 }
450 }
451 ++row;
452 i = i.sibling(row, 0);
453 }
454 return items;
455 }
456
collectionFromIndex(const QModelIndex & index)457 Akonadi::Collection CalendarSupport::collectionFromIndex(const QModelIndex &index)
458 {
459 return index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
460 }
461
collectionIdFromIndex(const QModelIndex & index)462 Akonadi::Collection::Id CalendarSupport::collectionIdFromIndex(const QModelIndex &index)
463 {
464 return index.data(Akonadi::EntityTreeModel::CollectionIdRole).value<Akonadi::Collection::Id>();
465 }
466
collectionsFromIndexes(const QModelIndexList & indexes)467 Akonadi::Collection::List CalendarSupport::collectionsFromIndexes(const QModelIndexList &indexes)
468 {
469 Akonadi::Collection::List l;
470 l.reserve(indexes.count());
471 for (const QModelIndex &idx : indexes) {
472 l.push_back(collectionFromIndex(idx));
473 }
474 return l;
475 }
476
displayName(Akonadi::ETMCalendar * calendar,const Akonadi::Collection & c)477 QString CalendarSupport::displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &c)
478 {
479 Akonadi::Collection fullCollection;
480 if (calendar && calendar->collection(c.id()).isValid()) {
481 fullCollection = calendar->collection(c.id());
482 } else {
483 fullCollection = c;
484 }
485
486 QString cName = fullCollection.name();
487 const QString resourceName = fullCollection.resource();
488
489 // Kolab Groupware
490 if (resourceName.contains(QLatin1String("kolab"))) {
491 QString typeStr = cName; // contents type: "Calendar", "Tasks", etc
492 QString ownerStr; // folder owner: "fred", "ethel", etc
493 QString nameStr; // folder name: "Public", "Test", etc
494 if (calendar) {
495 Akonadi::Collection p = c.parentCollection();
496 while (p != Akonadi::Collection::root()) {
497 Akonadi::Collection tCol = calendar->collection(p.id());
498 const QString tName = tCol.name();
499 if (tName.startsWith(QLatin1String("shared.cal"), Qt::CaseInsensitive)) {
500 ownerStr = QStringLiteral("Shared");
501 nameStr = cName;
502 typeStr = i18n("Calendar");
503 break;
504 } else if (tName.startsWith(QLatin1String("shared.tasks"), Qt::CaseInsensitive)
505 || tName.startsWith(QLatin1String("shared.todo"), Qt::CaseInsensitive)) {
506 ownerStr = QStringLiteral("Shared");
507 nameStr = cName;
508 typeStr = i18n("Tasks");
509 break;
510 } else if (tName.startsWith(QLatin1String("shared.journal"), Qt::CaseInsensitive)) {
511 ownerStr = QStringLiteral("Shared");
512 nameStr = cName;
513 typeStr = i18n("Journal");
514 break;
515 } else if (tName.startsWith(QLatin1String("shared.notes"), Qt::CaseInsensitive)) {
516 ownerStr = QStringLiteral("Shared");
517 nameStr = cName;
518 typeStr = i18n("Notes");
519 break;
520 } else if (tName != i18n("Calendar") && tName != i18n("Tasks") && tName != i18n("Journal") && tName != i18n("Notes")) {
521 ownerStr = tName;
522 break;
523 } else {
524 nameStr = typeStr;
525 typeStr = tName;
526 }
527 p = p.parentCollection();
528 }
529 }
530
531 if (!ownerStr.isEmpty()) {
532 if (!ownerStr.compare(QLatin1String("INBOX"), Qt::CaseInsensitive)) {
533 return i18nc("%1 is folder contents", "My Kolab %1", typeStr);
534 } else if (!ownerStr.compare(QLatin1String("SHARED"), Qt::CaseInsensitive) || !ownerStr.compare(QLatin1String("CALENDAR"), Qt::CaseInsensitive)
535 || !ownerStr.compare(QLatin1String("RESOURCES"), Qt::CaseInsensitive)) {
536 return i18nc("%1 is folder name, %2 is folder contents", "Shared Kolab %1 %2", nameStr, typeStr);
537 } else {
538 if (nameStr.isEmpty()) {
539 return i18nc("%1 is folder owner name, %2 is folder contents", "%1's Kolab %2", ownerStr, typeStr);
540 } else {
541 return i18nc("%1 is folder owner name, %2 is folder name, %3 is folder contents", "%1's %2 Kolab %3", ownerStr, nameStr, typeStr);
542 }
543 }
544 } else {
545 return i18nc("%1 is folder contents", "Kolab %1", typeStr);
546 }
547 } // end kolab section
548
549 // Dav Groupware
550 if (resourceName.contains(QLatin1String("davgroupware"))) {
551 const QString resourceDisplayName = Akonadi::AgentManager::self()->instance(resourceName).name();
552 return i18nc("%1 is the folder name", "%1 in %2", fullCollection.displayName(), resourceDisplayName);
553 } // end caldav section
554
555 // Google
556 if (resourceName.contains(QLatin1String("google"))) {
557 QString ownerStr; // folder owner: "user@gmail.com"
558 if (calendar) {
559 Akonadi::Collection p = c.parentCollection();
560 ownerStr = calendar->collection(p.id()).displayName();
561 }
562
563 const QString nameStr = c.displayName(); // folder name: can be anything
564
565 QString typeStr;
566 const QString mimeStr = c.contentMimeTypes().join(QLatin1Char(','));
567 if (mimeStr.contains(QLatin1String(".event"))) {
568 typeStr = i18n("Calendar");
569 } else if (mimeStr.contains(QLatin1String(".todo"))) {
570 typeStr = i18n("Tasks");
571 } else if (mimeStr.contains(QLatin1String(".journal"))) {
572 typeStr = i18n("Journal");
573 } else if (mimeStr.contains(QLatin1String(".note"))) {
574 typeStr = i18n("Notes");
575 } else {
576 typeStr = mimeStr;
577 }
578
579 if (!ownerStr.isEmpty()) {
580 const int atChar = ownerStr.lastIndexOf(QLatin1Char('@'));
581 if (atChar >= 0) {
582 ownerStr.truncate(atChar);
583 }
584 if (nameStr.isEmpty()) {
585 return i18nc("%1 is folder owner name, %2 is folder contents", "%1's Google %2", ownerStr, typeStr);
586 } else {
587 return i18nc("%1 is folder owner name, %2 is folder name", "%1's %2", ownerStr, nameStr);
588 }
589 } else {
590 return i18nc("%1 is folder contents", "Google %1", typeStr);
591 }
592 } // end google section
593
594 // Not groupware so the collection is "mine"
595 const QString dName = fullCollection.displayName();
596
597 if (!dName.isEmpty()) {
598 return fullCollection.name().startsWith(QLatin1String("akonadi_")) ? i18n("My %1", dName) : dName;
599 } else if (!fullCollection.name().isEmpty()) {
600 return fullCollection.name();
601 } else {
602 return i18nc("unknown resource", "Unknown");
603 }
604 }
605
toolTipString(const Akonadi::Collection & coll,bool richText)606 QString CalendarSupport::toolTipString(const Akonadi::Collection &coll, bool richText)
607 {
608 Q_UNUSED(richText)
609
610 QString str = QStringLiteral("<qt>");
611
612 // Display Name
613 QString displayName;
614 if (coll.hasAttribute<Akonadi::EntityDisplayAttribute>()) {
615 displayName = coll.attribute<Akonadi::EntityDisplayAttribute>()->displayName();
616 }
617
618 if (displayName.isEmpty()) {
619 displayName = coll.name();
620 }
621 if (coll.id() == CalendarSupport::KCalPrefs::instance()->defaultCalendarId()) {
622 displayName = i18nc("this is the default calendar", "%1 (Default Calendar)", displayName);
623 }
624 str += QLatin1String("<b>") + displayName + QLatin1String("</b>");
625 str += QLatin1String("<hr>");
626
627 // Calendar Type
628 QString calendarType;
629 if (!coll.isVirtual()) {
630 const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(coll.resource());
631 calendarType = instance.type().name();
632 } else {
633 calendarType = i18nc("a virtual folder type", "Virtual");
634 }
635 str += QLatin1String("<i>") + i18n("Folder type:") + QLatin1String("</i>");
636 str += QLatin1String(" ") + calendarType;
637
638 // Content Type
639 QStringList mimeTypes = coll.contentMimeTypes();
640 mimeTypes.removeAll(QStringLiteral("inode/directory"));
641 QString mimeTypeStr;
642 if (!mimeTypes.isEmpty()) {
643 mimeTypeStr = QLocale().createSeparatedList(mimeTypes.replaceInStrings(QStringLiteral("application/x-vnd.akonadi.calendar."), QString()));
644 } else {
645 mimeTypeStr = i18nc("collection has no mimetypes to show the user", "none");
646 }
647 str += QLatin1String("<br>");
648 str += QLatin1String("<i>") + i18n("Content type:") + QLatin1String("</i>");
649 str += QLatin1String(" ") + mimeTypeStr;
650 str += QLatin1String("</br>");
651
652 // Read only?
653 bool isReadOnly = !(coll.rights() & Akonadi::Collection::CanChangeItem);
654 str += QLatin1String("<br>");
655 str += QLatin1String("<i>") + i18n("Rights:") + QLatin1String("</i>");
656 str += QLatin1String(" ");
657 if (isReadOnly) {
658 str += i18nc("the calendar is read-only", "read-only");
659 } else {
660 str += i18nc("the calendar is read and write", "read+write");
661 }
662 str += QLatin1String("</br>");
663
664 // Blocking reminders?
665 QStringList blockList;
666 if (coll.hasAttribute<Akonadi::BlockAlarmsAttribute>()) {
667 if (coll.attribute<Akonadi::BlockAlarmsAttribute>()->isEverythingBlocked()) {
668 blockList << i18nc("blocking all reminders for this calendar", "all");
669 } else {
670 if (coll.attribute<Akonadi::BlockAlarmsAttribute>()->isAlarmTypeBlocked(KCalendarCore::Alarm::Audio)) {
671 blockList << i18nc("blocking audio reminders for this calendar", "audio");
672 } else if (coll.attribute<Akonadi::BlockAlarmsAttribute>()->isAlarmTypeBlocked(KCalendarCore::Alarm::Display)) {
673 blockList << i18nc("blocking display pop-up dialog reminders for this calendar", "display");
674 } else if (coll.attribute<Akonadi::BlockAlarmsAttribute>()->isAlarmTypeBlocked(KCalendarCore::Alarm::Email)) {
675 blockList << i18nc("blocking email reminders for this calendar", "email");
676 } else if (coll.attribute<Akonadi::BlockAlarmsAttribute>()->isAlarmTypeBlocked(KCalendarCore::Alarm::Procedure)) {
677 blockList << i18nc("blocking run a command reminders for this calendar", "procedure");
678 } else {
679 blockList << i18nc("blocking unknown type reminders for this calendar", "other");
680 }
681 }
682 } else {
683 blockList << i18nc("not blocking any reminder types for this calendar", "none");
684 }
685 str += QLatin1String("<br>");
686 str += QLatin1String("<i>") + i18n("Blocked Reminders:") + QLatin1String("</i>");
687 str += QLatin1String(" ");
688 str += QLocale().createSeparatedList(blockList);
689 str += QLatin1String("</br>");
690
691 str += QLatin1String("</qt>");
692 return str;
693 }
694
subMimeTypeForIncidence(const KCalendarCore::Incidence::Ptr & incidence)695 QString CalendarSupport::subMimeTypeForIncidence(const KCalendarCore::Incidence::Ptr &incidence)
696 {
697 return incidence->mimeType();
698 }
699
workDays(QDate startDate,QDate endDate)700 QList<QDate> CalendarSupport::workDays(QDate startDate, QDate endDate)
701 {
702 QList<QDate> result;
703
704 const int mask(~(KCalPrefs::instance()->mWorkWeekMask));
705 const int numDays = startDate.daysTo(endDate) + 1;
706
707 for (int i = 0; i < numDays; ++i) {
708 const QDate date = startDate.addDays(i);
709 if (!(mask & (1 << (date.dayOfWeek() - 1)))) {
710 result.append(date);
711 }
712 }
713
714 if (KCalPrefs::instance()->mExcludeHolidays) {
715 const QStringList holidays = KCalPrefs::instance()->mHolidays;
716 for (const QString ®ionStr : holidays) {
717 KHolidays::HolidayRegion region(regionStr);
718 if (region.isValid()) {
719 for (auto const &h : region.holidays(startDate, endDate)) {
720 if (h.dayType() == KHolidays::Holiday::NonWorkday) {
721 for (int i = 0; i < h.duration(); i++) {
722 result.removeOne(h.observedStartDate().addDays(i));
723 }
724 }
725 }
726 }
727 }
728 }
729
730 return result;
731 }
732
holiday(QDate date)733 QStringList CalendarSupport::holiday(QDate date)
734 {
735 QStringList hdays;
736
737 bool showCountryCode = (KCalPrefs::instance()->mHolidays.count() > 1);
738 const QStringList holidays = KCalPrefs::instance()->mHolidays;
739 for (const QString ®ionStr : holidays) {
740 KHolidays::HolidayRegion region(regionStr);
741 if (region.isValid()) {
742 const KHolidays::Holiday::List list = region.holidays(date);
743 const int listCount = list.count();
744 for (int i = 0; i < listCount; ++i) {
745 // don't add duplicates.
746 // TODO: won't find duplicates in different languages however.
747 const QString name = list.at(i).name();
748 if (showCountryCode) {
749 // If more than one holiday region, append the country code to the holiday
750 // display name to help the user identify which region it belongs to.
751 const QRegularExpression holidaySE(i18nc("search pattern for holidayname", "^%1", name));
752 if (hdays.filter(holidaySE).isEmpty()) {
753 const QString pholiday = i18n("%1 (%2)", name, region.countryCode());
754 hdays.append(pholiday);
755 } else {
756 // More than 1 region has the same holiday => remove the country code
757 // i.e don't show "Holiday (US)" and "Holiday(FR)"; just show "Holiday".
758 const QRegularExpression holidayRE(i18nc("replace pattern for holidayname (countrycode)", "^%1 \\(.*\\)", name));
759 hdays.replaceInStrings(holidayRE, name);
760 hdays.removeDuplicates();
761 }
762 } else {
763 if (!hdays.contains(name)) {
764 hdays.append(name);
765 }
766 }
767 }
768 }
769 }
770
771 return hdays;
772 }
773
categories(const KCalendarCore::Incidence::List & incidences)774 QStringList CalendarSupport::categories(const KCalendarCore::Incidence::List &incidences)
775 {
776 QStringList cats;
777 QStringList thisCats;
778 // @TODO: For now just iterate over all incidences. In the future,
779 // the list of categories should be built when reading the file.
780 for (const KCalendarCore::Incidence::Ptr &incidence : incidences) {
781 thisCats = incidence->categories();
782 const QStringList::ConstIterator send(thisCats.constEnd());
783 for (QStringList::ConstIterator si = thisCats.constBegin(); si != send; ++si) {
784 if (!cats.contains(*si)) {
785 cats.append(*si);
786 }
787 }
788 }
789 return cats;
790 }
791
mergeCalendar(const QString & srcFilename,const KCalendarCore::Calendar::Ptr & destCalendar)792 bool CalendarSupport::mergeCalendar(const QString &srcFilename, const KCalendarCore::Calendar::Ptr &destCalendar)
793 {
794 if (srcFilename.isEmpty()) {
795 qCCritical(CALENDARSUPPORT_LOG) << "Empty filename.";
796 return false;
797 }
798
799 if (!QFile::exists(srcFilename)) {
800 qCCritical(CALENDARSUPPORT_LOG) << "File'" << srcFilename << "' doesn't exist.";
801 }
802
803 // merge in a file
804 destCalendar->startBatchAdding();
805 KCalendarCore::FileStorage storage(destCalendar);
806 storage.setFileName(srcFilename);
807 bool loadedSuccesfully = storage.load();
808 destCalendar->endBatchAdding();
809
810 return loadedSuccesfully;
811 }
812
createAlarmReminder(const KCalendarCore::Alarm::Ptr & alarm,KCalendarCore::IncidenceBase::IncidenceType type)813 void CalendarSupport::createAlarmReminder(const KCalendarCore::Alarm::Ptr &alarm, KCalendarCore::IncidenceBase::IncidenceType type)
814 {
815 int duration; // in secs
816 switch (CalendarSupport::KCalPrefs::instance()->mReminderTimeUnits) {
817 default:
818 case 0: // mins
819 duration = CalendarSupport::KCalPrefs::instance()->mReminderTime * 60;
820 break;
821 case 1: // hours
822 duration = CalendarSupport::KCalPrefs::instance()->mReminderTime * 60 * 60;
823 break;
824 case 2: // days
825 duration = CalendarSupport::KCalPrefs::instance()->mReminderTime * 60 * 60 * 24;
826 break;
827 }
828 alarm->setType(KCalendarCore::Alarm::Display);
829 alarm->setEnabled(true);
830 if (type == KCalendarCore::Incidence::TypeEvent) {
831 alarm->setStartOffset(KCalendarCore::Duration(-duration));
832 } else {
833 alarm->setEndOffset(KCalendarCore::Duration(-duration));
834 }
835 }
836