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