MauiKit Calendar

calendarmanager.cpp
1// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
2// SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com>
3// SPDX-FileCopyrightText: 2003, 2004 Cornelius Schumacher <schumacher@kde.org>
4// SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
5// SPDX-FileCopyrightText: 2009 Sebastian Sauer <sebsauer@kdab.net>
6// SPDX-FileCopyrightText: 2010-2021 Laurent Montel <montel@kde.org>
7// SPDX-FileCopyrightText: 2012 Sérgio Martins <iamsergio@gmail.com>
8//
9// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-Qt-Commercial-exception-1.0
10
11#include "calendarmanager.h"
12
13// Akonadi
14
15#include <Akonadi/AgentFilterProxyModel>
16#include <Akonadi/AgentInstanceModel>
17#include <Akonadi/AgentManager>
18#include <Akonadi/AttributeFactory>
19#include <Akonadi/Collection>
20#include <Akonadi/CollectionColorAttribute>
21#include <Akonadi/CollectionDeleteJob>
22#include <Akonadi/CollectionIdentificationAttribute>
23#include <Akonadi/CollectionModifyJob>
24// #include <Akonadi/CollectionPropertiesDialog>
25#include <Akonadi/CollectionUtils>
26#include <Akonadi/Control>
27// #include <Akonadi/ETMViewStateSaver>
28#include <Akonadi/EntityDisplayAttribute>
29#include <Akonadi/EntityRightsFilterModel>
30#include <Akonadi/EntityTreeModel>
31#include <Akonadi/ItemModifyJob>
32#include <Akonadi/ItemMoveJob>
33#include <Akonadi/Monitor>
34#include <akonadi_version.h>
35#if AKONADICALENDAR_VERSION > QT_VERSION_CHECK(5, 19, 41)
36#include <Akonadi/History>
37#else
38#include <Akonadi/Calendar/History>
39#endif
40
41
42#include <KCheckableProxyModel>
43#include <KDescendantsProxyModel>
44#include <KLocalizedString>
45#include <QApplication>
46#include <QDebug>
47#include <QPointer>
48#include <QRandomGenerator>
49// #include <models/todosortfilterproxymodel.h>
50
51#if AKONADICALENDAR_VERSION > QT_VERSION_CHECK(5, 19, 41)
52#include <Akonadi/ETMCalendar>
53#else
54#include <etmcalendar.h>
55#endif
56
57#include <colorproxymodel.h>
58#include "incidencewrapper.h"
59
60using namespace Akonadi;
61
62static Akonadi::EntityTreeModel *findEtm(QAbstractItemModel *model)
63{
64 QAbstractProxyModel *proxyModel = nullptr;
65 while (model) {
66 proxyModel = qobject_cast<QAbstractProxyModel *>(model);
67 if (proxyModel && proxyModel->sourceModel()) {
68 model = proxyModel->sourceModel();
69 } else {
70 break;
71 }
72 }
73 return qobject_cast<Akonadi::EntityTreeModel *>(model);
74}
75
76/**
77 * Automatically checks new calendar entries
78 */
79class NewCalendarChecker : public QObject
80{
82public:
83 NewCalendarChecker(QAbstractItemModel *model)
84 : QObject(model)
85 , mCheckableProxy(model)
86 {
87 connect(model, &QAbstractItemModel::rowsInserted, this, &NewCalendarChecker::onSourceRowsInserted);
88 qRegisterMetaType<QPersistentModelIndex>("QPersistentModelIndex");
89 }
90
91private Q_SLOTS:
92 void onSourceRowsInserted(const QModelIndex &parent, int start, int end)
93 {
94 Akonadi::EntityTreeModel *etm = findEtm(mCheckableProxy);
95 // Only check new collections and not during initial population
96 if (!etm || !etm->isCollectionTreeFetched()) {
97 return;
98 }
99 for (int i = start; i <= end; ++i) {
100 // qCDebug(KORGANIZER_LOG) << "checking " << i << parent << mCheckableProxy->index(i, 0, parent).data().toString();
101 const QModelIndex index = mCheckableProxy->index(i, 0, parent);
102 QMetaObject::invokeMethod(this, "setCheckState", Qt::QueuedConnection, Q_ARG(QPersistentModelIndex, index));
103 }
104 }
105
106 void setCheckState(const QPersistentModelIndex &index)
107 {
108 mCheckableProxy->setData(index, Qt::Checked, Qt::CheckStateRole);
109 if (mCheckableProxy->hasChildren(index)) {
110 onSourceRowsInserted(index, 0, mCheckableProxy->rowCount(index) - 1);
111 }
112 }
113
114private:
115 QAbstractItemModel *const mCheckableProxy;
116};
117
118class CollectionFilter : public QSortFilterProxyModel
119{
120public:
121 explicit CollectionFilter(QObject *parent = nullptr)
123 {
125 }
126
127protected:
128 bool filterAcceptsRow(int row, const QModelIndex &sourceParent) const override
129 {
130 const QModelIndex sourceIndex = sourceModel()->index(row, 0, sourceParent);
131 Q_ASSERT(sourceIndex.isValid());
132
135
136 // We filter the user folders because we insert person nodes for user folders.
137 if ((attr && attr->collectionNamespace().startsWith("usertoplevel")) || col.name().contains(QLatin1String("Other Users"))) {
138 return false;
139 }
140 return true;
141 }
142
143
144};
145
146class KalendarCollectionFilterProxyModel : public Akonadi::CollectionFilterProxyModel
147{
148public:
149 explicit KalendarCollectionFilterProxyModel(QObject *parent = nullptr)
151 {
152 }
153
154protected:
155 bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override
156 {
157 const auto leftHasChildren = sourceModel()->hasChildren(source_left);
158 const auto rightHasChildren = sourceModel()->hasChildren(source_right);
159 if (leftHasChildren && !rightHasChildren) {
160 return false;
161 } else if (!leftHasChildren && rightHasChildren) {
162 return true;
163 }
164
165 return Akonadi::CollectionFilterProxyModel::lessThan(source_left, source_right);
166 }
167};
168
169Q_GLOBAL_STATIC(CalendarManager, calendarManagerGlobalInstance)
170
171CalendarManager *CalendarManager::instance()
172{
173 return calendarManagerGlobalInstance;
174}
175
176CalendarManager::CalendarManager(QObject *parent)
177 : QObject(parent)
178 , m_calendar(nullptr)
179{
180 qRegisterMetaType<Akonadi::CollectionFilterProxyModel *>();
181
182
184 qApp->exit(-1);
185 return;
186 }
187
188 qDebug() << "STARTING THE CALENDAR MANAGER";
189
190 auto colorProxy = new ColorProxyModel(this);
191 colorProxy->setObjectName(QStringLiteral("Show calendar colors"));
192 colorProxy->setDynamicSortFilter(true);
193
194 m_baseModel = colorProxy;
195
196 // Hide collections that are not required
197 auto collectionFilter = new CollectionFilter(this);
198 collectionFilter->setSourceModel(colorProxy);
199
200 m_calendar = QSharedPointer<Akonadi::ETMCalendar>::create(); // QSharedPointer
201 setCollectionSelectionProxyModel(m_calendar->checkableProxyModel());
202 connect(m_calendar->checkableProxyModel(), &KCheckableProxyModel::dataChanged, this, &CalendarManager::refreshEnabledTodoCollections);
203
204 m_changer = m_calendar->incidenceChanger();
205 m_changer->setHistoryEnabled(true);
206 connect(m_changer->history(), &Akonadi::History::changed, this, &CalendarManager::undoRedoDataChanged);
207
208 KSharedConfig::Ptr config = KSharedConfig::openConfig();
209 // mCollectionSelectionModelStateSaver = new Akonadi::ETMViewStateSaver(); // not a leak
210 // KConfigGroup selectionGroup = config->group("GlobalCollectionSelection");
211 // mCollectionSelectionModelStateSaver->setView(nullptr);
212 // mCollectionSelectionModelStateSaver->setSelectionModel(m_calendar->checkableProxyModel()->selectionModel());
213 // mCollectionSelectionModelStateSaver->restoreState(selectionGroup);
214
215 m_allCalendars = new Akonadi::CollectionFilterProxyModel(this);
216 m_allCalendars->setSourceModel(collectionFilter);
217 m_allCalendars->setExcludeVirtualCollections(true);
218
219 // Filter it by mimetype again, to only keep
220 // Kolab / Inbox / Calendar
221 m_eventMimeTypeFilterModel = new Akonadi::CollectionFilterProxyModel(this);
222 m_eventMimeTypeFilterModel->setSourceModel(collectionFilter);
223 m_eventMimeTypeFilterModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.event"));
224
225 // text/calendar mimetype includes todo cals
226 // m_todoMimeTypeFilterModel = new Akonadi::CollectionFilterProxyModel(this);
227 // m_todoMimeTypeFilterModel->setSourceModel(collectionFilter);
228 // m_todoMimeTypeFilterModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
229 // m_todoMimeTypeFilterModel->setExcludeVirtualCollections(true);
230
231 // Filter by access rights
232 m_allCollectionsRightsFilterModel = new Akonadi::EntityRightsFilterModel(this);
233 m_allCollectionsRightsFilterModel->setAccessRights(Collection::CanCreateItem);
234 m_allCollectionsRightsFilterModel->setSourceModel(collectionFilter);
235
236 m_eventRightsFilterModel = new Akonadi::EntityRightsFilterModel(this);
237 m_eventRightsFilterModel->setAccessRights(Collection::CanCreateItem);
238 m_eventRightsFilterModel->setSourceModel(m_eventMimeTypeFilterModel);
239
240 // m_todoRightsFilterModel = new Akonadi::EntityRightsFilterModel(this);
241 // m_todoRightsFilterModel->setAccessRights(Collection::CanCreateItem);
242 // m_todoRightsFilterModel->setSourceModel(m_todoMimeTypeFilterModel);
243
244 // Use our custom class to order them properly
245 m_selectableCollectionsModel = new KalendarCollectionFilterProxyModel(this);
246 m_selectableCollectionsModel->setSourceModel(m_allCollectionsRightsFilterModel);
247 m_selectableCollectionsModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.event"));
248 m_selectableCollectionsModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
249 m_selectableCollectionsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
250 m_selectableCollectionsModel->sort(0, Qt::AscendingOrder);
251
252 m_selectableEventCollectionsModel = new KalendarCollectionFilterProxyModel(this);
253 m_selectableEventCollectionsModel->setSourceModel(m_eventRightsFilterModel);
254 m_selectableEventCollectionsModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.event"));
255 m_selectableEventCollectionsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
256 m_selectableEventCollectionsModel->sort(0, Qt::AscendingOrder);
257
258 m_selectableTodoCollectionsModel = new KalendarCollectionFilterProxyModel(this);
259 m_selectableTodoCollectionsModel->setSourceModel(m_todoRightsFilterModel);
260 m_selectableTodoCollectionsModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
261 m_selectableTodoCollectionsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
262 m_selectableTodoCollectionsModel->sort(0, Qt::AscendingOrder);
263
264 // Model for todo via collection picker
265 m_todoViewCollectionModel = new KalendarCollectionFilterProxyModel(this);
266 m_todoViewCollectionModel->setSourceModel(collectionFilter);
267 m_todoViewCollectionModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
268 m_todoViewCollectionModel->setExcludeVirtualCollections(true);
269 m_todoViewCollectionModel->setSortCaseSensitivity(Qt::CaseInsensitive);
270 m_todoViewCollectionModel->sort(0, Qt::AscendingOrder);
271
272 // Model for the mainDrawer
273 m_viewCollectionModel = new KalendarCollectionFilterProxyModel(this);
274 m_viewCollectionModel->setSourceModel(collectionFilter);
275 m_viewCollectionModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.event"));
276 m_viewCollectionModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
277 m_viewCollectionModel->setExcludeVirtualCollections(true);
278 m_viewCollectionModel->setSortCaseSensitivity(Qt::CaseInsensitive);
279 m_viewCollectionModel->sort(0, Qt::AscendingOrder);
280
281 m_flatCollectionTreeModel = new KDescendantsProxyModel(this);
282 m_flatCollectionTreeModel->setSourceModel(m_viewCollectionModel);
283 m_flatCollectionTreeModel->setExpandsByDefault(true);
284
285 auto refreshColors = [=]() {
286 for (auto i = 0; i < m_flatCollectionTreeModel->rowCount(); i++) {
287 auto idx = m_flatCollectionTreeModel->index(i, 0, {});
288#if AKONADI_VERSION < QT_VERSION_CHECK(5, 20, 41)
289 colorProxy->getCollectionColor(CalendarSupport::collectionFromIndex(idx));
290#else
291 colorProxy->getCollectionColor(Akonadi::CollectionUtils::fromIndex(idx));
292#endif
293 }
294 };
295 connect(m_flatCollectionTreeModel, &QSortFilterProxyModel::rowsInserted, this, refreshColors);
296
297 KConfigGroup rColorsConfig(config, QStringLiteral("Resources Colors"));
298 m_colorWatcher = KConfigWatcher::create(config);
299 connect(m_colorWatcher.data(), &KConfigWatcher::configChanged, this, &CalendarManager::collectionColorsChanged);
300
301 connect(m_calendar.data(), &Akonadi::ETMCalendar::calendarChanged, this, &CalendarManager::calendarChanged);
302}
303
304CalendarManager::~CalendarManager()
305{
306 save();
307 // delete mCollectionSelectionModelStateSaver;
308}
309
310void CalendarManager::save()
311{
312 // Akonadi::ETMViewStateSaver treeStateSaver;
313 // KSharedConfig::Ptr config = KSharedConfig::openConfig();
314 // KConfigGroup group = config->group("GlobalCollectionSelection");
315 // treeStateSaver.setView(nullptr);
316 // treeStateSaver.setSelectionModel(m_calendar->checkableProxyModel()->selectionModel());
317 // treeStateSaver.saveState(group);
318 //
319 // config->sync();
320}
321
322void CalendarManager::delayedInit()
323{
324 Q_EMIT loadingChanged();
325}
326
327QAbstractProxyModel *CalendarManager::collections()
328{
329 return static_cast<QAbstractProxyModel *>(m_flatCollectionTreeModel->sourceModel());
330}
331
332QAbstractItemModel *CalendarManager::todoCollections()
333{
334 return m_todoViewCollectionModel;
335}
336
337QAbstractItemModel *CalendarManager::viewCollections()
338{
339 return m_viewCollectionModel;
340}
341
342QVector<qint64> CalendarManager::enabledTodoCollections()
343{
344 return m_enabledTodoCollections;
345}
346
347void CalendarManager::refreshEnabledTodoCollections()
348{
349 m_enabledTodoCollections.clear();
350 const auto selectedIndexes = m_calendar->checkableProxyModel()->selectionModel()->selectedIndexes();
351 for (auto selectedIndex : selectedIndexes) {
352 auto collection = selectedIndex.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
353 if (collection.contentMimeTypes().contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))) {
354 m_enabledTodoCollections.append(collection.id());
355 }
356 }
357
358 Q_EMIT enabledTodoCollectionsChanged();
359}
360
361bool CalendarManager::loading() const
362{
363 return m_calendar->isLoading();
364}
365
366void CalendarManager::setCollectionSelectionProxyModel(KCheckableProxyModel *m)
367{
368 if (m_selectionProxyModel == m) {
369 return;
370 }
371
372 m_selectionProxyModel = m;
373 if (!m_selectionProxyModel) {
374 return;
375 }
376
377 new NewCalendarChecker(m);
378 m_baseModel->setSourceModel(m_selectionProxyModel);
379}
380
381KCheckableProxyModel *CalendarManager::collectionSelectionProxyModel() const
382{
383 return m_selectionProxyModel;
384}
385
386Akonadi::ETMCalendar::Ptr CalendarManager::calendar() const
387{
388 return m_calendar;
389}
390
391Akonadi::IncidenceChanger *CalendarManager::incidenceChanger() const
392{
393 return m_changer;
394}
395
396Akonadi::CollectionFilterProxyModel *CalendarManager::allCalendars()
397{
398 return m_allCalendars;
399}
400
401Akonadi::CollectionFilterProxyModel *CalendarManager::selectableCalendars() const
402{
403 return m_selectableCollectionsModel;
404}
405
406Akonadi::CollectionFilterProxyModel *CalendarManager::selectableEventCalendars() const
407{
408 return m_selectableEventCollectionsModel;
409}
410
411Akonadi::CollectionFilterProxyModel *CalendarManager::selectableTodoCalendars() const
412{
413 return m_selectableTodoCollectionsModel;
414}
415
416qint64 CalendarManager::defaultCalendarId(IncidenceWrapper *incidenceWrapper)
417{
418 // Checks if default collection accepts this type of incidence
419 auto mimeType = incidenceWrapper->incidencePtr()->mimeType();
420 Akonadi::Collection collection = m_calendar->collection(-1);
421 bool supportsMimeType = collection.contentMimeTypes().contains(mimeType) || mimeType == QLatin1String("");
422 bool hasRights = collection.rights() & Akonadi::Collection::CanCreateItem;
423 if (collection.isValid() && supportsMimeType && hasRights) {
424 return collection.id();
425 }
426
427 // Should add last used collection by mimetype somewhere.
428
429 // Searches for first collection that will accept this incidence
430 for (int i = 0; i < m_allCalendars->rowCount(); i++) {
431 QModelIndex idx = m_allCalendars->index(i, 0);
432 collection = idx.data(Akonadi::EntityTreeModel::Roles::CollectionRole).value<Akonadi::Collection>();
433 supportsMimeType = collection.contentMimeTypes().contains(mimeType) || mimeType == QLatin1String("");
434 hasRights = collection.rights() & Akonadi::Collection::CanCreateItem;
435 if (collection.isValid() && supportsMimeType && hasRights) {
436 return collection.id();
437 }
438 }
439
440 return -1;
441}
442
443int CalendarManager::getCalendarSelectableIndex(IncidenceWrapper *incidenceWrapper)
444{
445 auto model = new KDescendantsProxyModel;
446
447 switch (incidenceWrapper->incidencePtr()->type()) {
448 default:
450 model->setSourceModel(m_selectableEventCollectionsModel);
451 break;
452 }
454 model->setSourceModel(m_selectableTodoCollectionsModel);
455 break;
456 }
457 }
458
459 for (int i = 0; i < model->rowCount(); i++) {
460 QModelIndex idx = model->index(i, 0);
461 QVariant data = idx.data(Akonadi::EntityTreeModel::Roles::CollectionIdRole);
462
463 if (data == incidenceWrapper->collectionId())
464 return i;
465 }
466
467 return 0;
468}
469
470QVariant CalendarManager::getIncidenceSubclassed(KCalendarCore::Incidence::Ptr incidencePtr)
471{
472 switch (incidencePtr->type()) {
474 return QVariant::fromValue(m_calendar->event(incidencePtr->instanceIdentifier()));
475 break;
477 return QVariant::fromValue(m_calendar->todo(incidencePtr->instanceIdentifier()));
478 break;
480 return QVariant::fromValue(m_calendar->journal(incidencePtr->instanceIdentifier()));
481 break;
482 default:
483 return QVariant::fromValue(incidencePtr);
484 break;
485 }
486}
487
488QVariantMap CalendarManager::undoRedoData()
489{
490 return QVariantMap{
491 {QStringLiteral("undoAvailable"), m_changer->history()->undoAvailable()},
492 {QStringLiteral("redoAvailable"), m_changer->history()->redoAvailable()},
493 {QStringLiteral("nextUndoDescription"), m_changer->history()->nextUndoDescription()},
494 {QStringLiteral("nextRedoDescription"), m_changer->history()->nextRedoDescription()},
495 };
496}
497
498Akonadi::Item CalendarManager::incidenceItem(KCalendarCore::Incidence::Ptr incidence) const
499{
500 return m_calendar->item(incidence);
501}
502
503Akonadi::Item CalendarManager::incidenceItem(const QString &uid) const
504{
505 return incidenceItem(m_calendar->incidence(uid));
506}
507
508KCalendarCore::Incidence::List CalendarManager::childIncidences(const QString &uid) const
509{
510 return m_calendar->childIncidences(uid);
511}
512
513void CalendarManager::addIncidence(IncidenceWrapper *incidenceWrapper)
514{
515 Akonadi::Collection collection(incidenceWrapper->collectionId());
516
517 switch (incidenceWrapper->incidencePtr()->type()) {
519
520 KCalendarCore::Event::Ptr event = incidenceWrapper->incidencePtr().staticCast<KCalendarCore::Event>();
521 auto res = m_changer->createIncidence(event, collection);
522 qDebug() << "TRYING TO ADD AN EVENT" << incidenceWrapper->summary() << incidenceWrapper->collectionId() << res << event->description();
523
524 break;
525 }
527 KCalendarCore::Todo::Ptr todo = incidenceWrapper->incidencePtr().staticCast<KCalendarCore::Todo>();
528 m_changer->createIncidence(todo, collection);
529 break;
530 }
531 default:
532 m_changer->createIncidence(KCalendarCore::Incidence::Ptr(incidenceWrapper->incidencePtr()->clone()), collection);
533 break;
534 }
535 // This will fritz if you don't choose a valid *calendar*
536}
537
538// Replicates IncidenceDialogPrivate::save
539void CalendarManager::editIncidence(IncidenceWrapper *incidenceWrapper)
540{
541 // We need to use the incidenceChanger manually to get the change recorded in the history
542 // For undo/redo to work properly we need to change the ownership of the incidence pointers
543 KCalendarCore::Incidence::Ptr changedIncidence(incidenceWrapper->incidencePtr()->clone());
544 KCalendarCore::Incidence::Ptr originalPayload(incidenceWrapper->originalIncidencePtr()->clone());
545
546 Akonadi::Item modifiedItem = m_calendar->item(changedIncidence->instanceIdentifier());
547 modifiedItem.setPayload<KCalendarCore::Incidence::Ptr>(changedIncidence);
548
549 m_changer->modifyIncidence(modifiedItem, originalPayload);
550
551 if (!incidenceWrapper->collectionId() || incidenceWrapper->collectionId() < 0 || modifiedItem.parentCollection().id() == incidenceWrapper->collectionId()) {
552 return;
553 }
554
555 changeIncidenceCollection(modifiedItem, incidenceWrapper->collectionId());
556}
557
558void CalendarManager::updateIncidenceDates(IncidenceWrapper *incidenceWrapper, int startOffset, int endOffset, int occurrences, const QDateTime &occurrenceDate)
559{ // start and end offsets are in msecs
560
561 Akonadi::Item item = m_calendar->item(incidenceWrapper->incidencePtr());
562 item.setPayload(incidenceWrapper->incidencePtr());
563
564 auto setNewDates = [&](KCalendarCore::Incidence::Ptr incidence) {
566 // For to-dos endOffset is ignored because it will always be == to startOffset because we only
567 // support moving to-dos, not resizing them. There are no multi-day to-dos.
568 // Lets just call it offset to reduce confusion.
569 const int offset = startOffset;
570
572 QDateTime due = todo->dtDue();
573 QDateTime start = todo->dtStart();
574 if (due.isValid()) { // Due has priority over start.
575 // We will only move the due date, unlike events where we move both.
576 due = due.addMSecs(offset);
577 todo->setDtDue(due);
578
579 if (start.isValid() && start > due) {
580 // Start can't be bigger than due.
581 todo->setDtStart(due);
582 }
583 } else if (start.isValid()) {
584 // So we're displaying a to-do that doesn't have due date, only start...
585 start = start.addMSecs(offset);
586 todo->setDtStart(start);
587 } else {
588 // This never happens
589 // qCWarning(CALENDARVIEW_LOG) << "Move what? uid:" << todo->uid() << "; summary=" << todo->summary();
590 }
591 } else {
592 incidence->setDtStart(incidence->dtStart().addMSecs(startOffset));
595 event->setDtEnd(event->dtEnd().addMSecs(endOffset));
596 }
597 }
598 };
599
600 if (incidenceWrapper->incidencePtr()->recurs()) {
601 switch (occurrences) {
603 // All occurrences
604 KCalendarCore::Incidence::Ptr oldIncidence(incidenceWrapper->incidencePtr()->clone());
605 setNewDates(incidenceWrapper->incidencePtr());
606 qDebug() << incidenceWrapper->incidenceStart();
607 m_changer->modifyIncidence(item, oldIncidence);
608 break;
609 }
610 case KCalUtils::RecurrenceActions::SelectedOccurrence: // Just this occurrence
611 case KCalUtils::RecurrenceActions::FutureOccurrences: { // All future occurrences
612 const bool thisAndFuture = (occurrences == KCalUtils::RecurrenceActions::FutureOccurrences);
613 auto tzedOccurrenceDate = occurrenceDate.toTimeZone(incidenceWrapper->incidenceStart().timeZone());
615 KCalendarCore::Calendar::createException(incidenceWrapper->incidencePtr(), tzedOccurrenceDate, thisAndFuture));
616
617 if (newIncidence) {
618 m_changer->startAtomicOperation(i18n("Move occurrence(s)"));
619 setNewDates(newIncidence);
620 m_changer->createIncidence(newIncidence, m_calendar->collection(incidenceWrapper->collectionId()));
621 m_changer->endAtomicOperation();
622 } else {
623 qDebug() << i18n("Unable to add the exception item to the calendar. No change will be done.");
624 }
625 break;
626 }
627 }
628 } else { // Doesn't recur
629 KCalendarCore::Incidence::Ptr oldIncidence(incidenceWrapper->incidencePtr()->clone());
630 setNewDates(incidenceWrapper->incidencePtr());
631 m_changer->modifyIncidence(item, oldIncidence);
632 }
633
634 Q_EMIT updateIncidenceDatesCompleted();
635}
636
637bool CalendarManager::hasChildren(KCalendarCore::Incidence::Ptr incidence)
638{
639 return !m_calendar->childIncidences(incidence->uid()).isEmpty();
640}
641
642void CalendarManager::deleteAllChildren(KCalendarCore::Incidence::Ptr incidence)
643{
644 const auto allChildren = m_calendar->childIncidences(incidence->uid());
645
646 for (const auto &child : allChildren) {
647 if (!m_calendar->childIncidences(child->uid()).isEmpty()) {
648 deleteAllChildren(child);
649 }
650 }
651
652 for (const auto &child : allChildren) {
653 m_calendar->deleteIncidence(child);
654 }
655}
656
657void CalendarManager::deleteIncidence(KCalendarCore::Incidence::Ptr incidence, bool deleteChildren)
658{
659 const auto directChildren = m_calendar->childIncidences(incidence->uid());
660
661 if (!directChildren.isEmpty()) {
662 if (deleteChildren) {
663 m_changer->startAtomicOperation(i18n("Delete task and its sub-tasks"));
664 deleteAllChildren(incidence);
665 } else {
666 m_changer->startAtomicOperation(i18n("Delete task and make sub-tasks independent"));
667 for (const auto &child : directChildren) {
668 const auto instances = m_calendar->instances(child);
669 for (const auto &instance : instances) {
670 KCalendarCore::Incidence::Ptr oldInstance(instance->clone());
671 instance->setRelatedTo(QString());
672 m_changer->modifyIncidence(m_calendar->item(instance), oldInstance);
673 }
674
675 KCalendarCore::Incidence::Ptr oldInc(child->clone());
676 child->setRelatedTo(QString());
677 m_changer->modifyIncidence(m_calendar->item(child), oldInc);
678 }
679 }
680
681 m_calendar->deleteIncidence(incidence);
682 m_changer->endAtomicOperation();
683 return;
684 }
685
686 m_calendar->deleteIncidence(incidence);
687}
688
689void CalendarManager::changeIncidenceCollection(KCalendarCore::Incidence::Ptr incidence, qint64 collectionId)
690{
691 KCalendarCore::Incidence::Ptr incidenceClone(incidence->clone());
692 Akonadi::Item modifiedItem = m_calendar->item(incidence->instanceIdentifier());
693 modifiedItem.setPayload<KCalendarCore::Incidence::Ptr>(incidenceClone);
694
695 if (modifiedItem.parentCollection().id() != collectionId) {
696 changeIncidenceCollection(modifiedItem, collectionId);
697 }
698}
699
700void CalendarManager::changeIncidenceCollection(Akonadi::Item item, qint64 collectionId)
701{
702 if (item.parentCollection().id() == collectionId) {
703 return;
704 }
705
707
708 Akonadi::Collection newCollection(collectionId);
709 item.setParentCollection(newCollection);
710
711 auto job = new Akonadi::ItemMoveJob(item, newCollection);
712 // Add some type of check here?
713 connect(job, &KJob::result, job, [=]() {
714 qDebug() << job->error();
715
716 if (!job->error()) {
717 const auto allChildren = m_calendar->childIncidences(item.id());
718 for (const auto &child : allChildren) {
719 changeIncidenceCollection(m_calendar->item(child), collectionId);
720 }
721
722 auto parent = item.payload<KCalendarCore::Incidence::Ptr>()->relatedTo();
723 if (!parent.isEmpty()) {
724 changeIncidenceCollection(m_calendar->item(parent), collectionId);
725 }
726 }
727 });
728}
729
730QVariantMap CalendarManager::getCollectionDetails(QVariant collectionId)
731{
732 QVariantMap collectionDetails;
733 Akonadi::Collection collection = m_calendar->collection(collectionId.toInt());
734 bool isFiltered = false;
735 int allCalendarsRow = 0;
736
737 for (int i = 0; i < m_allCalendars->rowCount(); i++) {
738 if (m_allCalendars->data(m_allCalendars->index(i, 0), Akonadi::EntityTreeModel::CollectionIdRole).toInt() == collectionId) {
739 isFiltered = !m_allCalendars->data(m_allCalendars->index(i, 0), Qt::CheckStateRole).toBool();
740 allCalendarsRow = i;
741 break;
742 }
743 }
744
745 collectionDetails[QLatin1String("id")] = collection.id();
746 collectionDetails[QLatin1String("name")] = collection.name();
747 collectionDetails[QLatin1String("displayName")] = collection.displayName();
748 collectionDetails[QLatin1String("color")] = m_baseModel->color(collection.id()); collectionDetails[QLatin1String("count")] = collection.statistics().count();
749 collectionDetails[QLatin1String("isResource")] = Akonadi::CollectionUtils::isResource(collection);
750 collectionDetails[QLatin1String("resource")] = collection.resource();
751 collectionDetails[QLatin1String("readOnly")] = collection.rights().testFlag(Collection::ReadOnly);
752 collectionDetails[QLatin1String("canChange")] = collection.rights().testFlag(Collection::CanChangeCollection);
753 collectionDetails[QLatin1String("canCreate")] = collection.rights().testFlag(Collection::CanCreateCollection);
754 collectionDetails[QLatin1String("canDelete")] =
755 collection.rights().testFlag(Collection::CanDeleteCollection) && !Akonadi::CollectionUtils::isResource(collection);
756 collectionDetails[QLatin1String("isFiltered")] = isFiltered;
757 collectionDetails[QLatin1String("allCalendarsRow")] = allCalendarsRow;
758
759 return collectionDetails;
760}
761
762void CalendarManager::setCollectionColor(qint64 collectionId, const QColor &color)
763{
764 auto collection = m_calendar->collection(collectionId);
766 colorAttr->setColor(color);
767 auto modifyJob = new Akonadi::CollectionModifyJob(collection);
768 connect(modifyJob, &Akonadi::CollectionModifyJob::result, this, [this, collectionId, color](KJob *job) {
769 if (job->error()) {
770 qWarning() << "Error occurred modifying collection color: " << job->errorString();
771 } else {
772 m_baseModel->setColor(collectionId, color);
773 }
774 });
775}
776
777void CalendarManager::undoAction()
778{
779 m_changer->history()->undo();
780}
781
782void CalendarManager::redoAction()
783{
784 m_changer->history()->redo();
785}
786
787void CalendarManager::updateAllCollections()
788{
789 for (int i = 0; i < collections()->rowCount(); i++) {
790 auto collection = collections()->data(collections()->index(i, 0), Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
792 }
793}
794
795void CalendarManager::updateCollection(qint64 collectionId)
796{
797 auto collection = m_calendar->collection(collectionId);
799}
800
801void CalendarManager::deleteCollection(qint64 collectionId)
802{
803 auto collection = m_calendar->collection(collectionId);
804 const bool isTopLevel = collection.parentCollection() == Akonadi::Collection::root();
805
806 if (!isTopLevel) {
807 // deletes contents
808 auto job = new Akonadi::CollectionDeleteJob(collection, this);
810 if (job->error()) {
811 qWarning() << "Error occurred deleting collection: " << job->errorString();
812 }
813 });
814 return;
815 }
816 // deletes the agent, not the contents
818 if (instance.isValid()) {
820 }
821}
822
823void CalendarManager::editCollection(qint64 collectionId)
824{ // TODO: Reimplement this dialog in QML
825 // auto collection = m_calendar->collection(collectionId);
826 // QPointer<Akonadi::CollectionPropertiesDialog> dlg = new Akonadi::CollectionPropertiesDialog(collection);
827 // dlg->setWindowTitle(i18nc("@title:window", "Properties of Calendar %1", collection.name()));
828 // dlg->show();
829}
830
831void CalendarManager::toggleCollection(qint64 collectionId)
832{
833 const auto matches = m_calendar->checkableProxyModel()->match(m_calendar->checkableProxyModel()->index(0, 0),
835 collectionId,
836 1,
838 if (matches.count() > 0) {
839 const auto collectionIndex = matches.first();
840 const auto collectionChecked = collectionIndex.data(Qt::CheckStateRole).toInt() == Qt::Checked;
841 const auto checkStateToSet = collectionChecked ? Qt::Unchecked : Qt::Checked;
842 m_calendar->checkableProxyModel()->setData(collectionIndex, checkStateToSet, Qt::CheckStateRole);
843 }
844}
845
846#ifndef UNITY_CMAKE_SUPPORT
847Q_DECLARE_METATYPE(KCalendarCore::Incidence::Ptr)
848#endif
849
850#include "calendarmanager.moc"
851#include "moc_calendarmanager.cpp"
852
static AgentManager * self()
void removeInstance(const AgentInstance &instance)
AgentInstance instance(const QString &identifier) const
void synchronizeCollection(const Collection &collection)
QString resource() const
QStringList contentMimeTypes() const
CollectionStatistics statistics() const
bool isValid() const
QString displayName() const
static Collection root()
const T * attribute() const
Rights rights() const
QString name() const
Collection & parentCollection()
static bool start()
bool isCollectionTreeFetched() const
void setParentCollection(const Collection &parent)
void setPayload(const T &p)
Collection & parentCollection()
bool hasPayload() const
Id id() const
T payload() const
Despite the name, this handles the presentation of collections including display text and icons,...
This class is a wrapper for a KCalendarCore::Incidence::Ptr object.
static Incidence::Ptr createException(const Incidence::Ptr &incidence, const QDateTime &recurrenceId, bool thisAndFuture=false)
void setDtEnd(const QDateTime &dtEnd)
QString description() const
static Ptr create(const KSharedConfig::Ptr &config)
void configChanged(const KConfigGroup &group, const QByteArrayList &names)
virtual QString errorString() const
int error() const
void result(KJob *job)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
AKONADI_CALENDAR_EXPORT KCalendarCore::Todo::Ptr todo(const Akonadi::Item &item)
AKONADICORE_EXPORT Collection fromIndex(const QModelIndex &index)
KCALUTILS_EXPORT QString mimeType()
const QList< QKeySequence > & end()
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
virtual bool hasChildren(const QModelIndex &parent) const const
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
virtual int rowCount(const QModelIndex &parent) const const=0
void rowsInserted(const QModelIndex &parent, int first, int last)
virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
virtual QVariant data(const QModelIndex &proxyIndex, int role) const const override
QTimeZone timeZone() const const
QDateTime toTimeZone(const QTimeZone &timeZone) const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QVariant data(int role) const const
bool isValid() const const
Q_EMITQ_EMIT
Q_OBJECTQ_OBJECT
Q_SLOTSQ_SLOTS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool event(QEvent *e)
QObject * parent() const const
QSharedPointer< T > create(Args &&... args)
QSharedPointer< X > staticCast() const const
virtual QVariant data(const QModelIndex &index, int role) const const override
void setDynamicSortFilter(bool enable)
virtual bool hasChildren(const QModelIndex &parent) const const override
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const const
virtual int rowCount(const QModelIndex &parent) const const override
virtual void setSourceModel(QAbstractItemModel *sourceModel) override
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
CaseInsensitive
QueuedConnection
CheckStateRole
MatchExactly
AscendingOrder
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QVariant fromValue(T &&value)
bool toBool() const const
int toInt(bool *ok) const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:02:37 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.