File indexing completed on 2024-05-26 04:49:09

0001 //  SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
0002 //  SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com>
0003 //  SPDX-FileCopyrightText: 2003, 2004 Cornelius Schumacher <schumacher@kde.org>
0004 //  SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0005 //  SPDX-FileCopyrightText: 2009 Sebastian Sauer <sebsauer@kdab.net>
0006 //  SPDX-FileCopyrightText: 2010-2021 Laurent Montel <montel@kde.org>
0007 //  SPDX-FileCopyrightText: 2012 Sérgio Martins <iamsergio@gmail.com>
0008 //
0009 //  SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-Qt-Commercial-exception-1.0
0010 
0011 #include "calendarmanager.h"
0012 
0013 // Akonadi
0014 
0015 #include <Akonadi/AgentFilterProxyModel>
0016 #include <Akonadi/AgentInstanceModel>
0017 #include <Akonadi/AgentManager>
0018 #include <Akonadi/AttributeFactory>
0019 #include <Akonadi/Collection>
0020 #include <Akonadi/CollectionColorAttribute>
0021 #include <Akonadi/CollectionDeleteJob>
0022 #include <Akonadi/CollectionIdentificationAttribute>
0023 #include <Akonadi/CollectionModifyJob>
0024 // #include <Akonadi/CollectionPropertiesDialog>
0025 #include <Akonadi/CollectionUtils>
0026 #include <Akonadi/Control>
0027 // #include <Akonadi/ETMViewStateSaver>
0028 #include <Akonadi/EntityDisplayAttribute>
0029 #include <Akonadi/EntityRightsFilterModel>
0030 #include <Akonadi/EntityTreeModel>
0031 #include <Akonadi/ItemModifyJob>
0032 #include <Akonadi/ItemMoveJob>
0033 #include <Akonadi/Monitor>
0034 #include <akonadi_version.h>
0035 #if AKONADICALENDAR_VERSION > QT_VERSION_CHECK(5, 19, 41)
0036 #include <Akonadi/History>
0037 #else
0038 #include <Akonadi/Calendar/History>
0039 #endif
0040 
0041 
0042 #include <KCheckableProxyModel>
0043 #include <KDescendantsProxyModel>
0044 #include <KLocalizedString>
0045 #include <QApplication>
0046 #include <QDebug>
0047 #include <QPointer>
0048 #include <QRandomGenerator>
0049 // #include <models/todosortfilterproxymodel.h>
0050 
0051 #if AKONADICALENDAR_VERSION > QT_VERSION_CHECK(5, 19, 41)
0052 #include <Akonadi/ETMCalendar>
0053 #else
0054 #include <etmcalendar.h>
0055 #endif
0056 
0057 #include <colorproxymodel.h>
0058 #include "incidencewrapper.h"
0059 
0060 using namespace Akonadi;
0061 
0062 static Akonadi::EntityTreeModel *findEtm(QAbstractItemModel *model)
0063 {
0064     QAbstractProxyModel *proxyModel = nullptr;
0065     while (model) {
0066         proxyModel = qobject_cast<QAbstractProxyModel *>(model);
0067         if (proxyModel && proxyModel->sourceModel()) {
0068             model = proxyModel->sourceModel();
0069         } else {
0070             break;
0071         }
0072     }
0073     return qobject_cast<Akonadi::EntityTreeModel *>(model);
0074 }
0075 
0076 /**
0077  * Automatically checks new calendar entries
0078  */
0079 class NewCalendarChecker : public QObject
0080 {
0081     Q_OBJECT
0082 public:
0083     NewCalendarChecker(QAbstractItemModel *model)
0084         : QObject(model)
0085         , mCheckableProxy(model)
0086     {
0087         connect(model, &QAbstractItemModel::rowsInserted, this, &NewCalendarChecker::onSourceRowsInserted);
0088         qRegisterMetaType<QPersistentModelIndex>("QPersistentModelIndex");
0089     }
0090 
0091 private Q_SLOTS:
0092     void onSourceRowsInserted(const QModelIndex &parent, int start, int end)
0093     {
0094         Akonadi::EntityTreeModel *etm = findEtm(mCheckableProxy);
0095         // Only check new collections and not during initial population
0096         if (!etm || !etm->isCollectionTreeFetched()) {
0097             return;
0098         }
0099         for (int i = start; i <= end; ++i) {
0100             // qCDebug(KORGANIZER_LOG) << "checking " << i << parent << mCheckableProxy->index(i, 0, parent).data().toString();
0101             const QModelIndex index = mCheckableProxy->index(i, 0, parent);
0102             QMetaObject::invokeMethod(this, "setCheckState", Qt::QueuedConnection, Q_ARG(QPersistentModelIndex, index));
0103         }
0104     }
0105 
0106     void setCheckState(const QPersistentModelIndex &index)
0107     {
0108         mCheckableProxy->setData(index, Qt::Checked, Qt::CheckStateRole);
0109         if (mCheckableProxy->hasChildren(index)) {
0110             onSourceRowsInserted(index, 0, mCheckableProxy->rowCount(index) - 1);
0111         }
0112     }
0113 
0114 private:
0115     QAbstractItemModel *const mCheckableProxy;
0116 };
0117 
0118 class CollectionFilter : public QSortFilterProxyModel
0119 {
0120 public:
0121     explicit CollectionFilter(QObject *parent = nullptr)
0122         : QSortFilterProxyModel(parent)
0123     {
0124         setDynamicSortFilter(true);
0125     }
0126 
0127 protected:
0128     bool filterAcceptsRow(int row, const QModelIndex &sourceParent) const override
0129     {
0130         const QModelIndex sourceIndex = sourceModel()->index(row, 0, sourceParent);
0131         Q_ASSERT(sourceIndex.isValid());
0132 
0133         const Akonadi::Collection &col = sourceIndex.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
0134         const auto attr = col.attribute<Akonadi::CollectionIdentificationAttribute>();
0135 
0136         // We filter the user folders because we insert person nodes for user folders.
0137         if ((attr && attr->collectionNamespace().startsWith("usertoplevel")) || col.name().contains(QLatin1String("Other Users"))) {
0138             return false;
0139         }
0140         return true;
0141     }
0142 
0143     
0144 };
0145 
0146 class KalendarCollectionFilterProxyModel : public Akonadi::CollectionFilterProxyModel
0147 {
0148 public:
0149     explicit KalendarCollectionFilterProxyModel(QObject *parent = nullptr)
0150         : Akonadi::CollectionFilterProxyModel(parent)
0151     {
0152     }
0153 
0154 protected:
0155     bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override
0156     {
0157         const auto leftHasChildren = sourceModel()->hasChildren(source_left);
0158         const auto rightHasChildren = sourceModel()->hasChildren(source_right);
0159         if (leftHasChildren && !rightHasChildren) {
0160             return false;
0161         } else if (!leftHasChildren && rightHasChildren) {
0162             return true;
0163         }
0164 
0165         return Akonadi::CollectionFilterProxyModel::lessThan(source_left, source_right);
0166     }
0167 };
0168 
0169 Q_GLOBAL_STATIC(CalendarManager, calendarManagerGlobalInstance)
0170 
0171 CalendarManager *CalendarManager::instance()
0172 {
0173     return calendarManagerGlobalInstance;
0174 }
0175 
0176 CalendarManager::CalendarManager(QObject *parent)
0177     : QObject(parent)
0178     , m_calendar(nullptr)
0179 {
0180         qRegisterMetaType<Akonadi::CollectionFilterProxyModel *>(); 
0181 
0182     
0183     if (!Akonadi::Control::start()) {
0184         qApp->exit(-1);
0185         return;
0186     }
0187 
0188     qDebug() << "STARTING THE CALENDAR MANAGER";
0189 
0190     auto colorProxy = new ColorProxyModel(this);
0191     colorProxy->setObjectName(QStringLiteral("Show calendar colors"));
0192     colorProxy->setDynamicSortFilter(true);
0193   
0194     m_baseModel = colorProxy;
0195 
0196     // Hide collections that are not required
0197     auto collectionFilter = new CollectionFilter(this);
0198     collectionFilter->setSourceModel(colorProxy);
0199 
0200     m_calendar = QSharedPointer<Akonadi::ETMCalendar>::create(); // QSharedPointer
0201     setCollectionSelectionProxyModel(m_calendar->checkableProxyModel());
0202     connect(m_calendar->checkableProxyModel(), &KCheckableProxyModel::dataChanged, this, &CalendarManager::refreshEnabledTodoCollections);
0203 
0204     m_changer = m_calendar->incidenceChanger();
0205     m_changer->setHistoryEnabled(true);
0206     connect(m_changer->history(), &Akonadi::History::changed, this, &CalendarManager::undoRedoDataChanged);
0207 
0208     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0209     // mCollectionSelectionModelStateSaver = new Akonadi::ETMViewStateSaver(); // not a leak
0210     // KConfigGroup selectionGroup = config->group("GlobalCollectionSelection");
0211     // mCollectionSelectionModelStateSaver->setView(nullptr);
0212     // mCollectionSelectionModelStateSaver->setSelectionModel(m_calendar->checkableProxyModel()->selectionModel());
0213     // mCollectionSelectionModelStateSaver->restoreState(selectionGroup);
0214 
0215     m_allCalendars = new Akonadi::CollectionFilterProxyModel(this);
0216     m_allCalendars->setSourceModel(collectionFilter);
0217     m_allCalendars->setExcludeVirtualCollections(true);
0218 
0219     // Filter it by mimetype again, to only keep
0220     // Kolab / Inbox / Calendar
0221     m_eventMimeTypeFilterModel = new Akonadi::CollectionFilterProxyModel(this);
0222     m_eventMimeTypeFilterModel->setSourceModel(collectionFilter);
0223     m_eventMimeTypeFilterModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.event"));
0224 
0225     // text/calendar mimetype includes todo cals
0226     // m_todoMimeTypeFilterModel = new Akonadi::CollectionFilterProxyModel(this);
0227     // m_todoMimeTypeFilterModel->setSourceModel(collectionFilter);
0228     // m_todoMimeTypeFilterModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0229     // m_todoMimeTypeFilterModel->setExcludeVirtualCollections(true);
0230 
0231     // Filter by access rights
0232     m_allCollectionsRightsFilterModel = new Akonadi::EntityRightsFilterModel(this);
0233     m_allCollectionsRightsFilterModel->setAccessRights(Collection::CanCreateItem);
0234     m_allCollectionsRightsFilterModel->setSourceModel(collectionFilter);
0235 
0236     m_eventRightsFilterModel = new Akonadi::EntityRightsFilterModel(this);
0237     m_eventRightsFilterModel->setAccessRights(Collection::CanCreateItem);
0238     m_eventRightsFilterModel->setSourceModel(m_eventMimeTypeFilterModel);
0239 
0240     // m_todoRightsFilterModel = new Akonadi::EntityRightsFilterModel(this);
0241     // m_todoRightsFilterModel->setAccessRights(Collection::CanCreateItem);
0242     // m_todoRightsFilterModel->setSourceModel(m_todoMimeTypeFilterModel);
0243 
0244     // Use our custom class to order them properly
0245     m_selectableCollectionsModel = new KalendarCollectionFilterProxyModel(this);
0246     m_selectableCollectionsModel->setSourceModel(m_allCollectionsRightsFilterModel);
0247     m_selectableCollectionsModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.event"));
0248     m_selectableCollectionsModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0249     m_selectableCollectionsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0250     m_selectableCollectionsModel->sort(0, Qt::AscendingOrder);
0251 
0252     m_selectableEventCollectionsModel = new KalendarCollectionFilterProxyModel(this);
0253     m_selectableEventCollectionsModel->setSourceModel(m_eventRightsFilterModel);
0254     m_selectableEventCollectionsModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.event"));
0255     m_selectableEventCollectionsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0256     m_selectableEventCollectionsModel->sort(0, Qt::AscendingOrder);
0257 
0258     m_selectableTodoCollectionsModel = new KalendarCollectionFilterProxyModel(this);
0259     m_selectableTodoCollectionsModel->setSourceModel(m_todoRightsFilterModel);
0260     m_selectableTodoCollectionsModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0261     m_selectableTodoCollectionsModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0262     m_selectableTodoCollectionsModel->sort(0, Qt::AscendingOrder);
0263 
0264     // Model for todo via collection picker
0265     m_todoViewCollectionModel = new KalendarCollectionFilterProxyModel(this);
0266     m_todoViewCollectionModel->setSourceModel(collectionFilter);
0267     m_todoViewCollectionModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0268     m_todoViewCollectionModel->setExcludeVirtualCollections(true);
0269     m_todoViewCollectionModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0270     m_todoViewCollectionModel->sort(0, Qt::AscendingOrder);
0271 
0272     // Model for the mainDrawer
0273     m_viewCollectionModel = new KalendarCollectionFilterProxyModel(this);
0274     m_viewCollectionModel->setSourceModel(collectionFilter);
0275     m_viewCollectionModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.event"));
0276     m_viewCollectionModel->addMimeTypeFilter(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0277     m_viewCollectionModel->setExcludeVirtualCollections(true);
0278     m_viewCollectionModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0279     m_viewCollectionModel->sort(0, Qt::AscendingOrder);
0280 
0281     m_flatCollectionTreeModel = new KDescendantsProxyModel(this);
0282     m_flatCollectionTreeModel->setSourceModel(m_viewCollectionModel);
0283     m_flatCollectionTreeModel->setExpandsByDefault(true);
0284 
0285     auto refreshColors = [=]() {
0286         for (auto i = 0; i < m_flatCollectionTreeModel->rowCount(); i++) {
0287             auto idx = m_flatCollectionTreeModel->index(i, 0, {});
0288 #if AKONADI_VERSION < QT_VERSION_CHECK(5, 20, 41)
0289             colorProxy->getCollectionColor(CalendarSupport::collectionFromIndex(idx));
0290 #else
0291             colorProxy->getCollectionColor(Akonadi::CollectionUtils::fromIndex(idx));
0292 #endif
0293         }
0294     };
0295     connect(m_flatCollectionTreeModel, &QSortFilterProxyModel::rowsInserted, this, refreshColors);
0296 
0297     KConfigGroup rColorsConfig(config, QStringLiteral("Resources Colors"));
0298     m_colorWatcher = KConfigWatcher::create(config);
0299     connect(m_colorWatcher.data(), &KConfigWatcher::configChanged, this, &CalendarManager::collectionColorsChanged);
0300 
0301     connect(m_calendar.data(), &Akonadi::ETMCalendar::calendarChanged, this, &CalendarManager::calendarChanged);
0302 }
0303 
0304 CalendarManager::~CalendarManager()
0305 {
0306     save();
0307     // delete mCollectionSelectionModelStateSaver;
0308 }
0309 
0310 void CalendarManager::save()
0311 {
0312     // Akonadi::ETMViewStateSaver treeStateSaver;
0313     // KSharedConfig::Ptr config = KSharedConfig::openConfig();
0314     // KConfigGroup group = config->group("GlobalCollectionSelection");
0315     // treeStateSaver.setView(nullptr);
0316     // treeStateSaver.setSelectionModel(m_calendar->checkableProxyModel()->selectionModel());
0317     // treeStateSaver.saveState(group);
0318     // 
0319     // config->sync();
0320 }
0321 
0322 void CalendarManager::delayedInit()
0323 {
0324     Q_EMIT loadingChanged();
0325 }
0326 
0327 QAbstractProxyModel *CalendarManager::collections()
0328 {
0329     return static_cast<QAbstractProxyModel *>(m_flatCollectionTreeModel->sourceModel());
0330 }
0331 
0332 QAbstractItemModel *CalendarManager::todoCollections()
0333 {
0334     return m_todoViewCollectionModel;
0335 }
0336 
0337 QAbstractItemModel *CalendarManager::viewCollections()
0338 {
0339     return m_viewCollectionModel;
0340 }
0341 
0342 QVector<qint64> CalendarManager::enabledTodoCollections()
0343 {
0344     return m_enabledTodoCollections;
0345 }
0346 
0347 void CalendarManager::refreshEnabledTodoCollections()
0348 {
0349     m_enabledTodoCollections.clear();
0350     const auto selectedIndexes = m_calendar->checkableProxyModel()->selectionModel()->selectedIndexes();
0351     for (auto selectedIndex : selectedIndexes) {
0352         auto collection = selectedIndex.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
0353         if (collection.contentMimeTypes().contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))) {
0354             m_enabledTodoCollections.append(collection.id());
0355         }
0356     }
0357 
0358     Q_EMIT enabledTodoCollectionsChanged();
0359 }
0360 
0361 bool CalendarManager::loading() const
0362 {
0363     return m_calendar->isLoading();
0364 }
0365 
0366 void CalendarManager::setCollectionSelectionProxyModel(KCheckableProxyModel *m)
0367 {
0368     if (m_selectionProxyModel == m) {
0369         return;
0370     }
0371 
0372     m_selectionProxyModel = m;
0373     if (!m_selectionProxyModel) {
0374         return;
0375     }
0376 
0377     new NewCalendarChecker(m);
0378     m_baseModel->setSourceModel(m_selectionProxyModel);
0379 }
0380 
0381 KCheckableProxyModel *CalendarManager::collectionSelectionProxyModel() const
0382 {
0383     return m_selectionProxyModel;
0384 }
0385 
0386 Akonadi::ETMCalendar::Ptr CalendarManager::calendar() const
0387 {
0388     return m_calendar;
0389 }
0390 
0391 Akonadi::IncidenceChanger *CalendarManager::incidenceChanger() const
0392 {
0393     return m_changer;
0394 }
0395 
0396 Akonadi::CollectionFilterProxyModel *CalendarManager::allCalendars()
0397 {
0398     return m_allCalendars;
0399 }
0400 
0401 Akonadi::CollectionFilterProxyModel *CalendarManager::selectableCalendars() const
0402 {
0403     return m_selectableCollectionsModel;
0404 }
0405 
0406 Akonadi::CollectionFilterProxyModel *CalendarManager::selectableEventCalendars() const
0407 {
0408     return m_selectableEventCollectionsModel;
0409 }
0410 
0411 Akonadi::CollectionFilterProxyModel *CalendarManager::selectableTodoCalendars() const
0412 {
0413     return m_selectableTodoCollectionsModel;
0414 }
0415 
0416 qint64 CalendarManager::defaultCalendarId(IncidenceWrapper *incidenceWrapper)
0417 {
0418     // Checks if default collection accepts this type of incidence
0419     auto mimeType = incidenceWrapper->incidencePtr()->mimeType();
0420     Akonadi::Collection collection = m_calendar->collection(-1);
0421     bool supportsMimeType = collection.contentMimeTypes().contains(mimeType) || mimeType == QLatin1String("");
0422     bool hasRights = collection.rights() & Akonadi::Collection::CanCreateItem;
0423     if (collection.isValid() && supportsMimeType && hasRights) {
0424         return collection.id();
0425     }
0426 
0427     // Should add last used collection by mimetype somewhere.
0428 
0429     // Searches for first collection that will accept this incidence
0430     for (int i = 0; i < m_allCalendars->rowCount(); i++) {
0431         QModelIndex idx = m_allCalendars->index(i, 0);
0432         collection = idx.data(Akonadi::EntityTreeModel::Roles::CollectionRole).value<Akonadi::Collection>();
0433         supportsMimeType = collection.contentMimeTypes().contains(mimeType) || mimeType == QLatin1String("");
0434         hasRights = collection.rights() & Akonadi::Collection::CanCreateItem;
0435         if (collection.isValid() && supportsMimeType && hasRights) {
0436             return collection.id();
0437         }
0438     }
0439 
0440     return -1;
0441 }
0442 
0443 int CalendarManager::getCalendarSelectableIndex(IncidenceWrapper *incidenceWrapper)
0444 {
0445     auto model = new KDescendantsProxyModel;
0446 
0447     switch (incidenceWrapper->incidencePtr()->type()) {
0448     default:
0449     case (KCalendarCore::IncidenceBase::TypeEvent): {
0450         model->setSourceModel(m_selectableEventCollectionsModel);
0451         break;
0452     }
0453     case (KCalendarCore::IncidenceBase::TypeTodo): {
0454         model->setSourceModel(m_selectableTodoCollectionsModel);
0455         break;
0456     }
0457     }
0458 
0459     for (int i = 0; i < model->rowCount(); i++) {
0460         QModelIndex idx = model->index(i, 0);
0461         QVariant data = idx.data(Akonadi::EntityTreeModel::Roles::CollectionIdRole);
0462 
0463         if (data == incidenceWrapper->collectionId())
0464             return i;
0465     }
0466 
0467     return 0;
0468 }
0469 
0470 QVariant CalendarManager::getIncidenceSubclassed(KCalendarCore::Incidence::Ptr incidencePtr)
0471 {
0472     switch (incidencePtr->type()) {
0473     case (KCalendarCore::IncidenceBase::TypeEvent):
0474         return QVariant::fromValue(m_calendar->event(incidencePtr->instanceIdentifier()));
0475         break;
0476     case (KCalendarCore::IncidenceBase::TypeTodo):
0477         return QVariant::fromValue(m_calendar->todo(incidencePtr->instanceIdentifier()));
0478         break;
0479     case (KCalendarCore::IncidenceBase::TypeJournal):
0480         return QVariant::fromValue(m_calendar->journal(incidencePtr->instanceIdentifier()));
0481         break;
0482     default:
0483         return QVariant::fromValue(incidencePtr);
0484         break;
0485     }
0486 }
0487 
0488 QVariantMap CalendarManager::undoRedoData()
0489 {
0490     return QVariantMap{
0491         {QStringLiteral("undoAvailable"), m_changer->history()->undoAvailable()},
0492         {QStringLiteral("redoAvailable"), m_changer->history()->redoAvailable()},
0493         {QStringLiteral("nextUndoDescription"), m_changer->history()->nextUndoDescription()},
0494         {QStringLiteral("nextRedoDescription"), m_changer->history()->nextRedoDescription()},
0495     };
0496 }
0497 
0498 Akonadi::Item CalendarManager::incidenceItem(KCalendarCore::Incidence::Ptr incidence) const
0499 {
0500     return m_calendar->item(incidence);
0501 }
0502 
0503 Akonadi::Item CalendarManager::incidenceItem(const QString &uid) const
0504 {
0505     return incidenceItem(m_calendar->incidence(uid));
0506 }
0507 
0508 KCalendarCore::Incidence::List CalendarManager::childIncidences(const QString &uid) const
0509 {
0510     return m_calendar->childIncidences(uid);
0511 }
0512 
0513 void CalendarManager::addIncidence(IncidenceWrapper *incidenceWrapper)
0514 {
0515     Akonadi::Collection collection(incidenceWrapper->collectionId());
0516 
0517     switch (incidenceWrapper->incidencePtr()->type()) {
0518     case (KCalendarCore::IncidenceBase::TypeEvent): {
0519 
0520         KCalendarCore::Event::Ptr event = incidenceWrapper->incidencePtr().staticCast<KCalendarCore::Event>();
0521         auto res = m_changer->createIncidence(event, collection);
0522         qDebug() << "TRYING TO ADD AN EVENT" << incidenceWrapper->summary() << incidenceWrapper->collectionId() << res << event->description();
0523 
0524         break;
0525     }
0526     case (KCalendarCore::IncidenceBase::TypeTodo): {
0527         KCalendarCore::Todo::Ptr todo = incidenceWrapper->incidencePtr().staticCast<KCalendarCore::Todo>();
0528         m_changer->createIncidence(todo, collection);
0529         break;
0530     }
0531     default:
0532         m_changer->createIncidence(KCalendarCore::Incidence::Ptr(incidenceWrapper->incidencePtr()->clone()), collection);
0533         break;
0534     }
0535     // This will fritz if you don't choose a valid *calendar*
0536 }
0537 
0538 // Replicates IncidenceDialogPrivate::save
0539 void CalendarManager::editIncidence(IncidenceWrapper *incidenceWrapper)
0540 {
0541     // We need to use the incidenceChanger manually to get the change recorded in the history
0542     // For undo/redo to work properly we need to change the ownership of the incidence pointers
0543     KCalendarCore::Incidence::Ptr changedIncidence(incidenceWrapper->incidencePtr()->clone());
0544     KCalendarCore::Incidence::Ptr originalPayload(incidenceWrapper->originalIncidencePtr()->clone());
0545 
0546     Akonadi::Item modifiedItem = m_calendar->item(changedIncidence->instanceIdentifier());
0547     modifiedItem.setPayload<KCalendarCore::Incidence::Ptr>(changedIncidence);
0548 
0549     m_changer->modifyIncidence(modifiedItem, originalPayload);
0550 
0551     if (!incidenceWrapper->collectionId() || incidenceWrapper->collectionId() < 0 || modifiedItem.parentCollection().id() == incidenceWrapper->collectionId()) {
0552         return;
0553     }
0554 
0555     changeIncidenceCollection(modifiedItem, incidenceWrapper->collectionId());
0556 }
0557 
0558 void CalendarManager::updateIncidenceDates(IncidenceWrapper *incidenceWrapper, int startOffset, int endOffset, int occurrences, const QDateTime &occurrenceDate)
0559 { // start and end offsets are in msecs
0560 
0561     Akonadi::Item item = m_calendar->item(incidenceWrapper->incidencePtr());
0562     item.setPayload(incidenceWrapper->incidencePtr());
0563 
0564     auto setNewDates = [&](KCalendarCore::Incidence::Ptr incidence) {
0565         if (incidence->type() == KCalendarCore::Incidence::TypeTodo) {
0566             // For to-dos endOffset is ignored because it will always be == to startOffset because we only
0567             // support moving to-dos, not resizing them. There are no multi-day to-dos.
0568             // Lets just call it offset to reduce confusion.
0569             const int offset = startOffset;
0570 
0571             KCalendarCore::Todo::Ptr todo = incidence.staticCast<KCalendarCore::Todo>();
0572             QDateTime due = todo->dtDue();
0573             QDateTime start = todo->dtStart();
0574             if (due.isValid()) { // Due has priority over start.
0575                 // We will only move the due date, unlike events where we move both.
0576                 due = due.addMSecs(offset);
0577                 todo->setDtDue(due);
0578 
0579                 if (start.isValid() && start > due) {
0580                     // Start can't be bigger than due.
0581                     todo->setDtStart(due);
0582                 }
0583             } else if (start.isValid()) {
0584                 // So we're displaying a to-do that doesn't have due date, only start...
0585                 start = start.addMSecs(offset);
0586                 todo->setDtStart(start);
0587             } else {
0588                 // This never happens
0589                 // qCWarning(CALENDARVIEW_LOG) << "Move what? uid:" << todo->uid() << "; summary=" << todo->summary();
0590             }
0591         } else {
0592             incidence->setDtStart(incidence->dtStart().addMSecs(startOffset));
0593             if (incidence->type() == KCalendarCore::Incidence::TypeEvent) {
0594                 KCalendarCore::Event::Ptr event = incidence.staticCast<KCalendarCore::Event>();
0595                 event->setDtEnd(event->dtEnd().addMSecs(endOffset));
0596             }
0597         }
0598     };
0599 
0600     if (incidenceWrapper->incidencePtr()->recurs()) {
0601         switch (occurrences) {
0602         case KCalUtils::RecurrenceActions::AllOccurrences: {
0603             // All occurrences
0604             KCalendarCore::Incidence::Ptr oldIncidence(incidenceWrapper->incidencePtr()->clone());
0605             setNewDates(incidenceWrapper->incidencePtr());
0606             qDebug() << incidenceWrapper->incidenceStart();
0607             m_changer->modifyIncidence(item, oldIncidence);
0608             break;
0609         }
0610         case KCalUtils::RecurrenceActions::SelectedOccurrence: // Just this occurrence
0611         case KCalUtils::RecurrenceActions::FutureOccurrences: { // All future occurrences
0612             const bool thisAndFuture = (occurrences == KCalUtils::RecurrenceActions::FutureOccurrences);
0613             auto tzedOccurrenceDate = occurrenceDate.toTimeZone(incidenceWrapper->incidenceStart().timeZone());
0614             KCalendarCore::Incidence::Ptr newIncidence(
0615                 KCalendarCore::Calendar::createException(incidenceWrapper->incidencePtr(), tzedOccurrenceDate, thisAndFuture));
0616 
0617             if (newIncidence) {
0618                 m_changer->startAtomicOperation(i18n("Move occurrence(s)"));
0619                 setNewDates(newIncidence);
0620                 m_changer->createIncidence(newIncidence, m_calendar->collection(incidenceWrapper->collectionId()));
0621                 m_changer->endAtomicOperation();
0622             } else {
0623                 qDebug() << i18n("Unable to add the exception item to the calendar. No change will be done.");
0624             }
0625             break;
0626         }
0627         }
0628     } else { // Doesn't recur
0629         KCalendarCore::Incidence::Ptr oldIncidence(incidenceWrapper->incidencePtr()->clone());
0630         setNewDates(incidenceWrapper->incidencePtr());
0631         m_changer->modifyIncidence(item, oldIncidence);
0632     }
0633 
0634     Q_EMIT updateIncidenceDatesCompleted();
0635 }
0636 
0637 bool CalendarManager::hasChildren(KCalendarCore::Incidence::Ptr incidence)
0638 {
0639     return !m_calendar->childIncidences(incidence->uid()).isEmpty();
0640 }
0641 
0642 void CalendarManager::deleteAllChildren(KCalendarCore::Incidence::Ptr incidence)
0643 {
0644     const auto allChildren = m_calendar->childIncidences(incidence->uid());
0645 
0646     for (const auto &child : allChildren) {
0647         if (!m_calendar->childIncidences(child->uid()).isEmpty()) {
0648             deleteAllChildren(child);
0649         }
0650     }
0651 
0652     for (const auto &child : allChildren) {
0653         m_calendar->deleteIncidence(child);
0654     }
0655 }
0656 
0657 void CalendarManager::deleteIncidence(KCalendarCore::Incidence::Ptr incidence, bool deleteChildren)
0658 {
0659     const auto directChildren = m_calendar->childIncidences(incidence->uid());
0660 
0661     if (!directChildren.isEmpty()) {
0662         if (deleteChildren) {
0663             m_changer->startAtomicOperation(i18n("Delete task and its sub-tasks"));
0664             deleteAllChildren(incidence);
0665         } else {
0666             m_changer->startAtomicOperation(i18n("Delete task and make sub-tasks independent"));
0667             for (const auto &child : directChildren) {
0668                 const auto instances = m_calendar->instances(child);
0669                 for (const auto &instance : instances) {
0670                     KCalendarCore::Incidence::Ptr oldInstance(instance->clone());
0671                     instance->setRelatedTo(QString());
0672                     m_changer->modifyIncidence(m_calendar->item(instance), oldInstance);
0673                 }
0674 
0675                 KCalendarCore::Incidence::Ptr oldInc(child->clone());
0676                 child->setRelatedTo(QString());
0677                 m_changer->modifyIncidence(m_calendar->item(child), oldInc);
0678             }
0679         }
0680 
0681         m_calendar->deleteIncidence(incidence);
0682         m_changer->endAtomicOperation();
0683         return;
0684     }
0685 
0686     m_calendar->deleteIncidence(incidence);
0687 }
0688 
0689 void CalendarManager::changeIncidenceCollection(KCalendarCore::Incidence::Ptr incidence, qint64 collectionId)
0690 {
0691     KCalendarCore::Incidence::Ptr incidenceClone(incidence->clone());
0692     Akonadi::Item modifiedItem = m_calendar->item(incidence->instanceIdentifier());
0693     modifiedItem.setPayload<KCalendarCore::Incidence::Ptr>(incidenceClone);
0694 
0695     if (modifiedItem.parentCollection().id() != collectionId) {
0696         changeIncidenceCollection(modifiedItem, collectionId);
0697     }
0698 }
0699 
0700 void CalendarManager::changeIncidenceCollection(Akonadi::Item item, qint64 collectionId)
0701 {
0702     if (item.parentCollection().id() == collectionId) {
0703         return;
0704     }
0705 
0706     Q_ASSERT(item.hasPayload<KCalendarCore::Incidence::Ptr>());
0707 
0708     Akonadi::Collection newCollection(collectionId);
0709     item.setParentCollection(newCollection);
0710 
0711     auto job = new Akonadi::ItemMoveJob(item, newCollection);
0712     // Add some type of check here?
0713     connect(job, &KJob::result, job, [=]() {
0714         qDebug() << job->error();
0715 
0716         if (!job->error()) {
0717             const auto allChildren = m_calendar->childIncidences(item.id());
0718             for (const auto &child : allChildren) {
0719                 changeIncidenceCollection(m_calendar->item(child), collectionId);
0720             }
0721 
0722             auto parent = item.payload<KCalendarCore::Incidence::Ptr>()->relatedTo();
0723             if (!parent.isEmpty()) {
0724                 changeIncidenceCollection(m_calendar->item(parent), collectionId);
0725             }
0726         }
0727     });
0728 }
0729 
0730 QVariantMap CalendarManager::getCollectionDetails(QVariant collectionId)
0731 {
0732     QVariantMap collectionDetails;
0733     Akonadi::Collection collection = m_calendar->collection(collectionId.toInt());
0734     bool isFiltered = false;
0735     int allCalendarsRow = 0;
0736 
0737     for (int i = 0; i < m_allCalendars->rowCount(); i++) {
0738         if (m_allCalendars->data(m_allCalendars->index(i, 0), Akonadi::EntityTreeModel::CollectionIdRole).toInt() == collectionId) {
0739             isFiltered = !m_allCalendars->data(m_allCalendars->index(i, 0), Qt::CheckStateRole).toBool();
0740             allCalendarsRow = i;
0741             break;
0742         }
0743     }
0744 
0745     collectionDetails[QLatin1String("id")] = collection.id();
0746     collectionDetails[QLatin1String("name")] = collection.name();
0747     collectionDetails[QLatin1String("displayName")] = collection.displayName();
0748     collectionDetails[QLatin1String("color")] = m_baseModel->color(collection.id());    collectionDetails[QLatin1String("count")] = collection.statistics().count();
0749     collectionDetails[QLatin1String("isResource")] = Akonadi::CollectionUtils::isResource(collection);
0750     collectionDetails[QLatin1String("resource")] = collection.resource();
0751     collectionDetails[QLatin1String("readOnly")] = collection.rights().testFlag(Collection::ReadOnly);
0752     collectionDetails[QLatin1String("canChange")] = collection.rights().testFlag(Collection::CanChangeCollection);
0753     collectionDetails[QLatin1String("canCreate")] = collection.rights().testFlag(Collection::CanCreateCollection);
0754     collectionDetails[QLatin1String("canDelete")] =
0755         collection.rights().testFlag(Collection::CanDeleteCollection) && !Akonadi::CollectionUtils::isResource(collection);
0756     collectionDetails[QLatin1String("isFiltered")] = isFiltered;
0757     collectionDetails[QLatin1String("allCalendarsRow")] = allCalendarsRow;
0758 
0759     return collectionDetails;
0760 }
0761 
0762 void CalendarManager::setCollectionColor(qint64 collectionId, const QColor &color)
0763 {
0764     auto collection = m_calendar->collection(collectionId);
0765     auto colorAttr = collection.attribute<Akonadi::CollectionColorAttribute>(Akonadi::Collection::AddIfMissing);
0766     colorAttr->setColor(color);
0767     auto modifyJob = new Akonadi::CollectionModifyJob(collection);
0768     connect(modifyJob, &Akonadi::CollectionModifyJob::result, this, [this, collectionId, color](KJob *job) {
0769         if (job->error()) {
0770             qWarning() << "Error occurred modifying collection color: " << job->errorString();
0771         } else {
0772              m_baseModel->setColor(collectionId, color);
0773         }
0774     });
0775 }
0776 
0777 void CalendarManager::undoAction()
0778 {
0779     m_changer->history()->undo();
0780 }
0781 
0782 void CalendarManager::redoAction()
0783 {
0784     m_changer->history()->redo();
0785 }
0786 
0787 void CalendarManager::updateAllCollections()
0788 {
0789     for (int i = 0; i < collections()->rowCount(); i++) {
0790         auto collection = collections()->data(collections()->index(i, 0), Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
0791         Akonadi::AgentManager::self()->synchronizeCollection(collection, true);
0792     }
0793 }
0794 
0795 void CalendarManager::updateCollection(qint64 collectionId)
0796 {
0797     auto collection = m_calendar->collection(collectionId);
0798     Akonadi::AgentManager::self()->synchronizeCollection(collection, false);
0799 }
0800 
0801 void CalendarManager::deleteCollection(qint64 collectionId)
0802 {
0803     auto collection = m_calendar->collection(collectionId);
0804     const bool isTopLevel = collection.parentCollection() == Akonadi::Collection::root();
0805 
0806     if (!isTopLevel) {
0807         // deletes contents
0808         auto job = new Akonadi::CollectionDeleteJob(collection, this);
0809         connect(job, &Akonadi::CollectionDeleteJob::result, this, [](KJob *job) {
0810             if (job->error()) {
0811                 qWarning() << "Error occurred deleting collection: " << job->errorString();
0812             }
0813         });
0814         return;
0815     }
0816     // deletes the agent, not the contents
0817     const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource());
0818     if (instance.isValid()) {
0819         Akonadi::AgentManager::self()->removeInstance(instance);
0820     }
0821 }
0822 
0823 void CalendarManager::editCollection(qint64 collectionId)
0824 { // TODO: Reimplement this dialog in QML
0825     // auto collection = m_calendar->collection(collectionId);
0826     // QPointer<Akonadi::CollectionPropertiesDialog> dlg = new Akonadi::CollectionPropertiesDialog(collection);
0827     // dlg->setWindowTitle(i18nc("@title:window", "Properties of Calendar %1", collection.name()));
0828     // dlg->show();
0829 }
0830 
0831 void CalendarManager::toggleCollection(qint64 collectionId)
0832 {
0833     const auto matches = m_calendar->checkableProxyModel()->match(m_calendar->checkableProxyModel()->index(0, 0),
0834                                                                   Akonadi::EntityTreeModel::CollectionIdRole,
0835                                                                   collectionId,
0836                                                                   1,
0837                                                                   Qt::MatchExactly | Qt::MatchWrap | Qt::MatchRecursive);
0838     if (matches.count() > 0) {
0839         const auto collectionIndex = matches.first();
0840         const auto collectionChecked = collectionIndex.data(Qt::CheckStateRole).toInt() == Qt::Checked;
0841         const auto checkStateToSet = collectionChecked ? Qt::Unchecked : Qt::Checked;
0842         m_calendar->checkableProxyModel()->setData(collectionIndex, checkStateToSet, Qt::CheckStateRole);
0843     }
0844 }
0845 
0846 #ifndef UNITY_CMAKE_SUPPORT
0847 Q_DECLARE_METATYPE(KCalendarCore::Incidence::Ptr)
0848 #endif
0849 
0850 #include "calendarmanager.moc"
0851 #include "moc_calendarmanager.cpp"
0852