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